1bf215546Sopenharmony_ci#!/usr/bin/env python3
2bf215546Sopenharmony_ciimport os
3bf215546Sopenharmony_ciimport socket
4bf215546Sopenharmony_ciimport sys
5bf215546Sopenharmony_ciimport select
6bf215546Sopenharmony_cifrom select import EPOLLIN, EPOLLPRI, EPOLLERR
7bf215546Sopenharmony_ciimport time
8bf215546Sopenharmony_cifrom collections import namedtuple
9bf215546Sopenharmony_ciimport argparse
10bf215546Sopenharmony_ci
11bf215546Sopenharmony_ciTIMEOUT = 1.0 # seconds
12bf215546Sopenharmony_ci
13bf215546Sopenharmony_ciVERSION_HEADER = bytearray('MesaOverlayControlVersion', 'utf-8')
14bf215546Sopenharmony_ciDEVICE_NAME_HEADER = bytearray('DeviceName', 'utf-8')
15bf215546Sopenharmony_ciMESA_VERSION_HEADER = bytearray('MesaVersion', 'utf-8')
16bf215546Sopenharmony_ci
17bf215546Sopenharmony_ciDEFAULT_SERVER_ADDRESS = "\0mesa_overlay"
18bf215546Sopenharmony_ci
19bf215546Sopenharmony_ciclass Connection:
20bf215546Sopenharmony_ci    def __init__(self, path):
21bf215546Sopenharmony_ci        # Create a Unix Domain socket and connect
22bf215546Sopenharmony_ci        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
23bf215546Sopenharmony_ci        try:
24bf215546Sopenharmony_ci            sock.connect(path)
25bf215546Sopenharmony_ci        except socket.error as msg:
26bf215546Sopenharmony_ci            print(msg)
27bf215546Sopenharmony_ci            sys.exit(1)
28bf215546Sopenharmony_ci
29bf215546Sopenharmony_ci        self.sock = sock
30bf215546Sopenharmony_ci
31bf215546Sopenharmony_ci        # initialize poll interface and register socket
32bf215546Sopenharmony_ci        epoll = select.epoll()
33bf215546Sopenharmony_ci        epoll.register(sock, EPOLLIN | EPOLLPRI | EPOLLERR)
34bf215546Sopenharmony_ci        self.epoll = epoll
35bf215546Sopenharmony_ci
36bf215546Sopenharmony_ci    def recv(self, timeout):
37bf215546Sopenharmony_ci        '''
38bf215546Sopenharmony_ci        timeout as float in seconds
39bf215546Sopenharmony_ci        returns:
40bf215546Sopenharmony_ci            - None on error or disconnection
41bf215546Sopenharmony_ci            - bytes() (empty) on timeout
42bf215546Sopenharmony_ci        '''
43bf215546Sopenharmony_ci
44bf215546Sopenharmony_ci        events = self.epoll.poll(timeout)
45bf215546Sopenharmony_ci        for ev in events:
46bf215546Sopenharmony_ci            (fd, event) = ev
47bf215546Sopenharmony_ci            if fd != self.sock.fileno():
48bf215546Sopenharmony_ci                continue
49bf215546Sopenharmony_ci
50bf215546Sopenharmony_ci            # check for socket error
51bf215546Sopenharmony_ci            if event & EPOLLERR:
52bf215546Sopenharmony_ci                return None
53bf215546Sopenharmony_ci
54bf215546Sopenharmony_ci            # EPOLLIN or EPOLLPRI, just read the message
55bf215546Sopenharmony_ci            msg = self.sock.recv(4096)
56bf215546Sopenharmony_ci
57bf215546Sopenharmony_ci            # socket disconnected
58bf215546Sopenharmony_ci            if len(msg) == 0:
59bf215546Sopenharmony_ci                return None
60bf215546Sopenharmony_ci
61bf215546Sopenharmony_ci            return msg
62bf215546Sopenharmony_ci
63bf215546Sopenharmony_ci        return bytes()
64bf215546Sopenharmony_ci
65bf215546Sopenharmony_ci    def send(self, msg):
66bf215546Sopenharmony_ci        self.sock.send(msg)
67bf215546Sopenharmony_ci
68bf215546Sopenharmony_ciclass MsgParser:
69bf215546Sopenharmony_ci    MSGBEGIN = bytes(':', 'utf-8')[0]
70bf215546Sopenharmony_ci    MSGEND = bytes(';', 'utf-8')[0]
71bf215546Sopenharmony_ci    MSGSEP = bytes('=', 'utf-8')[0]
72bf215546Sopenharmony_ci
73bf215546Sopenharmony_ci    def __init__(self, conn):
74bf215546Sopenharmony_ci        self.cmdpos = 0
75bf215546Sopenharmony_ci        self.parampos = 0
76bf215546Sopenharmony_ci        self.bufferpos = 0
77bf215546Sopenharmony_ci        self.reading_cmd = False
78bf215546Sopenharmony_ci        self.reading_param = False
79bf215546Sopenharmony_ci        self.buffer = None
80bf215546Sopenharmony_ci        self.cmd = bytearray(4096)
81bf215546Sopenharmony_ci        self.param = bytearray(4096)
82bf215546Sopenharmony_ci
83bf215546Sopenharmony_ci        self.conn = conn
84bf215546Sopenharmony_ci
85bf215546Sopenharmony_ci    def readCmd(self, ncmds, timeout=TIMEOUT):
86bf215546Sopenharmony_ci        '''
87bf215546Sopenharmony_ci        returns:
88bf215546Sopenharmony_ci            - None on error or disconnection
89bf215546Sopenharmony_ci            - bytes() (empty) on timeout
90bf215546Sopenharmony_ci        '''
91bf215546Sopenharmony_ci
92bf215546Sopenharmony_ci        parsed = []
93bf215546Sopenharmony_ci
94bf215546Sopenharmony_ci        remaining = timeout
95bf215546Sopenharmony_ci
96bf215546Sopenharmony_ci        while remaining > 0 and ncmds > 0:
97bf215546Sopenharmony_ci            now = time.monotonic()
98bf215546Sopenharmony_ci
99bf215546Sopenharmony_ci            if self.buffer == None:
100bf215546Sopenharmony_ci                self.buffer = self.conn.recv(remaining)
101bf215546Sopenharmony_ci                self.bufferpos = 0
102bf215546Sopenharmony_ci
103bf215546Sopenharmony_ci            # disconnected or error
104bf215546Sopenharmony_ci            if self.buffer == None:
105bf215546Sopenharmony_ci                return None
106bf215546Sopenharmony_ci
107bf215546Sopenharmony_ci            for i in range(self.bufferpos, len(self.buffer)):
108bf215546Sopenharmony_ci                c = self.buffer[i]
109bf215546Sopenharmony_ci                self.bufferpos += 1
110bf215546Sopenharmony_ci                if c == self.MSGBEGIN:
111bf215546Sopenharmony_ci                    self.cmdpos = 0
112bf215546Sopenharmony_ci                    self.parampos = 0
113bf215546Sopenharmony_ci                    self.reading_cmd = True
114bf215546Sopenharmony_ci                    self.reading_param = False
115bf215546Sopenharmony_ci                elif c == self.MSGEND:
116bf215546Sopenharmony_ci                    if not self.reading_cmd:
117bf215546Sopenharmony_ci                        continue
118bf215546Sopenharmony_ci                    self.reading_cmd = False
119bf215546Sopenharmony_ci                    self.reading_param = False
120bf215546Sopenharmony_ci
121bf215546Sopenharmony_ci                    cmd = self.cmd[0:self.cmdpos]
122bf215546Sopenharmony_ci                    param = self.param[0:self.parampos]
123bf215546Sopenharmony_ci                    self.reading_cmd = False
124bf215546Sopenharmony_ci                    self.reading_param = False
125bf215546Sopenharmony_ci
126bf215546Sopenharmony_ci                    parsed.append((cmd, param))
127bf215546Sopenharmony_ci                    ncmds -= 1
128bf215546Sopenharmony_ci                    if ncmds == 0:
129bf215546Sopenharmony_ci                        break
130bf215546Sopenharmony_ci                elif c == self.MSGSEP:
131bf215546Sopenharmony_ci                    if self.reading_cmd:
132bf215546Sopenharmony_ci                        self.reading_param = True
133bf215546Sopenharmony_ci                else:
134bf215546Sopenharmony_ci                    if self.reading_param:
135bf215546Sopenharmony_ci                        self.param[self.parampos] = c
136bf215546Sopenharmony_ci                        self.parampos += 1
137bf215546Sopenharmony_ci                    elif self.reading_cmd:
138bf215546Sopenharmony_ci                        self.cmd[self.cmdpos] = c
139bf215546Sopenharmony_ci                        self.cmdpos += 1
140bf215546Sopenharmony_ci
141bf215546Sopenharmony_ci            # if we read the entire buffer and didn't finish the command,
142bf215546Sopenharmony_ci            # throw it away
143bf215546Sopenharmony_ci            self.buffer = None
144bf215546Sopenharmony_ci
145bf215546Sopenharmony_ci            # check if we have time for another iteration
146bf215546Sopenharmony_ci            elapsed = time.monotonic() - now
147bf215546Sopenharmony_ci            remaining = max(0, remaining - elapsed)
148bf215546Sopenharmony_ci
149bf215546Sopenharmony_ci        # timeout
150bf215546Sopenharmony_ci        return parsed
151bf215546Sopenharmony_ci
152bf215546Sopenharmony_cidef control(args):
153bf215546Sopenharmony_ci    if args.socket:
154bf215546Sopenharmony_ci        address = '\0' + args.socket
155bf215546Sopenharmony_ci    else:
156bf215546Sopenharmony_ci        address = DEFAULT_SERVER_ADDRESS
157bf215546Sopenharmony_ci
158bf215546Sopenharmony_ci    conn = Connection(address)
159bf215546Sopenharmony_ci    msgparser = MsgParser(conn)
160bf215546Sopenharmony_ci
161bf215546Sopenharmony_ci    version = None
162bf215546Sopenharmony_ci    name = None
163bf215546Sopenharmony_ci    mesa_version = None
164bf215546Sopenharmony_ci
165bf215546Sopenharmony_ci    msgs = msgparser.readCmd(3)
166bf215546Sopenharmony_ci
167bf215546Sopenharmony_ci    for m in msgs:
168bf215546Sopenharmony_ci        cmd, param = m
169bf215546Sopenharmony_ci        if cmd == VERSION_HEADER:
170bf215546Sopenharmony_ci            version = int(param)
171bf215546Sopenharmony_ci        elif cmd == DEVICE_NAME_HEADER:
172bf215546Sopenharmony_ci            name = param.decode('utf-8')
173bf215546Sopenharmony_ci        elif cmd == MESA_VERSION_HEADER:
174bf215546Sopenharmony_ci            mesa_version = param.decode('utf-8')
175bf215546Sopenharmony_ci
176bf215546Sopenharmony_ci    if version != 1 or name == None or mesa_version == None:
177bf215546Sopenharmony_ci        print('ERROR: invalid protocol')
178bf215546Sopenharmony_ci        sys.exit(1)
179bf215546Sopenharmony_ci
180bf215546Sopenharmony_ci
181bf215546Sopenharmony_ci    if args.info:
182bf215546Sopenharmony_ci        info = "Protocol Version: {}\n"
183bf215546Sopenharmony_ci        info += "Device Name: {}\n"
184bf215546Sopenharmony_ci        info += "Mesa Version: {}"
185bf215546Sopenharmony_ci        print(info.format(version, name, mesa_version))
186bf215546Sopenharmony_ci
187bf215546Sopenharmony_ci    if args.cmd == 'start-capture':
188bf215546Sopenharmony_ci        conn.send(bytearray(':capture=1;', 'utf-8'))
189bf215546Sopenharmony_ci    elif args.cmd == 'stop-capture':
190bf215546Sopenharmony_ci        conn.send(bytearray(':capture=0;', 'utf-8'))
191bf215546Sopenharmony_ci
192bf215546Sopenharmony_ciif __name__ == '__main__':
193bf215546Sopenharmony_ci    parser = argparse.ArgumentParser(description='MESA_overlay control client')
194bf215546Sopenharmony_ci    parser.add_argument('--info', action='store_true', help='Print info from socket')
195bf215546Sopenharmony_ci    parser.add_argument('--socket', '-s', type=str, help='Path to socket')
196bf215546Sopenharmony_ci
197bf215546Sopenharmony_ci    commands = parser.add_subparsers(help='commands to run', dest='cmd')
198bf215546Sopenharmony_ci    commands.add_parser('start-capture')
199bf215546Sopenharmony_ci    commands.add_parser('stop-capture')
200bf215546Sopenharmony_ci
201bf215546Sopenharmony_ci    args = parser.parse_args()
202bf215546Sopenharmony_ci
203bf215546Sopenharmony_ci    control(args)
204