Repository: pwr-Solaar/Solaar

Issue:
Fix for lack of Process condition in Wayland
Currently solaar doesn't support Process or Mouse process condition since the X11 api that we use for it is not available on Wayland,
one way we can implement it is by building a gnome extension that exposes the current wm_class value and the mouse over wm_class values over a dbus api maybe ?
I have seen this being done by some application to workaround this exact same issue but don't remember the name of it.

If this is okay then I can take ownership of building the gnome extension.

And then we can use the api made available by it in solaar for wayland users and add info in the docs and maybe when somewhere in rules editor that the corresponding gnome extension will need to be installed for some rules to work.

Let me know if this works or if you have any other approach in mind.


Discussion:
That only works for Gnome.  I would prefer something that works for any window manager under Wayland.  I also seem to remember that there was some talk about implementing an extension like this for general use.  If this extension already exists or is in progress then using it would be reasonable.
I  only found one extension doing this and it was specific to an application that I just can't remember the name of right now.
We can make this extension general purpose so other applications needing it could use it too, unless there are other things broken in Wayland that can be fixed with an extension that are specific to Solaar.

I agree that this would only solve the issue for Gnome and not the others but I am not sure there is a way yet to do this across DE.
We can have a similar extension for KDE, I think the extension that I had seen also had a KDE counterpart.

I also found https://gist.github.com/rbreaves/257c3edfa301786e66e964d7ac036269 which may allow you to implement this without Gnome extension and the link mentions a similar KDE counterpart.
Does this actually work on Gnome?  If so, it might be possible to include it, although something that works with multiple window managers would be preferable.
The command mentioned in the gist did not work for me, I was trying to replace the javascript code inside the command to get it to work correctly but didn't finish it. I can take a look at it again though if that would help.
Sure, see whether there is a way to get it to work in Gnome without extensions.
@pfps Looks like the gdbus Eval interface is not accessible anymore from Gnome 41,
so the only way to do this would be a Gnome extension,
also I found the extension I had seen that implements similar functionality https://github.com/xremap/xremap-gnome
If an extension is okay then I can go ahead and implement one and also make the changes to read from that extension in Wayland when Gnome is running,
afaik, an extension is the only way to accomplish this right now.
Aaah, this is where your comment belongs.

Sure, go ahead and see whether this extension works and how to get Solaar to interface with it.  It would be also useful to get the window that the mouse is over.
https://github.com/kando-menu/gnome-shell-integration

Maybe this extension might be helpful? 
It provides the currently running application and mouse position via GNOME's dbus interface.
That might do the trick, but someone who runs Gnome will have to figure out how to get Solaar to use it.

Resolving patch:
diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py
--- a/lib/logitech_receiver/diversion.py
+++ b/lib/logitech_receiver/diversion.py
@@ -28,6 +28,7 @@
 from math import sqrt as _sqrt
 from struct import unpack as _unpack
 
+import dbus
 import evdev
 import keysyms.keysymdef as _keysymdef
 import psutil
@@ -94,7 +95,10 @@
 
 wayland = _os.getenv('WAYLAND_DISPLAY')  # is this Wayland?
 if wayland:
-    _log.warn('rules cannot access active process or modifier keys in Wayland')
+    _log.warn(
+        'rules cannot access modifier keys in Wayland, '
+        'accessing process only works on GNOME with Solaar Gnome extension installed'
+    )
 
 try:
     import Xlib
@@ -108,6 +112,8 @@
 modifier_keycodes = []
 XkbUseCoreKbd = 0x100
 
+_dbus_interface = None
+
 
 class XkbDisplay(_ctypes.Structure):
     """ opaque struct """
@@ -143,6 +149,20 @@ def x11_setup():
     return _x11
 
 
+def gnome_dbus_interface_setup():
+    global _dbus_interface
+    if _dbus_interface is not None:
+        return _dbus_interface
+    try:
+        bus = dbus.SessionBus()
+        remote_object = bus.get_object('org.gnome.Shell', '/io/github/pwr_solaar/solaar')
+        _dbus_interface = dbus.Interface(remote_object, 'io.github.pwr_solaar.solaar')
+    except dbus.exceptions.DBusException:
+        _log.warn('Solaar Gnome extension not installed - some rule capabilities inoperable', exc_info=_sys.exc_info())
+        _dbus_interface = False
+    return _dbus_interface
+
+
 def xkb_setup():
     global X11Lib, Xkbdisplay
     if Xkbdisplay is not None:
@@ -562,13 +582,30 @@ def x11_pointer_prog():
     return (wm_class[0], wm_class[1], name) if wm_class else (name, )
 
 
+def gnome_dbus_focus_prog():
+    if not gnome_dbus_interface_setup():
+        return None
+    wm_class = _dbus_interface.ActiveWindow()
+    return (wm_class, ) if wm_class else None
+
+
+def gnome_dbus_pointer_prog():
+    if not gnome_dbus_interface_setup():
+        return None
+    wm_class = _dbus_interface.PointerOverWindow()
+    return (wm_class, ) if wm_class else None
+
+
 class Process(Condition):
 
     def __init__(self, process, warn=True):
         self.process = process
-        if wayland or not x11_setup():
+        if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()):
             if warn:
-                _log.warn('rules can only access active process in X11 - %s', self)
+                _log.warn(
+                    'rules can only access active process in X11 or in wayland under GNOME with Solaar Gnome extension - %s',
+                    self
+                )
         if not isinstance(process, str):
             if warn:
                 _log.warn('rule Process argument not a string: %s', process)
@@ -582,7 +619,7 @@ def evaluate(self, feature, notification, device, status, last_result):
             _log.debug('evaluate condition: %s', self)
         if not isinstance(self.process, str):
             return False
-        focus = x11_focus_prog()
+        focus = x11_focus_prog() if not wayland else gnome_dbus_focus_prog()
         result = any(bool(s and s.startswith(self.process)) for s in focus) if focus else None
         return result
 
@@ -594,9 +631,12 @@ class MouseProcess(Condition):
 
     def __init__(self, process, warn=True):
         self.process = process
-        if wayland or not x11_setup():
+        if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()):
             if warn:
-                _log.warn('rules cannot access active mouse process in X11 - %s', self)
+                _log.warn(
+                    'rules cannot access active mouse process '
+                    'in X11 or in wayland under GNOME with Solaar Gnome extension - %s', self
+                )
         if not isinstance(process, str):
             if warn:
                 _log.warn('rule MouseProcess argument not a string: %s', process)
@@ -610,7 +650,7 @@ def evaluate(self, feature, notification, device, status, last_result):
             _log.debug('evaluate condition: %s', self)
         if not isinstance(self.process, str):
             return False
-        pointer_focus = x11_pointer_prog()
+        pointer_focus = x11_pointer_prog() if not wayland else gnome_dbus_pointer_prog()
         result = any(bool(s and s.startswith(self.process)) for s in pointer_focus) if pointer_focus else None
         return result
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -82,6 +82,7 @@ def _data_files():
         'PyYAML (>= 3.12)',
         'python-xlib (>= 0.27)',
         'psutil (>= 5.4.3)',
+        'dbus-python (>=1.3.2)',
     ],
     extras_require={
         'report-descriptor': ['hid-parser'],


Test patch: