普通视图

发现新文章,点击刷新页面。
昨天以前伪斜杠青年

Parallels Desktop 实时回收 Linux 虚拟机磁盘不生效问题

作者 Mosaic-C
2026年4月17日 20:16

在 MacOS 上从 vmware 换到 PD 体验好了不是一点半点,但是操作略有不同,在最喜欢的 elementary os 支持了 arm 后就折腾了一个纯血 Linux,毕竟很多时候磁盘相关底层操作都需要 Linux。

在 Linux 用了段时间后发现占用越发的大,想起以前类似 vmware 也需要刻意执行命令去瘦身。想着今年各种国内Ai 进步的消息,试试了国内 DS,百度之类没有一个给我有用答案,都是重复废话,切到 Gemini 后直接给了我答案。

虚拟机内执行 trim 命令,速度很快:

# sudo fstrim -av
/boot/efi: 245.4 MiB (257298432 bytes) trimmed on /dev/sda1
/: 50.1 GiB (53832556544 bytes) trimmed on /dev/mapper/data-root

看到类似这样的输出后,即可关闭虚拟机,手动点击回收磁盘空间,或者虚拟机设置 -> 通用 -> 关机时自动回收空间。

立竿见影!国内 AI 还有很长路走,完全不值得浪费时间。

以上。

无主之地2 MacOS 1.8.5 天邈汉化补丁与一键安装脚本

作者 Mosaic-C
2026年4月2日 21:29

背景

M4 可以流畅玩无主之地3,打完了全部 3 的内容包括 DLC 后,想起来大学时候玩的 无主之地2 ,Steam 版本支持 Mac,4k 画质玩着也没问题,最大的问题是汉化,本想将就繁体,但 Mac 版不支持繁体。

折腾

搜索后自然第一建议是某乎 Mr.One 的一篇文章,不包括莉莉丝,经过在评论区寻找,有位叫 负负得正 的大哥提供了汉化补丁,以为一切正常,直到打了两章莉莉丝才意识到没有语音

经过对比原版文件尝试得知 DefaultEngine.ini 中配置与 MacOS 版本不匹配,缺少部分参数导致语音无法加载、字幕无法显示的问题,曾经我一度认为 莉莉丝 DLC 中的 NPC 都是哑巴。。。目前莉莉丝 DLC 已经通关,测试通过。

而最大的问题其实是 MacOS 的汉化补丁安装,一开始我是使用 BeyondCompare 去对比拷贝,实在是麻烦,后来为了测试莉莉丝 DLC 语音问题需要重置游戏文件,便安排了一键汉化脚本。

使用方法

下载整合包,解压,随后打开 MacOS 终端将路径 cd 到解压后的补丁包中,修改 deploy.shgameSourcelangSource 两个变量上的文件夹位置,这个每个人不同,无法自动处理。

授权并执行即可:

chmod +x ./deploy.sh && ./deploy.sh

资源下载

无主之地2 Mac 汉化补丁 整合版下载

链接: https://pan.baidu.com/s/1kee-8OtGH7mBp1JJ3ZziKg 提取码: 9jy2

有问题请留言,以上!

抛弃 Zeroclaw 选择 AstrBot,简单易用,不过前提是不在意内存

作者 Mosaic-C
2026年3月27日 22:08

在折腾了两周多的版本迭代后,Zeroclaw 的最新版本直接像抛弃了 ollama 一样,所有 tool 请求一概拒绝,ollama 无法识别从而500。依旧理解最透彻的还是那个 v0.3.2,但正如这么多天修复的无数 bug,那仅仅只能作为体验。

于是,不再追求所谓低内存占用,转向 AstrBot,简单使用后便被其简单易配置的 Web 管理平台圈粉,似乎不需要我再去对着源码看各种配置,具备完整日志,自带暴露 QQ 端口,纯网页端完成模型与渠道配置。

而部署也更简单,Docker 本身使用 python:3.12-slim 作为基础,也不用再去刻意加入 Python 环境,正如标题所说,内存这方面我 8G 的树莓派自然不是太在意,但不同人不同需求:

部署参考官方文档,一个文件即可解决,配置下网络以及挂载即可:https://docs.astrbot.app/deploy/astrbot/docker.html
其他,暂时没有可说的,玩几天再来补,希望是全功能,不磨叽拐弯的,善解人意的,毕竟占用了这么大内存。

DockerFile

考虑到都有自定义镜像的需求,遂提供自动构建脚本:

资源地址

https://github.com/Anr-C/AstrBot_DockerFile

特性:每次执行 deploy.sh 将以官方最新 release 进行构建,如无更新则静默。

PS:AstrBot 非其他单二进制式 claw,且完全依赖源码,所以使用覆盖 DockerFile 的形式,不够优雅,但有效。其中 .dockerignore 也想过去排除一些无用文件,但考虑到文件不大,与官方保持同步,不进行修改。

注意:AstrBot 的 Web 面板存在于数据目录中的 dist 文件夹,虽然突兀但非多余,请勿删除。这也是和其他 claw 不同的地方。

后续

它是目前唯一能在树莓派的低性能下体验到的,流程迅速、理解正确、结果完整的,会使用代码编写/技能优先,而不是使用 web-search/web-fetch 偷懒/假装完成一整套 环境安装代码编写任务执行的 claw。

我给予极高的体验评价。

以上。

目前处于 BUG 天梯状态的低效率 Claw 还不值得作为 Gatway 使用,需及时止损。

作者 Mosaic-C
2026年3月16日 14:45

从繁杂的 Openclaw 切换到简单的 Picoclaw,再到工具看上去更高级的 Zeroclaw 的两周目总结。

闲言

Claw 是一个开放性极强的“手”,虽然有废弃 Mac 但最合适且安全的体验用法,应是 Docker,其具备理解并执行一切 Linux 开源工具的能力,在 Docker 中可以将一切权限都给他。只可惜,理想太美好,目前的进度在 30%,基于本地模型脑子不够用的情况,换成高纯度付费大模型也许能到 60%,只是随便 十几M 的 token,不值得如此尝试。

浅谈

基于树莓派 4B 8G Docker 的各种 claw 作为 gatway,Mac mini M4 32G 本地 Omlx Qwen3.5 9B-MLX-4bit 做的统一尝试:

Openclaw:知道手在哪儿,但手得弯弯绕绕把磁盘拉爆,最后极慢的给了一个结果,源码并不好改,复杂冗余,这货也不是为了 gatway 设计的,部署也不简单。

Picoclaw:知道有手,但手不记得上次手做了什么(上下文未超过)。每次都需要 call tool 、try error cmd、read skill.md 、writecode,test,try,走完这一整套流程,最后给一个错误的杂交结果。优点是日志详细,每一步都很清楚,缺点是工具链调用慢。

Zeroclaw:一直都很聪明的样子,知道用什么工具最优,执行速度最快,但结果出不来,上下文容易超限,max_tool_iterations 容易超过 100,最终静默无后文,问题应出在 try 这个过程死循环。而问题定位需要日志,缺点就是日志不够详尽,RUST_LOG=info 也不够。

总:任意 claw 都存在一个毛病,错误循环太多时,max_tool_iterations 超限。所以要求 claw 自身工具调用准确,快速,这与模型大脑无关。

在信任网络无聊的安全盾极大的阻止了可玩性,功能解禁涉及源码,所以不建议使用官方 Docker 镜像,功能少,限制多,自行构建更合适。频繁日更大大增加了不稳定性,建议停在能用的版本,按月更新。

其实最终形态应是 Zeroclaw 的工具选取,Picoclaw 的工具执行,以及他们共同的低内存占用。

这种通用 Agent 在 26 年初被资本拉入普通人世界,还是太早了一些。好在,Docker 只是需要删掉容器而已,折腾存在乐趣,就是废时间。

后续

一夜过去各 claw 均罢工 tool 调用,tool 未经 claw 拦截直接变成了 Direct Answer,经过排查定位在于 Omlx tools 协议支持存在问题,于是由 Omlx 切换到 Ollama,一改以前对 Ollama 的看法,相比较以速度起家的 Omlx,协议更通用的 Ollama 更合适,测试下来,速度也快。

最终移除了其他 claw ,选择了 Zeroclaw ,胜在响应快,工具调用不磨叽。能看到结果后的 Zeroclaw 有了新的槽点,稳定性欠佳,比如: deamon channel 会莫名其妙断开、channel restart 会失败、与模型的 tool 交互偶尔 500 Error。

Qwen3.5:9b 模型下,一轮长对话下来的耗时,仅供参考。对话内容为对此篇文章的爬取分析,这里就不贴了。

总而言之,言而总之,一切还只处于初期阶段,太勉强,则,及时止损。

一夜过去,又又又废了,推荐 Zeroclaw 停留在 v0.3.2 版本,新版本真是没有一个能用的,各种权限问题,路径问题,技能安全盾拦截问题。

水文一篇,以上。

过去的 2025 教会了我从流量品牌祛魅 – 关于消费的思考

作者 Mosaic-C
2026年1月7日 22:39

很多东西明显不合适,因为其是大牌/流量/宣传噱头而觉得其不可替代,而偏好选择,便有了祛魅。

本来也不想聊这事儿,只是最近把身边一切,从数码到日用全换了遍,才知一直活在被动选择中多累,原本可以更舒适。

手机从小米换到一加也几个月了,很好,很棒,用得很舒服,不用在意别人的评价。同一时间也从小米路由换到了中兴,内网满载很稳,发热很低,很安逸。数码这方面,以前总觉得用一套,追求整体协调性。切过来正如耳机只用索尼大法、电脑独爱 MacOS,iPad 只用苹果一样,混用也没觉得不妥,各设备选择符合自己需求的能接受的最好的,便不会有错。

那时候,小米的风评并没现在这么烂,走到今天这一步,原因很简单,一直以来小米只是能用,走高端了依旧只是能用,这就是核心矛盾。

此外还得感谢 AI 的存在,让触及很多认知之外的智商税变得轻松,比如在衣物材料方面。直白的或者大多数人都知道的例子 — “蕉内”。这个牌子火了几年我就用了几年,一直用“科技”冠以高价格,当然这市面上贴牌货大把,研创确实算得上独一档。

问题在于并不适合我,要么不够保暖,要么不够排汗散热,一直达不到他们宣传所说。一直以来是认为花的钱不够多,档次不够高,于是用 3 系的时候想着 5 系是不是会更有效。购入体验后看着那新一代变动几个点的成分配比,便可相差几百块…

直到最近到了寿命,准备再一次购入,想起现在有 AI ,将自身感受到的不适以及其成份配比交给 AI 方知其智商税之深厚,从而也通过 AI 分析也找到了更适合的成份贴身面料,品牌便不说了,毕竟每人不同,这几年类似 “蕉内” 的新生品牌也很多,选那些回购多的有真实评价的不会错的。

此外还有一众爆火的活力28、红卫等,何尝不是一种流量裹挟,用过便知道,也许反馈平平。众所周知,无功无过已是大善,太多被央妈揭开的品牌事故证明,大多数人只是在为营销买单。当然,那几年经济好,不用在这些上有所思考。

去看看那些朴实低调的,少看点广告推流,没有什么不可替代

水文一篇,以上! (略带愤怒)

从 GPT 换到 Google Gemini 的感受 – 无敌

作者 Mosaic-C
2025年12月16日 22:13

故事是这样的,最近有些技术上的新玩具,需要问 AI 来扩展知识和提速。而今天打开 GPT 提示了更新到了 GPT 5.2,其中与它对话是这样描述的:

于是晚上有点小需求,再次信它一次,前前后后纠错它,以及用它提供的方案尝试花了 1 小时。而我预估仅需 5 分钟,毕竟很多东西不是我不会只是我忘了,试到抓狂,转身打开 Google Gemini ,同样模糊描述仅纠正一次背景信息,5 分钟搞定。

结论:垃圾GPT ,我信他个鬼。

此前一直习惯 GPT ,但 GPT 自从 4B 后就开始了疯狂的胡编乱造和臆想虚构,而且在人为纠正后上下记忆错乱无章,早已痛苦不已。

听闻 Google Gemini 实力之强,今日一试,百闻不如一用,能清晰快速猜测用户需求,直击问题痛点,无敌好用。

时间就是💰,珍爱生命,远离垃圾 GPT !!!

水文一篇,以上!

关于 MacOS 自带 OpenSSH 兼容性问题

作者 Mosaic-C
2025年11月14日 13:43

排查了一上午,MacOS 15 真是巨烂。

这两天折腾内核,旧 X86 的 MacBook Pro 上的 ElementaryOS 虚拟机再次派上用场,之前一直在虚拟机里操作,来回隔空接力倒是也没啥,到底是心急直接 ssh 不是更好嘛,试试才发现,各种超时。

思路:

  1. 使用 ssh -vvv 查看日志,卡在新服务器都会有个 known_hosts 指纹添加的前面,等待返回握手消息。
  2. 使用 sudo tcpdump -i en0 host x.x.x.x and port 22 跟踪消息,发现 MacOS 这边的包是一直在发,而 Linux 那边防火墙没开,sshd 日志毫无反应,明显没收到包。
  3. 再就架构和软件包版本,ElementaryOS 那边的 OpenSSH_8.9p1 OpenSSL 3.0.2,寻思这不是 Ubuntu 20.X 都一样,两台服务器也是这个版本,x86 和 ARM 设备各一。
  4. 接着现象,手机 Termux,局域网中的 Linux,其他任意设备可与这台 Mac 通信,也可与那台 ElementaryOS 通信。

从版本,到 ARM 与 x86 的兼容性配置等等一系列排查下来,最终定位:

MacOS 自带的 OpenSSH_9.9p2, LibreSSL 3.3.6 与 Linux 那边的 OpenSSH_8.9p1 , OpenSSL 3.0.2 不兼容。

解决办法(也是尝试过程):

brew install openssh

再次连接则一切正常:

$ /opt/homebrew/bin/ssh user@ip
The authenticity of host 'xxxx' can't be established.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

至此,不更新 MacOS 26, 解决办法就只有使用 Brew 的 OpenSSH 替代,版本为最新 OpenSSH_10.2p1, OpenSSL 3.6.0 。

为了使其替代默认,优先使用,在 .zshrc 环境中添加以下代码即可:

export PATH="/opt/homebrew/bin:$PATH"

以前咋用,现在就咋用,以上。

Hide Portainer Business Upgrade Button (attribute based)

作者 Mosaic-C
2025年10月28日 10:58

更新了 Portainer 新版,这颜色方案不太适应,应该说 Upgrade 特别显眼,貌似故意为之。

为了体验更舒适,便留 Tampermonkey 脚本一份替代手工移除 Upgrade 按钮。

// ==UserScript==
// @name Hide Portainer Business Upgrade Button (attribute based)
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 感谢 Portainer CE 提供出色的容器管理工具。
// 对于个人用户,社区版已足够使用。
// 本脚本仅隐藏界面中的“升级到商业版”提示,让界面更清爽。
// 脚本简单,若随版本更新失效,可再调整。
// @match *://*/*
// @grant none
// ==/UserScript==

(function() {
'use strict';

// 判断是否 Portainer 页面
function isPortainerPage() {
const html = document.documentElement;
return (
html.getAttribute('ng-app') === 'portainer' ||
html.dataset.edition === 'CE'
);
}

// 屏蔽按钮逻辑(彻底删除)
function removeUpgradeButton() {
const btns = document.querySelectorAll('button');
btns.forEach(btn => {
if (btn.textContent.includes('Upgrade to Business Edition') ||
btn.textContent.includes('升级到商业版')) {
btn.remove(); // 直接删除节点,而非隐藏
}
});
}

// 主逻辑
function init() {
if (!isPortainerPage()) return;

console.log('[Tampermonkey] Portainer detected — removing Business Edition button');

removeUpgradeButton();

// 监听 DOM 变化,防止按钮重新渲染出来
const observer = new MutationObserver(removeUpgradeButton);
observer.observe(document.body, { childList: true, subtree: true });
}

// 等待 DOM 加载完成
if (document.readyState === 'complete' || document.readyState === 'interactive') {
init();
} else {
window.addEventListener('DOMContentLoaded', init);
}
})();

已同步自 greasyfork,自行安装即可。Tampermonkey 启用生效后刷新可见清爽界面:

以上。

一加 13T 体验报告,脱坑小米,从米系 12X 换至欧系一加 13T 的醒悟!

作者 Mosaic-C
2025年10月18日 16:50

别轻易相信参数,唯有深度体验方知实际。

背景(非体验,仅前情可跳过)

又是一年双 11 ,钟爱(不得不)用小屏机器的我终于在三年后以合适价格等到了一台钟意的机器:一加 13T。PDD 百补 + 国补这价格没啥好说的,欧加电商发货安全下车,至于淘天的乌龙,文末再吐槽。

什么样的体验才会让前 5 台都是小米的人不再用小米,这里就不得不多说废话。几年前的小米 11 是一导火线:小米 11 是一件成功也失败的产品!!!,后续我不像其他人有备用机,退款后急用,高位购入 小米12X 沿用至今。12X 的标题没有浮夸,唯一的优点,手感。其他各方面的*,系统卡顿体验如米 11 的祖传,指纹识别慢,最难受的是这小米配的华星屏幕,一度导致眼睛干涩难以忍受。

于是在 8gen3 市场反馈开始好转时,有过最强烈的换机想法。小屏受众很少不受厂商待见,苹果 13 后再无 mini,赛道就很窄,看来看去只有小米数字旗舰。即便如此也是很宽,要么宽要么长。米 11 的经验让我没那么冲动,周围当时有米13、米14 ,借来一把 QQ 飞车就被劝退。参数的触摸采样率没输过哈,瞬时触摸采样 2160 Hz,上哔哩哔哩搜下就知道一言难尽。不喜米氏调度的至今大把开源调教,还有那系统一如既往,懂的都懂。

一直等到了今年国补优惠,认为是换机好时机。新的小米澎湃 OS3 赞不绝口,小米 15 映入眼帘,和一加 13T 二选一,感谢小米再次的不争气,米 15 大面积的后盖门事件。恍然醒悟,何必吊死一颗树,世上还有整片的森林。天玑 9400 线下体验同系统体验不如 8E ,更多的侧重于拍照,于是不用争。

体验篇

硬件方面

网上诟病最多的如下:

1、砍了超广角

有人对超广角是刚需,建议选天玑 9400 的小屏。个人对拍照无要求,感谢米系调教,小米 11 当年扫码都卡!几年过去硬件也吊打 12X,当年 12X 还是个影像**呢,没超广角就是照片拍不出那么宽广。能接受就不难受

后续:经过多张随手拍确认,成图 AI 痕迹有时明显,但将画面改为全屏后,裁切后的结果也是可以接受的。

2、充电不至宣称 80w

拿到手第一时间上物理工具检测过,在有这些检测设备的情况下只能 35w。去掉检测设备明显感知很快,软件测的不准确。充电和我的 12X 体感时间差不多,12X 4500mAh、13T 6260mAh,见仁见智吧。提一嘴那个自带充电头,磨砂的粗糙感有点廉价感。

3、振动马达砍了一刀

没吃过细糠,这玩意儿最低档都比 12X 强。

4、没有超声波指纹

短焦光学实测也很快,大概是 12X 的三倍,触上即开,位置和 12X 类似,无感切换。12X 是按着还得等震动 1s 多才开,偶尔还开不了,感谢米有这么好的产品,才让我不挑剔。

5、没有无线充电

想了下我除了索尼的 WF-1000XM5 和电动牙刷有无线充电,压根没有其他设备需要这个,难评。能接受就不难受

6、屏幕不太满意

这个只能表示我的容忍度其实很高,相机可以得到的答案:13T 在晚上和白天都是宽慢,亮度够则无感。12X 在晚上是细快,白天是宽快,不说了,那速度闪瞎了。拿着相机对着旁边的 iPad Pro,频闪是什么。。。完全发现不了。

后续:亮度会略微偏亮而非网上所说亮度不够,于是去设置里将白值拉低,辅以手动降低亮度。

7、没有防水

场景防水不是刚需,能抗点热气雨水就行。何况防水会失效,修一次气密性也就没了。能接受就不难受

8、找不出来了,就这样吧。

优点:

1、芯片强

8Elite 是颗好核心,能耗性能都均衡。但不可否认这几年 870 也是个好核心,不热就是好,7nm 换 3nm 鸟枪换炮。

PS:这不是 Scene,那毕竟是付费软件,且功能繁多。这是我自己手搓的简单版,也正因为是自己手搓,于是这种情况能稳住帧率才真实,自己写的软件在处理这些数据时几斤几两还是知道的。

2、电池大,续航顶

其实参数上也没觉得大 1720mAh 就大很多,之前 12X 在 24 年11 月换过电池,现在体感越来越差,保守估计仅剩 3500mAh。 3 天体验下来,大两倍绰绰有余。中规中矩,SOC 制程小 2 倍电池大 2 倍,结果自然也就是 2 倍。

PS:之所以是 6 点开始,是因为今早从6 点开始就各种探索 ColorOS 的设置项,期间包括测试游戏,测试拍照,测试耳机,测试软件使用,最后才来写这篇文。期间持续亮屏,所以掉电均匀。

最终测试结果:维持到晚上 10 点剩余 10%,开省电模式断断续续轻度使用最终维持到凌晨 4 点。省电模式也很流畅,好评+++。

后续:户外双卡+导航也依旧很顶,至此,数据传输,游戏,户外5G 续航都很满意。

3、按键不松动

以前不觉得,现在换成不是小米的设备,理解了。包容万岁

4、像苹果一样多出来的快捷键

可能很多人很早就体验过,我没有,见谅。魔改一下,很方便

5、信号好很多

新的基带,更多的 5G 频段(多 8 个 5G 频段 n48 / n66 / n80–84 / n89 ),自然比旧时代主支持 4G 的 12X 强,12X 虽然支持 4G 频段多,但 4G 基本上也是没法联网的状态。但比不上旗舰大哥一加 13,砍了一些,只能说够用。

软件方面

MIUI 是当之无愧的功能强大繁杂到大家都诟病一些莫名其妙的问题,不进行赘述,于是这里就只能谈谈切过来 ColorOS 的体感不适。

这里不好分优缺点一起混合了:

1、系统

到手出厂系统为 201,直接更新到 603 后的体验感受,即我也没太多体验出厂系统。抱歉啊,第一次知道 Android 的丝滑可以比肩我手上 iPad Pro 的 IOS 18.7。

如果说新手机出厂系统是刻意打磨,现在更新了发售后近 1 年的系统版本,转移了 150G 数据,软件也全部安装登录后,那就不能吐槽什么了,系统确实流畅。

常见的三方软件体验也都不错,至于有些则还是需要看应用本身素质,Android 15 有些也没兼容会有些错误,Android 本质上是这样的,理解则可接受。

2、软件自由度

预装的软件和 MIUI 半斤八两,澎湃是否改善不知道,两个差不多都全部可卸载,功能上开机有个增强功能选择,大概有近半百的开关项,比米系全部默认死了也不知道有还是强点。

PS:初始化时可以不用管,在设置项中 系统与更新 -> 增强服务 可重新配置。

直观感受是,ColorOS 的集中度比较高,集成度比较低,功能多为单独软件,或者说系统流畅多少有点谷歌原生的助力。米系是盘根错节,包含集成在系统里。当然后面用久了也许也能发现诟病。

3、花里胡哨的功能

ColorOS 有点花里胡哨的都是谷歌的一些原生功能的稍微改进。米的强大此刻展现无疑,数不胜数的细分“高级”功能(加了高大上的描述和视频),开了耗电,不开就像个吉祥物。大多数基础的配置项,手势操作,触感都是直接平替,米有的都有,就是没米那种可以单独拿来开发布会的比如自然声音

4、系统常用功能体验(目前体验到的)

截图

不太习惯三指区域截图的体验,或者说这个做得确实不如米。在 ColorOS 上没法直接三指后直接划区域,得等他那个缩放动画和引导指示结束后才能去划区域,按米的习惯三指后立马去划区域就会区域错乱或者被引导指示挡住无法进行划取,需要二次划区域。

图库

相册比较简陋,单独相册中无时间轴,更早期的米状态。图库编辑功能弱于米,拼接只能竖着拼接,不能横着拼接,长得差不多,但是旁边少了个切换横竖。

麦克风

昨天微信视频测试通话有噪音,电话没事,早上找到一个开关叫【人声突显】,设置项在 声音与震荡的麦克风项,下拉磁贴也有。

蓝牙

测试耳机时发现存在默认的音频模式,导致我的索尼与平时不同,重新在 Sound Connect 里的声音模式那边调回来才正常,不清楚原因。

桌面文件夹

我更喜欢米的 4 格大文件夹,这边是 9 格大文件夹,于是只能凑 3 小横条稍微有点搭配性。

桌面/负一屏卡片/主题图标

没米强大,接近原生简陋,米在这方面确实是神,什么乱七八糟的样式都有,也比较好搭配。

需要开会员,同时目前还没找到合适的,米那边是免费,设计师米那边也更多,很多设计师都不做这边的主题,难评。最后选择了开会员使用《盐OS》的状态栏、《寻界》的图标,半个 oPhone体验。最终隔天起来看着不伦不类,就把会员退了,两块钱都不行🙅‍♂️,要么最拉,要么满意,绝不将就。

字体

粗重有点不协调,不太好描述,米在这块儿确实有一手。换主题后协调很多,还是默认吧,也不是不能用。

日历

日程这些编辑、查阅体验强过米系,不会误触日期到处飞。

天气

怎么会有这么简陋的天气,看着像我大学时候写的 Demo,无法定位到街道,看个天气详情还打开浏览器跳转墨迹,绝绝子。

总评:系统软件属于能用级别,不全是好用级别。于是酷安上有大把人怀念米系软件,魔改天气、图库、编辑去给 OPPO 系使用,但结果不尽人意,终无两全

折腾项

喜欢解锁和 ROOT 的朋友,这毫无疑问是最早期米的姿态,几条命令搞定解锁 和 Magisk,市面上可以的选择不多了。不过我本身也不怎么折腾,只是为了自己写的玩具不至于荒废,这也是我果子全家桶只差一台 iPhone 的主要原因。

关于 TEE 假死问题:这又得怀念过去的米了,现在的米无法解锁也就没得选。于是微信指纹不能用的问题,不想整什么三方模块魔法使用,那就这样吧,暂未发现其他软件如此苛刻。

总评

原来 Android 也可以流畅如 iOS,原来骁龙可以调教得这么稳定,原来打游戏可以不用上散热器。这是小屏爱好者重性能轻拍照的不二选择。

总之,这价格带来的性能 + 续航我是很满意的,10 分我给 9.2,0.8 扣在软件功能细节还需打磨,一定还有我没用到的不方便

系统软件易用性方便期待 ColorOS 16 带来提升,并希望不改流畅度。

淘天乌龙

一开始我是在淘宝准备好了一切,我本来也是多年的 88VIP,开了省钱卡,蹲 8 点等结算。

算了一套美梦,最后 8 点直接来了一手涨价(此处是已经算上88VIP -500 的价格),省钱卡的 -120 也不能用了,之前购物车里能用,省钱卡可找专属客服退这倒无所谓,就是浪费精力了。

所以说 88VIP 好几年那个券都用不出去是有理由的,为什么不选简单直接的 PDD ,要来你这个凑单满减开卡乱七八糟,造孽啊。

此文写于体验三天后,更新于使用一周后,以上。

MacOS 免费 MTP 工具 OpenMTP – 3.2.25 汉化版

作者 Mosaic-C
2025年7月29日 21:08

背景

在 MacOS 上的 MTP 工具选择很少,大多太复杂需要付费,OpenMTP 是唯一满足基础且好用的软件,甚至可以用来和 Switch 大气层 DBI 进行交互。

同时作者的更新频次已经放慢,似乎没有维护需求,个人使用下来也没什么问题,这里选择将其汉化养老,更新后的源码在此:https://github.com/Anr-C/openmtp,同时分享汉化打包方法。

编译无 Apple 公证版

环境准备

该项目对于 npm 有要求,同时需要保证 node 不低于 16 :

This project requires npm version >=6.x <=8.16.0. You have version 10.9.2.

于是先安装 nvm 进行 node 版本管理,nvm 可自行了解,好用方便的多版本管理:

brew install nvm

如果之前有其他 node 环境变量请移除,并按 brew 要求在 .zshrc 文件添加:

export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion

随后安装作者指定的 node 16 版本:

nvm install 16.20.2

安装特定版本 npm 8.16.0 :

npm install -g npm@8.16.0

编译链准备

环境安装完成后继续安装 yarn

npm install -g yarn

随后在项目根目录进行依赖初始化,因为要安装 electron 于是需要更改为国内镜像(阿里云):

yarn config set electron_mirror https://npmmirror.com/mirrors/electron/
# 执行 yarn 进行项目依赖初始化
yarn

打包前的修改

禁用 electron-builder-config.js 中的 forceCodeSigning:

  return {
    productName: 'OpenMTP',
    appId: 'io.ganeshrvel.openmtp',
    forceCodeSigning: false,
    // eslint-disable-next-line no-template-curly-in-string
    artifactName: '${name}-${version}-${os}-${arch}.${ext}',
    copyright: '© Ganesh Rathinavel',

同时这里也需要注释掉哨兵模式插件,项目中有两处 config.renderer.prod.babel.jsconfig.main.prod.babel.js

    // new SentryWebpackPlugin({
// include: 'app/dist',
// ignore: ['node_modules', 'webpack'],
// urlPrefix: '~/app/dist',
// configFile: 'sentry.properties',
// rewrite: false,
// release: pkginfo.version,
// }),

在 Apple 的限制下,非 Apple 开发者无法进行应用公证,从而无法编译打包正常版本,只能跳过应用公证。

修改 package.json 文件中的打包命令,追加 -c.mac.identity=null 表示不进行签名,不追加也可打包但默认会以个人 Apple 信息作为签名。

"package-mac-without-notarize-no-verify": "yarn build-no-verify && cross-env ELECTRON_NOTARIZE=NO electron-builder --config electron-builder-config.js -c.mac.identity=null build --mac --publish never",

如果需要调整其他配置,自行修改 package.json 文件,打包:

yarn package-mac-without-notarize-no-verify

随后 dist 目录下可见成果,如需查看生成的应用签名信息可执行:

codesign -dv --verbose=4 /Applications/OpenMTP.app

注意事项

打包要求不同 CPU 架构在对应架构机器打包,生成的包才能正常运行,即 x64 需要 Intel 机器,arm 需要 M 系列芯片,这点确实麻烦作者也有提及。这次勉强打出 x64 后面我就没什么兴趣了,如果有需要自行打包吧。

其他

经过安装测试,汉化目前已覆盖绝大多数场景,有兴趣的可以提汉化 PR 。

公证签名貌似 MacOS 不像 iOS 那么严格,除了初次启动并不影响什么,大多数开源软件也是没公证签名的状态,如果有为爱发电勇士可以提供公证签名那便更好了

下载地址:

通过网盘分享的文件:OpenMTP
链接: https://pan.baidu.com/s/11OgCzzv1qQs52yDBFWGPgA 提取码: fq69

以上。

Kotlin Multiplatform 调用 IOS Swift 代码指北

作者 Mosaic-C
2025年7月15日 17:55

背景

KMM 使用中有些数据需要 IOS 原生提供,被 GPT 坑了一天,最后根据 medium 一篇较新文章 30 分钟解决,GPT 一直在 c_interop 文件路径和 Xcode header 配置里无用折腾,也就是携程旅行分享的那些方案的老一套。

迭代这么久实际没那么复杂了,但官网文档依旧没法看,这里以无 Shared 模块的 KMM 为例。

理论部分

Kotlin/Native 并不直接与 Swift 交互,而是与 Objective-C 交互,由于 Swift 可以将类暴露给 Objective-C,因此这成为了一个简洁明了的桥梁。

干活步骤

定义 swift 代码

最简单的类共享示例 iosApp/iosApp/SignatureUtils.swift

import Foundation

@objc(SignatureUtils)
public class SignatureUtils: NSObject {
@objc public static let shared = SignatureUtils()

@objc public func getRemainingSignatureDays() -> Int {
//...
return -1
}
}

注意:不要在 @objc 中省略类名,使用 @objc(MyClassName) 来确保符号名称可预测、无混乱,避免增加莫名其妙失败因素。

因为是新加入的 swift 文件,Xcode 并不会识别,需要用 Xcode 打开 iosApp 项目后,在左侧资源目录对 xcodeproj 点击右键,随后找到文件添加即可。

如果不将其加入项目,编译时该类不会被编译,Kotlin/Native 也就找不到。

定义头文件以及 def

头文件 SignatureUtils.h,用于定义类的哪些方法、属性被 Kotlin/Native 使用:

#import <Foundation/Foundation.h>
@interface SignatureUtils : NSObject
+ (SignatureUtils *)shared;
- (NSInteger)getRemainingSignatureDays;
@end

def 文件 SignatureUtils.def

language = Objective-C
headers = SignatureUtils.h
package = 项目包名

简单说 def 就是定义的意思,def 文件描述详情见官网:https://kotlinlang.org/docs/native-definition-file.html

参数解析:

language :默认是 C 但这里指定为 Objective-C 是必须,理论部分已经说明。

headers :链接哪些本机代码(静态库、框架等)可以有多项,空格隔开。

package :指通过桥接方式生成的 Kotlin 绑定类存放的位置,也就是当引用共享类时导入的包。

注意:保持文件名、类名的一致性 ,勿使用 GPT 去生成,100% 错误答案。建议手写,仅需改类名、方法名和返回结果类型,继承 NSObject 是必须的固定模板,其他为固定格式。

其他:这俩不必使用 Xcode 和 iosApp 关联,仅需知道位置,比如我这里将其放在 iosApp/iosApp/Signature

配置 Gradle

需要配置的是 composeApp/build.gradle.kts ,将目录指向至 def 与 .h 文件所处位置:

kotlin {
listOf(
iosArm64(),
iosSimulatorArm64()
iosX64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
//...
}
//此处 Signature 只需辨识度高即可
iosTarget.compilations.getByName("main") {
cinterops.create("Signature") {
definitionFile.set(file(rootDir.absolutePath + "/iosApp/iosApp/Signature/SignatureUtils.def"))
includeDirs.allHeaders(rootDir.absolutePath + "/iosApp/iosApp/Signature")
}
}
}
}

使用方法和原生无差:

@OptIn(ExperimentalForeignApi::class)
fun getRemainingDays(): Int {
return SignatureUtils.shared()?.getRemainingSignatureDays()?.toInt() ?: -1
}

同步 Gradle 无报错即成功,有报错查看错误日志无用,请检查命名文件路径等细节。

完结

剩下的就是 KMM expect/actual 那套,原文中还有更多内容,但我不需要,便不赘述。

以上。

参考:Yes, Calling Swift from Kotlin Multiplatform is Easy : No Plugins, No Magic, Just CInterop.

关于 Hekate 多 emuMMC 启动项配置这件事儿-终极方案

作者 Mosaic-C
2025年6月12日 11:16

折腾的乐趣不来自折腾后的成果,而来自折腾本身,于是,需求有时候是为折腾服务的。

背景

最近 Switch2 带来了塞尔达系列的更新包,系统要求 20+,之前我将系统降到 19 后,就无法进行这次更新,主要是好奇,于是我去真实系统更新到了 20.1.1。

不过好在没什么难的,说说我的配置:原有虚拟系统 19.0.1 (分区格式),再选择虚拟系统-创建文件虚拟系统(这里没有复选确认,点了就开始无后悔药)。

理论

分区文件共存,文件文件共存都比较简单,默认都不会冲突。

  • 文件文件共存似乎要更简单,直接复制一份 emuMMC 中的 SD00(例)改名为 SD01(例)即可。
  • 分区分区共存会比较麻烦,需要使用 PC 进行分区操作,复杂后面单独讲,何况之前也测试过,文件格式与分区格式除速度外差异并不大。

随后更改 hekate_ipl.ini,复制原有启动项并由 emupath 字段区分,当然 GPT 、Deepseek 会推荐什么 emummc!path、emummc_path、emummc!id 总之别信,因为这是来自官方更新日志中的字段:

点击展开或者选择看 5 年前的原文:hekate_v520_nyx_v090_released

新增啟動時 emuMMC 選擇 使用啟動項中的 emupath 鍵將載入所選的 emuMMC。 這也可以透過使用正確的啟動 cfg 儲存位元並在 emummc 路徑偏移處寫入路徑來強制執行。請查看自述檔案以了解這些內容。 格式為: emupath=emuMMC/RAW1, emupath=emuMMC/SD00 等等。 (僅適用於由 hekate 建立的,因為它依賴於具有 emuMMC 資訊的 raw_based/file_based 檔案)。

附上我的启动项模板:

[虚拟系统-PLAY]
fss0=atmosphere/package3
kip1patch=nosigchk
cal0blank=1
emummcforce=1
atmosphere=1
usb3force=1
icon=bootloader/res/icon_payloademu.bmp
emupath=emuMMC/RAW1

[虚拟系统-LAST]
fss0=atmosphere/package3
kip1patch=nosigchk
cal0blank=1
emummcforce=1
atmosphere=1
usb3force=1
icon=bootloader/res/icon_payloademu.bmp
emupath=emuMMC/SD00

至此最基础的双虚拟系统配置完成。

说说缺点:

1、不支持选择虚拟系统来进行第二个虚拟系统的创建,默认都是以真实系统做镜像。

2、文件系统创建方式占用的 SD 卡空间会很大,默认 29G 直接做的真实系统镜像,仅分区形式可调整大小。

3、20 系统可用的 System 内存还是太少了,仅剩余 8 M,而 19 余 39M 差距并不是一点点。

4、一张 SD 卡,大气层插件并没法按系统进行区分。像达达可能需要的只是 60FPS 解锁和 amiibo,其他不那么重要,内存更加捉急。

说说好处:

1、可以在不同版本的系统玩不同 HOS 支持级别的游戏,比如,塞尔达在 20 系统解帧后体验更好。

2、各自运行在各自系统,Nintendo 文件夹各自独立,并不会冲突。

3、Pro 手柄/蓝牙音箱不用担心双虚拟系统每次都得重连,可通过大气层配置:enable_external_bluetooth_db = u8!0x1 进行共享处理,前人栽树后人乘凉。

分区与分区共存(仅尝试)

操作

多分区的系统做起来实际很简单:用 PE 下的 DG,将 SD 卡划出两片空闲空间,随便选个格式比如 EXT、FAT32,然后保存分区表,不要格式化,就形成了两个 RAW 格式的分区

这两个 RAW 和 hekate 分区工具抹盘创建的 RAW 一样,可以直接在创建虚拟系统时被识别,然后对应每个分区各创建一次虚拟系统即可。

分区表的坑

分区式的虚拟系统是一锤子买卖,定下来后,就不可后续增减。因为分区很讲究,无论如何都会是一张“错误”的分区表

为什么这是”错误”的呢,再上一张 hekate 分区读取图:

SWITCH SD 分区对应的是第 0 部分Unknown 10G 对应第 1 部分Unknown 15G 对应第 2 部分

而这个顺序是 hekate 默认做分区时的顺序,从左往右,从而导致了我在加分区时,经历了这样的变化:

  • 如果我再加一个 Unknown 18G 在 Unknown 10G 的左边,毕竟只有 Unknown 10G 左边有空闲对吧,思路没问题。
  • hekate 就会变成 Unknown 18G 为 第 1 部分,Unknown 10G 对应第 2 部分,Unknown 15G 对应第 3 部分。
  • 因为 hekate 每一个部分对应的是 RAW1 RAW2 …类推,那么此时要被识别为 RAW3 只能选第 3 部分,因为 1,2 部分已经被之前的系统占用了地址与唯一 ID
  • 最后不管是选第 1 部分 重做(ID 冲突了,旧的找不到了),还是选第 3 部分重做(旧的真实文件分区直接被覆盖),都会是失败的。

这并非三分区的问题,二分区也会,我这里只是用更加复杂的举例了。如何解决?假设分区表倒过来

此时理论上,空闲空间在 unknown 10G 右侧,增加的新分区自然而然成为了第 3 部分,也对应了 RAW3 并具有新的唯一 ID。

但不幸的是,此路不通。

1、首先 RAW 分区不能是最左第一个分区,因为第 1 部分会无法被识别,原第 2 部分会成为第 1 部分,SWITCH SD 因此成为第 2 部分,推测是为 Linux / Android 留的 EFI 引导分区

2、位置问题好解,前面做一个空分区,虚拟系统就可以创建在我们想要的位置。但创建完成启动就会发现,这个顺序做的虚拟系统无法启动。不是大气层报错,而是 hekate 根本找不到虚拟系统所在位置。

所以分区-分区式 是一锤子买卖,一开始就做好,永不变动。hekate 必然有他这么做的理由,不必折腾。

这套方案致命的弱点除了无法变动,同时 hekate 并不支持备份第二分区,也就无从恢复,当然可以整盘 DD ,可几百 G 的空间 DD 很麻烦。

双分区示例:

其他细节

缩小 USER 分区

因为是虚拟系统,都在 SD 上,内置 USER 空间太大不方便备份也不方便移动,有必要了解这部分。

缩小 USER 分区的原理:按磁盘地址截断。意思就是,比真实系统小的空间,或者任何不由 hekate 创建的RAW 都会视为修改过的分区, 会对应文件系统碎片不全的文件映射(文件记录在,文件实际上不在)。

这也是分区类型多次操作后将导致文件系统交叉读写错误的来源。从而虚拟系统启动后一堆游戏打开/安装错误,碰到对应不存在的碎片地址大气层就直接崩溃

解决办法是:在设置中初始化主机或直接 daybreak 降级恢复出厂。NS 的虚拟系统创建没有 DG 那种划配空间时的文件碎片整理功能,这也是 NxNandManager 不清除 User 分区数据就无法缩小的原因。

NxNandManager 缩小 SD00 式虚拟系统很简单,5.2 版本即可,支持 20.1.1 最新系统:https://github.com/eliboa/NxNandManager/releases,教程网上有很多,这里不赘述。

降级失败/初始化失败问题

另外值得一提的是,多系统因为是一张 SD 卡,共用一套大气层,从而产生的文件可能会导致某个系统在降级/恢复初始化后启动蓝屏。从而无法进入初始化流程,浅层的解决办法是:降级完成后,启动之前,将大气层还原到初始状态,不过这样治标不治本。

如果去检测 FAT32 文件系统,会发现频繁出现文件无法删除/一直占用等问题,DG 会提示文件交叉读写错误/分区损坏。这时候用 DG 检查磁盘坏块,就会得到答案。(注: DG 的坏块检测原理决定了对于不支持的磁盘格式会误判,需格式化对应位置为FAT32后单独对该区检测)

解决办法是:整盘格式化为 FAT 32 后再对虚拟分区那块地址检测坏块,如果不存在错误,就只是折腾时 hekate 多次来回创建虚拟系统累积导致的文件系统错乱。如果依旧存在错误,很不幸,SD 已经被折腾坏了,尽早换卡。

终极方案总结

经过多次双分区尝试,总结最合适方案为 10G RAW1(分区式主力 19.0.1)+ 8G SD00(文件式备用 20.1.1)的组合,好处如下:

1、最适合备份/恢复,RAW1 可直接通过 hekate 备份/恢复,单分区的情况下,换卡也可还原。SD00 直接拷贝即可备份/恢复。

2、最适合动态扩增,RAW1 虽然依旧是一锤子买卖,但 SD00 可通过 NxNandManager 进行空间缩小或者扩大,文件形式也不会存在分区那种 ID 或者物理地址覆盖问题。

文件式缺点:无法开启 USB 3.0。

当然有人会疑问为什么不是 文件 + 文件,很显然 FAT32 文件系统交叉读写导致的磁盘坏柱一旦发生,就不会是简单的修复可以还原,MacOS 操作下这类事件很常见。

后续

虚拟 19.0.1 维持不变,虚拟 20.1.1 已通过 daybreak 更新至 20.5.0,主机正版系统维持 20.1.1(使用时拔除 SD 卡),实测三系统互不影响。

在给朋友的 续航版硬破机器 处理多系统时发现,硬破使用 sdloader.enc 引导时不支持 SD 文件进行启动,于是往往硬破自带仅能 单分区破解正版破解 形式的双系统。

以上。

参考:

Multiple emuMMC setup, including restoring a clean NAND for online use

Hekate v5.2.0 & Nyx v0.9.0 發佈啦!

使用 Docker Devkitpro 编译 Switch 插件 – 例 sys-clk 2.0.1 汉化版

作者 Mosaic-C
2025年6月4日 12:19

背景

Switch 很多插件已经稳定运行了几年不再更新,虽然英文简单,但中文看起来确实舒服点,于是自己动手吧。

Sys-clk 简介

Sys-clk 是一个超频软件,项目地址:https://github.com/retronx-team/sys-clk,而网上有些人打包出来的汉化版要比原版大一倍,默认什么都不做的情况下,打开游戏就会死机黑屏,就比如:

左侧是我自行打包的,右上的原项目 release 2.0.1 版本,右下,不提也罢,B站上很多诸如此类不知来源的软件包。对我而言,我会选择与官方二进制文件进行对比,仅做字符修改的,我才会认可。

编译和打包这类 switch 软件很简单,会用 docker 就行,甚至也不用去管什么平台差异,更不会污染本机环境。

准备 Docker 环境

对于 Switch 我们需要的环境是现成的,默认就好:

docker pull devkitpro/devkita64

创建一个容器,并把 sys-clk 的源码挂载到容器的 /sys-clk

docker run --rm -it -v path/to/sys-clk/:/sys-clk devkitpro/devkita64:latest /bin/bash

此时会进入容器的终端,配置一下环境变量:

export DEVKITPRO=/opt/devkitpro
export DEVKITARM=$DEVKITPRO/devkitARM
export DEVKITA64=$DEVKITPRO/devkitA64

随后切入挂载点,给 build.sh 执行权限,然后执行即可:

cd /sys-clk
chmod +x build.sh
./build.sh

编译时间很短,不过几秒钟,实属再小不过的项目。结果输出在 overlay/out 目录下,全套文件(包括补丁、manager)则在 dist 目录下。

注:此处编译为 sys-clk 官方提供的 build.sh,部分项目是直接 make,需自行探索。

关于汉化

对于这个项目,有点计算机底子的应该都不会觉得难,何况还有字符搜索,sys-clk-manager.nro 我用不着,也许需要的朋友可以自行尝试。

这里附上汉化 sys-clk-overlay 的 diff,自编译的可以直接拿去用(git apply update.patch),以及生成的成品 sys-clk-overlay.ovl,需要自取:

下载链接

链接: https://pan.baidu.com/s/1VRXrCekklrFcfKSDYODcyA 提取码: kx8i

注:需配合官方的 patch 补丁,不要乱用他人补丁,认真对比文件二进制差异以及文件大小,小心被人植入后门。

软件上不会有关于我的信息,我喜欢干干净净,有问题请留言。

以上。

使用 jpackage 将 JavaFx jar 封装为 MacOS DMG 安装器 – 例 NS-USBloader

作者 Mosaic-C
2025年6月3日 11:26

常用 Switch 的朋友,可能知道有一个工具叫 ns-usbloader,github 地址:https://github.com/developersu/ns-usbloader

因作者仅提供了 jar 运行方式,无法在启动菜单留存,使用略有不便,于是就自己打个包套个壳。

适用于所有 Java 环境下双击 jar 就能运行的程序。示例:

jpackage \
--name NS-USBloader \
--input ./jar \
--main-jar ns-usbloader-7.2-m1.jar \
--type dmg \
--dest ../ \
--icon ./app.icns \
--app-version 7.2 \
--jlink-options "--strip-debug --compress=2 --no-header-files --no-man-pages"

可编辑的部分:

--name       应用程序名
--input jar 所在位置,打包时会将该文件夹内所有文件打包
--main-jar jar 文件名
--type dmg 打包为 dmg,当然也可以是 app-image 等其他
--dest 输出目录位置
--icon 图标位置,MacOS 的图标格式为 icns
--app-version 版本号
--jlink-options 本来是想精简下成包体积,但 DMG 特有的压缩算法,只能图个安慰

由于 jpackage 会默认打包完整的 jre ,好处不用配置环境,坏处文件体积略大,尝试了自打包,解包再打包,总而言之,言而总之,jpackage 自带的打包算法已经是最优。

关于生成的 APP:可在 NS-USBloader.app/Contents/app 中找到 Jar 对比 github 上的原文件。此外,文件结构应清晰明了无多余文件:

/Applications/NS-USBloader.app ❯ tree -L 3
.
└── Contents
├── Info.plist
├── MacOS
│   └── NS-USBloader
├── PkgInfo
├── Resources
│   └── NS-USBloader.icns
├── _CodeSignature
│   └── CodeResources
├── app
│   ├── NS-USBloader.cfg
│   └── ns-usbloader-7.2-m1.jar
└── runtime
└── Contents

8 directories, 7 files

包括生成的安装器,也是精细布局过的,倒是有点不像 Java 的风格。

打包的 NS-USBloader-7.2.dmg 也分享一下,也许有朋友用得着。

下载地址

链接: https://pan.baidu.com/s/1iMjTJ3An1jxU243OR3wzww 提取码: ts9f

免责声明:请勿从其他地方获取该安装器,因此遭遇的风险自行承担,图标资源来自 GPT 自动生成。

以上。

2025 Kotlin Multiplatform 感受浅谈,跨端应用是否可以考虑 KMM

作者 Mosaic-C
2025年4月30日 18:20

背景

这几天整了个小 IOS 需求,大致体验了下双端开发,目前已经接近完工,涉及主要是 SQLite、IO 以及最常见的 UI,未涉及网络。理论上网络诉求远大于本地端,体验应当不太差(仅理论)。

之前的同事(两年前)开发过一款 KMM 的网易云,但后续我并未尝试,因为懒得踩坑,现在 BETA 。从新手的角度看,还算顺畅。

感觉直到 KMM ,才意义上的符合了当初的一个 Activity,因为一旦越界,就不是一个单端开发能搞定的了,一切行为围绕 ComposeView,以及 Gradle 与 Kotlin 官方文档。

一些流程

在构想一个功能的时候,脑子里第一件事还是停留在“是否支持 KMM”,毕竟想要通用,等于大换血。GPT 能给不少助力,至少能得到一个“看上去可行与否”的答案。

随之便是尝试找对应的 lib,因为 KMM 核心在于 Kotlin 与 Compose 的通用,版本太新了会崩溃,太旧了也会崩溃,不支持的便用不了,此刻崩溃对于版本号⚠突然不再显得刺眼。

找得到 lib 还好,找不到的呢?KMM 迭代了这么久,自然是有组合拳,定义一个 expect 预期 :

expect fun loadDatabaseBuilder(): IDatabaseBuilder

两端再分别来个 actual 实现:

actual fun loadDatabaseBuilder(): IDatabaseBuilder = DatabaseBuilder()

这个组合拳支持的部分,按属性、方法,类排,进程到了 object 这里会给个提示 are Beta:

'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https:// youtrack. jetbrains. com/ issue/ KT-61573

我不知道 Bate 和正式的区别有多久,毕竟 Google 还有很多东西 3 年前是 alpha 现在还是,并非停止维护也是一直在更新,就是这个正式太难了。

总的来说,两端依旧在用 Kotlin。脱离了 UI 的部分,Kotlin 才开始不再像 Kotlin,在涉及到 IOS 的原生部分(文件、plist、日志)时,NSxxxxxxx 之类的特定代码,会变得常见。是单端需要花时间去查找的部分,例:

@OptIn(ExperimentalForeignApi::class)
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}

U1S1,这时就有了点 AIDL 的味道,做过系统开发的都应知道,GPT 说 Flutter 也是一样,不过上面都是小问题,习惯了就好。

大问题在哪儿呢,就是这个 KMM 用的人确实不多。

1、最常见的 toast 场景 ,Github 上被 GPT 推荐的库 Star 不过百,而且相当的新。当然也可能是 toast 在 IOS 那边太丑了,大家选择各自实现

2、文章基本停留在 2022,和 2024 年底。

这个时段很有意思,2022 那批文基本上包含了一个 Shared,以至于现在问 GPT 回答总是带 Shared 的错误答案。

图中是 Kotlin Multiplatform Wizard :https://kmp.jetbrains.com/

而 2024 年底,是 K2 和 Share UI Beta 开始从而将我拉进坑的时间,这个项目初始化是无 shared 目录的。

这样理解一切就合理了,毕竟时间超短,加上全球经济形势,恐难以出现现象级的社区支持,何况大国之争下,鸿蒙成了强推,好不好用另说,得先遥遥领先

其他,貌似技术上没什么好说的。基本上逻辑都写在 commonMain ,像开发 Compose Android 一样去写就好了,只是需要注意很多常见的 java 包都不再支持,需要换成 kotlinx,简单如 datetime 也不能用 currentTimeMillis 了事。

BB 完了

如果是新项目,如果是 Android 背景的独立开发者,如果是一个不需要什么黑科技的普通 APP(不需要什么底层支持),只需两端 UI 同步,目前的 androidMain、commonMain、iosMain 已经很顾名思义,可以尝试,所见即所得。

和 Flutter 似乎没有比的必要,思路流程差不多,何况是同一个爹。至于 Flutter ,目前群基本安静了,正如目前的中小企业现状。。。不是,难道 KMM 的核心不应该是“不用新学”?

同时吐槽下 IDE 性能,M4 32G 的配置,开发起来还是有点卡卡的,能瘦瘦身吗?还要吐槽下 Apple, 不当 Apple Dev 不知,开发 IOS APP 都得先花 688 交会费才能玩,不然 ipa 打包资格都没有,dev 7 天就得重签,企业也才 1 年,也难怪欧盟一直搁这制裁。

以上。

为了 K2 compiler, 将 Kotlin 更新至 2.0.0 需要做的事儿

作者 Mosaic-C
2025年4月28日 12:04

最近有个开发 IOS 的小需求,想看看 Compose 的 Multiplatform 出新手村没,然后看到评论区一堆人在 IDEA Multiplatform 插件评论区 call 咩有 K2。疑惑 K2 是啥玩意儿,于是在官网看到了”遥遥领先式“宣传手法:

The new K2 compiler is enabled by default starting with 2.0.0. For more information on the new features provided in Kotlin 2.0.0, as well as the new K2 compiler, see What's new in Kotlin 2.0.0.

Performance improvements

To evaluate the performance of the K2 compiler, we ran performance tests on two open-source projects: Anki-Android and Exposed. Here are the key performance improvements that we found:

The K2 compiler brings up to 94% compilation speed gains. For example, in the Anki-Android project, clean build times were reduced from 57.7 seconds in Kotlin 1.9.23 to 29.7 seconds in Kotlin 2.0.0.

The initialization phase is up to 488% faster with the K2 compiler. For example, in the Anki-Android project, the initialization phase for incremental builds was cut from 0.126 seconds in Kotlin 1.9.23 to just 0.022 seconds in Kotlin 2.0.0.

The Kotlin K2 compiler is up to 376% quicker in the analysis phase compared to the previous compiler. For example, in the Anki-Android project, analysis times for incremental builds were slashed from 0.581 seconds in Kotlin 1.9.23 to only 0.122 seconds in Kotlin 2.0.0.

For more details on these improvements and to learn more about how we analyzed the performance of the K2 compiler, see our blog post.

原文:https://kotlinlang.org/docs/k2-compiler-migration-guide.html#performance-improvements

简单说就是:编译速度提升 94%、分析速度提升 376%、初始化阶段速度提升 488%。

文章发布日期也才 4 天前,这次赶了个早,从标题看,主要是利好 Multiplatform ,不过无所谓,需要 Kotlin 2.0.0,也算专业对口,这就给自己的 APP 安排。

如果经常用 Android studio 自动更新的朋友,一定会发现触发编译必有错误,当你费尽心思调顺后,warning 又会将你指向这俩文档,意思要用 2.0.0 必须整这么个 compiler :

https://developer.android.com/develop/ui/compose/compiler?hl=zh-cn

https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compiler.html

但不好意思,一路按他的流程 next 下来,只会是 Error 、Sync failed 等常见组合拳,我习惯称为“水土不服”。

总的来说,就是他们在做 kotlin、gradle、compose 的分分合合,没说的那么简单,也没那么复杂。

Plugin 的分分合合

一句话:增加 compose-compiler-gradle-plugin ,移除 kotlin-gradle-plugin 换为 kotlin.android.gradle.plugin

对于 plugin 的管理,可能一些老旧项目切不过来,这里分两个版本。

非 TOML 管理版本

在用 classpath 的那个根 build.gradle.kts 追加一个 compose-compiler-gradle-plugin ,移除 kotlin-gradle-plugin,换为 kotlin.android.gradle.plugin,同时需要更新下 gradle 版本 ,完整版:

classpath("com.android.tools.build:gradle:8.9.2")
//kotlin 2.0+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
classpath("org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:2.0.0")
classpath("org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.0.0")

TOML 管理的版本

这里就不多说了,根 build.gradle.kts ,移除 kotlin-gradle-plugin ,换为 kotlin.android

plugins {
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler.plugin) apply false
}

libs.versions.toml 文件:

[versions]
kotlin = "2.0.0"

[plugins]
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler-plugin = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

然后

既然增加了一个 plugin,自然在 module 也需要配置,比如 build.gradle (若是 kts 自行按 kts 写法):

plugins {
...
id 'org.jetbrains.kotlin.plugin.compose'
}

不多吧,就这点东西,可以成功同步了。这段代码记得删了:

composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}

官方文档提到的 Compose 编译器 Gradle 插件配置选项,也可以加了:

android { … }

composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
    stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

PS: Compose 稳定性的配置文件 stability_config.conf 还请手动创建,这部分的配置见:

https://developer.android.com/develop/ui/compose/performance/stability/fix?hl=zh-cn#configuration-file

Kotlin 官方推荐的注解处理工具:KSP

按上面的流程,可以编译了,但编译一下就会发现:

w: Kapt currently doesn't support language version 2.0+. Falling back to 1.9.

Kapt 仅支持 Kotlin 1.9,且进入了维护阶段,而有个新东西,com.google.devtools.ksp 已经先行了很久,于是

同样在 classpath 的那个根 build.gradle.kts 增加(toml 的更简单就不贴了):

//replace kapt
classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.0-1.0.24")

在 module 中的 build.gradle 更换 ‘kotlin-kapt’ 为:

plugins {
// byby kapt
id 'com.google.devtools.ksp'
}

然后移除所有的 kapt 配置项,将 dependencies 中的 kapt 替换为 ksp。Kotlin 是 2.0.0 ,Ksp 则也只能是 2.0.0,否则会甩你一堆“版本太高了”,具体版本见:

https://central.sonatype.com/artifact/com.google.devtools.ksp/com.google.devtools.ksp.gradle.plugin/versions

不过也就需要一直忍受 A newer version of com. google. devtools. ksp than 2.0.0-1.0.24 is available,但不好意思就是不能升。

无了

上面就是 update to kotlin 2.0.0 需要干的简单事儿了。

试用了一把,本来就是 M4 ,编译实在是没什么太多感受,以前 Intel 转 2/3min 的东西,现在不过 25s/1s ,硬件决定的还是多一点

不过别失望,折腾是值得的,Kotlin 2.0.0 随之而来的 Compose 生成的 Debug 应用流畅了许多。

后续

既然是大更新,自然免不了要适配,目前初步测试,触摸/动画事件的改变较多,已知:

1、awaitHorizontalTouchSlopOrCancellation 行为进行了变更,事件消费后其他控件依旧可消费,不如直接使用 detectHorizontalDragGestures 可靠,像是刻意的规正,扩展进一步收缩。不过也许是BUG,毕竟 Kotlin 1.9 并没问题。

2、API 变更,这个官网有披露,animateItemPlacement() 变成了 animateItem()。

既然要适配,于是后来一口气直接更新到最新的 2.1.20,Debug 版 APP 流畅度居然够得着正式版,相当的离谱!!!当然敢这么改,也是自 TOML 管理后,只需要改改 TOML 文件的 Version 不用到处找代码来的方便。

建议仅 try try dev,毕竟大项目免不了各种自定义事件,一个个调未免太费时。

以上。

MacOS 15 移除自带系统 ABC 输入法

作者 Mosaic-C
2025年3月14日 14:56

背景

因为用户词库等原因,已经与某个输入法深度捆绑,苹果自带的拼音输入法比以前好用了点,但和已经熟悉的输入法比起来差之千里。重点是一个输入法可以解决的事,不想一直切,这对开发极不友好。

顺口一提

看到网上很多说需要关闭 SIP 才能操作,实际上不需要,因为输入法的配置文件在用户目录,并不是在系统目录,如果在系统目录,关闭 SIP 也无法修改,SYSTEM 是只读模式

解决

首先自行安装 PlistEdit Pro,然后终端打开并输入以下代码,输入密码会自动打开文件:

sudo open ~/Library/Preferences/com.apple.HIToolbox.plist

AppleEnabledInputSources 项中找到包含 ABC 的组,右键删除并 Ctrl + s 保存。

按网络上的说法,需要在 Finder 中右键点击该文件,显示简介 -> 勾选已锁定。但经过我的重启,退出登录等测试,发现并不会还原,可能因人而异。

其他

再吐槽一下百度输入法 Mac 版的 BUG,在百度输入法支持 AI 超会写后,输入文字时会因为不支持旧版皮肤从而变得特别小,设置中的更改字体也对候选词大小不生效,百度输入法的皮肤目前大多数都有这个问题。

于是换回了最后一个没有 超会写 的输入法版本,版本号为:v5.7.0.16。经测试,输入功能正常,就是很多服务接下来可能不再支持,比如皮肤、词库、云输入联想等。这个问题也提交了反馈,不过大概也没人理会吧。

后续

发现禁用 ABC 后,经常在登录界面,或者其他密码输入框出现按键正确,但是结果错误的问题,即输入与系统识别不一致的问题,后将 ABC 补回一切正常。

不必刻意追求完美,反而导致莫名其妙的问题,MacOS 会自动在不同的时机切换不同的输入法,已经是完美。

以上。

使用 JavaScript 生成一个简单文章目录(二)

作者 Mosaic-C
2025年2月20日 19:29

至于为什么是,二,当然是因为当年年少

很久以前写过一篇 使用 JavaScript 生成一个简单文章目录,还大言不惭的说很简单,于是才有了这个拖了四年的 BUG。

之前只做了简单层级拼接,两层就没问题,在稍微复杂时便错乱了,当时水平也一般,看不出端倪。

理论上这是一个需要多次递归处理的类似树型结构的问题,修复完成后的代码更多也更长,直接上代码:

/**
* 节点类
* menu 为节点 element
* parent 为父节点
* child 为子节点
* 例:
* h2-1
* h3-1
* h4-1
* h2-2
* h3-2
*/
class Node {
constructor(menu, parent = null) {
this.menu = menu;
this.parent = parent;
this.child = [];
}

//节点层级
level() {
return levelNum(this.menu.id);
}

//添加子节点
add(node) {
this.child.push(node);
}

//寻找同级节点的父节点
findParent(level) {
if (this.level() === level) {
return this.parent;
} else {
return this.child.length === 0
? this
: this.child[this.child.length - 1].findParent(level);
}
}

toString() {
return this.menu + JSON.stringify(this.child);
}
}

//增加 html 锚点,格式:h2-num ,比如:h2-1、h3-1
function add_tag(container, tag) {
for (let i = 0; i < container.length; i++) {
container[i].id = tag + "-" + (i + 1);
container[i].className = "j-menu";
}
}

//判断 锚点 层级,取锚点 h2-1 前两位 h2 的后一位 2,越大则层级越深
function levelNum(tag) {
return tag.toString().substring(1, 2);
}

//获取整个文章结构 进行 tag 标记(不限于 h2 h3 h4)
var tags = ["h2", "h3", "h4"];
for (t = 0; t < tags.length; t++) {
var container = document.querySelectorAll(".article_content > " + tags[t]);
add_tag(container, tags[t]);
}

var menuDom = [];
menuDom[0] =
'<h3 class="uk-h5">文章结构</h3><ul class="uk-nav uk-nav-default">';
let menus = document.querySelectorAll(".j-menu");

//定义节点列表
const nodes = [];
//上次使用的父节点
let lastNode;

menus.forEach((menu) => {
const level = levelNum(menu.id);

if (nodes.length === 0) {
//初始化节点列表
nodes.push(new Node(menu));
lastNode = nodes[nodes.length - 1];
return;
}
//上一次的层级
const lastLevel = lastNode.level();
//尝试在上一个父节点中寻找相同层级 并返回父节点
const parent = lastNode.findParent(level);

if (lastLevel === level) {
if (!parent) {
//层级相同 父节点为空 顶层节点
const element = new Node(menu);
nodes.push(element);
lastNode = element;
} else {
//层级相同 有父节点则直接使用
parent.add(new Node(menu, parent));
lastNode = parent;
}
} else {
if (!parent || parent.level() > level) {
//层级不同 父节点为空 或者 父节点的层级已经大于当前节点 比如 h3-3/h4-3 切换到 h2-2 时
const element = new Node(menu);
nodes.push(element);
lastNode = element;
} else {
//层级不同 但能找到有父节点 直接使用
lastNode = parent;
parent.add(new Node(menu, parent));
}
}
});

//递归构建菜单 html
function handleDom(child) {
child.forEach((node) => {
let menu = node.menu;
var content = '<a href="#' + menu.id + '">' + menu.innerText + "</a>";
if (node.child.length > 0) {
menuDom[menuDom.length + 1] =
'<li class="uk-parent">' + content + '<ul class="uk-nav-sub">';
handleDom(node.child);
menuDom[menuDom.length + 1] = "</ul></li>";
} else {
var subfix = "<li>" + content + "</li>";
menuDom[menuDom.length + 1] = subfix;
}
});
}

handleDom(nodes);
//完成最后一笔
menuDom[menuDom.length + 1] = "</ul>";

// console.log(menuDom.join(""));

if (menus.length >= 1) {
document.getElementById("menu-container").innerHTML = menuDom.join("");
} else {
document.getElementById("menu-container").innerHTML =
"<div class='uk-nav-h5 uk-margin-large-top'>不好意思噢, 没有在这篇文章中找到目录。</div>";
}

想理解逻辑就自行 debug 吧,当然里面还留了一个 BUG,如果你的文章目录有十几层,可以到 H11,那么这句就不对了:

tag.toString().substring(1, 2);

文件可直接在本站网页资源上获取,算法的一部分看似可以合并,实际上无法合并,不是专业写 JS 的,能用就行。

以上。

MacOS 毒瘤软件 Logi Options+ 瘦身以及 Backend connection problem 无法打开问题处理

作者 Mosaic-C
2025年2月12日 23:03

用罗技也有些年头了,这些毛病至今未曾改变,甚至在后台自动下载,更新导致占用达到近 2.5G,只是键/鼠标,需要的也仅只是鼠标!

今天看到占用后直接就给它卸载了,但卸载了鼠标用着又不是那么顺畅,于是就搜索 Options+ lite,最后得到这个项目:https://github.com/Qetesh/logi-options-plus-mini ,实践了一番:

##############################################################
2025年 2月12日 星期三 22时55分00秒 CST | Starting install of Logi Options+
##############################################################

Please select the features you want to keep:
1. analytics: Shows or hides choice for users to opt in to share app usage and diagnostics data.
2. flow: Shows or hides the Flow feature. Default value is Yes
3. sso: Shows or hides ability for users to sign into the app.
4. update: Enables or disables app updates.
5. dfu: Enables or disables device firmware updates.
6. backlight: Enables or disables keyboard backlight on the supported keyboards.
7. logivoice: Enables or disables LogiVoice feature.
8. aipromptbuilder: Enables or disables AI Prompt Builder feature.
9. device-recommendation: Enables or disables device recommendation feature.
10. smartactions: Enables or disables Smart Actions feature.
11. all
Press enter for none

Enter your choices(e.g. 2 6, default is none): 1 2 3 10

等待安装完成,最终获得:

好像好了一点点,也仅仅只是一点点,想不通实在是想不通。当然不能止步于此,直接选择卸载 LogiPluginService,同时清空 /Users/Shared/LogiOptionsPlus/cache 文件夹,重启得到:

终于有所成效。

然后一直以来还有一个无法启动的问题,就是一直转圈转圈,今天得到了一个错误信息,在 app 底部看到:

Backend connection problem - click here to launch backend

然而点了没用,不用使劲点,直到看到这篇很久以前的:罗技鼠标MAC系统 故障 Backend connection problem – click here to launch backend解决之法,心中只有万句草泥马。

其实它就是想在后台自动更新,自动下载,必须将这三自启动,特别是 updater 那个启动项开启,才能启动:

很多软件都可以进行配置,这里是腾讯柠檬,也可以用 AppCleaner 等。

最后我选择了将 Options+ 禁网,打进黑名单,以后不进行更新,就此用到 Mac 报废,如果哪天无法使用,将直接卸载,甚至考虑更换鼠标。

以上。

后续

最后还是选择了 “卸载”,因为永远不知道何时,何处,又给下载了 1.5G,远离。

❌
❌