def _on_inhibitor_destroy(self, listener: Listener, surface: surface.Surface): # We don't have reference to the inhibitor, but it doesn't really # matter we only need to keep count of how many inhibitors there are self._idle_inhibitors_count -= 1 listener.remove() if self._idle_inhibitors_count == 0: self.core.check_idle_inhibitor()
def __init__( self, *, display: Display, backend: Backend, renderer: Renderer, xdg_shell: XdgShell, cursor: Cursor, cursor_manager: XCursorManager, seat: Seat, output_layout: OutputLayout, ) -> None: # elements that we need to hold on to self._display = display self._backend = backend self._renderer = renderer # the xdg shell will generate new surfaces self._xdg_shell = xdg_shell self.views: List[View] = [] # new pointing devices are attached to the cursor, and rendered with the manager self._cursor = cursor self._cursor_manager = cursor_manager # the seat manages the keyboard focus information self._seat = seat self.keyboards: List[KeyboardHandler] = [] self.cursor_mode = CursorMode.PASSTHROUGH self.grabbed_view: Optional[View] = None self.grab_x = 0.0 self.grab_y = 0.0 self.grab_geobox: Optional[Box] = None self.resize_edges: Edges = Edges.NONE self._output_layout = output_layout self.outputs: List[Output] = [] xdg_shell.new_surface_event.add(Listener(self.server_new_xdg_surface)) backend.new_output_event.add(Listener(self.server_new_output)) cursor.motion_event.add(Listener(self.server_cursor_motion)) cursor.motion_absolute_event.add( Listener(self.server_cursor_motion_absolute)) cursor.button_event.add(Listener(self.server_cursor_button)) cursor.axis_event.add(Listener(self.server_cursor_axis)) cursor.frame_event.add(Listener(self.server_cursor_frame)) seat.request_set_cursor_event.add(Listener(self.seat_request_cursor)) seat.request_set_selection_event.add( Listener(self.seat_request_set_selection)) backend.new_input_event.add(Listener(self.server_new_input))
def __init__( self, keyboard: Keyboard, input_device: InputDevice, tinywl_server: TinywlServer, ) -> None: self.keyboard = keyboard self.input_device = input_device self.tinywl_server = tinywl_server keyboard.modifiers_event.add(Listener(self.keyboard_handle_modifiers)) keyboard.key_event.add(Listener(self.keyboard_handle_key))
def __init__(self, core: Core, qtile: Qtile, surface: xdg_shell.XdgSurface, wid: int): base.Window.__init__(self) self.core = core self.qtile = qtile self.surface = surface self._wid = wid self._group = 0 self.mapped = False self.x = 0 self.y = 0 self.borderwidth: int = 0 self.bordercolor: ffi.CData = _rgb((0, 0, 0, 1)) self.opacity: float = 1.0 self.surface.set_tiled(EDGES_TILED) self._float_state = FloatStates.NOT_FLOATING self.float_x = self.x self.float_y = self.y self.float_width = self.width self.float_height = self.height self._on_map_listener = Listener(self._on_map) self._on_unmap_listener = Listener(self._on_unmap) self._on_destroy_listener = Listener(self._on_destroy) self._on_request_fullscreen_listener = Listener( self._on_request_fullscreen) surface.map_event.add(self._on_map_listener) surface.unmap_event.add(self._on_unmap_listener) surface.destroy_event.add(self._on_destroy_listener) surface.toplevel.request_fullscreen_event.add( self._on_request_fullscreen_listener)
def __init__(self, xdg_surface: XdgSurface, tinywl_server: TinywlServer) -> None: self.xdg_surface = xdg_surface self.tinywl_server = tinywl_server self.mapped = False self.x = 0.0 self.y = 0.0 xdg_surface.map_event.add(Listener(self.xdg_surface_map)) xdg_surface.unmap_event.add(Listener(self.xdg_surface_unmap)) xdg_surface.destroy_event.add(Listener(self.xdg_surface_destroy)) toplevel = xdg_surface.toplevel toplevel.request_move_event.add( Listener(self.xdg_toplevel_request_move)) toplevel.request_resize_event.add( Listener(self.xdg_toplevel_request_resize))
def __init__(self, core: Core, wlr_output: wlrOutput): self.core = core self.renderer = core.renderer self.wlr_output = wlr_output self.output_layout = self.core.output_layout self.wallpaper = None self.transform_matrix = wlr_output.transform_matrix self.x, self.y = self.output_layout.output_coords(wlr_output) self._on_destroy_listener = Listener(self._on_destroy) self._on_frame_listener = Listener(self._on_frame) wlr_output.destroy_event.add(self._on_destroy_listener) wlr_output.frame_event.add(self._on_frame_listener) self._mapped_windows: Set[WindowType] = set() hook.subscribe.setgroup(self._get_windows) hook.subscribe.group_window_add(self._get_windows) hook.subscribe.client_killed(self._get_windows) hook.subscribe.client_managed(self._get_windows)
class Keyboard: def __init__(self, core: Core, device: InputDevice): self.core = core self.device = device self.qtile = core.qtile self.seat = core.seat self.keyboard = device.keyboard self.grabbed_keys = core.grabbed_keys xkb_context = xkb.Context() self.keyboard.set_keymap(xkb_context.keymap_new_from_names()) self.keyboard.set_repeat_info(25, 600) self._on_modifier_listener = Listener(self._on_modifier) self._on_key_listener = Listener(self._on_key) self._on_destroy_listener = Listener(self._on_destroy) self.keyboard.modifiers_event.add(self._on_modifier_listener) self.keyboard.key_event.add(self._on_key_listener) self.keyboard.destroy_event.add(self._on_destroy_listener) def finalize(self): self._on_modifier_listener.remove() self._on_key_listener.remove() self._on_destroy_listener.remove() self.core.keyboards.remove(self) if self.core.keyboards and self.core.seat.keyboard._ptr == self.keyboard._ptr: self.seat.set_keyboard(self.core.keyboards[-1].device) def _on_destroy(self, _listener, _data): logger.debug("Signal: keyboard destroy") self.finalize() def _on_modifier(self, _listener, _data): logger.debug("Signal: keyboard modifier") self.seat.keyboard_notify_modifiers(self.keyboard.modifiers) def _on_key(self, _listener, event: KeyboardKeyEvent): logger.debug("Signal: keyboard key") handled = False if self.qtile is None: # shushes mypy self.qtile = self.core.qtile assert self.qtile is not None if event.state == KEY_PRESSED: # translate libinput keycode -> xkbcommon keycode = event.keycode + 8 keysyms = _get_keysyms(self.keyboard._ptr.xkb_state, keycode) mods = self.keyboard.modifier for keysym in keysyms: if (keysym, mods) in self.grabbed_keys: self.qtile.process_key_event(keysym, mods) handled = True if not handled: self.seat.keyboard_notify_key(event)
def server_new_output(self, listener, output: Output) -> None: output.init_render(self._allocator, self._renderer) if output.modes != []: mode = output.preferred_mode() if mode is None: logger.error("Got no output mode") return output.set_mode(mode) output.enable() output.commit() self.outputs.append(output) self._output_layout.add_auto(output) output.frame_event.add(Listener(self.output_frame))
def server_new_output(self, listener, output: Output) -> None: if output.modes != []: mode = output.preferred_mode() if mode is None: logger.error("Got no output mode") return output.set_mode(mode) output.enable() if not output.commit(): logger.error("Unable to commit output") return self.outputs.append(output) self._output_layout.add_auto(output) output.frame_event.add(Listener(self.output_frame))
def __init__(self, core: Core, device: InputDevice): self.core = core self.device = device self.qtile = core.qtile self.seat = core.seat self.keyboard = device.keyboard self.grabbed_keys = core.grabbed_keys xkb_context = xkb.Context() self.keyboard.set_keymap(xkb_context.keymap_new_from_names()) self.keyboard.set_repeat_info(25, 600) self._on_modifier_listener = Listener(self._on_modifier) self._on_key_listener = Listener(self._on_key) self._on_destroy_listener = Listener(self._on_destroy) self.keyboard.modifiers_event.add(self._on_modifier_listener) self.keyboard.key_event.add(self._on_key_listener) self.keyboard.destroy_event.add(self._on_destroy_listener)
def test_destroy_resource(): global destroyed, notified destroyed = False notified = False s1, s2 = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0) display = Display() client = Client(display, s1.fileno()) listener = Listener(_destroy_notify) # Create resource res = WlDisplay.resource_class(client, version=4) # Attach a destructor and a destroy notification res.dispatcher.destructor = _destroy_callback res.add_destroy_listener(listener) # Destroy the resource res.destroy() assert destroyed assert notified assert client.get_object(res.id) is None # Create resource res = WlDisplay.resource_class(client, version=2) # Attach a destructor and a destroy notification res.dispatcher.destructor = _destroy_callback res.add_destroy_listener(listener) # Destroy the client client.destroy() assert destroyed assert notified display.destroy() s2.close()
def add_listener(self, event: Signal, callback: Callable): if not hasattr(self, "_listeners"): self._listeners = [] listener = Listener(callback) event.add(listener) self._listeners.append(listener)
class Output: def __init__(self, core: Core, wlr_output: wlrOutput): self.core = core self.renderer = core.renderer self.wlr_output = wlr_output self.output_layout = self.core.output_layout self.wallpaper = None self.transform_matrix = wlr_output.transform_matrix self.x, self.y = self.output_layout.output_coords(wlr_output) self._on_destroy_listener = Listener(self._on_destroy) self._on_frame_listener = Listener(self._on_frame) wlr_output.destroy_event.add(self._on_destroy_listener) wlr_output.frame_event.add(self._on_frame_listener) self._mapped_windows: Set[WindowType] = set() hook.subscribe.setgroup(self._get_windows) hook.subscribe.group_window_add(self._get_windows) hook.subscribe.client_killed(self._get_windows) hook.subscribe.client_managed(self._get_windows) def finalize(self): self._on_destroy_listener.remove() self._on_frame_listener.remove() def _on_destroy(self, _listener, _data): logger.debug("Signal: output destroy") self.finalize() self.core.outputs.remove(self) def _on_frame(self, _listener, _data): now = Timespec.get_monotonic_time() wlr_output = self.wlr_output if not wlr_output.attach_render(): logger.error("Could not attach renderer") return self.renderer.begin(*wlr_output.effective_resolution()) self.renderer.clear([0, 0, 0, 1]) if self.wallpaper: self.renderer.render_texture(self.wallpaper, self.transform_matrix, 0, 0, 1) for window in self._mapped_windows: rdata = ( now, window, self.x + window.x, self.y + window.y, window.opacity, wlr_output.scale, ) window.surface.for_each_surface(self._render_surface, rdata) wlr_output.render_software_cursors() self.renderer.end() wlr_output.commit() def _render_surface(self, surface: Surface, sx: int, sy: int, rdata: Tuple) -> None: now, window, wx, wy, opacity, scale = rdata texture = surface.get_texture() if texture is None: return x = (wx + sx) * scale y = (wy + sy) * scale width = surface.current.width * scale height = surface.current.height * scale transform_matrix = self.wlr_output.transform_matrix if surface == window.surface.surface and window.borderwidth: bw = window.borderwidth * scale bc = window.bordercolor border = Box( int(x), int(y), int(width + bw * 2), int(bw), ) x += bw y += bw self.renderer.render_rect(border, bc, transform_matrix) # Top border border.y = int(y + height) self.renderer.render_rect(border, bc, transform_matrix) # Bottom border border.y = int(y - bw) border.width = int(bw) border.height = int(height + bw * 2) self.renderer.render_rect(border, bc, transform_matrix) # Left border border.x = int(x + width) self.renderer.render_rect(border, bc, transform_matrix) # Right border box = Box( int(x), int(y), int(width), int(height), ) inverse = wlrOutput.transform_invert(surface.current.transform) matrix = Matrix.project_box(box, inverse, 0, transform_matrix) self.renderer.render_texture_with_matrix(texture, matrix, opacity) surface.send_frame_done(now) def get_geometry(self) -> Tuple[int, int, int, int]: x, y = self.output_layout.output_coords(self.wlr_output) width, height = self.wlr_output.effective_resolution() return int(x), int(y), width, height def _get_windows(self, *args): """Get the set of mapped windows for rendering.""" mapped = set() for win in self.core.qtile.windows_map.values(): if win.mapped: mapped.add(win) self._mapped_windows = mapped
def __init__(self): """Setup the Wayland core backend""" self.qtile: Optional[Qtile] = None self.desktops: int = 1 self.current_desktop: int = 0 self.display = Display() self.event_loop = self.display.get_event_loop() self.compositor, self.backend = wlroots_helper.build_compositor( self.display) self.renderer = self.backend.renderer self.socket = self.display.add_socket() self.fd = None # set up inputs self.keyboards: List[keyboard.Keyboard] = [] self.grabbed_keys: List[Tuple[int, int]] = [] self.grabbed_buttons: List[Tuple[int, int]] = [] self.device_manager = DataDeviceManager(self.display) self.seat = seat.Seat(self.display, "seat0") self._on_request_set_selection_listener = Listener( self._on_request_set_selection) self._on_new_input_listener = Listener(self._on_new_input) self.seat.request_set_selection_event.add( self._on_request_set_selection_listener) self.backend.new_input_event.add(self._on_new_input_listener) # set up outputs self.output_layout = OutputLayout() self.outputs: List[output.Output] = [] self._on_new_output_listener = Listener(self._on_new_output) self.backend.new_output_event.add(self._on_new_output_listener) # set up cursor self.cursor = Cursor(self.output_layout) self.cursor_manager = XCursorManager(24) self._on_request_cursor_listener = Listener(self._on_request_cursor) self.seat.request_set_cursor_event.add( self._on_request_cursor_listener) self._on_cursor_axis_listener = Listener(self._on_cursor_axis) self._on_cursor_frame_listener = Listener(self._on_cursor_frame) self._on_cursor_button_listener = Listener(self._on_cursor_button) self._on_cursor_motion_listener = Listener(self._on_cursor_motion) self._on_cursor_motion_absolute_listener = Listener( self._on_cursor_motion_absolute) self.cursor.axis_event.add(self._on_cursor_axis_listener) self.cursor.frame_event.add(self._on_cursor_frame_listener) self.cursor.button_event.add(self._on_cursor_button_listener) self.cursor.motion_event.add(self._on_cursor_motion_listener) self.cursor.motion_absolute_event.add( self._on_cursor_motion_absolute_listener) # set up shell self.xdg_shell = xdg_shell.XdgShell(self.display) self._on_new_xdg_surface_listener = Listener(self._on_new_xdg_surface) self.xdg_shell.new_surface_event.add(self._on_new_xdg_surface_listener) # Add support for additional protocols XdgOutputManagerV1(self.display, self.output_layout) ScreencopyManagerV1(self.display) GammaControlManagerV1(self.display) self._virtual_keyboard_manager_v1 = VirtualKeyboardManagerV1( self.display) self._on_new_virtual_keyboard_listener = Listener( self._on_new_virtual_keyboard) self._virtual_keyboard_manager_v1.new_virtual_keyboard_event.add( self._on_new_virtual_keyboard_listener) # start os.environ["WAYLAND_DISPLAY"] = self.socket.decode() logger.info("Starting core with WAYLAND_DISPLAY=" + self.socket.decode()) self.backend.start()
class Core(base.Core): def __init__(self): """Setup the Wayland core backend""" self.qtile: Optional[Qtile] = None self.desktops: int = 1 self.current_desktop: int = 0 self.display = Display() self.event_loop = self.display.get_event_loop() self.compositor, self.backend = wlroots_helper.build_compositor( self.display) self.renderer = self.backend.renderer self.socket = self.display.add_socket() self.fd = None # set up inputs self.keyboards: List[keyboard.Keyboard] = [] self.grabbed_keys: List[Tuple[int, int]] = [] self.grabbed_buttons: List[Tuple[int, int]] = [] self.device_manager = DataDeviceManager(self.display) self.seat = seat.Seat(self.display, "seat0") self._on_request_set_selection_listener = Listener( self._on_request_set_selection) self._on_new_input_listener = Listener(self._on_new_input) self.seat.request_set_selection_event.add( self._on_request_set_selection_listener) self.backend.new_input_event.add(self._on_new_input_listener) # set up outputs self.output_layout = OutputLayout() self.outputs: List[output.Output] = [] self._on_new_output_listener = Listener(self._on_new_output) self.backend.new_output_event.add(self._on_new_output_listener) # set up cursor self.cursor = Cursor(self.output_layout) self.cursor_manager = XCursorManager(24) self._on_request_cursor_listener = Listener(self._on_request_cursor) self.seat.request_set_cursor_event.add( self._on_request_cursor_listener) self._on_cursor_axis_listener = Listener(self._on_cursor_axis) self._on_cursor_frame_listener = Listener(self._on_cursor_frame) self._on_cursor_button_listener = Listener(self._on_cursor_button) self._on_cursor_motion_listener = Listener(self._on_cursor_motion) self._on_cursor_motion_absolute_listener = Listener( self._on_cursor_motion_absolute) self.cursor.axis_event.add(self._on_cursor_axis_listener) self.cursor.frame_event.add(self._on_cursor_frame_listener) self.cursor.button_event.add(self._on_cursor_button_listener) self.cursor.motion_event.add(self._on_cursor_motion_listener) self.cursor.motion_absolute_event.add( self._on_cursor_motion_absolute_listener) # set up shell self.xdg_shell = xdg_shell.XdgShell(self.display) self._on_new_xdg_surface_listener = Listener(self._on_new_xdg_surface) self.xdg_shell.new_surface_event.add(self._on_new_xdg_surface_listener) # Add support for additional protocols XdgOutputManagerV1(self.display, self.output_layout) ScreencopyManagerV1(self.display) GammaControlManagerV1(self.display) self._virtual_keyboard_manager_v1 = VirtualKeyboardManagerV1( self.display) self._on_new_virtual_keyboard_listener = Listener( self._on_new_virtual_keyboard) self._virtual_keyboard_manager_v1.new_virtual_keyboard_event.add( self._on_new_virtual_keyboard_listener) # start os.environ["WAYLAND_DISPLAY"] = self.socket.decode() logger.info("Starting core with WAYLAND_DISPLAY=" + self.socket.decode()) self.backend.start() def finalize(self): self._on_new_xdg_surface_listener.remove() self._on_request_cursor_listener.remove() self._on_new_output_listener.remove() self._on_new_input_listener.remove() self._on_request_set_selection_listener.remove() self._on_new_virtual_keyboard_listener.remove() for kb in self.keyboards: kb.finalize() for out in self.outputs: out.finalize() self.cursor_manager.destroy() self.cursor.destroy() self.output_layout.destroy() self.seat.destroy() self.backend.destroy() self.display.destroy() self.qtile = None @property def display_name(self) -> str: return self.socket.decode() def _on_request_set_selection(self, _listener, event: seat.RequestSetSelectionEvent): self.seat.set_selection(event._ptr.source, event.serial) logger.debug("Signal: seat request_set_selection") def _on_new_input(self, _listener, device: input_device.InputDevice): logger.debug("Signal: backend new_input_event") if device.device_type == input_device.InputDeviceType.POINTER: self._add_new_pointer(device) elif device.device_type == input_device.InputDeviceType.KEYBOARD: self._add_new_keyboard(device) capabilities = WlSeat.capability.pointer if len(self.keyboards) > 0: capabilities |= WlSeat.capability.keyboard logger.info("New input: " + str(device.device_type)) logger.info("Input capabilities: " + str(capabilities)) self.seat.set_capabilities(capabilities) def _on_new_output(self, _listener, wlr_output: wlrOutput): logger.debug("Signal: backend new_output_event") if wlr_output.modes != []: mode = wlr_output.preferred_mode() if mode is None: logger.error("New output has no output mode") return wlr_output.set_mode(mode) wlr_output.enable() wlr_output.commit() self.outputs.append(output.Output(self, wlr_output)) self.output_layout.add_auto(wlr_output) def _on_request_cursor(self, _listener, event: seat.PointerRequestSetCursorEvent): logger.debug("Signal: seat request_set_cursor_event") # if self._seat.pointer_state.focused_surface == event.seat_client: # needs updating pywlroots first self.cursor.set_surface(event.surface, event.hotspot) def _on_new_xdg_surface(self, _listener, surface: xdg_shell.XdgSurface): logger.debug("Signal: xdg_shell new_surface_event") assert self.qtile is not None if surface.role != xdg_shell.XdgSurfaceRole.TOPLEVEL: return wid = 0 wids = self.qtile.windows_map.keys() while True: if wid not in wids: break wid += 1 win = window.Window(self, self.qtile, surface, wid) logger.info(f"Managing new top-level window with window ID: {wid}") self.qtile.manage(win) def _on_cursor_axis(self, _listener, event: pointer.PointerEventAxis): logger.debug("Signal: cursor axis") self.seat.pointer_notify_axis( event.time_msec, event.orientation, event.delta, event.delta_discrete, event.source, ) def _on_cursor_frame(self, _listener, _data): logger.debug("Signal: cursor frame") self.seat.pointer_notify_frame() def _on_cursor_button(self, _listener, event: pointer.PointerEventButton): assert self.qtile is not None logger.debug("Signal: cursor button") self.seat.pointer_notify_button(event.time_msec, event.button, event.button_state) state = self.seat.keyboard.modifier button = wlrq.buttons_inv.get(event.button) if event.button_state == input_device.ButtonState.PRESSED: self.qtile.process_button_click(button, state, self.cursor.x, self.cursor.y, event) else: self.qtile.process_button_release(button, state) def _on_cursor_motion(self, _listener, event: pointer.PointerEventMotion): assert self.qtile is not None logger.debug("Signal: cursor motion") self.cursor.move(event.delta_x, event.delta_y, input_device=event.device) self._process_cursor_motion(event.time_msec) def _on_cursor_motion_absolute(self, _listener, event: pointer.PointerEventMotionAbsolute): assert self.qtile is not None logger.debug("Signal: cursor motion_absolute") self.cursor.warp( WarpMode.AbsoluteClosest, event.x, event.y, input_device=event.device, ) self._process_cursor_motion(event.time_msec) def _on_new_virtual_keyboard(self, _listener, virtual_keyboard: VirtualKeyboardV1): self._add_new_keyboard(virtual_keyboard.input_device) def _process_cursor_motion(self, time): self.qtile.process_button_motion(self.cursor.x, self.cursor.y) found = self._under_pointer() if found: win, surface, sx, sy = found focus_changed = self.seat.pointer_state.focused_surface != surface self.seat.pointer_notify_enter(surface, sx, sy) if focus_changed: hook.fire("client_mouse_enter", win) if self.qtile.config.follow_mouse_focus: if win.group.current_window != self: win.group.focus(win, False) if win.group.screen and self.qtile.current_screen != win.group.screen: self.qtile.focus_screen(win.group.screen.index, False) self.focus_window(win, surface) else: # The enter event contains coordinates, so we only need to # notify on motion if the focus did not change self.seat.pointer_notify_motion(time, sx, sy) else: self.cursor_manager.set_cursor_image("left_ptr", self.cursor) self.seat.pointer_clear_focus() def _add_new_pointer(self, device: input_device.InputDevice): logger.info("Adding new pointer") self.cursor.attach_input_device(device) def _add_new_keyboard(self, device: input_device.InputDevice): logger.info("Adding new keyboard") self.keyboards.append(keyboard.Keyboard(self, device)) self.seat.set_keyboard(device) def setup_listener(self, qtile: Qtile) -> None: """Setup a listener for the given qtile instance""" logger.debug("Adding io watch") self.qtile = qtile self.fd = lib.wl_event_loop_get_fd(self.event_loop._ptr) asyncio.get_running_loop().add_reader(self.fd, self._poll) def remove_listener(self) -> None: """Remove the listener from the given event loop""" if self.fd is not None: logger.debug("Removing io watch") loop = asyncio.get_running_loop() loop.remove_reader(self.fd) self.fd = None def _poll(self) -> None: if not self.display.destroyed: self.display.flush_clients() self.event_loop.dispatch(-1) def focus_window(self, win: window.Window, surface: Surface = None): if surface is None: surface = win.surface.surface previous_surface = self.seat.keyboard_state.focused_surface if previous_surface == surface: return if previous_surface is not None and previous_surface.is_xdg_surface: # Deactivate the previously focused surface previous_xdg_surface = xdg_shell.XdgSurface.from_surface( previous_surface) previous_xdg_surface.set_activated(False) # activate the new surface win.surface.set_activated(True) self.seat.keyboard_notify_enter(surface, self.seat.keyboard) logger.debug("Focussed new window") def focus_by_click(self, event) -> None: found = self._under_pointer() if found: win, surface, _, _ = found self.focus_window(win, surface) def _under_pointer(self): assert self.qtile is not None cx = self.cursor.x cy = self.cursor.y for win in self.qtile.windows_map.values(): assert isinstance(win, window.Window) # mypy is dumb and needs this if win.mapped: surface, sx, sy = win.surface.surface_at( cx - win.x, cy - win.y) if surface: return win, surface, sx, sy if win.borderwidth: bw = win.borderwidth if win.x - bw <= cx and win.y - bw <= cy: if cx <= win.x + win.width + bw and cy <= win.y + win.height + bw: return win, win.surface.surface, 0, 0 return None def update_desktops(self, groups: List[group._Group], index: int) -> None: """Set the current desktops of the window manager The list of desktops is given by the list of groups, with the current desktop given by the index """ new_count = len(groups) while new_count > self.desktops: self.desktops += 1 while new_count < self.desktops: self.desktops -= 1 self.current_desktop = index def get_screen_info(self) -> List[Tuple[int, int, int, int]]: """Get the screen information""" return [screen.get_geometry() for screen in self.outputs] def grab_key(self, key: Union[config.Key, config.KeyChord]) -> Tuple[int, int]: """Configure the backend to grab the key event""" keysym = xkb.keysym_from_name(key.key, case_insensitive=True) mask_key = wlrq.translate_masks(key.modifiers) self.grabbed_keys.append((keysym, mask_key)) return keysym, mask_key def ungrab_key(self, key: Union[config.Key, config.KeyChord]) -> Tuple[int, int]: """Release the given key event""" keysym = xkb.keysym_from_name(key.key, case_insensitive=True) mask_key = wlrq.translate_masks(key.modifiers) self.grabbed_keys.remove((keysym, mask_key)) return keysym, mask_key def ungrab_keys(self) -> None: """Release the grabbed key events""" self.grabbed_keys.clear() def grab_button(self, mouse: config.Mouse) -> int: """Configure the backend to grab the mouse event""" keysym = wlrq.buttons.get(mouse.button_code) assert keysym is not None mask_key = wlrq.translate_masks(mouse.modifiers) self.grabbed_buttons.append((keysym, mask_key)) return mask_key def ungrab_buttons(self) -> None: """Release the grabbed button events""" self.grabbed_buttons.clear() def grab_pointer(self) -> None: """Configure the backend to grab mouse events""" def ungrab_pointer(self) -> None: """Release grabbed pointer events""" def warp_pointer(self, x, y) -> None: """Warp the pointer to the coordinates in relative to the output layout""" self.cursor.warp(WarpMode.LayoutClosest, x, y) def flush(self) -> None: self._poll() def graceful_shutdown(self): """Try to close windows gracefully before exiting""" assert self.qtile is not None for win in self.qtile.windows_map.values(): win.kill() # give everyone a little time to exit and write their state. but don't # sleep forever (1s). end = time.time() + 1 while time.time() < end: self._poll() if not self.qtile.windows_map: break def change_vt(self, vt: int) -> bool: """Change virtual terminal to that specified""" success = self.backend.get_session().change_vt(vt) if not success: logger.warning(f"Could not change VT to: {vt}") return success @property def painter(self): return wlrq.Painter(self)
class Window(base.Window): def __init__(self, core: Core, qtile: Qtile, surface: xdg_shell.XdgSurface, wid: int): base.Window.__init__(self) self.core = core self.qtile = qtile self.surface = surface self._wid = wid self._group = 0 self.mapped = False self.x = 0 self.y = 0 self.borderwidth: int = 0 self.bordercolor: ffi.CData = _rgb((0, 0, 0, 1)) self.opacity: float = 1.0 self.surface.set_tiled(EDGES_TILED) self._float_state = FloatStates.NOT_FLOATING self.float_x = self.x self.float_y = self.y self.float_width = self.width self.float_height = self.height self._on_map_listener = Listener(self._on_map) self._on_unmap_listener = Listener(self._on_unmap) self._on_destroy_listener = Listener(self._on_destroy) self._on_request_fullscreen_listener = Listener( self._on_request_fullscreen) surface.map_event.add(self._on_map_listener) surface.unmap_event.add(self._on_unmap_listener) surface.destroy_event.add(self._on_destroy_listener) surface.toplevel.request_fullscreen_event.add( self._on_request_fullscreen_listener) def finalize(self): self._on_map_listener.remove() self._on_unmap_listener.remove() self._on_destroy_listener.remove() self._on_request_fullscreen_listener.remove() self.core.flush() @property def wid(self): return self._wid @property def width(self): return self.surface.surface.current.width @property def height(self): return self.surface.surface.current.height @property def group(self): return self._group @group.setter def group(self, index): self._group = index def _on_map(self, _listener, data): logger.debug("Signal: window map") self.mapped = True self.core.focus_window(self) def _on_unmap(self, _listener, data): logger.debug("Signal: window unmap") self.mapped = False if self.surface.surface == self.core.seat.keyboard_state.focused_surface: self.core.seat.keyboard_clear_focus() def _on_destroy(self, _listener, data): logger.debug("Signal: window destroy") self.qtile.unmanage(self.wid) self.finalize() def _on_request_fullscreen(self, _listener, event: xdg_shell.XdgTopLevelSetFullscreenEvent): logger.debug("Signal: window request_fullscreen") if self.qtile.config.auto_fullscreen: self.fullscreen = event.fullscreen def hide(self): if self.mapped: self.surface.unmap_event.emit() def unhide(self): if not self.mapped: self.surface.map_event.emit() def kill(self): self.surface.send_close() def get_wm_class(self) -> Optional[str]: # TODO return None def paint_borders(self, color, width) -> None: self.bordercolor = _rgb(color) self.borderwidth = width @property def floating(self): return self._float_state != FloatStates.NOT_FLOATING @floating.setter def floating(self, do_float): if do_float and self._float_state == FloatStates.NOT_FLOATING: if self.group and self.group.screen: screen = self.group.screen self._enablefloating(screen.x + self.float_x, screen.y + self.float_y, self.float_width, self.float_height) else: # if we are setting floating early, e.g. from a hook, we don't have a screen yet self._float_state = FloatStates.FLOATING elif (not do_float) and self._float_state != FloatStates.NOT_FLOATING: if self._float_state == FloatStates.FLOATING: # store last size self.float_width = self.width self.float_height = self.height self._float_state = FloatStates.NOT_FLOATING self.group.mark_floating(self, False) hook.fire('float_change') @property def fullscreen(self): return self._float_state == FloatStates.FULLSCREEN @fullscreen.setter def fullscreen(self, do_full): if do_full: screen = self.group.screen or \ self.qtile.find_closest_screen(self.x, self.y) self._enablefloating(screen.x, screen.y, screen.width, screen.height, new_float_state=FloatStates.FULLSCREEN) return if self._float_state == FloatStates.FULLSCREEN: self.floating = False @property def maximized(self): return self._float_state == FloatStates.MAXIMIZED @maximized.setter def maximized(self, do_maximize): if do_maximize: screen = self.group.screen or \ self.qtile.find_closest_screen(self.x, self.y) self._enablefloating(screen.dx, screen.dy, screen.dwidth, screen.dheight, new_float_state=FloatStates.MAXIMIZED) else: if self._float_state == FloatStates.MAXIMIZED: self.floating = False @property def minimized(self): return self._float_state == FloatStates.MINIMIZED @minimized.setter def minimized(self, do_minimize): if do_minimize: if self._float_state != FloatStates.MINIMIZED: self._enablefloating(new_float_state=FloatStates.MINIMIZED) else: if self._float_state == FloatStates.MINIMIZED: self.floating = False def focus(self, warp): self.core.focus_window(self) if warp: self.core.warp_pointer(self.x + self.width, self.y + self.height) def place(self, x, y, width, height, borderwidth, bordercolor, above=False, margin=None): # Adjust the placement to account for layout margins, if there are any. if margin is not None: if isinstance(margin, int): margin = [margin] * 4 x += margin[3] y += margin[0] width -= margin[1] + margin[3] height -= margin[0] + margin[2] self.x = x self.y = y self.surface.set_size(width, height) self.paint_borders(bordercolor, borderwidth) if above: # TODO when general z-axis control is implemented pass def _tweak_float(self, x=None, y=None, dx=0, dy=0, w=None, h=None, dw=0, dh=0): if x is None: x = self.x x += dx if y is None: y = self.y y += dy if w is None: w = self.width w += dw if h is None: h = self.height h += dh if h < 0: h = 0 if w < 0: w = 0 screen = self.qtile.find_closest_screen(self.x + self.width // 2, self.y + self.height // 2) if self.group and screen is not None and screen != self.group.screen: self.group.remove(self, force=True) screen.group.add(self, force=True) self.qtile.focus_screen(screen.index) self._reconfigure_floating(x, y, w, h) def _enablefloating(self, x=None, y=None, w=None, h=None, new_float_state=FloatStates.FLOATING): self._reconfigure_floating(x, y, w, h, new_float_state) def _reconfigure_floating(self, x, y, w, h, new_float_state=FloatStates.FLOATING): if new_float_state == FloatStates.MINIMIZED: self.hide() else: # TODO: Can we get min/max size, resizing increments etc and respect them? self.place( x, y, w, h, self.borderwidth, self.bordercolor, above=True, ) if self._float_state != new_float_state: self._float_state = new_float_state if self.group: # may be not, if it's called from hook self.group.mark_floating(self, True) hook.fire('float_change') def cmd_focus(self, warp=None): """Focuses the window.""" if warp is None: warp = self.qtile.config.cursor_warp self.focus(warp=warp) def cmd_info(self) -> Dict: """Return a dictionary of info.""" return dict(name=self.name, x=self.x, y=self.y, width=self.width, height=self.height, group=self.group.name if self.group else None, id=self.wid, floating=self._float_state != FloatStates.NOT_FLOATING, maximized=self._float_state == FloatStates.MAXIMIZED, minimized=self._float_state == FloatStates.MINIMIZED, fullscreen=self._float_state == FloatStates.FULLSCREEN) def cmd_move_floating(self, dx: int, dy: int) -> None: self._tweak_float(dx=dx, dy=dy) def cmd_resize_floating(self, dw: int, dh: int) -> None: self._tweak_float(dw=dw, dh=dh) def cmd_set_position_floating(self, x: int, y: int) -> None: self._tweak_float(x=x, y=y) def cmd_set_size_floating(self, w: int, h: int) -> None: self._tweak_float(w=w, h=h) def cmd_place(self, x, y, width, height, borderwidth, bordercolor, above=False, margin=None): self.place(x, y, width, height, borderwidth, bordercolor, above, margin) def cmd_get_position(self) -> Tuple[int, int]: return self.x, self.y def cmd_get_size(self) -> Tuple[int, int]: return self.width, self.height def cmd_toggle_floating(self) -> None: self.floating = not self.floating def cmd_enable_floating(self): self.floating = True def cmd_disable_floating(self): self.floating = False def cmd_toggle_maximize(self) -> None: self.maximized = not self.maximized def cmd_toggle_fullscreen(self) -> None: self.fullscreen = not self.fullscreen def cmd_enable_fullscreen(self) -> None: self.fullscreen = True def cmd_disable_fullscreen(self) -> None: self.fullscreen = False def cmd_bring_to_front(self) -> None: # TODO pass