如何在C程序中从X11获取更新后的系统DPI信息?

3
我正在尝试创建一个DPI感知应用程序,它会在窗口大小改变时响应用户请求的DPI更改事件。
这个程序是使用C和SDL2创建的,但为了获取系统DPI信息,我直接使用xlib,因为X11中的SDL DPI支持不足。
我发现了两种获取程序启动时正确DPI信息的方法,都涉及从Xresource获取Xft.dpi信息:一种是使用XGetDefault(display, "Xft", "dpi"),另一种是使用XResourceManagerString、XrmGetStringDatabase和XrmGetResource。当程序创建时,它们都返回正确的DPI值。
问题在于,如果用户在程序运行时更改了系统缩放比例,XGetDefault和XrmGetResource仍然返回旧的DPI值,尽管当我运行“xrdb -query | grep Xft.dpi”时,该值确实已更改。
有人知道如何获取更新的Xft.dpi值吗?
3个回答

1
首先需要注意的是,Xft.dpi资源的值不一定准确,这取决于系统和/或用户登录脚本是否正确设置它。
另外,重要的是要记住,Xft.dpi资源旨在由Xft库使用,而不是任意寻找屏幕分辨率的程序。
可以通过以下方式设置Xft.dpi资源。此示例仅处理具有单个屏幕的显示器,并注意它使用xdpyinfo。这也显示了它可能不是精确的,但可以四舍五入。最后,此示例显示如何计算水平和垂直分辨率,但Xft实际上只需要水平分辨率。
SCREENDPI=$(xdpyinfo | sed -n 's/^[ ]*resolution:[ ]*\([^ ][^ ]*\) .*$/\1/p;//q')
SCREENDPI_X=$(expr "$SCREENDPI" : '\([0-9]*\)x')
SCREENDPI_Y=$(expr "$SCREENDPI" : '[0-9]*x\([0-9]*\)')
# N.B.:  If true screen resolution is within 10% of 100DPI it makes the most
# sense to claim 100DPI to avoid font-scaling artifacts for bitmap fonts.
if expr \( $SCREENDPI_X / 100 = 1 \) \& \( $SCREENDPI_X % 100 \<= 10 \) >/dev/null; then
    FontXDPI=100
fi
if expr \( $SCREENDPI_Y / 100 = 1 \) \& \( $SCREENDPI_Y % 100 \<= 10 \) >/dev/null; then
    FontYDPI=100
fi
echo "Xft.dpi: ${FontYDPI}" | xrdb -merge

我真希望知道为什么Xft不至少尝试自己查找屏幕分辨率,而是一直依赖于其“dpi”资源的设置。但我发现当前实现仅使用资源设置,因此像上面那样设置资源始终是必要的(而且还必须确保X服务器本身已正确配置了正确的物理屏幕尺寸)。
从C程序中,您只需要执行xdpyinfo本身所做的操作并跳过有关Xft资源的所有废话。以下是xdpyinfo代码的释义:
Display *dpy;
dpy = XOpenDisplay(displayname);
for (scr = 0; scr < ScreenCount(dpy); scr++) {
    int xres, yres;

    /*
     * there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
     *
     *     dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
     *         = N pixels / (M inch / 25.4)
     *         = N * 25.4 pixels / M inch
     */
    xres = ((((double) DisplayWidth(dpy, scr)) * 25.4) /
        ((double) DisplayWidthMM(dpy, scr))) + 0.5;
    yres = ((((double) DisplayHeight(dpy, scr)) * 25.4) /
        ((double) DisplayHeightMM(dpy, scr))) + 0.5;
}
XCloseDisplay(dpy);

请注意,如果你因为某种奇怪的原因要缩放整个显示器(例如使用 xrandr),那么你应该希望字体与其他所有内容等比例缩放。仅仅为了缩放字体而全屏缩放只是一个可怕的错误操作,尤其是因为对于大多数事情而言,更简单的方法是告诉应用程序使用正确缩放的字体,这些字体将以恒定的屏幕点大小显示(这正是 Xft 使用“dpi”资源来实现的)。我猜测Ubuntu会做一些愚蠢的事情来改变屏幕分辨率,例如使用 xrandr 来扩大图标和其他屏幕小部件的视觉大小,而无需让应用程序知道屏幕大小和分辨率,然后它必须通过重写 Xft.dpi 资源来欺骗 Xft
请注意,如果避免全屏缩放,则不使用 Xft 的应用程序仍可以通过正确请求正确缩放的字体来获得正确的字体缩放,即使使用位图字体也可以通过在字体规范中使用屏幕的实际分辨率将其缩放到正确的物理屏幕尺寸。例如,从上面的 shell 片段继续:
# For pre-Xft applications we can specify physical font text sizes IFF we also tell
# it the screen's actual resolution when requesting a font.  Note the use of the
# rounded values here.
#
DecentDeciPt="80"
DecentPt="8"
export DecentDeciPt DecentPt
#
# Best is to arrange one's font-path to get the desired one first, but....
# If you know the name of a font family that you like and you can be sure
# it is installed and in the font-path somewhere....
#
DefaultFontSpec='-*-liberation mono-medium-r-*-*-*-${DecentDeciPt}-${FontXDPI}-${FontYDPI}-m-*-iso10646-1'
export DefaultFontSpec
#
# For Xft we have set the Xft.dpi resource so this allows the physical font size to
# be specified (e.g. with Xterm's "-fs" option) and for a decent scalable font
# to be chosen:
#
DefaultFTFontSpec="-*-*-medium-r-*-*-*-*-0-0-m-*-iso10646-1"
DefaultFTFontSpecL1="-*-*-medium-r-*-*-*-*-0-0-m-*-iso8859-1"
export DefaultFTFontSpec DefaultFTFontSpecL1

# Set a default font that should work for everything
#
eval echo "*font: ${DefaultFontSpec}" | xrdb -merge

最后,这里有一个启动 xterm 的示例(该终端已编译为使用Xft),使用上述设置(即Xft.dpi资源和上面的shell变量)在屏幕上显示物理大小为10.0点的文本:

xterm -fs 10 -fa $DefaultFTFontSpec

1
我找到了一个方法来做我想要的事情,尽管这种方法有点 hackish。
解决方案(使用 XLib)是创建一个新的临时连接到 X 服务器,使用 XOpenDisplay 和 XCloseDisplay,然后从该新连接中轮询资源信息。
之所以需要这样做是因为 X 仅在每个新连接时获取资源信息一次,并且永远不会更新它。因此,通过打开一个新连接,X 将获得更新的 xresource 数据,可以用于旧的主连接。
请注意,不断打开和关闭新的 X 连接可能对性能不利,因此只有在绝对必要时才这样做。在我的情况下,由于窗口有边框,因此仅在标题高度更改时检查 DPI 更改,因为 DPI 更改将由于字体大小差异而更改标题边框的大小。

你是如何获取标题高度变化事件的?谢谢。 - Liang Qi
已经有一段时间了,我记不太清楚了,但可能是在窗口移动或调整大小事件期间。DPI更改会触发窗口调整大小(或移动...?)事件并更改标题栏高度。因此,通过比较旧的标题栏高度和新的高度,我可以确定移动/调整大小是由用户移动/调整窗口还是由可能的DPI更改引起的。 - José Cadete

0
你可以尝试使用xdpyinfo(1)命令;在我的系统上,它会输出很多信息,其中包括:
  dimensions:    1280x1024 pixels (332x250 millimeters)
  resolution:    98x104 dots per inch
  depths (7):    24, 1, 4, 8, 15, 16, 32

我不知道它是否能帮到你,因为我不知道如何更改屏幕的DPI,但有可能会起作用。祝你好运!

--- 评论后更新 --- 在下面的评论中,OP说“有一个设置可以更改DPI”...但我仍然不知道是哪个设置。无论如何,我尝试使用Ctrl+Alt+Plus和Ctrl+Alt+Minus来实时更改X服务器的分辨率。在更改了分辨率并看到所有内容比以前都要大之后,我再次运行了xdpyinfo。它没有起作用:输出仍然相同。但也许你使用的方法(哪个?)会起作用...


在Ubuntu和Linux Mint(我尝试过的唯一发行版)上,您可以更改设置以将窗口缩放为1x、2x或3x。这些更新Xfi.dpi。感谢建议,但获取监视器尺寸并不理想,因为用户可能会选择不同的比例,而不考虑监视器的物理DPI。 - José Cadete
@JoséCadete看看更新后的答案。不过,你可以试试,对吧? - linuxfan says Reinstate Monica
在Ubuntu中更改DPI,您需要进入“首选项”>“设备”>“显示器”>“缩放”选项。我认为该选项仅出现在某些分辨率的监视器上,不管怎样,这就是以下链接中图像中显示的设置:https://www.reddit.com/r/linux/comments/8bt4eg/oh_cool_i_can_scale_my_display_300_but_not_125/ - José Cadete
@JoséCadete 我更偏向于裸机…但是那些200% 300%的缩放可能完全可以成为桌面管理器的事情,而不是“裸机”X11。我的意思是,尤其是Ubuntu和许多桌面管理器都试图隐藏系统的细节,这对用户来说可能是好事,但对开发人员来说则是一种痛苦。所以我不知道... - linuxfan says Reinstate Monica
我想你是对的,而且Wayland使用完全不同的方法。我想我会等到事情变得更加一致/SDL改进DPI支持... - José Cadete

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接