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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
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.WM_NAME = self.disp.intern_atom('WM_NAME')
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 get_property_value(self, window, property_):
""" Return the requested property for the given window, or None if the
property is not defined. """
prop_name = window.get_full_property(property_, 0)
if prop_name is not None:
return str(prop_name.value).lower()
return None
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_name = self.get_property_value(window, self.NET_WM_NAME) \
or self.get_property_value(window, self.WM_NAME)
class_name = self.get_property_value(window, self.WM_CLASS)
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 ((window_name is not None and pattern.lstrip() in window_name)
or (class_name is not None and 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:
print(f"Mapping '{window_name}' / '{class_name}' to default")
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:
""" Stop the thread properly.
"""
self.running = False
|