Beispiel #1
0
 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()
Beispiel #2
0
    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))
Beispiel #3
0
    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))
Beispiel #4
0
    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)
Beispiel #5
0
    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))
Beispiel #6
0
    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)
Beispiel #7
0
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)
Beispiel #8
0
    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))
Beispiel #9
0
    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))
Beispiel #10
0
    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)
Beispiel #11
0
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()
Beispiel #12
0
 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)
Beispiel #13
0
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
Beispiel #14
0
    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()
Beispiel #15
0
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)
Beispiel #16
0
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