├── .gitignore ├── LICENSE ├── README.md ├── demo.py ├── setup.py ├── tests ├── __init__.py ├── data.py └── test_wayland.py └── wayland ├── __init__.py ├── client.py ├── protocol.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | __pycache__ 4 | /dist/ 5 | /build/ 6 | /*.egg-info 7 | /.eggs/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Stephen Early 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pure Python 3 Wayland protocol implementation 2 | ============================================= 3 | 4 | Very much a work in progress; do not use, the API is almost certainly 5 | going to change, there's no test suite and no documentation! 6 | 7 | Doesn't wrap libwayland, instead reads Wayland protocol description 8 | XML files and speaks the Wayland wire protocol directly. 9 | 10 | Requires python 3, because python 2 doesn't have 11 | ```socket.sendmsg()``` and ```socket.recvmsg()``` which are required 12 | for fd passing. 13 | 14 | See also 15 | ======== 16 | 17 | https://github.com/flacjacket/pywayland - I am not aiming for API 18 | compatibility with this because I expect the libraries to be used in 19 | different circumstances. Use this one if you want to use the Wayland 20 | protocol with as few external dependencies as possible, and if you 21 | want to keep control of your event loop: it should integrate well with 22 | async libraries like https://twistedmatrix.com/ 23 | 24 | pywayland is a more appropriate choice if you're integrating with 25 | other libraries that expect to see a C ```struct wl_display *``` 26 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import mmap 5 | import cairocffi as cairo 6 | import wayland.protocol 7 | from wayland.client import MakeDisplay 8 | from wayland.utils import AnonymousFile 9 | import math 10 | 11 | import select 12 | import time 13 | import logging 14 | 15 | # See https://github.com/sde1000/python-xkbcommon for the following: 16 | from xkbcommon import xkb 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | shutdowncode = None 21 | 22 | # List of future events; objects must support the nexttime attribute 23 | # and alarm() method. nexttime should be the time at which the object 24 | # next wants to be called, or None if the object temporarily does not 25 | # need to be scheduled. 26 | eventlist = [] 27 | 28 | # List of file descriptors to watch with handlers. Expected to be objects 29 | # with a fileno() method that returns the appropriate fd number, and methods 30 | # called doread(), dowrite(), etc. 31 | rdlist = [] 32 | 33 | # List of functions to invoke each time around the event loop. These 34 | # functions may do anything, including changing timeouts and drawing 35 | # on the display. 36 | ticklist = [] 37 | 38 | # List of functions to invoke before calling select. These functions 39 | # may not change timeouts or draw on the display. They will typically 40 | # flush queued output. 41 | preselectlist = [] 42 | 43 | class time_guard(object): 44 | def __init__(self, name, max_time): 45 | self._name = name 46 | self._max_time = max_time 47 | def __enter__(self): 48 | self._start_time = time.time() 49 | def __exit__(self, type, value, traceback): 50 | t = time.time() 51 | time_taken = t - self._start_time 52 | if time_taken > self._max_time: 53 | log.info("time_guard: %s took %f seconds",self._name,time_taken) 54 | 55 | tick_time_guard = time_guard("tick",0.5) 56 | preselect_time_guard = time_guard("preselect",0.1) 57 | doread_time_guard = time_guard("doread",0.5) 58 | dowrite_time_guard = time_guard("dowrite",0.5) 59 | doexcept_time_guard = time_guard("doexcept",0.5) 60 | alarm_time_guard = time_guard("alarm",0.5) 61 | 62 | def eventloop(): 63 | global shutdowncode 64 | while shutdowncode is None: 65 | for i in ticklist: 66 | with tick_time_guard: 67 | i() 68 | # Work out what the earliest timeout is 69 | timeout = None 70 | t = time.time() 71 | for i in eventlist: 72 | nt = i.nexttime 73 | i.mainloopnexttime = nt 74 | if nt is None: 75 | continue 76 | if timeout is None or (nt - t) < timeout: 77 | timeout = nt - t 78 | for i in preselectlist: 79 | with preselect_time_guard: 80 | i() 81 | try: 82 | (rd, wr, ex) = select.select(rdlist, [], [], timeout) 83 | except KeyboardInterrupt: 84 | (rd, wr, ex) = [], [], [] 85 | shutdowncode = 1 86 | for i in rd: 87 | with doread_time_guard: 88 | i.doread() 89 | for i in wr: 90 | with dowrite_time_guard: 91 | i.dowrite() 92 | for i in ex: 93 | with doexcept_time_guard: 94 | i.doexcept() 95 | # Process any events whose time has come 96 | t = time.time() 97 | for i in eventlist: 98 | if not hasattr(i, 'mainloopnexttime'): 99 | continue 100 | if i.mainloopnexttime and t >= i.mainloopnexttime: 101 | with alarm_time_guard: 102 | i.alarm() 103 | 104 | def ping_handler(thing, serial): 105 | """ 106 | Respond to a 'ping' with a 'pong'. 107 | """ 108 | thing.pong(serial) 109 | 110 | class Window: 111 | def __init__(self, connection, width, height, title="Window", 112 | class_="python-wayland-test", redraw=None, fullscreen=False): 113 | self.title = title 114 | self.orig_width = width 115 | self.orig_height = height 116 | self._w = connection 117 | if not self._w.shm_formats: 118 | raise RuntimeError("No suitable Shm formats available") 119 | self.is_fullscreen = fullscreen 120 | self.redraw_func = redraw 121 | self.surface = self._w.compositor.create_surface() 122 | self._w.surfaces[self.surface] = self 123 | self.xdg_surface = self._w.xdg_wm_base.get_xdg_surface(self.surface) 124 | self.xdg_toplevel = self.xdg_surface.get_toplevel() 125 | self.xdg_toplevel.set_title(title) 126 | self.xdg_toplevel.set_parent(None) 127 | self.xdg_toplevel.set_app_id(class_) 128 | self.xdg_toplevel.set_min_size(width, height) 129 | self.xdg_toplevel.set_max_size(width, height) 130 | 131 | if fullscreen: 132 | self.xdg_toplevel.set_fullscreen(None) 133 | 134 | self.wait_for_configure = True 135 | self.xdg_surface.dispatcher['ping'] = ping_handler 136 | self.xdg_surface.dispatcher['configure'] = \ 137 | self._xdg_surface_configure_handler 138 | 139 | #self.xdg_toplevel.dispatcher['configure'] = lambda *x: None 140 | #self.xdg_toplevel.dispatcher['close'] = lambda *x: None 141 | 142 | self.buffer = None 143 | self.shm_data = None 144 | self.surface.commit() 145 | 146 | def close(self): 147 | if not self.surface.destroyed: 148 | self.surface.destroy() 149 | if self.buffer is not None: 150 | self.buffer.destroy() 151 | self.buffer = None 152 | self.shm_data.close() 153 | del self.s, self.shm_data 154 | 155 | def resize(self, width, height): 156 | # Drop previous buffer and shm data if necessary 157 | if self.buffer: 158 | self.buffer.destroy() 159 | self.shm_data.close() 160 | 161 | # Do not complete a resize until configure has been acknowledged 162 | if self.wait_for_configure: 163 | return 164 | 165 | wl_shm_format, cairo_shm_format = self._w.shm_formats[0] 166 | 167 | stride = cairo.ImageSurface.format_stride_for_width( 168 | cairo_shm_format, width) 169 | size = stride * height 170 | 171 | with AnonymousFile(size) as fd: 172 | self.shm_data = mmap.mmap( 173 | fd, size, prot=mmap.PROT_READ | mmap.PROT_WRITE, 174 | flags=mmap.MAP_SHARED) 175 | pool = self._w.shm.create_pool(fd, size) 176 | self.buffer = pool.create_buffer( 177 | 0, width, height, stride, wl_shm_format) 178 | pool.destroy() 179 | self.s = cairo.ImageSurface(cairo_shm_format, width, height, 180 | data=self.shm_data, stride=stride) 181 | self.surface.attach(self.buffer, 0, 0) 182 | self.width = width 183 | self.height = height 184 | 185 | if self.redraw_func: 186 | # This should invoke `redraw` which then invokes `surface.commit` 187 | self.redraw_func(self) 188 | else: 189 | self.surface.commit() 190 | 191 | def redraw(self): 192 | """Copy the whole window surface to the display""" 193 | self.add_damage() 194 | self.surface.commit() 195 | 196 | def add_damage(self, x=0, y=0, width=None, height=None): 197 | if width is None: 198 | width = self.width 199 | if height is None: 200 | height = self.height 201 | self.surface.damage(x, y, width, height) 202 | 203 | def pointer_motion(self, seat, time, x, y): 204 | pass 205 | 206 | def _xdg_surface_configure_handler( 207 | self, the_xdg_surface, serial): 208 | the_xdg_surface.ack_configure(serial) 209 | 210 | self.wait_for_configure = False 211 | if not self.surface.destroyed: 212 | self.resize(self.orig_width, self.orig_height) 213 | 214 | class Seat: 215 | def __init__(self, obj, connection, global_name): 216 | self.c_enum = connection.interfaces['wl_seat'].enums['capability'] 217 | self.s = obj 218 | self._c = connection 219 | self.global_name = global_name 220 | self.name = None 221 | self.capabilities = 0 222 | self.pointer = None 223 | self.keyboard = None 224 | self.s.dispatcher['capabilities'] = self._capabilities 225 | self.s.dispatcher['name'] = self._name 226 | self.tabsym = xkb.keysym_from_name("Tab") 227 | 228 | def removed(self): 229 | if self.pointer: 230 | self.pointer.release() 231 | self.pointer = None 232 | if self.keyboard: 233 | self.keyboard.release() 234 | del self.keyboard_state 235 | self.keyboard = None 236 | # ...that's odd, there's no request in the protocol to destroy 237 | # the seat proxy! I suppose we just have to leave it lying 238 | # around. 239 | 240 | def _name(self, seat, name): 241 | print("Seat got name: {}".format(name)) 242 | self.name = name 243 | 244 | def _capabilities(self, seat, c): 245 | print("Seat {} got capabilities: {}".format(self.name, c)) 246 | self.capabilities = c 247 | pointer_available = c & self.c_enum['pointer'] 248 | if pointer_available and not self.pointer: 249 | self.pointer = self.s.get_pointer() 250 | self.pointer.dispatcher['enter'] = self.pointer_enter 251 | self.pointer.dispatcher['leave'] = self.pointer_leave 252 | self.pointer.dispatcher['motion'] = self.pointer_motion 253 | self.pointer.silence['motion'] = True 254 | self.pointer.dispatcher['button'] = self.pointer_button 255 | self.pointer.dispatcher['axis'] = self.pointer_axis 256 | self.current_pointer_window = None 257 | if self.pointer and not pointer_available: 258 | self.pointer.release() 259 | self.current_pointer_window = None 260 | self.pointer = None 261 | keyboard_available = c & self.c_enum['keyboard'] 262 | if keyboard_available and not self.keyboard: 263 | self.keyboard = self.s.get_keyboard() 264 | self.keyboard.dispatcher['keymap'] = self.keyboard_keymap 265 | self.keyboard.dispatcher['enter'] = self.keyboard_enter 266 | self.keyboard.dispatcher['leave'] = self.keyboard_leave 267 | self.keyboard.dispatcher['key'] = self.keyboard_key 268 | self.keyboard.dispatcher['modifiers'] = self.keyboard_modifiers 269 | self.current_keyboard_window = None 270 | if self.keyboard and not keyboard_available: 271 | self.keyboard.release() 272 | self.current_keyboard_window = None 273 | self.keyboard_state = None 274 | self.keyboard = None 275 | 276 | def pointer_enter(self, pointer, serial, surface, surface_x, surface_y): 277 | print("pointer_enter {} {} {} {}".format( 278 | serial, surface, surface_x, surface_y)) 279 | self.current_pointer_window = self._c.surfaces.get(surface, None) 280 | pointer.set_cursor(serial, None, 0, 0) 281 | 282 | def pointer_leave(self, pointer, serial, surface): 283 | print("pointer_leave {} {}".format(serial, surface)) 284 | self.current_pointer_window = None 285 | 286 | def pointer_motion(self, pointer, time, surface_x, surface_y): 287 | if not self.current_pointer_window: 288 | raise Exception("Pointer motion encountered even though there is not a matching window") 289 | self.current_pointer_window.pointer_motion( 290 | self, time, surface_x, surface_y) 291 | 292 | def pointer_button(self, pointer, serial, time, button, state): 293 | print("pointer_button {} {} {} {}".format(serial, time, button, state)) 294 | if state == 1 and self.current_pointer_window: 295 | print("Seat {} starting shell surface move".format(self.name)) 296 | self.current_pointer_window.xdg_toplevel.move(self.s, serial) 297 | 298 | def pointer_axis(self, pointer, time, axis, value): 299 | print("pointer_axis {} {} {}".format(time, axis, value)) 300 | 301 | def keyboard_keymap(self, keyboard, format_, fd, size): 302 | print("keyboard_keymap {} {} {}".format(format_, fd, size)) 303 | keymap_data = mmap.mmap( 304 | fd, size, prot=mmap.PROT_READ, flags=mmap.MAP_PRIVATE) 305 | os.close(fd) 306 | # The provided keymap appears to have a terminating NULL which 307 | # xkbcommon chokes on. Specify length=size-1 to remove it. 308 | keymap = self._c.xkb_context.keymap_new_from_buffer( 309 | keymap_data, length=size - 1) 310 | keymap_data.close() 311 | self.keyboard_state = keymap.state_new() 312 | 313 | def keyboard_enter(self, keyboard, serial, surface, keys): 314 | print("keyboard_enter {} {} {}".format(serial, surface, keys)) 315 | self.current_keyboard_window = self._c.surfaces.get(surface, None) 316 | 317 | def keyboard_leave(self, keyboard, serial, surface): 318 | print("keyboard_leave {} {}".format(serial, surface)) 319 | self.current_keyboard_window = None 320 | 321 | def keyboard_key(self, keyboard, serial, time, key, state): 322 | print("keyboard_key {} {} {} {}".format(serial, time, key, state)) 323 | sym = self.keyboard_state.key_get_one_sym(key + 8) 324 | if state == 1 and sym == self.tabsym: 325 | # Why did I put this in?! 326 | print("Saw a tab!") 327 | if state == 1: 328 | s = self.keyboard_state.key_get_string(key + 8) 329 | print("s={}".format(repr(s))) 330 | if s == "q": 331 | global shutdowncode 332 | shutdowncode = 0 333 | elif s == "c": 334 | # Close the window 335 | self.current_keyboard_window.close() 336 | elif s == "f": 337 | # Fullscreen toggle 338 | if self.current_keyboard_window.is_fullscreen: 339 | self.current_keyboard_window.xdg_toplevel.unset_fullscreen() 340 | self.current_keyboard_window.is_fullscreen = False 341 | self.current_keyboard_window.resize( 342 | self.current_keyboard_window.orig_width, 343 | self.current_keyboard_window.orig_height) 344 | else: 345 | self.current_keyboard_window.xdg_toplevel.set_fullscreen(None) 346 | self.current_keyboard_window.is_fullscreen = True 347 | 348 | def keyboard_modifiers(self, keyboard, serial, mods_depressed, 349 | mods_latched, mods_locked, group): 350 | print("keyboard_modifiers {} {} {} {} {}".format( 351 | serial, mods_depressed, mods_latched, mods_locked, group)) 352 | self.keyboard_state.update_mask(mods_depressed, mods_latched, 353 | mods_locked, group, 0, 0) 354 | 355 | class Output: 356 | def __init__(self, obj, connection, global_name): 357 | self.o = obj 358 | self._c = connection 359 | self.global_name = global_name 360 | self.o.dispatcher['geometry'] = self._geometry 361 | self.o.dispatcher['mode'] = self._mode 362 | self.o.dispatcher['done'] = self._done 363 | 364 | def _geometry(self, output, x, y, phy_width, phy_height, subpixel, 365 | make, model, transform): 366 | print("Ouput: got geometry: x={}, y={}, phy_width={}, phy_height={}," 367 | "make={}, model={}".format(x, y, phy_width, phy_height, 368 | make, model)) 369 | 370 | def _mode(self, output, flags, width, height, refresh): 371 | print("Output: got mode: flags={}, width={}, height={}, refresh={}" \ 372 | .format(flags, width, height, refresh)) 373 | 374 | def _done(self, output): 375 | print("Output: done for now") 376 | 377 | class WaylandConnection: 378 | def __init__(self, wp_base, *other_wps): 379 | self.wps = (wp_base,) + other_wps 380 | self.interfaces = {} 381 | for wp in self.wps: 382 | for k,v in wp.interfaces.items(): 383 | self.interfaces[k] = v 384 | 385 | # Create the Display proxy class from the protocol 386 | Display = MakeDisplay(wp_base) 387 | self.display = Display() 388 | 389 | self.registry = self.display.get_registry() 390 | self.registry.dispatcher['global'] = self.registry_global_handler 391 | self.registry.dispatcher['global_remove'] = \ 392 | self.registry_global_remove_handler 393 | 394 | self.xkb_context = xkb.Context() 395 | 396 | # Dictionary mapping surface proxies to Window objects 397 | self.surfaces = {} 398 | 399 | self.compositor = None 400 | self.xdg_wm_base = None 401 | self.shm = None 402 | self.shm_formats = [] 403 | self.seats = [] 404 | self.outputs = [] 405 | 406 | # Bind to the globals that we're interested in. NB we won't 407 | # pick up things like shm_formats at this point; after we bind 408 | # to wl_shm we need another roundtrip before we can be sure to 409 | # have received them. 410 | self.display.roundtrip() 411 | 412 | if not self.compositor: 413 | raise RuntimeError("Compositor not found") 414 | if not self.xdg_wm_base: 415 | raise RuntimeError("xdg_wm_base not found") 416 | if not self.shm: 417 | raise RuntimeError("Shm not found") 418 | 419 | # Pick up shm formats 420 | self.display.roundtrip() 421 | 422 | rdlist.append(self) 423 | preselectlist.append(self._preselect) 424 | 425 | def fileno(self): 426 | return self.display.get_fd() 427 | 428 | def disconnect(self): 429 | self.display.disconnect() 430 | 431 | def doread(self): 432 | self.display.recv() 433 | self.display.dispatch_pending() 434 | 435 | def _preselect(self): 436 | self.display.flush() 437 | 438 | def registry_global_handler(self, registry, name, interface, version): 439 | print("registry_global_handler: {} is {} v{}".format( 440 | name, interface, version)) 441 | if interface == "wl_compositor": 442 | # We know up to and require version 3 443 | self.compositor = registry.bind( 444 | name, self.interfaces['wl_compositor'], 3) 445 | elif interface == "xdg_wm_base": 446 | # We know up to and require version 1 447 | self.xdg_wm_base = registry.bind( 448 | name, self.interfaces['xdg_wm_base'], 1) 449 | elif interface == "wl_shm": 450 | # We know up to and require version 1 451 | self.shm = registry.bind( 452 | name, self.interfaces['wl_shm'], 1) 453 | self.shm.dispatcher['format'] = self.shm_format_handler 454 | elif interface == "wl_seat": 455 | # We know up to and require version 4 456 | self.seats.append(Seat(registry.bind( 457 | name, self.interfaces['wl_seat'], 4), self, name)) 458 | elif interface == "wl_output": 459 | # We know up to and require version 2 460 | self.outputs.append(Output(registry.bind( 461 | name, self.interfaces['wl_output'], 2), self, name)) 462 | 463 | def registry_global_remove_handler(self, registry, name): 464 | # Haven't been able to get weston to send this event! 465 | print("registry_global_remove_handler: {} gone".format(name)) 466 | for s in self.seats: 467 | if s.global_name == name: 468 | print("...it was a seat! Releasing seat resources.") 469 | s.removed() 470 | 471 | def shm_format_handler(self, shm, format_): 472 | f = shm.interface.enums['format'] 473 | if format_ == f.entries['argb8888'].value: 474 | self.shm_formats.append((format_, cairo.FORMAT_ARGB32)) 475 | elif format_ == f.entries['xrgb8888'].value: 476 | self.shm_formats.append((format_, cairo.FORMAT_RGB24)) 477 | elif format_ == f.entries['rgb565'].value: 478 | self.shm_formats.append((format_, cairo.FORMAT_RGB16_565)) 479 | 480 | def draw_in_window(w): 481 | ctx = cairo.Context(w.s) 482 | ctx.set_source_rgba(0,0,0,0) 483 | ctx.set_operator(cairo.OPERATOR_SOURCE) 484 | ctx.paint() 485 | ctx.set_operator(cairo.OPERATOR_OVER) 486 | ctx.scale(w.width, w.height) 487 | pat = cairo.LinearGradient(0.0, 0.0, 0.0, 1.0) 488 | pat.add_color_stop_rgba(1, 0.7, 0, 0, 0.5) 489 | pat.add_color_stop_rgba(0, 0.9, 0.7, 0.2, 1) 490 | 491 | ctx.rectangle(0, 0, 1, 1) 492 | ctx.set_source(pat) 493 | ctx.fill() 494 | 495 | del pat 496 | 497 | ctx.translate(0.1, 0.1) 498 | 499 | ctx.move_to(0, 0) 500 | ctx.arc(0.2, 0.1, 0.1, -math.pi/2, 0) 501 | ctx.line_to(0.5, 0.1) 502 | ctx.curve_to(0.5, 0.2, 0.5, 0.4, 0.2, 0.8) 503 | ctx.close_path() 504 | 505 | ctx.set_source_rgb(0.3, 0.2, 0.5) 506 | ctx.set_line_width(0.02) 507 | ctx.stroke() 508 | 509 | ctx.select_font_face("monospace") 510 | ctx.set_font_size(0.05) 511 | ctx.set_source_rgb(1.0, 1.0, 1.0) 512 | ctx.move_to(0.2, 0.2) 513 | ctx.show_text("{} {} x {}".format(w.title, w.width, w.height)) 514 | 515 | del ctx 516 | 517 | w.s.flush() 518 | w.redraw() 519 | 520 | if __name__ == "__main__": 521 | logging.basicConfig(level=logging.INFO) 522 | 523 | # Load the main Wayland protocol. 524 | wp_base = wayland.protocol.Protocol("/usr/share/wayland/wayland.xml") 525 | wp_xdg_shell = wayland.protocol.Protocol("/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") 526 | 527 | try: 528 | conn = WaylandConnection(wp_base, wp_xdg_shell) 529 | except FileNotFoundError as e: 530 | if e.errno == 2: 531 | print("Unable to connect to the compositor - " 532 | "is one running?") 533 | sys.exit(1) 534 | raise 535 | w1 = Window(conn, 640, 480, title="Window 1", redraw=draw_in_window) 536 | w2 = Window(conn, 320, 240, title="Window 2", redraw=draw_in_window) 537 | w3 = Window(conn, 160, 120, title="Window 3", redraw=draw_in_window) 538 | 539 | eventloop() 540 | 541 | w1.close() 542 | w2.close() 543 | w3.close() 544 | 545 | conn.display.roundtrip() 546 | conn.disconnect() 547 | print("About to exit with code {}".format(shutdowncode)) 548 | 549 | logging.shutdown() 550 | sys.exit(shutdowncode) 551 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def readme(): 4 | with open('README.md') as f: 5 | return f.read() 6 | 7 | setup(name='wayland', 8 | version='0.1', 9 | description='Wayland protocol implementation', 10 | long_description=readme(), 11 | classifiers=[ 12 | 'Development Status :: 3 - Alpha', 13 | 'License :: OSI Approved :: MIT License', 14 | 'Programming Language :: Python :: 3.5', 15 | 'Topic :: Software Development :: Libraries', 16 | 'Intended Audience :: Developers', 17 | ], 18 | url='https://github.com/sde1000/python-wayland', 19 | author='Stephen Early', 20 | author_email='steve@assorted.org.uk', 21 | license='MIT', 22 | packages=['wayland'], 23 | zip_safe=True, 24 | test_suite='tests.test_wayland', 25 | ) 26 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sde1000/python-wayland/b017596de0d550aabfc24a26554a22e43ceb7861/tests/__init__.py -------------------------------------------------------------------------------- /tests/data.py: -------------------------------------------------------------------------------- 1 | # This is wayland.xml from Wayland 1.9.0. 2 | sample_protocol = r""" 3 | 4 | 5 | 6 | Copyright © 2008-2011 Kristian Høgsberg 7 | Copyright © 2010-2011 Intel Corporation 8 | Copyright © 2012-2013 Collabora, Ltd. 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation files 12 | (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, 14 | publish, distribute, sublicense, and/or sell copies of the Software, 15 | and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice (including the 19 | next paragraph) shall be included in all copies or substantial 20 | portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 26 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 27 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 28 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | 32 | 33 | 34 | The core global object. This is a special singleton object. It 35 | is used for internal Wayland protocol features. 36 | 37 | 38 | 39 | 40 | The sync request asks the server to emit the 'done' event 41 | on the returned wl_callback object. Since requests are 42 | handled in-order and events are delivered in-order, this can 43 | be used as a barrier to ensure all previous requests and the 44 | resulting events have been handled. 45 | 46 | The object returned by this request will be destroyed by the 47 | compositor after the callback is fired and as such the client must not 48 | attempt to use it after that point. 49 | 50 | The callback_data passed in the callback is the event serial. 51 | 52 | 53 | 54 | 55 | 56 | 57 | This request creates a registry object that allows the client 58 | to list and bind the global objects available from the 59 | compositor. 60 | 61 | 62 | 63 | 64 | 65 | 66 | The error event is sent out when a fatal (non-recoverable) 67 | error has occurred. The object_id argument is the object 68 | where the error occurred, most often in response to a request 69 | to that object. The code identifies the error and is defined 70 | by the object interface. As such, each interface defines its 71 | own set of error codes. The message is an brief description 72 | of the error, for (debugging) convenience. 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | These errors are global and can be emitted in response to any 82 | server request. 83 | 84 | 86 | 88 | 90 | 91 | 92 | 93 | 94 | This event is used internally by the object ID management 95 | logic. When a client deletes an object, the server will send 96 | this event to acknowledge that it has seen the delete request. 97 | When the client receive this event, it will know that it can 98 | safely reuse the object ID. 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | The global registry object. The server has a number of global 107 | objects that are available to all clients. These objects 108 | typically represent an actual object in the server (for example, 109 | an input device) or they are singleton objects that provide 110 | extension functionality. 111 | 112 | When a client creates a registry object, the registry object 113 | will emit a global event for each global currently in the 114 | registry. Globals come and go as a result of device or 115 | monitor hotplugs, reconfiguration or other events, and the 116 | registry will send out global and global_remove events to 117 | keep the client up to date with the changes. To mark the end 118 | of the initial burst of events, the client can use the 119 | wl_display.sync request immediately after calling 120 | wl_display.get_registry. 121 | 122 | A client can bind to a global object by using the bind 123 | request. This creates a client-side handle that lets the object 124 | emit events to the client and lets the client invoke requests on 125 | the object. 126 | 127 | 128 | 129 | 130 | Binds a new, client-created object to the server using the 131 | specified name as the identifier. 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | Notify the client of global objects. 140 | 141 | The event notifies the client that a global object with 142 | the given name is now available, and it implements the 143 | given version of the given interface. 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Notify the client of removed global objects. 153 | 154 | This event notifies the client that the global identified 155 | by name is no longer available. If the client bound to 156 | the global using the bind request, the client should now 157 | destroy that object. 158 | 159 | The object remains valid and requests to the object will be 160 | ignored until the client destroys it, to avoid races between 161 | the global going away and a client sending a request to it. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | Clients can handle the 'done' event to get notified when 170 | the related request is done. 171 | 172 | 173 | 174 | Notify the client when the related request is done. 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | A compositor. This object is a singleton global. The 183 | compositor is in charge of combining the contents of multiple 184 | surfaces into one displayable output. 185 | 186 | 187 | 188 | 189 | Ask the compositor to create a new surface. 190 | 191 | 192 | 193 | 194 | 195 | 196 | Ask the compositor to create a new region. 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | The wl_shm_pool object encapsulates a piece of memory shared 205 | between the compositor and client. Through the wl_shm_pool 206 | object, the client can allocate shared memory wl_buffer objects. 207 | All objects created through the same pool share the same 208 | underlying mapped memory. Reusing the mapped memory avoids the 209 | setup/teardown overhead and is useful when interactively resizing 210 | a surface or for many small buffers. 211 | 212 | 213 | 214 | 215 | Create a wl_buffer object from the pool. 216 | 217 | The buffer is created offset bytes into the pool and has 218 | width and height as specified. The stride arguments specifies 219 | the number of bytes from beginning of one row to the beginning 220 | of the next. The format is the pixel format of the buffer and 221 | must be one of those advertised through the wl_shm.format event. 222 | 223 | A buffer will keep a reference to the pool it was created from 224 | so it is valid to destroy the pool immediately after creating 225 | a buffer from it. 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | Destroy the shared memory pool. 239 | 240 | The mmapped memory will be released when all 241 | buffers that have been created from this pool 242 | are gone. 243 | 244 | 245 | 246 | 247 | 248 | This request will cause the server to remap the backing memory 249 | for the pool from the file descriptor passed when the pool was 250 | created, but using the new size. This request can only be 251 | used to make the pool bigger. 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | A global singleton object that provides support for shared 261 | memory. 262 | 263 | Clients can create wl_shm_pool objects using the create_pool 264 | request. 265 | 266 | At connection setup time, the wl_shm object emits one or more 267 | format events to inform clients about the valid pixel formats 268 | that can be used for buffers. 269 | 270 | 271 | 272 | 273 | These errors can be emitted in response to wl_shm requests. 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | This describes the memory layout of an individual pixel. 283 | 284 | All renderers should support argb8888 and xrgb8888 but any other 285 | formats are optional and may not be supported by the particular 286 | renderer in use. 287 | 288 | 289 | 290 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | Create a new wl_shm_pool object. 354 | 355 | The pool can be used to create shared memory based buffer 356 | objects. The server will mmap size bytes of the passed file 357 | descriptor, to use as backing memory for the pool. 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | Informs the client about a valid pixel format that 368 | can be used for buffers. Known formats include 369 | argb8888 and xrgb8888. 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | A buffer provides the content for a wl_surface. Buffers are 378 | created through factory interfaces such as wl_drm, wl_shm or 379 | similar. It has a width and a height and can be attached to a 380 | wl_surface, but the mechanism by which a client provides and 381 | updates the contents is defined by the buffer factory interface. 382 | 383 | 384 | 385 | 386 | Destroy a buffer. If and how you need to release the backing 387 | storage is defined by the buffer factory interface. 388 | 389 | For possible side-effects to a surface, see wl_surface.attach. 390 | 391 | 392 | 393 | 394 | 395 | Sent when this wl_buffer is no longer used by the compositor. 396 | The client is now free to re-use or destroy this buffer and its 397 | backing storage. 398 | 399 | If a client receives a release event before the frame callback 400 | requested in the same wl_surface.commit that attaches this 401 | wl_buffer to a surface, then the client is immediately free to 402 | re-use the buffer and its backing storage, and does not need a 403 | second buffer for the next surface content update. Typically 404 | this is possible, when the compositor maintains a copy of the 405 | wl_surface contents, e.g. as a GL texture. This is an important 406 | optimization for GL(ES) compositors with wl_shm clients. 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | A wl_data_offer represents a piece of data offered for transfer 415 | by another client (the source client). It is used by the 416 | copy-and-paste and drag-and-drop mechanisms. The offer 417 | describes the different mime types that the data can be 418 | converted to and provides the mechanism for transferring the 419 | data directly from the source client. 420 | 421 | 422 | 423 | 424 | Indicate that the client can accept the given mime type, or 425 | NULL for not accepted. 426 | 427 | Used for feedback during drag-and-drop. 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | To transfer the offered data, the client issues this request 437 | and indicates the mime type it wants to receive. The transfer 438 | happens through the passed file descriptor (typically created 439 | with the pipe system call). The source client writes the data 440 | in the mime type representation requested and then closes the 441 | file descriptor. 442 | 443 | The receiving client reads from the read end of the pipe until 444 | EOF and then closes its end, at which point the transfer is 445 | complete. 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | Destroy the data offer. 454 | 455 | 456 | 457 | 458 | 459 | Sent immediately after creating the wl_data_offer object. One 460 | event per offered mime type. 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | The wl_data_source object is the source side of a wl_data_offer. 470 | It is created by the source client in a data transfer and 471 | provides a way to describe the offered data and a way to respond 472 | to requests to transfer the data. 473 | 474 | 475 | 476 | 477 | This request adds a mime type to the set of mime types 478 | advertised to targets. Can be called several times to offer 479 | multiple types. 480 | 481 | 482 | 483 | 484 | 485 | 486 | Destroy the data source. 487 | 488 | 489 | 490 | 491 | 492 | Sent when a target accepts pointer_focus or motion events. If 493 | a target does not accept any of the offered types, type is NULL. 494 | 495 | Used for feedback during drag-and-drop. 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | Request for data from the client. Send the data as the 504 | specified mime type over the passed file descriptor, then 505 | close it. 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | This data source has been replaced by another data source. 515 | The client should clean up and destroy this data source. 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | There is one wl_data_device per seat which can be obtained 524 | from the global wl_data_device_manager singleton. 525 | 526 | A wl_data_device provides access to inter-client data transfer 527 | mechanisms such as copy-and-paste and drag-and-drop. 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | This request asks the compositor to start a drag-and-drop 537 | operation on behalf of the client. 538 | 539 | The source argument is the data source that provides the data 540 | for the eventual data transfer. If source is NULL, enter, leave 541 | and motion events are sent only to the client that initiated the 542 | drag and the client is expected to handle the data passing 543 | internally. 544 | 545 | The origin surface is the surface where the drag originates and 546 | the client must have an active implicit grab that matches the 547 | serial. 548 | 549 | The icon surface is an optional (can be NULL) surface that 550 | provides an icon to be moved around with the cursor. Initially, 551 | the top-left corner of the icon surface is placed at the cursor 552 | hotspot, but subsequent wl_surface.attach request can move the 553 | relative position. Attach requests must be confirmed with 554 | wl_surface.commit as usual. The icon surface is given the role of 555 | a drag-and-drop icon. If the icon surface already has another role, 556 | it raises a protocol error. 557 | 558 | The current and pending input regions of the icon wl_surface are 559 | cleared, and wl_surface.set_input_region is ignored until the 560 | wl_surface is no longer used as the icon surface. When the use 561 | as an icon ends, the current and pending input regions become 562 | undefined, and the wl_surface is unmapped. 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | This request asks the compositor to set the selection 573 | to the data from the source on behalf of the client. 574 | 575 | To unset the selection, set the source to NULL. 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | The data_offer event introduces a new wl_data_offer object, 584 | which will subsequently be used in either the 585 | data_device.enter event (for drag-and-drop) or the 586 | data_device.selection event (for selections). Immediately 587 | following the data_device_data_offer event, the new data_offer 588 | object will send out data_offer.offer events to describe the 589 | mime types it offers. 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | This event is sent when an active drag-and-drop pointer enters 598 | a surface owned by the client. The position of the pointer at 599 | enter time is provided by the x and y arguments, in surface 600 | local coordinates. 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | This event is sent when the drag-and-drop pointer leaves the 613 | surface and the session ends. The client must destroy the 614 | wl_data_offer introduced at enter time at this point. 615 | 616 | 617 | 618 | 619 | 620 | This event is sent when the drag-and-drop pointer moves within 621 | the currently focused surface. The new position of the pointer 622 | is provided by the x and y arguments, in surface local 623 | coordinates. 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | The event is sent when a drag-and-drop operation is ended 633 | because the implicit grab is removed. 634 | 635 | 636 | 637 | 638 | 639 | The selection event is sent out to notify the client of a new 640 | wl_data_offer for the selection for this device. The 641 | data_device.data_offer and the data_offer.offer events are 642 | sent out immediately before this event to introduce the data 643 | offer object. The selection event is sent to a client 644 | immediately before receiving keyboard focus and when a new 645 | selection is set while the client has keyboard focus. The 646 | data_offer is valid until a new data_offer or NULL is received 647 | or until the client loses keyboard focus. The client must 648 | destroy the previous selection data_offer, if any, upon receiving 649 | this event. 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | This request destroys the data device. 659 | 660 | 661 | 662 | 663 | 664 | 665 | The wl_data_device_manager is a singleton global object that 666 | provides access to inter-client data transfer mechanisms such as 667 | copy-and-paste and drag-and-drop. These mechanisms are tied to 668 | a wl_seat and this interface lets a client get a wl_data_device 669 | corresponding to a wl_seat. 670 | 671 | 672 | 673 | 674 | Create a new data source. 675 | 676 | 677 | 678 | 679 | 680 | 681 | Create a new data device for a given seat. 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | This interface is implemented by servers that provide 691 | desktop-style user interfaces. 692 | 693 | It allows clients to associate a wl_shell_surface with 694 | a basic surface. 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | Create a shell surface for an existing surface. This gives 704 | the wl_surface the role of a shell surface. If the wl_surface 705 | already has another role, it raises a protocol error. 706 | 707 | Only one shell surface can be associated with a given surface. 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | An interface that may be implemented by a wl_surface, for 718 | implementations that provide a desktop-style user interface. 719 | 720 | It provides requests to treat surfaces like toplevel, fullscreen 721 | or popup windows, move, resize or maximize them, associate 722 | metadata like title and class, etc. 723 | 724 | On the server side the object is automatically destroyed when 725 | the related wl_surface is destroyed. On client side, 726 | wl_shell_surface_destroy() must be called before destroying 727 | the wl_surface object. 728 | 729 | 730 | 731 | 732 | A client must respond to a ping event with a pong request or 733 | the client may be deemed unresponsive. 734 | 735 | 736 | 737 | 738 | 739 | 740 | Start a pointer-driven move of the surface. 741 | 742 | This request must be used in response to a button press event. 743 | The server may ignore move requests depending on the state of 744 | the surface (e.g. fullscreen or maximized). 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | These values are used to indicate which edge of a surface 753 | is being dragged in a resize operation. The server may 754 | use this information to adapt its behavior, e.g. choose 755 | an appropriate cursor image. 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | Start a pointer-driven resizing of the surface. 771 | 772 | This request must be used in response to a button press event. 773 | The server may ignore resize requests depending on the state of 774 | the surface (e.g. fullscreen or maximized). 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | Map the surface as a toplevel surface. 784 | 785 | A toplevel surface is not fullscreen, maximized or transient. 786 | 787 | 788 | 789 | 790 | 791 | These flags specify details of the expected behaviour 792 | of transient surfaces. Used in the set_transient request. 793 | 794 | 795 | 796 | 797 | 798 | 799 | Map the surface relative to an existing surface. 800 | 801 | The x and y arguments specify the locations of the upper left 802 | corner of the surface relative to the upper left corner of the 803 | parent surface, in surface local coordinates. 804 | 805 | The flags argument controls details of the transient behaviour. 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | Hints to indicate to the compositor how to deal with a conflict 817 | between the dimensions of the surface and the dimensions of the 818 | output. The compositor is free to ignore this parameter. 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | Map the surface as a fullscreen surface. 829 | 830 | If an output parameter is given then the surface will be made 831 | fullscreen on that output. If the client does not specify the 832 | output then the compositor will apply its policy - usually 833 | choosing the output on which the surface has the biggest surface 834 | area. 835 | 836 | The client may specify a method to resolve a size conflict 837 | between the output size and the surface size - this is provided 838 | through the method parameter. 839 | 840 | The framerate parameter is used only when the method is set 841 | to "driver", to indicate the preferred framerate. A value of 0 842 | indicates that the app does not care about framerate. The 843 | framerate is specified in mHz, that is framerate of 60000 is 60Hz. 844 | 845 | A method of "scale" or "driver" implies a scaling operation of 846 | the surface, either via a direct scaling operation or a change of 847 | the output mode. This will override any kind of output scaling, so 848 | that mapping a surface with a buffer size equal to the mode can 849 | fill the screen independent of buffer_scale. 850 | 851 | A method of "fill" means we don't scale up the buffer, however 852 | any output scale is applied. This means that you may run into 853 | an edge case where the application maps a buffer with the same 854 | size of the output mode but buffer_scale 1 (thus making a 855 | surface larger than the output). In this case it is allowed to 856 | downscale the results to fit the screen. 857 | 858 | The compositor must reply to this request with a configure event 859 | with the dimensions for the output on which the surface will 860 | be made fullscreen. 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | Map the surface as a popup. 870 | 871 | A popup surface is a transient surface with an added pointer 872 | grab. 873 | 874 | An existing implicit grab will be changed to owner-events mode, 875 | and the popup grab will continue after the implicit grab ends 876 | (i.e. releasing the mouse button does not cause the popup to 877 | be unmapped). 878 | 879 | The popup grab continues until the window is destroyed or a 880 | mouse button is pressed in any other clients window. A click 881 | in any of the clients surfaces is reported as normal, however, 882 | clicks in other clients surfaces will be discarded and trigger 883 | the callback. 884 | 885 | The x and y arguments specify the locations of the upper left 886 | corner of the surface relative to the upper left corner of the 887 | parent surface, in surface local coordinates. 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | Map the surface as a maximized surface. 901 | 902 | If an output parameter is given then the surface will be 903 | maximized on that output. If the client does not specify the 904 | output then the compositor will apply its policy - usually 905 | choosing the output on which the surface has the biggest surface 906 | area. 907 | 908 | The compositor will reply with a configure event telling 909 | the expected new surface size. The operation is completed 910 | on the next buffer attach to this surface. 911 | 912 | A maximized surface typically fills the entire output it is 913 | bound to, except for desktop element such as panels. This is 914 | the main difference between a maximized shell surface and a 915 | fullscreen shell surface. 916 | 917 | The details depend on the compositor implementation. 918 | 919 | 920 | 921 | 922 | 923 | 924 | Set a short title for the surface. 925 | 926 | This string may be used to identify the surface in a task bar, 927 | window list, or other user interface elements provided by the 928 | compositor. 929 | 930 | The string must be encoded in UTF-8. 931 | 932 | 933 | 934 | 935 | 936 | 937 | Set a class for the surface. 938 | 939 | The surface class identifies the general class of applications 940 | to which the surface belongs. A common convention is to use the 941 | file name (or the full path if it is a non-standard location) of 942 | the application's .desktop file as the class. 943 | 944 | 945 | 946 | 947 | 948 | 949 | Ping a client to check if it is receiving events and sending 950 | requests. A client is expected to reply with a pong request. 951 | 952 | 953 | 954 | 955 | 956 | 957 | The configure event asks the client to resize its surface. 958 | 959 | The size is a hint, in the sense that the client is free to 960 | ignore it if it doesn't resize, pick a smaller size (to 961 | satisfy aspect ratio or resize in steps of NxM pixels). 962 | 963 | The edges parameter provides a hint about how the surface 964 | was resized. The client may use this information to decide 965 | how to adjust its content to the new size (e.g. a scrolling 966 | area might adjust its content position to leave the viewable 967 | content unmoved). 968 | 969 | The client is free to dismiss all but the last configure 970 | event it received. 971 | 972 | The width and height arguments specify the size of the window 973 | in surface local coordinates. 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | The popup_done event is sent out when a popup grab is broken, 984 | that is, when the user clicks a surface that doesn't belong 985 | to the client owning the popup surface. 986 | 987 | 988 | 989 | 990 | 991 | 992 | A surface is a rectangular area that is displayed on the screen. 993 | It has a location, size and pixel contents. 994 | 995 | The size of a surface (and relative positions on it) is described 996 | in surface local coordinates, which may differ from the buffer 997 | local coordinates of the pixel content, in case a buffer_transform 998 | or a buffer_scale is used. 999 | 1000 | A surface without a "role" is fairly useless, a compositor does 1001 | not know where, when or how to present it. The role is the 1002 | purpose of a wl_surface. Examples of roles are a cursor for a 1003 | pointer (as set by wl_pointer.set_cursor), a drag icon 1004 | (wl_data_device.start_drag), a sub-surface 1005 | (wl_subcompositor.get_subsurface), and a window as defined by a 1006 | shell protocol (e.g. wl_shell.get_shell_surface). 1007 | 1008 | A surface can have only one role at a time. Initially a 1009 | wl_surface does not have a role. Once a wl_surface is given a 1010 | role, it is set permanently for the whole lifetime of the 1011 | wl_surface object. Giving the current role again is allowed, 1012 | unless explicitly forbidden by the relevant interface 1013 | specification. 1014 | 1015 | Surface roles are given by requests in other interfaces such as 1016 | wl_pointer.set_cursor. The request should explicitly mention 1017 | that this request gives a role to a wl_surface. Often, this 1018 | request also creates a new protocol object that represents the 1019 | role and adds additional functionality to wl_surface. When a 1020 | client wants to destroy a wl_surface, they must destroy this 'role 1021 | object' before the wl_surface. 1022 | 1023 | Destroying the role object does not remove the role from the 1024 | wl_surface, but it may stop the wl_surface from "playing the role". 1025 | For instance, if a wl_subsurface object is destroyed, the wl_surface 1026 | it was created for will be unmapped and forget its position and 1027 | z-order. It is allowed to create a wl_subsurface for the same 1028 | wl_surface again, but it is not allowed to use the wl_surface as 1029 | a cursor (cursor is a different role than sub-surface, and role 1030 | switching is not allowed). 1031 | 1032 | 1033 | 1034 | 1035 | These errors can be emitted in response to wl_surface requests. 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | Deletes the surface and invalidates its object ID. 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | Set a buffer as the content of this surface. 1050 | 1051 | The new size of the surface is calculated based on the buffer 1052 | size transformed by the inverse buffer_transform and the 1053 | inverse buffer_scale. This means that the supplied buffer 1054 | must be an integer multiple of the buffer_scale. 1055 | 1056 | The x and y arguments specify the location of the new pending 1057 | buffer's upper left corner, relative to the current buffer's upper 1058 | left corner, in surface local coordinates. In other words, the 1059 | x and y, combined with the new surface size define in which 1060 | directions the surface's size changes. 1061 | 1062 | Surface contents are double-buffered state, see wl_surface.commit. 1063 | 1064 | The initial surface contents are void; there is no content. 1065 | wl_surface.attach assigns the given wl_buffer as the pending 1066 | wl_buffer. wl_surface.commit makes the pending wl_buffer the new 1067 | surface contents, and the size of the surface becomes the size 1068 | calculated from the wl_buffer, as described above. After commit, 1069 | there is no pending buffer until the next attach. 1070 | 1071 | Committing a pending wl_buffer allows the compositor to read the 1072 | pixels in the wl_buffer. The compositor may access the pixels at 1073 | any time after the wl_surface.commit request. When the compositor 1074 | will not access the pixels anymore, it will send the 1075 | wl_buffer.release event. Only after receiving wl_buffer.release, 1076 | the client may re-use the wl_buffer. A wl_buffer that has been 1077 | attached and then replaced by another attach instead of committed 1078 | will not receive a release event, and is not used by the 1079 | compositor. 1080 | 1081 | Destroying the wl_buffer after wl_buffer.release does not change 1082 | the surface contents. However, if the client destroys the 1083 | wl_buffer before receiving the wl_buffer.release event, the surface 1084 | contents become undefined immediately. 1085 | 1086 | If wl_surface.attach is sent with a NULL wl_buffer, the 1087 | following wl_surface.commit will remove the surface content. 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | This request is used to describe the regions where the pending 1098 | buffer is different from the current surface contents, and where 1099 | the surface therefore needs to be repainted. The pending buffer 1100 | must be set by wl_surface.attach before sending damage. The 1101 | compositor ignores the parts of the damage that fall outside of 1102 | the surface. 1103 | 1104 | Damage is double-buffered state, see wl_surface.commit. 1105 | 1106 | The damage rectangle is specified in surface local coordinates. 1107 | 1108 | The initial value for pending damage is empty: no damage. 1109 | wl_surface.damage adds pending damage: the new pending damage 1110 | is the union of old pending damage and the given rectangle. 1111 | 1112 | wl_surface.commit assigns pending damage as the current damage, 1113 | and clears pending damage. The server will clear the current 1114 | damage as it repaints the surface. 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | Request a notification when it is a good time start drawing a new 1126 | frame, by creating a frame callback. This is useful for throttling 1127 | redrawing operations, and driving animations. 1128 | 1129 | When a client is animating on a wl_surface, it can use the 'frame' 1130 | request to get notified when it is a good time to draw and commit the 1131 | next frame of animation. If the client commits an update earlier than 1132 | that, it is likely that some updates will not make it to the display, 1133 | and the client is wasting resources by drawing too often. 1134 | 1135 | The frame request will take effect on the next wl_surface.commit. 1136 | The notification will only be posted for one frame unless 1137 | requested again. For a wl_surface, the notifications are posted in 1138 | the order the frame requests were committed. 1139 | 1140 | The server must send the notifications so that a client 1141 | will not send excessive updates, while still allowing 1142 | the highest possible update rate for clients that wait for the reply 1143 | before drawing again. The server should give some time for the client 1144 | to draw and commit after sending the frame callback events to let them 1145 | hit the next output refresh. 1146 | 1147 | A server should avoid signalling the frame callbacks if the 1148 | surface is not visible in any way, e.g. the surface is off-screen, 1149 | or completely obscured by other opaque surfaces. 1150 | 1151 | The object returned by this request will be destroyed by the 1152 | compositor after the callback is fired and as such the client must not 1153 | attempt to use it after that point. 1154 | 1155 | The callback_data passed in the callback is the current time, in 1156 | milliseconds, with an undefined base. 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | This request sets the region of the surface that contains 1165 | opaque content. 1166 | 1167 | The opaque region is an optimization hint for the compositor 1168 | that lets it optimize out redrawing of content behind opaque 1169 | regions. Setting an opaque region is not required for correct 1170 | behaviour, but marking transparent content as opaque will result 1171 | in repaint artifacts. 1172 | 1173 | The opaque region is specified in surface local coordinates. 1174 | 1175 | The compositor ignores the parts of the opaque region that fall 1176 | outside of the surface. 1177 | 1178 | Opaque region is double-buffered state, see wl_surface.commit. 1179 | 1180 | wl_surface.set_opaque_region changes the pending opaque region. 1181 | wl_surface.commit copies the pending region to the current region. 1182 | Otherwise, the pending and current regions are never changed. 1183 | 1184 | The initial value for opaque region is empty. Setting the pending 1185 | opaque region has copy semantics, and the wl_region object can be 1186 | destroyed immediately. A NULL wl_region causes the pending opaque 1187 | region to be set to empty. 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | This request sets the region of the surface that can receive 1196 | pointer and touch events. 1197 | 1198 | Input events happening outside of this region will try the next 1199 | surface in the server surface stack. The compositor ignores the 1200 | parts of the input region that fall outside of the surface. 1201 | 1202 | The input region is specified in surface local coordinates. 1203 | 1204 | Input region is double-buffered state, see wl_surface.commit. 1205 | 1206 | wl_surface.set_input_region changes the pending input region. 1207 | wl_surface.commit copies the pending region to the current region. 1208 | Otherwise the pending and current regions are never changed, 1209 | except cursor and icon surfaces are special cases, see 1210 | wl_pointer.set_cursor and wl_data_device.start_drag. 1211 | 1212 | The initial value for input region is infinite. That means the 1213 | whole surface will accept input. Setting the pending input region 1214 | has copy semantics, and the wl_region object can be destroyed 1215 | immediately. A NULL wl_region causes the input region to be set 1216 | to infinite. 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | Surface state (input, opaque, and damage regions, attached buffers, 1225 | etc.) is double-buffered. Protocol requests modify the pending 1226 | state, as opposed to current state in use by the compositor. Commit 1227 | request atomically applies all pending state, replacing the current 1228 | state. After commit, the new pending state is as documented for each 1229 | related request. 1230 | 1231 | On commit, a pending wl_buffer is applied first, all other state 1232 | second. This means that all coordinates in double-buffered state are 1233 | relative to the new wl_buffer coming into use, except for 1234 | wl_surface.attach itself. If there is no pending wl_buffer, the 1235 | coordinates are relative to the current surface contents. 1236 | 1237 | All requests that need a commit to become effective are documented 1238 | to affect double-buffered state. 1239 | 1240 | Other interfaces may add further double-buffered surface state. 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | This is emitted whenever a surface's creation, movement, or resizing 1247 | results in some part of it being within the scanout region of an 1248 | output. 1249 | 1250 | Note that a surface may be overlapping with zero or more outputs. 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | This is emitted whenever a surface's creation, movement, or resizing 1258 | results in it no longer having any part of it within the scanout region 1259 | of an output. 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | This request sets an optional transformation on how the compositor 1269 | interprets the contents of the buffer attached to the surface. The 1270 | accepted values for the transform parameter are the values for 1271 | wl_output.transform. 1272 | 1273 | Buffer transform is double-buffered state, see wl_surface.commit. 1274 | 1275 | A newly created surface has its buffer transformation set to normal. 1276 | 1277 | wl_surface.set_buffer_transform changes the pending buffer 1278 | transformation. wl_surface.commit copies the pending buffer 1279 | transformation to the current one. Otherwise, the pending and current 1280 | values are never changed. 1281 | 1282 | The purpose of this request is to allow clients to render content 1283 | according to the output transform, thus permiting the compositor to 1284 | use certain optimizations even if the display is rotated. Using 1285 | hardware overlays and scanning out a client buffer for fullscreen 1286 | surfaces are examples of such optimizations. Those optimizations are 1287 | highly dependent on the compositor implementation, so the use of this 1288 | request should be considered on a case-by-case basis. 1289 | 1290 | Note that if the transform value includes 90 or 270 degree rotation, 1291 | the width of the buffer will become the surface height and the height 1292 | of the buffer will become the surface width. 1293 | 1294 | If transform is not one of the values from the 1295 | wl_output.transform enum the invalid_transform protocol error 1296 | is raised. 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | This request sets an optional scaling factor on how the compositor 1306 | interprets the contents of the buffer attached to the window. 1307 | 1308 | Buffer scale is double-buffered state, see wl_surface.commit. 1309 | 1310 | A newly created surface has its buffer scale set to 1. 1311 | 1312 | wl_surface.set_buffer_scale changes the pending buffer scale. 1313 | wl_surface.commit copies the pending buffer scale to the current one. 1314 | Otherwise, the pending and current values are never changed. 1315 | 1316 | The purpose of this request is to allow clients to supply higher 1317 | resolution buffer data for use on high resolution outputs. Its 1318 | intended that you pick the same buffer scale as the scale of the 1319 | output that the surface is displayed on.This means the compositor 1320 | can avoid scaling when rendering the surface on that output. 1321 | 1322 | Note that if the scale is larger than 1, then you have to attach 1323 | a buffer that is larger (by a factor of scale in each dimension) 1324 | than the desired surface size. 1325 | 1326 | If scale is not positive the invalid_scale protocol error is 1327 | raised. 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | A seat is a group of keyboards, pointer and touch devices. This 1336 | object is published as a global during start up, or when such a 1337 | device is hot plugged. A seat typically has a pointer and 1338 | maintains a keyboard focus and a pointer focus. 1339 | 1340 | 1341 | 1342 | 1343 | This is a bitmask of capabilities this seat has; if a member is 1344 | set, then it is present on the seat. 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | This is emitted whenever a seat gains or loses the pointer, 1354 | keyboard or touch capabilities. The argument is a capability 1355 | enum containing the complete set of capabilities this seat has. 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | The ID provided will be initialized to the wl_pointer interface 1363 | for this seat. 1364 | 1365 | This request only takes effect if the seat has the pointer 1366 | capability. 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | The ID provided will be initialized to the wl_keyboard interface 1374 | for this seat. 1375 | 1376 | This request only takes effect if the seat has the keyboard 1377 | capability. 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | The ID provided will be initialized to the wl_touch interface 1385 | for this seat. 1386 | 1387 | This request only takes effect if the seat has the touch 1388 | capability. 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | In a multiseat configuration this can be used by the client to help 1398 | identify which physical devices the seat represents. Based on 1399 | the seat configuration used by the compositor. 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | The wl_pointer interface represents one or more input devices, 1409 | such as mice, which control the pointer location and pointer_focus 1410 | of a seat. 1411 | 1412 | The wl_pointer interface generates motion, enter and leave 1413 | events for the surfaces that the pointer is located over, 1414 | and button and axis events for button presses, button releases 1415 | and scrolling. 1416 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1423 | 1424 | Set the pointer surface, i.e., the surface that contains the 1425 | pointer image (cursor). This request gives the surface the role 1426 | of a cursor. If the surface already has another role, it raises 1427 | a protocol error. 1428 | 1429 | The cursor actually changes only if the pointer 1430 | focus for this device is one of the requesting client's surfaces 1431 | or the surface parameter is the current pointer surface. If 1432 | there was a previous surface set with this request it is 1433 | replaced. If surface is NULL, the pointer image is hidden. 1434 | 1435 | The parameters hotspot_x and hotspot_y define the position of 1436 | the pointer surface relative to the pointer location. Its 1437 | top-left corner is always at (x, y) - (hotspot_x, hotspot_y), 1438 | where (x, y) are the coordinates of the pointer location, in surface 1439 | local coordinates. 1440 | 1441 | On surface.attach requests to the pointer surface, hotspot_x 1442 | and hotspot_y are decremented by the x and y parameters 1443 | passed to the request. Attach must be confirmed by 1444 | wl_surface.commit as usual. 1445 | 1446 | The hotspot can also be updated by passing the currently set 1447 | pointer surface to this request with new values for hotspot_x 1448 | and hotspot_y. 1449 | 1450 | The current and pending input regions of the wl_surface are 1451 | cleared, and wl_surface.set_input_region is ignored until the 1452 | wl_surface is no longer used as the cursor. When the use as a 1453 | cursor ends, the current and pending input regions become 1454 | undefined, and the wl_surface is unmapped. 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | 1465 | Notification that this seat's pointer is focused on a certain 1466 | surface. 1467 | 1468 | When an seat's focus enters a surface, the pointer image 1469 | is undefined and a client should respond to this event by setting 1470 | an appropriate pointer image with the set_cursor request. 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | 1479 | 1480 | 1481 | Notification that this seat's pointer is no longer focused on 1482 | a certain surface. 1483 | 1484 | The leave notification is sent before the enter notification 1485 | for the new focus. 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | Notification of pointer location change. The arguments 1494 | surface_x and surface_y are the location relative to the 1495 | focused surface. 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1505 | Describes the physical state of a button which provoked the button 1506 | event. 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | Mouse button click and release notifications. 1515 | 1516 | The location of the click is given by the last motion or 1517 | enter event. 1518 | The time argument is a timestamp with millisecond 1519 | granularity, with an undefined base. 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | Describes the axis types of scroll events. 1531 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | Scroll and other axis notifications. 1539 | 1540 | For scroll events (vertical and horizontal scroll axes), the 1541 | value parameter is the length of a vector along the specified 1542 | axis in a coordinate space identical to those of motion events, 1543 | representing a relative movement along the specified axis. 1544 | 1545 | For devices that support movements non-parallel to axes multiple 1546 | axis events will be emitted. 1547 | 1548 | When applicable, for example for touch pads, the server can 1549 | choose to emit scroll events where the motion vector is 1550 | equivalent to a motion event vector. 1551 | 1552 | When applicable, clients can transform its view relative to the 1553 | scroll distance. 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | Using this request client can tell the server that it is not going to 1566 | use the pointer object anymore. 1567 | 1568 | This request destroys the pointer proxy object, so user must not call 1569 | wl_pointer_destroy() after using this request. 1570 | 1571 | 1572 | 1573 | 1574 | 1575 | 1576 | 1577 | The wl_keyboard interface represents one or more keyboards 1578 | associated with a seat. 1579 | 1580 | 1581 | 1582 | 1583 | This specifies the format of the keymap provided to the 1584 | client with the wl_keyboard.keymap event. 1585 | 1586 | 1588 | 1590 | 1591 | 1592 | 1593 | 1594 | This event provides a file descriptor to the client which can be 1595 | memory-mapped to provide a keyboard mapping description. 1596 | 1597 | 1598 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | Notification that this seat's keyboard focus is on a certain 1605 | surface. 1606 | 1607 | 1608 | 1609 | 1610 | 1611 | 1612 | 1613 | 1614 | Notification that this seat's keyboard focus is no longer on 1615 | a certain surface. 1616 | 1617 | The leave notification is sent before the enter notification 1618 | for the new focus. 1619 | 1620 | 1621 | 1622 | 1623 | 1624 | 1625 | 1626 | Describes the physical state of a key which provoked the key event. 1627 | 1628 | 1629 | 1630 | 1631 | 1632 | 1633 | 1634 | A key was pressed or released. 1635 | The time argument is a timestamp with millisecond 1636 | granularity, with an undefined base. 1637 | 1638 | 1639 | 1640 | 1641 | 1642 | 1643 | 1644 | 1645 | 1646 | 1647 | Notifies clients that the modifier and/or group state has 1648 | changed, and it should update its local state. 1649 | 1650 | 1651 | 1652 | 1653 | 1654 | 1655 | 1656 | 1657 | 1658 | 1659 | 1660 | 1661 | 1662 | 1663 | 1664 | 1665 | 1666 | 1667 | 1668 | Informs the client about the keyboard's repeat rate and delay. 1669 | 1670 | This event is sent as soon as the wl_keyboard object has been created, 1671 | and is guaranteed to be received by the client before any key press 1672 | event. 1673 | 1674 | Negative values for either rate or delay are illegal. A rate of zero 1675 | will disable any repeating (regardless of the value of delay). 1676 | 1677 | This event can be sent later on as well with a new value if necessary, 1678 | so clients should continue listening for the event past the creation 1679 | of wl_keyboard. 1680 | 1681 | 1682 | 1684 | 1686 | 1687 | 1688 | 1689 | 1690 | 1691 | The wl_touch interface represents a touchscreen 1692 | associated with a seat. 1693 | 1694 | Touch interactions can consist of one or more contacts. 1695 | For each contact, a series of events is generated, starting 1696 | with a down event, followed by zero or more motion events, 1697 | and ending with an up event. Events relating to the same 1698 | contact point can be identified by the ID of the sequence. 1699 | 1700 | 1701 | 1702 | 1703 | A new touch point has appeared on the surface. This touch point is 1704 | assigned a unique @id. Future events from this touchpoint reference 1705 | this ID. The ID ceases to be valid after a touch up event and may be 1706 | re-used in the future. 1707 | 1708 | 1709 | 1710 | 1711 | 1712 | 1713 | 1714 | 1715 | 1716 | 1717 | 1718 | The touch point has disappeared. No further events will be sent for 1719 | this touchpoint and the touch point's ID is released and may be 1720 | re-used in a future touch down event. 1721 | 1722 | 1723 | 1724 | 1725 | 1726 | 1727 | 1728 | 1729 | A touchpoint has changed coordinates. 1730 | 1731 | 1732 | 1733 | 1734 | 1735 | 1736 | 1737 | 1738 | 1739 | Indicates the end of a contact point list. 1740 | 1741 | 1742 | 1743 | 1744 | 1745 | Sent if the compositor decides the touch stream is a global 1746 | gesture. No further events are sent to the clients from that 1747 | particular gesture. Touch cancellation applies to all touch points 1748 | currently active on this client's surface. The client is 1749 | responsible for finalizing the touch points, future touch points on 1750 | this surface may re-use the touch point ID. 1751 | 1752 | 1753 | 1754 | 1755 | 1756 | 1757 | 1758 | 1759 | 1760 | 1761 | 1762 | 1763 | An output describes part of the compositor geometry. The 1764 | compositor works in the 'compositor coordinate system' and an 1765 | output corresponds to rectangular area in that space that is 1766 | actually visible. This typically corresponds to a monitor that 1767 | displays part of the compositor space. This object is published 1768 | as global during start up, or when a monitor is hotplugged. 1769 | 1770 | 1771 | 1772 | 1773 | This enumeration describes how the physical 1774 | pixels on an output are laid out. 1775 | 1776 | 1777 | 1778 | 1779 | 1780 | 1781 | 1782 | 1783 | 1784 | 1785 | 1786 | This describes the transform that a compositor will apply to a 1787 | surface to compensate for the rotation or mirroring of an 1788 | output device. 1789 | 1790 | The flipped values correspond to an initial flip around a 1791 | vertical axis followed by rotation. 1792 | 1793 | The purpose is mainly to allow clients render accordingly and 1794 | tell the compositor, so that for fullscreen surfaces, the 1795 | compositor will still be able to scan out directly from client 1796 | surfaces. 1797 | 1798 | 1799 | 1800 | 1801 | 1802 | 1803 | 1804 | 1805 | 1806 | 1807 | 1808 | 1809 | 1810 | 1811 | The geometry event describes geometric properties of the output. 1812 | The event is sent when binding to the output object and whenever 1813 | any of the properties change. 1814 | 1815 | 1817 | 1819 | 1821 | 1823 | 1825 | 1827 | 1829 | 1831 | 1832 | 1833 | 1834 | 1835 | These flags describe properties of an output mode. 1836 | They are used in the flags bitfield of the mode event. 1837 | 1838 | 1840 | 1842 | 1843 | 1844 | 1845 | 1846 | The mode event describes an available mode for the output. 1847 | 1848 | The event is sent when binding to the output object and there 1849 | will always be one mode, the current mode. The event is sent 1850 | again if an output changes mode, for the mode that is now 1851 | current. In other words, the current mode is always the last 1852 | mode that was received with the current flag set. 1853 | 1854 | The size of a mode is given in physical hardware units of 1855 | the output device. This is not necessarily the same as 1856 | the output size in the global compositor space. For instance, 1857 | the output may be scaled, as described in wl_output.scale, 1858 | or transformed , as described in wl_output.transform. 1859 | 1860 | 1861 | 1862 | 1863 | 1864 | 1865 | 1866 | 1867 | 1868 | This event is sent after all other properties has been 1869 | sent after binding to the output object and after any 1870 | other property changes done after that. This allows 1871 | changes to the output properties to be seen as 1872 | atomic, even if they happen via multiple events. 1873 | 1874 | 1875 | 1876 | 1877 | 1878 | This event contains scaling geometry information 1879 | that is not in the geometry event. It may be sent after 1880 | binding the output object or if the output scale changes 1881 | later. If it is not sent, the client should assume a 1882 | scale of 1. 1883 | 1884 | A scale larger than 1 means that the compositor will 1885 | automatically scale surface buffers by this amount 1886 | when rendering. This is used for very high resolution 1887 | displays where applications rendering at the native 1888 | resolution would be too small to be legible. 1889 | 1890 | It is intended that scaling aware clients track the 1891 | current output of a surface, and if it is on a scaled 1892 | output it should use wl_surface.set_buffer_scale with 1893 | the scale of the output. That way the compositor can 1894 | avoid scaling the surface, and the client can supply 1895 | a higher detail image. 1896 | 1897 | 1898 | 1899 | 1900 | 1901 | 1902 | 1903 | A region object describes an area. 1904 | 1905 | Region objects are used to describe the opaque and input 1906 | regions of a surface. 1907 | 1908 | 1909 | 1910 | 1911 | Destroy the region. This will invalidate the object ID. 1912 | 1913 | 1914 | 1915 | 1916 | 1917 | Add the specified rectangle to the region. 1918 | 1919 | 1920 | 1921 | 1922 | 1923 | 1924 | 1925 | 1926 | 1927 | 1928 | Subtract the specified rectangle from the region. 1929 | 1930 | 1931 | 1932 | 1933 | 1934 | 1935 | 1936 | 1937 | 1938 | 1939 | 1940 | 1941 | The global interface exposing sub-surface compositing capabilities. 1942 | A wl_surface, that has sub-surfaces associated, is called the 1943 | parent surface. Sub-surfaces can be arbitrarily nested and create 1944 | a tree of sub-surfaces. 1945 | 1946 | The root surface in a tree of sub-surfaces is the main 1947 | surface. The main surface cannot be a sub-surface, because 1948 | sub-surfaces must always have a parent. 1949 | 1950 | A main surface with its sub-surfaces forms a (compound) window. 1951 | For window management purposes, this set of wl_surface objects is 1952 | to be considered as a single window, and it should also behave as 1953 | such. 1954 | 1955 | The aim of sub-surfaces is to offload some of the compositing work 1956 | within a window from clients to the compositor. A prime example is 1957 | a video player with decorations and video in separate wl_surface 1958 | objects. This should allow the compositor to pass YUV video buffer 1959 | processing to dedicated overlay hardware when possible. 1960 | 1961 | 1962 | 1963 | 1964 | Informs the server that the client will not be using this 1965 | protocol object anymore. This does not affect any other 1966 | objects, wl_subsurface objects included. 1967 | 1968 | 1969 | 1970 | 1971 | 1973 | 1974 | 1975 | 1976 | 1977 | Create a sub-surface interface for the given surface, and 1978 | associate it with the given parent surface. This turns a 1979 | plain wl_surface into a sub-surface. 1980 | 1981 | The to-be sub-surface must not already have another role, and it 1982 | must not have an existing wl_subsurface object. Otherwise a protocol 1983 | error is raised. 1984 | 1985 | 1986 | 1988 | 1990 | 1992 | 1993 | 1994 | 1995 | 1996 | 1997 | An additional interface to a wl_surface object, which has been 1998 | made a sub-surface. A sub-surface has one parent surface. A 1999 | sub-surface's size and position are not limited to that of the parent. 2000 | Particularly, a sub-surface is not automatically clipped to its 2001 | parent's area. 2002 | 2003 | A sub-surface becomes mapped, when a non-NULL wl_buffer is applied 2004 | and the parent surface is mapped. The order of which one happens 2005 | first is irrelevant. A sub-surface is hidden if the parent becomes 2006 | hidden, or if a NULL wl_buffer is applied. These rules apply 2007 | recursively through the tree of surfaces. 2008 | 2009 | The behaviour of wl_surface.commit request on a sub-surface 2010 | depends on the sub-surface's mode. The possible modes are 2011 | synchronized and desynchronized, see methods 2012 | wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized 2013 | mode caches the wl_surface state to be applied when the parent's 2014 | state gets applied, and desynchronized mode applies the pending 2015 | wl_surface state directly. A sub-surface is initially in the 2016 | synchronized mode. 2017 | 2018 | Sub-surfaces have also other kind of state, which is managed by 2019 | wl_subsurface requests, as opposed to wl_surface requests. This 2020 | state includes the sub-surface position relative to the parent 2021 | surface (wl_subsurface.set_position), and the stacking order of 2022 | the parent and its sub-surfaces (wl_subsurface.place_above and 2023 | .place_below). This state is applied when the parent surface's 2024 | wl_surface state is applied, regardless of the sub-surface's mode. 2025 | As the exception, set_sync and set_desync are effective immediately. 2026 | 2027 | The main surface can be thought to be always in desynchronized mode, 2028 | since it does not have a parent in the sub-surfaces sense. 2029 | 2030 | Even if a sub-surface is in desynchronized mode, it will behave as 2031 | in synchronized mode, if its parent surface behaves as in 2032 | synchronized mode. This rule is applied recursively throughout the 2033 | tree of surfaces. This means, that one can set a sub-surface into 2034 | synchronized mode, and then assume that all its child and grand-child 2035 | sub-surfaces are synchronized, too, without explicitly setting them. 2036 | 2037 | If the wl_surface associated with the wl_subsurface is destroyed, the 2038 | wl_subsurface object becomes inert. Note, that destroying either object 2039 | takes effect immediately. If you need to synchronize the removal 2040 | of a sub-surface to the parent surface update, unmap the sub-surface 2041 | first by attaching a NULL wl_buffer, update parent, and then destroy 2042 | the sub-surface. 2043 | 2044 | If the parent wl_surface object is destroyed, the sub-surface is 2045 | unmapped. 2046 | 2047 | 2048 | 2049 | 2050 | The sub-surface interface is removed from the wl_surface object 2051 | that was turned into a sub-surface with 2052 | wl_subcompositor.get_subsurface request. The wl_surface's association 2053 | to the parent is deleted, and the wl_surface loses its role as 2054 | a sub-surface. The wl_surface is unmapped. 2055 | 2056 | 2057 | 2058 | 2059 | 2061 | 2062 | 2063 | 2064 | 2065 | This schedules a sub-surface position change. 2066 | The sub-surface will be moved so, that its origin (top-left 2067 | corner pixel) will be at the location x, y of the parent surface 2068 | coordinate system. The coordinates are not restricted to the parent 2069 | surface area. Negative values are allowed. 2070 | 2071 | The scheduled coordinates will take effect whenever the state of the 2072 | parent surface is applied. When this happens depends on whether the 2073 | parent surface is in synchronized mode or not. See 2074 | wl_subsurface.set_sync and wl_subsurface.set_desync for details. 2075 | 2076 | If more than one set_position request is invoked by the client before 2077 | the commit of the parent surface, the position of a new request always 2078 | replaces the scheduled position from any previous request. 2079 | 2080 | The initial position is 0, 0. 2081 | 2082 | 2083 | 2084 | 2085 | 2086 | 2087 | 2088 | 2089 | This sub-surface is taken from the stack, and put back just 2090 | above the reference surface, changing the z-order of the sub-surfaces. 2091 | The reference surface must be one of the sibling surfaces, or the 2092 | parent surface. Using any other surface, including this sub-surface, 2093 | will cause a protocol error. 2094 | 2095 | The z-order is double-buffered. Requests are handled in order and 2096 | applied immediately to a pending state. The final pending state is 2097 | copied to the active state the next time the state of the parent 2098 | surface is applied. When this happens depends on whether the parent 2099 | surface is in synchronized mode or not. See wl_subsurface.set_sync and 2100 | wl_subsurface.set_desync for details. 2101 | 2102 | A new sub-surface is initially added as the top-most in the stack 2103 | of its siblings and parent. 2104 | 2105 | 2106 | 2108 | 2109 | 2110 | 2111 | 2112 | The sub-surface is placed just below of the reference surface. 2113 | See wl_subsurface.place_above. 2114 | 2115 | 2116 | 2118 | 2119 | 2120 | 2121 | 2122 | Change the commit behaviour of the sub-surface to synchronized 2123 | mode, also described as the parent dependent mode. 2124 | 2125 | In synchronized mode, wl_surface.commit on a sub-surface will 2126 | accumulate the committed state in a cache, but the state will 2127 | not be applied and hence will not change the compositor output. 2128 | The cached state is applied to the sub-surface immediately after 2129 | the parent surface's state is applied. This ensures atomic 2130 | updates of the parent and all its synchronized sub-surfaces. 2131 | Applying the cached state will invalidate the cache, so further 2132 | parent surface commits do not (re-)apply old state. 2133 | 2134 | See wl_subsurface for the recursive effect of this mode. 2135 | 2136 | 2137 | 2138 | 2139 | 2140 | Change the commit behaviour of the sub-surface to desynchronized 2141 | mode, also described as independent or freely running mode. 2142 | 2143 | In desynchronized mode, wl_surface.commit on a sub-surface will 2144 | apply the pending state directly, without caching, as happens 2145 | normally with a wl_surface. Calling wl_surface.commit on the 2146 | parent surface has no effect on the sub-surface's wl_surface 2147 | state. This mode allows a sub-surface to be updated on its own. 2148 | 2149 | If cached state exists when wl_surface.commit is called in 2150 | desynchronized mode, the pending state is added to the cached 2151 | state, and applied as whole. This invalidates the cache. 2152 | 2153 | Note: even if a sub-surface is set to desynchronized, a parent 2154 | sub-surface may override it to behave as synchronized. For details, 2155 | see wl_subsurface. 2156 | 2157 | If a surface's parent surface behaves as desynchronized, then 2158 | the cached state is applied on set_desync. 2159 | 2160 | 2161 | 2162 | 2163 | 2164 | 2165 | """ 2166 | -------------------------------------------------------------------------------- /tests/test_wayland.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import wayland.protocol 4 | import wayland.client 5 | 6 | from tests.data import sample_protocol 7 | import io 8 | 9 | class TestProtocol(TestCase): 10 | """Test wayland.protocols""" 11 | 12 | @classmethod 13 | def setUpClass(cls): 14 | # A wayland.protocol.Protocol is immutable once loaded, so we 15 | # can load the test data once and use it for all the tests. 16 | f = io.StringIO(sample_protocol) 17 | cls.w = wayland.protocol.Protocol(f) 18 | 19 | def test_protocol_name(self): 20 | self.assertEqual(self.w.name, "wayland") 21 | 22 | def test_protocol_copyright(self): 23 | self.assertIsNotNone(self.w.copyright) 24 | 25 | def test_protocol_interfaces(self): 26 | self.assertIsInstance(self.w.interfaces, dict) 27 | self.assertIn("wl_display", self.w.interfaces) 28 | self.assertIsInstance(self.w['wl_display'], wayland.protocol.Interface) 29 | 30 | def test_duplicate_interface_name(self): 31 | f = io.StringIO(sample_protocol) 32 | with self.assertRaises(wayland.protocol.DuplicateInterfaceName): 33 | # Try to load the test protocol with itself as a parent; 34 | # should fail on the first interface declaration and leave 35 | # the parent unchanged 36 | wayland.protocol.Protocol(f, parent=self.w) 37 | 38 | def test_interface_name(self): 39 | for i in self.w.interfaces.keys(): 40 | self.assertEqual(self.w[i].name, i) 41 | 42 | def test_interface_version(self): 43 | for i in self.w.interfaces.keys(): 44 | self.assertIsInstance(self.w[i].version, int) 45 | -------------------------------------------------------------------------------- /wayland/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sde1000/python-wayland/b017596de0d550aabfc24a26554a22e43ceb7861/wayland/__init__.py -------------------------------------------------------------------------------- /wayland/client.py: -------------------------------------------------------------------------------- 1 | """Wayland protocol client implementation""" 2 | 3 | import wayland.protocol 4 | import os 5 | import socket 6 | import select 7 | import struct 8 | import array 9 | import io 10 | 11 | class ServerDisconnected(Exception): 12 | """The server disconnected unexpectedly""" 13 | pass 14 | 15 | class NoXDGRuntimeDir(Exception): 16 | """The XDG_RUNTIME_DIR environment variable is not set""" 17 | pass 18 | 19 | class ProtocolError(Exception): 20 | """The server sent data that could not be decoded""" 21 | pass 22 | 23 | class UnknownObjectError(Exception): 24 | """The server sent an event for an object we don't know about""" 25 | def __init__(self, oid): 26 | self.oid = oid 27 | def __str__(self): 28 | return "UnknownObjectError({})".format(self.oid) 29 | 30 | class DisplayError(Exception): 31 | """The server sent a fatal error event 32 | 33 | This error can be raised during dispatching of the default queue. 34 | """ 35 | def __init__(self, obj, code, codestr, message): 36 | self.obj = obj 37 | self.code = code 38 | self.codestr = codestr 39 | self.message = message 40 | def __str__(self): 41 | return "DisplayError({}, {} (\"{}\"), {})".format( 42 | self.obj, self.code, self.codestr, self.message) 43 | 44 | class _Display: 45 | """Additional methods for wl_display interface proxy 46 | 47 | The wl_display proxy class obtained by loading the Wayland 48 | protocol XML file needs to be augmented with some additional 49 | methods to function as a full Wayland protocol client. 50 | """ 51 | def __init__(self, name_or_fd=None): 52 | self._f = None 53 | self._oids = iter(range(1, 0xff000000)) 54 | self._reusable_oids = [] 55 | self._default_queue = [] 56 | super(_Display, self).__init__(self, self._get_new_oid(), 57 | self._default_queue, 1) 58 | if hasattr(name_or_fd, 'fileno'): 59 | self._f = name_or_fd 60 | self.log.info("connected to existing fd %d", self._f) 61 | else: 62 | xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') 63 | if not xdg_runtime_dir: 64 | raise NoXDGRuntimeDir() 65 | if not name_or_fd: 66 | display = os.getenv('WAYLAND_DISPLAY') 67 | if not display: 68 | display = "wayland-0" 69 | path = os.path.join(xdg_runtime_dir, display) 70 | self._f = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) 71 | self._f.connect(path) 72 | self.log.info("connected to %s", path) 73 | 74 | self._f.setblocking(0) 75 | 76 | # Partial event left from last read 77 | self._read_partial_event = b'' 78 | self._incoming_fds = [] 79 | 80 | self.objects = {self.oid: self} 81 | self._send_queue = [] 82 | 83 | self.dispatcher['delete_id'] = self._delete_id 84 | self.silence['delete_id'] = True 85 | self.dispatcher['error'] = self._error_event 86 | 87 | def __del__(self): 88 | self.disconnect() 89 | 90 | def disconnect(self): 91 | """Disconnect from the server. 92 | 93 | Closes the socket. After calling this method, all further 94 | calls to this proxy or any other proxies on the connection 95 | will fail. 96 | """ 97 | if self._f: 98 | self._f.close() 99 | self._f = None 100 | 101 | def get_fd(self): 102 | """Get the file descriptor number of the server connection. 103 | 104 | This can be used in calls to select(), poll(), etc. to wait 105 | for events from the server. 106 | """ 107 | return self._f.fileno() 108 | 109 | def _get_new_oid(self): 110 | if self._reusable_oids: 111 | return self._reusable_oids.pop() 112 | return next(self._oids) 113 | 114 | def _delete_id(self, display, id_): 115 | self.log.info("server deleted %s", self.objects[id_]) 116 | self.objects[id_].oid = None 117 | del self.objects[id_] 118 | if id_ < 0xff000000: 119 | self._reusable_oids.append(id_) 120 | 121 | def _error_event(self, *args): 122 | # XXX look up string for error code in enum 123 | objs, (code, message) = args[:-2],args[-2:] 124 | raise DisplayError(str(objs), str(code), "", str(message)) 125 | 126 | def _queue_request(self, r, fds=[]): 127 | self.log.debug("queueing to send: %s with fds %s", r, fds) 128 | self._send_queue.append((r, fds)) 129 | 130 | def flush(self): 131 | """Send buffered requests to the display server. 132 | 133 | Will send as many requests as possible to the display server. 134 | Will not block; if sendmsg() would block, will leave events in 135 | the queue. 136 | 137 | Returns True if the queue was emptied. 138 | """ 139 | while self._send_queue: 140 | b, fds = self._send_queue.pop(0) 141 | try: 142 | self._f.sendmsg([b], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, 143 | array.array("i", fds))]) 144 | for fd in fds: 145 | os.close(fd) 146 | except socket.error as e: 147 | if socket.errno == 11: 148 | # Would block. Return the data to the head of the queue 149 | # and try again later! 150 | self.log.debug("flush would block; returning data to queue") 151 | self._send_queue.insert(0, (b, fds)) 152 | return 153 | raise 154 | return True 155 | 156 | def recv(self): 157 | """Receive as much data as is available. 158 | 159 | Returns True if any data was received. Will not block. 160 | """ 161 | data = None 162 | try: 163 | fds = array.array("i") 164 | data, ancdata, msg_flags, address = self._f.recvmsg( 165 | 1024, socket.CMSG_SPACE(16 * fds.itemsize)) 166 | for cmsg_level, cmsg_type, cmsg_data in ancdata: 167 | if (cmsg_level == socket.SOL_SOCKET and 168 | cmsg_type == socket.SCM_RIGHTS): 169 | fds.frombytes(cmsg_data[ 170 | :len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) 171 | self._incoming_fds.extend(fds) 172 | if data: 173 | self._decode(data) 174 | return True 175 | else: 176 | raise ServerDisconnected() 177 | except socket.error as e: 178 | if e.errno == 11: 179 | # No data available; would otherwise block 180 | return 181 | raise 182 | 183 | def dispatch(self): 184 | """Dispatch the default event queue. 185 | 186 | If the queue is empty, block until events are available and 187 | dispatch them. 188 | """ 189 | self.flush() 190 | while not self._default_queue: 191 | select.select([self._f], [], []) 192 | self.recv() 193 | self.dispatch_pending() 194 | 195 | def dispatch_pending(self, queue=None): 196 | """Dispatch pending events in an event queue. 197 | 198 | If queue is None, dispatches from the default event queue. 199 | Will not read from the server connection. 200 | """ 201 | if not queue: 202 | queue = self._default_queue 203 | while queue: 204 | e = self._default_queue.pop(0) 205 | if isinstance(e, Exception): 206 | raise e 207 | proxy, event, args = e 208 | proxy.dispatch_event(event, args) 209 | 210 | def roundtrip(self): 211 | """Send a sync request to the server and wait for the reply. 212 | 213 | Events are read from the server and dispatched if they are on 214 | the default event queue. This call blocks until the "done" 215 | event on the wl_callback generated by the sync request has 216 | been dispatched. 217 | """ 218 | ready = False 219 | def set_ready(callback, x): 220 | nonlocal ready 221 | ready = True 222 | l = self.sync() 223 | l.dispatcher['done'] = set_ready 224 | while not ready: 225 | self.dispatch() 226 | 227 | def _decode(self, data): 228 | # There may be partial event data already received; add to it 229 | # if it's there 230 | if self._read_partial_event: 231 | data = self._read_partial_event + data 232 | while len(data) > 8: 233 | oid, sizeop = struct.unpack("II", data[0 : 8]) 234 | 235 | size = sizeop >> 16 236 | op = sizeop & 0xffff 237 | 238 | if len(data) < size: 239 | self.log.debug("partial event received: %d byte event, " 240 | "%d bytes available", size, len(data)) 241 | break 242 | 243 | argdata = io.BytesIO(data[8 : size]) 244 | data = data [size : ] 245 | 246 | obj = self.objects.get(oid, None) 247 | if obj: 248 | with argdata: 249 | e = obj._unmarshal_event(op, argdata, self._incoming_fds) 250 | self.log.debug( 251 | "queueing event: %s(%d) %s %s", 252 | e[0].interface.name, e[0].oid, e[1].name, e[2]) 253 | obj.queue.append(e) 254 | else: 255 | obj.queue.append(UnknownObjectError(obj)) 256 | self._read_partial_event = data 257 | 258 | def MakeDisplay(protocol): 259 | """Create a Display class from a Wayland protocol definition 260 | 261 | Args: 262 | protocol: a wayland.protocol.Protocol instance containing a 263 | core Wayland protocol definition. 264 | 265 | Returns: 266 | A Display proxy class built from the specified protocol. 267 | """ 268 | class Display(_Display, protocol['wl_display'].client_proxy_class): 269 | pass 270 | return Display 271 | -------------------------------------------------------------------------------- /wayland/protocol.py: -------------------------------------------------------------------------------- 1 | """Wayland protocol parser and wire protocol implementation""" 2 | 3 | import xml.etree.ElementTree as ET 4 | import struct 5 | import os 6 | import logging 7 | 8 | def _description(d): 9 | assert d.tag == "description" 10 | return d.text, d.get('summary') 11 | 12 | class NullArgumentException(Exception): 13 | """None was passed where a value was expected""" 14 | pass 15 | 16 | class DeletedProxyException(Exception): 17 | """A request was made on an object that has already been deleted""" 18 | pass 19 | 20 | class DuplicateInterfaceName(Exception): 21 | """A duplicate interface name was detected. 22 | 23 | A protocol file specified an interface name that already exists. 24 | """ 25 | pass 26 | 27 | class ClientProxy: 28 | """Abstract base class for a proxy to an interface. 29 | 30 | Classes are derived from this for each interface in a protocol. 31 | Instances of these classes correspond to objects in the Wayland 32 | connection. Each class has a method for each request defined in 33 | the interface, and deals with despatching events received for the 34 | object. 35 | 36 | Useful attributes: 37 | 38 | interface (class attribute): the Interface this class is a proxy 39 | for 40 | 41 | display: the wl_display this instance is connected to 42 | 43 | oid: the object ID of this instance 44 | 45 | version: the version of this object 46 | 47 | dispatcher: dictionary mapping event names to callback functions 48 | 49 | silence: dictionary of event names that will not be logged 50 | """ 51 | 52 | def __init__(self, display, oid, queue, version): 53 | self.display = display 54 | self.oid = oid 55 | self.queue = queue 56 | self.version = version 57 | self.dispatcher = {} 58 | self.silence = {} 59 | self.destroyed = False 60 | self.log = logging.getLogger(__name__ + "." + self.interface.name) 61 | 62 | def _marshal_request(self, request, *args): 63 | # args is a tuple when called; we make it a list so it's mutable, 64 | # because args are consumed in the 'for' loop 65 | args = list(args) 66 | al = [] 67 | rval = None 68 | fl = [] 69 | for a in request.args: 70 | b, r, fds = a.marshal_for_request(args, self) 71 | al.append(b) 72 | fl = fl + fds 73 | rval = rval or r 74 | assert len(args) == 0 75 | al = bytes().join(al) 76 | b = struct.pack('II', self.oid, ((len(al) + 8) << 16) | request.opcode) 77 | self.display._queue_request(b + al, fl) 78 | return rval 79 | 80 | def _unmarshal_event(self, opcode, argdata, fd_source): 81 | event = self.interface.events_by_number[opcode] 82 | args = [] 83 | for arg in event.args: 84 | v = arg.unmarshal_from_event(argdata, fd_source, self) 85 | args.append(v) 86 | return (self, event, args) 87 | 88 | def set_queue(self, new_queue): 89 | # Sets the queue for events received from this object 90 | self.queue = new_queue 91 | 92 | def dispatch_event(self, event, args): 93 | if self.destroyed: 94 | self.log.info("ignore event %s(%d).%s%s on destroyed proxy", 95 | self.interface.name, 96 | self.oid, event.name, args) 97 | return 98 | f = self.dispatcher.get(event.name, None) 99 | if f: 100 | if event.name not in self.silence: 101 | self.log.info("dispatch event %s(%d).%s%s", 102 | self.interface.name, 103 | self.oid, event.name, args) 104 | f(self, *args) 105 | else: 106 | if event.name not in self.silence: 107 | self.log.info("ignore event %s(%d).%s%s", 108 | self.interface.name, 109 | self.oid, event.name, args) 110 | 111 | def __str__(self): 112 | return "{}({})".format(self.interface.name, self.oid) 113 | 114 | def __repr__(self): 115 | return str(self) 116 | 117 | class Arg: 118 | """An argument to a request or event. 119 | 120 | The request or event this argument belongs to is accessible using 121 | the "parent" attribute. 122 | 123 | Has a name, type, optional description, and optional summary. 124 | 125 | If this argument creates a new object, the interface for the new 126 | object is accessible as the "interface" attribute. 127 | 128 | If the argument may be null (None), the "allow_null" attribute is 129 | True. 130 | """ 131 | def __init__(self, parent, arg): 132 | self.parent = parent 133 | 134 | self.name = arg.get('name') 135 | self.type = arg.get('type') 136 | 137 | self.description = None 138 | self.summary = arg.get('summary', None) 139 | self.allow_null = (arg.get('allow-null', None) == "true") 140 | 141 | for c in arg: 142 | if c.tag == "description": 143 | self.description, self.summary = _description(c) 144 | 145 | def marshal(self, args): 146 | """Marshal the argument. 147 | 148 | Implement this when marshalling for requests and events is the 149 | same operation. 150 | 151 | args is the list of arguments still to marshal; this call 152 | removes the appropriate number of items from args. 153 | 154 | The return value is a tuple of (bytes, optional return value, 155 | list of fds to send). 156 | """ 157 | raise RuntimeError 158 | 159 | def unmarshal(self, argdata, fd_source): 160 | """Unmarshal the argument. 161 | 162 | Implement this when unmarshalling from requests and events is 163 | the same operation. 164 | 165 | argdata is a file-like object providing access to the 166 | remaining marshalled arguments; this call will consume the 167 | appropriate number of bytes from this source 168 | 169 | fd_source is an iterator object supplying fds that have been 170 | received over the connection 171 | 172 | The return value is the value of the argument. 173 | """ 174 | raise RuntimeError 175 | 176 | def marshal_for_request(self, args, proxy): 177 | """Marshal the argument 178 | 179 | args is the list of arguments still to marshal; this call 180 | removes the appropriate number of items from args 181 | 182 | proxy is the interface proxy class instance being used for the 183 | call. 184 | 185 | The return value is a tuple of (bytes, optional return value, 186 | list of fds to send) 187 | """ 188 | return self.marshal(args) 189 | 190 | def unmarshal_from_event(self, argdata, fd_source, proxy): 191 | """Unmarshal the argument 192 | 193 | argdata is a file-like object providing access to the 194 | remaining marshalled arguments; this call will consume the 195 | appropriate number of bytes from this source 196 | 197 | fd_source is an iterator object supplying fds that have been 198 | received over the connection 199 | 200 | proxy is the interface proxy class instance being used for the 201 | event. 202 | 203 | The return value is the value of the argument 204 | """ 205 | return self.unmarshal(argdata, fd_source) 206 | 207 | class Arg_int(Arg): 208 | """Signed 32-bit integer argument""" 209 | 210 | def marshal(self, args): 211 | v = args.pop(0) 212 | return struct.pack('i', v), None, [] 213 | 214 | def unmarshal(self, argdata, fd_source): 215 | (v, ) = struct.unpack("i", argdata.read(4)) 216 | return v 217 | 218 | class Arg_uint(Arg): 219 | """Unsigned 32-bit integer argument""" 220 | 221 | def marshal(self, args): 222 | v = args.pop(0) 223 | return struct.pack('I', v), None, [] 224 | 225 | def unmarshal(self, argdata, fd_source): 226 | (v, ) = struct.unpack("I", argdata.read(4)) 227 | return v 228 | 229 | class Arg_new_id(Arg): 230 | """Newly created object argument""" 231 | 232 | def __init__(self, parent, arg): 233 | super(Arg_new_id, self).__init__(parent, arg) 234 | self.interface = arg.get('interface', None) 235 | if isinstance(parent, Event): 236 | assert self.interface 237 | 238 | def marshal_for_request(self, args, proxy): 239 | nid = proxy.display._get_new_oid() 240 | if self.interface: 241 | # The interface type is part of the argument, and the 242 | # version of the newly created object is the same as the 243 | # version of the proxy. 244 | npc = self.parent.interface.protocol[self.interface]\ 245 | .client_proxy_class 246 | version = proxy.version 247 | b = struct.pack('I', nid) 248 | else: 249 | # The interface and version are supplied by the caller, 250 | # and the argument is marshalled as string,uint32,uint32 251 | interface = args.pop(0) 252 | version = args.pop(0) 253 | npc = interface.client_proxy_class 254 | iname = interface.name.encode('utf-8') 255 | parts = (struct.pack('I',len(iname)+1), 256 | iname, 257 | b'\x00'*(4-(len(iname) % 4)), 258 | struct.pack('II',version,nid)) 259 | b = b''.join(parts) 260 | new_proxy = npc(proxy.display, nid, proxy.display._default_queue, 261 | version) 262 | proxy.display.objects[nid] = new_proxy 263 | return b, new_proxy, [] 264 | 265 | def unmarshal_from_event(self, argdata, fd_source, proxy): 266 | assert self.interface 267 | (nid, ) = struct.unpack("I", argdata.read(4)) 268 | npc = self.parent.interface.protocol[self.interface].client_proxy_class 269 | new_proxy = npc(proxy.display, nid, proxy.display._default_queue, 270 | proxy.version) 271 | proxy.display.objects[nid] = new_proxy 272 | return new_proxy 273 | 274 | class Arg_string(Arg): 275 | """String argument""" 276 | 277 | def marshal(self, args): 278 | estr = args.pop(0).encode('utf-8') 279 | parts = (struct.pack('I',len(estr)+1), 280 | estr, 281 | b'\x00'*(4-(len(estr) % 4))) 282 | return b''.join(parts), None, [] 283 | 284 | def unmarshal(self, argdata, fd_source): 285 | # The length includes the terminating null byte 286 | (l, ) = struct.unpack("I", argdata.read(4)) 287 | assert l > 0 288 | l = l-1 289 | s = argdata.read(l).decode('utf-8') 290 | argdata.read(4 - (l % 4)) 291 | return s 292 | 293 | class Arg_object(Arg): 294 | """Existing object argument""" 295 | 296 | def marshal(self, args): 297 | v = args.pop(0) 298 | if v: 299 | oid = v.oid 300 | else: 301 | if self.allow_null: 302 | oid = 0 303 | else: 304 | raise NullArgumentException() 305 | return struct.pack("I", oid), None, [] 306 | 307 | def unmarshal_from_event(self, argdata, fd_source, proxy): 308 | (v, ) = struct.unpack("I", argdata.read(4)) 309 | return proxy.display.objects.get(v, None) 310 | 311 | class Arg_fd(Arg): 312 | """File descriptor argument""" 313 | 314 | def marshal(self, args): 315 | v = args.pop(0) 316 | fd = os.dup(v) 317 | return b'', None, [fd] 318 | 319 | def unmarshal(self, argdata, fd_source): 320 | return fd_source.pop(0) 321 | 322 | class Arg_fixed(Arg): 323 | """Signed 24.8 decimal number argument""" 324 | 325 | # XXX not completely sure I've understood the format here - in 326 | # particular, is it (as the protocol description says) a sign bit 327 | # followed by 23 bits of integer precision and 8 bits of decimal 328 | # precision, or is it 24 bits of 2's complement integer precision 329 | # followed by 8 bits of decimal precision? I've assumed the 330 | # latter because it seems to work! 331 | 332 | def marshal(self, args): 333 | v = args.pop(0) 334 | if isinstance(v, int): 335 | m = v << 8 336 | else: 337 | m = (int(v) << 8) + int((v % 1.0) * 256) 338 | return struct.pack("i",m), None, [] 339 | 340 | def unmarshal(self, argdata, fd_source): 341 | b = argdata.read(4) 342 | (m, ) = struct.unpack("i",b) 343 | return float(m >> 8) + ((m & 0xff) / 256.0) 344 | 345 | class Arg_array(Arg): 346 | """Array argument""" 347 | 348 | # This appears to be very similar to a string, except without any 349 | # zero termination. Interpretation of the contents of the array 350 | # is request- or event-dependent. 351 | 352 | def marshal(self, args): 353 | v = args.pop(0) 354 | # v should be bytes 355 | parts = (struct.pack('I',len(v)), 356 | estr, 357 | b'\x00'*(3 - ((len(v) - 1) % 4))) 358 | return b''.join(parts), None, [] 359 | 360 | def unmarshal(self, argdata, fd_source): 361 | (l, ) = struct.unpack("I", argdata.read(4)) 362 | v = argdata.read(l) 363 | pad = 3 - ((l - 1) % 4) 364 | if pad: 365 | argdata.read(pad) 366 | return v 367 | 368 | def _make_arg(parent, tag): 369 | t = tag.get("type") 370 | c = "Arg_" + tag.get("type") 371 | return globals()[c](parent, tag) 372 | 373 | class Request: 374 | """A request on an interface. 375 | 376 | Requests have a name, optional type (to indicate whether the 377 | request destroys the object), optional "since version of 378 | interface", optional description, and optional summary. 379 | 380 | If a request has an argument of type "new_id" then the request 381 | creates a new object; the Interface for this new object is 382 | accessible as the "creates" attribute. 383 | """ 384 | def __init__(self, interface, opcode, request): 385 | self.interface = interface 386 | self.opcode = opcode 387 | assert request.tag == "request" 388 | 389 | self.name = request.get('name') 390 | self.type = request.get('type', None) 391 | self.since = int(request.get('since', 1)) 392 | 393 | self.is_destructor = (self.type == "destructor") 394 | 395 | self.description = None 396 | self.summary = None 397 | 398 | self.creates = None 399 | 400 | self.args = [] 401 | 402 | for c in request: 403 | if c.tag == "description": 404 | self.description, self.summary = _description(c) 405 | elif c.tag == "arg": 406 | a = _make_arg(self, c) 407 | if a.type == "new_id": 408 | self.creates = a.interface 409 | self.args.append(a) 410 | 411 | def __str__(self): 412 | return "{}.{}".format(self.interface.name,self.name) 413 | 414 | def invoke(self, proxy, *args): 415 | """Invoke this request on a client proxy.""" 416 | if not proxy.oid: 417 | proxy.log.warning("request %s on deleted %s proxy", 418 | self.name, proxy.interface.name) 419 | raise DeletedProxyException 420 | if proxy.destroyed: 421 | proxy.log.info("request %s.%s%s on destroyed object; ignoring", 422 | proxy, self.name, args) 423 | return 424 | if proxy.version < self.since: 425 | proxy.log.error( 426 | "request %s.%s%s only exists from version %s, but proxy is " 427 | "version %s", proxy, self.name, args, self.since, 428 | proxy.version) 429 | return 430 | r = proxy._marshal_request(self, *args) 431 | if r: 432 | proxy.log.info( 433 | "request %s.%s%s -> %s", proxy, self.name, args, r) 434 | else: 435 | proxy.log.info("request %s.%s%s", proxy, self.name, args) 436 | if self.is_destructor: 437 | proxy.destroyed = True 438 | proxy.log.info( 439 | "%s proxy destroyed by destructor request %s%s", 440 | proxy, self.name, args) 441 | return r 442 | 443 | class Event: 444 | """An event on an interface. 445 | 446 | Events have a number (which depends on the order in which they are 447 | declared in the protocol XML file), name, optional "since version 448 | of interface", optional description, optional summary, and a 449 | number of arguments. 450 | """ 451 | def __init__(self, interface, event, number): 452 | self.interface = interface 453 | assert event.tag == "event" 454 | 455 | self.name = event.get('name') 456 | self.number = number 457 | self.since = int(event.get('since', 1)) 458 | self.args = [] 459 | self.description = None 460 | self.summary = None 461 | 462 | for c in event: 463 | if c.tag == "description": 464 | self.description, self.summary = _description(c) 465 | elif c.tag == "arg": 466 | self.args.append(_make_arg(self, c)) 467 | 468 | def __str__(self): 469 | return "{}::{}".format(self.interface, self.name) 470 | 471 | class Entry: 472 | """An entry in an enumeration. 473 | 474 | Has a name, integer value, optional description, optional summary, 475 | and optional "since version of interface". 476 | """ 477 | 478 | def __init__(self, enum, entry): 479 | self.enum = enum 480 | assert entry.tag == "entry" 481 | 482 | self.name = entry.get('name') 483 | self.value = int(entry.get('value'), base=0) 484 | self.description = None 485 | self.summary = entry.get('summary', None) 486 | self.since = int(entry.get('since', 1)) 487 | 488 | for c in entry: 489 | if c.tag == "description": 490 | self.description, self.summary = _description(c) 491 | 492 | class Enum: 493 | """An enumeration declared in an interface. 494 | 495 | Enumerations have a name, optional "since version of interface", 496 | option description, optional summary, and a number of entries. 497 | 498 | The entries are accessible by name in the dictionary available 499 | through the "entries" attribute. Further, if the Enum instance is 500 | accessed as a dictionary then if a string argument is used it 501 | returns the integer value of the corresponding entry, and if an 502 | integer argument is used it returns the name of the corresponding 503 | entry. 504 | """ 505 | def __init__(self, interface, enum): 506 | self.interface = interface 507 | assert enum.tag == "enum" 508 | 509 | self.name = enum.get('name') 510 | self.since = int(enum.get('since', 1)) 511 | self.entries = {} 512 | self.description = None 513 | self.summary = None 514 | self._values = {} 515 | self._names = {} 516 | 517 | for c in enum: 518 | if c.tag == "description": 519 | self.description, self.summary = _description(c) 520 | elif c.tag == "entry": 521 | e = Entry(self, c) 522 | self.entries[e.name] = e 523 | self._values[e.name] = e.value 524 | self._names[e.value] = e.name 525 | 526 | def __getitem__(self, i): 527 | if isinstance(i, int): 528 | return self._names[i] 529 | return self._values[i] 530 | 531 | class Interface: 532 | """A Wayland protocol interface. 533 | 534 | Wayland interfaces have a name and version, plus a number of 535 | requests, events and enumerations. Optionally they have a 536 | description. 537 | 538 | The name and version are accessible as the "name" and "version" 539 | attributes. 540 | 541 | The requests and enums are accessible as dictionaries as the 542 | "requests" and "enums" attributes. The events are accessible by 543 | name as a dictionary as the "events_by_name" attribute, and by 544 | number as a list as the "events_by_number" attribute. 545 | 546 | A client proxy class for this interface is available as the 547 | "client_proxy_class" attribute; instances of this class have 548 | methods corresponding to the requests, and deal with dispatching 549 | the events. 550 | """ 551 | 552 | def __init__(self, protocol, interface): 553 | self.protocol = protocol 554 | assert interface.tag == "interface" 555 | 556 | self.name = interface.get('name') 557 | self.version = int(interface.get('version')) 558 | assert self.version > 0 559 | self.description = None 560 | self.summary = None 561 | self.requests = {} 562 | self.events_by_name = {} 563 | self.events_by_number = [] 564 | self.enums = {} 565 | 566 | for c in interface: 567 | if c.tag == "description": 568 | self.description, self.summary = _description(c) 569 | elif c.tag == "request": 570 | e = Request(self, len(self.requests), c) 571 | self.requests[e.name] = e 572 | elif c.tag == "event": 573 | e = Event(self, c, len(self.events_by_number)) 574 | self.events_by_name[e.name] = e 575 | self.events_by_number.append(e) 576 | elif c.tag == "enum": 577 | e = Enum(self, c) 578 | self.enums[e.name] = e 579 | 580 | def client_proxy_request(x): 581 | def call_request(*args): 582 | return x.invoke(*args) 583 | return call_request 584 | d = { 585 | '__doc__': self.description, 586 | 'interface': self, 587 | } 588 | for r in self.requests.values(): 589 | d[r.name] = client_proxy_request(r) 590 | self.client_proxy_class = type( 591 | str(self.name + '_client_proxy'), (ClientProxy,), d) 592 | 593 | # TODO: create a server proxy class as well 594 | 595 | def __str__(self): 596 | return self.name 597 | 598 | def __repr__(self): 599 | return "Interface('{}', {})".format(self.name, self.version) 600 | 601 | class Protocol: 602 | """A Wayland protocol. 603 | 604 | A Wayland connection will often have multiple Wayland protocols 605 | running over it: the core protocol, plus a number of other 606 | protocols that add completely new functionality or extend the 607 | functionality of some other protocol. 608 | 609 | See https://cgit.freedesktop.org/wayland/wayland-protocols for the 610 | current collection of Wayland protocols. 611 | 612 | This Protocol class corresponds to one protocol XML file. These 613 | contain one or more interfaces, which are accessible in this class 614 | via the "interfaces" attribute which is a dictionary keyed by 615 | interface name. Once instantiated this class should be treated as 616 | immutable, with the only exception being that interfaces of 617 | "child" protocols that are loaded with this class instance as an 618 | ancestor will be added to the "interfaces" dictionary. 619 | 620 | As a shortcut, accessing an instance of this class through 621 | __getitem__ (for example wayland['wl_display']) will access the 622 | interfaces dictionary. 623 | 624 | The copyright notice from the XML file, if present, is accessible 625 | as the "copyright" attribute. 626 | """ 627 | def __init__(self, file, parent=None): 628 | """Load a Wayland protocol file. 629 | 630 | Args: 631 | file: a filename or file object containing an XML Wayland 632 | protocol description 633 | 634 | parent: a Protocol object containing interfaces that are 635 | referred to by name in the XML protocol description 636 | """ 637 | tree = ET.parse(file) 638 | 639 | protocol = tree.getroot() 640 | assert protocol.tag == "protocol" 641 | 642 | self.copyright = None 643 | if parent: 644 | self.interfaces = parent.interfaces 645 | else: 646 | self.interfaces = {} 647 | 648 | self.name = protocol.get('name') 649 | 650 | for c in protocol: 651 | if c.tag == "copyright": 652 | self.copyright = c.text 653 | elif c.tag == "interface": 654 | i = Interface(self, c) 655 | if i.name in self.interfaces: 656 | raise DuplicateInterfaceName(i.name) 657 | self.interfaces[i.name] = i 658 | 659 | def __getitem__(self, x): 660 | return self.interfaces.__getitem__(x) 661 | -------------------------------------------------------------------------------- /wayland/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | from wayland.client import NoXDGRuntimeDir 4 | 5 | class AnonymousFile(object): 6 | def __init__(self, size): 7 | xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') 8 | if not xdg_runtime_dir: 9 | raise NoXDGRuntimeDir() 10 | self._fd, name = tempfile.mkstemp(dir=xdg_runtime_dir) 11 | os.ftruncate(self._fd, size) 12 | def fileno(self): 13 | if self._fd: 14 | return self._fd 15 | raise OSError 16 | def close(self): 17 | if self._fd: 18 | os.close(self._fd) 19 | self._fd = None 20 | def __enter__(self): 21 | return self._fd 22 | def __exit__(self, exc_type, exc_value, traceback): 23 | if self._fd: 24 | self.close() 25 | --------------------------------------------------------------------------------