aboutsummaryrefslogtreecommitdiff
path: root/xlib.py
blob: d28dfd4fcb9ec738172b376bad16dc702e3dbb9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from threading import Thread

import Xlib
import Xlib.display

from zope import interface

from zope import component

from interfaces.message import Debug
from interfaces import desktopEvent
from consumer import Mapping

@interface.implementer(desktopEvent.IDesktop)
class Listener(Thread):

    def __init__(self, mapping):
        super().__init__()
        self.mapping = mapping
        self.active_window = None
        self.last_code = None
        self.running = False
        self.daemon = True

    def getForegroundWindowTitle(self):
        """ Return the name of the selected window
        """
        return self.active_window

    def run(self):
        self.disp = Xlib.display.Display()
        root = self.disp.screen().root
        root.change_attributes(event_mask=Xlib.X.PropertyChangeMask)

        self.NET_WM_NAME = self.disp.intern_atom('_NET_WM_NAME')
        self.WM_CLASS = self.disp.intern_atom('WM_CLASS')
        self.NET_ACTIVE_WINDOW = self.disp.intern_atom('_NET_ACTIVE_WINDOW')
        component.handle(Debug("Waiting for xlib event"))
        self.running = True
        while self.running:

            try:
                self.handle_event(root, self.disp.next_event())
            except Exception as e:
                component.handle(Debug( str(e) ))
                print(e)

    def handle_event(self, root, event):
        try:
            window_id_property = root.get_full_property(self.NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType)
            if window_id_property is None:
                return
            window_id = window_id_property.value[0]
            window = self.disp.create_resource_object('window', window_id)
            window_prop = window.get_full_property(self.NET_WM_NAME, 0)
            if window_prop is None:
                return
            window_name = str(window_prop.value).lower()
            class_name = str(window.get_full_property(self.WM_CLASS, 0).value).lower()
        except Xlib.error.XError:
            return

        if window_name is None or window_name == self.active_window:
            return

        self.active_window = window_name
        found = False

        # Create a reveverse dictionnary for getting the users added first
        # In python, the elements in a dictionnary are sorted by insertion
        # order, so we get the user added first here
        for pattern in reversed(self.mapping.keys()):
            # Ignore the default layout
            if pattern == "default":
                continue
            if not (pattern.lstrip() in window_name or pattern.lstrip() in class_name):
                continue
            code = self.mapping[pattern]
            # We found something. At this point, even if we do not update
            # the layer (because it is the same as the previous) we do not
            # switch back to the default layer.
            found = True
            if code != self.last_code:

                component.handle(Debug(f"Switching to '{pattern}' for '{window_name}'"))
                component.handle(Mapping((code, None)))
                self.last_code = code
            # We found a matching configuration. Even if the match is the
            # same as the current one, we break the loop in order to
            # prevent another layer to update.
            break
        if not found and self.last_code != "default":
            default = self.mapping.get("default", None)
            if default is None:
                return

            component.handle(Mapping((default, None)))
            self.last_code = "default"


    def stop(self) -> None:
        self.running = False