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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
""" The module manage the socket server which allow to send new commands to
apply to the macropad.
Any client connected to the server will also be informed from the manual
changes in the macropad by the user.
The module is not blocking, and run in the main thread.
"""
import socket
import selectors
from dataclasses import dataclass
from typing import Callable
from zope import component
from interfaces import desktopEvent
from interfaces.message import Debug, ISocketMessage
@dataclass
class waitEvent:
callback = Callable[[object, socket, int], None]
@dataclass
class sendEvent:
callback = Callable[[object, socket, int], None]
message: str
actions = {
"stack" : lambda x : x,
"layout" : lambda x : x,
}
class Handler(object):
""" Listen the incomming connexions and dispatch them into the connexion
object """
def __init__(self, configuration, layout_queue):
""" Initialize the the socket server
The layout_queue will be populated with all the elements received from the socket.
"""
super().__init__()
self.sel = selectors.DefaultSelector()
component.provideHandler(self.sendMessage)
self.socket = socket.socket()
self.socket.bind((configuration["host"], int(configuration["port"])))
self.socket.listen(100)
self.socket.setblocking(False)
c = waitEvent()
c.callback = self.accept
self.sel.register(self.socket, selectors.EVENT_READ, c)
self.application_name = configuration["name"].lower()
self.connexions = []
self.layout_queue = layout_queue
@component.adapter(ISocketMessage)
def sendMessage(self, content:ISocketMessage) -> None:
""" Send a message to all the sockets connected to the server
"""
c = sendEvent(message = content.content)
c.callback = self._send
map = self.sel.get_map()
for value in list(map.values())[:]:
if value.fileobj == self.socket:
# Ignore the main connexion, keep it only in accept mode.
continue
self.sel.modify(value.fileobj, selectors.EVENT_WRITE, c)
def update(self):
""" Read the status for all the sockets, if they are available.
"""
events = self.sel.select(timeout=0)
for key, mask in events:
c = key.data
if isinstance(c, waitEvent):
c.callback(key.fileobj, mask)
elif isinstance(c, sendEvent):
c.callback(key.fileobj, mask, c.message)
def _switch_read(self, conn):
""" Send the socket back into read mode
"""
c = waitEvent()
c.callback = self._read
try:
self.sel.register(conn, selectors.EVENT_READ, c)
except KeyError:
self.sel.modify(conn, selectors.EVENT_READ, c)
def accept(self, sock, mask):
conn, addr = sock.accept() # Should be ready
conn.setblocking(False)
self._switch_read(conn)
component.handle(Debug("Received new connection"))
def _read(self:object, conn:socket, mask:int):
""" Internal method used to retreive data from the socket
"""
data = conn.recv(1024).strip()
if data == bytes("", "ascii"):
# A socket ready but sending garbage is a dead socket.
self.close(conn)
return
last_layout = str(data, "utf-8")
try:
js = json.loads(last_layout)[-1]
for key, value in js.items():
last_layout = actions[key](value)
except:
print(last_layout)
#title = component.queryUtility(desktopEvent.IDesktop).getForegroundWindowTitle()
# The application name is hardcoded in the code, and should be reported
# in the configuration or somewhere else.
# The name of the current application is not reliable because the
# message can be send with some lattency.
component.handle(Debug("Received %s from the socket" % (last_layout)))
# Associate the layout with the current window
self.layout_queue.put((last_layout, self.application_name))
def _send(self:object, conn:socket, mask:int, text) -> None:
""" Internal method used to dispatch the message to the socket. """
try:
conn.sendall(bytes(text , "utf-8"))
except:
self.close(conn)
return
self._switch_read(conn)
def close(self, conn):
self.sel.unregister(conn)
conn.close()
|