返回首页

Python Xlib 上的 CPU 温度监控

使用 Xlib 的 Python 脚本在 X11 中以叠加层显示 CPU 温度。支持 FreeBSD (sysctl coretemp/amdtemp) 和 Linux (sysfs)。无 OOP 的极简实现,带有区域清除。

通过 Xlib 的 CPU 温度叠加层:Python 脚本
Advertisement 728x90

用Python在X11上实现CPU温度实时叠加显示

一个约120行的Python脚本可实时监测CPU温度,并将文本直接叠加在X11桌面之上。该脚本利用python-xlib库,在不需管理员权限的情况下,向根窗口或桌面窗口绘制内容。支持FreeBSD(通过sysctl配合coretemp/amdtemp模块)和Linux(/sys/class/thermal)。每15秒更新一次,按Ctrl-C可清除显示区域。

读取传感器数据

在FreeBSD上,加载内核模块后可通过sysctl获取温度信息:

  • Intel处理器:kldload coretemp,执行 sysctl dev.cpu |grep temperature
  • AMD处理器:kldload amdtemp,执行 sysctl dev.amdtemp.0 |grep core

在Linux系统中,使用sysfs接口:

Google AdInline article slot
cat /sys/class/thermal/thermal_zone*/temp

返回值单位为毫摄氏度(除以1000即得实际温度)。脚本通过subprocess.Popen捕获输出,解析并格式化为逗号分隔的温度字符串。

与X服务器交互

核心技巧:在其他窗口中绘图而不触发通知。Xlib实现方式如下:

  • 通过 _NET_CLIENT_LIST 获取根窗口或桌面窗口。
  • 创建一个前景为白色、背景为黑色的图形上下文(GC)。
  • 绘制前先用fill_rectangle清空目标区域。
  • 使用等宽字体调用draw_text绘制文本。

get_root_window函数会扫描顶层窗口,查找WM_NAME中包含"Desktop"的窗口(适用于KDE/Xfce),若未找到则回退至根窗口。

Google AdInline article slot

完整源码

自执行脚本(带shebang的.sh文件)。唯一依赖项:python-xlib。

#!/usr/bin/env python3

import Xlib
from Xlib import display, X   # display and X are not imported automatically
import subprocess,time,logging

# 日志配置
logging.basicConfig(level=logging.INFO)
#logging.basicConfig(level=logging.DEBUG)

# 叠加显示位置
POS_X = 150
POS_Y = 50
# FreeBSD及AMD系统适用
PATTERN = 'sysctl dev.amdtemp.0 |grep core'
# FreeBSD使用coretemp模块
#PATTERN = 'sysctl dev.cpu |grep temperature'
# Linux系统
#PATTERN = "cat /sys/class/thermal/thermal_zone*/temp | awk '{ print "temp: " ($1 / 1000) "C" }'"
# 字体设置
FONT = '-misc-fixed-medium-r-normal--13-120-75-75-c-70-iso8859-1'
# 刷新间隔(秒)
REFRESH_SECS = 15

last_dim = [0,0]

def get_root_window(d):
    
    screen = d.screen()
    root = screen.root

    windowIDs = root.get_full_property(d.intern_atom('_NET_CLIENT_LIST'), 
                                                  X.AnyPropertyType).value
    logging.debug('发现 %d 个窗口。',len(windowIDs))

    for windowID in windowIDs:
        window = d.create_resource_object('window', windowID)

        try:
            window_name = window.get_wm_name()
            if window_name:
                if 'Desktop' in window_name:
                    logging.debug("找到桌面窗口 ID: %d - 名称: %s",
                                                 windowID,window_name)
                    return window
            else:
                logging.debug("ID: %d - 名称: 无 (无WM_NAME属性)",
                                                 windowID)

        except X.BadWindow:
            logging.debug("ID: %d - 窗口已不存在",windowID)
    return root


def clear_rect(msg):
    global last_dim
    if last_dim[0] > 0:    
     root.fill_rectangle(gc2, POS_X,POS_Y-last_dim[1], 
                          last_dim[0]-20, last_dim[1]+5)
    else:
        text_extents = font.query_text_extents(msg)
        tw = text_extents.overall_width
        th = text_extents.font_ascent + text_extents.font_descent
        root.fill_rectangle(gc2, POS_X, POS_Y-th, tw-20, th+5)
        last_dim = [tw,th]


def draw_message(msg):
    clear_rect(msg)
    root.draw_text(gc, POS_X, POS_Y, msg)
    display.flush()


display = Xlib.display.Display()
screen = display.screen()
root = get_root_window(display)

root_id = root.id
logging.debug("根窗口ID: %d",root_id)
root.change_attributes(event_mask=X.ExposureMask)
gc = root.create_gc(foreground = screen.white_pixel, 
                    background = screen.black_pixel)
colormap = screen.default_colormap
color = colormap.alloc_named_color('black') 
gc2 = root.create_gc(foreground=color.pixel)

try:
    font = display.open_font(FONT)
    gc.font = font.id
except Exception as e:
    logging.exception(e)
    exit(1)

try:
    while 1:
            process = subprocess.Popen(PATTERN, 
                    shell=True, text=True,
                    stdout=subprocess.PIPE)
            stdout_list = process.communicate()[0].split('\n')

            out = ''
            for s in stdout_list:
                if ':' not in s: continue
                kv = s.split(':')
                if len(out) > 0: out+= ','
                out+= kv[1]

            logging.debug(out)
            draw_message(out.encode())
            time.sleep(REFRESH_SECS)
            
except KeyboardInterrupt:
        x = POS_X // 2
        y = POS_Y // 2
        root.clear_area(x,y,last_dim[0]+x,last_dim[1]+y,True)
        display.flush()

自定义设置

所有常量均位于脚本顶部:

  • POS_X, POS_Y:叠加显示在屏幕上的坐标位置。
  • PATTERN:传感器命令(根据平台调整)。
  • FONT:X11字体,采用XLFD格式。
  • REFRESH_SECS:刷新频率(秒)。
  • 日志级别:设为INFO或DEBUG。

安装python-xlib:

Google AdInline article slot
  • FreeBSD:pkg install py311-python-xlib
  • Linux:通过apt/yum安装python3-xlib

核心亮点

  • 跨平台兼容:支持FreeBSD/Linux,Intel/AMD处理器,无需Wayland。
  • 极简设计:仅120行代码,无面向对象结构或多线程,单一依赖。
  • X11黑科技:无需权限即可在根窗口/桌面绘制,无视觉提示。
  • 优雅清理:退出时使用fill_rectangle和clear_area清除残留。
  • 高效解析:依赖subprocess + split处理sysctl/sysfs数据。

— Editorial Team

Advertisement 728x90

继续阅读