Created
March 25, 2026 11:54
-
-
Save alex0x08/8f743382cc1563c3140ac86cb9e36ed0 to your computer and use it in GitHub Desktop.
Simple temperature widget, Python + Xlib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 = 1150 | |
| 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 = 5 | |
| last_dim = [0,0] | |
| # определение рабочего стола | |
| # d - display (экран)s | |
| def get_root_window(d): | |
| screen = d.screen() | |
| # root window - рабочий стол по-умолчанию | |
| root = screen.root | |
| # Получаем ID всех окон верхнего уровня | |
| windowIDs = root.get_full_property(d.intern_atom('_NET_CLIENT_LIST'), | |
| X.AnyPropertyType).value | |
| logging.debug('Found %d windows.',len(windowIDs)) | |
| for windowID in windowIDs: | |
| # Create a window object from the ID to access its properties | |
| window = d.create_resource_object('window', windowID) | |
| try: | |
| # Get the window title (WM_NAME or _NET_WM_NAME) | |
| # Use get_wm_name() for simplicity, | |
| # or look up EWMH properties for better compatibility | |
| window_name = window.get_wm_name() | |
| if window_name: | |
| # в продвинутых DE вроде KDE/Xfce за рабочий стол отвечает | |
| # отдельное окно и рисовать придется в нем | |
| if 'Desktop' in window_name: | |
| logging.debug("Found desktop ID: %d - Name: %s", | |
| windowID,window_name) | |
| return window | |
| else: | |
| # Бывает, что у окна нет заголовка | |
| logging.debug("ID: %d - Name: None (no WM_NAME property)", | |
| windowID) | |
| except X.BadWindow: | |
| # Обработка ситуации, когда считываемое окно больше не существует | |
| logging.debug("ID: %d - Window no longer exists",windowID) | |
| # если отдельного окна с именем Desktop не обнаружено - рисуем | |
| # прямо на root window | |
| 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 | |
| # очистка происходит через отрисовку черного прямоугольника | |
| # clear_area плохо работает с KDE | |
| root.fill_rectangle(gc2, POS_X,POS_Y-th, tw-20, th+5) | |
| # запоминаем размеры надписи для следующей очистки | |
| last_dim = [tw,th] | |
| # отрисовка сообщения на экране | |
| # msg - текст сообщения | |
| 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) | |
| # Access the window ID (an integer) | |
| root_id = root.id | |
| logging.debug("Root window ID: %d",root_id) | |
| # для реакции на события | |
| root.change_attributes(event_mask=X.ExposureMask) # "adds" this event mask | |
| # создание графического контекста, белый текст на черном фоне | |
| 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: | |
| # при нажатии Ctrl-C делаем очистку области экрана, где | |
| # происходила отрисовка виджета | |
| x = POS_X // 2 | |
| y = POS_Y // 2 | |
| root.clear_area(x,y,last_dim[0]+x,last_dim[1]+y,True) | |
| display.flush() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment