Arch Linux 星球

June 15, 2023

中文社区新闻

OpenBLAS >= 0.3.23-2 更新需要手动干预

openblas 包在 0.3.23-2 版本之前没有打包进优化过的 LAPACK 过程和它与 CBLAS/LAPACKE 接口的兼容性。我们现在改变了这个决定,并且现在用户可以选择安装另一个系统级默认 BLAS/LAPACK 实现同时保持安装 openblas 包,方便今后同时安装 BLIS, ATLAS 之类的包。
默认的 BLAS 实现会被用在大部分包中,比如 NumPy 或者 R。请安装 “blas-openblas” 和 “blas64-openblas” 包,让 OpenBLAS 成为默认的 BLAS 实现,就像以前的行为。
不过如果你现在已经安装了 OpenBLAS 作为默认 BLAS 实现,在更新时会遇到如下报错:

error: failed to prepare transaction (could not satisfy dependencies)
:: installing openblas (0.3.23-2) breaks dependency 'blas' required by cblas
:: installing openblas (0.3.23-2) breaks dependency 'blas' required by lapack

请在通常的 -Syu 命令后加上你想使用的默认 BLAS 实现来避开这个问题,比如:

pacman -Syu blas-openblas

pacman -Syu blas

by farseerfc at June 15, 2023 12:48 AM

June 07, 2023

Alynx Zhou

PipeWire 和 HDMI 音频和虚拟设备和复合/分离通道

这篇文章同时有 中文版本英文版本

This post is both available in Chinese version and English version.

中文版本

认识的朋友里很少有人有像我这么复杂的音频系统。长话短说,为了能让 PS4、Switch 和电脑分享一个扬声器,我把它接在了显示器上而不是电脑的内置声卡上,这样所有设备都通过 HDMI/DP 输出音频到扬声器。一开始这也没什么,后来我又添置了一块显示器,我发现在 Linux 下面经常搞不清楚究竟哪一个音频设备才是连接着扬声器的显示器,可能上周还是 HDMI 1,这周就变成 HDMI 2,而且也不是每次都会变,导致我经常需要试试才知道哪一个是我需要的。直到前天我忍不了了,决定发挥动手能力解决这个问题。

一开始我以为是 PipeWire 给设备排序的时候是随机排序的,那简单,只要我找到每个设备对应的 ID,然后关掉没有扬声器的那个 HDMI 输出就可以了。但是我发现似乎 PipeWire 只是按照 ALSA 给出的设备编号来排序,并没有自己编号,于是就算关闭一个设备,下次被关闭的也可能是另一个。然后我就在想难道 ALSA 没有固定 HDMI 音频设备的功能吗?毕竟就算是显示器也是有 EDID 这种东西的,于是我查了各种 ALSA 的资料,确实是可以通过 udev 指定不同声卡的顺序,但对于 HDMI 这种属于同一个声卡的不同端口的设备没什么办法。我甚至查到了 NVIDIA 关于显卡音频的文档,里面说每个端口会有一个叫做 ELD 的数据,描述了连接的显示器信息,不过通过 cat /proc/asound/cardX/eld* 查看之后我发现这个标准最多只给到显示器的型号,而我为了不在多显示器缩放上浪费精力,买了两台同样型号的显示器,没有序列号字段就还是没办法分辨不同的显示器!当然如果你的 HDMI 设备的型号不同,那其实就简单了,ALSA 现在会读取 ELD 里面的显示器型号,然后 PipeWire 会把这个作为 node.nick 属性,你可以直接通过这个属性分辨设备,也可以利用这个属性写 WirePlumber 重命名规则修改你的桌面环境会用到的属性,就可以固定名称了。不过我还得继续寻求帮助。

于是我就在公司的 research 邮件列表发了封邮件讲述了我的设备连接方式和需求,结果 Takashi Iwai(内核音频子系统的维护者之一)回复我说确实没有什么办法,音频驱动只是按照显卡给的顺序分配编号,所以大概率是随机的。特别是我还发现这玩意好像也不一定按照显示器输出的顺序来排号,于是 Plan A 是彻底行不通了。那我还有 Plan B 和 Plan C。

和其它同事给的建议一样,其中一个想法是购买一个硬件的混合器,把两台显示器的音频输出硬件连接到同一个扬声器的输入,甚至一个同事还给我画了电路图说你只要这样就能自己做一个了。不过这个方案既有优点也优缺点,优点是电脑和游戏机可以同时发声,缺点是我要在电脑上修改音量就得始终记得把两个音量都改成一样的。我对前者需求不大,所以打算最后再尝试这个。

当然有硬件的解法就有软件的解法,PipeWire 和 JACK 一样可以进行基于图的连接,那我只要搞一个虚拟的输出设备然后把两个 HDMI 设备跟它连一起不就行了?Arch Wiki 上恰好有一段 同时向一块声卡上的不同端口输出音频 的文档,我本来以为照做即可,但发现还是不对,并没有出现我想象中的一个新音频设备。不过后来我仔细研究,搞懂了里面各种术语,才知道是怎么回事。

首先我发现这一段文档其实只是描述如何创建一个“能同时显示两个 mapping 的 profile”,那到底什么是 mapping 什么是 profile?Mapping 可以理解成声卡上的某一种输入/输出组合,然后 profile 决定当前可以在哪几种组合中选择。举例来说就是假如你有一个 2 进 4 出的音频设备,那它可以是只有双声道输出,只有四声道输出,或者双声道输入四声道输出等等组合,这就是不同的 profile。为什么要同时输出不同端口需要创建一个 profile 呢,因为默认 ALSA 采用的是 auto-profile,会给每一个 mapping 创建一个 profile,而默认的一个 mapping 就是一个 HDMI 端口,因此假如你打开 pavucontrol 或者 Helvum,会发现如果不切换 profile,两个 HDMI 设备只能显示一个,也就没法给它们同时连接。当然你可能又会问为什么 GNOME Shell 里面又能显示两个 HDMI 设备?因为 libgnome-volume-control 是先枚举设备然后枚举端口,并不是直接枚举端口(受 profile 影响),选择端口的时候再自动切换 profile。

所以第一步是创建一个新的 profile sets,比如我创建的是 /usr/share/alsa-card-profile/mixer/profile-sets/hdmi-multiple.conf

[General]
auto-profiles = no

[Mapping hdmi-stereo]
description = Digital Stereo (HDMI)
device-strings = hdmi:%f
paths-output = hdmi-output-0
channel-map = left,right
priority = 9
direction = output

[Mapping hdmi-stereo-extra1]
description = Digital Stereo (HDMI 2)
device-strings = hdmi:%f,1
paths-output = hdmi-output-1
channel-map = left,right
priority = 7
direction = output

# If you have more HDMI devices, add them here.

# Show multiple HDMI mappings so I could connect to them all.
[Profile hdmi-multiple]
description = Multiple Digital Stereo (HDMI)
output-mappings = hdmi-stereo hdmi-stereo-extra1

上面的 mapping 是直接从 default.conf 里面抄的,下面那个 profile 就是包含上面的两个 mapping,然后需要写 WirePlumber 规则来给显卡上的声卡套用这个 profile。我把它写到 /etc/wireplumber/main.lua.d/51-hdmi-multiple.lua

rule = {
  matches = {
    {
      -- Sometimes PCI sound card name has `.1` or other suffix, so it's better
      -- to use description to match it.
      { "device.description", "matches", "TU104 HD Audio Controller" },
    },
  },
  apply_properties = {
    ["api.alsa.use-acp"] = true,
    -- By default, it creates profiles for each mappings, so one profile has one
    -- mapping, but I want to combine 2 mappings, so I have to manually create
    -- a profile to show 2 mappings.
    ["api.acp.auto-profile"] = false,
    ["api.acp.auto-port"] = false,
    ["device.profile-set"] = "hdmi-multiple.conf",
    ["device.profile"] = "hdmi-multiple",
  },
}

table.insert(alsa_monitor.rules, rule)

然后执行 systemctl --user restart wireplumber,Helvum 里面应该就能同时看到两个显示器的 HDMI 音频设备了。

接下来是 Arch Wiki 里面没有提到的部分,如何同时向两个设备输出音频?最简单的就是像 JACK 一样直接把输出的程序同时连接到两个音频设备上就行了,但这样既不能持久化,也不能在桌面环境里调节音量。阅读了 PipeWire 的文档之后发现这部分可以通过虚拟设备来解决,有一个叫做 combine-stream 的模块就可以创建这样的复合设备,于是参考 combine-stream 的文档,我创建 /etc/pipewire/pipewire.conf.d/10-hdmi-combined-sink.conf 并写入如下内容:

context.modules = [
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = sink
            node.name = "combined-hdmi-stereo"
            node.description = "Combined HDMI / DisplayPort"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "TU104 HD Audio Controller"
                #device.icon-name = "audio-card-analog-pci"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts glob. Match all HDMI devices on TU104.
                            media.class = "Audio/Sink"
                            # Sometimes PCI sound card name has `.1` or other
                            # suffix, so it's better to use description to
                            # match it.
                            node.description = "~TU104 HD Audio Controller Digital Stereo *"
                        }
                    ]
                    actions = {
                        create-stream = {
                            combine.audio.position = [ FL FR ]
                            audio.position = [ FL FR ]
                        }
                    }
                }
            ]
        }
    }
]

逻辑很简单,就是创建一个复合设备,输入到该设备的音频会输出给给定显卡上的所有 HDMI 输出,然后 systemctl --user restart pipewire wireplumber 就可以在 GNOME 里选择这个输出设备并调节音量了,不管扬声器插在哪个 HDMI/DP 显示器上,都能工作。

解决了这个问题之后我发现利用 PipeWire 的虚拟设备还可以解决我 USB 声卡的通道问题。我现在用的是我上高三时候买的 Scarlett 2i4,有两个输入和四个输出,而 auto-profile 就会自动把它设置成一个四声道环绕声输出和一个立体声输入,但实际上这个四个输出是双声道的耳机和双声道的扬声器,两个输入通常会分别用来输入话筒和乐器,而不是作为双声道输入。于是长久以来我只能在各种软件里手动设置单声道音频解决这个问题。而这次读文档我发现 PipeWire 早就给出了例子,虽然是针对另一款声卡(UMC404HD 的扬声器/耳机分离UMC404HD 的话筒/乐器分离),不过总而言之是大同小异,我也参照着弄了一下我的声卡。

首先你想要手动分离声卡的各个通道,仍然需要换掉默认的 profile,不过这次不需要手动编写了,PipeWire 给所有音频设备都提供了一个叫做 pro-audio 的 profile,这个会直接暴露声卡的所有通道而不做额外的假设(显然桌面环境对于这种裸配置的支持并不好),而后我们就可以为所欲为了,所以先创建 /etc/wireplumber/main.lua.d/51-scarlett-2i4.lua 写入规则让它默认使用 pro-audio

rule = {
  matches = {
    {
      { "device.name", "matches", "alsa_card.usb-Focusrite_Scarlett_2i4_USB-00" },
    },
  },
  apply_properties = {
    ["audio.rate"] = 48000,
    ["audio.allowed-rates"] = "44100,48000,88200,96000",
    --["api.alsa.period-size"] = 2048,
    --["api.alsa.headroom"] = 1024,
    ["api.alsa.use-acp"] = true,
    -- By default, it creates profiles for stereo input and surround 4.0 output,
    -- but actually the card is 2 inputs, stereo headphones output and stereo
    -- speakers output, so we disable auto profile here, and use the Pro Audio
    -- profile to expose all ports, and combine them manually.
    ["api.acp.auto-profile"] = false,
    ["api.acp.auto-port"] = false,
    ["device.profile"] = "pro-audio",
  },
},

table.insert(alsa_monitor.rules, rule)

然后 systemctl --user restart wireplumber,再打开 Helvum 应该能看到声卡不再被瞎推测为什么 LR RR 之类的声道,而是直接显示 AUX0~3,接下来就可以创建虚拟设备分别映射不同的通道了。

首先对于输出,我分离出耳机/扬声器两个不同的双声道虚拟输出设备,平时我只用耳机。这里和官方文档示例里声卡不同的地方是那款声卡后两个通道是耳机,而 Scarlett 2i4 前两个通道就是耳机,这也是为什么就算默认被当成四通道环绕声也能用的原因。总之在 /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-sinks.conf 里面写入如下的配置就可以了:

context.modules = [
    # See <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices#behringer-umc404hd-speakersheadphones-virtual-sinks>.
    #
    # Differs from UMC404HD, Scarlett 2i4 uses the first two outputs for headphones.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Headphones"
            capture.props = {
                node.name = "Scarlett_2i4_Headphones"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
            }
            playback.props = {
                node.name = "playback.Scarlett_2i4_Headphones"
                audio.position = [ AUX0 AUX1 ]
                target.object = "alsa_output.usb-Focusrite_Scarlett_2i4_USB-00.pro-output-0"
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Speakers"
            capture.props = {
                node.name = "Scarlett_2i4_Speakers"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
            }
            playback.props = {
                node.name = "playback.Scarlett_2i4_Speakers"
                audio.position = [ AUX2 AUX3 ]
                target.object = "alsa_output.usb-Focusrite_Scarlett_2i4_USB-00.pro-output-0"
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
]

执行 systemctl --user restart pipewire wireplumber 应该可以看到多了两个分别是 Scarlett 2i4 Headphones 和 Scarlett 2i4 Speakers 的音频输出设备。对于输入通道,我们也同理将它映射成两个单独的单声道虚拟输入设备,写到 /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-sources.conf

context.modules = [
    # See <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices#behringer-umc404hd-microphoneguitar-virtual-sources>.
    #
    # Differs from UMC404HD, Scarlett 2i4 can be two mono inputs or one stereo
    # input, depends on how we wire it in software.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Left Mono Input"
            capture.props = {
                node.name = "capture.Scarlett_2i4_Left_Mono_Input"
                audio.position = [ AUX0 ]
                stream.dont-remix = true
                target.object = "alsa_input.usb-Focusrite_Scarlett_2i4_USB-00.pro-input-0"
                node.passive = true
            }
            playback.props = {
                node.name = "Scarlett_2i4_Left_Mono_Input"
                media.class = "Audio/Source"
                audio.position = [ MONO ]
            }
        }
    }
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Right Mono Inputt"
            capture.props = {
                node.name = "capture.Scarlett_2i4_Right_Mono_Input"
                audio.position = [ AUX1 ]
                stream.dont-remix = true
                target.object = "alsa_input.usb-Focusrite_Scarlett_2i4_USB-00.pro-input-0"
                node.passive = true
            }
            playback.props = {
                node.name = "Scarlett_2i4_Right_Mono_Input"
                media.class = "Audio/Source"
                audio.position = [ MONO ]
            }
        }
    }
]

按理说到这里就结束了,但以防万一真的有人想用这款声卡做四声道环绕声输出,或者立体声输入,同理可以使用之前的 combine-stream 再把这些虚拟设备复合起来,可以在 /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-combined.conf 写入如下配置:

context.modules = [
    # Is there anyone who really uses Scarlett 2i4 for surround 4.0 output?
    # Anyway, we could achieve this with PipeWire's combine stream.
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = sink
            node.name = "Scarlett_2i4_Surround_4_0_Output"
            node.description = "Scarlett 2i4 Surround 4.0 Output"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR RL RR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "Scarlett 2i4"
                #device.icon-name = "audio-card-analog-usb"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            # To get better effect, treat headphones output as rear output.
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Speakers.
                            media.class = "Audio/Sink"
                            node.name = "Scarlett_2i4_Speakers"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ FL FR ]
                            combine.audio.position = [ FL FR ]
                        }
                    }
                }
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Headphones.
                            media.class = "Audio/Sink"
                            node.name = "Scarlett_2i4_Headphones"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ FL FR ]
                            combine.audio.position = [ RL RR ]
                        }
                    }
                }
            ]
        }
    }
    # To make it easier, we also use PipeWire's combine stream to make a stereo
    # input, so we don't need to wire manually.
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = source
            node.name = "Scarlett_2i4_Stereo_Input"
            node.description = "Scarlett 2i4 Stereo Input"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "Scarlett 2i4"
                #device.icon-name = "audio-card-analog-usb"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Left Mono Input.
                            media.class = "Audio/Source"
                            node.name = "Scarlett_2i4_Left_Mono_Input"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ MONO ]
                            combine.audio.position = [ FL ]
                        }
                    }
                }
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Right Mono Input.
                            media.class = "Audio/Source"
                            node.name = "Scarlett_2i4_Right_Mono_Input"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ MONO ]
                            combine.audio.position = [ FR ]
                        }
                    }
                }
            ] 
        }
    }
]

理论上来说,再创建虚拟设备直接连到物理通道应该也是可行的,但我尝试过之后连接图乱掉了,所以我换成 combine-stream 实现了。有一个要注意的点是我在环绕声里交换了一下通道,扬声器输出被我当作前面的音源,而耳机输出被我当作后面的音源,这样应该效果会更好,不过是和 auto-profile 假设的相反。

于是在购买这款声卡七八年之后我终于在 Linux 下面把它按我想要的用法划分了通道,同时发现 PipeWire 对复杂音频设备的处理确实比 PulseAudio 更加灵活,而和同样基于图和连接的 JACK 相比,又能同时控制不同的声卡,对于我这种设备复杂需求却不复杂的用户而言显然更加方便。

screenshot-1 screenshot-2 screenshot-3

English Version

I might be the only one who owns a complex audio setup among my friends. TL;DR: To share the only pair of speakers between PS4, Switch and computer I connect it to my monitor instead of internal sound card of my computer, so all devices can output audio to speakers via HDMI/DP. It's fine until I bought another monitor, it's hard to find which monitor is the one with speakers, maybe it's HDMI 1 this week and become HDMI 2 next week, so I always need to test before playing audio. I'm too angry to accept this recently, so I try to fix it by hand.

At first I guess PipeWire just randomly sorts audio ports, so it's easy to fix it, what I need to do is finding ID for each port and disabling the HDMI port without speakers. But soon I see PipeWire just sorts ports via ALSA's device number, so if I disable a port, the port might be the other monitor on next boot. Is there no way to let ALSA do a fixed mapping for HDMI audio devices? We all know monitors report EDID to system, and I read ALSA's document, it contains how to handle sequence of different sound cards via udev, but no way to handle ports on the same sound card like HDMI ports. I even find document of GPU audio from NVIDIA, it says each port has a ELD file, which contains monitor info, but if you try to read it with cat /proc/asound/cardX/eld*, you'll find it only contains model, not serial number, and I have two monitors of the same model in order to save time on dual-monitor scale, so they looks the same. But if your monitors/TVs are of different models, it is easier, ALSA will read model in ELD and you can access it via node.nick property of a PipeWire device, you can just read it, or write some WirePlumber rules to rename properties that your desktop environment uses, so you get a fixed name. But I need more help.

Then I send a Email to our company's research mailing list of my setup and demand, and Takashi Iwai (maintainer of kernel's audio subsystem) tell me there is no better way, audio driver just assign number when GPU driver notifies a new port, so it's dynamic. And I also find GPU driver may not emit ports as display probing sequence, so Plan A fails, but I have Plan B and Plan C.

Another colleague suggests me to buy a mixer hardware so I can connect two monitors into one pair of speakers, and he even draws a circuit diagram and says you could make one by yourself like this. I also considered this, it allows PC and game consoles play audio at the same time, but I have to manually sync volume of two audio devices on my PC. I don't need to play audio at the same time but I am lazy, so I decide to try this last.

If there is a hardware solution, there should be a software solution. PipeWire supports graph-based connection like JACK, then I could just create a virtual output device, and wire two HDMI devices to it. There is a section called Simultaneous output to multiple sinks on the same sound card on Arch Wiki, I thought I just need to follow it, but I was wrong, there is no new audio device. The I read more documents to understand the term and totally understand it.

First I find that section is only about how to create a "profile that shows both two mappings", but what is mapping and what is profile? Mapping is like one kine of combination of input/output on a sound card, and profile controls which kind of combination you could use. For example, if you have a sound device which has 2 input channels and 4 output channels, it could be a stereo output, or surround 4.0 output, or stereo input + surround 4.0 output, those are different profiles. And why you need to manually create a profile to simultaneously output to two sinks? Because by default ALSA does auto-profile which creates a profile for each mapping, and by default one mapping is for one HDMI port, so if you launch pavucontrol or Helvum, you'll find you can only see 1 of 2 HDMI devices if you don't switch profile, so you cannot wire them both. But you may also ask why GNOME Shell shows both of 2 HDMI sinks? Because libgnome-volume-control iterates sound cards first, and then ports on a sound card, not directly iterate ports (which could be effected by profile), and it will switch profile when you choose ports.

So the first step to do is create a new profile sets, I use /usr/share/alsa-card-profile/mixer/profile-sets/hdmi-multiple.conf:

[General]
auto-profiles = no

[Mapping hdmi-stereo]
description = Digital Stereo (HDMI)
device-strings = hdmi:%f
paths-output = hdmi-output-0
channel-map = left,right
priority = 9
direction = output

[Mapping hdmi-stereo-extra1]
description = Digital Stereo (HDMI 2)
device-strings = hdmi:%f,1
paths-output = hdmi-output-1
channel-map = left,right
priority = 7
direction = output

# If you have more HDMI devices, add them here.

# Show multiple HDMI mappings so I could connect to them all.
[Profile hdmi-multiple]
description = Multiple Digital Stereo (HDMI)
output-mappings = hdmi-stereo hdmi-stereo-extra1

I just copy mapping from default.conf, and the profile just contains those two mappings, and then write a WirePlumber rule to use this profile for GPU sound card. I write the rule into /etc/wireplumber/main.lua.d/51-hdmi-multiple.lua:

rule = {
  matches = {
    {
      -- Sometimes PCI sound card name has `.1` or other suffix, so it's better
      -- to use description to match it.
      { "device.description", "matches", "TU104 HD Audio Controller" },
    },
  },
  apply_properties = {
    ["api.alsa.use-acp"] = true,
    -- By default, it creates profiles for each mappings, so one profile has one
    -- mapping, but I want to combine 2 mappings, so I have to manually create
    -- a profile to show 2 mappings.
    ["api.acp.auto-profile"] = false,
    ["api.acp.auto-port"] = false,
    ["device.profile-set"] = "hdmi-multiple.conf",
    ["device.profile"] = "hdmi-multiple",
  },
}

table.insert(alsa_monitor.rules, rule)

And then run systemctl --user restart wireplumber, you should see both 2 HDMI sinks in Helvum now.

Then let's do steps which Arch Wiki does not contain, how to output audio to 2 sinks? The easiest way is manually wire output program to both 2 sinks, but that's not persistent, and you cannot control volume in desktop environment. After reading PipeWire's document, I find I could solve this via virtual devices, there is a module called combine-stream which could create such a combination device, so I just follow combine-stream's document, write following content into /etc/pipewire/pipewire.conf.d/10-hdmi-combined-sink.conf:

context.modules = [
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = sink
            node.name = "combined-hdmi-stereo"
            node.description = "Combined HDMI / DisplayPort"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "TU104 HD Audio Controller"
                #device.icon-name = "audio-card-analog-pci"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts glob. Match all HDMI devices on TU104.
                            media.class = "Audio/Sink"
                            # Sometimes PCI sound card name has `.1` or other
                            # suffix, so it's better to use description to
                            # match it.
                            node.description = "~TU104 HD Audio Controller Digital Stereo *"
                        }
                    ]
                    actions = {
                        create-stream = {
                            combine.audio.position = [ FL FR ]
                            audio.position = [ FL FR ]
                        }
                    }
                }
            ]
        }
    }
]

It's fairly easy to understand, just create a combination device, all audio streams point to this device will be send to all HDMI sinks on a GPU sound card, and run systemctl --user restart pipewire wireplumber you should be able to choose it as output sink and control its volume. No matter speakers are connected to which monitor, it should work.

Then I find I could solve the channel problem of my USB sound card. I still uses Scarlett 2i4 bought when I was in high school, it has 2 input channels and 4 output channels, and auto-profile will set it to a surround 4.0 output and stereo input, but those 4 output channels is made of a stereo headphone output and a stereo speaker output, the 2 input channels typically are used as mono microphone and mono instructment. I used to set mono input in different software to fix my microphone. But now I find there is a example of another sound card in PipeWire's document (Split speakers/headphones of UMC404HDSplit speakers/headphones of UMC404HD), but mostly they are the same, so I also tweak my sound card.

The same thing is to replace default profile in order to split each channels, but this time manually creating profile is not needed, PipeWire provides a pro-audio profile for all audio devices, it will expose all channels without assuming their usage (obviously, your desktop environment supports this badly), and then we could do what we need, so just create a rule to use pro-audio by default in /etc/wireplumber/main.lua.d/51-scarlett-2i4.lua:

rule = {
  matches = {
    {
      { "device.name", "matches", "alsa_card.usb-Focusrite_Scarlett_2i4_USB-00" },
    },
  },
  apply_properties = {
    ["audio.rate"] = 48000,
    ["audio.allowed-rates"] = "44100,48000,88200,96000",
    --["api.alsa.period-size"] = 2048,
    --["api.alsa.headroom"] = 1024,
    ["api.alsa.use-acp"] = true,
    -- By default, it creates profiles for stereo input and surround 4.0 output,
    -- but actually the card is 2 inputs, stereo headphones output and stereo
    -- speakers output, so we disable auto profile here, and use the Pro Audio
    -- profile to expose all ports, and combine them manually.
    ["api.acp.auto-profile"] = false,
    ["api.acp.auto-port"] = false,
    ["device.profile"] = "pro-audio",
  },
},

table.insert(alsa_monitor.rules, rule)

Then run systemctl --user restart wireplumber, and launch Helvum, the sound card now should has AUX0~3 instead of LR RR, and then create virtual devices that map to different channels.

For output channels, I create two devices for headphones and speakers, I typically only uses headphones. Which differs from the example is Scarlett 2i4 uses AUX0/1 for headphones instead of AUX2/3, and that's the reason why is works in surround 4.0 output profile. Anyway, just write those configuration into /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-sinks.conf:

context.modules = [
    # See <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices#behringer-umc404hd-speakersheadphones-virtual-sinks>.
    #
    # Differs from UMC404HD, Scarlett 2i4 uses the first two outputs for headphones.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Headphones"
            capture.props = {
                node.name = "Scarlett_2i4_Headphones"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
            }
            playback.props = {
                node.name = "playback.Scarlett_2i4_Headphones"
                audio.position = [ AUX0 AUX1 ]
                target.object = "alsa_output.usb-Focusrite_Scarlett_2i4_USB-00.pro-output-0"
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Speakers"
            capture.props = {
                node.name = "Scarlett_2i4_Speakers"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
            }
            playback.props = {
                node.name = "playback.Scarlett_2i4_Speakers"
                audio.position = [ AUX2 AUX3 ]
                target.object = "alsa_output.usb-Focusrite_Scarlett_2i4_USB-00.pro-output-0"
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
]

Then run systemctl --user restart pipewire wireplumber there should be 2 sinks called Scarlett 2i4 Headphones and Scarlett 2i4 Speakers. For input channels, I also map them into 2 mono virtual input devices, write configuration into /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-sources.conf:

context.modules = [
    # See <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices#behringer-umc404hd-microphoneguitar-virtual-sources>.
    #
    # Differs from UMC404HD, Scarlett 2i4 can be two mono inputs or one stereo
    # input, depends on how we wire it in software.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Left Mono Input"
            capture.props = {
                node.name = "capture.Scarlett_2i4_Left_Mono_Input"
                audio.position = [ AUX0 ]
                stream.dont-remix = true
                target.object = "alsa_input.usb-Focusrite_Scarlett_2i4_USB-00.pro-input-0"
                node.passive = true
            }
            playback.props = {
                node.name = "Scarlett_2i4_Left_Mono_Input"
                media.class = "Audio/Source"
                audio.position = [ MONO ]
            }
        }
    }
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Scarlett 2i4 Right Mono Inputt"
            capture.props = {
                node.name = "capture.Scarlett_2i4_Right_Mono_Input"
                audio.position = [ AUX1 ]
                stream.dont-remix = true
                target.object = "alsa_input.usb-Focusrite_Scarlett_2i4_USB-00.pro-input-0"
                node.passive = true
            }
            playback.props = {
                node.name = "Scarlett_2i4_Right_Mono_Input"
                media.class = "Audio/Source"
                audio.position = [ MONO ]
            }
        }
    }
]

Every thing should be done here, but just in case someone really uses this sound card for surround 4.0 output or stereo input, combine-stream also could be used to combine those virtual devices, it could be done via writing those contents into /etc/pipewire/pipewire.conf.d/10-scarlett-2i4-combined.conf:

context.modules = [
    # Is there anyone who really uses Scarlett 2i4 for surround 4.0 output?
    # Anyway, we could achieve this with PipeWire's combine stream.
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = sink
            node.name = "Scarlett_2i4_Surround_4_0_Output"
            node.description = "Scarlett 2i4 Surround 4.0 Output"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR RL RR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "Scarlett 2i4"
                #device.icon-name = "audio-card-analog-usb"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            # To get better effect, treat headphones output as rear output.
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Speakers.
                            media.class = "Audio/Sink"
                            node.name = "Scarlett_2i4_Speakers"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ FL FR ]
                            combine.audio.position = [ FL FR ]
                        }
                    }
                }
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Headphones.
                            media.class = "Audio/Sink"
                            node.name = "Scarlett_2i4_Headphones"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ FL FR ]
                            combine.audio.position = [ RL RR ]
                        }
                    }
                }
            ]
        }
    }
    # To make it easier, we also use PipeWire's combine stream to make a stereo
    # input, so we don't need to wire manually.
    {   name = libpipewire-module-combine-stream
        args = {
            combine.mode = source
            node.name = "Scarlett_2i4_Stereo_Input"
            node.description = "Scarlett 2i4 Stereo Input"
            combine.latency-compensate = false
            combine.props = {
                audio.position = [ FL FR ]
                # Those could be added here, but libgnome-volume-control only
                # read those for ports from cards, not for virtual / network
                # devices.
                #device.description = "Scarlett 2i4"
                #device.icon-name = "audio-card-analog-usb"
            }
            stream.props = {
                # Link matching channels without remixing.
                stream.dont-remix = true
            }
            stream.rules = [
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Left Mono Input.
                            media.class = "Audio/Source"
                            node.name = "Scarlett_2i4_Left_Mono_Input"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ MONO ]
                            combine.audio.position = [ FL ]
                        }
                    }
                }
                {
                    matches = [
                        # Any of the items in matches needs to match, if one
                        # does, actions are emited.
                        {
                            # All keys must match the value. `~` in value
                            # starts regex. Match Scarlett 2i4 Right Mono Input.
                            media.class = "Audio/Source"
                            node.name = "Scarlett_2i4_Right_Mono_Input"
                        }
                    ]
                    actions = {
                        create-stream = {
                            audio.position = [ MONO ]
                            combine.audio.position = [ FR ]
                        }
                    }
                }
            ] 
        }
    }
]

Theoretically creating new virtual devices wired to physical channels should also work, but my graph messed up after I tried it. Note that I swapped channels in surround 4.0 output, I use speakers sink for front and headphones sink for rear, which might leads into a better result, but it's opposite to what auto-profile generates.

So after owning this sound card for 7~8 years I finally tweaked it's channels as my will, and I find PipeWire is more flexible than PulseAudio on handling complex sound devices, and when compared with JACK which also uses graph and wire, PipeWire can control different sound cards, which is more convenient for users like me who have complex setup but simple demand.

screenshot-1 screenshot-2 screenshot-3

by Alynx Zhou (alynx.zhou@gmail.com) at June 07, 2023 08:47 AM

May 26, 2023

Lainme

自动上传剪贴板的图文到FTP

目前向公司研发内网传数据只能通过FTP,如果总是用Filezilla等FTP软件传输有时会显得比较繁琐,效率不高。一个可行的解决方案是编写一个能够将剪贴板的图片或者文字上传到FTP的脚本,并绑定到某个快捷键上,这样在复制文字或者图片后,就可以直接按快捷键实现传输。

Linux上的脚本可以从这里获取:https://github.com/lainme/dotfiles/blob/master/bin/clipboardToFTP.py

需要安装以下Python库才能使用:

这个脚本运行时会从命令行参数或者配置文件中读入FTP连接信息等配置,并将当前剪贴板内容上传到FTP,其中图片的保存名称是clipboard.png,文字的保存名称是clipboard.txt

通过命令行指定参数的方法如下:

python3 clipboardToFTP.py --host [FTP地址] \
    --username [用户名] --password [密码] --active \
    --remote [用于保存文件的FTP路径,默认是根路径]

通过配置文件指定参数的方法如下:

config.ini
[Server]
host=[FTP地址]
username=[用户名]
password=[密码]
active=true
remote=[用于保存文件的FTP路径,默认是根路径]

然后在命令行指定

python3 clipboardToFTP.py --config config.ini

这里的--active或者active=true表示用主动模式进行FTP连接,如果不需要可以省略。

如果你不想在命令行或者配置文件中写入密码,也可以使用系统keyring来存储密码。首先向keyring中增加密码,可以直接用Python的Keyring库,如下所示:

lainme@home-server:~$ python
Python 3.11.3 (main, Apr  5 2023, 15:52:25) [GCC 12.2.1 20230201] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> keyring.set_password("[FTP地址]", "[用户名]", "[密码]")

然后在命令行指定--keyring或者在配置文件中写入keyring=true即可。

by lainme (lainme@undisclosed.example.com) at May 26, 2023 10:09 PM

May 21, 2023

中文社区新闻

Git 迁移已完工

我们非常骄傲地宣布迁移到 Git 打包的工作已经成功完成! 🥳
感谢所有在迁移工作中提供过帮助的人!
打包源码现在可以从 GitLab 上获取。请注意我们的问题跟踪(bugtracker)工具仍然是 flyspray 并且目前还没有开放接受合并请求(merge request)。我们准备在不远的将来开放 GitLab 上打包的问题跟踪(issue tracker)和合并请求(merge request)功能。
镜像站恢复同步,不过你在用的镜像站可能需要一些时间跟上进度。

对用户而言

请更新系统并合并 pacman 的 /etc/pacman.conf.pacnew 配置文件的 pacnew 文件。因为我们将 [community] 软件仓库合并到了 [extra] 所以需要这一步。


$ pacman -Syu "pacman>=6.0.2-7"

对仍然在用已被抛弃的 asp 的用户,需要转而使用 pkgctl


$ pacman -Syu "devtools>=1:1.0.0-1"
$ pkgctl repo clone linux

关于具体如何使用和如何获取 PKGBUILD 请参阅相关 wiki 上的文档

对打包者而言

在继续之前,请先卸载 devtools-git-poc 并删除所有在 git 迁移测试期间克隆下来的所有本地仓库。
确保你的系统中同时安装了更新版本的 devtools 和 pacman:


$ pacman -Syu "devtools>=1:1.0.0-1" "pacman>=6.0.2-7"

此外请删除在 /var/lib/archbuild 中遗留的旧 chroot 环境:


$ rm -rf /var/lib/archbuild/
# 或者可选得, 用 --clean 选项来执行 *一次* pkgctl build
$ pkgctl build --clean

关于如何使用 pkgctl 打包,请参阅 “How to be a packager” wiki 文章 并查询相关子命令的手册页获取更多信息:


$ man pkgctl-build
$ man pkgctl-repo-clone

by farseerfc at May 21, 2023 11:56 AM

May 16, 2023

中文社区新闻

Git迁移通知

本周五(2023-05-19)早晨开始至本周日(2023-05-21)我们将进行 Git 的打包迁移。整个 Arch Linux 打包组将无法在这期间内更新任何软件仓库中的软件包。
关于迁移进展何时开始何时结束的通知将发布在 [arch-dev-public] 邮件列表。

这会对 Arch Linux 用户有何影响?

首先 [testing] 仓库会被分成 [core-testing][extra-testing] ,同样 [staging] 仓库会被分成 [core-staging][extra-staging] 。然后 [community] 仓库会合并入 [extra] ,因此前者在迁移后会变成空的。
所有受影响的仓库在迁移后的一段时间内会保持空仓库的状态。这意味着对普通用户而言,所有东西都应该像往常一样继续工作。
注意: 在迁移后,开启了 testing 仓库的用户需要在更新系统之前,在 pacman.conf 中改用 [core-testing][extra-testing] 仓库。

伴随的其它变更:

  • 将停止SVN访问方式,之后将删除它。
  • svn2git 镜像将不再更新。
  • asp工具因为依赖 svn2git 镜像,将不再工作。它被替换为 pkgctl repo clone

这会对 Arch Linux tier1 镜像站有何影响?

迁移期间 rsync 和 HTTP 访问会关闭。所有工作完成之后我们会向 arch-mirrors 发送邮件通知。

这会对 Arch Linux 打包者有何影响?

打包者不能更新和修改他们的软件包。内部用的 tier0 镜像也会在迁移期间停止访问。

by farseerfc at May 16, 2023 01:54 AM

May 08, 2023

Alynx Zhou

都不能算是 GNOME 的 Bug

Arch Linux 的官方仓库里终于有 GNOME 44 了,今天更新了一下系统,在思考出怎么解决 DaVinci Resolve 一定要去加载 onetbb 里面 intel 的 OpenCL 实现之前,我遇到了一个更奇怪的问题:所有的 XWayland 程序都显示不出来窗口,程序启动了,没有报错,但是点不到。

忘了我当时在查什么反正看了一下 journalctl -f 发现一直刷一个 mutter-x11-frames core dump 的 log,我想起来 mutter 44 应该是把 X11 程序的 decoration 挪到单独的 client 里面实现了,所以也许是 mutter 的问题,不过我还是尝试用 gdb 看了一下 backtrace:

Thread 1 "mutter-x11-fram" received signal SIGSEGV, Segmentation fault.
___pthread_mutex_lock (mutex=0x123) at pthread_mutex_lock.c:80
Downloading source file /usr/src/debug/glibc/glibc/nptl/pthread_mutex_lock.c
80        unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);                                                                                                                                                                                             
(gdb) bt
#0  ___pthread_mutex_lock (mutex=0x123) at pthread_mutex_lock.c:80
#1  0x00007ffff685aaf6 in wl_proxy_create_wrapper (proxy=proxy@entry=0x55555558e510) at ../wayland-1.22.0/src/wayland-client.c:2446
#2  0x00007ffff2ad337c in getServerProtocolsInfo (protocols=0x7fffffffdc70, nativeDpy=0x55555558e510) at ../egl-wayland/src/wayland-egldisplay.c:464
#3  wlEglGetPlatformDisplayExport (data=0x5555555ae000, platform=<optimized out>, nativeDpy=0x55555558e510, attribs=<optimized out>) at ../egl-wayland/src/wayland-egldisplay.c:580
#4  0x00007ffff26acfa0 in  () at /usr/lib/libEGL_nvidia.so.0
#5  0x00007ffff264c71c in  () at /usr/lib/libEGL_nvidia.so.0
#6  0x00007ffff2ba4885 in GetPlatformDisplayCommon (platform=12760, native_display=0x55555558e510, attrib_list=0x0, funcName=0x7ffff2baad18 "eglGetDisplay") at ../libglvnd-v1.6.0/src/EGL/libegl.c:324
#7  0x00007ffff7a19357 in gdk_display_create_egl_display (native_display=0x55555558e510, platform=12757) at ../gtk/gdk/gdkdisplay.c:1484
#8  gdk_display_init_egl (self=0x5555555a1820, platform=12757, native_display=0x55555558e510, allow_any=0, error=0x5555555a17f8) at ../gtk/gdk/gdkdisplay.c:1667
#9  0x00007ffff79edb53 in gdk_x11_display_init_gl_backend (error=0x5555555a17f8, out_depth=0x5555555a18c4, out_visual=0x5555555a18c8, self=0x5555555a1820) at ../gtk/gdk/x11/gdkdisplay-x11.c:2975
#10 gdk_x11_display_init_gl (display=0x5555555a1820, error=0x5555555a17f8) at ../gtk/gdk/x11/gdkdisplay-x11.c:3013
#11 0x00007ffff7a198f0 in gdk_display_init_gl (self=0x5555555a1820) at ../gtk/gdk/gdkdisplay.c:1248
#12 gdk_display_prepare_gl (self=0x5555555a1820, error=0x0) at ../gtk/gdk/gdkdisplay.c:1320
#13 0x00007ffff79ec355 in gdk_x11_display_open (display_name=<optimized out>) at ../gtk/gdk/x11/gdkdisplay-x11.c:1479
#14 0x00007ffff7a15c62 in gdk_display_manager_open_display (manager=<optimized out>, name=0x0) at ../gtk/gdk/gdkdisplaymanager.c:431
#15 0x00007ffff777dda9 in gdk_display_open_default () at ../gtk/gdk/gdk.c:331
#16 gtk_init_check () at ../gtk/gtk/gtkmain.c:621
#17 gtk_init_check () at ../gtk/gtk/gtkmain.c:603
#18 0x00007ffff777dfee in gtk_init () at ../gtk/gtk/gtkmain.c:659
#19 0x0000555555557070 in main (argc=<optimized out>, argv=<optimized out>) at ../mutter/src/frames/main.c:56

然后我就在想看起来是 GTK4 的问题,我还去群里问了一下有没有 GNOME + NVIDIA 的用户,看看是我的问题还是 bug,不过没人理我,还差点把我恶心到了。然后我想了一下试了 GDK_BACKEND=x11 nautilus 发现也有一样的问题,就跑到 GTK 那边提了个 issue,结果那位有点出名的毒舌老哥跟我说看着不像是 GTK 的问题倒像是 nvidia 的问题,我也怀疑过,但我检查了一下和 nvidia 相关的都没什么变化,然后我去翻 glvnd 和 egl-wayland 的仓库也没翻出什么。换到 KDE 下面还是一样有问题。但我突然想到会不会和我设置的一些环境变量有关系,于是就去注销了一大片,结果就好了。最后我看了一下好像有 platform,发现是我设置过一个 EGL_PLATFORM=wayland 的环境变量,删掉这个就好了。

我想了一下这应该是我当初弄 Firefox 的硬件解码视频时候设置的,果不其然在 https://github.com/elFarto/nvidia-vaapi-driver#firefox 里面写了,看起来是因为这个变量导致 XWayland 程序加载 EGL 的时候把 platform 当成了 Wayland,不过我没想清楚为什么滚系统之前没有遇到这个问题。

总之这是个不能算 bug 的问题了,如果我在群里问的时候有人回我,我就能直接排除法发现是我自己配置的问题,结果提了 issue 以后发现不是上游的问题感觉很尴尬。想了一下还是决定把这个记在这里,因为我推测有很多人看了 nvidia-vaapi-driver 的文档,说不定也设置了这个变量然后遇到了同样的问题,记录下来方便搜到。

by Alynx Zhou (alynx.zhou@gmail.com) at May 08, 2023 10:52 AM

March 26, 2023

Justin Wong

为什么说 GPT 是无损压缩

上周一从 Tim 处听到一个“暴论”:大语言模型本质上是无损压缩。可惜聊天的时间太短了,Tim 说不完这个理论。经过一段时间的网上冲浪,我也从多个地方

March 26, 2023 12:00 AM

March 20, 2023

Justin Wong

握着你的手训一个类GPT语言模型 (二)

书接上回,我们搞了个最简单的胡说八道机,距离GPT其实只差更科学的模型结构了。本文中公式较多,嚼不动的同学建议参看一些图文并茂的科普文。 上下

March 20, 2023 12:00 AM

March 12, 2023

Justin Wong

握着你的手训一个类GPT语言模型 (一)

这段时间 ChatGPT 大有 AI 奇点将至的势头,它展现的各种能力也的确很惊人,让人忍不住想要复刻一个。不过我从来没搞过 NLP 相关领域,机缘巧合在 youtube 上看见 Andrew Karpathy 的视

March 12, 2023 12:00 AM

March 05, 2023

Lainme

在ThinkPad Neo 14上安装ArchLinux遇到的问题和部分解决方法

无法识别麦克风

扬声器是正常工作的,但找不到麦克风设备,目前这一问题只能通过自己给内核打Patch编译来解决。

Kernel.org上的BUG报告如下:https://bugzilla.kernel.org/buglist.cgi?quicksearch=neo%2014

基本的内核编译方法请参考Arch Wiki:https://wiki.archlinux.org/title/Kernel/Arch_Build_System

编译的时候不需要修改prepare()函数的内容和更新checksum,只需要把patch放到PKGBUILD的同目录,并增加到source=()一栏,相应的sha256sums写SKIP就可以。

patch如下(这是在6.2.2版本下生成的,如果是别的版本可能第一行的行号会有所不同)

--- a/sound/soc/amd/yc/acp6x-mach.c
+++ b/sound/soc/amd/yc/acp6x-mach.c
@@ -49,6 +49,13 @@
 		.driver_data = &acp6x_card,
 		.matches = {
 			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "21EF0002CD"),
+		}
+	},
+	{
+		.driver_data = &acp6x_card,
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
 			DMI_MATCH(DMI_PRODUCT_NAME, "21D0"),
 		}
 	},

之后编译安装,增加用自己编译的内核启动的启动项。

然后还需要修改/etc/pulse/default.pa文件,将里面的

load-module module-udev-detect

改成

load-module module-udev-detect use_ucm=0 

然后重新启动,看麦克风是否正常。

VirtualBox启动报错

启动时报错“Failed to create the VirtualBoxClient COM object”。安装libvpx可以解决

$yaourt -S libvpx

休眠 (suspend) 不工作

如果用s2idle模式,休眠后可以唤醒,但唤醒时nvme就掉了,各种fs error,只能强制重启,没有找到解决方法。如果用s3模式,休眠后无法唤醒,只能强制重启,没有找到解决方法。

最后把各种target都mask了,眼不见心为静

$sudo systemctl mask sleep.target suspend.target hybrid-sleep.target

Wayland拖影和停止响应的问题

移动窗口的时候会有拖影,界面毫无征兆的会卡住。刚开始以为是硬件不兼容或者什么驱动问题,后来换Xorg登陆就正常了,所以是Wayland自身的问题。

MailSpring随系统最小化自启动时无法从托盘打开

MailSpring如果设置了随系统自启动(通过~/.config/autostart),启动后点击托盘上的图标会没有反应,托盘插件是gnome-shell-extension-appindicator。

没有找到解决方法,刚开始尝试了延迟启动也没有用,现在只能暂时关掉了自启动。

by lainme (lainme@undisclosed.example.com) at March 05, 2023 06:42 PM

在ArchLinux上安装H3C的iNode客户端

之前写过在CentOS7上安装H3C的iNode客户端,在Arch上的安装方式基本一致,只是需要额外解决几个小问题。

首先在执行/opt/iNodeClient/install_64.sh之前准备以下依赖的动态连接库

$ yaourt -S ncurses5-compat-libs libxcrypt-compat libjpeg6-turbo 

由于iNodeClient的安装脚本是通过rc.local来启动其服务的,并且会把服务脚本放到/etc/init.d,所以我们先手动创建init.d目录

$ sudo mkdir -p /etc/init.d/

并新建/etc/systemd/system/rc-local.service,内容如下

[Unit]
Description=/etc/rc.local
ConditionPathExists=/etc/rc.local

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
StandardOutput=tty
RemainAfterExit=yes
SysVStartPriority=99

[Install]
WantedBy=multi-user.target

然后激活这个服务

$ sudo systemctl enable rc-local.service

当然,你也可以注释掉安装脚本里相应的内容,后面直接用systemd来启动服务。

之后就可以执行安装脚本了,执行完成后需要手动把desktop文件移动过去(脚本只针对它认识的发行版执行这个操作,Arch不在列表里……)

$ sudo cp /opt/iNodeClient/iNodeClient.desktop /usr/share/applications/

这样应该就可以运行了。这部分是我安装好后回忆的,或许有疏漏,发现了再补充吧。

by lainme (lainme@undisclosed.example.com) at March 05, 2023 06:10 PM

百合仙子

Linux 上的字体配置与故障排除

本文来自依云's Blog,转载请注明。

常见汉字字体

电脑系统要显示字,首先得有字体。现在 Linux 上常用的、在维护的开源中文字体就一套,同时被 Noto思源两个项目收录。Noto 系列字体是 Google 主导的,名字的含义是「没有豆腐」(no tofu),因为缺字时显示的方框或者方框被叫作「tofu」。思源系列字体是 Adobe 主导的。其中汉字部分被称为「思源黑体」和「思源宋体」,是由这两家公司共同开发的,两个字体系列的汉字部分是一样的。

Noto 字体在 Arch Linux 上位于以下软件包中:

  • noto-fonts: 大部分文字的常见样式,不包含汉字
  • noto-fonts-cjk: 汉字部分
  • noto-fonts-emoji: 彩色的表情符号字体
  • noto-fonts-extra: 提供额外的字重和宽度变种

Noto 系列字族名只支持英文,命名规则是 Noto + Sans 或 Serif + 文字名称。其中汉字部分叫 Noto Sans/Serif CJK SC/TC/HK/JP/KR,最后一个词是地区变种。

思源系列则有:

  • adobe-source-sans-fonts: 无衬线字体,不含汉字。字族名叫 Source Sans 3 和 Source Sans Pro,以及带字重的变体,加上 Source Sans 3 VF
  • adobe-source-serif-fonts: 衬线字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体
  • adobe-source-code-pro-fonts: 等宽字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体,加上 Source Code Variable。
  • adobe-source-han-{sans,serif,mono}-{cn,hk,jp,kr,tw}-fonts: 五个地区的汉字之黑体、宋体和等宽版本
  • adobe-source-han-{sans,serif,mono}-otc-fonts: 所有地区合体了的汉字之黑体、宋体和等宽版本

其中等宽版本的中文字体位于 [archlinuxcn] 仓库中。

思源汉字字体的字族名有两种,「独立包装」的版本(非 OTC 版本),是「Source Han Sans/Serif」或本地化名称、空格、地区代码(CN/HK/TW/JP/KR)。比如「思源黑体 CN」、「源ノ角ゴシック JP」等。也有带字重的别名。

而全部打包的 OTC 版本,字族名是本地化名称或者英文的「Source Han Sans/Serif」空格再加上「HC/TC/HC/K」变种代码。如果没有变种代码,则是日文变种。为了区分,香港繁体的版本附带「香港」字样,比如黑体叫「思源黑體 香港」。这些字体也有不同字重的别名。另外有个半宽的版本,是在字族名的变种代码前加「HW」字样,仅有少数几个字符是半宽的。

OTC 版本有趣的地方在于,对于大多数软件来说,不管你叫它的哪个地区的名字,它都会以设定的语种来显示。比如网页声明语种为日文(<html lang=ja>),那么不管字体指定为「源ノ角ゴシック」还是「思源黑体」或者「본고딕」,它都会「门上插刀、直字拐弯、天顶加盖、船顶漏雨」。所以用这个字体的话,不妨一律写「Source Han Sans」,然后加好语种标记。我知道的唯一例外是 mpv 的 ass 字幕文件,里边指定本地化名称的话,会使用那个语种的变体显示。

早些年还没有 Noto 和思源的时候,Linux 系统上通常使用文泉驿正黑或者文泉驿微米黑。后者是基于 Android 系统上的 Droid Sans Fallback 字体,体积较小。再之前是文鼎系列字体,也就是名字「AR PL」开头、包名叫 ttf-arphic-{uming,ukai} 的那些。

字体的属性

字体有很多属性,常用的有字族(family)、倾斜(slant)、字重(weight)。后两者合一起叫样式(style)。

字族就是它的名字啦。常见的指代字体的方式除了字族之外还有 Postscript 名,它不含空格、使用短横线将样式附加在名称之后,比如「DejaVuSans-BoldOblique」。后者是 CSS @font-face 规则中使用 local唯一指定样式的方法(除非该字体把样式也写到了字族名里)。

倾斜就是斜不斜,英文叫「Roman」「Italic」或者「Oblique」,Italic 是专门的斜体写法(更接近手写样式), Oblique 是把常规写法倾斜一下完事。

字重就更简单了,就是笔划的粗细。常见的有 Regular、Normal、Medium、Bold、Semibold、Black、Thin、Light、Extralight 等。

详细信息可以 man 5 fonts-conf 查询。

通用字族名

很多时候,程序并不在乎用户具体使用的是哪款字体,像很多网站的 CSS 那样把各个平台的常见字体全部列出来太傻了,又容易出问题。所以,人们发明了「通用字族名」,也就是 sans-serif (sans)、serif 和 monospace (mono) 这些。中文分别叫无衬线字体、衬线字体和等宽字体。但是中文字体不讲衬线不衬线的,而是叫「黑体」和「宋体」(有些地区叫「明体」)。黑体常用于屏幕显示的正文,而宋体常用于印刷文本的正文。

另外,中文没有斜体。英文使用斜体的场合,中文通常是使用仿宋或者楷体。中文本也没有粗体。传统上,强调的时候,中文使用着重号,也就是在字的下方或者右方加点,像这样子

最近有一个新加的通用字族名叫作「emoji」。Pango 渲染表情符号的文本时,会自动使用 emoji 字体。但是 Qt 尚不支持,导致有时会出问题,而将 emoji 字体排到常规字体之前的做法,又会导致数字和空格显示为全角。火狐自带了一个 SVG 格式的 emoji 字体,会自动使用。很多软件(比如 Telegram)也会使用图片来取代 emoji 字符。

CSS 4 又加了一套 ui- 开头的字族名但是除了 Safari 没浏览器支持。fontconfig 倒是可以通过配置来支持上,但是由于火狐的一个 bug 导致 ui-sans-serif 无效。

fontconfig 配置

大部分 Linux 桌面软件都或多或少地使用 fontconfig 来获取字体配置信息。其中 Pango(GTK 使用的文字渲染库)的支持是最好的。很多简陋的图形界面库则只用来读取默认字体,可能完全不支持字体回落,造成部分文字明明有字体却显示为「豆腐」。

了解了通用字族名,我们就可以为它们指定我们喜欢的字体啦。在 ~/.config/fontconfig/fonts.conf 里为每一个通用字族名像这样写即可:

  <match target="pattern">
    <test qual="any" name="family">
      <string>sans-serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans</string>
      <string>文泉驿正黑</string>
      <string>Twemoji</string>
      <string>Font Awesome 6 Free</string>
      <string>Font Awesome 6 Brands</string>
      <string>Source Han Sans</string>
    </edit>
  </match>

因为我并没有完全采用思源字体来显示汉字,所以我还是为不同语言和地区变种分别匹配了不同的字体。我完整的配置文件见:https://github.com/lilydjwg/dotconfig/tree/master/fontconfig。其中,web-ui-fonts.conf 文件用于提供 CSS 4 新增的字族名,而 source-han-for-noto-cjk.conf 则使用思源系列字体来代替 Noto CJK 系列字体。

查看浏览器使用的字体

排查字体问题时,一个常见的要知道的事实是,软件究竟在用什么字体来显示这些文本?想知道这个通常很难,但是对浏览器来说却很简单。所以字体匹配问题首先看浏览器能不能复现。

火狐浏览器,对着有疑问的字点右键,选择「检查」(也可以按 Q 键),然后看弹出的开发者工具右边的「字体」选项卡即可。鼠标悬停到下方灰色的字体名上时还能将使用该字体的字高亮显示。

在火狐中查看网页所使用的字体

Google Chrome 浏览器及其变种类似,对着有疑问的字点右键,选择「检查」(也可以按 N 键),然后看弹出的开发者工具右边的「计算样式」选项卡,拖动到最下面,可以看到使用的字体名以及有多少个字形。

在 Google Chrome 中查看网页所使用的字体

至于这个字体是怎么选上的,可以切换到「规则」(火狐)或者「样式」(Google Chrome)选项卡来看 CSS 规则。搜索「font-family」看看具体被应用上的规则是哪一条。通常这里会写上一大排字体名。火狐会将正在使用的那个加上下划线,但是有时候不准确(比如该 HTML 元素使用了多种字体)。更好的除错方法是,从头到尾一个个删字体,删到哪一个时网页上的字体变动了,就说明在使用的是哪一个。我通过这种方式找出了好些我学生时代不懂事从 Windows 下复制过来的字体导致的问题。

Google Chrome 默认的字体比较奇怪,是「Times New Roman」、「Arial」和「Monospace」。见《Google Chrome 中的字体设置》一文。

Qt

https://z.sh/qtfontbugs。其中最著名的 bug 是 QTBUG-80434 (https://z.sh/434)。

小技巧

使用 gucharmap 软件可以检查所有字符使用指定的字体时的渲染效果,以及它回落到什么字体上了。找到要查看的字符,然后对着它按住右键即可。

使用 fc-match -s NAME:charset=HHHH 可以查看针对指定字符的字体优先顺序,包含这个字符的字体会优先。如果不加 -s 就是看指定的模式会匹配上的字体了。其中 HHHH 是该字符的 Unicode 码点之十六进制值。如 fc-match :charset=7684 查看默认字体下「的」字会用什么字体,而 fc-match serif:charset=7684:lang=ja 查看在语种为日文的时候,使用 serif 字族名会使用哪个字体来显示「的」字。使用 fc-list :charset=HHHH 则是查看包含该字符的所有字体。

参考资料

by 依云 at March 05, 2023 03:27 PM

February 14, 2023

中文社区新闻

切换到 base-devel 元包需要手动干预

从2月2日开始,以前的 base-devel 包组(package group)被替换成了同名的元包(metapackage)。
如果在此之前你装过 base-devel 包组(package group),请在系统中显式地再安装一次新的 base-devel 元包(metapackage):

pacman -Syu base-devel

by farseerfc at February 14, 2023 01:44 AM

February 09, 2023

百合仙子

新的 PaddleOCR 部署方案

本文来自依云's Blog,转载请注明。

PaddleOCR 发布 2.6 版本了,支持 Python 3.10 啦,于是可以在 Arch Linux 上跑了~所以我决定再部署一次。

我之前跑 PaddleOCR 有两个方案,使用 chroot 加一大堆 systemd 的限制选项,以及使用 bwrap 和用户命名空间。

chroot 的方案总感觉不知道限制够了没。实际上当初那篇文章写完我就意识到这服务怎么用我的 uid 在跑啊,乱发信号好像还能把我的进程都杀掉的样子。另外这个 chroot 其实是我用来学习、研究和适配 Debian 用的,并不是专门跑这个服务的,感觉有点——怎么说呢——碍事?总之不太好。

bwrap 方案更干净一些,不过创建起来挺麻烦的(所以我才只部署了一次嘛)。不使用用户命名空间可能会简单一些,但那样就是用我的用户在跑了。

所以这次我决定试试方案,使用 systemd-nspawn。另外(再次)尝试了使用 NVIDIA GPU 的版本,把我电脑上闲得发慌的 GeForce 940MX 显卡给用上了。

过程

首先去 Arch 镜像里的 iso/latest/ 目录下载个 archlinux-bootstrap-x86_64.tar.gz 回来。在 /var/lib/machines 下创建个叫 paddleocr 的 btrfs 子卷 / zfs 文件系统 / 普通目录用来存放新的 rootfs。sudo bsdtar xf ...... -C /var/lib/machines/paddleocr 解压出来。记得一定要用 bsdtar 以避免丢失某些文件元信息(虽然我不知道那些信息有啥用但是有警告就是不爽嘛)。

然后就可以 systemd-nspawn -M paddleocr 拿到个 shell 了。这里边只安装了 base 和 arch-install-scripts。可以先修改 pacman 镜像然后 pacman -Syu python 滚一下顺便装上 Python。然后 useradd -s /bin/bash -m -U paddleocr 创建个跑 paddleocr 的用户。su - paddleocr 切过去,python -m venv venv 创建虚拟环境,然后进去按 PaddleOCR 的文档装就行了。装好运行起来没问题之后,写个 for 循环把所有支持的语种都识别一遍,以下载各语言的模型(当然你也可以只下载你想要的)。做好之后可以清一下缓存啥的。gdu 就挺好用的。

哦,以上是 CPU 版本的安装流程。GPU 版本的可没有这么简单。首先要把显卡设备传进这个 nspawn 里。创建 /etc/systemd/nspawn/paddleocr.nspawn 文件,然后里边写上:

[Exec]
ResolvConf=off
NoNewPrivileges=true
User=paddleocr

[Files]
Bind=/run/paddleocr
Bind=/var/cache/pacman/pkg

Bind=/dev/nvidia0
Bind=/dev/nvidiactl
Bind=/dev/nvidia-modeset
Bind=/dev/nvidia-uvm
Bind=/dev/nvidia-uvm-tools

[Network]
Private=true

哦,这里有挂载 pacman 缓存目录前边忘了说,不过这个不重要啦。这里指定了用户,但是可以在命令行上用 -u root 覆盖的,不影响进去维护。私有网络,也就是给它配置个网络命名空间,里边除了 lo 外啥网络接口都没有。那它怎么访问网络呢?它访问不了网络啦。所以要 bind mount 进去一个 /run/paddleocr,用于通信的 UNIX 域套接字将会放在这里。网络不通,走文件系统就好啦。

然后找台机器把 AUR 包 cuda-10.2 和 cudnn7-cuda10.2 打一下,但是不用安装。我们不搞 CUDA 开发,里边有一大堆东西都是不需要的。把需要的库复制进 rootfs 里去就行了。至于需要什么库?进那个虚拟环境的 Python 里,import paddle 然后 paddle.utils.run_check() 跑一下就知道了。复制库之后记得跑 ldconfig 啊。

PaddleOCR 能跑起来之后,就可以把我的服务丢进去跑啦。最终命令长这样:

sudo systemd-nspawn -M paddleocr --user=paddleocr /home/paddleocr/paddleocr-http --loglevel=warn -j 4

-j 参数是限制并发识别数的,避免过载 CPU 或者 GPU,并不是线程数。

跑起来之后,sudo setfacl -m u:$USER:rwx /run/paddleocr/http.sock 给自己授权,然后 curl 一下试试:

time curl -sS -F file=@a.png -F lang=zh-Hans --unix-socket /run/paddleocr/http.sock http://localhost:5174/api | jq .

对于小图片的话挺快的,不到一秒就能出结果。我使用 CPU 版本跑的话,会慢个近十倍的样子。顺便说一下,这是我对服务进行性能优化之后的结果。之前每张图都开新进程跑太慢了。大概是需要加载一大堆库,然后把模型上传到 GPU,每张图一进程的话 GPU 版本反而会明显慢于 CPU 版本。代价是服务会一直占用大约 2G 内存,即使你并没有在用。

系统挂起到内存或者休眠到磁盘时,内存里的内容是被保留了,但是 GPU 显存并没有,大概因此会报 cuda runtime error 999。这时候,只需要停止服务,卸载 nvidia_uvm 内核模块然后重新加载,再启动服务就可以恢复了。如果 nvidia_uvm 卸载不掉的话,那就没办法了,要么重启,要么改用 CPU 版本。NVIDIA 是有个把显存 dump 到内存里存起来的方案的,但是没必要啊,尤其是休眠到磁盘上的时候,多浪费时间啊。

文件下载

你可以直接用我做好的文件。通过本地的 IPFS 服务访问:

http://localhost:8080/ipns/k51qzi5uqu5di433o42zgqk2xck3y160q1hyvqbyyerd36au2pk0c2jw3hcqxx/

你也可以用别的网关来访问,都一样。如果 IPNS 解析失败的话,试试

http://localhost:8080/ipfs/QmNV31bApmgRcHCQjGufQ3zrFDaf6JBWvBt8pU2TA2Baz6/

我把用于跑服务、设置权限的配置文件打了个 Arch 软件包。nspawn 用的 rootfs 也打包上传了。PaddleOCR CPU 和 GPU 版本是分开的,所以有两个包。CPU 版本的 nspawn 叫 paddleocr-cpu,服务名也是。把 rootfs 解压到正确的地方之后,systemctl start paddleocr 或者 paddleocr-cpu 就好啦。用户需要加入 paddleocr 组才能访问 HTTP 套接字哦。

如果遇到CUDA error(803), system has unsupported display driver / cuda driver combination报错,请将系统当前的 libcuda.so.1 复制进 nspawn 里:

sudo cp /usr/lib/libcuda.so.1 /var/lib/machines/paddleocr/usr/local/lib

另外服务配置文件放到 GitHub 上了:paddleocr-service

by 依云 at February 09, 2023 07:47 AM

January 29, 2023

Justin Wong

Rosetta in Linux Virtual Machine on Apple Silicon

2021 年底我的主力设备换成 M1 Macbook Pro,整体上还是非常够用的,唯独就是一些 linux 开发搞不定了,所以被迫大小事情都得 ssh 到某个 linux 开发机解决。虽然可以跑 linux 虚

January 29, 2023 12:00 AM

January 21, 2023

百合仙子

使用 EasyEffects 调整 Bose 音箱的体验

本文来自依云's Blog,转载请注明。

最近到手一个「Bose SoundLink Mini 蓝牙扬声器 II-特别版」音箱,蓝牙名称「Bose Mini II SE SoundLink」。这家伙小巧、沉重,黑色版和我显示器的黑色支架也挺合得来的。然而音质上我遇到了一点问题。

就如同 Bose 产品页说的,它「低音浑厚」。效果就是,只要播放的声音有一点低音,它都给它放大到很明显,震动人心的同时也震动了我的桌面。这用来听强调低音的音乐应该非常有感觉。可是,我听的大部分歌曲都是女声呀。这就像纯净清澈的蓝天蒙上了一层雾霾。

我用白噪声、粉噪声和频率连续变化的正弦波测了一下,用 Spectroid 查看,发现这音箱会加强 100Hz 及 7kHz 附近的声音。所以我把播放的声音处理一下,降低这个地方的强度不就好了吗——嗯,我需要个均衡器。

我记得群里有人提到一个叫 PulseEffects 的软件,于是找了一下。它已经更名为 EasyEffects 啦,不过仅支持 PipeWire。而我还在使用 PulseAudio,于是先装上 pulseeffects-legacy 试了一下。效果十分不错,清澈的女声回来啦(还丢掉了笔记本扬声器所附带的金属感)。不过有点吃 CPU,即使不显示频谱图,也大约得消耗掉 10% 的 CPU。群友说 EasyEffects 的资源占用很小,于是我花了一些时间,切换到 PipeWire 上来啦。

EasyEffects 均衡器截图

切换起来其实不难,我主要是担心有功能不支持以及遇到 bug。pacman -Syu pipewire-pulse pipewire-alsa wireplumber easyeffects就好啦。然后把 PulseAudio 的服务停掉,PipeWire 的对应物开起来,就切换完毕了。PipeWire 的 PulseAudio 兼容性还不错,pavucontrol 用起来完全没有问题,甚至还解决了之前蓝牙编码器在连接之后从 SBC XQ 变回 SBC 的问题。EasyEffects 的 CPU 占用大约在 4%,低了不少。我关心的另一个问题是网络支持,但我发现这个 PipeWire 也兼容了,同样的命令pactl load-module module-native-protocol-tcp auth-ip-acl=192.168.57.0/24对 PipeWire 也能用。

至于 bug 嘛,确实有一些。虚拟机里通过网络播放的时候,偶尔会卡一下。EasyEffects 有时候会需要重启。刚刚不知道为什么音箱明明是连接上的状态,但是就是没声音。重连之后才恢复。我大概会用一些天,如果不是很严重的话我就不切回去了。

哦对了,easyeffects --gapplication-service 这样启动 EasyEffects 就可以不显示图形界面了。但是依旧需要连接上 Wayland 或者 X11,所以需要安排在图形界面启动之后运行。我给 EasyEffects 写了个 systemd 服务,WantedBy 自己写的 xprofile.target,然后在~/.xprofile的最后启动一下,就可以了。

至于其它系统,Windows 上可以使用 Equalizer APO,Android 上我使用的播放器 Poweramp 也有均衡器功能。而且这俩也是可以给指定的输出设备配置的(不过好像 Equalizer APO 只支持一组配置)。

最后再吐槽一下,作为音箱,做不到把声音完美还原也就算了,就请不要主动乱改好吗……哦对了,手机上的相机不少也有同样的问题。我不反对你们后期搞点效果,但是请不要不支持真实的世界。(经常听女声的人,记得看到「低音浑厚」就离远点儿~)

by 依云 at January 21, 2023 12:47 PM

January 20, 2023

Alynx Zhou

Emacs 和 Lazy Loading 和 use-package

事先叠 buff:我不是说 use-package 一定要这么用,我也不是说所用不用 use-package 的人都不好,我只是说我觉得应该这样用 use-package 比较合适。

use-package 是个好东西,因为它解决了 Emacs 插件包从安装到配置的全过程,可以让配置更结构化。不过也有人觉得 use-package 关键字过于复杂,总是没办法确定什么配置写到什么字段里面,也不知道展开之后悄悄发生了什么事情。从我再次决定自己打造一份 Emacs 配置以来看了很多不同人的配置,发现他们使用 use-package 的方式也是五花八门,有些人不爱用 :bind:hook,干脆自己在 :config 里面调用 define-keyadd-hook,有些人不清楚为什么自己的 :config 被延迟运行了,干脆全都用 :init。还有些人直接换成了其它号称更简单可控的替代品。我不是说上面这些方法都错了,实际上只要能得到想要的结果也无所谓怎么写,但我是一个比较注重逻辑的人,所以研究了一下到底这些关键字是怎么回事,并且试图写篇文章记录我推荐的写法。本来我打算写在注释里的,可是感觉写得太多,所以就放到博客里了。

问题的核心无非是:为什么我写的 :config 没有运行?到底什么情况下会有延迟加载?我打算在这里详细分析一下。

首先 Emacs 有一种叫做 autoloads 的东西,插件包的作者可以在某些函数前加上 autoloads 标记,然后创建 autoloads 文件。这个功能的作用很好理解,原本在启动时需要加载所有插件包的文件以便用户使用相应的功能,但不是所有的插件都是启动时就需要,只在启动时加载会让启动速度变得很慢。有了 autoloads 之后启动时只要加载 autoloads 文件,里面定义了如果运行某个函数,就去加载某个文件,这样等到对应的函数第一次运行的时候才被加载,从而提高启动速度。

use-package 做了什么呢?use-package 可以自动创建 autoloads,这样即使一个包本身没有 autoloads,也是可以延迟加载的。最简单的触发这个逻辑的关键字是 :commands,就是给后续的函数创建 autoloads 的意思。但如果你仔细阅读文档,就会发现还有几个创建 autoloads 的关键字,分别是 :bind:hook:mode:interpreter如果有这几个关键字,use-package 不会立即加载一个包,而是依靠创建的 autoloads 实现延迟加载。这也很好理解,这几个关键字的意思都是“在某种情况下启用”,所以自动延迟加载也很好理解。

然后为什么会遇到 :config 不会运行所以有些人统统把要调用的语句写到 :init 里面的问题呢?一般都是发生在类似下面的写法(既有快捷键 / 钩子,又要一启动就启用什么模式)里:

(use-package marginalia
  :ensure t
  :bind (:map minibuffer-local-map
              ("M-A" . marginalia-cycle))
  :config
  (marginalia-mode 1))

我们得明确 :init:config 的区别,:init 是“无论包有没有加载,都一定会先执行”的配置,:config 是“包加载之后才会被执行”的配置。然后按照上面关于延迟加载的分析,这就变成了一个“先有鸡还是先有蛋”的问题:

  1. use-package 给这个包创建了快捷键对应的 autoloads,于是这个包不会立刻加载。
  2. :config 里面 (marginalia-mode 1) 要等到包加载之后才运行。
  3. 这个包本身有给 marginalia-mode 创建 autoloads,只要调用 marginalia-mode 就会加载这个包,但是根据 1 和 2,这句不会被调用,包也不会被加载,除非按了 1 里面的快捷键。

打破这个循环的办法不止一种,比如把 (marginalia-mode 1) 写进 :init,这样无论如何都会调用它,于是 use-package 创建的 autoloads 被忽略了,包肯定会被加载。但我个人倾向于把这种启用模式的函数放在 :config 里面,而且作者也推荐不要在 :init 里面放过于复杂的函数,这时候打破循环的办法也很简单,只要使用 :demand t,告诉 use-package 立即加载这个包即可。

以上的内容其实都写在 https://github.com/jwiegley/use-package#notes-about-lazy-loading,只是可能很多人没有注意或者没看明白,于是我试图在这里更详细的解释一下。可能有人会问搞这么复杂真的有意义吗?我直接自己不要延迟加载直接都 require 不可以?不过我觉得这种激进的延迟加载方案确实让我的 Emacs 启动非常快,所以大概是有意义的吧。

by Alynx Zhou (alynx.zhou@gmail.com) at January 20, 2023 09:55 AM

January 15, 2023

中文社区新闻

PHP8.2 更新及引入传统分支

php 包已经更新到最新版 8.2 。随着这次更新,我们同时引入了一系列 php-legacy 包。这些包将跟随 还在受支持的版本中最旧的 PHP 版本分支。这么做允许用户安装最新版本的同时,仍然可以使用依赖老版本的第三方应用程序。两个分支都将遵循我们的滚动更新模型保持更新。 phpphp-legacy 可以被同时安装,因为后者在它的二进制和配置文件中使用 -legacy 后缀区分。
除此之外, php7 包已经被移除,因为它们已经超过了生命周期。还有不再提供 imap 扩展,因为它依赖早已被废弃多年的 c-client 库。

by farseerfc at January 15, 2023 04:37 AM

January 12, 2023

中文社区新闻

纪念 Jonathon Fernyhough

Arch Linux 社区缅怀突然辞世的 Jonathon Fernyhough ,在我们社区中通常以 jonathon 的名字贡献,在周六夜间与世长辞。
Jonathon 曾活跃地参与和贡献了 Arch Linux 、数个衍生发行版、AUR和个人仓库。他的热情、乐于助人和积极贡献改进了整个自由开源软件社区。
代表整个 Arch Linux 社区,我们向他的家人和朋友致以哀悼。

by farseerfc at January 12, 2023 02:48 PM

January 10, 2023

Alynx Zhou

StackHarbor 的 2022 尾记

每年写年终总结我都会拖很晚,因为基本上我写博客是看心情,最近事情比较多,其实也打算再拖几天的,但是实在是不想回家之后写,所以不得不今天仓促动手。

然后实际上我不喜欢分类列提纲的写法,我本质上比较倾向于文学性的写法或者就是想到什么写什么,不过最近我在翻以前的年终总结的时候发现事情总是记的乱七八糟而且有些我都想不起来出现在哪篇文章里了,这当然可能也和我写完年终总结从来不看有关系,反正这次打算试一下分类的写法。

按照惯例还是要感慨一句时间过得真快,仅仅只是靠记忆的话,就会觉得自己什么也没做就过了一年。写年终总结的时候到处翻一下记录,才会意识到自己其实做了不少事情。

编程

我经常会处于一种“我好菜啊怎么什么都做不了”和“我还能搞定这个其实还不错”的叠加态,实际上仔细翻一下感觉去年还是做了不少东西。比如说在 HackWeek 把 Show Me The Key 成功换成了 GTK4。然后还抽时间利用 Telegram 机器人做了个照片墙,虽然中间我把它关了很长时间,不过后来我又把它跑起来了。

这一年我印象最深的其实是搞我的 Emacs 配置。在有确定消息说 GitHub 打算放弃 Atom 之后,我不得不给我自己重新找一个编辑器,因为我得了一种看到 Visual Studio 就会死的病所以坚决不会用 VSCode,除非他们哪天改名部把 VS 从里面去掉。然后我一直是不喜欢模态编辑的所以也不会用 Vim,同时 Emacs 的 PGTK 分支已经被上游接受,所以我很高兴地重回 Emacs 拥抱我所知的第一个 pure Wayland 的 GUI 编辑器。说是重回,其实相当于重新学习了一遍 Emacs Lisp,毕竟我一开始尝试 Emacs 是被 Spacemacs 那句著名的口号吸引的(但是我又不用 Evil)。那时候其实我不太懂 Emacs Lisp,但是现在回头再看发现确实是更好掌握了。虽然有无数的人说应该从别人配好的 Emacs 配置开始,但我还是决定自己编写一套配置而不是使用最流行的 doom。一个是这些配置好像都以模态编辑为中心,另一个是我经常会自己定制自己的编辑器,使用这些别人配好的配置调起来总觉得很不自在。然后就是我逐渐理解了 use-package 的用法,解决了各种奇奇怪怪的问题,甚至还自己用 Emacs Lisp 写了很多自己需要的功能。虽然可能有人要问你搞这一通有什么意义之类的,但是我做事的一个原则就是看心情,我高兴就好,所以觉得还挺值的。

然后不论是工作还是个人爱好上这一年多少也做了点东西,毕竟我的工作就是我的爱好。比如很有意思的一个是我研究了一下 GNOME 的智能卡登录到底怎么搞,顺便也大致了解了一下 PAM 的配置,虽然可能这个还是没什么用,不过最后我修改了 openSUSE 的 gdm 包添加了一直缺失的指纹和智能卡的 PAM 配置,也算是帮助了其他人。我还抽出时间调查了一下 GTK3 和 GTK4 的亮色 / 暗色主题切换到底是怎么回事。然后还做了一些微小的贡献,比如我印象里一直有人吐槽说 GNOME Shell 的搜索只能从开头匹配而不能做子串匹配,还有人说难道他们只会用 String.prototype.startsWith() 不会用 String.prototype.includes(),我一开始只是想既然这么简单,有吐槽的时间为什么不自己改一个?于是我花时间看了一下还真不是这么简单,总之最后我阅读了 glib 里面的算法,并且添加了根据不同的匹配模式分组的功能,现在如果有单词开头匹配的会优先显示,然后再显示子串匹配,就可以通过搜索 fox 得到 Firefox 了(https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3107)。

和这个类似的还有另一个,我看到有人说 gdbus-codegen 生成的代码没有加空指针检查导致程序崩溃,然后和开发者吵了起来,开发者说加空指针检查不是真的解决问题,这里不应该传空指针,那个人就丢出一堆各种代码规范说传了空指针应该继续运行不该崩溃,开发者说你给我们加的话我们愿意接受,他又说自己不擅长 python,总之我看了觉得很不可理喻,于是我自己改掉提交了然后嘲笑了那人一通。有些时候真不是开发者脾气不好,是有人态度太差……(https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3175

音乐

去年一年我还是录了好几个曲子的,虽然我自己是觉得没怎么练琴而且还经常咕咕咕。不过我发出来的我自己还都觉得不错,虽然不是每个都有很多播放量吧。最近手上有几个想录的,比如 シリウスの心臓 和 暗恋是一个人的事,不过可能又要拖到年后了。

然后今年通过 澪音奏 的翻唱听了好多伍佰的曲子,对于我这种几乎不会主动找歌曲来听的人,能扩充曲库还是好事。还在 B 站听了纵贯线的亡命之徒,没早点听到这个真是有点可惜。

数码

其实我觉得我也没买什么东西,但是再看一下又不少,很多其实没什么可说的,比如买了个新镜头,那就是新镜头,也没什么好在博客里分析一番的。考虑到那块老移动硬盘用了很久,又买了一块三星的 T7 Shield,固态的移动硬盘还是可靠很多。犹豫了很久还是买了平板,不过不是 iPad,是 Galaxy Tab S8,除了 LCD 屏幕有点漏光,别的我都很满意,不管是看谱子还是看视频都不错,虽然有些 app 不支持横屏,但我还是觉得文件管理更重要。

开销比较大的是装了一台 NAS,实际也是挑来挑去才决定的,运行了小一年觉得还不错,极大的缓解了我的存储压力。

摄影

自我评价的话我觉得还是有点进步,别的不说,今年我拍了很多自己觉得不错的照片。摸索了一年,我大概也知道怎么用 darktable 得到想要的效果了。之前看到有人总结了一下自己拍过的照片里觉得不错的,我觉得这个想法很好,于是挑了一些今年满意的照片放在这里:

很想找机会把我喜欢的照片打印出来装上相框挂在家里,只是一直没去做这件事。

动漫

今年应该只看了两部,一部是 DitF,具体的评价我在 这篇文章 写过了。另一部是 赛博朋克:边缘行者,我其实是不喜欢赛博朋克题材的,但是这一部做得太好了,我在这里向所有读者推荐。

小说

今年应该是看了基地三部曲,还有格兰特船长的儿女和神秘岛,因为都是很好找到电子版的,所以没有买实体书,神秘岛以前看过简写版,其他的都是头一次看,总体上来说我觉得都不错,毕竟也是流传很久的书了。

游戏

除了和开黑群的朋友打 Dota 2 就是和牛爷爷高先生吃鸡,今年更多的玩了游廊地图,感觉我还是想玩休闲一点不太需要团队配合的,吃鸡的话我以为我很久没玩了水平会很差,不过我好像逐渐掌握这个游戏怎么赢了,而且经常可以吃鸡。其实玩游戏本身倒不重要,重要的是有人一起玩。一个人玩的话,要不是因为看中单光一,我大概不会坚持玩 Dota 2。

生活

在经历了翻大饼之后果然是不负众望的夺冠了,虽然已经好了。大饼现在是翻过来了,但是保不准哪天又要翻回去,反正我是受够了这种提心吊胆的日子。仔细想想今年我大概有好几次对着共享单车打开健康码扫码,还是挺可怕的。蓝猫今年去了日本,没办法再找她玩了,虽然我很羡慕,但是我又穷又懒。至于脱单这种事情,算了吧,我已经放弃了,一个人待着也挺好的,我一点都不羡慕别人(假的)。

最近几天突然又肋骨痛,只要有动作扯到就很难受,我也不知道是气胸还是肋间神经痛还是肌肉拉伤,本来打算去医院看看的,但最近事情安排满了,回家的时间取决于车票不取决于我,所以只能等回家之后再说。

by Alynx Zhou (alynx.zhou@gmail.com) at January 10, 2023 05:43 AM

January 08, 2023

frantic1048

Helix 键盘诞生记

Chino with Helix keyboard

这个键盘原本是作为灾备组起来的,吃灰了一阵时间的。我原本有两个在用的 2018 年组起来的键盘,分别是办公用的 ErgoDone 和移动用的 Atreus60,结果这两天突然因为 Atreus60 有四颗按键突然不工作了,调查半天也没发现到底哪里有问题,于是 Helix 终于上岗,顺便记录下拖延很久的组装记录 6Д9

准备

Helix 有多种配置,我这里组装的是五行的,带键盘正面的 OLED 小屏幕和键盘背面的 LED 底灯的组合。按照左右手一对键盘来算,用了以下零件:

种类 数量 备注
4pin 排针, 2.54mm 间距, 11.4mm 高 2 OLED 屏用
4pin 排母, 2.54mm 间距 2 OLED 屏用, 买不到足够矮的, 实际没装这个, 键盘还是能用
OLED 显示屏, 0.91" 4 针 IIC 接口 2 OLED 屏用
LED 灯带, WS2812B (6 灯) 3535, 4mm 宽 2 LED 底灯用,最好带背胶;我用 4mm 发现宽度略窄, 5mm 应该更合适一些
Micro USB 线 1
TRRS/TRS 线 1
TRRS 插座, PJ320-A 2
贴片二极管, 1N4148WS SOD-123 64
插脚电阻, 4.7 kohm 4 Helix 文档未列出,经小伙伴提醒补充
轻触开关, 3x6x4.3mm 2
橡胶脚垫 12 数量形状都比较随意
扁头螺丝, M2 5mm 4
螺丝, M2 3mm 28
铜柱, M2 8mm 4
铜柱, M2 4mm 12 矮轴专用
排针, 12 pin 4 Pro Micro 自带了
Arduino Pro Micro 2
轴体, 凯华 1350 Low Proflie Red 64 不好用
键帽, 1U 凯华 Low Profile 64 不好用,插上去就别想拔下来了
底板, OLED 屏上盖,亚克力 2mm 厚 2 提供图纸 给亚克力厂家订制
定位版, 亚克力 3mm 厚 2 提供图纸 给亚克力厂家订制
Helix PCB 2 提供图纸给 PCB 厂家订制

组装会用上的耗材:

种类 备注
焊锡
焊接锡浆 我选择的 183°C 熔点的中温锡浆

工具:

种类 备注
电烙铁 配合尖头的烙铁头感觉比较好用
热风枪 配合锡浆,处理贴片元件
镊子 安置贴片元件
斜口钳 用来剪多余的引脚
电脑 键盘组装好之后,刷固件用

零件不完整照片(图中左侧轴体并没有用上):

Helix keyboard parts

组装

左右手用的是同样的 PCB,左右正面朝上的时候,两边看起来应该是垂直对称的,后面的图基本都是左手的 PCB 示例。

贴片二极管

先装贴片二极管,装的位置是 PCB 背面,朝向应当和 PCB 上印刷的标记一致,所有的二极管朝向应该是一致的(个别位置没有标方向),用镊子放上合适的位置,两头挤一点点锡浆上去,不用担心位置有一定的歪斜,用热风枪吹一下之后位置就自动正了。

 DSC4161

 DSC4157

轻触开关、TRRS 插座、和 OLED 屏幕用的跳线

PCB 正面安装 TRRS 插座和轻触开关。以及连接稍微靠上一点的几个跳线(带有 SDA SCL VCC GND 字样的那些成对触点)。

 DSC4164

Pro Micro 、OLED 屏、4.7 kohm 电阻

如图所示的方向,把 12 pin 排针与 Pro Micro 焊接到一起:  DSC4167

然后翻一面,没有元件的这一面朝上,尽可能修剪这些突出来的针脚,整个插到PCB 正面  DSC4168

对于 OLED 屏,先和 4 pin 排针焊接上,然后按照图里的样子装到 Pro Micro 上方(这里应该 PCB 上先装个排母的,我没有找到 Helix 组装指南里高度那么低的排母,结果就直接插上去了,能用……):  DSC4169

剪掉突出的针脚,顺便焊上 TRRS 插座旁边的两个电阻:  DSC4170

LED 灯带

PCB 背面装上 LED 灯带,灯带的触点应该和 PCB 的字样吻合,我的 4mm 灯带有点窄,不是很对的上触点,费了点功夫还是接上了,能用:

 DSC4171

 DSC4172

定位板、轴体

把定位版叠放到 PCB 正面,然后把轴体安放上去(推荐从对角位置开始),并且在 PCB 背面焊接轴体的引脚:

 DSC4178  DSC4181  DSC4179

按照官方指南的推荐,到这一步已经可以给键盘刷个固件,测试一下键盘了。

外壳

PCB 正面,轻触开关旁边装上 8mm 铜柱,背面用 M2 5mm 扁头螺丝固定:

 DSC4189  DSC4190

把 4mm 铜柱从PCB 背面插上去,上面再盖上背板,两头都用 M2 3mm 螺丝固定,脚垫可以顺便粘上去。

 DSC4192  DSC4196  DSC4193

用扁头螺丝固定 OLED 屏的盖板:  DSC4194

最后键帽安上去就算组装完工了,凯华这个矮轴键帽需要超级大力才能装上去。

Chino with Helix keyboard

固件

Helix 可以使用 QMK 键盘固件

准备

在 Arch Linux 上,可以直接装官方源提供的 QMK CLI

pacman -S qmk

初始化 QMK CLI,,执行完命令,它会将 QMK 固件仓库下载到本地:

qmk setup

调整与刷写

对于键盘的配置,参考 https://github.com/qmk/qmk_firmware/tree/master/keyboards/helix/rev2

可以先刷一个默认布局来测试,文档能看到 的带 OLED 和底灯的配置名字是 helix/rev2/under ,将键盘连上电脑,戳一下键盘上的轻触开关,执行对应的 qmk 命令:

qmk compile -kb helix/rev2/under -km default
qmk flash -kb helix/rev2/under -km default

然后就可以试试键盘了。

参考

January 08, 2023 09:02 AM

November 30, 2022

Alynx Zhou

YubiKey 和 GNOME 和智能卡登录

最近我终于决定买了一个 YubiKey 5C,说出来不怕各位笑话,我买这玩意最初的动机只是觉得每次开机和解锁输密码太麻烦(但是为什么我不觉得 sudo 输密码麻烦呢?)。这还和我之前处理了一个 openSUSE 的 PAM 问题有关,我发现 GDM 有好几种不同的 PAM 配置,除了平时用的 gdm-password 密码登录,还有 gdm-fingerprint 指纹登录和 gdm-smartcard 智能卡登录。我一开始是打算买个指纹传感器的,查了一下 fprintd 的文档,支持的型号并不多,而且在淘宝上问客服 USB Product ID 和 Vendor ID 显然得不到回答,就退而求其次买智能卡了,而搜索智能卡得到最多的结果就是 YubiKey,进入一个不了解的领域之前和大部分人选一样的一般不会错太多,于是就下手了。

话说回来智能卡登录,如果你搜索 YubiKey 相关的文章,绝大多数都会告诉你把 pam_u2f.so 加到需要密码登录的 PAM 配置里,比如 sudo 或者 gdm-password,但这显然不是我想要的,我要的方案不是替换密码登录,而是和密码登录平行的配置文件,我知道 GNOME 有已经写好的智能卡配置,但是任何地方都搜不到如何启用,设置里没有相关选项,连 Arch Wiki 给的方案都只是用 pam_u2f.so。Red Hat 的支持文档里倒是提到了智能卡登录,然而用的却是他们自己的某个工具配置的。显然这是个起夜级 feature,最好的办法也许是找个起夜级 Linux 的桌面工程师来问问,哦什么我自己就是起夜级 Linux 桌面工程师,那没事了。还要说的一件事是怎么想智能卡这东西都和安全相关,而我自己不是专业的安全行业人士,所以我不会尝试解释清楚和安全相关的一些名词,以及如果我哪里真的写错了,希望专业人士多多指点,我肯定改。

总之相信你自己因为你才是职业选手,我还是自己看看这东西怎么弄吧,毕竟用 Arch 再用 GNOME 同时还打算搞 GNOME 的智能卡登录的人没几个,所以 Wiki 没有倒也正常。首先肯定是看 /etc/pam.d/gdm-smartcard 这个文件,里面别的看起来都比较正常,只有一行看起来和智能卡有关系:

auth       required                    pam_pkcs11.so        wait_for_card card_only

线索是有了,看来我需要这个 pam_pkcs11.so,虽然我也不知道这是什么,先搜一下哪个包有这个文件比较好。pacman -F pam_pkcs11.so 竟然没有返回任何结果,我确定不是我的 pacman 数据库没更新,那只能去浏览器里搜索了,最后我搜到了 https://github.com/OpenSC/pam_pkcs11,虽然我也不知道 PKCS#11 是个什么玩意,但反正它是个 PAM 模块,既然不在官方仓库里,那大概率 AUR 里有人打包了,于是直接 paru pam_pksc11 就装了一个上来。

但装是装好了,也看不出来这玩意和 YubiKey 有什么联系,我大概是搜索了 PKCS YubiKey 然后搜到了 YubiKey 给的文档 Using PIV for SSH through PKCS #11,好吧虽然我不是要用来 SSH 但是多半也有点用。看下来反正这个东西和 YubiKey 的 PIV 功能有关,我把 PIV 相关的文档都看了一遍,结果是云里雾里,相当没有头绪。一大堆文档告诉你各种各样的需求要做什么,但是几乎没怎么说这都是什么,于是恰好我的需求不在列表里我就不知道怎么办了。我又回头去看 pam_pkcs11 的文档,它写了一长串的东西,我反复看了几遍之后发现只要看 第 11 节的 HOWTO 部分 就可以了。虽然我也不太清楚它都在说什么,但是至少这里告诉我说需要一个 root CA certificate,但我是个人使用哪来的这玩意,再回头看 YubiKey 的那篇文档里面恰好提到了什么 self-signed certificate,我拿这个试一试,结果成功了。为了方便参考,下面我就不讲我是怎么倒推这些奇怪的需求的了,而是顺序讲一下都需要配置什么。

首先如果你像我一样刚买了一个 YubiKey 打算利用它的 PIV 功能,那你得先初始化它,也就是改掉默认的 PIN,PUK 和管理密钥,这个可以通过官方的 YubiKey Manager 软件来操作,有 Qt 写的 GUI 版和命令行版本:

# pacman -S yubikey-manager yubikey-manager-qt

我推荐使用命令行版本操作,因为那个 GUI 经常转圈转半天或者点了没反应:

% ykman piv access change-pin
% ykman piv access change-puk
% ykman piv access change-management-key --generate --protect --touch

默认 PIN 是 123456,默认 PUK 是 12345678,而管理密钥是个特别长的一串,用 --generate 可以让 ykman 给你生成一个,--protect 则是把这个直接存到 YubiKey 里面并用 PIN 保护,--touch 则是说每次要管理密钥的时候需要你摸一下。我也不是很懂,也许写进去以后需要的时候就不用自己背这玩意而是输 PIN 就行了吧,反正建议看官方文档 Device setupykman--help

我的建议是不要看太多官方文档,因为它一会告诉你用 yubico-piv-tool 创建密钥,一会告诉你说可以用 openssl 创建密钥,一会又告诉你可以用 pkcs11-tool 搭配 libykcs11.so 创建密钥,算了吧,头都看晕了,我的测试是用 yubico-piv-tool 就可以了。

% paru yubico-piv-tool

9a 这个槽创建一个 key 并把它的公钥写出来,为什么是 9a 好像因为这是第一个槽来着,自己去查官方文档吧,也可以写到别的槽里面:

% yubico-piv-tool -s 9a -a verify-pin -a generate -o public.pem

需要先输入 PIN,然后灯闪的时候需要摸一下 YubiKey,它就开始生成了。

还要给这个密钥生成一个签名:

% yubico-piv-tool -s 9a -a verify-pin -a selfsign-certificate -S "/CN=Alynx Zhou/" -i public.pem -o cert.pem

注意 CN= 后面的部分,这里会被 pam_pkcs11.so 用来验证这个智能卡属于系统里面哪个用户,所以简单的话直接写你的登录用户名,当然你像我一样不想写用户名也是有办法对应的,同样要输入 PIN。

再把证书导回到同一个槽,我也不知道为什么,文档说了我照做了:

% yubico-piv-tool -s 9a -a verify-pin -a import-certificate -i cert.pem

还是要输入 PIN 然后灯闪的时候摸一下。

到这里 YubiKey 的配置就结束了。

要在系统上使用智能卡验证需要安装系统上和智能卡交互的软件包:

# pacman -S ccid opensc pcsclite
% paru pam_pkcs11

启动一个相关的 daemon,或者启动 socket 也行,需要的时候它就自己起来了:

# systemctl enable --now pcscd.socket

如果我没漏掉什么乱七八糟的,就可以配置 PAM 模块了,它有一个配置目录叫 /etc/pam_pksc11,首先你要把上面生成的证书放到 /etc/pam_pkcs11/cacerts

# cd /pam/pkcs11/cacerts
# cp PATH_TO_YOUT_CERT/cert.pem ./

你要在同一个目录下面运行一个什么什么 hash 命令生成一个 hash:

# pkcs11_make_hash_link

接下来你要去搞它的配置文件,先复制一个样本过来:

# cp /usr/share/doc/pam_pkcs11/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf

好像其实也没什么需要改的。文档说默认的配置用的是 OpenSC 的 PKCS#11 库,虽然 YubiKey 的文档一直跟你说什么 libykcs11.so,我的测试结果是不用理它,通用的接口就够了,以及这个 libykcs11.so 是属于 yubico-piv-tool 这个包的。

假如你刚才 CN= 后面写的不是你的用户名,那你需要一些配置告诉 pam_pkcs11.so 你这个证书对应的哪个用户,这一步在它的配置文件里叫 mapper。默认启用了一些 mapper 比如 pwent,这个就是把 CN= 后面的内容和 /etc/passwd 里面的用户名做匹配,但是如果你像我一样写的是全名,那就需要另一个默认启用的模块叫 subject。至于 subject 是什么需要运行下面这个命令:

% pkcs11_inspect

它会输出各种 mapper 对应的 data,比如 pwent 输出的就是 Alynx Zhousubject 输出的则是 /CN=Alynx Zhou。我们需要复制一个 subject_mapping 配置文件的样本过来:

# cp /usr/share/doc/pam_pkcs11/subject_mapping.example /etc/pam_pkcs11/subject_mapping

在这个文件后面加一行:

/CN=Alynx Zhou -> alynx

我的用户名是 alynx,你可以换成你自己的。

到这一步 pam_pkcs11.so 这个模块已经可以通过智能卡验证你的身份了,但是如果你火急火燎兴高采烈的重启了系统,GDM 还是会和你要密码。原因其实很简单,虽然现在 /etc/pam.d/gdm-smartcard 已经可用了,但 GDM 只有在检测到智能卡之后才会调用这个文件尝试智能卡登录,很显然它没检测到智能卡。

这里就比较难搞清楚了,我智能卡插的好好的,上面各种程序都能用,为什么你检测不到?我尝试用什么 GDM YubiKey 之类的关键词搜索了半天,也没人告诉我 GDM 到底怎么检测智能卡的。没有办法还是读代码吧,GNOME Shell js/gdm/util.js 里面的逻辑是通过 D-Bus 的 org.gnome.SettingsDaemon.Smartcard 获取智能卡信息,那我打开 D-Feet 从 Session Bus 里面找到这个,直接运行 org.gnome.SettingsDaemon.Smartcard.ManagerGetInsertedTokens,什么都没有。

根据 D-Bus 的信息,很显然这个接口是 gnome-settings-daemonsmartcard 插件提供的,我大概是搜索了什么 gsd-smartcard PKCS#11 的关键字之后找到了 https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/merge_requests/208,其实我一开始也没太看懂这是什么意思,但得到一些有用的信息:

  • gsd-smartcard 用了什么 NSS API 获取智能卡设备。
  • 这玩意要一个什么 system shared certificate NSS database。
  • 除了 Red Hat 家那一套好像没什么别的发行版弄这个。

这一路下来乱七八糟的名词已经够多的了现在又多了一个什么 NSS 而且只有 Red Hat 才配置了 system shared certificate NSS database,但不管怎么样我是职业选手我不能轻言放弃,还好 Arch Wiki 有这么一个页面 Network Security Services,但这不是管证书的吗,和智能卡设备有什么关系啊。这时候我又翻开了 Arch Wiki 关于智能卡的页面 Smartcards,里面讲了在 Chromium 里面加载智能卡需要在 NSS 数据库里面加一个模块(什么乱七八糟的),不过它操作的都是用户的家目录下面的数据库,这显然不是 system shared certificate NSS database。然后如果手工执行 /usr/lib/gsd-smartcard -v,会发现这玩意尝试读取 /etc/pki/nssdb 获取什么智能卡驱动列表,我系统里面根本没这个目录。算了,既然是 Red Hat 搞的东西,我看看他们怎么写的。正好我有个 Fedora 的虚拟机,打开虚拟机一看还真有这个目录,那就运行下面命令看看:

% modutil -dbdir /etc/pki/nssdb -list

结果里面除了默认项还真有个叫 p11-kit-proxy 的玩意,我又回去看了一眼那个 Merge Request,现在我完全明白了,不知道为什么 NSS 这玩意会记录一个读取智能卡的驱动列表,然后 gsd-smartcard 是通过 NSS 获取到智能卡的驱动列表之后再尝试查询智能卡,实际上现在没什么人用 NSS 这个功能了,你这还得往系统的 NSS 数据库里面写东西,除了红帽子家都没人搞这个了,就算有用 NSS 读的(比如浏览器)也是读用户的 NSS 数据库。别的用智能卡的都直接用 p11-kit 去读智能卡,所以这个 Merge Request 也改成直接用 p11-kit 读了。不知道为什么这个 Merge Request 没能合并。再多说一句,就算是 Red Hat 的系统 NSS 数据库,现在也不直接写智能卡的驱动了,而也是通过 p11-kit,所以刚才在 Fedora 的数据库里只看到 p11-kit-proxy 这一个驱动……

既然这样我们也在这个数据库里写一个 p11-kit-proxy,根据 Arch Wiki 的智能卡页面,如果你要通过 p11-kit 操作 OpenSC 的驱动(这都什么乱七八糟的),那可能需要安装一个 AUR 包来保证它被加载(实际上就是个文件而已):

% paru opensc-p11-kit-module

创建数据库目录并往数据库里写 p11-kit-proxy

# mkdir /etc/pki/nssdb
# modutil -dbdir sql:/etc/pki/nssdb -add "p11-kit-proxy" -libfile p11-kit-proxy.so

如果你和我一样又心急火燎的重启了,就会发现还是没用。这不科学啊,Fedora 的数据库里也是这么写的,看一眼 D-Bus 为什么还是没有智能卡。

实际上最后我发现只差一点点,Fedora 给这个目录下文件的权限是 -rw-r--r--,而我这边创建好的是 -rw------gsd-smartcard 是以 session 用户运行的当然读不了。所以改一下权限就可以了。

# chmod 0644 /etc/pki/nssdb/*

接下来插着 YubiKey 重启,GDM 启动的 gsd-smartcard 就能读到系统的 NSS 数据库,检测到智能卡,于是调用 /etc/pam.d/gdm-smartcard,直接让你输入用户名,输入之后会提示你输入智能卡的 PIN,然后 pam_pkcs11.so 进行验证,就可以登录了。锁屏之后也只要输入智能卡的 PIN 就可以解锁。

按理说如果给 pam_pkcs11.so 发一个空白的用户名,它会根据智能卡返回用户名的,不知道为什么我在 GDM 用不了,一定要开机手动输入,有空我看看代码也许可以修改一下。 我也不知道为什么一定要在 GDM 启动之前插入卡才可以,显示用户列表之后再插入卡我这里没反应。

更新(2022-11-30):花了我半天时间研究 GDM 和 PAM,问题不在 GDM,而是因为 Arch Linux 的 gdm-smartcard 首先调用了 pam_shells 检查用户是否有合法的 shell,遇到空用户名它第一个失败了,于是我提交了 一个 MR,把 pam_shells 挪到 pam_pkcs11 下面,这样它会检查自动返回的用户名。(虽然这些 PAM 配置文件是发行版自己写的但是大家都提交到 GNOME 那边了,我只改了 Arch 的因为我在用,别的发行版的用户先偷着乐吧。)

如果你想用智能卡解锁的话,一定得是用智能卡登录才可以,它会检查当前的卡是不是登录所用的那张卡,不是的话就只能密码解锁了。折腾这一套花了我一整天时间,因为资料实在是太少了,根本不知道它是怎么工作的。

以及最后我还发现一篇文章,里面的内容也是讲这个 NSS 数据库的解决方案的,也许我早看见这个就不会这么麻烦了: Fixing NSS and p11-kit in Fedora (and beyond)

by Alynx Zhou (alynx.zhou@gmail.com) at November 30, 2022 01:08 PM

November 25, 2022

Alynx Zhou

DaVinci Resolve 在 Linux 下的输入法支持

令人出乎意料,我竟然是 DaVinci Resolve(后面都简称达芬奇了)的付费用户。虽然它不是开源软件,但是有很好的 Linux 支持,使用体验和功能都是同类中的佼佼者,而且收费也相当合理。我选择付费一个原因是你支持我,我就支持你,这其实和我支持 Steam 和 Valve 的理由差不多。另一个原因是众所周知的由于什么所谓系统专利许可证的原因达芬奇 Linux 版本不能解码 H264 和 H265 这两种常见的视频编码,只能使用 NVIDIA 显卡的 NVENC 和 NVDEC 来处理,而达芬奇将显卡加速功能作为收费的卖点。于是我就这样半自愿的上了贼船。

当然排除掉解码问题之后还有另一个比较难受的地方,就是达芬奇 Linux 版没有输入法支持,于是完全没办法输入中文。我猜测不像是某些故意恶心人的企业对 Linux 不友好,而单纯是因为英语开发者没有“输入法”这种概念。毕竟达芬奇的图形界面是基于 Qt 的,而 Qt 直接有现成的输入法支持,构建的时候打开开关就可以了嘛。为此我甚至专门跑到 BlackMagic Design 的用户论坛发了个帖子(https://forum.blackmagicdesign.com/viewtopic.php?f=33&t=150886),作为付费用户,我给你钱,你就得给我办事,就是这么硬气。显然某位员工看到了我的帖子并把它移动到了 Feature Requests 版面,然后就没有然后了。闭源拖拉机总是这样,我看到了,但我懒得改,你给我等着吧。我倒不是说开源拖拉机的维护者都比较勤快,但是至少代码放在那里,说不定用户自己就给你改了送到你面前了,一般再懒的维护者都乐意接受。谁叫我没找到和达芬奇一样好用的开源视频剪辑软件呢。

不过从它用的 Qt 这一点上来看,应该是有什么办法可以 hack 一下让它支持输入法的。虽然我不是很熟悉 Qt,但是 Fcitx 的开发者 @csslayer 给了我一个方案,他之前写了一篇博客是关于给 Mathematica 添加输入法支持的(https://www.csslayer.info/wordpress/fcitx-dev/a-case-study-how-to-compile-a-fcitx-platforminputcontext-plugin-for-a-proprietary-software-that-uses-qt-5/),他觉得达芬奇也可以如法炮制,于是我阅读了一下,简单地概括就是首先查出来软件用了什么版本的 Qt,然后下载对应的源码,因为输入法支持属于 Qt 的某种插件,所以只要构建插件的时候链接到软件自带的 Qt,再把得到的插件复制到软件的 Qt 目录就可以了。一般来说软件就算修改了自带的 Qt,也不会修改有关插件的部分,所以我打算如法炮制一下。

首先是查看达芬奇自带的 Qt 的版本,这个非常简单:

% strings /opt/resolve/libs/libQt5Core.so.5 | rg 'Qt 5'
Qt 5.15.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 9.0.1 )
This is the QtCore library version Qt 5.15.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 9.0.1 )
If that is not possible, in Qt 5 you must at least reimplement

到这里应该就是去下载 Qt 5.15.2 版本的源码了,不过我突发奇想看了一眼系统安装的 Qt 版本:

% pacman -Qi qt5-base | rg Version
Version         : 5.15.7+kde+r176-1

一般来说主次版本号不变的话不会有什么不兼容的改动,会不会我直接把系统的 .so 文件复制过去就可以用了呢?Qt 5 的 ibus 支持已经是 Qt 本身代码库的一部分了,安装到系统的路径是 /usr/lib/qt/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so,我尝试直接把它链接过去:

% sudo mkdir /opt/resolve/libs/plugins/platforminputcontexts
% sudo ln -s /usr/lib/qt/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so /opt/resolve/libs/plugins/platforminputcontexts

然后就没有然后了,我启动达芬奇之后 ibus 就直接工作了。没想到他们虽然不太了解 Linux 输入法,Qt 版本跟的倒是还挺新的。

just works

对于 Fcitx5 用户的话,首先要注意 Qt 5 的 Fcitx5 支持并不在 Qt 的代码库里,所以你需要安装 fcitx5-qt。不过文件路径的话都是一样的,只要把文件名里的 ibus 改成 fcitx5 就可以了。如果直接链接不能用,需要按照老 K 博客里的办法自己编译的话,需要下载单独的 fcitx5-qt 代码库。当然从根源上解决问题的话还是希望大家去论坛回复我的帖子,让 BlackMagic Design 开启构建开关,就不需要用奇怪的办法 hack 了。

by Alynx Zhou (alynx.zhou@gmail.com) at November 25, 2022 09:50 AM

November 23, 2022

Alynx Zhou

GTK 和 libhandy 和 Arc-Dark 主题

黑夜让我选了黑色的主题,但是有些程序非要寻找光明?

我自认为不是个对应用程序外观有着病态一致性要求的人,我也从不介意一些个性化的程序选择自己的特殊样式。所以当 GTK 4 推荐的 libadwaita 不再支持传统的 GTK 主题的时候我也没什么反应:毕竟这玩意默认的样式看起来还挺好看的。但即使是我这样宽容的人,对于 GTK 3 那个熟的不能再熟的 Adwaita 主题也审美疲劳了,那个银色和棕色会让所有手机厂笑话的,即使有些手机厂的审美还不如这玩意。

我个人最喜欢的配色其实是 Atom 的 One Dark 和 One Light,但我没那个精力利用调色盘自己维护一份主题,所以我退而求其次选择了在观感上比较接近的 Arc 主题,这个主题其实是一个系列,我自己只在乎里面的两个变体:全亮色的 Arc 和全暗色的 Arc-Dark(似乎它自己 README 里面给的截图也有点问题)。

我自己是一个暗色模式爱好者,毕竟长时间面对屏幕,白底黑字实在是太刺眼了,相对而言,深蓝色做背景色浅灰色做前景色要好看很多。在很久很久以前混沌初开,Linux 桌面程序员还没有意识到需要有个全局的暗色/亮色开关的时候,设置主题非常简单粗暴,打开 GNOME Tweaks 把 GTK Theme 设置为 Arc-Dark,我就心满意足了。

可能是 libadwaita 不能更换主题导致很多反对的声音,并没有太多人谈论随之而来的全局暗色模式开关,但是某天我更新了系统之后发现设置里多了一个亮色/暗色选择,我觉得挺好,那我这里选暗色就行了嘛,果然所有用了 libadwaita 的程序都跟着变了亮暗,不过我用着用着就感觉不对劲了——怎么以前那些 GTK 3 的程序不用 Arc-Dark 而是用 Adwaita-dark 了,这和我想的不一样啊?然后我研究了一下,觉得更奇怪了,GTK 4 + libadwaita 的 GNOME Settings 用的是 libadwaita 的暗色版本(预期行为),GTK 3 的 GNOME Tweaks 用的是 GTK 3 的 Adwaita-dark(不对劲),但是同样是 GTK 3 的 GNOME Terminal 用的是我设置的 Arc-Dark(预期行为)。好家伙好家伙,我这一个桌面上三花聚顶了。

三花聚顶

总这么待着我觉得怪怪的,于是我研究了一下,如果我要是选亮色模式呢?现在 GNOME Settings 是 libadwaita 的亮色版本了,然后 GNOME Tweaks 和 GNOME Terminal 都是 Arc-Dark,虽然好像一致了,又好像有点不一致,这回从三花聚顶变成黑白通吃。总之我忍受了很久 GTK 4 程序 大部分 是白的而 GTK 3 程序是黑的,直到我再也受不了决定翻开代码看看这些人是怎么写的。

黑白通吃

为了能说明白,下面我就不从结果反推原因了,毕竟大家看到这里可能已经云里雾里,没必要和我再重复一遍破案过程了。

可能大部分人不是 GTK 开发者也不使用 GTK,对这玩意怎么调用主题存在一定的误区。实际上可以分为以下几类:

  1. 不使用 libhandy 的 GTK 3 程序(比如 GNOME Terminal)和不使用 libadwaita 的 GTK 4 程序(比如 Show Me The Key),这一类程序不考虑 GNOME Settings 里面的亮色/暗色开关(指的是 GSettings 里面 org.gnome.desktop.interfacecolor-scheme 选项),而只考虑 gtk-application-prefer-dark-theme,这个值属于 GtkSettings,需要编辑 ~/.config/gtk-4.0/settings.ini~/.config/gtk-3.0/settings.ini。以及是的你没看错,GTK 4 不一定非要用 libadwaita,实际上虽然这个库叫 libadwaita,但它和 GTK 3 那个叫做 Adwaita 的默认主题几乎没有关系,它是 GTK 3 的组件库 libhandy 的进化版本。GNOME 推荐使用这个以便让整个桌面有统一的风格,但是 GTK 4 仍然是个完整的 UI 库,程序开发者完全可以不使用 libadwaita。
  2. 使用 libadwaita 的 GTK 4 程序(比如 GNOME Settings),这一类程序不考虑 GNOME Tweaks 里面的 GTK Theme 选项(实际上是 GSettings 里面 org.gnome.desktop.interfacegtk-theme 选项),只使用 libadwaita 内置的配色,所以我们也完全不需要关心它,它永远按照设置里的开关工作。
  3. 使用 libhandy 的 GTK 3 程序(比如 GNOME Tweaks),这个就相当复杂了,libhandy 考虑了桌面环境的亮色/暗色主题切换,但也考虑了用户自定义的 GTK Theme,于是在这里它华丽的乱套了。
  4. 还有最后一类程序,它们出于特定需要自己给自己套了自定义的 CSS,所以你拿它一点办法也没有,直接忽略(比如 Show Me The Key 的悬浮窗口)。

看到这里一定有小黑子要怒吼了:“看看你们搞的乱七八糟的玩意!GNOME 真垃圾!老子就要刀耕火种就要当原始人,libadwaita 滚啊!”但是我建议用你那可怜的小脑袋瓜想一想,上面三条里面反而 libadwaita 是最符合预期的一个(亮色模式用亮色,暗色模式用暗色),所以我不会解决第二个,而是解决另外的两个。

首先从 libhandy 下手,相关的代码位于 https://gitlab.gnome.org/GNOME/libhandy/-/blob/main/src/hdy-style-manager.c#L286-L348,如果你可怜的小脑袋瓜也没耐心看看代码的话,那么我大发慈悲替你读了一遍。相关的逻辑大概是说首先覆盖掉当前程序的 gtk-application-prefer-dark-theme(别忘了 libhandy 程序也是 GTK 3 程序),这个值会被设置成 color-scheme 的值。然后获取当前系统的 GTK Theme,因为我们考虑到浅色和深色主题切换,所以主题名被分成基础名和种类名两部分,如果系统的主题以 -dark 结尾,那就去掉这个后缀,得到基础名,并设置为当前程序的主题。那问题难道出在 Arc-Dark 的结尾是 -Dark 而不是 -dark 吗?也不是,GTK 主题对于暗色和亮色的区分不在主题名上,而是在主题目录下面的两个文件,一个叫做 gtk.css,另一个叫做 gtk-dark.css,如果 color-schemeprefer-dark,libhandy 就会加载后者而非前者,这部分的代码在 https://gitlab.gnome.org/GNOME/libhandy/-/blob/main/src/hdy-style-manager.c#L106-L141。于是在系统设置为暗色模式的时候,libhandy 会去加载 Arc-Dark 的 gtk-dark.css,但 Arc-Dark 作为一个暗色变体,只有 gtk.css,所以加载失败,libhandy 回退到 Adwaitagtk-dark.css。而系统设置为亮色的时候,libhandy 会去加载 Arc-Dark 的 gtk.css,而作为一个暗色变体,这个文件实际写的是暗色配色,于是看起来正常了。(以及如果你 GTK Theme 设置为 Adwaita-dark 从这里你就会发现实际上加载的是 Adwaitagtk-dark.css,而不是 Adwaita-darkgtk.css,即使它们的配色是一样的。)

那么显然又有另一个问题,既然主题是靠内部的两个文件区分亮色和暗色的,为什么又会有 Adwaita-darkArc-Dark 这种名字里带暗色后缀的变体呢?并且还要在 libhandy 里面处理这个后缀,是不是多此一举?我们可以暂时先不考虑这个问题,而先简单解决第三条。从上面的分析可以得知为了能正常支持系统的亮色暗色切换,我们需要的是一个同时包含亮色暗色的主题,而不是一个只有暗色变体的主题,于是我们不能把 GTK Theme 设置为 Arc-Dark,而应该使用 Arc,但假如你在 GNOME Tweaks 里面设置好之后,你会发现仍然是黑白通吃:GNOME Settings 是 libadwaita 的暗色版本,GNOME Tweaks 是 Arc 的暗色版本,而 GNOME Terminal 和 Show Me The Key 却变成了亮的 Arc!

小黑子们不要笑得太早了

我知道有的小黑子要迫不及待开始炮轰 GNOME 了:“什么玩意,整来整去不还是整不好吗,不如来当原始人。”但问题其实就是原始人留下的。现在我们回头看第一条:不使用 libhandy 的 GTK 3 程序和不使用 libadwaita 的 GTK 4 程序,这一类程序不考虑 GNOME Settings 里面的亮色/暗色开关,而只考虑 gtk-application-prefer-dark-theme。所以这个奇怪的表现恰好验证了这一条,同时也解释了“既然主题是靠内部的两个文件区分亮色和暗色的,为什么又会有 Adwaita-darkArc-Dark 这种名字里带暗色后缀的变体”这个问题:因为在一开始的设计里并没有什么全局亮色/暗色开关,也就没有要求主题同时提供 gtk.cssgtk-dark.css,那么为了让用户可以自选亮色暗色,只有提供两个不同的主题来解决问题。这也就是在 AdwaitaArc 都提供了 gtk-dark.css 的情况下仍然存在 Adwaita-darkArc-Dark 的原因。然后在主题添加了 gtk-dark.css 之后,为了让 libhandy 的程序能够跟随系统开关切换亮色和暗色,就不能为了那些传统程序把 GTK Theme 设置为暗色变体的主题了,此时如果设置为同时包含两个文件的主题,默认这些程序会选择 gtk.css,也就会出现上面截图里的情况。解决这个的方案也不是很困难,gtk-application-prefer-dark-theme 就是为此添加的,支持它的 GTK 程序会按照这个选项来加载 gtk.cssgtk-dark.css。如果你像我一样平时主要用暗色模式,那就手动编辑 ~/.config/gtk-4.0/settings.ini 写入以下内容(GTK 3 的话就是 ~/.config/gtk-3.0/settings.ini):

[Settings]
gtk-application-prefer-dark-theme=1

你要是亮色爱好者,那就改成 0。这下倒是满足原始人的刀耕火种需求了哈,毕竟他们看起来也不想要系统的亮色/暗色开关的样子,不过说不定以后哪天系统的亮色/暗色开关也会同时修改这个选项呢?只是读取这个选项的 GTK 程序不会像 libhandy/libadwaita 的程序那样会动态切换,必须要关了重开才行。

还有一个奇怪的问题要注意,通常我们是在 GNOME Tweaks 里面设置 GTK Theme,不过根据 https://gitlab.gnome.org/GNOME/gnome-tweaks/-/blob/master/gtweak/tweaks/tweak_group_appearance.py#L75-L88,它会把上面那个 gtk-application-prefer-dark-theme 设置成 0,看注释里面的 BUG 描述,应该也是为了某种刀耕火种的情况解决的(甚至那时候还推荐搞个单独的暗色主题,并且删除了全局的倾向暗色的开关),大概那时候还没推荐用 libhandy,也没有 libadwaita,也没有设置里这种全局暗色/亮色的开关。总之我不建议经常修改 GTK 主题,并且每次修改之后记得手动修改这个选项。如果你觉得这种反复横跳又要保证兼容以前的决策的行为很蠢,那我只能说毕竟你不能要求以前的开发者预见到未来的人们怎么定义桌面的功能。

当然如果你毫不在乎亮色暗色切换(我就是要一直用暗色,所以你暗色模式给我选对了暗色主题就行了!),那还有个比较投机取巧的解决方案:把 Arc-Dark 的 gtk.css 复制并改名 gtk-dark.css 就可以了,原理不难理解。并且 Arc 主题已经做了这样的修改,只是还没有 Release(参见 https://github.com/jnsh/arc-theme/commit/73ada8563591fa48ae365686a358e874ca12edad)。

by Alynx Zhou (alynx.zhou@gmail.com) at November 23, 2022 10:23 AM

November 09, 2022

Alynx Zhou

谁动了我的 DNS 解析?

如果有人看到这个标题以为是什么科学上网相关然后高兴地点进来的话不要怪我,我其实想说的是 Linux 上有关 DNS 解析的流程,这个标题显然是化用自《谁动了我的奶酪?》,即使我并没有读过这本书。我计网真的没认真听课,写的内容都是我现学现卖的,有不对的希望读者指正。

需求

我有一台 NAS,一台 PC 和一台路由器,为了能上网也为了家里的无线设备可以连接 NAS,我给 PC 和 NAS 分别接上路由器,但是路由器只有千兆网口,而 PC 和 NAS 各自多一个 2500 Mbps 的网卡,为了实现最高的连接速度,我又买了一根网线把 PC 和 NAS 直接连接起来,于是现在三台设备两两相连。

直连两台设备其实非常简单,Network Manager 里面 IPv4 设置成手动,然后分别配置 IP 地址和子网掩码,再关掉 IPv6 就可以了,比如我分别设置 IP 为 10.10.10.110.10.10.2,然后子网掩码就是 255.255.255.0。然后在进行各种网络访问的时候只要使用这个 IP 就可以通过直连访问了。

但是我还是不太满意,我设置了帅气的主机名,为什么还得用 IP 访问呢?但如果我查询主机名对应的 IP,发现得到的并不是直连的 IP,而是比如 192.168.1.80 这样的通过路由器的 IP。于是我开始研究如何配置让 DNS 解析给我返回直连的 IP。

long long ago

一般要讲故事,开头都是“很久很久以前……”,不过计算机领域也没什么太古老的故事可讲,毕竟公认的互联网前身 ARPANET 也就是二十世纪的事情。那个时候能互联的机器一共也就那么几个,所以解决的办法简单粗暴:我们每个机器都保存一个文件,里面记录所有人对应的域名和 IP 不就行了?这个优良传统一直留了下来,也就是现在所有系统里都有的 hosts 文件,不管你写的对不对,它的优先级都比 DNS 查询要高。对于我这个及其简单的网络环境,这肯定是不错的解决方案,但是程序员总会觉得这种非自动化的手段太 low 了,于是就被我 pass 掉了。

然后随着加入网络的机器越来越多,这个办法不好用了,毕竟每来一个新人就要所有人更新自己的文件,这复杂度也太高了。所以干脆我们搞一个集中的服务器专门放这个列表,其它机器都向它查询就好了。这就是 DNS 服务器的原理了,然后在局域网里,一般路由器和 DNS 服务器以及 DHCP 服务器都是同一台机器,因为很自然的所有设备都会连到路由器上,而 DHCP 服务器恰好知道它分配出去的 IP 地址,所以如果你输入主机名恰好能解析,那通常是你的路由器做了这些工作。但对于我这个子网来说,为了这两台电脑再配置 DHCP 和 DNS 显然太麻烦了,pass。

再后来各种子网越来越多,子网里的设备也越来越多,比如打印机这种,以至于现在各种智能家居,不可能再搞一个服务器用来注册“喂,我是茶壶”这种东西,于是苹果搞出了一个叫 Zeroconf 的协议,大概是在 DNS 的基础上可以让子网里支持这个协议的设备互相发现互相通知自己是什么。因为和 DNS 相关,所以有一个部分是 MulticastDNS (mDNS),简单来说就是不通过 DNS 服务器,而是通过这个协议发现的设备列表实现 DNS 解析。所以这是第三种方式。

以上三种方式其实都是我从 Arch Wiki 抄来的:https://wiki.archlinux.org/title/Network_configuration#Local_network_hostname_resolution

所以我决定搞一个第三种,这个好说,wiki 写了可以用 Avahi 做这个,不过怎么 systemd-resolved 也能做 mDNS?这玩意不是管 /etc/resolv.conf 的吗?Network Manager 不是也管这个吗?

chattr +i /etc/resolv.conf

很多 Linux 用户都知道修改 DNS 服务器可以通过编辑 /etc/resolv.conf 实现,很多 Linux 用户也被 /etc/resolv.conf 困扰,一些人发现自己的这个文件是个软链接,而另一些人发现这个文件总被 Network Manager 覆盖,还有些人的发行版让他们用一个叫 resolvconf 的工具处理,然后现在 systemd 又搞了个叫 resolved 的东西来插一脚……我说的这些已经足够让一些不想学新东西同时又神经紧张的人开始大喊“fuck systemd, fuck network manager, fuck desktop environment and fuck the whole modern world”然后执行 chattr +i /etc/resolv.conf 了。不过别着急小炸药包们,也许这个世界上新出现的各种东西目的并不只是惹恼你们这群大笨蛋,哦是的,没错,我说,大笨蛋,恐龙勇士(停停停不要翻译腔了),而是真的有场景需要他们。也许对于某个 VPN 连接需要使用自己的 DNS 服务器,总之,不要觉得世界都围着你转,至少读一下这些东西的文档,会告诉你怎么阻止它们修改你的 /etc/resolv.conf 的。

但其实也不是一个 /etc/resolv.conf 搞定所有,有关这个的故事也是 long long ago,但毕竟是 UNIX 纪元之后的事情,没有太久,大概确实上古时代的程序都是直接读这个获取 DNS 服务器然后再做 DNS 解析的,但实际上这也不一定 OK,比如像之前说的打印机这种怎么解决?以及 hosts 呢?所以就有了更复杂的解决方案,大部分程序做 DNS 解析实际上是调用 glibc 里面 getaddrinfo 这个 API,所以在它后面我们就可以做一些工作。一个叫做 Name Service Switch 的东西发明出来就是干这个的,它可以理解为一个基于插件的结构,我们可以通过阅读 /etc/nsswitch.conf 里面的 hosts 这一行来理解,比如我这里默认是这样的:

hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns

简单翻译一下的话意思就是查询一个域名的时候首先看看是不是 systemd-machined 的容器(mymachines 模块),不是的话再问问 systemd-resolved 能不能解析(resolve 模块),如果 systemd-resolved 可用,那到这也就完事了,后面的就不管了([!UNAVAIL=return]),至于为什么我一会解释,然后 files 模块会读 hosts 文件,所以它优先级总是高于 DNS 服务器,然后看看是不是本机(myhostname 模块),然后再读 /etc/resolv.conf 里面的 DNS 服务器进行查询。

对于一个普通的桌面用户,应该使用的只是 Network Manager,默认 Network Manager 不会用 systemd-resolved,于是大部分情况一个外部域名最后还是查询 DNS 服务器,和以前没什么本质区别。那 Network Manager 你为什么要修改 /etc/resolv.conf?原因之一就是之前提到不同的 VPN 服务可能有不同的 DNS 服务器,因此建议这些用户不要手动编辑这个文件,可以直接在 Network Manager 的连接配置里设置某个连接的 DNS 服务器。

那 systemd-resolved 又是什么玩意?是不是 systemd 作者又要搞出什么花样替换我习惯的东西?但这东西好像还真是有些实际的需求,它不是一个简单的 /etc/resolv.conf 的管理工具,而可以理解为是一个自带缓存的 DNS 服务器。glibc 通过 /etc/resolv.conf 查询 DNS 其实是不做缓存的,有些场景用户可能希望能自己缓存结果加快速度,这时候就需要搞这个东西了,它自己是一个 DNS 服务器,因此也就不再执行 nsswitch 里面后续的组件(用你的 dns 模块查询了我怎么缓存?)。除此之外它还声称自己提供了一个更好的 D-Bus 接口用来解析,而不是用 getaddrinfo,不过话又说回来,谁闲得没事去支持你这新搞的 D-Bus API,特别是你自己还搞了个 getaddrinfo 的模块。主观来说我其实不推荐一般的桌面用户配置这个,因为大概率你是在一个路由器后面,你的 DNS 服务器一般设置的都是路由器,而路由器上的 DNS 服务器一般会做缓存,所以其实完全没必要在自己电脑上启用这个……我也没遇到任何一定要使用它这个 D-Bus API 的程序。那 systemd-resolved 你为什么要修改 /etc/resolv.conf?原因是为了兼容那些直接读这个的上古程序,实际上人家就在这里写一行,就是让这些程序去查 systemd-resolved 内置的 DNS 服务器。

那至于 resolvconf 又是啥?这是一个叫 openresolv 的项目搞出来的东西,需求就是有各种程序都打算自己修改 /etc/resolv.conf,不单单是上面那两个,还有一些 VPN 服务之类的,那你们干脆都别管了,我来管,听我的(现在有 N + 1 种解决方案了)。但实际上我也不推荐你使用这个,因为桌面用户根本没有使用场景,你用 Network Manager 的话,就不要再单独使用什么 VPN 工具,因为 Network Manager 本身支持很多种 VPN 连接,你直接用它管理就好了。就算你需要用 systemd-resolved,其实这个也替你考虑好了,Network Manager 支持 systemd-resolved,检测到你用它的话,就不会去改 /etc/resolv.conf,而是直接去修改 systemd-resolved 的配置了。

计算机科学是门艺术,当且仅当你不需要考虑兼容性的时候……

那说回到 mDNS 这个问题,为什么我不直接用 systemd-resolved 解决呢?一个是上面提到的我不需要再做一次 DNS 缓存了,另一个是因为 CUPS 这个打印服务依赖 Avahi,它其实不只用到 mDNS,还用到了 Zeroconf 里面其它的功能比如发现设备去连接打印机,虽然我暂时也用不到 CUPS,但我确实是不想搞 systemd-resolved 的 DNS 服务器了,还是配置 Avahi 吧。当然假如你说我既想要 systemd-resolved 的 DNS 缓存和 D-Bus API 又想要 Avahi 的 Zeroconf 怎么办呢?额,其实也有办法,systemd-resolved 提供了选项让你关掉它的 mDNS 功能,具体我没有尝试,不过这样应该就不会冲突了。所以不要见到点新东西就生气,人家把各种兼容的东西都考虑到了,看两眼文档还不行吗……

然后搞清楚整个流程之后 Avahi 的配置其实不难,首先安装 nss-mdns 这个包,顾名思义是给 nsswitch 提供 mdns 模块,然后启动 avahi-daemon.service,然后编辑 /etc/nsswitch.conf,在 resolve 模块之前加入 mdns4_minimal [NOTFOUND=return]

hosts: mymachines mdns4 resolve [!UNAVAIL=return] files myhostname dns

mdns4 模块会试图通过 mDNS 也就是找网络上其它的 Zeroconf 协议设备来解析 IPv4 地址,4 表示只尝试 IPv4,因为这种内网设备多半你不会给它分配 IPv6,当然也有 6 和没有数字同时支持两种的,不过由于现在的程序都优先查询 IPv6,而我只给直连配置了 IPv4,所以如果不用只支持 4 的,就会 fallback 到后面的模块,那就跑到路由器上查去了,我就是不想走路由器的。

读者看爽了,但好像结果不是我想要的……

等到我把所有的东西都搞好以后我发现一个问题……mDNS 虽然说是子网上的设备互相发现,但是它没规定是哪个子网……于是喜闻乐见的每次 getent ahosts timbersaw.local 查询给我返回不一样的 IP,一会是 10.10.10.2 一会是 192.168.1.80,看起来还是写 hosts 比较靠谱……

最后我的配置是不用 mdns4,而是用 mdns4_minimal,这两个的区别是后者只考虑 .local 结尾的域名,并且如果查找不到的话直接返回 NOTFOUND,而不是继续 fallback:

hosts: mymachines mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns

然后再修改 /etc/hosts 分别添加不带 .local 的主机名(因为 .local 会在 files 之前先被 mDNS 处理)。

by Alynx Zhou (alynx.zhou@gmail.com) at November 09, 2022 01:06 PM

November 08, 2022

Felix Yan

[Arch] systemd 时代的 NBD 客户端持久化配置方法

NBD 用于提供块设备给远程设备使用是一种非常简便、低成本的方法。然而,让 NBD 开始工作的方法在网上能找到很多,但是 NBD 客户端的配置持久化却很难搜到比较完整的资料。在参考了一些过时博客、manpage 等比较分散的资料之后,我总算是凭借蛛丝马迹找到了应当是正确的配置方法。

1、自动加载 nbd 内核模块

echo nbd > /etc/modules-load.d/nbd.conf

(虽然——我觉得这件事应该在 nbd 包里完成,因为上游不愿意默认提供的理由只是为了考虑 nbd 未被编译为内核模块的情况。)

2、/etc/nbdtab

没错,光是发现这个东西就花了我不少时间。

nbd 服务器、连接选项等本来在 nbd-client 命令中配置的内容,应当被写到这个文件里。

一个简单的例子:

nbd0 192.168.0.10 export0 persist

显而易见,分别对应设备名、服务器地址、服务器上配置的 export 名、其他选项。完整的介绍可以参考对应的 manpage

3、/etc/fstab 和 nbd@<设备名>.service

到这里就是最后一步了,也是非常容易出错的一步。

此处的设备名应当和 nbdtab 内配置的设备名相符,nbdtab 的配置由这个对应的服务应用。和其他网络设备一样,挂载点、挂载相关的配置应当设置在 /etc/fstab。

这里需要使用 x-systemd.requires 来声明对 systemd 服务的依赖关系。由于服务会被这个依赖关系自动唤起,不需要手动 enable 服务。

/dev/nbd0 /var/lib/archbuild btrfs defaults,x-systemd.requires=nbd@nbd0.service,_netdev,nofail 0 0

这里的 _netdev 会让 systemd 等待网络可用后再进行挂载。注意我在这里写了 nofail 以避免因为网络原因使设备无法启动。如果要考虑在访问时再挂载,也可以改用 noauto,x-systemd.automount 之类的方法。


参考资料:

The post [Arch] systemd 时代的 NBD 客户端持久化配置方法 first appeared on Felix's Blog.

by Felix Yan at November 08, 2022 08:19 PM

November 07, 2022

Felix Yan

[Arch] OpenSSL 3 更新杂记

最近 Arch Linux 终于把 OpenSSL 更新到了 3.x 系列版本。一直以来,在处理涉及打包工具链本身的 soname bump 等更新问题时,我们一直缺乏一个透明、优雅的流程。

以往采用过的方法包括但不限于:临时往编译环境里手动塞旧版本兼容包、手动在过渡版本的新版 PKGBUILD 里再编译一份旧版包然后把 lib 装进去等。由于处理这件事的开发者一般独自完成了整个过程,留下来的资料除了 IRC 里的寥寥几语往往十分有限,对于其他开发者、或是下游发行版试图重现这个过程来说,都是一个比较痛苦的过程。

这一次趁着 OpenSSL 3 的机会,本喵深度参与了整个 bootstrap rebuild 过程,并且在 RISC-V port 里复现了一遍。现在记录一下大致的过程和遇到的问题,以备不时之需。

1、首先把旧版库打包,使其可以与新版库同时安装。

openssl-1.1:https://github.com/archlinux/svntogit-packages/commit/d50ecccc79b637830b71795bd919e6467e118ef0

由于需要避免文件冲突,相应的编译选项(–libdir)和 package() 过程中做了一些兼容性处理。如果这个包还需要在 rebuild 之后留下来,比如这次的 openssl-1.1 的情况,头文件和 pkgconfig 的 .pc 文件也需要做处理。如果只是作为兼容包,可以仅保留带 soname 和具体版本的库文件本身。(当然,这种情况下也可以考虑在新版包里直接编译一份旧版库安装进去,毕竟只是临时使用。

2、让新版库临时依赖旧版库的包

https://github.com/archlinux/svntogit-packages/commit/eef05b437f55c4d9403668ebdc27973c6a6c2134#diff-37538beb61ff63edebbf735dfcf39e5d732f49183d6beb097169d971875ca422R56

这里用到的技巧是,在 package() 方法内追加 depends,以避免编译环境中提前引入这个依赖,产生文件冲突(此时的仓库中,原包名对应的包仍然是旧版本,和 openssl-1.1 兼容包存在文件冲突)。

3、用此时的环境 rebuild 整个工具链需要用到的基础包

这一步具体要处理哪些包需要仔细分析。以这次 openssl rebuild 为例,一共有这么多包需要先使用这个依赖了旧版库的新版包来 bootstrap 以避免破坏打包环境本身:

coreutils
cryptsetup
curl
kmod
krb5
libarchive
libevent
libfido2 (for systemd's tests)
libssh2
openldap
pacman
sudo
systemd
tpm2-tss (for systemd's tests)

4、rebuild 新版库,去掉对旧版库的依赖

在完成这个步骤之后,需要重建 build chroot 以去掉之前引入的旧版库(这确实是 Arch 工具链的一个毛病,当前的机制不能自动保证 chroot 是干净、完整的)。

5、将前面步骤里 rebuild 过的基础包再次 rebuild

这一步主要是为了保证环境干净。编译得到的包应该和之前没有差异。

至此,已经可以继续平常的 rebuild 过程处理剩下的包了。


OpenSSL 更新进入稳定仓库后,在群组里果然遇到了大胆部分更新的用户不幸炸掉自己的 pacman。甚至有使用 pacman-static 的用户也因为 sudo 链接到 openssl 而悲剧了。再次奉劝大家切忌对官方仓库内的软件包部分更新,尤其是这种一看就不好惹的系统核心库!


另外,本喵自己的老 OpenVPN 配置在更新后也被制裁了:

SSL_CTX_use_certificate:ca md too weak

于是只好加入了 tls-cipher "DEFAULT:@SECLEVEL=0" 从此远离了推荐的配置

The post [Arch] OpenSSL 3 更新杂记 first appeared on Felix's Blog.

by Felix Yan at November 07, 2022 02:10 PM

November 06, 2022

Felix Yan

用 pacman-accel 给 pacman 加速

我在选镜像站的时候,总会遇到一个矛盾:镜像站访问快、镜像站和上游同步延迟低(同步到了最新的包)两者不可兼得。

比较容易想到的解决思路是:只从同步延迟低的镜像下 db,然后从速度快的镜像开始依次试,跳过 404 的镜像,直到找到一个已经存在该文件的镜像。

在过往的十来年里,我一直是通过写一个脚本来分别给 pacman -Sy 和 pacman -Su 设置不同的镜像来勉强解决的。但是这个用法在 pacman 最新系列中被破坏了——pacman 加入了一个镜像站如果 404 次数过多,在同一次更新中就再也不尝试了的新行为。

想到以往的用法会在命令中夹杂许多 404 报错,需要专门的脚本来换镜像体验也并不是很好,我写了个非常简单的本地服务来实现这个需求:

#!/usr/bin/ruby
#
# A simple local redirector for pacman, to get you the latest packages and
# utilize available mirrors.
#
# Usage:
# - Set multiple mirrors in /etc/pacman.d/mirrorlist-accel with ordering:
# https://fastest-mirror-but-updates-once-a-day/archlinux/
# https://relatively-slower-mirror-that-updates-more-frequently/archlinux/
# ...
# https://pkgbuild-dot-com-or-another-mirror-that-gives-you-the-latest-packages/
#
# - Set /etc/pacman.d/mirrorlist to this redirector:
# Server = http://127.0.0.1:4567/$repo/os/$arch

require 'http'
require 'sinatra'

mirrors = File.readlines("/etc/pacman.d/mirrorlist-accel").filter_map { |line| line.strip if line && line[0] != "#" }

get '/*' do |path|
    # Set TIER 0/1 mirrors as the last one, for:
    #  - DB syncing
    #  - Download fallback
    # These two use cases always the same server for consistency.
    mirror = mirrors[-1]

    unless path.end_with? '.db'
        # Find a faster mirror with the requested file present
        mirrors[..-2].each { |m|
            response = HTTP.head(m + path)
            if response.status == 200
                mirror = m
                break
            else
                logger.info "skipping #{m} for #{path}, code: #{response.status}"
            end
        }
    end

    logger.info "redirecting to #{mirror + path}"
    redirect mirror + path, 302
end

set :bind, ENV.fetch("PACMAN_ACCEL_BIND", "127.0.0.1")
set :port, ENV.fetch("PACMAN_ACCEL_PORT", "4567")

如注释所说,在 /etc/pacman.d/mirrorlist-accel 里按照本地访问速度依次设置几个快的镜像,并把最后一个镜像设置为和上游同步频繁的镜像即可。

贴上我目前使用的配置供参考:

$ cat /etc/pacman.d/mirrorlist-accel
https://mirrors.bfsu.edu.cn/archlinux/
https://mirrors.wsyu.edu.cn/archlinux/
https://geo.mirror.pkgbuild.com/

$ cat /etc/pacman.d/mirrorlist
Server = http://127.0.0.1:4567/$repo/os/$arch

原理很简单:pacman 访问本地 HTTP 服务,这个服务对非 .db 的下载请求按照配置依次 HTTP HEAD 直到找到一个返回值为 200 的镜像,然后返回 302 让 pacman 从这个镜像下载。

由于 .db 文件全部由最后一个镜像提供,而前面配置的镜像不存在对应文件时最终也会 fallback 到最后一个镜像,这样使用应该不会产生新的 db 不一致问题。

这个简单的工具日后会在我的小工具集 GitHub 仓库继续维护,同时我也创建了一个简单的 AUR 包:https://aur.archlinux.org/packages/pacman-accel-git

The post 用 pacman-accel 给 pacman 加速 first appeared on Felix's Blog.

by Felix Yan at November 06, 2022 12:43 AM

October 29, 2022

October 06, 2022

Lainme

给Git-for-Windows加上Pacman包管理

Git的Windows版自带一个msys2,但是没有集成Pacman包管理,不方便安装新的软件。这里记录一下给它加上Pacman包管理的方法。文章主要参考:

首先从MSYS2的仓库(https://repo.msys2.org/msys/x86_64/)下载pacman、pacman-mirrors、msys2-keyring三个软件并解压成.tar格式(可以安装7zip的插件modern7z来解压)。解压完成后用管理员身份打开一个Git Bash并执行tar命令,比如

tar -xvf ~/Downloads/msys2-keyring-1_20220623-1-any.pkg.tar -C / usr
tar -xvf ~/Downloads/pacman-mirrors-20220205-1-any.pkg.tar -C / etc
tar -xvf ~/Downloads/pacman-6.0.1-9-x86_64.pkg.tar -C / usr

然后配置Pacman,

curl --location https://raw.githubusercontent.com/git-for-windows/git-sdk-64/main/etc/pacman.conf > /etc/pacman.conf

再配置Keyring

pacman-key --init

导入Key

pacman-key --populate msys2
curl -L https://raw.githubusercontent.com/git-for-windows/build-extra/HEAD/git-for-windows-keyring/git-for-windows.gpg |
pacman-key --add - &&
pacman-key --lsign-key E8325679DFFF09668AD8D7B67115A57376871B1C &&
pacman-key --lsign-key 3B6D86A1BA7701CD0F23AED888138B9E1A9F3986

最后还需要手动创建Pacman需要的两个目录,

mkdir -p /var/lib/pacman/local 
mkdir -p /var/lib/pacman/sync

这样Pacman应该就可以工作了。

by lainme (lainme@undisclosed.example.com) at October 06, 2022 05:44 PM

September 23, 2022

中文社区新闻

从软件仓库中移除了 python2

Python 2 已于 2020 年 1 月结束了生命。我们一直在积极地减少软件仓库中对 python2 依赖的项目数量,终于到了最后能从我们的发行版中移除掉它的时候了。如果你的系统中仍然安装着 python2 请考虑删除掉它和所有 python2 的包。
如果你还需要使用 python2 包当然可以还留着,但是请注意它不再会收到安全更新。如果你需要打补丁的版本,请考虑使用 AUR 或者 非官方的第三方仓库

by farseerfc at September 23, 2022 02:17 PM

September 17, 2022

中文社区新闻

Arch Linux 邮件列表的变化

作为抛弃已经EOL的Python2工作的一部分,我们从 mailman2 迁移到了 mailman3 。

为了保持 DKIM 签名完整,我们已经关掉了改写 “From” 邮件头和主题(附加上列表名)的功能。这意味着想要回复邮件列表的时候,必须使用“回复邮件列表”功能,以及你可能需要更新你的过滤规则以匹配新的”From” 邮件头。

过去订阅过邮件列表的邮箱已经迁移到新的列表,不需要重新订阅。不过要管理你的订阅设置,必须重新注册新的 mailman3 账户。

by farseerfc at September 17, 2022 12:57 AM

September 10, 2022

frantic1048

GSC - Nendoroid 凯露

Karyl

最近终于稍微忙过来了,拍一波到了许久的凯露。本文不会有可怕的接头环节。

首先是盒子和藏在神奇位置的作为特技的装饰物。部件非常多,甚至连尾巴都是可拆卸的。

Karyl Karyl

接下来是本体! Karyl

素材力很高的书。 Karyl

「疯狂星期四,V 我 50」 Karyl

「快给我,不然……」 Karyl

「这就对了」 Karyl

「(ಥ︿ಥ)」 Karyl

September 10, 2022 05:28 PM

September 07, 2022

百合仙子

让离线软件真正离线

本文来自依云's Blog,转载请注明。

去年我做了个索引 Telegram 群组的软件——落絮,终于可以搜索到群里的中文消息了。然而后来发现,好多消息群友都是通过截图发送的,落絮就索引不到了。也不能不让人截图嘛,毕竟很多人描述能力有限,甚至让复制粘贴都能粘出错,截图就相对客观真实可靠多了。

所以落絮想要 OCR。我知道百度有 OCR 服务,但是我显然不会在落絮上使用。我平常使用的 OCR 工具是 tesseract,不少开源软件也用的它。它对英文的识别能力还可以,尤其是可自定义字符集所以识别 IP 地址的效果非常好,但是对中文的识别能力不怎么样,图片稍有不清晰(比如被 Telegram JPEG 压缩)、变形(比如拍照),它就乱得一塌糊涂,就不说它给汉字之间加空格是啥奇怪行为了。

后来听群友说 PaddleOCR 的中文识别效果非常好。我实际测试了一下,确实相当不错,而且完全离线工作还开源。但是,开源是开源了,我又没能力审查它所有的代码,用户量太小也不能指望「有足够多的眼睛」。作为基于机器学习的软件,它也继承了该领域十分复杂难解的构建过程,甚至依赖了个叫「opencv-contrib-python」的自带了 ffmpeg、Qt5、OpenSSL、XCB 各种库的、不知道干什么的组件,试图编译某个旧版 numpy 结果由于太旧不支持 Python 3.10 而失败。所以我决定在 Debian chroot 里安装,那边有 Python 3.9 可以直接使用预编译包。所以问题来了:这么一大堆来源不明的二进制库,用起来真的安全吗?

我不知道。但是我知道,如果它联不上网的话,那还是相对安全的。毕竟我最关心的就是隐私安全——一定不能把群友发的图片泄漏给未知的第三方。而且联不上网的话,不管你是要 DDoS 别人、还是想挖矿,收不到指令、传不出数据,都行不通了嘛。我只要它能从外界读取图片,然后把识别的结果返回给我就好了。

于是一个简单的办法是,拿 bwrap 给它个只能访问自己的独立网络空间它不就访问不了互联网了吗?不过说起来简单,做起来还真不容易。首先,debootstrap 需要使用 root 执行,执行完之后再 chown。为了进一步限制权限,我使用了 subuid,但这也使得事情复杂了起来——我自己都难以访问到它了。几经摸索,我找到了让我进入这个 chroot 环境的方法:

#!/bin/bash -e

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --cap-add ALL \
  --uid 0 \
  --gid 0 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --share-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  -- \
  /bin/bash -l

这里给了联网权限,是因为我需要安装 PaddleOCR。没有在创建好 chroot 之后、chown 之前安装,是因为我觉得拿着虽然在 chroot 里但依旧真实的 root 权限装不信任的软件实在是风险太大了。装好之后,再随便找个图,每种语言都识别一遍,让它下载好各种语言的模型,接下来它就再也上不了网啦(为避免恶意代码储存数据在有网的时候再发送):

#!/bin/bash -e

dir="$(dirname $2)"
file="$(basename $2)"

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --uid 1000 \
  --gid 1000 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --unshare-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  --ro-bind "$dir" /workspace --chdir /workspace \
  --setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
  --setenv HOME /home/worker \
  -- \
  /home/worker/paddleocr/ocr.py "$1" "$file"

kill $child_pid

这个脚本会把指定文件所在的目录挂载到 chroot 内部,然后对着这个文件调用 PaddleOCR 来识别并通过返回结果。这个调用 PaddleOCR 的 ocr.py 脚本位于我的 paddleocr-web 项目

不过这也太复杂了。后来我又使用 systemd 做了个服务,简单多了:

[Unit]
Description=PaddleOCR HTTP service

[Service]
Type=exec
RootDirectory=/var/lib/machines/lxc-debian/
ExecStart=/home/lilydjwg/PaddleOCR/paddleocr-http --loglevel=warn -j 2
Restart=on-failure
RestartSec=5s

User=1000
NoNewPrivileges=true
PrivateTmp=true
CapabilityBoundingSet=
IPAddressAllow=localhost
IPAddressDeny=any
SocketBindAllow=tcp:端口号
SocketBindDeny=any
SystemCallArchitectures=native
SystemCallFilter=~connect

[Install]
WantedBy=multi-user.target

这里的「paddleocr-http」脚本就是 paddleocr-web 里那个「server.py」。

但它的防护力也差了一些。首先这里只限制了它只能访问本地网络,TCP 方面只允许它绑定指定的端口、不允许调用 connect 系统调用,但是它依旧能向本地发送 UDP 包。其次运行这个进程的用户就是我自己的用户,虽然被 chroot 到了容器里应该出不来。嗯,我大概应该给它换个用户,比如 uid 1500,应该能起到跟 subuid 差不多的效果。

顺便提一句,这个 PaddleOCR 说的是支持那么多种语言,但实际上只有简体中文等少数语言支持得好(繁体都不怎么样),别的语言甚至连语言名和缩写都弄错,越南语识别出来附加符号几乎全军覆没。

by 依云 at September 07, 2022 07:12 AM

frantic1048

在 LilyPond 乐谱中插入有动态的 Ghost Note

一开始用 LilyPond 打谱的时候,学到的插入 ghost note 的方式是使用 \parenthesize 来给音符弄个括号,这样生成的乐谱阅读上没有什么问题,但是生成的音频听起来的话,音符加 \parenthesize 前后声音完全没有区别,试听乐谱的时候不太方便。搜了一圈 2019 年也有人遇到同样问题,但是看起来没有后续。

目标上来说希望有个神奇命令能搓出一个「真」的 ghost note:让单个音符生成的乐谱和 \parenthesize 效果一样,同时生成的音频里音量能比普通音符小一些。

先来波折腾结果示例,两个小节里的 ghost note(那些带括号的音符)分别由 LilyPond 的 \parenthesize 和本文介绍的自定义函数生成。

ghost-note-comparison

ghost-note.ly

过程

文档翻啊翻,找到 midi-extra-velocity 这个影响音符音量,但是又不会像强弱记号那样影响后续乐谱音量的属性,加上现成的 \parenthesize 加个括号,搓一个函数:

ghosta = #(define-scheme-function
           (note)
           (ly:music?)
           (displayMusic note)
           (ly:music-set-property! note 'midi-extra-velocity -50) ;; 减小音量
           (displayMusic note)
           #{
             < \parenthesize #note >
           #}
           )
 
% 乐谱里用 `\ghosta sn4` 这样的方式调用

define-scheme-function 用法参见 2.2.1 Scheme function definitions。第三个参数开始的所有内容会被依次求值,最后一个部分会作为函数的返回值。

displayMusic 是 LilyPond 提供的检查音符(音乐)内容的函数,它能把音符在 LilyPond 内部的表示形式给显示出来(默认显示在 lilypond 的命令行输出里),一般会看到的是一串更低级的 LilyPond 函数的调用,调试 Scheme 函数的时候很有用。

对于刚刚实现的那个函数,写一个 \ghosta sn4,函数里两个 displayMusic 会给出下面的信息(为了方便阅读,下面内容有手动排版和加注释,不影响含义):

;; 修改之前的音符
(make-music 'NoteEvent
  'drum-type 'snare
  'duration (ly:make-duration 2))
 
;; 修改之后的音符
(make-music 'NoteEvent
  'midi-extra-velocity -50
  'drum-type 'snare
  'duration (ly:make-duration 2))

可以看到预期的属性被设置上去了,接下来检查乐谱,括号显示没啥问题,然后是音频,发现音符的音量并没有什么变化,这不太对劲。

接着翻着翻到了源代码,发现 midi-extra-velocity 看起来只有在音符的 articulations 属性里面的元素上才有机会被用上LilyPond - Internal Reference 对 articulations 属性的描述是

articulations (list of music objects)

Articulation events specifically for this note.

直接在音符的属性上修改属性看起来不太行了,那就照它说的搓一个设定了midi-extra-velocity 属性的 ArticulationEvent。用来生成 ArticulationEvent 的内置函数 make-articulation 需要一个额外的 name 参数为了避免更多的奇奇怪怪错误, 尝试用了 LilyPond 已有的 accent 这个用于重音的 articulation 名。

ghostb = #(define-scheme-function
           (note)
           (ly:music?)
           #{
             < \parenthesize #note #(make-articulation "accent" 'midi-extra-velocity -50) >
           #}
           )
 
% 乐谱里用 `\ghostb sn4` 这样的方式调用

这下声音调整成功了,但是音符多了个重音记号,这个还好,能用 \omit 去掉。

TL;DR

于是生成 ghost note 的能用的函数终于搓出来了:

ghost = #(define-scheme-function
           (note)
           (ly:music?)
           #{
             <\parenthesize #note \omit #(make-articulation "accent" 'midi-extra-velocity -50)>
           #}
           )
 
% 乐谱里用 `\ghost sn4` 这样的方式调用

参考

September 07, 2022 12:00 AM

September 01, 2022

Lainme

在Centos7/8上使用HillstoneVPN

公司内网使用的是Hillstone的SSL VPN,官方的Linux客户端只支持非常有限的几个老发行版,连Centos7都不支持。不过有些发行版并非真的不能用,只是程序里对发行版做了判断,不是指定的几个就强行退出。

后来我发现有人做了一个移除了版本限制的客户端,在Centos7下面可以完美运行:https://github.com/JackMoriarty/HillstoneVPN/releases

但在Centos8上运行时总是报错,

Unit dbus-org.freedesktop.resolve1.service not found

这个报错的服务是systemd-resolved的,默认没有开启。但我启动systemd-resolved服务后,虽然不再报错,VPN连接后却完全无法上网。搜索后才知道systemd-resolved更改了很多原本的机制,VPN软件必须做相应的修改才能使用。那么怎么才能不用systemd-resolved,又不让客户端报错呢?我花了几个小时终于发现了最简单的解决方法,删除/sbin/resolvconf

虽然客户端可以用了,但我对这个客户端很不满意:

  • 无法做成systemd服务开机自动启动和连接指定的VPN
  • 需要在桌面持续运行,无法很好的最小化

经过一些搜索后,我发现了一个第三方的命令行客户端Hilldust (https://github.com/LionNatsu/hilldust),基本能解决上述问题,但它也有自己的一点不足

  • 不支持从配置文件读入参数,在多人使用的电脑上会暴露密码
  • 没有从VPN服务器返回的路由表中设置路由,对我们公司的网络无法设置正确的路由
  • 不能很好的作为systemd服务运行,主要是退出时的判断仅有KeyInterrupt,没有考虑SIGNAL。
  • 使用iproute2来配置网络,可能和NetworkManager冲突。另外网络复位主要靠save & restore,如果在VPN运行期间还做了其他网络的调整,复位的时候也会回退这些调整。

于是我在Hilldust上重新做了一个Wrapper,处理了一下这些地方,在Centos7/8上都测试通过了。如果你也需要的话,可以试试:https://github.com/lainme/hilldustWrapper

by lainme (lainme@undisclosed.example.com) at September 01, 2022 01:06 PM

August 30, 2022

中文社区新闻

Grub引导器升级的配置文件不兼容

grub 包最近的变化给 fwsetup 命令增加了新的命令参数,改变了生成出的引导配置中调用命令的方式。根据你的硬件和设置这可能导致系统不能引导,因为安装的引导器和配置文件之间不兼容。在 grub 包升级之后,我们建议重新运行以下命令,安装和再次生成配置:

grub-install ...
grub-mkconfig -o /boot/grub/grub.cfg

关于 grub-install 的具体用法请参考 wiki 页面:GRUB – ArchWiki

by farseerfc at August 30, 2022 11:36 PM

August 22, 2022

frantic1048

给罗技 G903 换微动

用了三年多的 G903 最近按键开始双击了,网上翻了一下有现成的微动板可以直接换,于是就搞了一组同款欧姆龙 D2F-F-K(50M) 微动板,以及一组新的脚贴(因为会被拆掉)来拯救鼠标。工具上需要 1.5 的十字螺丝刀、T5 螺丝刀、镊子和翘棒,以及肯定会发现鼠标超级脏大量积灰皮屑谜之毛茸茸,弄点清洁工具。图里左右侧蓝色线的那个东西就是微动板。

G903

首先顶盖有 1 颗露出来的 T5 螺丝和 6 颗隐藏的十字螺丝。开关上方(靠近 USB 口)这颗是 T5 螺丝,直接拧下。

G903

下面这几颗,需要先拆掉图里圈出来的位置的胶贴,以及撬开一点点开关下方的贴纸,用镊子去戳边缘,然后撬开就行。

G903 G903 G903

然后可以捏着鼠标侧面来分开顶盖和底板,然后从 USB 口那侧掀开,绝对不能大力,因为两者还有排线连着。

G903

上图方框的位置是排线的插口,那个白色的东西可以掀开,然后排线就会松开;圆圈是电池的线,那个就直接拔下来,比较费劲,需要小心不要把插座弄坏了。

G903 G903 G903

连接的部分弄开之后就可以拿开底板了,接下来是电池(它挡住了一颗需要拆的螺丝 (┙>∧<)┙ へ ┻┻),电池是背面被双面胶粘着的,用翘棒温柔地从电池一侧捅进去,一点点分离开电池底面有胶的地方,过程电池会稍微一点点变形,问题不大 ˊ_>ˋ 。

G903

电池掀掉之后是下面这几个螺丝,拆了之后,把鼠标侧键的盖子扒下来,分离手掌部位的那块外壳。

G903

然后是鼠标左右键的螺丝。

G903

这个时候已经能看到微动了,但是还得继续拆中间滚轮上面那个盖子的螺丝。

G903

然后拆固定微动板子的螺丝。

G903

然后拔掉图里的插头,分离旧的微动板,把新的微动板的插头插到相同的位置,这样换微动就算完成了。

G903

最后,看到一堆零件后悔也没用了,怎么拆的就怎么组装回去,鼠标就好了。这鼠标真是难拆!!!

G903

August 22, 2022 07:00 PM

August 18, 2022

Alynx Zhou

不应该做 EVA,而应该做环太平洋

想必把 2018 年的 DARLING in the FRANXX (名字太长了,后面简称 DitF 吧)称作有争议的作品应该不会有人反对,不过我恰好是个不喜欢追新番的人,不然也许我在 2018 年写篇关于这个的博客应该会能获得不少点击量。总之我在 2022 年下载了全集并且几乎是不间断的在三天之内看完了,可能不是特别好评价,但是觉得还是得写点什么。如果读者觉得“怎么复读了很多已有的观点”或者“和我想看的完全不一样”,还麻烦多包涵或者自行关闭标签页。

我认为这是一个优点和缺点同样突出的作品,倒不像很多人觉得是烂尾,结尾至少情理之中可以接受。主要问题是在于塑造人物形象和完善背景设定之间的冲突,也就是标题里写的“不应该做 EVA,而应该做环太平洋”。看完之后我半开玩笑地和 @垚 说:“都怪庵野秀明,非要在巨大机器人动画里面加上一堆反乌托邦末世玄学宗教的背景设定,导致后来的巨大机器人动画不这么做就好像缺了点什么一样。”我其实对巨大机器人动画不算是专家(比如我显然没看过高达),但我觉得对 EVA 还算是熟悉。

虽然我想说的问题是剧情方面的,不过还是要简单提一下作画。@垚 和我表示他一开始是奔着 TRIGGER 才去看的 DitF,结果看过之后对于动作场面大失所望。我其实也不是特别了解 TRIGGER,只是之前被他拉去电影院看了普罗米亚。一定要比较的话确实不管是美术风格还是动作场面都没有普罗米亚那么有特色,但我还是觉得至少在及格线以上了。我觉得特别出色的是机体的设计,不管是 EVA 还是环太平洋,机体设计都是偏向机械化的(虽然 EVA 内在是生物,但是外表仍然是机械),同时是男性化的设计。我还是头一次在动漫里看到女性形象和不是特别机械化设计的机体,而且甚至有丰富的表情,非常新鲜的同时也很符合设定(实际上动画里很多时候使用了 FRANXX 的形象代替女性寄驶员),属于是一个巨大的加分项。叫龙的设计也算新鲜,至少对我来说,看第一集的时候我明明期待的是出来一个传统的怪兽形象的,结果出来的是这么一种可以算是放飞自我的东西。虽然在逻辑上可能比较难以解释它的存在性,不过好在是动漫嘛,不需要在里面找现实。(话说回来了解我的朋友应该知道我最喜欢的敌人设计是 NieR: Automata 里面的机械生命体。)

然后说到我最关注的剧情了,我始终认为作品的核心是剧情。而且剧情是很难把控的东西,特别是对于原创剧情的作品来说,把剧情写好真的是一种不多见的能力。在我看来 DitF 在人物形象和感情戏上达到了一个极高的高度。主要人物有很多,但是每个人的个性都很清晰,并且我没有觉得哪个人的性格令我讨厌。同时故事本身不是简单直白一眼看到头的类型。比如第 13 集将故事推向了高潮,不仅仅因为这一集本身讲述的内容非常感人,而且将整个剧情前半部分埋下的伏笔全部都衔接上了(我甚至差点以为剧情要按绘本发展走向 bad end)。贯穿全篇出现过不止一次的“比翼鸟”的比喻,也非常的符合主题。但我在看一些二创视频的时候还是能看到一些 2018 年的评论在说第 14 集的剧情是喂屎,我确实可以理解追更的朋友当初等了一周之后看到这些阴差阳错然后还要提心吊胆等上一周才能看到下一集的焦躁心情,但这一集的矛盾激化成功的在一个高潮之后推进了剧情的节奏同时与下一集的高潮形成对比,而且这一集的内容非常的合理,虽然是各种巧合,但又很符合现实,符合人物的心理和动机。至于其它一些风评不好的部分比如搭档交换的剧情,只能说是见仁见智,有人不喜欢无可厚非,我还是觉得这部分也增加了故事的复杂性。

但是与塑造人物形象形成对比,完善背景设定方面我认为有比较严重的硬伤。24 集里面在前半部分简单介绍了一个可以说是反乌托邦的设定,然后大量的篇幅用来刻画人物之间的关系和人物细腻的心理活动,结果在感情戏达到高潮之后仿佛是编剧突然想起来“啊,我们挖了好大的坑还没填呢”一样,开始匆忙的填之前的坑。比如我到现在也没想通只在第 15 集里出现了一次的那个巨大的手到底是什么和有什么存在意义。比如叫龙公主在第 17 集开始有大篇幅的剧情之前几乎没有任何铺垫(你别告诉我第 15 集核心里掉出来的小人就算铺垫了),这和感情戏部分各种伏笔先放好然后再衔接完全不像是一部作品的风格,反正你让我看到前半段各种叫龙出现之后是想不到有这么一个个体的存在的。第 19 集通过介绍博士的角度介绍了人类向不死方向的发展,多少算是成功地填了一部分坑。然后整个作品似乎就陷入了“编剧发现还剩五集了填不完坑了于是开始放飞自我”的方向发展了,星实体是个什么东西?之前完全没铺垫过,现在强行在一集内塞给观众。鹤望兰·天燕座又是怎么来的?就算不在动漫里找现实,这也过于不符合逻辑了,看看别的叫龙是什么样子,它们怎么搞出这么个造型的啊。到底是博士发癫了还是编剧发癫了?然后可能是由于实在没办法了,机械降神一个最大反派叫做 VIRM,这已经不是硬伤了,这是直接一刀把脑袋砍掉从脖子往下截肢了。于是一个完全没有铺垫,思维及其简单,做事不讲逻辑的工具 boss 出现了……你可以告诉我打了半年的敌人并不是真正的敌人,但我不能容忍你用这么一种侮辱观众智商的方式告诉我这个事实。然后再一次放飞自我把科技水平拉到太空时代,说实在的,这个和反派登场比起来,已经到了我看见什么都不惊讶的程度了。然后再经过几集漫长的毫无必要的人类叫龙和 VIRM 的太空混战剧情终于结束了这种煎熬,我只能说这部分比起种田是完全的不讲道理了。

至于最后大结局的“生孩子”剧情,想必也有很多人不满意,虽然我自己讨厌小孩,但我觉得这部分不是什么问题。可能是考虑到当下的现实环境,年轻人确实对催生比较反感吧,但放在剧情里面,作为一个新世界开始的必要环节实际上是没什么问题的。最后黑色头发的少年和樱花色头发的少女在树下相遇的结尾也是我最满意的部分之一了,或者可能我恰好是一个容易被这种剧情打动的人吧。

另外 @垚 表示同样是 TRIGGER 的作品,普罗米亚的剧情要好很多,但我不是特别同意,我个人觉得普罗米亚最后 1/4 的剧情其实也向着强行收尾的方向走了,不过一部剧场版动画和季番还是不一样的,剧场版动画只有两个小时,不会有太复杂的背景设定,故事本身又是快节奏,就算强行推进一下剧情,观感也不会太差,而且独特的美术风格和配乐相当大程度的掩盖了剧情的问题。

总而言之,我其实是不太在意“符合逻辑但是大部分观众都不喜欢”的剧情的,比起这个,我觉得“不符合逻辑”的剧情问题要严重得多,这说明剧情走向已经变成无法把控的东西了,为了强行在剩下的集数里面结束故事,不得不强行引入一些东西。如果你问我怎么修改剧情能解决掉硬伤,我其实一开始也没什么思路。不过和 @垚 简单聊了一下之后我意识到了问题所在。从感情戏的篇幅和水平来看,很显然巨大机器人战斗只是个载体,这应该是个披着机器人战斗的皮的爱情故事,并且爱情故事部分相当的成功。在我看来 DitF 的思维和 EVA 其实是有相当大的差别的,虽然可能大家总是津津乐道碇真嗣、绫波零、明日香、渚薰之间的情感关系,但 EVA 没有对这部分的直接描写,更多是通过侧面细节描写,以及粉丝进行的分析推理得出的。所以 EVA 可以在背景设定上挖很大的坑然后有充足的时间填坑。因此我的想法是让这个作品向环太平洋的方向靠近,去掉反乌托邦的设定,比如什么种植园和 APE 都可以不要,直接快进到不知道为什么出现了名叫叫龙的怪兽进攻人类,于是人类设计了 FRANXX 并要少年少女操作来防御。然后人物的背景全部都不需要修改,最终的结局就像环太平洋一样直接摧毁掉叫龙来源就可以了。虽然可能会被 EVA 观众认为“没有达到 EVA 的高度”,但既然本来就不在一个赛道上(我这写的是爱情故事啊),这也没什么所谓了。考虑到第 13 集和 15 集的口碑,这样改应该不会折损它的优点。

最后还是要说,虽然有这么明显的硬伤,这部作品突出的优点还是让我受到了很大震撼并且在接下来的一周都沉浸在剧情里不能自拔。我个人也非常喜欢这部作品的 ED,无论是旋律还是歌词,以及演唱方面都可以说是一流的作品,特别是第 13 集高潮部分的《ひとり》,单从音乐和剧情结合的角度来说,确实达到了 EVA 的高度(让我想起来《翼をください》。《トリカゴ》也是绝妙的作品,特别是伴随 ED 出现的画面,“如果这些人物所在的是一个没有叫龙和 FRANXX 存在的世界会是什么样子呢?”(很遗憾我对 OP 没什么感觉)我同样对广、02、五郎和莓的人物形象非常的喜爱,复杂的情感关系使得这些人物变得颇为立体,而且他们完全没有动漫里一些经常出现的会让我讨厌的人物特质。

by Alynx Zhou (alynx.zhou@gmail.com) at August 18, 2022 05:06 PM

百合仙子

我所讨厌的网页行为

本文来自依云's Blog,转载请注明。

有些网页的行为通常不被视为 bug,甚至是故意为之,但很令人讨厌。这里记录一些我所讨厌的网页「特性」。它们被归为两类,要么导致某些场景下用不了,或者用着很不方便,要么很打扰人。

可访问性问题

忽视系统、浏览器设置,在浏览器使用浅色主题的情况下默认使用深色主题,或者在浅色主题下代码部分使用深色主题。反过来问题不大,因为我有 DarkReader

主体文本不支持选择和复制。选择和复制之后,用户能做很多事情,比如查生字、翻译、搜索相关主题。

已访问链接与未访问链接显示没有差别。

消除可交互元素(链接、按钮)的 outline。这个 outline 以前是虚线框,现在火狐改成了蓝色框,用于标识当前键盘交互的对象。

搜索框不支持回车确认,必须换鼠标点击。

位于文本框后的按钮不支持使用 Tab 键切换过去,并且 Tab 键在此文本框中也没有任何显著的作用。必须换鼠标点击。

需要交互的元素不能被 vimium 插件识别为可点击。这大概是使用非交互元素来处理交互事件,甚至事件监听器都不在元素本身上。

使用 JavaScript 实现原本可以直接用链接实现的内容(链接目标是某个 JavaScript 函数调用)。这导致我无法使用中键来在新标签页中打开。

显著不同的内容没有独立的 URL。尤其见于一些单页应用(SPA)。要到达特定内容(比如加书签或者分享给别人),就只能记录先点哪里、再点哪里等。

预设用户的屏幕大小,导致浏览器窗口过小的时候部分关键内容(如登录按钮)看不到、无法操作。

交互元素没有无障碍标签。成堆的「未加标签 按钮」。

通过 User-Agent 判断浏览器,并拒绝为某些 User-Agent 服务(但实际上去除这个限制之后,功能是完全没有问题的)。

当没有带声音自动播放权限时,无声播放主体内容(而非等待用户操作使其获得权限)。说的就是 Bilibili。

为大屏幕用户(如桌面用户)展示为手机屏幕设计的页面。这些页面中字体特别巨大,并且不能被浏览器缩放影响。交互元素上鼠标指针不改变为手状,甚至只支持触摸操作而不支持鼠标点击。

悬浮于主内容之上的「在App中打开」。点名批评 imgur。它的按钮不光挡住图片,而且用户放大图片的时候它也被放大,挡住更多图片内容。

不能禁用的图片懒加载,或者视频内容被移出画面、切换到后台就停止加载。点名批评 Telegram、维基百科。我等你加载呢,你非要我盯着看你加载浪费时间?现在网好,你赶紧给我加载好,进电梯或者地铁或者山洞了,我再慢慢看你的内容啊。

视频内容被移出画面就停止播放。点名批评知乎。我让你播放你就给我播放。我不看视频,是因为视频画面没啥可看的,可是我听音频部分呀。

覆盖浏览器的 Ctrl-F 查找快捷键,并不提供方案来避免覆盖。我就搜索当前页面,不要你的站内搜索功能。

注册前请务必先阅读用户条款和规则,用户条款和规则页面需登录后才可访问。

简体中文内容指定繁体中文的字体,或者添加繁体中文的标签。或者反过来。

打扰用户

在内容页面,任何会动的非主体内容,包括但不限于广告、内容推荐。形式可以是动态 GIF、滚动动画、视频等。用于首页渲染效果的背景动画和视频不算,作为主体内容者也不算。

针对非音视频网站,自动或者非用户明确表达地(比如在用户点击不相关内容时)播放带音频的内容。

消耗 CPU 的背景特效。如 canvas-nest。会让 CPU 很吵,也会浪费能源、加剧气候变化。

by 依云 at August 18, 2022 09:14 AM

August 17, 2022

Alynx Zhou

阻止 clangd 污染项目根目录的一些方法

Emacs 的 lsp-mode 推荐使用 clangd 分析 C/C++ 代码,用起来体验还不错,但是让人非常恼火的是用户要主动或者被迫地在项目根目录下面添加一些文件,比如 .clang_complete 或者 compile_commands.json 来让 clangd 知道项目需要包含哪些库的头文件,以及 clangd 会直接把建立的索引丢到项目根目录下面的 .cache 目录里。虽然可以把这些加入 .gitignore,但保不齐哪个脾气古怪的上游维护者会和你纠缠半天让你解释为什么要加这些,实在是很麻烦。

compile_commands.json 比较好解决,clangd 提供了一个参数 --compile-commands-dir=<string> 可以指定查找这个文件的目录,我直接把它设置为 ./build/,因为大部分项目的 .gitignore 都会包含构建目录,也免得运行 bear -- meson compile 之后再把这个文件从 build 目录移出来。

(use-package lsp-mode
  :ensure t
  :commands lsp
  :hook ((c-mode . lsp-deferred)
         (c++-mode . lsp-deferred)
         (c-or-c++-mode . lsp-deferred)
         (lsp-mode . lsp-enable-which-key-integration))
  :bind (:map lsp-mode-map
              ("M-." . lsp-find-definition)
              ("M-," . lsp-find-references))
  :custom
  ;; Move lsp files into local dir.
  (lsp-server-install-dir (locate-user-emacs-file ".local/lsp/"))
  (lsp-session-file (locate-user-emacs-file ".local/lsp-session"))
  (lsp-keymap-prefix "C-c l")
  ;; Only enable log for debug.
  ;; This controls `*lsp-log*` buffer.
  (lsp-log-io nil)
  ;; JavaScript (ts-ls) settings.
  ;; OMG, the FUCKING EVIL SHITTY VSCode TypeScript language server generates
  ;; log in project dir, can MicroSoft stop to let their software put shit in
  ;; front of users?
  (lsp-clients-typescript-server-args '("--stdio" "--tsserver-log-file" "/tmp/tsserver-log.txt"))
  (lsp-javascript-format-insert-space-after-opening-and-before-closing-nonempty-braces nil)
  ;; Always let clangd look for compile_commands.json under build dir so it will
  ;; not make project root dirty.
  (lsp-clients-clangd-args ("--header-insertion-decorators=0" "--compile-commands-dir=./build/" "--enable-config")))

对于 .cache/ 就不是那么好解决了,根据 https://github.com/clangd/clangd/issues/341#issuecomment-1003560792,似乎他们并没有关闭或者修改缓存目录的支持。不过我想到一个弯道超车的方案,git 本身应该是有从其它位置加载用户定义的 gitignore 文件的功能的,我利用这个写一个本地的 gitignore 不就行了吗,搜索之后得到 https://stackoverflow.com/questions/5724455/can-i-make-a-user-specific-gitignore-file,操作起来也很简单。首先我把这个文件放到 ~/.config/git/gitignore,里面写上要忽略的 glob,然后运行 git config --global core.excludesfile ~/.config/git/gitignore 就大功告成。

不过就在我写这篇文章时,clangd 的 issue 上有人回复我,根据 https://github.com/clangd/clangd/issues/184#issuecomment-998244415,现在 clangd 应该是会把索引放在 compile_commands.json 所在的目录,所以多少也算是解决了问题吧。虽然这样删掉构建目录之后索引缓存也没了,不过我觉得比起重建缓存,还是弄脏项目目录更恶心一点。

更新(2022-08-17):还有一个头疼的问题是 GLib 的 g_clear_pointer 宏里面使用到了对指针本体取 sizeof 的语法,而 clangd 默认会认为这是个错误,于是 lsp 就会标出一大堆问题。可以对项目进行设置关掉这一条,不过又会弄脏项目目录,查询文档得知 clangd 会读取 ~/.config/clangd/config.yaml 这个用户级别的配置文件,于是在里面写入内容关掉这条检查:

Diagnostics:
  ClangTidy:
    Remove: bugprone-sizeof-expression

然后给 clangd 传递 --enable-config 这个参数即可。

by Alynx Zhou (alynx.zhou@gmail.com) at August 17, 2022 08:28 AM

August 10, 2022

百合仙子

tmux 状态栏优化

本文来自依云's Blog,转载请注明。

在 tmux 的状态栏里,通常会显示当前时间。配置起来也非常简单,%Y-%m-%d %H:%M:%S这样的时间格式化字符串扔过去就可以了。然而这样做有个小问题:这个时间只能精确到秒。我的意思不是说我想让它显示毫秒,而是希望它像电视台和广播电台的时间一样,显示(播报)「12:00:00」的时候,就刚好是这一秒的开始。

一般来说,这么延迟个一秒以内的随机数问题不大,除了你有多个这种时间戳的时候——

tmux inside tmux inside tmux

这些时间戳哪个先更新、哪个后更新可完全说不准的,你可能看到明明在地球另一边的服务器上先到某一秒,本地才跟上。甚至同一个 tmux 的不同客户端里,这个时间戳的更新时间都可能会有差异。

我想优化这个的另一个原因是,我经常使用 extrace 来查看程序调用另一程序使用的命令行参数,然而我本地连了多少个 tmux,每秒便会有多少个 sh + awk 进程出来读系统负载。尤其是我从 Awesome 换到 Wayfire 之后,顶栏改用 waybar 了,很多指示器都是内建或者自己写的外部脚本,不再需要每隔几秒跑个子进程去获取信息,这样 tmux 调用子进程来刷新状态造成的干扰就突显了出来。

于是就有了 accurate-time 程序。它每个整秒会去读系统负载,然后和当前时间一起送给 tmux 来显示。每秒一个进程,已经少了很多啦。

既然是我的程序自己来读负载,也就方便做更多事情了,比如根据负载情况使用不同的文字颜色:绿色表示低负载,灰白是稍微有点活干,蓝色和 cyan 是比较忙碌,黄色、品红表示已经忙不过来啦,红色就是要累趴下啦。之前偶然间发现 qemu-git 这个包使用 ninja、但是链接的时候又套了一层 make,造成系统负载冲到了两百多。但是无论高低,tmux 的负载显示都是红色,所以我可能之前已经视而不见许多次了。加上颜色之后,这类异常就更容易被注意到了。以前我本地每次风扇呼呼地转才发现系统负载高,但是我要是用耳机的话就听不到了,现在也多了个高负载的指示。

安装和配置很简单,cargo build --release 编译,然后把编译出来的 target/release/accurate-time 扔到 $PATH 里,再如下配置 tmux 状态栏右边即可:

if-shell "accurate-time tmux" {
  set -g status-interval 0
} {
  set -g status-interval 1
  set -g status-right "#[fg=red]#(awk '{print $1, $2, $3}' /proc/loadavg) #[fg=colour15]%Y-%m-%d %H:%M:%S"
}

本来我还打算给 waybar 上的时间也这么做一下的,不过程序写好了才发现 waybar 自己已经把时间对齐到更新间隔了。

by 依云 at August 10, 2022 10:43 AM

Alynx Zhou

PHP 故释

// 新来的!如果你看到这段注释,说明上一个负责重构这个项目的程序员已经被气死了!
// 请你把下一行的数字加一,然后祝你好运!
// 63

起因是昨天晚上吃完饭回家路上和铁道迷闲聊说起他正在重写的 PHP 项目。于是我随口编了一个 恐怖 段子。

为什么是 63:

% node
Welcome to Node.js v18.7.0.
Type ".help" for more information.
> Math.floor(Math.random() * 100)
63
> 

by Alynx Zhou (alynx.zhou@gmail.com) at August 10, 2022 05:24 AM

August 08, 2022

百合仙子

Google Chrome 中的字体设置

本文来自依云's Blog,转载请注明。

Google Chrome 是我的备用浏览器,主要用于对比和检查网页的渲染效果,以及某些网页在火狐上可能不太正常,就用 Google Chrome 试试。

但 Google Chrome 有个非常令人恼火的问题:默认字体实在太难受了!

如果没有指明字体,那么 Google Chrome 默认使用 Times New Roman 字体。这是我十年前从 Windows 那边拷过来的字体,看上去细细软软的,很复古,不太适合屏幕显示。屏幕显示一般使用无衬线字体,但 Google Chrome 默认是衬线字体。好吧,那网页要是指明要 sans-serif 字体呢?Google Chrome 这次看上了 Arial,同样是一款古老的、来自于 Windows 的字体。

Google Chrome 默认使用 Windows 字体

行吧……反正我也不是多喜欢这两字体,就全部删掉好了!结果,呃,「自定义」??

Google Chrome 固执己见

实际上它们分别是「Liberation Serif」和「Liberation Sans」字体。这是我的系统上 fc-match「Times New Roman」和「Arial」给出的字体,由 ttf-liberation 包提供,google-chrome 包依赖(看 AUR 上的评论,这是因为 Google Chrome 的 PDF 渲染需要这个字体)。

Google Chrome 就是这么喜欢 Times New Roman 和 Arial,系统上不安装它也要找个替代品来用,就是不听用户通过 fontconfig 设置的默认字体。用户要想 Google Chrome 听点话,需要在「设置」->「外观」->「自定义字体」里像这样设置一下:

Google Chrome 使用 fontconfig 的设置方法

而火狐的话,用户不需要在这种犄角旮旯里设置:

火狐默认使用 fontconfig 的设置

可惜大部分用户都在用 Google Chrome 或其变种,所以制作网页上还是得手动指定一些现代点的字体。

by 依云 at August 08, 2022 04:05 AM

July 21, 2022

Alynx Zhou

从 PulseAudio 到 PipeWire

这篇的操作是在之前 运行在 JACK 上层的 PulseAudio 基础上进行的。

我自己的音频配置比较复杂,虽然 PipeWire 号称能兼容 PulseAudio 和 JACK 的 client 并且在某些发行版成为了默认选项,我还是没很快换掉。因为我想像之前用 PulseAudio 那样把 PipeWire 做成 JACK 的 client,虽然它的文档一直说支持这样,但是看起来两个月前代码才写好。于是我最近尝试了一下。

首先需要安装 pipewirepipewire-alsapipewire-pulse 和一个 PipeWire Media Session Manager,我用的是新的 WirePlumber 但是不要装 pipewire-jack 因为这个是模拟 JACK server 的。然后配置你的 session manager 开启 alsa.jack-device = true,然后理论上就能在设置里看到 JACK Sink/Source 了……但是……

首先装 pipewire-pulse 会替代 pulseaudio,但是按照上篇文章应该是安装了 pulseaudio-jack 这个依赖 pulseaudio 的包,解决方法是先卸载掉 pulseaudio-jack。然后继续安装重新登录应该 PipeWire 已经起来了,按理说这时候启动 JACK 就可以,但是不管我怎么搞都看不到 JACK Sink/Source,所以就备用方案,直接用 PipeWire 替代 JACK 看看,虽然早就可以这么做了,但是之前之前尝试 PipeWire 感觉不是那么稳定,所以就没一直用。

使用 PipeWire 当 JACK 的话要安装 pipewire-jack,和前面差不多的问题是 pipewire-jack 替代 jack2 但是 jack2-dbus 依赖 jack2,那就先删掉 jack2-dbus 再装就好了,然后重新登录,一切正常,Qjackctl 的 Graph 也能正常操作。而且比较有趣的是这样原本使用 PulseAudio 的程序也会在 JACK Graph 里面显示成节点(因为最后都通过 PipeWire),使用 Ardour 录音也没什么问题。其实我的需求还是比较简单的,也不需要什么太低的延迟,只是很多录音的程序都用 JACK 所以才要用。

用了一段时间之后感觉没什么问题,设备之间来回切换也没有卡顿了,驱动我的 2i4 也是完全正常,以后应该就先这样用了。

更新(2022-07-21):我发现在 Ardour 里面录音还是有问题,具体表现是录超过 1 分钟就会报 xrun,怀疑是这个 bug:https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2257。不过其它 DAW 比如 REAPER 或者 Zrythm 都没问题,我倒是挺想换成 Zrythm 的,但是它还在 Beta 阶段。以及前面板插入耳机似乎设置里检测不到,但是开启一下 pauvcontrol 又能检测到了,总之是一些奇怪的小问题,也许我应该换回去。

更新(2022-07-21):我把我的 Sony Playstation Eye 拔掉之后似乎 Ardour 就正常了……我记得这个摄像头的麦克风阵列以前可以用的,不知道为什么现在 PulseAudio 都用不了了,所以看起来不是 PipeWire 的问题。至于为什么只有 Ardour + PipeWire 会出现这个问题,我猜是因为 Ardour 会连接所有可用的设备给自己用,于是就被这个不工作的设备影响出现延迟,而原版的 JACK 只会请求一个设备,Ardour 根本就看不到 PS Eye。也许我还是得买个正经的摄像头……

by Alynx Zhou (alynx.zhou@gmail.com) at July 21, 2022 09:16 AM

July 18, 2022

Alynx Zhou

可能只适合我自己的 RIME 配置 2

上一篇:可能只适合我自己的 RIME 配置

这一篇的原因是我最近在偶然间刷博客刷到一篇 讲 RIME 简体输入方案的文章,里面提到说朙月拼音因为是繁体转简体所以会出现各种错误(其实我个人倒是没怎么遇到过),然后推荐了一个完全针对简体字的输入方案 极光拼音,我自己其实只会输入简体字,不怎么需要输入繁体字的功能,所以打算试试。

已经有人在 AUR 打包了 rime-aurora-pinyin,所以我直接拿来用了,然后类似于我上一篇文章处理朙月拼音的办法,给这个也做了一些自定义设置,主要是添加 emoji,修改默认的全角标点上屏行为,以及加载扩展过的字典,不过遇到了几个问题。

首先是我像上篇文章说的那样直接在 patch 下面添加 __include: emoji_suggestion:/patch 并不能输入 emoji,我研究了很长时间,甚至以为 emoji 功能依赖繁体转简体。结果其实并不是,打开 emoji_suggestion.yaml 可以看到下面几句:

patch:
  switches/@next:
    name: emoji_suggestion
    reset: 1
    states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
  'engine/filters/@before 0':
    simplifier@emoji_suggestion
  emoji_suggestion:
    opencc_config: emoji.json
    option_name: emoji_suggestion
    tips: all

switches 的部分可以先忽略,关键在于 engine,这个 emoji 输入的原理是添加一个 filter,它接收一个输入,然后去附带的 opencc 的词典里查找这个输入得到对应的结果,再把这个输出给下一个 filter,按照词典,输入应该是中文字或者词,并且我看了一下词典,简体和繁体是都有的,所以也不存在简繁转换的问题。其实问题在于这段配置会把它作为第一个 filter 加入列表,而极光拼音的默认 filter 列表是这样的:

  filters:
    - uniquifier
    - charset_filter@gb2312
    - charset_filter@gbk

也就是说如果把 emoji 的 filter 加到第一个,它的输出就要继续经过 uniquifiercharset_filter@gb2312charset_filter@gbk,后两个是极光拼音为了排除掉几乎用不到的生僻字而添加的。而 emoji 显然不属于 gb2312 也不属于 gbk,自然就被过滤掉了。

所以我的解决方案是把 emoji 的 filter 加到列表最后,其实加到哪里无所谓,只要你确定前一个 filter 的输出是中文,能触发 emoji 的 opencc 词典就好了,我单独写了一个 emoji_suggestion.patch.yaml 文件:

switches/@next:
  name: emoji_suggestion
  reset: 1
  states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
engine/filters/@next: simplifier@emoji_suggestion
emoji_suggestion:
  opencc_config: emoji.json
  option_name: emoji_suggestion
  tips: all

导入的时候就写 __include: emoji_suggestion.patch:/。不过虽然我这个不再需要原来的那个 YAML 了,还是需要 rime-emoji 这个项目里其余的文件的。

顺便这也解释了为什么使用朙月拼音时候 emoji 后面的提示框显示的是繁体而非简体,因为朙月拼音从词库直接吐出来的是繁体,然后直接经过第一个 filter 就是 emoji,自然 emoji 查找时候用的就是繁体,然后才会经过简繁转换的 filter,所以如果把 emoji 的 filter 挪到简繁转换的 filter 后面,提示就会变成简体。

解决了 emoji 问题之后还有另一个问题,因为这个 emoji 的 filter 的输入是中文词组,也就意味着必须词库能吐出对应的中文词才能输入 emoji,比如说刚配置出来极光拼音的时候是吐不出来“笑哭”这个词的,所以就不会触发笑哭的 emoji。据说其它平台的输入法也有这个问题。其实没什么太好的解决方案,你可以说自己先手动打几次对应的词然后等 RIME 记住这个输入,不过我觉得也不太好。我想到的办法是既然需要词库里有,不如就让我用 emoji 的 opencc 词典生成一个 RIME 词库,然后扩展词库的时候加进去,这样无论如何都能吐出来了。其实也不是很麻烦,但是需要你把文字转成对应的拼音,那当然不能人工做这个操作了,我利用 Node 的 pinyin 库写了个脚本来做这件事:

#!/usr/bin/env node

const fs = require("fs");
const OpenCC = require("opencc");
const {pinyin} = require("pinyin");

// 我的词库只需要简体中文,如果你需要繁体中文,把 `t2s` 改成 `s2t` 应该就好了。
const converter = new OpenCC("t2s.json");

const outputFileName = "emoji_suggestion.dict.yaml";

const inputFileNames = [];

if (process.argv.length <= 2) {
  console.log(`Usage: ${process.argv[1]} file1 file2 ...`);
  process.exit(0);
}

for (let i = 2; i < process.argv.length; ++i) {
  inputFileNames.push(process.argv[i]);
}

const results = {};

for (const inputFileName of inputFileNames) {
  const words = fs.readFileSync(
    inputFileName, "utf8"
  ).split("\n").filter((line) => {
    return line.length !== 0;
  }).map((line) => {
    return line.split("\t")[0];
  });
  for (const w of words) {
    // rime-emoji 的 opencc 词典同时包含简体中文和繁体中文,但比如极光拼音
    // 这种默认不包含简繁转换的方案多半只想要其中一种,所以使用 opencc 对候选词
    // 进行一次转换。
    const word = converter.convertSync(w);
    if (results[word] != null) {
      continue;
    }
    const py = pinyin(word, {
      "heteronym": true,
      "segment": true,
      "style": "normal"
    }).map((array) => {
      // 有些时候就算利用结巴分词了,这个库仍然会没法判断多音字的读音然后丢出好
      // 几个结果,只取第一个好了。
      return array[0];
    }).join(" ");
    // 遇到处理不了的生僻字这个库会直接丢出原本的字……什么奇怪逻辑,只能判断是不
    // 是字母或空格了。
    if (/^[a-z ]*$/.test(py)) {
      results[word] = py;
    }
  }
}

const outputLines = [
  "# Rime dictionary for emoji",
  "# encoding: utf-8",
  "# Generated by `gen-emoji-dict.js` written by Alynx Zhou",
  "",
  "---",
  "name: emoji_suggestion",
  "version: \"0.1\"",
  "sort: by_weight",
  "...",
  ""
];
for (const k in results) {
  outputLines.push(`${k}\t${results[k]}`);
}
// console.log(outputLines.join("\n"));
fs.writeFileSync(outputFileName, outputLines.join("\n"), "utf8");

当然这个脚本不是很完美,比如 pinyin 识别不了的生僻字直接忽略了,不过我觉得它都识别不了,我多半也不会打出来的。然后虽然可以利用 jieba 分词提高多音字的准确性,还是有些不正确的,这些遇到了再手动纠错吧。

最后把这个词库添加进扩充词库:

# 原来要结合默认词库和第三方词库,
# 需要自己编写一个词库让它 fallback 到极光拼音和第三方词库。
# 我说佛老师对不起对不起,我不懂规矩。
---
name: aurora_pinyin.extended
version: "0.1"
# `by_weight`(按词频高低排序)或 `original`(保持原码表中的顺序)。
sort: by_weight
# 听说默认简化字八股文效果不好,还是算了。
# https://blog.coelacanthus.moe/posts/tech/a-new-rime-simp-pinyin-schema/
# 因为导入的朙月拼音词库是繁转简,所以这里不能导入简化字八股文。
# 导入简化字八股文。
# vocabulary: essay-zh-hans
# 选择是否导入预设词汇表【八股文】。
# use_preset_vocabulary: true

import_tables:
  # 主要是为了肥猫 wiki 词库。极光拼音好像是内置常用简化字表的。
  - zhwiki
  - aurora_pinyin
  - emoji_suggestion

顺便说一下我其实也不太了解这个扩展词库的顺序怎么设置比较好,不过我尝试的结果是像这样把 emoji 放在最后面,就不会每次输入在前面提示很多并不常用的 emoji 词组的问题。

我这个脚本生成的词库只有简体,不过我发现朙月拼音的简繁转换还是可以正常处理简体词库的,也就是说会变成 词库出简体 -> 简繁转换 -> 繁体变 emoji,所以直接加给朙月拼音也没问题,如果我需要用繁体中文,可以直接切换方案到朙月拼音(虽然实际上我的配置是简化字版,不过看起来主要区别只是默认是否开启繁体转简体)。平时输入简体则直接用极光拼音。

完整配置在 GitHub Repo 更新。

by Alynx Zhou (alynx.zhou@gmail.com) at July 18, 2022 10:50 AM

July 15, 2022

中文社区新闻

wxWidgets 3.2 更新可能需要手动干预

wxWidgets 3.2 在 GTK3 前端之外也提供了 Qt 前端,所以相关包的名称已从 wxgtk- 改名到 wxwidgets- 。不再提供 GTK2 前端。如果你安装过 wxgtk2 升级过程中可能遇到下述错误:

error: failed to prepare transaction (could not satisfy dependencies) :: removing wxgtk-common breaks dependency 'wxgtk-common' required by wxgtk2

这种情况下请删除 wxgtk2 并继续升级。

by farseerfc at July 15, 2022 10:32 PM

June 30, 2022

中文社区新闻

[archlinuxcn] 社区仓库现提供 debuginfod 服务

[archlinuxcn] 仓库现提供 debuginfod 服务(支持部分有调试符号的包),设置以下环境变量即可:

DEBUGINFOD_URLS="https://debuginfod.archlinux.org https://repo.archlinuxcn.org"

by lilydjwg at June 30, 2022 03:53 AM