파이썬으로 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를 사용합니다:
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). 실패 시 루트 창을 기본으로 사용합니다.
전체 소스 코드
실행 가능한 스크립트 (.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 설치 방법:
- FreeBSD:
pkg install py311-python-xlib - Linux:
apt또는yum으로python3-xlib설치
주요 특징
- 크로스플랫폼: FreeBSD/Linux, 인텔/AMD 모두 지원, 웨이랜드 미지원.
- 미니멀리즘: 단 120줄, OOP/쓰레드 없음, 의존성 하나만 필요.
- X11 해킹: 권한 없이도 루트/데스크탑 창에 그리기, 시각적 표시 없음.
- 정리: 종료 시
fill_rectangle와clear_area사용. - 파싱:
subprocess+split을 활용해sysctl/sysfs데이터 처리.
— Editorial Team
아직 댓글이 없습니다.