4
1
2022
9

从 getmail6 到 offlineimap

起因

上个月收到这样一封邮件:

自 5 月 30 日起,您可能会无法再访问那些采用安全性较低的登录技术的应用

意思就是说,Google 觉得把密码直接交给邮件客户端,权限太大,不够安全。所以要用户改用基于 OAuth2 的认证方式,只给程序邮件相关的权限。哦,你说应用专属密码?要用那个必须得启用两步验证——也就是意味着遇到灾难的话,我无法从一无所有的状态开始恢复。

从 POP 到 IMAP

getmail6 只支持使用 XOAUTH2 认证的 IMAP 协议,并不在 POP 协议上支持这个(不知道是否有可能)。所以我得换 IMAP 协议了。

具体操作步骤在 getmail6 的示例配置中有写。简单来说就是自己去申请个桌面软件的 app 信息,然后给自己的用户添加试用权限,再通过 OAuth2 获取 refresh token 和 access token,就能登录了。getmail6 自带了个 getmail-gmail-xoauth-tokens 程序用来走 OAuth2 流程,不需要另外安装程序来处理的同时也可以给其它程序使用。

所以我的 msmtp 配置就不用麻烦了,改两行配置就好:

auth oauthbearer
passwordeval getmail-gmail-xoauth-tokens ~/.getmail/gmail/lilydjwg@gmail.com.json

但是呢,虽然邮件是收回来了,IMAP 和 POP 还是挺不一样的。POP 没有「文件夹」的概念,所有收到的邮件,不管我有没有在 Gmail 网页或者客户端上阅读、归档,不管它进了哪个标签(文件夹)(「垃圾邮件」除外),我都会收到,并且把收过的邮件标记为已读。

而通过 getmail6 使用 IMAP 收取,我能做的选择就是,要不要把收过的邮件标记为已读或者删掉(可在 Gmail 中设置为归档)。不管如何,getmail6 只会收到它运行时位于收件箱中的邮件。如果我选择标记为已读的话,那么已读邮件也不会被 getmail6 收到。所以标已读的话,我在别的地方看过的邮件不会被收到。删除的话会好点,收过的邮件归档了,还省得我手动去归档,但是在别的地方,收过的邮件和已处理的邮件没了区分。

所以不如上 offlineimap,完全同步好了。

从 getmail6 到 offlineimap

offlineimap 的配置就比较复杂了,一是要对文件夹名进行转码,二是我要设定只同步指定的文件夹:收件箱、Maillist 和垃圾邮件。要同步垃圾邮件的原因是,Gmail 经常把有用的邮件往里边扔。

[general]
accounts = gmail
maxsyncaccounts = 10
socktimeout = 60
pythonfile = ~/.offlineimap/offlineimap.py

[Account gmail]
localrepository = gmail-local
remoterepository = gmail-remote

[Repository gmail-local]
type = GmailMaildir
localfolders = ~/.Maildir
filename_use_mail_timestamp = no
nametrans = gmail_nametrans_local

[Repository gmail-remote]
type = Gmail
remoteuser = lilydjwg@gmail.com

sslcacertfile = /etc/ssl/cert.pem
ssl = yes
starttls = no

oauth2_client_id_eval = get_client_id("lilydjwg@gmail.com")
oauth2_client_secret_eval = get_client_secret("lilydjwg@gmail.com")
oauth2_access_token_eval = get_access_token("lilydjwg@gmail.com")

nametrans = gmail_nametrans_remote
folderfilter = gmail_folderfilter
import os
import json
import subprocess

_LOADED_DATA = {}

def _load_data(account):
  with open(os.path.expanduser(f'~/.getmail/gmail/{account}.json')) as f:
    _LOADED_DATA[account] = json.load(f)

def get_client_id(account):
  if account not in _LOADED_DATA:
    _load_data(account)
  return _LOADED_DATA[account]['client_id']

def get_client_secret(account):
  if account not in _LOADED_DATA:
    _load_data(account)
  return _LOADED_DATA[account]['client_secret']

def get_access_token(account):
  cmd = [
    'getmail-gmail-xoauth-tokens',
    os.path.expanduser(f'~/.getmail/gmail/{account}.json'),
  ]
  out = subprocess.check_output(cmd, text=True)
  return out

def gmail_nametrans_remote(foldername):
  foldername = foldername.removeprefix('[Gmail]/').encode('ascii').decode('imap4-utf-7')
  if foldername == '垃圾邮件':
    foldername = 'Spam'
  elif foldername == '草稿':
    foldername = 'Drafts'
  return foldername

def gmail_nametrans_local(foldername):
  if foldername == 'Spam':
    foldername = '[Gmail]/垃圾邮件'
  elif foldername == 'Drafts':
    foldername = '[Gmail]/草稿'
  return foldername.encode('imap4-utf-7').decode('ascii')

def gmail_folderfilter(foldername):
  foldername = foldername.encode('ascii').decode('imap4-utf-7')
  return foldername in [
    'INBOX', '[Gmail]/垃圾邮件', '[Gmail]/草稿',
    'Maillist',
  ]

然后在 Gmail 那边创建个过滤器,把来自邮件列表的邮件扔到「Maillist」文件夹里去。搜索「 (to:@googlegroups.com OR from:vim-dev-github@256bit.org OR to:@zsh.org)」并创建过滤器,选择操作「跳过收件箱、 应用标签“Maillist”」即可。注意以后在修改的时候直接修改「包含字词」字段即可,并且记得「OR」「AND」「NOT」之类的操作符需要改回大写。

这样做完之后还有个问题:一封邮件同步到 offlineimap 后,我在 mutt 里阅读并删掉了它。offlineimap 一看,哟,邮件没了,得在服务器上删掉。Gmail 根据我的设置,把从 IMAP 删除的邮件归档,但是它并没有选项来标记为已读。所以这封邮件最终会以未读的状态躺在「所有邮件」里。

于是我去 App Script 里写了个脚本,把这些邮件标记为已读:

function mark_as_read() {
  const threads = GmailApp.search('is:unread AND NOT (label:Maillist OR in:inbox)', 0, 30)
  for(const thread of threads) {
    Logger.log('Marking as read: %s', thread.getFirstMessageSubject())
    thread.markRead()
  }
}

手动运行一遍之后,就可以在左侧栏里给它设置个触发器定时跑啦。

新邮件提示

使用 offlineimap 之后,最大的问题变成了邮件散落在不同的账号下的不同文件夹,一个个过去翻看太低效了。所以我就给 zsh 设置了提醒:

mailpath=(
  ~/.Maildir/INBOX/new'?GMail has a new message.'
  ~/.Maildir/Spam/new'?GMail has a new spam.'
  ~/.Mail/inbox'?New local mails.'
)

问号前边是邮箱的路径,后边是提示信息。之前那个 mbox 格式的邮箱我还留着,用来收取来自本地 cron 的邮件。

一个小问题是,procmail 用不成了。不过现在各种无用的网站消息也少了,所以不需要通过 procmail 处理垃圾邮件了(新浪微博我没有使用邮件注册、LinkedIn 和 Twitter 消停了、网易和QQ邮箱不用了)。现在中文邮件列表也几乎没人用了,我也不用让程序去重写「回复:RE:回复:」这类糟糕的邮件标题和过滤掉自动回复了。

Category: Linux | Tags: linux 电子邮件 IMAP
3
8
2022
8

Qt 的字体渲染问题

GUI 程序我现在依然倾向于 GTK,因为虽然 Qt 拥有良好的跨平台性,但可能是太注重跨平台性了,在 Linux 平台上反而有一些水土不服的问题。

字体太多,支持太少

你可能觉得,系统上字体太少,所以经常会遇到不常见的字符无法显示的情况。然而对于 Qt 来说,字体越多,反而越容易遇到个别字符不能显示的情况。

这是我的 /etc/fonts/conf.d/66-qt.conf 中的一段。因为顺序的原因,我只能放到 /etc 下。除了针对 sans-serif 配置外,我也有同样的配置应用于 serif 和 monospace。

<fontconfig>
  <!-- Adjust font order for Qt applications -->
  <alias>
    <family>sans-serif</family>
    <prefer>
      <!-- 格拉哥里字母:Ⰽⱁⱀⱄⱅⰰⱀⱅⰹⱀ Ⰹⱍⰹⰳⱁⰲ -->
      <family>Noto Sans Glagolitic</family>
      <!-- 爪哇文:꧁   ꧂ -->
      <family>Noto Sans Javanese</family>
      <!-- 西夏文:𗷲𗒅 -->
      <family>Noto Serif Tangut</family>
      <!-- 埃及象形文字:𓁹 -->
      <family>Noto Sans Egyptian Hieroglyphs</family>
      <!-- 苏美尔楔形文字:𒆠𒂗𒂠 -->
      <family>Noto Sans Cuneiform</family>
      <!-- 中日韩统一表意文字扩展 C:𫚥 -->
      <family>HanaMinB</family>
      <!-- 拉让文:ꥃ -->
      <family>Noto Sans Rejang</family>
      <!-- 越南傣文:ꪀꪑ -->
      <family>Noto Sans Tai Viet</family>
      <!-- 切罗基文:ꮳꮧꮢ ᨣ -->
      <family>Noto Sans Cherokee</family>
      <!-- 老傣仂文:ᨣ -->
      <family>Noto Sans Tai Tham</family>
      <!-- 安纳托利亚象形文字:𔘓 -->
      <family>Noto Sans Anatolian Hieroglyphs</family>
      <!-- 马姆穆文补充:𖤍  -->
      <family>Noto Sans Bamum</family>
      <!-- 图标字体(PUA): -->
      <family>OperatorMonoSSmLig Nerd Font</family>
      <!-- 巴塔克文:ᯤ -->
      <family>Noto Sans Batak</family>
      <!-- 古北欧文:ᛋᛖᚱᚣᚨᛚᚳᚨᚾᛞᛚᛖ -->
      <family>Noto Sans Runic</family>
    </prefer>
  </alias>
</fontconfig>

这个配置的意思是,把这些字体的优先级提高一些。当使用 fontconfig 的程序要显示字符的时候,它会指定一个模式,匹配到一个字体列表。渲染文字的时候,就可以遍历这个列表,直到找到可以显示这个字符的字体,所以一般来说,只要系统上装了对应字符的字体,它就能显示出来。

但是 Qt 额外地需要这个配置,因为 Qt 只会检查列表中的前255项。而世界上的不同文字那么多,所以想要能够显示它们,就得有一堆字体。比如 noto-fonts 这个包里就有614个字体文件,远超 Qt 支持的数量。总有些奇奇怪怪的文字被网友用来当颜文字,或者挂在名字上彰显个性。不这么调整一下,Qt 遇到了就只能「吃豆腐」了。

空心豆腐

当一个字符显示不出来的时候,那么怎么显示好呢?一般会显示成某种方框。Pango火狐会将该字符的 Unicode 码点以十六进制的形式显示在方框里边,这样虽然不知道这个字符长什么样子,但至少知道它是哪个字符,也知道多块豆腐是不是同一字符,在不能复制字符本身的时候很有用。比如当它出现在求助者的截图里的时候,比如当它出现在不能复制的地方的时候。

然而 Qt 不这样做。管你什么字符,Qt 统一显示为空心方框。从视觉上完全无法知晓它到底是什么字符,要是复制不到的话,就别想弄明白你缺什么字体了。

PS: Matrix 客户端 fluffychat 的 Web 版,使用的是 Fluffy 图形界面库,即使在 Web 版,文字渲染依然完全是自己做的。不管浏览器的设置不管系统的设置,豆腐块是带叉号的方框,还不能选中,十分讨厌。

非 BMP 字符

所有使用 UTF-16 的平台(Java、JavaScript、Windows、Qt),外加 MySQL 容易遇到的一个问题:非 BMP 字符(也就是那些 U+FFFF 之后的字符)会被当作是两个字符处理。随着 emoji 的流行,大家应该都修了不少。然而,Qt 在展示非 BMP 字符的时候,你可以选中半个字符。如果不小心漏掉半个的话,复制出来的半个字符就会变成问号(还好不是 GBK 时代那样弄乱后续所有字符)。

font features

一些字体可以通过 fontconfig 设置 fontfeatures 属性来启用(或者禁用)一些特性,比如连字,带斜杠的 0,小型大写字母,居中的中文标点,等等。Pango 很早就支持了,火狐最近也支持了,但 Qt 那边依旧没啥动静。(感谢 Coelacanthus 的评论。)

Category: Linux | Tags: linux 字体 Qt
2
2
2022
19

Wayfire 迁移进展(四):不那么 high 的 DPI

使用24寸4k屏幕作为主屏的时候很简单,设置 scale 为 2 就好了。但是,当 2 嫌太大、1 嫌太小的时候,问题就来了。比如我希望使用 120dpi,把 scale 设置为 1.25 可好?

scale=1.25 text

而这才是理想的效果:

120dpi text

看不出来差别?放大八倍,你看差别多明显:

8x compare

正常 120dpi 渲染出来的文字边缘清晰犀利,次像素平滑左红右蓝。再看看 scale=1.25 的文字,线条经常糊掉,次像素平滑效果几乎完全被抹掉。实际看上去的效果就是跟透明麿沙玻璃看屏幕似的,线条边缘总是有点糊糊的感觉,1080p 的屏幕被降级成了 720p 似的。

之所以出现这样的情况,是因为 Wayland 只支持整数倍缩放。因为,Wayland 混成器不能告诉客户端你得把窗口给画成 1.25 倍的,而客户端也无法告诉混成器我这个图像画的是 1.25 倍。所以,混成器只好告诉客户端你给我画个 2 倍的图像吧。混成器拿到图像之后再缩小 0.625 倍,自然有些逻辑像素就不能对应到单个的物理像素上去了。

所以,我还是设置 scale=1,不要混成器帮我去缩放。我自己通过另外的办法告诉客户端把字写大点儿。图标之类的就顾不上啦,反而大点小点都还能看。比如我要 1.25 倍大小的文字,就这样做:

  • GTK 3:在 dconf 里设置org.gnome.desktop.interface.text-scaling-factor=1.25就好了。最开始的截图就是 dconf-editor 里这一项配置。
  • Qt:设置环境变量 QT_WAYLAND_FORCE_DPI=120
  • Telegram:除了上边这个环境变量外,额外地在它自己的设置里设置 150% 的缩放(Telegram 的字偏小所以要设置得大一些)。设置环境变量是为了 fcitx5。
  • waybar:config 文件中设置 heightstyle.css 中设置 font-size
  • Xwayland:和 X11 下的 HiDPI 设置差不多的。比如 GTK 2 设置 Xresources Xft.dpi: 120 就好了。

我遇到的差不多就这些了。没办法,Linux 就是这么乱 QAQ。不过虽然 Wayland 协议不支持,好歹还有绕过的办法。

Category: Linux | Tags: Wayland screen 显示器 linux
8
26
2021
5

倾听蓝牙耳机的按键事件

缘起

我的蓝牙耳机有简单的多媒体按键:上一曲、下一曲、播放、暂停。这几个按键在 Android 手机上是开箱即用的,然而在 Arch Linux 上,尤其是我的 Awesome 桌面环境上,并不那么自动。

其实按键事件都能收到的啦。可以收到 XF86AudioPrev, XF86AudioNext, XF86AudioPlay, XF86AudioPause 这么几个按键。给它们绑定热键,去调用 MPRIS 就好了。我使用的是 playerctl 工具。mpv 的 MPRIS 支持用的是 mpv-mpris。火狐自动就支持了,不用做什么。

看起来这样就好了?我也以为如此,直到我离电脑远了一些……

问题

躺在床上玩手机时也可以用电脑听歌啦~你问我为什么不用手机听歌?因为我的电脑没有 NFC 功能,耳机切到手机碰一下就可以了,可是切到电脑上是要打命令抢连接的!所以就不切来切去啦,反正手机上的曲库和电脑上是同步的。

可是!耳机多媒体按键怎么不管用了呢?我瞟了一眼电脑,哦,它怎么屏幕还亮起来了?反复几次之后,我终于搞明白了——锁屏的时候按键事件全被锁屏软件给挡下来啦……

那怎么办呢?

我有看到 acpid 那边也收到了些事件,比如「cd/prev / CDPREV」和「cd/next / CDNEXT」。但是不是很稳定,时有时无,也没看到播放和暂停。再加上从作为系统服务的 acpid 将指令传到用户会话比较麻烦,就放弃了。

后来想到,既然能收到按键事件,那么应当有个输入设备在。xinput 看了一下,果然有个「WH-1000XM2 (AVRCP)」,用 evtest 在 /dev/input/ 也能找到对应的设备文件。那直接读这个设备文件不就好了?

解决

好是好,但是没权限啊。不过像/dev/video*之类的文件就有权限,是 systemd 拿 udev 规则给加上的。我之前也给 i2c 设备加过权限,只是那次是直接 chmod 了,这次想试试更优雅的方案——uaccess tag。

这个 uaccess tag 是 systemd 用来给当前会话的用户设备权限的,切换用户会话的时候权限会自动变化。不过没有文档 QAQ,所以只好自己研究了。最终的结果是这样:

SUBSYSTEM=="input", ATTRS{name}=="WH-1000XM2 (AVRCP)", TAG+="uaccess"

这个规则的序号需要小于70,不然赶不上处理 uaccess 的逻辑。sudo udevadm control --reload-rules 然后再 sudo udevadm trigger,就可以看到对应的 /dev/input/event* 文件上已经有 ACL 给我的用户权限了。不过多了写权限,问题不大。

然后就可以开始愉快地找设备文件、读取事件啦。我用 Rust 写的,日常练习嘛,顺便用用前不久看到的 eyre 和 tracing。有个 input-linux 库,不用自己拿 libc 调用 ioctl、定义 C 结构体了。不过这个库不支持从按键名到按键枚举值的转换,所以我 fork 了一下。蓝牙耳机说来来、说走走,所以 inotify 也是少不了的啦。然后还用 toml 整了个配置文件,好放出来给有需要的人用~

啊对了,程序里一上来就把对应的输入设备用 xinput 给禁用了。这样桌面环境就不会收到事件,不会唤醒屏幕,也不会有重复操作了。(不过它退出的时候并没有把设备重新启用,懒~)

代码

Category: Linux | Tags: 硬件 linux Rust
8
12
2021
10

使用 bwrap 沙盒

bwrap 是命令的名字。这个项目的名字叫 bubblewrap。它是一个使用 Linux 命名空间的非特权沙盒(有用户命名空间支持的话)。

我之前使用过 Gentoo 的 sandbox 工具。它是 Gentoo 用于打包的工具,使用的是 LD_PRELOAD 机制,所以并不可靠。主要用途也就是避免打包软件的时候不小心污染到用户家目录。

使用 bwrap 的话,限制是强制的,没那么容易绕过(至于像 Go 这种因为不使用 libc 而意外绕过就更难得了)。不过 bwrap 不会在触发限制的时候报错。

bwrap 的原理是,把 / 放到一个 tmpfs 上,然后需要允许访问的目录通过 bind mount 弄进来。所以没弄进来的部分就是不存在,写数据的话就存在内存里,用完就扔掉了。这一点和 systemd 也不一样——systemd 会把不允许的地方挂载一个没权限访问的目录过去。

bwrap 的挂载分为只读和可写挂载。默认是 nodev 的,所以在里边是不能挂载硬盘设备啥的。它也提供最简 /proc 和 /dev,需要手动指定。整个 / 都是通过命令行来一点点填充内容的,所以很容易漏掉部分内容(比如需要联网的时候忘记挂载 resolv.conf 或者 TLS 证书),而不会不小心允许不应当允许访问的地方(当然前提是不偷懒直接把外面的 / 挂载过去啦)。

至于别的命名空间,有 --unshare-all 选项,不用写一堆了。如果需要网络,就加个 --share-net(这个选项文档里没写)。没有别的网络方案,因为没特权,不能对网络接口进行各种操作。--die-with-parent 可以保证不会有残留进程一直跑着。

我目前的打包命令长这样:

alias makepkg='bwrap --unshare-all --share-net --die-with-parent \
  --ro-bind /usr /usr --ro-bind /etc /etc --proc /proc --dev /dev \
  --symlink usr/bin /bin --symlink usr/bin /sbin --symlink usr/lib /lib --symlink usr/lib /lib64 \
  --bind $PWD /build/${PWD:t} --ro-bind /var/lib/pacman /var/lib/pacman --ro-bind ~/.ccache ~/.ccache \
  --bind ~/.cache/ccache ~/.cache/ccache --chdir /build/${PWD:t} /usr/bin/makepkg'

以后应该随着问题的出现还会修改的。

其实我学 bwrap 主要不是自己打包啦(毕竟基本上都交给 lilac 了),而是给 lilac 加固。Arch 的打包脚本是 shell 脚本,所以很多时候不执行脚本就没办法获取一些信息、进行某些操作。唉,这些发行版都喜欢糙快猛的风格,然后在上边打各种补丁。deb 和 rpm 的打包也都是基于 shell 脚本的。而 lilac 经常通过脚本编辑打包脚本,或者从 AUR 取,万一出点事情,把不该删的东西给删掉了,或者把私钥给上传了,就不好了。所以前些天我给 lilac 执行 PKGBUILD 的地方全部加上了 bwrap。期间还发现 makepkg --printsrcinfo 不就是读取 PKGBUILD 然后打印点信息嘛,竟然不断要求读取 install 脚本,还要对打包目录可写……

另一个用法是,跑不那么干净的软件。有些软件不得不用,又害怕它在自己家里拉屎,就可以让它在沙盒里放肆了。比如使用反斜杠作为文件路径分隔符写一堆奇怪文件名的 WPS Office。再比如不确定软件会不会到处拉屎,所以事先确认一下。我以前使用的是基于 systemd-nspawn 和 overlayfs 的方案(改进自基于 aufs 和 lxc 的方案所以名字没改),不过显然 bwrap 更轻量一些。跑 GUI 的话,我用的命令长这样:

bwrap --unshare-all --die-with-parent --ro-bind / / \
  --tmpfs /sys --tmpfs /home --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  --ro-bind ~/.fonts ~/.fonts --ro-bind ~/.config/fontconfig ~/.config/fontconfig \
  --bind ~/.cache/fontconfig ~/.cache/fontconfig --ro-bind ~/.Xauthority ~/.Xauthority \
  --ro-bind /tmp/.X11-unix /tmp/.X11-unix --ro-bind /run/user/$UID/bus /run/user/$UID/bus \
  --chdir ~ /bin/bash

其实还可以用来给别的发行版编译东西,取代我之前使用 systemd-nspawn 的方案。bwrap 在命令行上指定如何挂载,倒是十分方便灵活,很适合这种需要共享工作目录的情况呢。以后有需要的时候我再试试看。(好像一般人都是使用 docker / podman 的,但是我喜欢使用自己建立和维护的 rootfs,便于开发和调试,也更安全。)

和 bwrap 类似的工具还有 SELinux 和 AppArmor。它们是作用于整个系统的,Arch Linux 安装会很麻烦,对于我的需求也过于复杂。Firejail 是面向应用程序的,但是配置起来也挺不容易。bwrap 更偏重于提供底层功能而不是完整的解决方案,具体用法可以让用户自由发挥。

Category: Linux | Tags: Arch Linux linux 安全
11
21
2020
11

HiDPI 配置记录

首先,我是用 X11 窗口系统的,不同屏幕分别设置肯定没戏。所以只好让笔记本电脑的屏幕迁就一下我的4K主屏啦,把笔记本屏幕缩放一下。算一下 scale 值:192 / 120 = 1.6。不是整数,会糊,可总比放大两倍的巨大界面要好。

xrandr --output eDP-1 --scale 1.6 --auto --output DP-2 --auto --pos 3072x0 --primary --fb 6912x2160

这里要注意的是,要指定--pos(或者--panning),不然会重叠;要指定--fb,不然鼠标可能会有部分区域去不了。

然后开始设置。本来我是尝试了一下 KDE 的,但因为我将在下一篇文章中写的原因而放弃,回到了 Awesome。不过也不是全无收获。我把 KDE 的配置方案拿过来用了。你想问怎么拿的?我 btrfs 的文件系统,做好快照再 rsync -n 对比一下它动了哪些文件就有了。

首先是 X11 的资源。在~/.Xresources里写上Xft.dpi: 192,然后xrdb -merge ~/.Xresources一下就好了。顺便再xrandr --dpi 192一下,听说有些程序会读这个。

然后是 GTK。GTK 2 就放弃吧,没办法。文字会按设置的 Xft.dpi 放大,图标啥的不会。GTk 3,要设置两个环境变量:

export GDK_SCALE=2 GDK_DPI_SCALE=0.5

前一个是把界面放大,后一个是把文字缩回去,因为文字已经按 Xft.dpi 放大过,不能再放大一次了。

再然后是 Qt。Qt4 早卸载干净了不用管。Qt5 嘛,也不用管。它自己会处理好。有个按不同屏幕缩放的环境变量QT_SCREEN_SCALE_FACTORS,效果跟 Windows 10 差不多的。但是我为了照顾其它程序已经把屏幕给 scale 过了,就不需要设置这个了。你要设置个QT_AUTO_SCREEN_SCALE_FACTOR=0也行,但这个是默认行为。

最后是个别的程序。

Telegram 直接在设置里关掉「默认界面缩放比例」并且设置缩放比例为 300% 就好了。我也不知道为什么,Telegram 默认的字总是很小。之前 120dpi 的时候我要 200% 缩放,现在 192dpi 需要 300% 缩放了。

YouTube,就是那个网站啦。它其实没什么显示上的问题,只是死活不会给我自动选择 1080p 以上的分辨率。经过仔细二分测试之后发现,把火狐的配置文件夹下的storage/default/https+++www.youtube.com目录删掉之后就好了。没发现删掉这个会有其它影响。

mpv 要修改配置文件,加上no-hidpi-window-scale参数,不然会把视频自动放大,4K视频一打开会只能看到四分之一的画面。加上这个参数,默认窗口大小时,一个视频里的像素会对应一个显示器上的像素,不大不小刚刚好。mpv 文档上说这是 OS X 系统上的默认行为,可我这是 Linux 桌面啊,你把别的平台上的习惯搬过来是几个意思?另外我加了个demuxer-readahead-secs = 20选项。我的大文件都在机械硬盘上,4K 码率又比较高,不多预读一点容易卡。

我的 qemu 之前使用的是-display gtk,也坏掉了。窗口那么大,虚拟机只用左下角那里四分之一的空间。spicy 也有问题,会告诉虚拟机只有 1080p。解决方法是 unset GDK_SCALE GDK_DPI_SCALE。它们在放大了自己的界面的同时,把显示的虚拟机的内容也给放大了,所以干脆叫它们别动。也没什么别的影响。

哦还有 Zoom。设置个QT_AUTO_SCREEN_SCALE_FACTOR=1似乎就好了?我试了一下QT_SCREEN_SCALE_FACTORS,会导致很怪异的行为。

以上解决了显示大小的问题,但我发现还有个问题:我的鼠标光标时大时小的……从 KDE 那边弄来几个设置之后就好了,而且主题也更加一致了呢。

首先是设置 xcursor 环境变量:

export XCURSOR_THEME=Vanilla-DMZ XCURSOR_SIZE=36

听说对应的 X 资源大家都不理睬,那我也就不设好了。

然后是 GTK 2 的~/.gtkrc-2.0文件里写上:

gtk-cursor-theme-name = "Vanilla-DMZ"
gtk-cursor-theme-size = 36

再接下来是 GTK 3 的~/.config/gtk-3.0/settings.ini

[Settings]
gtk-cursor-theme-name = Vanilla-DMZ
gtk-cursor-theme-size = 36

然后又没了。天知道为什么 Qt 那边啥都不干就好好的,GTK 却这么麻烦。

啊,你问这些环境变量在哪里设?我给写~/.xprofile里了。不过这还不够。有些 GUI 程序会由用户的 systemd启动(比如我的 Telegram 是由 systemd 启动的,为了在内存用得太多的时候自动重启),有些 GUI 程序会由 D-Bus 激活(比如 gnome-terminal)。这些是和登录会话分开的,所以要手动导入一下。以下是我的 .xprofile 中导入图形界面相关环境变量的部分:

_envs=(
  GDK_SCALE GDK_DPI_SCALE
  XCURSOR_THEME XCURSOR_SIZE
  XMODIFIERS QT_IM_MODULE GTK_IM_MODULE
  LIBVA_DRIVER_NAME GST_VAAPI_ALL_DRIVERS
)
dbus-update-activation-environment "${_envs[@]}"
systemctl --user import-environment "${_envs[@]}"

至于登录界面怎么办,我是在 lightdm 的 display-setup-script 里,跑了跑 xrandr,设置了一下 Xft.dpi 资源。环境变量啥的没动,反正用不上。当然你也可以去改~lightdm/.pam_environment来设环境变量,反正现在 Arch Linux 还是读它的。别的 dm 同理。

Category: Linux | Tags: screen linux hidpi 显示器
11
20
2020
20

让 QEMU 使用 SPICE 协议

缘起

我就是买了个4K显示器,咋这么多事呢……(有两篇文章还在路上)

第一个问题是,我的显卡 vGPU 最高只支持 1920x1200 的分辨率。行吧,我缩放成了吧?嘿嘿,-display gtk 的缩放不会保持比例,我只好算了算最大保持比例的大小,然后窗口切成浮动,再调用命令调整到指定的大小:

sleep 1 && xdotool getactivewindow windowsize 3280 2122

然后还要居中放置一下。多麻烦!

第二个问题是,我想在虚拟机里试试 i3,但是我的按键总是会被外边捕获。Super 键基本上是 Awesome 在用,而 Alt 键会撞上这个 GTK 窗口菜单栏的快捷键。

配置

经过多番尝试和摸索之后,确定了如下的参数:

  -display egl-headless,gl=on,rendernode=/dev/dri/renderD128
  -spice unix,addr=/run/user/1000/qemu/ArchKDE/spice.sock,disable-ticketing
  -device virtio-serial-pci
  -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0
  -chardev spicevmc,id=spicechannel0,name=vdagent

当然要gl=on啦,不然我怎么玩特效,我还不如回到 vbox 去呢(啊不,我的 vbox 不喜欢 btrfs,经常出错然后只读挂载,所以已经被删掉了)。要指定rendernode,不然它会 fallback 到 llvmpipe,然后还段错误崩掉……这也是我不-display spice-app的原因。

后边三行是从这个 QEMU + Spice with Copy & Paste 抄来的。搜索问题时不小心遇见,然后解决了这个我一直没有处理的问题。SPICE 不但能共享剪贴板,而且还支持 PRIMARY 选择区呢~虚拟机里要安装 spice-vdagent 并启动相应的服务。

然后是客户端的选择。一个很神奇的地方是,virt-viewer 看上去轻量,实际上只是选项少而已。它不光拖进来个 gtk-vnc 依赖,还把 libvirt 都给我带上了……然后 GNOME 的 vinagre,我不知道为啥,它就是连不上我的 spice+unix 地址。哦对了,virt-viewer 直接敲命令调用也是连不上,只能用 xdg-open 才能正常打开。

virt-viewer 有个依赖叫 spice-gtk。我试着 pacman -Ql 了一下,还真找到个 spicy 工具。比 virt-viewer 轻量多了,选项也更为丰富,比如可以选择不 grab 键盘。

一点额外的东西

在群里听说了 virtio-fs 共享方案,听说比 virtio + 9p 更高效。然后我用它成功取代了之前用的 NFS 方案(反正我的 vbox 虚拟机已经被删掉啦)。NFS 的服务也可以卸载啦(开放一堆端口到公网,看着有点怕怕的,虽然是 IPv6 地址不太会被扫到,但知道我的地址的咋办呢)。

virtio-fs 相比 virtio + 9p 的另一个优点是,和 NFS 一样,virtiofsd 是以 root 权限运行的,所以可以写入我的 pacman 缓存。qemu 那个 9p 似乎没有办法。至于启动嘛,用 systemd abstract socket 触发一下就好了。

另外,我使用 GVT-g 和 virtio 输出视频信号时,均遇到了声音在视频画面变化时声音卡顿的情况。一个绕过的办法是,通过设置 PULSE_SERVER 环境变量以及加载 module-native-protocol-tcp 模块,将音频信号直接通过网络发送到宿主机上,一点也不卡!

Category: Linux | Tags: spice qemu kvm 虚拟机 linux
10
29
2020
10

让 Arch Linux 系统和最新的镜像同步,从最快的镜像下载

2024年03月20日更新:pacman 6.1.0 增加了 CacheServer 的支持,已经不需要使用本文这种办法啦~


Arch Linux 就是要追新!要追新自然要选择一个更新及时的软件仓库镜像啦,比如国内的 TUNA、USTC 同步都很及时。但是呢,这俩难兄难弟最近一段时间有些吃不消了,导致下载包的时候很慢,甚至超时失败,使用体验真糟糕。如果直接用上游镜像,比如 pkgbuild.com,漂洋过海的,也挺慢的。

而国内另一些镜像,比如网易腾讯云阿里云华为云,他们要么有 CDN,要么线路很好,下载速度飞快。但是呢,他们基本上每天才同步一次,阿里云还时不时连续数天都没能同步成功,这让喜欢追新的 Arch Linux 用户多不舒服呀。当群里的小伙伴们都用上了最新版本的软件,体会到了让人心痒痒的新特性和 bug 时,你 -Syu 却是「今日无事可做」,真是扫兴呢。

和最新的镜像同步,从最快的镜像下载,真的不可兼得吗?

非也。只需要稍微配置一下,用上我的 pacsync 脚本,就可以啦~

配置方式是,为 /etc/pacman.d 下的镜像列表文件创建一个.sync后缀的同名文件,里边指定用于同步的镜像,而不带.sync后缀的文件里按优先级列出多个镜像。pacman 在下载文件时,会按顺序依次尝试列出的镜像,如果遇到更新不及时 404 的时候,就会尝试另一个。这样,可以仅在下载快的镜像里还没有需要的包文件时,才转而从比较慢的镜像下载。

而需要同步 pacman 数据库的时候,使用pacsync脚本取代pacman -Sy。脚本会使用 bind mount 用.sync文件取代不.sync的版本,就能同步到最新的数据库了。原来的pacman -Syu命令要拆开来用,先pacsyncpacman -Su了。

脚本里使用了单独的挂载空间并且将挂载改为了私有,所以并不会影响到外边。

Category: Linux | Tags: linux Arch Linux
10
29
2020
4

tar 归档的权限问题

一次系统升级之后,我的许多 Python 程序突然开始报错:

[...]
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2762, in _get_metadata
    for line in self.get_metadata_lines(name):
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1415, in get_metadata_lines
    return yield_lines(self.get_metadata(name))
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1405, in get_metadata
    value = self._get(path)
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1609, in _get
    with open(path, 'rb') as stream:
PermissionError: [Errno 13] Permission denied: '/usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO'

WTF 滚坏了!立即回滚!

回滚完之后,我开始调查这个事件——因为 [archlinuxcn] 的这个包是我管的呀。而且我记得之前也遇到过一次类似的情况,当时没有深究。

检查一下软件包里的文件的权限:

>>> tar tvf python-telethon-1.17.4-1-any.pkg.tar.zst | grep PKG-INFO
-rw------- root/root      3659 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO
>>> tar tvf python-telethon-1.17.4-1-any.pkg.tar.zst | grep -- ----
-rw------- root/root      3659 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO
-rw------- root/root     12078 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/SOURCES.txt
-rw------- root/root         1 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/dependency_links.txt
-rw------- root/root        27 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/requires.txt
-rw------- root/root        15 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/top_level.txt

好奇怪,Telethon 这些包信息文件怎么只让 root 读了呢?

从 PyPI 上下载 Telethon 的原始 tar 归档回来看看,发现最近几个版本里,文件权限全部只有自己可以读(-rw-------),而所有者是 u0_a167/10167。开发者突然在 Android 系统上打包了呢……安装的时候,部分文件的权限被保留了下来(Arch Linux 打包时强烈反对使用 root 权限执行,因此我用 devtools 打包,解包部分自然是普通用户操作的,所有者无法被保留)。

然后我又看了一下之前的版本,哦豁,所有者成开发者的 id 了,但是有三个版本的 pyc 文件,还有好几个 pyc 文件都是 -rwxrwxrwx。大概系统上的低权限用户可以去改改,然后看谁跑 Telegram 机器人就拿谁的权限?

经过跟开发者的讨论,最终干掉了 pyc 文件,也不在 Android 上打包了。777 权限问题还待解决。不过我更在意的是,为什么会发生这种状况呢?setuptools 干嘛不修一修呢?别的工具创建的用于发布的 tar 归档会不会有类似的问题呢?

结果找了找,发现 setuptools 前年就有人报告这个问题,但是并没有解决。行吧,我打包时统一修正一下权限好了……

下一个 GitHub 生成的 tar 归档看看?咦,-rw-rw-r-- root/root,是处理过了么?啊对,git archive 生成的包是怎么样的?去试了试,原来一样的啊。看来 git 想到了这个问题并且处理了,只是 002 的 umask 有点意外。

Arch Linux 为了普通用户打出文件为 root 所有的 tar 归档使用了 fakeroot,那么 git 是怎么实现的呢?翻了翻代码,git 是自己生成 tar 文件的,写死了所有者是 root/root,但是权限位还是有专门的 umask,默认是 002。可以配置,比如git config --global tar.umask user一下,就会取当前 umask 作为 tar 归档里文件的 umask 了。

至于传统的 GNU autotools 构建系统创建的 tar 归档,我也创建了一个看了一下,并没有特殊处理,跟手动跑 tar 一样。

Category: Linux | Tags: linux python Git
7
22
2020
12

Linux 的环境变量怎么设

最近,Arch Linux 的 pam 包将更新到 1.4.0,然后因为一个字符的变化,不少中文用户都开始避难了:pam_env 将默认不读取用户的环境变量设置。许多中文用户使用~/.pam_environment文件来配置 fcitx 输入法所需要的那三个环境变量。更新之后,这些配置将不再生效,意味着他们可能无法输入中文了,于是大家热烈讨论现在要在哪里配置环境变量比较好。

当然了,在 pam 的配置文件里加上user_readenv=1就可以恢复原先的行为。只是,pam 的开发者这么改当然是有原因的:CVE-2010-4708。简单地说,就是在鉴权模块里设置用户指定的环境变量不安全,怕被提权。

2020年08月20日更新:Arch Linux 打包时添加了以上这个配置

当然啦,桌面用户完全可以去改系统级的环境变量设置,只要你愿意让你个人的配置信息跑到系统配置那边去的话。

正文

用户经常需要设置一些环境变量,比如输入法,比如语言和地区选项,比如自定义的 PATH,等等。然而 Linux 的环境变量又不像 Windows 那样有一个专门设置所谓「系统环境变量」的地方,而是所有环境变量全部由子进程继承父进程获得,这么一路继承下来的。于是,想要给所有想设置上的进程都设置上,就不是一件简单的事情了。

这里关注的是桌面用户,自然首先从图形界面说起。

使用 X11 的桌面环境,通常通过 display manager 来登录,比如 lightdm 和 sddm。这俩都支持 ~/.xprofile。这个文件会在启动过程中被 source,使用的 shell 是由 dm 自己确定的。lightdm 和 sddm 都是用的 /bin/sh(分别位于 /etc/lightdm/Xsession 和 /usr/share/sddm/scripts/Xsession 文件里)。可以看到,除了读取 .xprofile 外,lightdm 也会读取 .profile。sddm 甚至连 bash、zsh、tcsh、fish 的启动配置脚本都给读了。

对于 Wayland,我这儿只有 sddm,类似的,也是各种 shell 的都给你读了。(当然就不读 X11 相关的东西了。)

而对于手动 startx 的用户,~/.xinitrc 里自己写上呗。还有 VNC 啥的,也都可以自己看启动脚本找到方法。

没有图形界面的登录,通常是从 tty 或者 ssh 登录。这两者都是登录到命令行 shell。写在 shell 配置里就可以了。具体到 zsh,我是写到 ~/.zprofile,这样不管是否交互,只要是登录 shell 就执行,不登录的大概已经继承到了。另外有些情况可能写这儿也没有用,比如 cron 调用的时候。非要搞的话其实可以写到 ~/.zshenv,这个基本上所有的 zsh 都会读,只能被系统级设置或者命令行参数禁用掉。而如果是 bash 用户,.bash_profile .bash_login .profile 这仨,会读找到的第一个(sddm 也会按这个顺序找,但是 lightdm 只会找 .profile)。Arch Linux 现在默认会给新用户建立 .bash_profile,我建议 bash 用户把它改名成 .profile。这里有个图 ,大致上说明了 bash 和 zsh 不同情况下会读取的配置文件

systemd 用户实例也有一份环境变量,用于通过 systemd 启动的用户级服务,通过 systemctl --user 的子命令可以查看和更新。D-Bus 也有一份,可以通过 dbus-update-activation-environment 命令来更新。这俩通常不需要用户操心,桌面环境应该会维护好。

如果你用 tmux 的话,那么 tmux 还有一份,用于新创建的窗格。tmux 会在被连接时更新 update-environment 选项指定的环境变量。当然你也可以用 tmux setenv 子命令去更新。这个尤其值得注意,因为 tmux 可能在不同的环境中被连接,导致环境变量混乱(比如 X11 下没有 DISPLAY 变量)。

要注意的一点是,不同的登录过程并不是只会读取自己专属的配置文件,尽管大家都在努力,也并没有完全统一好用的配置文件。所以有些环境变量,你可能想尽量避免重复设置(比如冗长重复的 PATH 项就很讨厌)。好在这些都是 shell 脚本,不光能设置环境变量,也能做条件判断。

要查看当前进程的环境变量,可以用 env 命令。要查看别的进程的,去读它的 /proc/PID/environ 文件,或者开个 htop 然后在进程上按 e 键。要看看某个环境变量在不同进程里是什么情况,可以用我写的小工具 compare-env

Linux 软件生态的特点就是这样,没有谁规定一定要用怎样的方式做一件事情,所以大家的想法总会有些出入,就导致设置个环境变量还有这么一大群配置文件(systemd 还有一份呢,我还没读没试所以没说)。但另一方面,理解它们也是十分容易的:不用猜测,不用撞大运般地耗费时间去尝试,顺着代码摸过去,总能弄明白软件的行事逻辑。即使文档像 pam_env 那样前后矛盾,也有源代码这条路可以精确刻画软件的行为。

Category: Linux | Tags: linux 环境变量

| Theme: Aeros 2.0 by TheBuckmaker.com