Created
December 5, 2024 22:24
-
-
Save ssokolow/1cbe7d9341d6b4401b17c0f7eebbd2b8 to your computer and use it in GitHub Desktop.
Python+QtDBUS reimplementation of (most of) org.kde.StatusNotifierItem
This file contains 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 python | |
# -*- coding: utf-8 -*- | |
"""A demonstration of implementing (most of) org.kde.StatusNotifierItem using | |
QtDBus to serve as as an example of how more advanced QtDBus features map to | |
PyQt5, absent robust documentation for how the schema annotations translate. | |
""" | |
import os | |
import signal | |
import sys | |
from PyQt5.QtCore import QObject, qCritical | |
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Q_CLASSINFO | |
from PyQt5.QtCore import pyqtProperty # type: ignore[attr-defined] | |
from PyQt5.QtDBus import QDBus, QDBusConnection, QDBusInterface | |
from PyQt5.QtWidgets import QApplication, QMainWindow | |
def die(msg): | |
"""Die without trying to core dump like qFatal would""" | |
qCritical("ERROR: ", msg) | |
sys.exit(1) | |
class StatusNotifierItem(QObject): | |
attention_icon = "go-previous" | |
icon = "go-up" | |
status = "Active" | |
title = "StatusNotifierItem Demo" | |
Q_CLASSINFO("D-Bus Interface", 'org.kde.StatusNotifierItem') | |
def __init__(self, window, *args, **kwargs): | |
self.window = window | |
super().__init__(*args, **kwargs) | |
# -- Properties -- | |
@pyqtProperty(str) | |
def Category(self): | |
return "ApplicationStatus" | |
@pyqtProperty(str) | |
def Id(self): | |
return "KSNI Example" | |
@pyqtProperty(str) | |
def Title(self): | |
return self.title | |
@pyqtProperty(str) | |
def Status(self): | |
return self.status | |
@pyqtProperty(int) | |
def WindowId(self): | |
return int(self.window.winId()) | |
@pyqtProperty(str) | |
def IconName(self): | |
return self.icon | |
# IconPixmap | |
# OverlayIconName (May not be implemented on Kubuntu 22.04 LTS) | |
# OverlayIconPixmap (May not be implemented on Kubuntu 22.04 LTS) | |
@pyqtProperty(str) | |
def AttentionIconName(self): | |
return self.attention_icon | |
# AttentionIconPixmap | |
# AttentionMovieName | |
# ToolTip | |
@pyqtProperty(bool) | |
def ItemIsMenu(self): | |
return False # Don't prefer left-clicking being ContextMenu too | |
# Menu | |
# -- Methods -- | |
@pyqtSlot(int, int) | |
def Activate(self, x, y): | |
print(f"Activated: {x}, {y}") | |
if self.window.isVisible(): | |
self.window.hide() | |
else: | |
self.window.showNormal() | |
@pyqtSlot(int, int) | |
def ContextMenu(self, x, y): | |
print(f"Context Menu: {x}, {y}") | |
@pyqtSlot(int, int) | |
def SecondaryActivate(self, x, y): | |
print(f"Secondary Activated: {x}, {y}") | |
if self.status == "Active": | |
self.status = "NeedsAttention" | |
else: | |
self.status = "Active" | |
self.NewStatus.emit(self.status) | |
self.title = f"SNI Status is {self.status}." | |
self.NewTitle.emit() | |
@pyqtSlot(int, str) | |
def Scroll(self, delta, orientation): | |
print(f"Scrolled: {delta}, {orientation}") | |
if delta < 0: | |
if self.attention_icon == "go-previous": | |
self.attention_icon = "go-next" | |
else: | |
self.attention_icon = "go-previous" | |
self.NewAttentionIcon.emit() | |
else: | |
if self.icon == "go-up": | |
self.icon = "go-down" | |
else: | |
self.icon = "go-up" | |
self.NewIcon.emit() | |
# -- Signals -- | |
# NewOverlayIcon | |
# NewToolTip | |
NewIcon = pyqtSignal() | |
NewAttentionIcon = pyqtSignal() | |
NewStatus = pyqtSignal(str) | |
NewTitle = pyqtSignal() | |
def main(): | |
app = QApplication(sys.argv) | |
window = QMainWindow() | |
session_bus = QDBusConnection.sessionBus() | |
if not session_bus.isConnected(): | |
die("Failed to connect to D-Bus session bus") | |
svc_name = "org.freedesktop.StatusNotifierItem-{}-0".format(os.getpid()) | |
if not session_bus.registerService(svc_name): | |
die("Could not register service for status notifier item") | |
notifier_item = StatusNotifierItem(window) | |
session_bus.registerObject('/StatusNotifierItem', notifier_item, | |
QDBusConnection.ExportAllContents) | |
watcher = QDBusInterface( | |
"org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", | |
"org.kde.StatusNotifierWatcher", session_bus) | |
if not watcher.isValid(): | |
die("Couldn't find org.kde.StatusNotifierWatcher") | |
response = watcher.call(QDBus.AutoDetect, # type: ignore[attr-defined] | |
"RegisterStatusNotifierItem", svc_name) | |
if response.errorName(): | |
die(response.errorMessage()) | |
signal.signal(signal.SIGINT, signal.SIG_DFL) # Allow Ctrl+C in all.exec_() | |
print("Press Ctrl+C to quit or close main window") | |
sys.exit(app.exec_()) # Start main loop | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment