后端程序员如何配置 macOS

标签:Apple, Mac OS X

鉴于我的红米 K60 仅使用不到半年,电池健康度就只剩 80% 了,我入手新 MacBook Pro 后的第一件事就是安装 AIDente
锂电池的健康度主要和这三个因素相关:
  1. 循环次数:从 100% 用到 0%,或是从 100% 用到 50%,充满后再用到 50% 都算一次循环次数。可以理解为总共使用了多少电量,所以长期插电使用,而不是用电池供电是正确的。
  2. 温度:充电会导致电池温度上升,而过高(> 35°C)和过低(< 0°C)的温度都会影响电池的性能。一般越接近 25°C 越好。所谓的快充伤电池,其实是快充会导致电池升温更快。
  3. 充放电深度:过度的充放电(特别是放电)都可能对锂电池造成不可逆的损伤,尽量避免充电至 80% 以上和放电至 20% 以下。例如 100% 的充放电深度,大概 300 次循环次数就会使健康度降到 70%,80% 的充放电深度则可以到 400 次,10% 的充放电深度则可以到 6000 次(但是相当于只使用 10% 电池容量)。
AIDente 对这几点都有处理:
如果经常需要移动办公,将充电限制设置到 80% 就行了,60% 的充放电深度也够用大半天了;如果大部分时间都插电使用,限制到 70% 也够用;如果几乎不移动,让它保持在 50% 附近也是可以的。
Intel 芯片的 MacBook 是可以设置硬件充电上限,之后即使退出 AIDente 甚至关机都不会过充;而 Apple silicon 芯片则需要保持 AIDente 运行,且启用「MacBook 进入睡眠时停止充电」,并在关机后拔下充电头才能避免过充。
后面的设置就需要购买 AIDente Pro 了,但也不是非买不可:
  • 「过热保护」可以在电池温度过高时停止充电。
  • 「续航模式」可以避免短暂用电后又充到上限这种微小充电,不过这也没啥危害。
  • 「控制 MagSafe LED」可以在达到充电上限停止充电时使 MagSafe LED 显示绿色,而不是充电中的橙色。
  • 「图标样式」可以改成「咬合状态」,用来区分不同的充电状态。
  • 「硬件电池百分比」可以更精确地显示和控制电量,macOS 为了避免过度充放电,一般会隐藏一小部分电量。(比如充到 95% 就显示充满了,还剩 5% 时显示成没电了。)
我顺便还读了下它的源码,发现它是通过写入 SMC 来限制充电的。还有一个叫 battery 的项目是调用 smc 命令行实现的,可能更易懂。

接下来就是翻墙软件了,不然啥都下载不了。
我用的是 mihomo,让局域网和国内走直连,其他都走自建的 hysteria2 代理,部分域名因为屏蔽了我的服务器,所以改成走 Cloudflare Worker。
Safari 只能用系统代理,但是公司还需要用 Microsoft Team,这个软件在启用系统代理后无法进入会议,所以我直接放弃使用 Safari 了。Chrome 默认也使用系统代理,我写了个 NaiveProxy 插件来使用 SOCKS5 代理。顺带一提,Safari 在展示需要 GPU 参与的页面时更省电,Chrome 则更省内存。
Python 和其他很多软件可以改环境变量:export ALL_PROXY=socks5h://127.0.0.1:1080。但是注意如果没安装 PySocks 会使用不了 SOCKS 代理,可以临时用 ALL_PROXY="" pip install PySocks 安装。修改 ~/.pip/pip.conf,换个国内的 pypi 源也是很有必要的。
curl 和 brew 也可以使用 ALL_PROXY 环境变量。此外还可以修改 ~/.curlrc,加上 -x socks5h://127.0.0.1:1080。如果要临时取消代理,可以加上 --noproxy '*' 参数。brew 并不会直接使用 curl 的配置,还需要修改 ~/.homebrew/brew.env,加上 all_proxy=socks5h://127.0.0.1:1080
Go 可以用官方的代理:go env -w GOPROXY=https://goproxy.cn,direct
VSCode 可以打开设置,搜索 http.proxy,把它改成 socks5://127.0.0.1:1080
GitHub 可以修改 ~/.gitconfig,添加:
[http "https://github.com"]
    proxy = socks5://127.0.0.1:1080
以及 ~/.ssh/config
Host github.com
	HostName github.com
	ProxyCommand nc -v -x 127.0.0.1:1080 %h %p
	User git
	PreferredAuthentications publickey
	IdentityFile ~/.ssh/id_ed25519
	UseKeychain yes
	AddKeysToAgent yes
aria2 只支持 HTTP 代理,可以用 --all-proxy="http://127.0.0.1:8080" 参数来设置。

翻完墙就可以装 Homebrew 了,我习惯所有命令行的工具都用它来管理。
更换它的源经常不好使,下载速度还是很慢,所以直接按上文改代理更好。

接着就是换终端了。
我使用了 kitty + Oh My Zsh + Powerlevel10k 的组合。
插件中最重要的是 fzf(模糊搜索历史命令)和 zsh-autosuggestions(自动补全)。
但是 kitty 没有原生的搜索,而是调用 less 的方式,这使得没法实时的增量搜索(需要按回车才能搜索,且增加字符时需要重新输入)。它可以和 fzf 配合模糊搜索,但是却无法跳转定位到该行,这对于查看日志的上下文并不友好。目前最好的方案是用 Search kitten for kitty 插件,虽然还有点体验上的问题,但有空可以自己改改。
iTerm2 也是一款很推荐的终端,优点是基本无需配置,除了性能差一点也没啥大毛病。
Warp 的优点很多,例如输入框可以用鼠标和 VIM 模式来快速定位,在不知道怎么写命令时可以直接问 AI,省去了 Google 的步骤。最大的缺点是闭源,不知道会不会泄漏数据;另一个缺点是内存泄漏,用几天后,即使关闭所有窗口,还是占用 1GB 内存。它的默认 prompt 不显示命令的执行时间,可以手动拖动配置;或者也可以选择更好看的 Powerlevel10k,需要把「Settings > Appearance > Prompt」切换成「Shell prompt (PS1)」,并且把「Settings > Appearance > Text > Enforce minimum contrast」设置为「Never」;但是在 ssh 时需要在远程机器上也安装 Powerlevel10k,所以为了一致性,不如用原装的。

基本工具安装完,就可以折腾输入输出了。

我以前一直使用百度输入法,可是近几年不知道啥时候引入了不会调整词频和无法删除错误词的 bug,于是这次换成了鼠须管。因为配置很麻烦,我直接使用了雾凇拼音的方案,并用 Rime auto deploy 维护自定义配置。
我用的是小鹤双拼,因此需要修改 rime-auto-deploy/custom/default.custom.yaml
patch:
  schema_list:
    - schema: double_pinyin_flypy
模糊音需要加个 rime-auto-deploy/custom/double_pinyin_flypy.custom.yaml 文件:
patch:
  speller/algebra:
    - erase/^xx$/
    - derive/^([zcs])h/$1/
    - derive/^([zcs])([^h])/$1h$2/
    - derive/^n/l/
    - derive/^l/n/
    - derive/ang/an/
    - derive/an/ang/
    - derive/eng/en/
    - derive/en/eng/
    - derive/in/ing/
    - derive/ing/in/
    - derive/ian/iang/
    - derive/iang/ian/
    - derive/uan/uang/
    - derive/uang/uan/
    - derive/^([jqxy])u$/$1v/
    - derive/^([aoe])([ioun])$/$1$1$2/
    - xform/^([aoe])(ng)?$/$1$1$2/
    - xform/iu$/Ⓠ/
    - xform/(.)ei$/$1Ⓦ/
    - xform/uan$/Ⓡ/
    - xform/[uv]e$/Ⓣ/
    - xform/un$/Ⓨ/
    - xform/^sh/Ⓤ/
    - xform/^ch/Ⓘ/
    - xform/^zh/Ⓥ/
    - xform/uo$/Ⓞ/
    - xform/ie$/Ⓟ/
    - xform/(.)i?ong$/$1Ⓢ/
    - xform/ing$|uai$/Ⓚ/
    - xform/(.)ai$/$1Ⓓ/
    - xform/(.)en$/$1Ⓕ/
    - xform/(.)eng$/$1Ⓖ/
    - xform/[iu]ang$/Ⓛ/
    - xform/(.)ang$/$1Ⓗ/
    - xform/ian$/Ⓜ/
    - xform/(.)an$/$1Ⓙ/
    - xform/(.)ou$/$1Ⓩ/
    - xform/[iu]a$/Ⓧ/
    - xform/iao$/Ⓝ/
    - xform/(.)ao$/$1Ⓒ/
    - xform/ui$/Ⓥ/
    - xform/in$/Ⓑ/
    - xlit/ⓆⓌⓇⓉⓎⓊⒾⓄⓅⓈⒹⒻⒼⒽⒿⓀⓁⓏⓍⒸⓋⒷⓃⓂ/qwrtyuiopsdfghjklzxcvbnm/
我不习惯输入标点时还需要选择,所以还改了下 rime-auto-deploy/custom/default.custom.yaml
patch:
  punctuator/half_shape:
    ',' : ','
    '.' : '。'
    '<' : '《'
    '>' : '》'
    '/' : '、'
    '?' : '?'
    ';' : ';'
    ':' : ':'
    "'" : { pair: [ '「', '」' ] }
    '"' : { pair: [ '『', '』' ] }
    '\' : '、'
    '|' : '|'
    '`' : '`'
    '~' : '~'
    '!' : '!'
    '@' : '@'
    '#' : '#'
    '%' : '%'
    '$' : '$'
    '^' : '……'
    '&' : '&'
    '*' : '*'
    '(' : '('
    ')' : ')'
    '-' : '-'
    '_' : '——'
    '+' : '+'
    '=' : '='
    '[' : '【'
    ']' : '】'
    '{' : '〖'
    '}' : '〗'
自定义短语可以添加到 ~/Library/Rime/custom_phrase_double.txt,内容参照 ~/Library/Rime/custom_phrase.txt。如果不想分开维护两个文件夹的话,可以编辑 rime-auto-deploy/os/MacOS.rb 里的 CopyCustomConfigJob 类,将其中的 .yaml 删掉即可同步 rime-auto-deploy/custom/ 里的所有内容到 ~/Library/Rime/

在不同应用间切换时,经常会出现输入法忘记切换而在 VIM 的普通模式下打不出字的问题。Input Source Pro 可以设置不同软件的默认输入法,还可以在切换到指定应用时显示当前输入法,避免出错。

我的机械键盘是 Windows 按键布局的,我物理交换了 alt 和 win 键以符合 Mac 的键盘布局,还需要在软件层面重新映射一下按键。这就需要用到 Karabiner-Elements 了。
在「Simple Modifications」里找到外接键盘就可以修改键位映射了。需要注意的是我的键盘左边的 win 键叫作 left_command,右边的菜单键是「Keys in PC keyboards」下的 application,而不是 right_command
功能键可以通过「Function Keys」修改,我把 f5 改为了 illumination_down,f6 改为了 illumination_up,这样就可以和老机型一样调整键盘背光灯了。
「Complex Modifications」可以进行一些复杂的映射,比如把一个键改成多个键,以及按住和单独使用时分别映射不同功能。我启用了如下规则:
  • 用 left_control 切换输入法,因为机械键盘上没有 fn 键:
    {
        "description": "left_control -> cmd + space",
        "manipulators": [
            {
                "from": {
                    "key_code": "left_control",
                    "modifiers": {
                        "optional": [
                            "any"
                        ]
                    }
                },
                "to": {
                    "key_code": "left_control",
                    "lazy": true
                },
                "to_if_alone": [
                    {
                        "key_code": "spacebar",
                        "modifiers": [
                            "left_command"
                        ]
                    }
                ],
                "type": "basic"
            }
        ]
    }
  • 将 left_shift 映射到 escape,用来切换到 VIM 的普通模式:
    {
        "description": "left_shift -> escape",
        "manipulators": [
            {
                "from": {
                    "key_code": "left_shift",
                    "modifiers": {
                        "optional": [
                            "any"
                        ]
                    }
                },
                "to": {
                    "key_code": "left_shift",
                    "lazy": true
                },
                "to_if_alone": [
                    {
                        "key_code": "escape",
                        "modifiers": []
                    }
                ],
                "type": "basic"
            }
        ]
    }
  • 将 right_alt 映射成 hyper 键:
    {
        "manipulators": [
            {
                "description": "right_alt -> ctrl + alt + cmd + shift",
                "from": {
                    "key_code": "right_alt",
                    "modifiers": {
                        "optional": [
                            "any"
                        ]
                    }
                },
                "to": [
                    {
                        "key_code": "left_shift",
                        "modifiers": [
                            "left_command",
                            "left_control",
                            "left_option"
                        ]
                    }
                ],
                "type": "basic"
            }
        ]
    }

鼠标和触摸板的手势则需要用到 Better And Better。它其实也支持键盘的改键,比如我把机械键盘的 Ps/Sr 键(被识别成了 F13)改成了 ctrl + shift + command + 4。
如果想让鼠标滚轮获得和触摸板一样的滚动体验,可以安装 MOS,将「平滑滚动」启用,「持续时间」调成 4.8 或以上。此外还需要禁用 Better And Better 的「平滑滚动」功能。如果只是想滚动快一点,就可以不装 MOS,只启用 Better And Better 的「平滑滚动」,然后调整滚动速度。

如果外接 4k 显示器,macOS 应该会自动启用 1920×1080(HiDPI)。但公司的显示器是 1440p 的,默认只能开启 720p 的 HiDPI,文字很大,显示范围又很小,不是很方便。
我运行了 RDM 后发现可以直接开启 1080p 的 HiDPI。调整完就可以退出甚至删除了,重启和插拔显示器都仍然有效。
如果这个工具不好用的话,可以参考这篇文章

接着就该安装开发工具了。
Visual Studio Code 自然是必装的。我尝试了下载量前十的主题,发现大部分要么对比度不够,要么很刺眼,而 Noctis 的 Minimus 主题是我肉眼看上去最舒服的。
在使用 VIM 模式时,我经常会遇到普通模式下用中文输入法无法切换到插入模式的问题。经过一番折腾,我把插件换成了 VSCode Neovim,安装 Neovimim-select,并给 Neovim 装上 brglng/vim-im-select 插件。这样配置后,从其他应用切换到 VSCode 时,如果是普通模式,就会自动切换成英文输入法。但是因为 VSCode 可以同时打开多个窗口和标签,在它们之间切换和打开新文件时,不会自动切换输入法,所以偶尔还是会遇到问题。
既然装了 Neovim,folke/flash.nvim 这个插件也是必装的。可惜 VSCode Neovim 强制绑定了 / 快捷键,但是还是能用 s 搜索。
Git 的 GUI 客户端 Sourcetree 也是少不了的,可视化地查看改动还是比命令行好用一些。
剩下必装的其实也就 OrbStack 了,它是 Docker Desktop 的替代,启动速度更快,还支持 k8s。它的配置中可以修改代理。
如果需要抓包的话,Wireshark 是很强大的工具,我学习很多协议都是靠它。

虽然已经可以开工了,但是还缺少一些提升效率的工具。
首先是启动器。我试了很多第三方的,发现其实用不到这么多功能,最终还是回到了系统自带的 Spotlight。但是因为太耗资源,我并不想用它搜索文件,所以在设置中找到「Siri 与聚焦」,在「搜索结果」里只勾选「计算器」、「系统设置」和「应用程序」即可。

然后是窗口管理。我试了台前调度、Rectangle 和 yabai 等方案,发现都不是很满意。我仔细思考了一下我的需求:大部分需要沉浸式体验时我会切换到全屏模式;那些杂乱无章的窗口其实主要是临时使用或随便开着,需要寻找时会用 ctrl + ↑ 打开调度中心;真正需要管理的其实是少数,也只需要左右分屏、最大化和居中显示而已。
于是我找到了 Hammerspoon。它其实是个提供了很多 Lua API 的工具,需要自己编写 Lua 脚本来定制功能。
先编辑一下 ~/.hammerspoon/init.lua,添加窗口管理功能:
local window = require "hs.window"
local hotkey = require "hs.hotkey"
local grid = require "hs.grid"

grid.GRIDWIDTH = 6  -- 把网格宽度分成 6 个部分
grid.GRIDHEIGHT = 1  -- 把网格高度分成 1 个部分
grid.MARGINX = 0
grid.MARGINY = 0

local hyper = {"ctrl", "alt", "cmd", "shift"}

grid.GRIDWIDTH = 6
grid.GRIDHEIGHT = 1
grid.MARGINX = 0
grid.MARGINY = 0

local middleGrid = {x = 1, y = 0, w = 4, h = 1}
local maxGrid = {x = 0, y = 0, w = 6, h = 1}
local leftGrid = {x = 0, y = 0, w = 3, h = 1}
local rightGrid = {x = 3, y = 0, w = 3, h = 1}

local function go(rect, currentWindow, count)
    if currentWindow == nil then
        currentWindow = window.focusedWindow()
        if currentWindow == nil then
            return
        end
    end

    if currentWindow:isFullScreen() then
        currentWindow:setFullScreen(false)
        hs.timer.doAfter(0.3, function()  -- 延迟到动画结束再检查一次
            go(rect, currentWindow, count)
        end)
    end

    if grid.get(currentWindow) == rect then
        return
    else
        grid.set(currentWindow, rect)

        if count == nil then
            count = 3  -- 最多调整 3 次就够了,避免出现极端情况下死循环
        else
            count = count - 1
        end

        if count >= 0 then
            hs.timer.doAfter(0.3, function()  -- 延迟到动画结束再检查一次
                go(rect, currentWindow, count)
            end)
        end
    end
end

hotkey.bind(hyper, "down", function() go(middleGrid) end)
hotkey.bind(hyper, "up", function() go(maxGrid) end)
hotkey.bind(hyper, "left", function() go(leftGrid) end)
hotkey.bind(hyper, "right", function() go(rightGrid) end)
这里的 hyper 键之前已经映射到 right_alt 了,Hammerspoon 不支持直接使用单个按键作为 hyper 键。
为了解决 Hammerspoon 没法一次性将窗口移动到位的 bug,这里添加了不少代码去处理。

再来实现一个移动窗口到其他屏幕的功能,我学了半天 Lua 才让它支持 3 屏幕和全屏模式:
local function moveToScreen(direction)
    local currentWindow = window.focusedWindow()
    if currentWindow == nil then
        return
    end

    if #hs.screen.allScreens() <= 1 then
        return
    end

    local srcIsFullScreen = currentWindow:isFullScreen()
    if srcIsFullScreen then
        currentWindow:setFullScreen(false)
    end

    local currentScreen = currentWindow:screen()
    local screen
    if direction then
        screen = currentScreen:next()
    else
        screen = currentScreen:previous()
    end

    local space = hs.spaces.activeSpaceOnScreen(screen)
    local destIsFullScreen = false
    if hs.spaces.spaceType(space) == "fullscreen" then
        -- 寻找一个非全屏的 space
        local spaces = hs.spaces.spacesForScreen(screen)
        for _, s in ipairs(spaces) do
            if hs.spaces.spaceType(s) == "user" then
                space = s
                break
            end
        end
        hs.spaces.gotoSpace(space)
        destIsFullScreen = true
    end

    if srcIsFullScreen or destIsFullScreen then
        hs.timer.doAfter(0.6, function()  -- 延迟到动画结束再执行,不然可能失败
            hs.spaces.moveWindowToSpace(currentWindow, space)
            if srcIsFullScreen then
                hs.timer.doAfter(0.6, function()
                    currentWindow:setFullScreen(true)
                    currentWindow:focus()  -- 如果不聚焦的话,下次再移动屏幕时,currentWindow:isFullScreen() 会返回 false
                end)
            end
        end)
    else
        hs.spaces.moveWindowToSpace(currentWindow, space)
    end
end

hotkey.bind(hyper, "pageup", function() moveToScreen(false) end)
hotkey.bind(hyper, "pagedown", function() moveToScreen(true) end)

再做个快速启动器,通过快捷键来启动或切换到这些应用:
local key2App = {
    b = "Google Chrome",
    c = "Code",
    f = "Finder",
    m = "Music",
    s = 'System Preferences',
    t = "Kitty",
    a = "Activity Monitor"
}

for key, app in pairs(key2App) do
    hotkey.bind(
        hyper,
        key,
        function()
            toggleApplication(app)
        end
    )
end

local function toggleApplication(appName)
    -- finds a running applications
    local app = application.find(appName)
    if not app then
        -- application not running, launch app
        application.launchOrFocus(appName)
        return
    end
    -- application running, toggle hide/unhide
    local mainwin = app:mainWindow()
    if mainwin then
        if app:isFrontmost() then
            mainwin:application():hide()
        else
            mainwin:application():activate(true)
            mainwin:application():unhide()
            mainwin:focus()
        end
    else
        -- no windows, maybe hide
        if app:isHidden() then
            -- focus app
            application.launchOrFocus(appName)
        else
            -- nothing to do
        end
    end
end

for key, app in pairs(key2App) do
    hotkey.bind(
        hyper,
        key,
        function()
            toggleApplication(app)
        end
    )
end

切换夜间模式:
hotkey.bind(
    hyper,
    "D",
    function()
        applescript([[
            tell application "System Events"
                tell appearance preferences
                    set dark mode to not dark mode
                end tell
            end tell
        ]])
    end
)
不过这个应用也有挺多 bug,比如上面提到的窗口没法一次调整到位,以及事件可能会没捕捉到等。

再关注一下美化相关的应用。
那个醒目的刘海需要先干掉。我装了个 pap.er,然后找了张顶部是黑色的壁纸,这样状态栏也会变成黑色,就看不到刘海了。外接显示器就无所谓了,可以换张亮色的平衡一下。
还有状态栏图标过多时会被刘海挡住的问题。首先自然是尽量在应用设置里隐藏不需要的图标;其次是按住 command 键来移动图标,把不常用的图标往左移;最后是装个软件把不需要的图标隐藏掉。免费的可以用 iBar,但它有时候不会自动显示隐藏的图标;愿意花钱的话也可以用 Bartender,功能更强大些;有精力也可以改造开源的 Hidden Bar,它的能耗是最低的。
此外,阻止我用深色模式的主要原因是很多网页没有适配深色模式,导致切换时会非常刺眼。Dark Reader 这个插件可以将大部分网页改成深色模式。

最后说下视频播放器吧。
最好的收费播放器是 Bunny,资源占用低,支持杜比视界。
最好的免费播放器是 IINA,资源占用低,但不支持播放杜比视界 Profile 5。
免费方案还可以考虑自带的 QuickTime Player,资源略高于前两者且比较波动,功能也很弱,支持 dvh1 编码的杜比视界。如果是 dvhe 编码的片源,可以用 mp4dovi 转换成 dvh1。
其他播放器要么不支持杜比视界,要么收费,且资源占用都高于 QuickTime Player,都不做推荐。

0条评论 你不来一发么↓

    想说点什么呢?