【技术】Xorg 实现不重启调整缩放 & 多显示器不同缩放

为了帮助搜索,这里加点 tag。

(ubuntu, kde, x-server, kubuntu, plasma, x-org)

这篇文章对你有帮助吗?

首先,本文适用于使用 Xorg 图形服务的 Linux 系统,如果你不在使用 Xorg,那此文对你没有帮助。

  • 目前 Gnome 默认使用 Wayland 而非 Xorg,因此默认配置的 Ubuntu 20.04~22.04 不适用。
  • 目前 KDE 默认采用 Xorg,因此默认配置的 Kubuntu 仍然适用(我就在用 Kubuntu 22.04)。
  • 其余系统请自行百度如何查询图形服务为 Xorg 还是 Wayland。

其次,此文操作原理是使用 xrandr 命令的 –scale 选项。如果你排斥使用 xrandr –scale,那此文对你没有帮助。

  • 非整数的缩放可能会带来十分轻微的画质下降和卡顿(不过影响很小,建议先试试);
  • 如果你已经熟练地阅读了 xrandr 的 manpage 并掌握其操作,那你大概率不需要阅读此文。

最后,如果你只是需要调整全局缩放,并不需要实现不重启 / 多显示器不同缩放,那建议使用更稳定的传统方法而非此文的方法。

  • 调整全局缩放的传统方法一是直接使用 kde 系统设置中的全局缩放设置,二是使用 xrandr –dpi 选项。前者众所周知需要重启,后者则必须要在 X 服务启动前就设定,因此一般需要写死在 ~/.Xsession 中,也不能在系统启动后热调整缩放。

前言

本人的需求是这样的,我有一台 14 英寸分辨率为 2160x1440 的笔记本,日常缩放为 175%,在宿舍里有一台 23.8 英寸分辨率为 2560x1440 的显示器,日常缩放为 150%,回宿舍就用显示器看。然而在使用 Xorg 的 Kubuntu 下,每次回宿舍或者出门都要调缩放、重启,实在是不够完美。经过 stfw,我摸索出了一个不重启调整缩放的 workaround,这个方法同时也能实现多屏幕不同缩放比。

我的想法是让全局缩放一直开在 175%,也就是比较高的一档,然后通过渲染出超分辨率的画面再缩小到显示器上,就可以实现降低全局缩放的效果。先贴一下效果图,我的显示器分辨率为 2560x1440,这里 Resolution 显示的是 2816x1584,说明已经完成了显示缩小。

image-20221015174744154

方法

首先,直接运行 xrandr 命令,可以看到目前有哪几个显示器以及它们的名字。输出应该类似于

1
2
3
4
5
6
Screen 0: minimum 320 x 200, current 4080 x 1440, maximum 8192 x 8192
eDP-1 connected primary 2160x1440+0+0 (normal left inverted right x axis y axis) 275mm x 183mm
2160x1440 60.00*+

DP-1 connected 1920x1080+2160+0 (normal left inverted right x axis y axis) 521mm x 293mm
1920x1080 60.00*+ 50.00 59.94

接下来讲一下 xrandr 工具用得到的各个选项的含义和使用方法。以下中括号表示需要代入具体内容,实际敲的时候不需要写中括号。

  • –fb [A]x[B]:指定画布大小为 A 像素长乘 B 像素宽。注意中间是小写字母 x,下同。
  • –output [A]:后续的选项对显示器 A 生效。
    • 显示器的名称可以通过上面说的方法,即直接执行 xrandr 看到
  • –mode [A]x[B]:指定当前显示器输出分辨率为 A 乘 B。
  • –scale [A]x[B]:指定当前显示器在长上缩小 A 倍,在宽上缩小 B 倍,因此 A > 1 表示缩小。
  • –pos [A]x[B]:指定当前显示器在画布上的位置。具体来说就是显示器应该显示的区域的左上角在画布上的坐标。
    • 坐标原点为左上角,单位为像素
  • –panning [A]x[B]+[C]+[D]:设定当前显示器可点击区域为左上角坐标为 (C 像素, D 像素),长为 A 像素,宽为 B 像素的矩形。所谓可点击区域,就是说在画布上的这一块区域是实际显示内容、可以点击的。

上面这部分内容可以和 xrandr 的 manpage 结合起来看,毕竟我复述的肯定不如官方文档全。我们的原理是先使用 –fb 选项渲染出一个超分辨率的画布,然后使用 –scale 选项缩小它的大小,并且使用 –pos 选项指定屏幕显示画布那一块被缩小了的部分。

单显示器

结合上面所说的,不重启实现单显示器缩放命令形式应该类似于

1
xrandr --fb [C*E]x[D*F] --output [display] --mode [C]x[D] --panning [C*E]x[D*F]+0+0 --scale [E]x[F]

举个例子,我们现在有一个物理分辨率为 2560 x 1440 的显示器,名字叫 HDMI-A-0,现在的全局缩放为 175%,需要改为 150%。

计算可得 $\sqrt{\frac{175}{150}}\approx 1.08$。由于渲染的分辨率需要为整数,选择缩小 1.1 倍。

我一开始忘记开根号了,缩小了 1.2 倍,然后一直寻思字怎么这么小(捂脸哭.jpg

以缩小 1.1 倍为例,需要渲染一个 (2560 * 1.1) x (1440 * 1.1) = 2816 x 1584 的画布然后再缩小。可点击区域就是左上为 (0, 0),2816 x 1584 大小的区域。因此命令为

1
xrandr --fb 2816x1584 --output HDMI-A-0 --mode 2560x1440 --scale 1.1x1.1 --pos 0x0 --panning 2816x1584+0+0

多显示器

多显示器不同分辨率拼接的命令形式应该为

1
xrandr --fb [C*E+A]x[D*F] --output [display1] --auto --output [display2] --mode [C]x[D] --panning [C*E]x[D*F]+[A]+0 --scale [E]x[F] --right-of [display1]

举个例子,这里参考了 Barry 的笔记。有一个物理分辨率为 2160x1440 的笔记本屏幕 eDP-1(这分辨率一眼 matebook),一个物理分辨率为 1920x1080 的显示器 DP-1 需要拼接在笔记本屏幕右侧,内容相较于笔记本屏幕需要缩小 1.5 倍。

由于是横向排布,整个画布的长应该为 1920*1.5 + 2160 = 5040,宽为 max{1080*1.5, 1440} = 1620。笔记本屏幕的位置应为 0x0,显示器的左上角位置为 2160*0。笔记本可点击区域直接使用默认设置,显示器的可点击区域为左上角为 2160*0,大小为 2880*1620 的矩形。因此,命令行为

1
xrandr --dpi 200 --fb 5040x1620 --output eDP-1 --mode 2160x1440 --pos 0x0 --output DP-1 --scale 1.5x1.5 --pos 2160x0 --panning 2880x1620+2160+0

这个 –dpi 选项是作者原来就写的,在我的电脑上不起作用,具体原因不明,我猜是因为 KDE 的默认缩放覆盖了这个设置。我个人是感觉不写也行,大家可以自己试试写不写 dpi 有没有区别。

后续

如果你在使用 KDE plasma 5 作为桌面,可能 plasma shell 不会根据最新的分辨率自动调整位置,这个时候需要重启 plasma shell 来适应新的分辨率。好在 plasma shell 是支持热重启的。

1
kquitapp5 plasmashell && kstart5 plasmashell

此时,如果你在屏幕下方和右方有 plasma 面板,你可能会发现它们变得十分卡顿。这估计是 KDE 的任务栏坐标不是整数导致渲染出问题,目前好像没啥办法可以根本解决(如果有大佬会请一定要娇娇我)。一个妥协的方法是将这些面板移到屏幕上方或者左侧。

花了一个小时和这个 bug 搏斗,最终还是妥协了


【技术】Xorg 实现不重启调整缩放 & 多显示器不同缩放
https://zjsheep.github.io/2023/06/29/Xorg-实现不重启调整缩放-多显示器不同缩放/
作者
ZJsheep
发布于
2023年6月29日
许可协议