Repository: pwr-Solaar/Solaar

Issue:
Process condition for flatpak apps
**Information**
<!-- Do not bother opening an issue for a version older than 1.0.1.  Please update to the latest version and see if your issue persists. -->
- Solaar version (`solaar --version` or `git describe --tags` if cloned from this repository): `solaar 1.0.5`
- Distribution: Elementary OS 5 (Ubuntu 18.04)
- Kernel version (ex. `uname -srmo`): `Linux 4.20.17-042017-generic x86_64 GNU/Linux`
- Output of `solaar show`: 

<details>

```
Unifying Receiver
  Device path  : /dev/hidraw0
  USB id       : 046d:C52B
  Serial       : 05529561
    Firmware   : 12.11.B0032
    Bootloader : 04.16
    Other      : AA.AA
  Has 2 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=39, 2=187

  1: Wireless Keyboard K375s
     Device path  : None
     WPID         : 4061
     Codename     : K375s
     Kind         : keyboard
     Protocol     : HID++ 4.5
     Polling rate : 20 ms (50Hz)
     Serial number: 06E177DF
     Model ID:      B34C40610000
     Unit ID:       58EFD8E0
        Bootloader: BOT 42.01.B0003
          Firmware: MPK 03.04.B0014
             Other: 
     The power switch is located on the base.
     Supports 31 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
            Firmware: Bootloader BOT 42.01.B0003 0000B5877FBE01
            Firmware: Firmware MPK 03.04.B0014 4061B5877FBE01
            Firmware: Other   
            Unit ID: 58EFD8E0  Model ID: B34C40610000  Transport IDs: {'btleid': 'B34C', 'wpid': '4061'}
         3: DEVICE NAME            {0005}   
            Name: K370s/K375s Multi-Device Wireless Keyboard
            Kind: keyboard
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: DEVICE FRIENDLY NAME   {0007}   
         7: BATTERY STATUS         {1000}   
            Battery: 90%, discharging, next level 50%.
         8: CHANGE HOST            {1814}   
            Change Host: 1:Swapnils-ASUS-VivoBook
         9: HOSTS INFO             {1815}   
            Host 0 (paired): 
            Host 1 (paired): Swapnil’s MacBook Air
            Host 2 (paired): Swapnil's Tab S6 Lite
        10: REPROG CONTROLS V4     {1B04}   
            Key/Button Diversion: {'209': 0, '210': 0, '211': 0, '213': 1, '214': 1, '189': 0, '6': 0, '4': 0, '5': 0, '3': 0, '2': 0, '1': 0, '191': 0, '212': 1, '10': 1, '221': 1, '193': 0, '192': 0}
        11: PERSISTENT REMAPPABLE ACTION {1C00}   
        12: K375S FN INVERSION     {40A3}   
            Swap Fx function: False
        13: ENCRYPTION             {4100}   
        14: LOCK KEY STATE         {4220}   
        15: KEYBOARD DISABLE KEYS  {4521}   
            Disable keys: {'1': False, '2': False, '4': False, '8': False, '16': False}
        16: MULTIPLATFORM          {4531}   
            Set OS: Windows
        17: DFUCONTROL SIGNED      {00C2}   
        18: unknown:1803           {1803}   internal, hidden
        19: CONFIG DEVICE PROPS    {1806}   internal, hidden
        20: OOBSTATE               {1805}   internal, hidden
        21: unknown:1813           {1813}   internal, hidden
        22: unknown:1830           {1830}   internal, hidden
        23: unknown:1861           {1861}   internal, hidden
        24: unknown:1890           {1890}   internal, hidden
        25: unknown:1891           {1891}   internal, hidden
        26: unknown:18A1           {18A1}   internal, hidden
        27: unknown:1DF3           {1DF3}   internal, hidden
        28: unknown:1E00           {1E00}   hidden
        29: unknown:1EB0           {1EB0}   internal, hidden
        30: unknown:18B0           {18B0}   internal, hidden
     Has 20 reprogrammable keys:
         0: Host Switch Channel 1     , default: HostSwitch Channel 1        => HostSwitch Channel 1      
             is FN, FN sensitive, divertable, persistently divertable, pos:1, group:0, group mask:empty
             reporting: default
         1: Host Switch Channel 2     , default: HostSwitch Channel 2        => HostSwitch Channel 2      
             is FN, FN sensitive, divertable, persistently divertable, pos:2, group:0, group mask:empty
             reporting: default
         2: Host Switch Channel 3     , default: HostSwitch Channel 3        => HostSwitch Channel 3      
             is FN, FN sensitive, divertable, persistently divertable, pos:3, group:0, group mask:empty
             reporting: default
         3: MultiPlatform Home/Mission Control, default: Multiplatform Home/Mission Control => Multiplatform Home/Mission Control
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty
             reporting: diverted
         4: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad, default: Multiplatform Menu/Launchpad => Multiplatform Menu/Launchpad
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, group mask:empty
             reporting: diverted
         5: Multiplatform Back        , default: MultiPlatform Back          => MultiPlatform Back        
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, group mask:empty
             reporting: default
         6: Previous                  , default: Previous                    => Previous                  
             is FN, FN sensitive, divertable, persistently divertable, pos:7, group:0, group mask:empty
             reporting: default
         7: Play/Pause                , default: Play/Pause                  => Play/Pause                
             is FN, FN sensitive, divertable, persistently divertable, pos:8, group:0, group mask:empty
             reporting: default
         8: Next                      , default: Next                        => Next                      
             is FN, FN sensitive, divertable, persistently divertable, pos:9, group:0, group mask:empty
             reporting: default
         9: Mute                      , default: Mute                        => Mute                      
             is FN, FN sensitive, divertable, persistently divertable, pos:10, group:0, group mask:empty
             reporting: default
        10: Volume Down               , default: Volume Down                 => Volume Down               
             is FN, FN sensitive, divertable, persistently divertable, pos:11, group:0, group mask:empty
             reporting: default
        11: Volume Up                 , default: Volume Up                   => Volume Up                 
             is FN, FN sensitive, divertable, persistently divertable, pos:12, group:0, group mask:empty
             reporting: default
        12: Screen Capture/Print Screen, default: Screen Capture              => Screen Capture            
             is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: default
        13: MultiPlatform Search      , default: Multiplatform Search        => Multiplatform Search      
             is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: diverted
        14: Calculator                , default: Calculator                  => Calculator                
             is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: diverted
        15: Multi Platform Language Switch, default: Multiplatform Language Switch => Multiplatform Language Switch
             is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: diverted
        16: Fn Up                     , default: unknown:0070                => unknown:0070              
             is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: default
        17: Fn Down                   , default: unknown:006F                => unknown:006F              
             is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty
             reporting: default
        18: F Lock                    , default: Do Nothing One              => Do Nothing One            
             is FN, pos:0, group:0, group mask:empty
             reporting: default
        19: unknown:0034              , default: Do Nothing One              => Do Nothing One            
             nonstandard, pos:0, group:0, group mask:empty
             reporting: default
     Battery: 90%, discharging, next level 50%.

  2: Multi Device Silent Mouse M585/M590
     Device path  : None
     WPID         : 406B
     Codename     : M585/M590
     Kind         : mouse
     Protocol     : HID++ 4.5
     Polling rate : 8 ms (125Hz)
     Serial number: 95138309
     Model ID:      B01B406B0000
     Unit ID:       25F556B4
        Bootloader: BOT 48.01.B0002
          Firmware: MPM 05.10.B0011
             Other: 
     The power switch is located on the base.
     Supports 35 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
            Firmware: Bootloader BOT 48.01.B0002 0000AE5E7277
            Firmware: Firmware MPM 05.10.B0011 406BAE5E7277
            Firmware: Other   
            Unit ID: 25F556B4  Model ID: B01B406B0000  Transport IDs: {'btleid': 'B01B', 'wpid': '406B'}
         3: DEVICE NAME            {0005}   
            Name: M585/M590 Multi-Device Mouse
            Kind: mouse
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: CRYPTO ID              {0021}   
         7: DEVICE FRIENDLY NAME   {0007}   
         8: BATTERY STATUS         {1000}   
            Battery: 90%, discharging, next level 50%.
         9: CHANGE HOST            {1814}   
            Change Host: 1:Swapnil’s MacBook Air
        10: HOSTS INFO             {1815}   
            Host 0 (paired): Swapnil’s MacBook Air
            Host 1 (paired): Swapnil’s MacBook Air
        11: REPROG CONTROLS V4     {1B04}   
            Key/Button Actions: {'80': 80, '81': 81, '82': 82, '83': 83, '86': 86, '91': 83, '93': 86}
            Key/Button Diversion: {'82': 0, '83': 1, '86': 1, '91': 0, '93': 0, '215': 0}
        12: PERSISTENT REMAPPABLE ACTION {1C00}   
        13: POINTER SPEED          {2205}   
            Pointer Speed: 1.0
            Sensitivity (Pointer Speed): 256
        14: VERTICAL SCROLLING     {2100}   
            Roller type: standard
            Ratchet per turn: 18
            Scroll lines: 0
        15: DFUCONTROL SIGNED      {00C2}   
        16: DEVICE RESET           {1802}   internal, hidden
        17: unknown:1803           {1803}   internal, hidden
        18: CONFIG DEVICE PROPS    {1806}   internal, hidden
        19: OOBSTATE               {1805}   internal, hidden
        20: unknown:1813           {1813}   internal, hidden
        21: unknown:1830           {1830}   internal, hidden
        22: unknown:1861           {1861}   internal, hidden
        23: unknown:1890           {1890}   internal, hidden
        24: unknown:1891           {1891}   internal, hidden
        25: unknown:18A1           {18A1}   internal, hidden
        26: unknown:1DF3           {1DF3}   internal, hidden
        27: unknown:1E00           {1E00}   hidden
        28: unknown:1EB0           {1EB0}   internal, hidden
        29: unknown:18B1           {18B1}   internal, hidden
        30: unknown:1850           {1850}   internal, hidden
        31: unknown:1E22           {1E22}   
        32: unknown:1F03           {1F03}   internal, hidden
        33: unknown:18C0           {18C0}   internal, hidden
        34: LOWRES WHEEL           {2130}   
            Wheel Reports: HID
            Scroll Wheel HID++ Scrolling: False
     Has 8 reprogrammable keys:
         0: Left Button               , default: Left Click                  => Left Click                
             mse, reprogrammable, pos:0, group:1, group mask:g1
             reporting: default
         1: Right Button              , default: Right Click                 => Right Click               
             mse, reprogrammable, pos:0, group:1, group mask:g1
             reporting: default
         2: Middle Button             , default: Mouse Middle Button         => Mouse Middle Button       
             mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: default
         3: Back Button               , default: Mouse Back Button           => Mouse Back Button         
             mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: diverted
         4: Forward Button            , default: Mouse Forward Button        => Mouse Forward Button      
             mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: diverted
         5: Left Tilt                 , default: Mouse Scroll Left Button    => Mouse Back Button         
             mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: default
         6: Right Tilt                , default: Mouse Scroll Right Button   => Mouse Forward Button      
             mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: default
         7: Virtual Gesture Button    , default: Virtual Gesture Button      => Virtual Gesture Button    
             divertable, virtual, raw XY, force raw XY, pos:0, group:3, group mask:empty
             reporting: default
     Battery: 90%, discharging, next level 50%.
```
</details>

**Describe the bug**
Is there a way to use process matching in rules for apps that are packaged with flatpak ?
I am running spotify flatpak version and using Spotify, spotify com.spotify.Client for process name does not work.
This may not be a limitation of Solaar but should be doable. Will close if that is the case.



Discussion:
The process name is the psutil Process name of the active X11 process id.  See what Spotify is named in `ps` output.
ps -aux has a bunch of entries for spotify but this looks like he main one:
```
swapn     6717  0.0  0.0   6912  1020 ?        S    Mar28   0:00 /usr/libexec/flatpak-bwrap --args 33 spotify

```
the `flatpak-bwrap` is not spotify specific though and is what launches flatpak apps, how can we test for an app running from within flatpak ?
That's a problem then.  flatpak-bwrap is deficient, and should rewrite its process information.  It might be possible to upgrade the relevant bit of code in diversion.py to look like https://psutil.readthedocs.io/en/latest/#find-process-by-name but that could end up with false positives - better to fix flatpak.
Maybe adding another condition for WM_CLASS could solve this, since that is only specified by the app developer and should be unchanged across any packaging formats or direct binary.
I can take a look at implementing this is the idea seems sound to you :)
I think that overloading the current condition would be the best approach here.  Another good enhancement would be to match against the filename part of the process name if the process name is a file path (i.e., contains a '/').
By overloading do you mean changing the code so that if the supplied process name doesn't match current active process then then matching the supplied process name wth the WM_CLASS ?
That can be done, and in that case the process condition should be renamed to `process or wm class` or something similar to clarify this,
or maybe just a info line below the process name input field would do.
Thoughts @pfps ?
Yes, an additional match against the wm class.


I don't think that there is a need for a change for the condition name, just a change to the Solaar documentation.
Raised #1133  based on our discussion here @pfps 

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
@@ -59,13 +59,15 @@
     # determine name of active process
     NET_ACTIVE_WINDOW = disp_prog.intern_atom('_NET_ACTIVE_WINDOW')
     NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID')
+    WM_CLASS = disp_prog.intern_atom('WM_CLASS')
     root2 = disp_prog.screen().root
     root2.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
 
 active_process_name = None
+active_wm_class_name = None
 
 
-def active_program():
+def active_program_name():
     try:
         window_id = root2.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
         window = disp_prog.create_resource_object('window', window_id)
@@ -75,17 +77,30 @@ def active_program():
         return None
 
 
-def determine_active_program():
+def active_program_wm_class():
+    try:
+        window_id = root2.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
+        window = disp_prog.create_resource_object('window', window_id)
+        window_wm_class = window.get_wm_class()[0]
+        return window_wm_class
+    except (Xlib.error.XError, AttributeError):  # simplify dealing with BadWindow
+        return None
+
+
+def determine_active_program_and_wm_class():
     global active_process_name
-    active_process_name = active_program()
+    global active_wm_class_name
+    active_process_name = active_program_name()
+    active_wm_class_name = active_program_wm_class()
     while True:
         event = disp_prog.next_event()
         if event.type == Xlib.X.PropertyNotify and event.atom == NET_ACTIVE_WINDOW:
-            active_process_name = active_program()
+            active_process_name = active_program_name()
+            active_wm_class_name = active_program_wm_class()
 
 
 if x11:
-    _thread.start_new_thread(determine_active_program, ())
+    _thread.start_new_thread(determine_active_program_and_wm_class, ())
 
 # determine current key modifiers
 # there must be a better way to do this
@@ -285,7 +300,9 @@ def __str__(self):
         return 'Process: ' + str(self.process)
 
     def evaluate(self, feature, notification, device, status, last_result):
-        return active_process_name.startswith(self.process) if isinstance(self.process, str) else False
+        if not isinstance(self.process, str):
+            return False
+        return active_process_name.startswith(self.process) or active_wm_class_name.startswith(self.process)
 
     def data(self):
         return {'Process': str(self.process)}


Test patch: