홈으로 돌아가기

Python Xlib을 사용한 CPU 온도 모니터링

Xlib을 사용한 Python 스크립트가 X11에서 CPU 온도를 오버레이로 표시. FreeBSD (sysctl coretemp/amdtemp)와 Linux (sysfs) 지원. OOP 없이 미니멀한 구현, 영역 지우기 포함.

Xlib을 통한 CPU 온도 오버레이: Python Script
Advertisement 728x90

파이썬으로 X11에 CPU 온도 오버레이 만들기: 크로스플랫폼 모니터링

약 120줄의 파이썬 스크립트가 CPU 온도를 모니터링하고, X11 데스크탑 위에 텍스트를 직접 오버레이합니다. python-xlib 라이브러리를 사용해 특권 없이도 루트 창이나 데스크탑 창에 그림을 그릴 수 있습니다. FreeBSD(커널 모듈 coretemp/amdtemp와 함께 sysctl 사용) 및 Linux(/sys/class/thermal)를 지원하며, 15초마다 업데이트되며 Ctrl-C로 화면을 지웁니다.

센서 데이터 읽기

FreeBSD에서는 커널 모듈을 로드한 후 sysctl을 통해 온도 정보를 얻을 수 있습니다:

  • 인텔: 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

전체 소스 코드

실행 가능한 스크립트 (.sh 파일 + shebang). 의존성은 python-xlib 하나뿐입니다.

#!/usr/bin/env python3

import Xlib
from Xlib import display, X   # display와 X는 자동으로 임포트되지 않음
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 - 이름: None (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: XLFD 형식의 X11 폰트.
  • REFRESH_SECS: 갱신 주기.
  • 로깅: INFO 또는 DEBUG로 설정.

python-xlib 설치 방법:

Google AdInline article slot
  • FreeBSD: pkg install py311-python-xlib
  • Linux: apt 또는 yum으로 python3-xlib 설치

주요 특징

  • 크로스플랫폼: FreeBSD/Linux, 인텔/AMD 모두 지원, 웨이랜드 미지원.
  • 미니멀리즘: 단 120줄, OOP/쓰레드 없음, 의존성 하나만 필요.
  • X11 해킹: 권한 없이도 루트/데스크탑 창에 그리기, 시각적 표시 없음.
  • 정리: 종료 시 fill_rectangleclear_area 사용.
  • 파싱: subprocess + split을 활용해 sysctl/sysfs 데이터 처리.

— Editorial Team

Advertisement 728x90

다음 읽기