Compare commits
27 Commits
0fbf25d214
...
wlr-finder
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ecec8768d | |||
| 5d08a48b6c | |||
| 82b0cf7aaa | |||
| e4744bab81 | |||
| 872dbfc792 | |||
| 64781af68f | |||
| 0ebfbdb3a9 | |||
| bf3920ad35 | |||
| 72c76c9fda | |||
| 743e1ed0c5 | |||
| f28dd0b6a2 | |||
| 0b8190ae8b | |||
| 9495dfba62 | |||
| 0cf1c5aeb7 | |||
| f8b352d624 | |||
| 53713ee0f5 | |||
| 736e1a47c9 | |||
| 0966c1ce70 | |||
| 2541edd0f4 | |||
| ce030a8734 | |||
| 133dc74fb9 | |||
| 0cec84bec5 | |||
| 2f6fc3b59c | |||
| 03db9c4e4a | |||
| 62800f227d | |||
| 722a096c1e | |||
| 72b635ff42 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
.direnv
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Todo
|
||||||
|
- https://github.com/jlumpe/pyorg
|
||||||
|
- https://github.com/jlumpe/ox-json
|
||||||
173
bar/bar.css
173
bar/bar.css
@@ -1,173 +0,0 @@
|
|||||||
/* Fabric bar.css
|
|
||||||
* https://github.com/Fabric-Development/fabric/blob/rewrite/examples/bar/bar.css
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* we can use webcss variables, fabric compiles that to gtk css.
|
|
||||||
global variables can be stored in :vars */
|
|
||||||
:vars {
|
|
||||||
--background: #150d16;
|
|
||||||
--foreground: #f5dbc4;
|
|
||||||
--color0: #150d16;
|
|
||||||
--color1: #72448D;
|
|
||||||
--color2: #9C5995;
|
|
||||||
--color3: #D5719F;
|
|
||||||
--color4: #9A62F3;
|
|
||||||
--color5: #E075DF;
|
|
||||||
--color6: #F98F9F;
|
|
||||||
--color7: #f5dbc4;
|
|
||||||
--color8: #ab9989;
|
|
||||||
--color9: #72448D;
|
|
||||||
--color10: #9C5995;
|
|
||||||
--color11: #D5719F;
|
|
||||||
--color12: #9A62F3;
|
|
||||||
--color13: #E075DF;
|
|
||||||
--color14: #F98F9F;
|
|
||||||
--color15: #f5dbc4;
|
|
||||||
--window-bg: alpha(var(--background), 0.6);
|
|
||||||
--module-bg: alpha(var(--background), 0.4);
|
|
||||||
--border-color: var(--color11);
|
|
||||||
--ws-active: var(--color9);
|
|
||||||
--ws-inactive: var(--color3);
|
|
||||||
--ws-empty: var(--color8);
|
|
||||||
--ws-hover: var(--color1);
|
|
||||||
--ws-urgent: var(--color12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unset so we can style everything from the ground up. */
|
|
||||||
* {
|
|
||||||
all: unset;
|
|
||||||
color: var(--foreground);
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: "Jost*", sans-serif;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-size: 400% 400%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bar-inner {
|
|
||||||
padding: 4px;
|
|
||||||
border: solid 2px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces {
|
|
||||||
padding: 6px;
|
|
||||||
min-width: 0px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button {
|
|
||||||
padding: 0px 8px;
|
|
||||||
transition: padding 0.05s steps(8);
|
|
||||||
background-color: var(--ws-inactive);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button>label {
|
|
||||||
font-size: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button:hover {
|
|
||||||
background-color: var(--ws-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button.urgent {
|
|
||||||
background-color: var(--ws-urgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button.active {
|
|
||||||
padding: 0px 32px;
|
|
||||||
background-color: var(--ws-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button.empty {
|
|
||||||
background-color: var(--ws-empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#date-time,
|
|
||||||
#hyprland-language,
|
|
||||||
#hyprland-window {
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
padding: 0px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem>label,
|
|
||||||
#date-time>label,
|
|
||||||
#hyprland-language>label,
|
|
||||||
#hyprland-window>label {
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* system tray */
|
|
||||||
#system-tray {
|
|
||||||
padding: 2px 4px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* menu and menu items (written for the system tray) */
|
|
||||||
menu {
|
|
||||||
border: solid 2px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem {
|
|
||||||
border-radius: 0px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
padding: 6px;
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:first-child {
|
|
||||||
margin-top: 1px;
|
|
||||||
border-radius: 8px 8px 0px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:last-child {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
border-radius: 0px 0px 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:hover {
|
|
||||||
background-color: var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#cpu-progress-bar,
|
|
||||||
#ram-progress-bar,
|
|
||||||
#volume-progress-bar {
|
|
||||||
color: transparent;
|
|
||||||
background-color: transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#cpu-progress-bar {
|
|
||||||
border: solid 0px alpha(var(--color9), 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ram-progress-bar,
|
|
||||||
#volume-progress-bar {
|
|
||||||
border: solid 0px var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#widgets-container {
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tooltip {
|
|
||||||
border: solid 2px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
tooltip>* {
|
|
||||||
padding: 2px 4px
|
|
||||||
}
|
|
||||||
162
bar/bar.py
162
bar/bar.py
@@ -1,162 +0,0 @@
|
|||||||
# fabric bar.py example
|
|
||||||
# https://github.com/Fabric-Development/fabric/blob/rewrite/examples/bar/bar.py
|
|
||||||
import psutil
|
|
||||||
from fabric import Application
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from fabric.widgets.label import Label
|
|
||||||
from fabric.widgets.overlay import Overlay
|
|
||||||
from fabric.widgets.eventbox import EventBox
|
|
||||||
from fabric.widgets.datetime import DateTime
|
|
||||||
from fabric.widgets.centerbox import CenterBox
|
|
||||||
from fabric.system_tray.widgets import SystemTray
|
|
||||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
|
||||||
from fabric.widgets.wayland import WaylandWindow as Window
|
|
||||||
from .river.widgets import RiverWorkspaces, RiverWorkspaceButton
|
|
||||||
from fabric.utils import (
|
|
||||||
FormattedString,
|
|
||||||
bulk_replace,
|
|
||||||
invoke_repeater,
|
|
||||||
get_relative_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
AUDIO_WIDGET = True
|
|
||||||
|
|
||||||
if AUDIO_WIDGET is True:
|
|
||||||
try:
|
|
||||||
from fabric.audio.service import Audio
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
AUDIO_WIDGET = False
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeWidget(Box):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.audio = Audio()
|
|
||||||
|
|
||||||
self.progress_bar = CircularProgressBar(
|
|
||||||
name="volume-progress-bar", pie=True, size=24
|
|
||||||
)
|
|
||||||
|
|
||||||
self.event_box = EventBox(
|
|
||||||
events="scroll",
|
|
||||||
child=Overlay(
|
|
||||||
child=self.progress_bar,
|
|
||||||
overlays=Label(
|
|
||||||
label="",
|
|
||||||
style="margin: 0px 6px 0px 0px; font-size: 12px", # to center the icon glyph
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.audio.connect("notify::speaker", self.on_speaker_changed)
|
|
||||||
self.event_box.connect("scroll-event", self.on_scroll)
|
|
||||||
self.add(self.event_box)
|
|
||||||
|
|
||||||
def on_scroll(self, _, event):
|
|
||||||
match event.direction:
|
|
||||||
case 0:
|
|
||||||
self.audio.speaker.volume += 8
|
|
||||||
case 1:
|
|
||||||
self.audio.speaker.volume -= 8
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_speaker_changed(self, *_):
|
|
||||||
if not self.audio.speaker:
|
|
||||||
return
|
|
||||||
self.progress_bar.value = self.audio.speaker.volume / 100
|
|
||||||
self.audio.speaker.bind(
|
|
||||||
"volume", "value", self.progress_bar, lambda _, v: v / 100
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class StatusBar(Window):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name="bar",
|
|
||||||
layer="top",
|
|
||||||
anchor="left top right",
|
|
||||||
margin="10px 10px -2px 10px",
|
|
||||||
exclusivity="auto",
|
|
||||||
visible=False,
|
|
||||||
all_visible=False,
|
|
||||||
)
|
|
||||||
self.workspaces = RiverWorkspaces(
|
|
||||||
44,
|
|
||||||
name="workspaces",
|
|
||||||
spacing=4,
|
|
||||||
buttons_factory=lambda ws_id: RiverWorkspaceButton(id=ws_id, label=None),
|
|
||||||
)
|
|
||||||
self.date_time = DateTime(name="date-time")
|
|
||||||
self.system_tray = SystemTray(name="system-tray", spacing=4)
|
|
||||||
|
|
||||||
self.ram_progress_bar = CircularProgressBar(
|
|
||||||
name="ram-progress-bar", pie=True, size=24
|
|
||||||
)
|
|
||||||
self.cpu_progress_bar = CircularProgressBar(
|
|
||||||
name="cpu-progress-bar", pie=True, size=24
|
|
||||||
)
|
|
||||||
self.progress_bars_overlay = Overlay(
|
|
||||||
child=self.ram_progress_bar,
|
|
||||||
overlays=[
|
|
||||||
self.cpu_progress_bar,
|
|
||||||
Label("", style="margin: 0px 6px 0px 0px; font-size: 12px"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.status_container = Box(
|
|
||||||
name="widgets-container",
|
|
||||||
spacing=4,
|
|
||||||
orientation="h",
|
|
||||||
children=self.progress_bars_overlay,
|
|
||||||
)
|
|
||||||
self.status_container.add(VolumeWidget()) if AUDIO_WIDGET is True else None
|
|
||||||
|
|
||||||
self.children = CenterBox(
|
|
||||||
name="bar-inner",
|
|
||||||
start_children=Box(
|
|
||||||
name="start-container",
|
|
||||||
spacing=4,
|
|
||||||
orientation="h",
|
|
||||||
children=self.workspaces,
|
|
||||||
),
|
|
||||||
center_children=Box(
|
|
||||||
name="center-container",
|
|
||||||
spacing=4,
|
|
||||||
orientation="h",
|
|
||||||
),
|
|
||||||
end_children=Box(
|
|
||||||
name="end-container",
|
|
||||||
spacing=4,
|
|
||||||
orientation="h",
|
|
||||||
children=[
|
|
||||||
self.status_container,
|
|
||||||
self.system_tray,
|
|
||||||
self.date_time,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
invoke_repeater(1000, self.update_progress_bars)
|
|
||||||
|
|
||||||
self.show_all()
|
|
||||||
|
|
||||||
def update_progress_bars(self):
|
|
||||||
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
|
||||||
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
bar = StatusBar()
|
|
||||||
app = Application("bar", bar)
|
|
||||||
app.set_stylesheet_from_file(get_relative_path("bar.css"))
|
|
||||||
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
52
bar/config.py
Normal file
52
bar/config.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
from platformdirs import user_config_dir
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
APP_NAME = "makku_bar"
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME = user_config_dir(appname=APP_NAME)
|
||||||
|
XDG_CONFIG_FILE = os.path.join(XDG_CONFIG_HOME, "config.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(config_path=XDG_CONFIG_FILE):
|
||||||
|
"""Loads configuration from a YAML file."""
|
||||||
|
if config_path is None:
|
||||||
|
print("No configuration file path provided or found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
return config
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Configuration file not found at {config_path}")
|
||||||
|
return None
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"Error parsing YAML file '{config_path}': {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred loading config file '{config_path}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_args():
|
||||||
|
parser = argparse.ArgumentParser(description="makku_bar")
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
help="Path to a custom configuration file.",
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args.config
|
||||||
|
|
||||||
|
|
||||||
|
app_config = load_config() if not load_args() else load_config(load_args())
|
||||||
|
|
||||||
|
if app_config is None:
|
||||||
|
raise Exception("Config file missing")
|
||||||
|
|
||||||
|
VINYL = app_config.get("vinyl", {"enable": False})
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from .wl_buffer import WlBuffer # noqa: F401
|
|
||||||
from .wl_callback import WlCallback # noqa: F401
|
|
||||||
from .wl_compositor import WlCompositor # noqa: F401
|
|
||||||
from .wl_data_device import WlDataDevice # noqa: F401
|
|
||||||
from .wl_data_device_manager import WlDataDeviceManager # noqa: F401
|
|
||||||
from .wl_data_offer import WlDataOffer # noqa: F401
|
|
||||||
from .wl_data_source import WlDataSource # noqa: F401
|
|
||||||
from .wl_display import WlDisplay # noqa: F401
|
|
||||||
from .wl_keyboard import WlKeyboard # noqa: F401
|
|
||||||
from .wl_output import WlOutput # noqa: F401
|
|
||||||
from .wl_pointer import WlPointer # noqa: F401
|
|
||||||
from .wl_region import WlRegion # noqa: F401
|
|
||||||
from .wl_registry import WlRegistry # noqa: F401
|
|
||||||
from .wl_seat import WlSeat # noqa: F401
|
|
||||||
from .wl_shell import WlShell # noqa: F401
|
|
||||||
from .wl_shell_surface import WlShellSurface # noqa: F401
|
|
||||||
from .wl_shm import WlShm # noqa: F401
|
|
||||||
from .wl_shm_pool import WlShmPool # noqa: F401
|
|
||||||
from .wl_subcompositor import WlSubcompositor # noqa: F401
|
|
||||||
from .wl_subsurface import WlSubsurface # noqa: F401
|
|
||||||
from .wl_surface import WlSurface # noqa: F401
|
|
||||||
from .wl_touch import WlTouch # noqa: F401
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import Global, Interface, Proxy, Resource
|
|
||||||
|
|
||||||
|
|
||||||
class WlBuffer(Interface):
|
|
||||||
"""Content for a :class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
|
|
||||||
A buffer provides the content for a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`. Buffers are created through
|
|
||||||
factory interfaces such as :class:`~pywayland.protocol.wayland.WlShm`,
|
|
||||||
wp_linux_buffer_params (from the linux-dmabuf protocol extension) or
|
|
||||||
similar. It has a width and a height and can be attached to a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`, but the mechanism by which
|
|
||||||
a client provides and updates the contents is defined by the buffer factory
|
|
||||||
interface.
|
|
||||||
|
|
||||||
If the buffer uses a format that has an alpha channel, the alpha channel is
|
|
||||||
assumed to be premultiplied in the color channels unless otherwise
|
|
||||||
specified.
|
|
||||||
|
|
||||||
Note, because :class:`WlBuffer` objects are created from multiple
|
|
||||||
independent factory interfaces, the :class:`WlBuffer` interface is frozen
|
|
||||||
at version 1.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_buffer"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlBufferProxy(Proxy[WlBuffer]):
|
|
||||||
interface = WlBuffer
|
|
||||||
|
|
||||||
@WlBuffer.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy a buffer
|
|
||||||
|
|
||||||
Destroy a buffer. If and how you need to release the backing storage is
|
|
||||||
defined by the buffer factory interface.
|
|
||||||
|
|
||||||
For possible side-effects to a surface, see :func:`WlSurface.attach()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.attach>`.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlBufferResource(Resource):
|
|
||||||
interface = WlBuffer
|
|
||||||
|
|
||||||
@WlBuffer.event()
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Compositor releases buffer
|
|
||||||
|
|
||||||
Sent when this :class:`WlBuffer` is no longer used by the compositor.
|
|
||||||
The client is now free to reuse or destroy this buffer and its backing
|
|
||||||
storage.
|
|
||||||
|
|
||||||
If a client receives a release event before the frame callback
|
|
||||||
requested in the same :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` that attaches this
|
|
||||||
:class:`WlBuffer` to a surface, then the client is immediately free to
|
|
||||||
reuse the buffer and its backing storage, and does not need a second
|
|
||||||
buffer for the next surface content update. Typically this is possible,
|
|
||||||
when the compositor maintains a copy of the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` contents, e.g. as a GL
|
|
||||||
texture. This is an important optimization for GL(ES) compositors with
|
|
||||||
:class:`~pywayland.protocol.wayland.WlShm` clients.
|
|
||||||
"""
|
|
||||||
self._post_event(0)
|
|
||||||
|
|
||||||
|
|
||||||
class WlBufferGlobal(Global):
|
|
||||||
interface = WlBuffer
|
|
||||||
|
|
||||||
|
|
||||||
WlBuffer._gen_c()
|
|
||||||
WlBuffer.proxy_class = WlBufferProxy
|
|
||||||
WlBuffer.resource_class = WlBufferResource
|
|
||||||
WlBuffer.global_class = WlBufferGlobal
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WlCallback(Interface):
|
|
||||||
"""Callback object
|
|
||||||
|
|
||||||
Clients can handle the 'done' event to get notified when the related
|
|
||||||
request is done.
|
|
||||||
|
|
||||||
Note, because :class:`WlCallback` objects are created from multiple
|
|
||||||
independent factory interfaces, the :class:`WlCallback` interface is frozen
|
|
||||||
at version 1.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_callback"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlCallbackProxy(Proxy[WlCallback]):
|
|
||||||
interface = WlCallback
|
|
||||||
|
|
||||||
|
|
||||||
class WlCallbackResource(Resource):
|
|
||||||
interface = WlCallback
|
|
||||||
|
|
||||||
@WlCallback.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def done(self, callback_data: int) -> None:
|
|
||||||
"""Done event
|
|
||||||
|
|
||||||
Notify the client when the related request is done.
|
|
||||||
|
|
||||||
:param callback_data:
|
|
||||||
request-specific data for the callback
|
|
||||||
:type callback_data:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, callback_data)
|
|
||||||
|
|
||||||
|
|
||||||
class WlCallbackGlobal(Global):
|
|
||||||
interface = WlCallback
|
|
||||||
|
|
||||||
|
|
||||||
WlCallback._gen_c()
|
|
||||||
WlCallback.proxy_class = WlCallbackProxy
|
|
||||||
WlCallback.resource_class = WlCallbackResource
|
|
||||||
WlCallback.global_class = WlCallbackGlobal
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_region import WlRegion
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlCompositor(Interface):
|
|
||||||
"""The compositor singleton
|
|
||||||
|
|
||||||
A compositor. This object is a singleton global. The compositor is in
|
|
||||||
charge of combining the contents of multiple surfaces into one displayable
|
|
||||||
output.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_compositor"
|
|
||||||
version = 6
|
|
||||||
|
|
||||||
|
|
||||||
class WlCompositorProxy(Proxy[WlCompositor]):
|
|
||||||
interface = WlCompositor
|
|
||||||
|
|
||||||
@WlCompositor.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def create_surface(self) -> Proxy[WlSurface]:
|
|
||||||
"""Create new surface
|
|
||||||
|
|
||||||
Ask the compositor to create a new surface.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` -- the new surface
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlSurface)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlCompositor.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlRegion),
|
|
||||||
)
|
|
||||||
def create_region(self) -> Proxy[WlRegion]:
|
|
||||||
"""Create new region
|
|
||||||
|
|
||||||
Ask the compositor to create a new region.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegion` -- the new region
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(1, WlRegion)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlCompositorResource(Resource):
|
|
||||||
interface = WlCompositor
|
|
||||||
|
|
||||||
|
|
||||||
class WlCompositorGlobal(Global):
|
|
||||||
interface = WlCompositor
|
|
||||||
|
|
||||||
|
|
||||||
WlCompositor._gen_c()
|
|
||||||
WlCompositor.proxy_class = WlCompositorProxy
|
|
||||||
WlCompositor.resource_class = WlCompositorResource
|
|
||||||
WlCompositor.global_class = WlCompositorGlobal
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_data_offer import WlDataOffer
|
|
||||||
from .wl_data_source import WlDataSource
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDevice(Interface):
|
|
||||||
"""Data transfer device
|
|
||||||
|
|
||||||
There is one :class:`WlDataDevice` per seat which can be obtained from the
|
|
||||||
global :class:`~pywayland.protocol.wayland.WlDataDeviceManager` singleton.
|
|
||||||
|
|
||||||
A :class:`WlDataDevice` provides access to inter-client data transfer
|
|
||||||
mechanisms such as copy-and-paste and drag-and-drop.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_data_device"
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
role = 0
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceProxy(Proxy[WlDataDevice]):
|
|
||||||
interface = WlDataDevice
|
|
||||||
|
|
||||||
@WlDataDevice.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlDataSource, nullable=True),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface, nullable=True),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def start_drag(self, source: WlDataSource | None, origin: WlSurface, icon: WlSurface | None, serial: int) -> None:
|
|
||||||
"""Start drag-and-drop operation
|
|
||||||
|
|
||||||
This request asks the compositor to start a drag-and-drop operation on
|
|
||||||
behalf of the client.
|
|
||||||
|
|
||||||
The source argument is the data source that provides the data for the
|
|
||||||
eventual data transfer. If source is NULL, enter, leave and motion
|
|
||||||
events are sent only to the client that initiated the drag and the
|
|
||||||
client is expected to handle the data passing internally. If source is
|
|
||||||
destroyed, the drag-and-drop session will be cancelled.
|
|
||||||
|
|
||||||
The origin surface is the surface where the drag originates and the
|
|
||||||
client must have an active implicit grab that matches the serial.
|
|
||||||
|
|
||||||
The icon surface is an optional (can be NULL) surface that provides an
|
|
||||||
icon to be moved around with the cursor. Initially, the top-left
|
|
||||||
corner of the icon surface is placed at the cursor hotspot, but
|
|
||||||
subsequent :func:`WlSurface.attach()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.attach>` request can move the
|
|
||||||
relative position. Attach requests must be confirmed with
|
|
||||||
:func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` as usual. The icon
|
|
||||||
surface is given the role of a drag-and-drop icon. If the icon surface
|
|
||||||
already has another role, it raises a protocol error.
|
|
||||||
|
|
||||||
The input region is ignored for wl_surfaces with the role of a drag-
|
|
||||||
and-drop icon.
|
|
||||||
|
|
||||||
:param source:
|
|
||||||
data source for the eventual transfer
|
|
||||||
:type source:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataSource` or `None`
|
|
||||||
:param origin:
|
|
||||||
surface where the drag originates
|
|
||||||
:type origin:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param icon:
|
|
||||||
drag-and-drop icon surface
|
|
||||||
:type icon:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` or `None`
|
|
||||||
:param serial:
|
|
||||||
serial number of the implicit grab on the origin
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(0, source, origin, icon, serial)
|
|
||||||
|
|
||||||
@WlDataDevice.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlDataSource, nullable=True),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def set_selection(self, source: WlDataSource | None, serial: int) -> None:
|
|
||||||
"""Copy data to the selection
|
|
||||||
|
|
||||||
This request asks the compositor to set the selection to the data from
|
|
||||||
the source on behalf of the client.
|
|
||||||
|
|
||||||
To unset the selection, set the source to NULL.
|
|
||||||
|
|
||||||
:param source:
|
|
||||||
data source for the selection
|
|
||||||
:type source:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataSource` or `None`
|
|
||||||
:param serial:
|
|
||||||
serial number of the event that triggered this request
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(1, source, serial)
|
|
||||||
|
|
||||||
@WlDataDevice.request(version=2)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Destroy data device
|
|
||||||
|
|
||||||
This request destroys the data device.
|
|
||||||
"""
|
|
||||||
self._marshal(2)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceResource(Resource):
|
|
||||||
interface = WlDataDevice
|
|
||||||
|
|
||||||
@WlDataDevice.event(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlDataOffer),
|
|
||||||
)
|
|
||||||
def data_offer(self, id: WlDataOffer) -> None:
|
|
||||||
"""Introduce a new :class:`~pywayland.protocol.wayland.WlDataOffer`
|
|
||||||
|
|
||||||
The data_offer event introduces a new
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer` object, which will
|
|
||||||
subsequently be used in either the data_device.enter event (for drag-
|
|
||||||
and-drop) or the data_device.selection event (for selections).
|
|
||||||
Immediately following the data_device.data_offer event, the new
|
|
||||||
data_offer object will send out data_offer.offer events to describe the
|
|
||||||
mime types it offers.
|
|
||||||
|
|
||||||
:param id:
|
|
||||||
the new data_offer object
|
|
||||||
:type id:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer`
|
|
||||||
"""
|
|
||||||
self._post_event(0, id)
|
|
||||||
|
|
||||||
@WlDataDevice.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Object, interface=WlDataOffer, nullable=True),
|
|
||||||
)
|
|
||||||
def enter(self, serial: int, surface: WlSurface, x: float, y: float, id: WlDataOffer | None) -> None:
|
|
||||||
"""Initiate drag-and-drop session
|
|
||||||
|
|
||||||
This event is sent when an active drag-and-drop pointer enters a
|
|
||||||
surface owned by the client. The position of the pointer at enter time
|
|
||||||
is provided by the x and y arguments, in surface-local coordinates.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the enter event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
client surface entered
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param id:
|
|
||||||
source data_offer object
|
|
||||||
:type id:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer` or `None`
|
|
||||||
"""
|
|
||||||
self._post_event(1, serial, surface, x, y, id)
|
|
||||||
|
|
||||||
@WlDataDevice.event()
|
|
||||||
def leave(self) -> None:
|
|
||||||
"""End drag-and-drop session
|
|
||||||
|
|
||||||
This event is sent when the drag-and-drop pointer leaves the surface
|
|
||||||
and the session ends. The client must destroy the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer` introduced at enter
|
|
||||||
time at this point.
|
|
||||||
"""
|
|
||||||
self._post_event(2)
|
|
||||||
|
|
||||||
@WlDataDevice.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def motion(self, time: int, x: float, y: float) -> None:
|
|
||||||
"""Drag-and-drop session motion
|
|
||||||
|
|
||||||
This event is sent when the drag-and-drop pointer moves within the
|
|
||||||
currently focused surface. The new position of the pointer is provided
|
|
||||||
by the x and y arguments, in surface-local coordinates.
|
|
||||||
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(3, time, x, y)
|
|
||||||
|
|
||||||
@WlDataDevice.event()
|
|
||||||
def drop(self) -> None:
|
|
||||||
"""End drag-and-drop session successfully
|
|
||||||
|
|
||||||
The event is sent when a drag-and-drop operation is ended because the
|
|
||||||
implicit grab is removed.
|
|
||||||
|
|
||||||
The drag-and-drop destination is expected to honor the last action
|
|
||||||
received through :func:`WlDataOffer.action()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.action>`, if the resulting
|
|
||||||
action is "copy" or "move", the destination can still perform
|
|
||||||
:func:`WlDataOffer.receive()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.receive>` requests, and is
|
|
||||||
expected to end all transfers with a :func:`WlDataOffer.finish()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.finish>` request.
|
|
||||||
|
|
||||||
If the resulting action is "ask", the action will not be considered
|
|
||||||
final. The drag-and-drop destination is expected to perform one last
|
|
||||||
:func:`WlDataOffer.set_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.set_actions>` request, or
|
|
||||||
:func:`WlDataOffer.destroy()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.destroy>` in order to cancel
|
|
||||||
the operation.
|
|
||||||
"""
|
|
||||||
self._post_event(4)
|
|
||||||
|
|
||||||
@WlDataDevice.event(
|
|
||||||
Argument(ArgumentType.Object, interface=WlDataOffer, nullable=True),
|
|
||||||
)
|
|
||||||
def selection(self, id: WlDataOffer | None) -> None:
|
|
||||||
"""Advertise new selection
|
|
||||||
|
|
||||||
The selection event is sent out to notify the client of a new
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer` for the selection for
|
|
||||||
this device. The data_device.data_offer and the data_offer.offer
|
|
||||||
events are sent out immediately before this event to introduce the data
|
|
||||||
offer object. The selection event is sent to a client immediately
|
|
||||||
before receiving keyboard focus and when a new selection is set while
|
|
||||||
the client has keyboard focus. The data_offer is valid until a new
|
|
||||||
data_offer or NULL is received or until the client loses keyboard
|
|
||||||
focus. Switching surface with keyboard focus within the same client
|
|
||||||
doesn't mean a new selection will be sent. The client must destroy the
|
|
||||||
previous selection data_offer, if any, upon receiving this event.
|
|
||||||
|
|
||||||
:param id:
|
|
||||||
selection data_offer object
|
|
||||||
:type id:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer` or `None`
|
|
||||||
"""
|
|
||||||
self._post_event(5, id)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceGlobal(Global):
|
|
||||||
interface = WlDataDevice
|
|
||||||
|
|
||||||
|
|
||||||
WlDataDevice._gen_c()
|
|
||||||
WlDataDevice.proxy_class = WlDataDeviceProxy
|
|
||||||
WlDataDevice.resource_class = WlDataDeviceResource
|
|
||||||
WlDataDevice.global_class = WlDataDeviceGlobal
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_data_device import WlDataDevice
|
|
||||||
from .wl_data_source import WlDataSource
|
|
||||||
from .wl_seat import WlSeat
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceManager(Interface):
|
|
||||||
"""Data transfer interface
|
|
||||||
|
|
||||||
The :class:`WlDataDeviceManager` is a singleton global object that provides
|
|
||||||
access to inter-client data transfer mechanisms such as copy-and-paste and
|
|
||||||
drag-and-drop. These mechanisms are tied to a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat` and this interface lets a
|
|
||||||
client get a :class:`~pywayland.protocol.wayland.WlDataDevice`
|
|
||||||
corresponding to a :class:`~pywayland.protocol.wayland.WlSeat`.
|
|
||||||
|
|
||||||
Depending on the version bound, the objects created from the bound
|
|
||||||
:class:`WlDataDeviceManager` object will have different requirements for
|
|
||||||
functioning properly. See :func:`WlDataSource.set_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.set_actions>`,
|
|
||||||
:func:`WlDataOffer.accept()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.accept>` and
|
|
||||||
:func:`WlDataOffer.finish()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.finish>` for details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_data_device_manager"
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
class dnd_action(enum.IntFlag):
|
|
||||||
none = 0
|
|
||||||
copy = 1
|
|
||||||
move = 2
|
|
||||||
ask = 4
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceManagerProxy(Proxy[WlDataDeviceManager]):
|
|
||||||
interface = WlDataDeviceManager
|
|
||||||
|
|
||||||
@WlDataDeviceManager.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlDataSource),
|
|
||||||
)
|
|
||||||
def create_data_source(self) -> Proxy[WlDataSource]:
|
|
||||||
"""Create a new data source
|
|
||||||
|
|
||||||
Create a new data source.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataSource` -- data source to
|
|
||||||
create
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlDataSource)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlDataDeviceManager.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlDataDevice),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
)
|
|
||||||
def get_data_device(self, seat: WlSeat) -> Proxy[WlDataDevice]:
|
|
||||||
"""Create a new data device
|
|
||||||
|
|
||||||
Create a new data device for a given seat.
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
seat associated with the data device
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataDevice` -- data device to
|
|
||||||
create
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(1, WlDataDevice, seat)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceManagerResource(Resource):
|
|
||||||
interface = WlDataDeviceManager
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataDeviceManagerGlobal(Global):
|
|
||||||
interface = WlDataDeviceManager
|
|
||||||
|
|
||||||
|
|
||||||
WlDataDeviceManager._gen_c()
|
|
||||||
WlDataDeviceManager.proxy_class = WlDataDeviceManagerProxy
|
|
||||||
WlDataDeviceManager.resource_class = WlDataDeviceManagerResource
|
|
||||||
WlDataDeviceManager.global_class = WlDataDeviceManagerGlobal
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataOffer(Interface):
|
|
||||||
"""Offer to transfer data
|
|
||||||
|
|
||||||
A :class:`WlDataOffer` represents a piece of data offered for transfer by
|
|
||||||
another client (the source client). It is used by the copy-and-paste and
|
|
||||||
drag-and-drop mechanisms. The offer describes the different mime types
|
|
||||||
that the data can be converted to and provides the mechanism for
|
|
||||||
transferring the data directly from the source client.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_data_offer"
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
invalid_finish = 0
|
|
||||||
invalid_action_mask = 1
|
|
||||||
invalid_action = 2
|
|
||||||
invalid_offer = 3
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataOfferProxy(Proxy[WlDataOffer]):
|
|
||||||
interface = WlDataOffer
|
|
||||||
|
|
||||||
@WlDataOffer.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.String, nullable=True),
|
|
||||||
)
|
|
||||||
def accept(self, serial: int, mime_type: str | None) -> None:
|
|
||||||
"""Accept one of the offered mime types
|
|
||||||
|
|
||||||
Indicate that the client can accept the given mime type, or NULL for
|
|
||||||
not accepted.
|
|
||||||
|
|
||||||
For objects of version 2 or older, this request is used by the client
|
|
||||||
to give feedback whether the client can receive the given mime type, or
|
|
||||||
NULL if none is accepted; the feedback does not determine whether the
|
|
||||||
drag-and-drop operation succeeds or not.
|
|
||||||
|
|
||||||
For objects of version 3 or newer, this request determines the final
|
|
||||||
result of the drag-and-drop operation. If the end result is that no
|
|
||||||
mime types were accepted, the drag-and-drop operation will be cancelled
|
|
||||||
and the corresponding drag source will receive
|
|
||||||
:func:`WlDataSource.cancelled()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.cancelled>`. Clients may still
|
|
||||||
use this event in conjunction with :func:`WlDataSource.action()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.action>` for feedback.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the accept request
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param mime_type:
|
|
||||||
mime type accepted by the client
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String` or `None`
|
|
||||||
"""
|
|
||||||
self._marshal(0, serial, mime_type)
|
|
||||||
|
|
||||||
@WlDataOffer.request(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
Argument(ArgumentType.FileDescriptor),
|
|
||||||
)
|
|
||||||
def receive(self, mime_type: str, fd: int) -> None:
|
|
||||||
"""Request that the data is transferred
|
|
||||||
|
|
||||||
To transfer the offered data, the client issues this request and
|
|
||||||
indicates the mime type it wants to receive. The transfer happens
|
|
||||||
through the passed file descriptor (typically created with the pipe
|
|
||||||
system call). The source client writes the data in the mime type
|
|
||||||
representation requested and then closes the file descriptor.
|
|
||||||
|
|
||||||
The receiving client reads from the read end of the pipe until EOF and
|
|
||||||
then closes its end, at which point the transfer is complete.
|
|
||||||
|
|
||||||
This request may happen multiple times for different mime types, both
|
|
||||||
before and after :func:`WlDataDevice.drop()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.drop>`. Drag-and-drop
|
|
||||||
destination clients may preemptively fetch data or examine it more
|
|
||||||
closely to determine acceptance.
|
|
||||||
|
|
||||||
:param mime_type:
|
|
||||||
mime type desired by receiver
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String`
|
|
||||||
:param fd:
|
|
||||||
file descriptor for data transfer
|
|
||||||
:type fd:
|
|
||||||
`ArgumentType.FileDescriptor`
|
|
||||||
"""
|
|
||||||
self._marshal(1, mime_type, fd)
|
|
||||||
|
|
||||||
@WlDataOffer.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy data offer
|
|
||||||
|
|
||||||
Destroy the data offer.
|
|
||||||
"""
|
|
||||||
self._marshal(2)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlDataOffer.request(version=3)
|
|
||||||
def finish(self) -> None:
|
|
||||||
"""The offer will no longer be used
|
|
||||||
|
|
||||||
Notifies the compositor that the drag destination successfully finished
|
|
||||||
the drag-and-drop operation.
|
|
||||||
|
|
||||||
Upon receiving this request, the compositor will emit
|
|
||||||
:func:`WlDataSource.dnd_finished()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.dnd_finished>` on the drag
|
|
||||||
source client.
|
|
||||||
|
|
||||||
It is a client error to perform other requests than
|
|
||||||
:func:`WlDataOffer.destroy()` after this one. It is also an error to
|
|
||||||
perform this request after a NULL mime type has been set in
|
|
||||||
:func:`WlDataOffer.accept()` or no action was received through
|
|
||||||
:func:`WlDataOffer.action()`.
|
|
||||||
|
|
||||||
If :func:`WlDataOffer.finish()` request is received for a non drag and
|
|
||||||
drop operation, the invalid_finish protocol error is raised.
|
|
||||||
"""
|
|
||||||
self._marshal(3)
|
|
||||||
|
|
||||||
@WlDataOffer.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def set_actions(self, dnd_actions: int, preferred_action: int) -> None:
|
|
||||||
"""Set the available/preferred drag-and-drop actions
|
|
||||||
|
|
||||||
Sets the actions that the destination side client supports for this
|
|
||||||
operation. This request may trigger the emission of
|
|
||||||
:func:`WlDataSource.action()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.action>` and
|
|
||||||
:func:`WlDataOffer.action()` events if the compositor needs to change
|
|
||||||
the selected action.
|
|
||||||
|
|
||||||
This request can be called multiple times throughout the drag-and-drop
|
|
||||||
operation, typically in response to :func:`WlDataDevice.enter()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.enter>` or
|
|
||||||
:func:`WlDataDevice.motion()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.motion>` events.
|
|
||||||
|
|
||||||
This request determines the final result of the drag-and-drop
|
|
||||||
operation. If the end result is that no action is accepted, the drag
|
|
||||||
source will receive :func:`WlDataSource.cancelled()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.cancelled>`.
|
|
||||||
|
|
||||||
The dnd_actions argument must contain only values expressed in the
|
|
||||||
:func:`WlDataDeviceManager.dnd_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataDeviceManager.dnd_actions>` enum, and
|
|
||||||
the preferred_action argument must only contain one of those values
|
|
||||||
set, otherwise it will result in a protocol error.
|
|
||||||
|
|
||||||
While managing an "ask" action, the destination drag-and-drop client
|
|
||||||
may perform further :func:`WlDataOffer.receive()` requests, and is
|
|
||||||
expected to perform one last :func:`WlDataOffer.set_actions()` request
|
|
||||||
with a preferred action other than "ask" (and optionally
|
|
||||||
:func:`WlDataOffer.accept()`) before requesting
|
|
||||||
:func:`WlDataOffer.finish()`, in order to convey the action selected by
|
|
||||||
the user. If the preferred action is not in the
|
|
||||||
:func:`WlDataOffer.source_actions()` mask, an error will be raised.
|
|
||||||
|
|
||||||
If the "ask" action is dismissed (e.g. user cancellation), the client
|
|
||||||
is expected to perform :func:`WlDataOffer.destroy()` right away.
|
|
||||||
|
|
||||||
This request can only be made on drag-and-drop offers, a protocol error
|
|
||||||
will be raised otherwise.
|
|
||||||
|
|
||||||
:param dnd_actions:
|
|
||||||
actions supported by the destination client
|
|
||||||
:type dnd_actions:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param preferred_action:
|
|
||||||
action preferred by the destination client
|
|
||||||
:type preferred_action:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(4, dnd_actions, preferred_action)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataOfferResource(Resource):
|
|
||||||
interface = WlDataOffer
|
|
||||||
|
|
||||||
@WlDataOffer.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def offer(self, mime_type: str) -> None:
|
|
||||||
"""Advertise offered mime type
|
|
||||||
|
|
||||||
Sent immediately after creating the :class:`WlDataOffer` object. One
|
|
||||||
event per offered mime type.
|
|
||||||
|
|
||||||
:param mime_type:
|
|
||||||
offered mime type
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(0, mime_type)
|
|
||||||
|
|
||||||
@WlDataOffer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def source_actions(self, source_actions: int) -> None:
|
|
||||||
"""Notify the source-side available actions
|
|
||||||
|
|
||||||
This event indicates the actions offered by the data source. It will be
|
|
||||||
sent immediately after creating the :class:`WlDataOffer` object, or
|
|
||||||
anytime the source side changes its offered actions through
|
|
||||||
:func:`WlDataSource.set_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataSource.set_actions>`.
|
|
||||||
|
|
||||||
:param source_actions:
|
|
||||||
actions offered by the data source
|
|
||||||
:type source_actions:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(1, source_actions)
|
|
||||||
|
|
||||||
@WlDataOffer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def action(self, dnd_action: int) -> None:
|
|
||||||
"""Notify the selected action
|
|
||||||
|
|
||||||
This event indicates the action selected by the compositor after
|
|
||||||
matching the source/destination side actions. Only one action (or none)
|
|
||||||
will be offered here.
|
|
||||||
|
|
||||||
This event can be emitted multiple times during the drag-and-drop
|
|
||||||
operation in response to destination side action changes through
|
|
||||||
:func:`WlDataOffer.set_actions()`.
|
|
||||||
|
|
||||||
This event will no longer be emitted after :func:`WlDataDevice.drop()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.drop>` happened on the drag-
|
|
||||||
and-drop destination, the client must honor the last action received,
|
|
||||||
or the last preferred one set through :func:`WlDataOffer.set_actions()`
|
|
||||||
when handling an "ask" action.
|
|
||||||
|
|
||||||
Compositors may also change the selected action on the fly, mainly in
|
|
||||||
response to keyboard modifier changes during the drag-and-drop
|
|
||||||
operation.
|
|
||||||
|
|
||||||
The most recent action received is always the valid one. Prior to
|
|
||||||
receiving :func:`WlDataDevice.drop()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.drop>`, the chosen action may
|
|
||||||
change (e.g. due to keyboard modifiers being pressed). At the time of
|
|
||||||
receiving :func:`WlDataDevice.drop()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.drop>` the drag-and-drop
|
|
||||||
destination must honor the last action received.
|
|
||||||
|
|
||||||
Action changes may still happen after :func:`WlDataDevice.drop()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.drop>`, especially on "ask"
|
|
||||||
actions, where the drag-and-drop destination may choose another action
|
|
||||||
afterwards. Action changes happening at this stage are always the
|
|
||||||
result of inter-client negotiation, the compositor shall no longer be
|
|
||||||
able to induce a different action.
|
|
||||||
|
|
||||||
Upon "ask" actions, it is expected that the drag-and-drop destination
|
|
||||||
may potentially choose a different action and/or mime type, based on
|
|
||||||
:func:`WlDataOffer.source_actions()` and finally chosen by the user
|
|
||||||
(e.g. popping up a menu with the available options). The final
|
|
||||||
:func:`WlDataOffer.set_actions()` and :func:`WlDataOffer.accept()`
|
|
||||||
requests must happen before the call to :func:`WlDataOffer.finish()`.
|
|
||||||
|
|
||||||
:param dnd_action:
|
|
||||||
action selected by the compositor
|
|
||||||
:type dnd_action:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(2, dnd_action)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataOfferGlobal(Global):
|
|
||||||
interface = WlDataOffer
|
|
||||||
|
|
||||||
|
|
||||||
WlDataOffer._gen_c()
|
|
||||||
WlDataOffer.proxy_class = WlDataOfferProxy
|
|
||||||
WlDataOffer.resource_class = WlDataOfferResource
|
|
||||||
WlDataOffer.global_class = WlDataOfferGlobal
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataSource(Interface):
|
|
||||||
"""Offer to transfer data
|
|
||||||
|
|
||||||
The :class:`WlDataSource` object is the source side of a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlDataOffer`. It is created by the
|
|
||||||
source client in a data transfer and provides a way to describe the offered
|
|
||||||
data and a way to respond to requests to transfer the data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_data_source"
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
invalid_action_mask = 0
|
|
||||||
invalid_source = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataSourceProxy(Proxy[WlDataSource]):
|
|
||||||
interface = WlDataSource
|
|
||||||
|
|
||||||
@WlDataSource.request(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def offer(self, mime_type: str) -> None:
|
|
||||||
"""Add an offered mime type
|
|
||||||
|
|
||||||
This request adds a mime type to the set of mime types advertised to
|
|
||||||
targets. Can be called several times to offer multiple types.
|
|
||||||
|
|
||||||
:param mime_type:
|
|
||||||
mime type offered by the data source
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._marshal(0, mime_type)
|
|
||||||
|
|
||||||
@WlDataSource.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the data source
|
|
||||||
|
|
||||||
Destroy the data source.
|
|
||||||
"""
|
|
||||||
self._marshal(1)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlDataSource.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def set_actions(self, dnd_actions: int) -> None:
|
|
||||||
"""Set the available drag-and-drop actions
|
|
||||||
|
|
||||||
Sets the actions that the source side client supports for this
|
|
||||||
operation. This request may trigger :func:`WlDataSource.action()` and
|
|
||||||
:func:`WlDataOffer.action()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.action>` events if the
|
|
||||||
compositor needs to change the selected action.
|
|
||||||
|
|
||||||
The dnd_actions argument must contain only values expressed in the
|
|
||||||
:func:`WlDataDeviceManager.dnd_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataDeviceManager.dnd_actions>` enum,
|
|
||||||
otherwise it will result in a protocol error.
|
|
||||||
|
|
||||||
This request must be made once only, and can only be made on sources
|
|
||||||
used in drag-and-drop, so it must be performed before
|
|
||||||
:func:`WlDataDevice.start_drag()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.start_drag>`. Attempting to
|
|
||||||
use the source other than for drag-and-drop will raise a protocol
|
|
||||||
error.
|
|
||||||
|
|
||||||
:param dnd_actions:
|
|
||||||
actions supported by the data source
|
|
||||||
:type dnd_actions:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(2, dnd_actions)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataSourceResource(Resource):
|
|
||||||
interface = WlDataSource
|
|
||||||
|
|
||||||
@WlDataSource.event(
|
|
||||||
Argument(ArgumentType.String, nullable=True),
|
|
||||||
)
|
|
||||||
def target(self, mime_type: str | None) -> None:
|
|
||||||
"""A target accepts an offered mime type
|
|
||||||
|
|
||||||
Sent when a target accepts pointer_focus or motion events. If a target
|
|
||||||
does not accept any of the offered types, type is NULL.
|
|
||||||
|
|
||||||
Used for feedback during drag-and-drop.
|
|
||||||
|
|
||||||
:param mime_type:
|
|
||||||
mime type accepted by the target
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String` or `None`
|
|
||||||
"""
|
|
||||||
self._post_event(0, mime_type)
|
|
||||||
|
|
||||||
@WlDataSource.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
Argument(ArgumentType.FileDescriptor),
|
|
||||||
)
|
|
||||||
def send(self, mime_type: str, fd: int) -> None:
|
|
||||||
"""Send the data
|
|
||||||
|
|
||||||
Request for data from the client. Send the data as the specified mime
|
|
||||||
type over the passed file descriptor, then close it.
|
|
||||||
|
|
||||||
:param mime_type:
|
|
||||||
mime type for the data
|
|
||||||
:type mime_type:
|
|
||||||
`ArgumentType.String`
|
|
||||||
:param fd:
|
|
||||||
file descriptor for the data
|
|
||||||
:type fd:
|
|
||||||
`ArgumentType.FileDescriptor`
|
|
||||||
"""
|
|
||||||
self._post_event(1, mime_type, fd)
|
|
||||||
|
|
||||||
@WlDataSource.event()
|
|
||||||
def cancelled(self) -> None:
|
|
||||||
"""Selection was cancelled
|
|
||||||
|
|
||||||
This data source is no longer valid. There are several reasons why this
|
|
||||||
could happen:
|
|
||||||
|
|
||||||
- The data source has been replaced by another data source.
|
|
||||||
|
|
||||||
- The drag-and-drop operation was performed, but the drop destination
|
|
||||||
did not accept any of the mime types offered through
|
|
||||||
:func:`WlDataSource.target()`.
|
|
||||||
|
|
||||||
- The drag-and-drop operation was performed, but the drop destination
|
|
||||||
did not select any of the actions present in the mask offered through
|
|
||||||
:func:`WlDataSource.action()`.
|
|
||||||
|
|
||||||
- The drag-and-drop operation was performed but didn't happen over a
|
|
||||||
surface.
|
|
||||||
|
|
||||||
- The compositor cancelled the drag-and-drop operation (e.g. compositor
|
|
||||||
dependent timeouts to avoid stale drag-and-drop transfers).
|
|
||||||
|
|
||||||
The client should clean up and destroy this data source.
|
|
||||||
|
|
||||||
For objects of version 2 or older, :func:`WlDataSource.cancelled()`
|
|
||||||
will only be emitted if the data source was replaced by another data
|
|
||||||
source.
|
|
||||||
"""
|
|
||||||
self._post_event(2)
|
|
||||||
|
|
||||||
@WlDataSource.event(version=3)
|
|
||||||
def dnd_drop_performed(self) -> None:
|
|
||||||
"""The drag-and-drop operation physically finished
|
|
||||||
|
|
||||||
The user performed the drop action. This event does not indicate
|
|
||||||
acceptance, :func:`WlDataSource.cancelled()` may still be emitted
|
|
||||||
afterwards if the drop destination does not accept any mime type.
|
|
||||||
|
|
||||||
However, this event might however not be received if the compositor
|
|
||||||
cancelled the drag-and-drop operation before this event could happen.
|
|
||||||
|
|
||||||
Note that the data_source may still be used in the future and should
|
|
||||||
not be destroyed here.
|
|
||||||
"""
|
|
||||||
self._post_event(3)
|
|
||||||
|
|
||||||
@WlDataSource.event(version=3)
|
|
||||||
def dnd_finished(self) -> None:
|
|
||||||
"""The drag-and-drop operation concluded
|
|
||||||
|
|
||||||
The drop destination finished interoperating with this data source, so
|
|
||||||
the client is now free to destroy this data source and free all
|
|
||||||
associated data.
|
|
||||||
|
|
||||||
If the action used to perform the operation was "move", the source can
|
|
||||||
now delete the transferred data.
|
|
||||||
"""
|
|
||||||
self._post_event(4)
|
|
||||||
|
|
||||||
@WlDataSource.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def action(self, dnd_action: int) -> None:
|
|
||||||
"""Notify the selected action
|
|
||||||
|
|
||||||
This event indicates the action selected by the compositor after
|
|
||||||
matching the source/destination side actions. Only one action (or none)
|
|
||||||
will be offered here.
|
|
||||||
|
|
||||||
This event can be emitted multiple times during the drag-and-drop
|
|
||||||
operation, mainly in response to destination side changes through
|
|
||||||
:func:`WlDataOffer.set_actions()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.set_actions>`, and as the data
|
|
||||||
device enters/leaves surfaces.
|
|
||||||
|
|
||||||
It is only possible to receive this event after
|
|
||||||
:func:`WlDataSource.dnd_drop_performed()` if the drag-and-drop
|
|
||||||
operation ended in an "ask" action, in which case the final
|
|
||||||
:func:`WlDataSource.action()` event will happen immediately before
|
|
||||||
:func:`WlDataSource.dnd_finished()`.
|
|
||||||
|
|
||||||
Compositors may also change the selected action on the fly, mainly in
|
|
||||||
response to keyboard modifier changes during the drag-and-drop
|
|
||||||
operation.
|
|
||||||
|
|
||||||
The most recent action received is always the valid one. The chosen
|
|
||||||
action may change alongside negotiation (e.g. an "ask" action can turn
|
|
||||||
into a "move" operation), so the effects of the final action must
|
|
||||||
always be applied in :func:`WlDataOffer.dnd_finished()
|
|
||||||
<pywayland.protocol.wayland.WlDataOffer.dnd_finished>`.
|
|
||||||
|
|
||||||
Clients can trigger cursor surface changes from this point, so they
|
|
||||||
reflect the current action.
|
|
||||||
|
|
||||||
:param dnd_action:
|
|
||||||
action selected by the compositor
|
|
||||||
:type dnd_action:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(5, dnd_action)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDataSourceGlobal(Global):
|
|
||||||
interface = WlDataSource
|
|
||||||
|
|
||||||
|
|
||||||
WlDataSource._gen_c()
|
|
||||||
WlDataSource.proxy_class = WlDataSourceProxy
|
|
||||||
WlDataSource.resource_class = WlDataSourceResource
|
|
||||||
WlDataSource.global_class = WlDataSourceGlobal
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_callback import WlCallback
|
|
||||||
from .wl_registry import WlRegistry
|
|
||||||
|
|
||||||
|
|
||||||
class WlDisplay(Interface):
|
|
||||||
"""Core global object
|
|
||||||
|
|
||||||
The core global object. This is a special singleton object. It is used
|
|
||||||
for internal Wayland protocol features.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_display"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
invalid_object = 0
|
|
||||||
invalid_method = 1
|
|
||||||
no_memory = 2
|
|
||||||
implementation = 3
|
|
||||||
|
|
||||||
|
|
||||||
class WlDisplayProxy(Proxy[WlDisplay]):
|
|
||||||
interface = WlDisplay
|
|
||||||
|
|
||||||
@WlDisplay.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlCallback),
|
|
||||||
)
|
|
||||||
def sync(self) -> Proxy[WlCallback]:
|
|
||||||
"""Asynchronous roundtrip
|
|
||||||
|
|
||||||
The sync request asks the server to emit the 'done' event on the
|
|
||||||
returned :class:`~pywayland.protocol.wayland.WlCallback` object. Since
|
|
||||||
requests are handled in-order and events are delivered in-order, this
|
|
||||||
can be used as a barrier to ensure all previous requests and the
|
|
||||||
resulting events have been handled.
|
|
||||||
|
|
||||||
The object returned by this request will be destroyed by the compositor
|
|
||||||
after the callback is fired and as such the client must not attempt to
|
|
||||||
use it after that point.
|
|
||||||
|
|
||||||
The callback_data passed in the callback is the event serial.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlCallback` -- callback object
|
|
||||||
for the sync request
|
|
||||||
"""
|
|
||||||
callback = self._marshal_constructor(0, WlCallback)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
@WlDisplay.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlRegistry),
|
|
||||||
)
|
|
||||||
def get_registry(self) -> Proxy[WlRegistry]:
|
|
||||||
"""Get global registry object
|
|
||||||
|
|
||||||
This request creates a registry object that allows the client to list
|
|
||||||
and bind the global objects available from the compositor.
|
|
||||||
|
|
||||||
It should be noted that the server side resources consumed in response
|
|
||||||
to a get_registry request can only be released when the client
|
|
||||||
disconnects, not when the client side proxy is destroyed. Therefore,
|
|
||||||
clients should invoke get_registry as infrequently as possible to avoid
|
|
||||||
wasting memory.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegistry` -- global registry
|
|
||||||
object
|
|
||||||
"""
|
|
||||||
registry = self._marshal_constructor(1, WlRegistry)
|
|
||||||
return registry
|
|
||||||
|
|
||||||
|
|
||||||
class WlDisplayResource(Resource):
|
|
||||||
interface = WlDisplay
|
|
||||||
|
|
||||||
@WlDisplay.event(
|
|
||||||
Argument(ArgumentType.Object),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def error(self, object_id: Any, code: int, message: str) -> None:
|
|
||||||
"""Fatal error event
|
|
||||||
|
|
||||||
The error event is sent out when a fatal (non-recoverable) error has
|
|
||||||
occurred. The object_id argument is the object where the error
|
|
||||||
occurred, most often in response to a request to that object. The code
|
|
||||||
identifies the error and is defined by the object interface. As such,
|
|
||||||
each interface defines its own set of error codes. The message is a
|
|
||||||
brief description of the error, for (debugging) convenience.
|
|
||||||
|
|
||||||
:param object_id:
|
|
||||||
object where the error occurred
|
|
||||||
:type object_id:
|
|
||||||
`ArgumentType.Object`
|
|
||||||
:param code:
|
|
||||||
error code
|
|
||||||
:type code:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param message:
|
|
||||||
error description
|
|
||||||
:type message:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(0, object_id, code, message)
|
|
||||||
|
|
||||||
@WlDisplay.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def delete_id(self, id: int) -> None:
|
|
||||||
"""Acknowledge object id deletion
|
|
||||||
|
|
||||||
This event is used internally by the object ID management logic. When a
|
|
||||||
client deletes an object that it had created, the server will send this
|
|
||||||
event to acknowledge that it has seen the delete request. When the
|
|
||||||
client receives this event, it will know that it can safely reuse the
|
|
||||||
object ID.
|
|
||||||
|
|
||||||
:param id:
|
|
||||||
deleted object ID
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(1, id)
|
|
||||||
|
|
||||||
|
|
||||||
class WlDisplayGlobal(Global):
|
|
||||||
interface = WlDisplay
|
|
||||||
|
|
||||||
|
|
||||||
WlDisplay._gen_c()
|
|
||||||
WlDisplay.proxy_class = WlDisplayProxy
|
|
||||||
WlDisplay.resource_class = WlDisplayResource
|
|
||||||
WlDisplay.global_class = WlDisplayGlobal
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlKeyboard(Interface):
|
|
||||||
"""Keyboard input device
|
|
||||||
|
|
||||||
The :class:`WlKeyboard` interface represents one or more keyboards
|
|
||||||
associated with a seat.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_keyboard"
|
|
||||||
version = 9
|
|
||||||
|
|
||||||
class keymap_format(enum.IntEnum):
|
|
||||||
no_keymap = 0
|
|
||||||
xkb_v1 = 1
|
|
||||||
|
|
||||||
class key_state(enum.IntEnum):
|
|
||||||
released = 0
|
|
||||||
pressed = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlKeyboardProxy(Proxy[WlKeyboard]):
|
|
||||||
interface = WlKeyboard
|
|
||||||
|
|
||||||
@WlKeyboard.request(version=3)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Release the keyboard object
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlKeyboardResource(Resource):
|
|
||||||
interface = WlKeyboard
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.FileDescriptor),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def keymap(self, format: int, fd: int, size: int) -> None:
|
|
||||||
"""Keyboard mapping
|
|
||||||
|
|
||||||
This event provides a file descriptor to the client which can be
|
|
||||||
memory-mapped in read-only mode to provide a keyboard mapping
|
|
||||||
description.
|
|
||||||
|
|
||||||
From version 7 onwards, the fd must be mapped with MAP_PRIVATE by the
|
|
||||||
recipient, as MAP_SHARED may fail.
|
|
||||||
|
|
||||||
:param format:
|
|
||||||
keymap format
|
|
||||||
:type format:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param fd:
|
|
||||||
keymap file descriptor
|
|
||||||
:type fd:
|
|
||||||
`ArgumentType.FileDescriptor`
|
|
||||||
:param size:
|
|
||||||
keymap size, in bytes
|
|
||||||
:type size:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, format, fd, size)
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Array),
|
|
||||||
)
|
|
||||||
def enter(self, serial: int, surface: WlSurface, keys: list) -> None:
|
|
||||||
"""Enter event
|
|
||||||
|
|
||||||
Notification that this seat's keyboard focus is on a certain surface.
|
|
||||||
|
|
||||||
The compositor must send the :func:`WlKeyboard.modifiers()` event after
|
|
||||||
this event.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the enter event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
surface gaining keyboard focus
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param keys:
|
|
||||||
the currently pressed keys
|
|
||||||
:type keys:
|
|
||||||
`ArgumentType.Array`
|
|
||||||
"""
|
|
||||||
self._post_event(1, serial, surface, keys)
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def leave(self, serial: int, surface: WlSurface) -> None:
|
|
||||||
"""Leave event
|
|
||||||
|
|
||||||
Notification that this seat's keyboard focus is no longer on a certain
|
|
||||||
surface.
|
|
||||||
|
|
||||||
The leave notification is sent before the enter notification for the
|
|
||||||
new focus.
|
|
||||||
|
|
||||||
After this event client must assume that all keys, including modifiers,
|
|
||||||
are lifted and also it must stop key repeating if there's some going
|
|
||||||
on.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the leave event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
surface that lost keyboard focus
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
"""
|
|
||||||
self._post_event(2, serial, surface)
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def key(self, serial: int, time: int, key: int, state: int) -> None:
|
|
||||||
"""Key event
|
|
||||||
|
|
||||||
A key was pressed or released. The time argument is a timestamp with
|
|
||||||
millisecond granularity, with an undefined base.
|
|
||||||
|
|
||||||
The key is a platform-specific key code that can be interpreted by
|
|
||||||
feeding it to the keyboard mapping (see the keymap event).
|
|
||||||
|
|
||||||
If this event produces a change in modifiers, then the resulting
|
|
||||||
:func:`WlKeyboard.modifiers()` event must be sent after this event.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the key event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param key:
|
|
||||||
key that produced the event
|
|
||||||
:type key:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param state:
|
|
||||||
physical state of the key
|
|
||||||
:type state:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(3, serial, time, key, state)
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def modifiers(self, serial: int, mods_depressed: int, mods_latched: int, mods_locked: int, group: int) -> None:
|
|
||||||
"""Modifier and group state
|
|
||||||
|
|
||||||
Notifies clients that the modifier and/or group state has changed, and
|
|
||||||
it should update its local state.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the modifiers event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param mods_depressed:
|
|
||||||
depressed modifiers
|
|
||||||
:type mods_depressed:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param mods_latched:
|
|
||||||
latched modifiers
|
|
||||||
:type mods_latched:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param mods_locked:
|
|
||||||
locked modifiers
|
|
||||||
:type mods_locked:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param group:
|
|
||||||
keyboard layout
|
|
||||||
:type group:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(4, serial, mods_depressed, mods_latched, mods_locked, group)
|
|
||||||
|
|
||||||
@WlKeyboard.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=4,
|
|
||||||
)
|
|
||||||
def repeat_info(self, rate: int, delay: int) -> None:
|
|
||||||
"""Repeat rate and delay
|
|
||||||
|
|
||||||
Informs the client about the keyboard's repeat rate and delay.
|
|
||||||
|
|
||||||
This event is sent as soon as the :class:`WlKeyboard` object has been
|
|
||||||
created, and is guaranteed to be received by the client before any key
|
|
||||||
press event.
|
|
||||||
|
|
||||||
Negative values for either rate or delay are illegal. A rate of zero
|
|
||||||
will disable any repeating (regardless of the value of delay).
|
|
||||||
|
|
||||||
This event can be sent later on as well with a new value if necessary,
|
|
||||||
so clients should continue listening for the event past the creation of
|
|
||||||
:class:`WlKeyboard`.
|
|
||||||
|
|
||||||
:param rate:
|
|
||||||
the rate of repeating keys in characters per second
|
|
||||||
:type rate:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param delay:
|
|
||||||
delay in milliseconds since key down until repeating starts
|
|
||||||
:type delay:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(5, rate, delay)
|
|
||||||
|
|
||||||
|
|
||||||
class WlKeyboardGlobal(Global):
|
|
||||||
interface = WlKeyboard
|
|
||||||
|
|
||||||
|
|
||||||
WlKeyboard._gen_c()
|
|
||||||
WlKeyboard.proxy_class = WlKeyboardProxy
|
|
||||||
WlKeyboard.resource_class = WlKeyboardResource
|
|
||||||
WlKeyboard.global_class = WlKeyboardGlobal
|
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WlOutput(Interface):
|
|
||||||
"""Compositor output region
|
|
||||||
|
|
||||||
An output describes part of the compositor geometry. The compositor works
|
|
||||||
in the 'compositor coordinate system' and an output corresponds to a
|
|
||||||
rectangular area in that space that is actually visible. This typically
|
|
||||||
corresponds to a monitor that displays part of the compositor space. This
|
|
||||||
object is published as global during start up, or when a monitor is
|
|
||||||
hotplugged.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_output"
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
class subpixel(enum.IntEnum):
|
|
||||||
unknown = 0
|
|
||||||
none = 1
|
|
||||||
horizontal_rgb = 2
|
|
||||||
horizontal_bgr = 3
|
|
||||||
vertical_rgb = 4
|
|
||||||
vertical_bgr = 5
|
|
||||||
|
|
||||||
class transform(enum.IntEnum):
|
|
||||||
normal = 0
|
|
||||||
transform_90 = 1
|
|
||||||
transform_180 = 2
|
|
||||||
transform_270 = 3
|
|
||||||
flipped = 4
|
|
||||||
flipped_90 = 5
|
|
||||||
flipped_180 = 6
|
|
||||||
flipped_270 = 7
|
|
||||||
|
|
||||||
class mode(enum.IntFlag):
|
|
||||||
current = 0x1
|
|
||||||
preferred = 0x2
|
|
||||||
|
|
||||||
|
|
||||||
class WlOutputProxy(Proxy[WlOutput]):
|
|
||||||
interface = WlOutput
|
|
||||||
|
|
||||||
@WlOutput.request(version=3)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Release the output object
|
|
||||||
|
|
||||||
Using this request a client can tell the server that it is not going to
|
|
||||||
use the output object anymore.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlOutputResource(Resource):
|
|
||||||
interface = WlOutput
|
|
||||||
|
|
||||||
@WlOutput.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def geometry(self, x: int, y: int, physical_width: int, physical_height: int, subpixel: int, make: str, model: str, transform: int) -> None:
|
|
||||||
"""Properties of the output
|
|
||||||
|
|
||||||
The geometry event describes geometric properties of the output. The
|
|
||||||
event is sent when binding to the output object and whenever any of the
|
|
||||||
properties change.
|
|
||||||
|
|
||||||
The physical size can be set to zero if it doesn't make sense for this
|
|
||||||
output (e.g. for projectors or virtual outputs).
|
|
||||||
|
|
||||||
The geometry event will be followed by a done event (starting from
|
|
||||||
version 2).
|
|
||||||
|
|
||||||
Note: :class:`WlOutput` only advertises partial information about the
|
|
||||||
output position and identification. Some compositors, for instance
|
|
||||||
those not implementing a desktop-style output layout or those exposing
|
|
||||||
virtual outputs, might fake this information. Instead of using x and y,
|
|
||||||
clients should use xdg_output.logical_position. Instead of using make
|
|
||||||
and model, clients should use name and description.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
x position within the global compositor space
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
y position within the global compositor space
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param physical_width:
|
|
||||||
width in millimeters of the output
|
|
||||||
:type physical_width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param physical_height:
|
|
||||||
height in millimeters of the output
|
|
||||||
:type physical_height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param subpixel:
|
|
||||||
subpixel orientation of the output
|
|
||||||
:type subpixel:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param make:
|
|
||||||
textual description of the manufacturer
|
|
||||||
:type make:
|
|
||||||
`ArgumentType.String`
|
|
||||||
:param model:
|
|
||||||
textual description of the model
|
|
||||||
:type model:
|
|
||||||
`ArgumentType.String`
|
|
||||||
:param transform:
|
|
||||||
transform that maps framebuffer to output
|
|
||||||
:type transform:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(0, x, y, physical_width, physical_height, subpixel, make, model, transform)
|
|
||||||
|
|
||||||
@WlOutput.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def mode(self, flags: int, width: int, height: int, refresh: int) -> None:
|
|
||||||
"""Advertise available modes for the output
|
|
||||||
|
|
||||||
The mode event describes an available mode for the output.
|
|
||||||
|
|
||||||
The event is sent when binding to the output object and there will
|
|
||||||
always be one mode, the current mode. The event is sent again if an
|
|
||||||
output changes mode, for the mode that is now current. In other words,
|
|
||||||
the current mode is always the last mode that was received with the
|
|
||||||
current flag set.
|
|
||||||
|
|
||||||
Non-current modes are deprecated. A compositor can decide to only
|
|
||||||
advertise the current mode and never send other modes. Clients should
|
|
||||||
not rely on non-current modes.
|
|
||||||
|
|
||||||
The size of a mode is given in physical hardware units of the output
|
|
||||||
device. This is not necessarily the same as the output size in the
|
|
||||||
global compositor space. For instance, the output may be scaled, as
|
|
||||||
described in :func:`WlOutput.scale()`, or transformed, as described in
|
|
||||||
:func:`WlOutput.transform()`. Clients willing to retrieve the output
|
|
||||||
size in the global compositor space should use xdg_output.logical_size
|
|
||||||
instead.
|
|
||||||
|
|
||||||
The vertical refresh rate can be set to zero if it doesn't make sense
|
|
||||||
for this output (e.g. for virtual outputs).
|
|
||||||
|
|
||||||
The mode event will be followed by a done event (starting from version
|
|
||||||
2).
|
|
||||||
|
|
||||||
Clients should not use the refresh rate to schedule frames. Instead,
|
|
||||||
they should use the :func:`WlSurface.frame()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.frame>` event or the
|
|
||||||
presentation-time protocol.
|
|
||||||
|
|
||||||
Note: this information is not always meaningful for all outputs. Some
|
|
||||||
compositors, such as those exposing virtual outputs, might fake the
|
|
||||||
refresh rate or the size.
|
|
||||||
|
|
||||||
:param flags:
|
|
||||||
bitfield of mode flags
|
|
||||||
:type flags:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param width:
|
|
||||||
width of the mode in hardware units
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
height of the mode in hardware units
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param refresh:
|
|
||||||
vertical refresh rate in mHz
|
|
||||||
:type refresh:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(1, flags, width, height, refresh)
|
|
||||||
|
|
||||||
@WlOutput.event(version=2)
|
|
||||||
def done(self) -> None:
|
|
||||||
"""Sent all information about output
|
|
||||||
|
|
||||||
This event is sent after all other properties have been sent after
|
|
||||||
binding to the output object and after any other property changes done
|
|
||||||
after that. This allows changes to the output properties to be seen as
|
|
||||||
atomic, even if they happen via multiple events.
|
|
||||||
"""
|
|
||||||
self._post_event(2)
|
|
||||||
|
|
||||||
@WlOutput.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=2,
|
|
||||||
)
|
|
||||||
def scale(self, factor: int) -> None:
|
|
||||||
"""Output scaling properties
|
|
||||||
|
|
||||||
This event contains scaling geometry information that is not in the
|
|
||||||
geometry event. It may be sent after binding the output object or if
|
|
||||||
the output scale changes later. If it is not sent, the client should
|
|
||||||
assume a scale of 1.
|
|
||||||
|
|
||||||
A scale larger than 1 means that the compositor will automatically
|
|
||||||
scale surface buffers by this amount when rendering. This is used for
|
|
||||||
very high resolution displays where applications rendering at the
|
|
||||||
native resolution would be too small to be legible.
|
|
||||||
|
|
||||||
It is intended that scaling aware clients track the current output of a
|
|
||||||
surface, and if it is on a scaled output it should use
|
|
||||||
:func:`WlSurface.set_buffer_scale()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.set_buffer_scale>` with the scale
|
|
||||||
of the output. That way the compositor can avoid scaling the surface,
|
|
||||||
and the client can supply a higher detail image.
|
|
||||||
|
|
||||||
The scale event will be followed by a done event.
|
|
||||||
|
|
||||||
:param factor:
|
|
||||||
scaling factor of output
|
|
||||||
:type factor:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(3, factor)
|
|
||||||
|
|
||||||
@WlOutput.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
version=4,
|
|
||||||
)
|
|
||||||
def name(self, name: str) -> None:
|
|
||||||
"""Name of this output
|
|
||||||
|
|
||||||
Many compositors will assign user-friendly names to their outputs, show
|
|
||||||
them to the user, allow the user to refer to an output, etc. The client
|
|
||||||
may wish to know this name as well to offer the user similar behaviors.
|
|
||||||
|
|
||||||
The name is a UTF-8 string with no convention defined for its contents.
|
|
||||||
Each name is unique among all :class:`WlOutput` globals. The name is
|
|
||||||
only guaranteed to be unique for the compositor instance.
|
|
||||||
|
|
||||||
The same output name is used for all clients for a given
|
|
||||||
:class:`WlOutput` global. Thus, the name can be shared across processes
|
|
||||||
to refer to a specific :class:`WlOutput` global.
|
|
||||||
|
|
||||||
The name is not guaranteed to be persistent across sessions, thus
|
|
||||||
cannot be used to reliably identify an output in e.g. configuration
|
|
||||||
files.
|
|
||||||
|
|
||||||
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
|
|
||||||
not assume that the name is a reflection of an underlying DRM
|
|
||||||
connector, X11 connection, etc.
|
|
||||||
|
|
||||||
The name event is sent after binding the output object. This event is
|
|
||||||
only sent once per output object, and the name does not change over the
|
|
||||||
lifetime of the :class:`WlOutput` global.
|
|
||||||
|
|
||||||
Compositors may re-use the same output name if the :class:`WlOutput`
|
|
||||||
global is destroyed and re-created later. Compositors should avoid re-
|
|
||||||
using the same name if possible.
|
|
||||||
|
|
||||||
The name event will be followed by a done event.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
output name
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(4, name)
|
|
||||||
|
|
||||||
@WlOutput.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
version=4,
|
|
||||||
)
|
|
||||||
def description(self, description: str) -> None:
|
|
||||||
"""Human-readable description of this output
|
|
||||||
|
|
||||||
Many compositors can produce human-readable descriptions of their
|
|
||||||
outputs. The client may wish to know this description as well, e.g. for
|
|
||||||
output selection purposes.
|
|
||||||
|
|
||||||
The description is a UTF-8 string with no convention defined for its
|
|
||||||
contents. The description is not guaranteed to be unique among all
|
|
||||||
:class:`WlOutput` globals. Examples might include 'Foocorp 11" Display'
|
|
||||||
or 'Virtual X11 output via :1'.
|
|
||||||
|
|
||||||
The description event is sent after binding the output object and
|
|
||||||
whenever the description changes. The description is optional, and may
|
|
||||||
not be sent at all.
|
|
||||||
|
|
||||||
The description event will be followed by a done event.
|
|
||||||
|
|
||||||
:param description:
|
|
||||||
output description
|
|
||||||
:type description:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(5, description)
|
|
||||||
|
|
||||||
|
|
||||||
class WlOutputGlobal(Global):
|
|
||||||
interface = WlOutput
|
|
||||||
|
|
||||||
|
|
||||||
WlOutput._gen_c()
|
|
||||||
WlOutput.proxy_class = WlOutputProxy
|
|
||||||
WlOutput.resource_class = WlOutputResource
|
|
||||||
WlOutput.global_class = WlOutputGlobal
|
|
||||||
@@ -1,592 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlPointer(Interface):
|
|
||||||
"""Pointer input device
|
|
||||||
|
|
||||||
The :class:`WlPointer` interface represents one or more input devices, such
|
|
||||||
as mice, which control the pointer location and pointer_focus of a seat.
|
|
||||||
|
|
||||||
The :class:`WlPointer` interface generates motion, enter and leave events
|
|
||||||
for the surfaces that the pointer is located over, and button and axis
|
|
||||||
events for button presses, button releases and scrolling.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_pointer"
|
|
||||||
version = 9
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
role = 0
|
|
||||||
|
|
||||||
class button_state(enum.IntEnum):
|
|
||||||
released = 0
|
|
||||||
pressed = 1
|
|
||||||
|
|
||||||
class axis(enum.IntEnum):
|
|
||||||
vertical_scroll = 0
|
|
||||||
horizontal_scroll = 1
|
|
||||||
|
|
||||||
class axis_source(enum.IntEnum):
|
|
||||||
wheel = 0
|
|
||||||
finger = 1
|
|
||||||
continuous = 2
|
|
||||||
wheel_tilt = 3
|
|
||||||
|
|
||||||
class axis_relative_direction(enum.IntEnum):
|
|
||||||
identical = 0
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlPointerProxy(Proxy[WlPointer]):
|
|
||||||
interface = WlPointer
|
|
||||||
|
|
||||||
@WlPointer.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface, nullable=True),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def set_cursor(self, serial: int, surface: WlSurface | None, hotspot_x: int, hotspot_y: int) -> None:
|
|
||||||
"""Set the pointer surface
|
|
||||||
|
|
||||||
Set the pointer surface, i.e., the surface that contains the pointer
|
|
||||||
image (cursor). This request gives the surface the role of a cursor. If
|
|
||||||
the surface already has another role, it raises a protocol error.
|
|
||||||
|
|
||||||
The cursor actually changes only if the pointer focus for this device
|
|
||||||
is one of the requesting client's surfaces or the surface parameter is
|
|
||||||
the current pointer surface. If there was a previous surface set with
|
|
||||||
this request it is replaced. If surface is NULL, the pointer image is
|
|
||||||
hidden.
|
|
||||||
|
|
||||||
The parameters hotspot_x and hotspot_y define the position of the
|
|
||||||
pointer surface relative to the pointer location. Its top-left corner
|
|
||||||
is always at (x, y) - (hotspot_x, hotspot_y), where (x, y) are the
|
|
||||||
coordinates of the pointer location, in surface-local coordinates.
|
|
||||||
|
|
||||||
On surface.attach requests to the pointer surface, hotspot_x and
|
|
||||||
hotspot_y are decremented by the x and y parameters passed to the
|
|
||||||
request. Attach must be confirmed by :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` as usual.
|
|
||||||
|
|
||||||
The hotspot can also be updated by passing the currently set pointer
|
|
||||||
surface to this request with new values for hotspot_x and hotspot_y.
|
|
||||||
|
|
||||||
The input region is ignored for wl_surfaces with the role of a cursor.
|
|
||||||
When the use as a cursor ends, the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` is unmapped.
|
|
||||||
|
|
||||||
The serial parameter must match the latest :func:`WlPointer.enter()`
|
|
||||||
serial number sent to the client. Otherwise the request will be
|
|
||||||
ignored.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the enter event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
pointer surface
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` or `None`
|
|
||||||
:param hotspot_x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type hotspot_x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param hotspot_y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type hotspot_y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(0, serial, surface, hotspot_x, hotspot_y)
|
|
||||||
|
|
||||||
@WlPointer.request(version=3)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Release the pointer object
|
|
||||||
|
|
||||||
Using this request a client can tell the server that it is not going to
|
|
||||||
use the pointer object anymore.
|
|
||||||
|
|
||||||
This request destroys the pointer proxy object, so clients must not
|
|
||||||
call wl_pointer_destroy() after using this request.
|
|
||||||
"""
|
|
||||||
self._marshal(1)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlPointerResource(Resource):
|
|
||||||
interface = WlPointer
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def enter(self, serial: int, surface: WlSurface, surface_x: float, surface_y: float) -> None:
|
|
||||||
"""Enter event
|
|
||||||
|
|
||||||
Notification that this seat's pointer is focused on a certain surface.
|
|
||||||
|
|
||||||
When a seat's focus enters a surface, the pointer image is undefined
|
|
||||||
and a client should respond to this event by setting an appropriate
|
|
||||||
pointer image with the set_cursor request.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the enter event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
surface entered by the pointer
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param surface_x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type surface_x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param surface_y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type surface_y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(0, serial, surface, surface_x, surface_y)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def leave(self, serial: int, surface: WlSurface) -> None:
|
|
||||||
"""Leave event
|
|
||||||
|
|
||||||
Notification that this seat's pointer is no longer focused on a certain
|
|
||||||
surface.
|
|
||||||
|
|
||||||
The leave notification is sent before the enter notification for the
|
|
||||||
new focus.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the leave event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
surface left by the pointer
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
"""
|
|
||||||
self._post_event(1, serial, surface)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def motion(self, time: int, surface_x: float, surface_y: float) -> None:
|
|
||||||
"""Pointer motion event
|
|
||||||
|
|
||||||
Notification of pointer location change. The arguments surface_x and
|
|
||||||
surface_y are the location relative to the focused surface.
|
|
||||||
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface_x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type surface_x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param surface_y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type surface_y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(2, time, surface_x, surface_y)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def button(self, serial: int, time: int, button: int, state: int) -> None:
|
|
||||||
"""Pointer button event
|
|
||||||
|
|
||||||
Mouse button click and release notifications.
|
|
||||||
|
|
||||||
The location of the click is given by the last motion or enter event.
|
|
||||||
The time argument is a timestamp with millisecond granularity, with an
|
|
||||||
undefined base.
|
|
||||||
|
|
||||||
The button is a button code as defined in the Linux kernel's
|
|
||||||
linux/input-event-codes.h header file, e.g. BTN_LEFT.
|
|
||||||
|
|
||||||
Any 16-bit button code value is reserved for future additions to the
|
|
||||||
kernel's event code list. All other button codes above 0xFFFF are
|
|
||||||
currently undefined but may be used in future versions of this
|
|
||||||
protocol.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the button event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param button:
|
|
||||||
button that produced the event
|
|
||||||
:type button:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param state:
|
|
||||||
physical state of the button
|
|
||||||
:type state:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(3, serial, time, button, state)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def axis(self, time: int, axis: int, value: float) -> None:
|
|
||||||
"""Axis event
|
|
||||||
|
|
||||||
Scroll and other axis notifications.
|
|
||||||
|
|
||||||
For scroll events (vertical and horizontal scroll axes), the value
|
|
||||||
parameter is the length of a vector along the specified axis in a
|
|
||||||
coordinate space identical to those of motion events, representing a
|
|
||||||
relative movement along the specified axis.
|
|
||||||
|
|
||||||
For devices that support movements non-parallel to axes multiple axis
|
|
||||||
events will be emitted.
|
|
||||||
|
|
||||||
When applicable, for example for touch pads, the server can choose to
|
|
||||||
emit scroll events where the motion vector is equivalent to a motion
|
|
||||||
event vector.
|
|
||||||
|
|
||||||
When applicable, a client can transform its content relative to the
|
|
||||||
scroll distance.
|
|
||||||
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param axis:
|
|
||||||
axis type
|
|
||||||
:type axis:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param value:
|
|
||||||
length of vector in surface-local coordinate space
|
|
||||||
:type value:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(4, time, axis, value)
|
|
||||||
|
|
||||||
@WlPointer.event(version=5)
|
|
||||||
def frame(self) -> None:
|
|
||||||
"""End of a pointer event sequence
|
|
||||||
|
|
||||||
Indicates the end of a set of events that logically belong together. A
|
|
||||||
client is expected to accumulate the data in all events within the
|
|
||||||
frame before proceeding.
|
|
||||||
|
|
||||||
All :class:`WlPointer` events before a :func:`WlPointer.frame()` event
|
|
||||||
belong logically together. For example, in a diagonal scroll motion the
|
|
||||||
compositor will send an optional :func:`WlPointer.axis_source()` event,
|
|
||||||
two :func:`WlPointer.axis()` events (horizontal and vertical) and
|
|
||||||
finally a :func:`WlPointer.frame()` event. The client may use this
|
|
||||||
information to calculate a diagonal vector for scrolling.
|
|
||||||
|
|
||||||
When multiple :func:`WlPointer.axis()` events occur within the same
|
|
||||||
frame, the motion vector is the combined motion of all events. When a
|
|
||||||
:func:`WlPointer.axis()` and a :func:`WlPointer.axis_stop()` event
|
|
||||||
occur within the same frame, this indicates that axis movement in one
|
|
||||||
axis has stopped but continues in the other axis. When multiple
|
|
||||||
:func:`WlPointer.axis_stop()` events occur within the same frame, this
|
|
||||||
indicates that these axes stopped in the same instance.
|
|
||||||
|
|
||||||
A :func:`WlPointer.frame()` event is sent for every logical event
|
|
||||||
group, even if the group only contains a single :class:`WlPointer`
|
|
||||||
event. Specifically, a client may get a sequence: motion, frame,
|
|
||||||
button, frame, axis, frame, axis_stop, frame.
|
|
||||||
|
|
||||||
The :func:`WlPointer.enter()` and :func:`WlPointer.leave()` events are
|
|
||||||
logical events generated by the compositor and not the hardware. These
|
|
||||||
events are also grouped by a :func:`WlPointer.frame()`. When a pointer
|
|
||||||
moves from one surface to another, a compositor should group the
|
|
||||||
:func:`WlPointer.leave()` event within the same
|
|
||||||
:func:`WlPointer.frame()`. However, a client must not rely on
|
|
||||||
:func:`WlPointer.leave()` and :func:`WlPointer.enter()` being in the
|
|
||||||
same :func:`WlPointer.frame()`. Compositor-specific policies may
|
|
||||||
require the :func:`WlPointer.leave()` and :func:`WlPointer.enter()`
|
|
||||||
event being split across multiple :func:`WlPointer.frame()` groups.
|
|
||||||
"""
|
|
||||||
self._post_event(5)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=5,
|
|
||||||
)
|
|
||||||
def axis_source(self, axis_source: int) -> None:
|
|
||||||
"""Axis source event
|
|
||||||
|
|
||||||
Source information for scroll and other axes.
|
|
||||||
|
|
||||||
This event does not occur on its own. It is sent before a
|
|
||||||
:func:`WlPointer.frame()` event and carries the source information for
|
|
||||||
all events within that frame.
|
|
||||||
|
|
||||||
The source specifies how this event was generated. If the source is
|
|
||||||
:func:`WlPointer.axis_source()`.finger, a :func:`WlPointer.axis_stop()`
|
|
||||||
event will be sent when the user lifts the finger off the device.
|
|
||||||
|
|
||||||
If the source is :func:`WlPointer.axis_source()`.wheel,
|
|
||||||
:func:`WlPointer.axis_source()`.wheel_tilt or
|
|
||||||
:func:`WlPointer.axis_source()`.continuous, a
|
|
||||||
:func:`WlPointer.axis_stop()` event may or may not be sent. Whether a
|
|
||||||
compositor sends an axis_stop event for these sources is hardware-
|
|
||||||
specific and implementation-dependent; clients must not rely on
|
|
||||||
receiving an axis_stop event for these scroll sources and should treat
|
|
||||||
scroll sequences from these scroll sources as unterminated by default.
|
|
||||||
|
|
||||||
This event is optional. If the source is unknown for a particular axis
|
|
||||||
event sequence, no event is sent. Only one
|
|
||||||
:func:`WlPointer.axis_source()` event is permitted per frame.
|
|
||||||
|
|
||||||
The order of :func:`WlPointer.axis_discrete()` and
|
|
||||||
:func:`WlPointer.axis_source()` is not guaranteed.
|
|
||||||
|
|
||||||
:param axis_source:
|
|
||||||
source of the axis event
|
|
||||||
:type axis_source:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(6, axis_source)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=5,
|
|
||||||
)
|
|
||||||
def axis_stop(self, time: int, axis: int) -> None:
|
|
||||||
"""Axis stop event
|
|
||||||
|
|
||||||
Stop notification for scroll and other axes.
|
|
||||||
|
|
||||||
For some :func:`WlPointer.axis_source()` types, a
|
|
||||||
:func:`WlPointer.axis_stop()` event is sent to notify a client that the
|
|
||||||
axis sequence has terminated. This enables the client to implement
|
|
||||||
kinetic scrolling. See the :func:`WlPointer.axis_source()`
|
|
||||||
documentation for information on when this event may be generated.
|
|
||||||
|
|
||||||
Any :func:`WlPointer.axis()` events with the same axis_source after
|
|
||||||
this event should be considered as the start of a new axis motion.
|
|
||||||
|
|
||||||
The timestamp is to be interpreted identical to the timestamp in the
|
|
||||||
:func:`WlPointer.axis()` event. The timestamp value may be the same as
|
|
||||||
a preceding :func:`WlPointer.axis()` event.
|
|
||||||
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param axis:
|
|
||||||
the axis stopped with this event
|
|
||||||
:type axis:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(7, time, axis)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=5,
|
|
||||||
)
|
|
||||||
def axis_discrete(self, axis: int, discrete: int) -> None:
|
|
||||||
"""Axis click event
|
|
||||||
|
|
||||||
Discrete step information for scroll and other axes.
|
|
||||||
|
|
||||||
This event carries the axis value of the :func:`WlPointer.axis()` event
|
|
||||||
in discrete steps (e.g. mouse wheel clicks).
|
|
||||||
|
|
||||||
This event is deprecated with :class:`WlPointer` version 8 - this event
|
|
||||||
is not sent to clients supporting version 8 or later.
|
|
||||||
|
|
||||||
This event does not occur on its own, it is coupled with a
|
|
||||||
:func:`WlPointer.axis()` event that represents this axis value on a
|
|
||||||
continuous scale. The protocol guarantees that each axis_discrete event
|
|
||||||
is always followed by exactly one axis event with the same axis number
|
|
||||||
within the same :func:`WlPointer.frame()`. Note that the protocol
|
|
||||||
allows for other events to occur between the axis_discrete and its
|
|
||||||
coupled axis event, including other axis_discrete or axis events. A
|
|
||||||
:func:`WlPointer.frame()` must not contain more than one axis_discrete
|
|
||||||
event per axis type.
|
|
||||||
|
|
||||||
This event is optional; continuous scrolling devices like two-finger
|
|
||||||
scrolling on touchpads do not have discrete steps and do not generate
|
|
||||||
this event.
|
|
||||||
|
|
||||||
The discrete value carries the directional information. e.g. a value of
|
|
||||||
-2 is two steps towards the negative direction of this axis.
|
|
||||||
|
|
||||||
The axis number is identical to the axis number in the associated axis
|
|
||||||
event.
|
|
||||||
|
|
||||||
The order of :func:`WlPointer.axis_discrete()` and
|
|
||||||
:func:`WlPointer.axis_source()` is not guaranteed.
|
|
||||||
|
|
||||||
:param axis:
|
|
||||||
axis type
|
|
||||||
:type axis:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param discrete:
|
|
||||||
number of steps
|
|
||||||
:type discrete:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(8, axis, discrete)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=8,
|
|
||||||
)
|
|
||||||
def axis_value120(self, axis: int, value120: int) -> None:
|
|
||||||
"""Axis high-resolution scroll event
|
|
||||||
|
|
||||||
Discrete high-resolution scroll information.
|
|
||||||
|
|
||||||
This event carries high-resolution wheel scroll information, with each
|
|
||||||
multiple of 120 representing one logical scroll step (a wheel detent).
|
|
||||||
For example, an axis_value120 of 30 is one quarter of a logical scroll
|
|
||||||
step in the positive direction, a value120 of -240 are two logical
|
|
||||||
scroll steps in the negative direction within the same hardware event.
|
|
||||||
Clients that rely on discrete scrolling should accumulate the value120
|
|
||||||
to multiples of 120 before processing the event.
|
|
||||||
|
|
||||||
The value120 must not be zero.
|
|
||||||
|
|
||||||
This event replaces the :func:`WlPointer.axis_discrete()` event in
|
|
||||||
clients supporting :class:`WlPointer` version 8 or later.
|
|
||||||
|
|
||||||
Where a :func:`WlPointer.axis_source()` event occurs in the same
|
|
||||||
:func:`WlPointer.frame()`, the axis source applies to this event.
|
|
||||||
|
|
||||||
The order of :class:`WlPointer`.axis_value120 and
|
|
||||||
:func:`WlPointer.axis_source()` is not guaranteed.
|
|
||||||
|
|
||||||
:param axis:
|
|
||||||
axis type
|
|
||||||
:type axis:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param value120:
|
|
||||||
scroll distance as fraction of 120
|
|
||||||
:type value120:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(9, axis, value120)
|
|
||||||
|
|
||||||
@WlPointer.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=9,
|
|
||||||
)
|
|
||||||
def axis_relative_direction(self, axis: int, direction: int) -> None:
|
|
||||||
"""Axis relative physical direction event
|
|
||||||
|
|
||||||
Relative directional information of the entity causing the axis motion.
|
|
||||||
|
|
||||||
For a :func:`WlPointer.axis()` event, the
|
|
||||||
:func:`WlPointer.axis_relative_direction()` event specifies the
|
|
||||||
movement direction of the entity causing the :func:`WlPointer.axis()`
|
|
||||||
event. For example: - if a user's fingers on a touchpad move down and
|
|
||||||
this causes a :func:`WlPointer.axis()` vertical_scroll down event,
|
|
||||||
the physical direction is 'identical' - if a user's fingers on a
|
|
||||||
touchpad move down and this causes a :func:`WlPointer.axis()`
|
|
||||||
vertical_scroll up scroll up event ('natural scrolling'), the
|
|
||||||
physical direction is 'inverted'.
|
|
||||||
|
|
||||||
A client may use this information to adjust scroll motion of
|
|
||||||
components. Specifically, enabling natural scrolling causes the content
|
|
||||||
to change direction compared to traditional scrolling. Some widgets
|
|
||||||
like volume control sliders should usually match the physical direction
|
|
||||||
regardless of whether natural scrolling is active. This event enables
|
|
||||||
clients to match the scroll direction of a widget to the physical
|
|
||||||
direction.
|
|
||||||
|
|
||||||
This event does not occur on its own, it is coupled with a
|
|
||||||
:func:`WlPointer.axis()` event that represents this axis value. The
|
|
||||||
protocol guarantees that each axis_relative_direction event is always
|
|
||||||
followed by exactly one axis event with the same axis number within the
|
|
||||||
same :func:`WlPointer.frame()`. Note that the protocol allows for other
|
|
||||||
events to occur between the axis_relative_direction and its coupled
|
|
||||||
axis event.
|
|
||||||
|
|
||||||
The axis number is identical to the axis number in the associated axis
|
|
||||||
event.
|
|
||||||
|
|
||||||
The order of :func:`WlPointer.axis_relative_direction()`,
|
|
||||||
:func:`WlPointer.axis_discrete()` and :func:`WlPointer.axis_source()`
|
|
||||||
is not guaranteed.
|
|
||||||
|
|
||||||
:param axis:
|
|
||||||
axis type
|
|
||||||
:type axis:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param direction:
|
|
||||||
physical direction relative to axis motion
|
|
||||||
:type direction:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(10, axis, direction)
|
|
||||||
|
|
||||||
|
|
||||||
class WlPointerGlobal(Global):
|
|
||||||
interface = WlPointer
|
|
||||||
|
|
||||||
|
|
||||||
WlPointer._gen_c()
|
|
||||||
WlPointer.proxy_class = WlPointerProxy
|
|
||||||
WlPointer.resource_class = WlPointerResource
|
|
||||||
WlPointer.global_class = WlPointerGlobal
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegion(Interface):
|
|
||||||
"""Region interface
|
|
||||||
|
|
||||||
A region object describes an area.
|
|
||||||
|
|
||||||
Region objects are used to describe the opaque and input regions of a
|
|
||||||
surface.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_region"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegionProxy(Proxy[WlRegion]):
|
|
||||||
interface = WlRegion
|
|
||||||
|
|
||||||
@WlRegion.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy region
|
|
||||||
|
|
||||||
Destroy the region. This will invalidate the object ID.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlRegion.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def add(self, x: int, y: int, width: int, height: int) -> None:
|
|
||||||
"""Add rectangle to region
|
|
||||||
|
|
||||||
Add the specified rectangle to the region.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
region-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
region-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param width:
|
|
||||||
rectangle width
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
rectangle height
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(1, x, y, width, height)
|
|
||||||
|
|
||||||
@WlRegion.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def subtract(self, x: int, y: int, width: int, height: int) -> None:
|
|
||||||
"""Subtract rectangle from region
|
|
||||||
|
|
||||||
Subtract the specified rectangle from the region.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
region-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
region-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param width:
|
|
||||||
rectangle width
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
rectangle height
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(2, x, y, width, height)
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegionResource(Resource):
|
|
||||||
interface = WlRegion
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegionGlobal(Global):
|
|
||||||
interface = WlRegion
|
|
||||||
|
|
||||||
|
|
||||||
WlRegion._gen_c()
|
|
||||||
WlRegion.proxy_class = WlRegionProxy
|
|
||||||
WlRegion.resource_class = WlRegionResource
|
|
||||||
WlRegion.global_class = WlRegionGlobal
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=Interface)
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegistry(Interface):
|
|
||||||
"""Global registry object
|
|
||||||
|
|
||||||
The singleton global registry object. The server has a number of global
|
|
||||||
objects that are available to all clients. These objects typically
|
|
||||||
represent an actual object in the server (for example, an input device) or
|
|
||||||
they are singleton objects that provide extension functionality.
|
|
||||||
|
|
||||||
When a client creates a registry object, the registry object will emit a
|
|
||||||
global event for each global currently in the registry. Globals come and
|
|
||||||
go as a result of device or monitor hotplugs, reconfiguration or other
|
|
||||||
events, and the registry will send out global and global_remove events to
|
|
||||||
keep the client up to date with the changes. To mark the end of the
|
|
||||||
initial burst of events, the client can use the :func:`WlDisplay.sync()
|
|
||||||
<pywayland.protocol.wayland.WlDisplay.sync>` request immediately after
|
|
||||||
calling :func:`WlDisplay.get_registry()
|
|
||||||
<pywayland.protocol.wayland.WlDisplay.get_registry>`.
|
|
||||||
|
|
||||||
A client can bind to a global object by using the bind request. This
|
|
||||||
creates a client-side handle that lets the object emit events to the client
|
|
||||||
and lets the client invoke requests on the object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_registry"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegistryProxy(Proxy[WlRegistry]):
|
|
||||||
interface = WlRegistry
|
|
||||||
|
|
||||||
@WlRegistry.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.NewId),
|
|
||||||
)
|
|
||||||
def bind(self, name: int, interface: type[T], version: int) -> Proxy[T]:
|
|
||||||
"""Bind an object to the display
|
|
||||||
|
|
||||||
Binds a new, client-created object to the server using the specified
|
|
||||||
name as the identifier.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
unique numeric name of the object
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param interface:
|
|
||||||
Interface name
|
|
||||||
:type interface:
|
|
||||||
`string`
|
|
||||||
:param version:
|
|
||||||
Interface version
|
|
||||||
:type version:
|
|
||||||
`int`
|
|
||||||
:returns:
|
|
||||||
:class:`pywayland.client.proxy.Proxy` of specified Interface --
|
|
||||||
bounded object
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, interface, name, interface.name, version)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegistryResource(Resource):
|
|
||||||
interface = WlRegistry
|
|
||||||
|
|
||||||
@WlRegistry.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def global_(self, name: int, interface: str, version: int) -> None:
|
|
||||||
"""Announce global object
|
|
||||||
|
|
||||||
Notify the client of global objects.
|
|
||||||
|
|
||||||
The event notifies the client that a global object with the given name
|
|
||||||
is now available, and it implements the given version of the given
|
|
||||||
interface.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
numeric name of the global object
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param interface:
|
|
||||||
interface implemented by the object
|
|
||||||
:type interface:
|
|
||||||
`ArgumentType.String`
|
|
||||||
:param version:
|
|
||||||
interface version
|
|
||||||
:type version:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, name, interface, version)
|
|
||||||
|
|
||||||
@WlRegistry.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def global_remove(self, name: int) -> None:
|
|
||||||
"""Announce removal of global object
|
|
||||||
|
|
||||||
Notify the client of removed global objects.
|
|
||||||
|
|
||||||
This event notifies the client that the global identified by name is no
|
|
||||||
longer available. If the client bound to the global using the bind
|
|
||||||
request, the client should now destroy that object.
|
|
||||||
|
|
||||||
The object remains valid and requests to the object will be ignored
|
|
||||||
until the client destroys it, to avoid races between the global going
|
|
||||||
away and a client sending a request to it.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
numeric name of the global object
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(1, name)
|
|
||||||
|
|
||||||
|
|
||||||
class WlRegistryGlobal(Global):
|
|
||||||
interface = WlRegistry
|
|
||||||
|
|
||||||
|
|
||||||
WlRegistry._gen_c()
|
|
||||||
WlRegistry.proxy_class = WlRegistryProxy
|
|
||||||
WlRegistry.resource_class = WlRegistryResource
|
|
||||||
WlRegistry.global_class = WlRegistryGlobal
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_keyboard import WlKeyboard
|
|
||||||
from .wl_pointer import WlPointer
|
|
||||||
from .wl_touch import WlTouch
|
|
||||||
|
|
||||||
|
|
||||||
class WlSeat(Interface):
|
|
||||||
"""Group of input devices
|
|
||||||
|
|
||||||
A seat is a group of keyboards, pointer and touch devices. This object is
|
|
||||||
published as a global during start up, or when such a device is hot
|
|
||||||
plugged. A seat typically has a pointer and maintains a keyboard focus and
|
|
||||||
a pointer focus.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_seat"
|
|
||||||
version = 9
|
|
||||||
|
|
||||||
class capability(enum.IntFlag):
|
|
||||||
pointer = 1
|
|
||||||
keyboard = 2
|
|
||||||
touch = 4
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
missing_capability = 0
|
|
||||||
|
|
||||||
|
|
||||||
class WlSeatProxy(Proxy[WlSeat]):
|
|
||||||
interface = WlSeat
|
|
||||||
|
|
||||||
@WlSeat.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlPointer),
|
|
||||||
)
|
|
||||||
def get_pointer(self) -> Proxy[WlPointer]:
|
|
||||||
"""Return pointer object
|
|
||||||
|
|
||||||
The ID provided will be initialized to the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` interface for this seat.
|
|
||||||
|
|
||||||
This request only takes effect if the seat has the pointer capability,
|
|
||||||
or has had the pointer capability in the past. It is a protocol
|
|
||||||
violation to issue this request on a seat that has never had the
|
|
||||||
pointer capability. The missing_capability error will be sent in this
|
|
||||||
case.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` -- seat pointer
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlPointer)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlSeat.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlKeyboard),
|
|
||||||
)
|
|
||||||
def get_keyboard(self) -> Proxy[WlKeyboard]:
|
|
||||||
"""Return keyboard object
|
|
||||||
|
|
||||||
The ID provided will be initialized to the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlKeyboard` interface for this
|
|
||||||
seat.
|
|
||||||
|
|
||||||
This request only takes effect if the seat has the keyboard capability,
|
|
||||||
or has had the keyboard capability in the past. It is a protocol
|
|
||||||
violation to issue this request on a seat that has never had the
|
|
||||||
keyboard capability. The missing_capability error will be sent in this
|
|
||||||
case.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlKeyboard` -- seat keyboard
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(1, WlKeyboard)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlSeat.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlTouch),
|
|
||||||
)
|
|
||||||
def get_touch(self) -> Proxy[WlTouch]:
|
|
||||||
"""Return touch object
|
|
||||||
|
|
||||||
The ID provided will be initialized to the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlTouch` interface for this seat.
|
|
||||||
|
|
||||||
This request only takes effect if the seat has the touch capability, or
|
|
||||||
has had the touch capability in the past. It is a protocol violation to
|
|
||||||
issue this request on a seat that has never had the touch capability.
|
|
||||||
The missing_capability error will be sent in this case.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlTouch` -- seat touch
|
|
||||||
interface
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(2, WlTouch)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlSeat.request(version=5)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Release the seat object
|
|
||||||
|
|
||||||
Using this request a client can tell the server that it is not going to
|
|
||||||
use the seat object anymore.
|
|
||||||
"""
|
|
||||||
self._marshal(3)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlSeatResource(Resource):
|
|
||||||
interface = WlSeat
|
|
||||||
|
|
||||||
@WlSeat.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def capabilities(self, capabilities: int) -> None:
|
|
||||||
"""Seat capabilities changed
|
|
||||||
|
|
||||||
This is emitted whenever a seat gains or loses the pointer, keyboard or
|
|
||||||
touch capabilities. The argument is a capability enum containing the
|
|
||||||
complete set of capabilities this seat has.
|
|
||||||
|
|
||||||
When the pointer capability is added, a client may create a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` object using the
|
|
||||||
:func:`WlSeat.get_pointer()` request. This object will receive pointer
|
|
||||||
events until the capability is removed in the future.
|
|
||||||
|
|
||||||
When the pointer capability is removed, a client should destroy the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` objects associated with
|
|
||||||
the seat where the capability was removed, using the
|
|
||||||
:func:`WlPointer.release()
|
|
||||||
<pywayland.protocol.wayland.WlPointer.release>` request. No further
|
|
||||||
pointer events will be received on these objects.
|
|
||||||
|
|
||||||
In some compositors, if a seat regains the pointer capability and a
|
|
||||||
client has a previously obtained
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` object of version 4 or
|
|
||||||
less, that object may start sending pointer events again. This behavior
|
|
||||||
is considered a misinterpretation of the intended behavior and must not
|
|
||||||
be relied upon by the client.
|
|
||||||
:class:`~pywayland.protocol.wayland.WlPointer` objects of version 5 or
|
|
||||||
later must not send events if created before the most recent event
|
|
||||||
notifying the client of an added pointer capability.
|
|
||||||
|
|
||||||
The above behavior also applies to
|
|
||||||
:class:`~pywayland.protocol.wayland.WlKeyboard` and
|
|
||||||
:class:`~pywayland.protocol.wayland.WlTouch` with the keyboard and
|
|
||||||
touch capabilities, respectively.
|
|
||||||
|
|
||||||
:param capabilities:
|
|
||||||
capabilities of the seat
|
|
||||||
:type capabilities:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, capabilities)
|
|
||||||
|
|
||||||
@WlSeat.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
version=2,
|
|
||||||
)
|
|
||||||
def name(self, name: str) -> None:
|
|
||||||
"""Unique identifier for this seat
|
|
||||||
|
|
||||||
In a multi-seat configuration the seat name can be used by clients to
|
|
||||||
help identify which physical devices the seat represents.
|
|
||||||
|
|
||||||
The seat name is a UTF-8 string with no convention defined for its
|
|
||||||
contents. Each name is unique among all :class:`WlSeat` globals. The
|
|
||||||
name is only guaranteed to be unique for the current compositor
|
|
||||||
instance.
|
|
||||||
|
|
||||||
The same seat names are used for all clients. Thus, the name can be
|
|
||||||
shared across processes to refer to a specific :class:`WlSeat` global.
|
|
||||||
|
|
||||||
The name event is sent after binding to the seat global. This event is
|
|
||||||
only sent once per seat object, and the name does not change over the
|
|
||||||
lifetime of the :class:`WlSeat` global.
|
|
||||||
|
|
||||||
Compositors may re-use the same seat name if the :class:`WlSeat` global
|
|
||||||
is destroyed and re-created later.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
seat identifier
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(1, name)
|
|
||||||
|
|
||||||
|
|
||||||
class WlSeatGlobal(Global):
|
|
||||||
interface = WlSeat
|
|
||||||
|
|
||||||
|
|
||||||
WlSeat._gen_c()
|
|
||||||
WlSeat.proxy_class = WlSeatProxy
|
|
||||||
WlSeat.resource_class = WlSeatResource
|
|
||||||
WlSeat.global_class = WlSeatGlobal
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_shell_surface import WlShellSurface
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlShell(Interface):
|
|
||||||
"""Create desktop-style surfaces
|
|
||||||
|
|
||||||
This interface is implemented by servers that provide desktop-style user
|
|
||||||
interfaces.
|
|
||||||
|
|
||||||
It allows clients to associate a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlShellSurface` with a basic surface.
|
|
||||||
|
|
||||||
Note! This protocol is deprecated and not intended for production use. For
|
|
||||||
desktop-style user interfaces, use xdg_shell. Compositors and clients
|
|
||||||
should not implement this interface.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_shell"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
role = 0
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellProxy(Proxy[WlShell]):
|
|
||||||
interface = WlShell
|
|
||||||
|
|
||||||
@WlShell.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlShellSurface),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def get_shell_surface(self, surface: WlSurface) -> Proxy[WlShellSurface]:
|
|
||||||
"""Create a shell surface from a surface
|
|
||||||
|
|
||||||
Create a shell surface for an existing surface. This gives the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` the role of a shell
|
|
||||||
surface. If the :class:`~pywayland.protocol.wayland.WlSurface` already
|
|
||||||
has another role, it raises a protocol error.
|
|
||||||
|
|
||||||
Only one shell surface can be associated with a given surface.
|
|
||||||
|
|
||||||
:param surface:
|
|
||||||
surface to be given the shell surface role
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlShellSurface` -- shell
|
|
||||||
surface to create
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlShellSurface, surface)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellResource(Resource):
|
|
||||||
interface = WlShell
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellGlobal(Global):
|
|
||||||
interface = WlShell
|
|
||||||
|
|
||||||
|
|
||||||
WlShell._gen_c()
|
|
||||||
WlShell.proxy_class = WlShellProxy
|
|
||||||
WlShell.resource_class = WlShellResource
|
|
||||||
WlShell.global_class = WlShellGlobal
|
|
||||||
@@ -1,463 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_output import WlOutput
|
|
||||||
from .wl_seat import WlSeat
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellSurface(Interface):
|
|
||||||
"""Desktop-style metadata interface
|
|
||||||
|
|
||||||
An interface that may be implemented by a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`, for implementations that
|
|
||||||
provide a desktop-style user interface.
|
|
||||||
|
|
||||||
It provides requests to treat surfaces like toplevel, fullscreen or popup
|
|
||||||
windows, move, resize or maximize them, associate metadata like title and
|
|
||||||
class, etc.
|
|
||||||
|
|
||||||
On the server side the object is automatically destroyed when the related
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` is destroyed. On the client
|
|
||||||
side, wl_shell_surface_destroy() must be called before destroying the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_shell_surface"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class resize(enum.IntFlag):
|
|
||||||
none = 0
|
|
||||||
top = 1
|
|
||||||
bottom = 2
|
|
||||||
left = 4
|
|
||||||
top_left = 5
|
|
||||||
bottom_left = 6
|
|
||||||
right = 8
|
|
||||||
top_right = 9
|
|
||||||
bottom_right = 10
|
|
||||||
|
|
||||||
class transient(enum.IntFlag):
|
|
||||||
inactive = 0x1
|
|
||||||
|
|
||||||
class fullscreen_method(enum.IntEnum):
|
|
||||||
default = 0
|
|
||||||
scale = 1
|
|
||||||
driver = 2
|
|
||||||
fill = 3
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellSurfaceProxy(Proxy[WlShellSurface]):
|
|
||||||
interface = WlShellSurface
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def pong(self, serial: int) -> None:
|
|
||||||
"""Respond to a ping event
|
|
||||||
|
|
||||||
A client must respond to a ping event with a pong request or the client
|
|
||||||
may be deemed unresponsive.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the ping event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(0, serial)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def move(self, seat: WlSeat, serial: int) -> None:
|
|
||||||
"""Start an interactive move
|
|
||||||
|
|
||||||
Start a pointer-driven move of the surface.
|
|
||||||
|
|
||||||
This request must be used in response to a button press event. The
|
|
||||||
server may ignore move requests depending on the state of the surface
|
|
||||||
(e.g. fullscreen or maximized).
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
seat whose pointer is used
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:param serial:
|
|
||||||
serial number of the implicit grab on the pointer
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(1, seat, serial)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def resize(self, seat: WlSeat, serial: int, edges: int) -> None:
|
|
||||||
"""Start an interactive resize
|
|
||||||
|
|
||||||
Start a pointer-driven resizing of the surface.
|
|
||||||
|
|
||||||
This request must be used in response to a button press event. The
|
|
||||||
server may ignore resize requests depending on the state of the surface
|
|
||||||
(e.g. fullscreen or maximized).
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
seat whose pointer is used
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:param serial:
|
|
||||||
serial number of the implicit grab on the pointer
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param edges:
|
|
||||||
which edge or corner is being dragged
|
|
||||||
:type edges:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(2, seat, serial, edges)
|
|
||||||
|
|
||||||
@WlShellSurface.request()
|
|
||||||
def set_toplevel(self) -> None:
|
|
||||||
"""Make the surface a toplevel surface
|
|
||||||
|
|
||||||
Map the surface as a toplevel surface.
|
|
||||||
|
|
||||||
A toplevel surface is not fullscreen, maximized or transient.
|
|
||||||
"""
|
|
||||||
self._marshal(3)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def set_transient(self, parent: WlSurface, x: int, y: int, flags: int) -> None:
|
|
||||||
"""Make the surface a transient surface
|
|
||||||
|
|
||||||
Map the surface relative to an existing surface.
|
|
||||||
|
|
||||||
The x and y arguments specify the location of the upper left corner of
|
|
||||||
the surface relative to the upper left corner of the parent surface, in
|
|
||||||
surface-local coordinates.
|
|
||||||
|
|
||||||
The flags argument controls details of the transient behaviour.
|
|
||||||
|
|
||||||
:param parent:
|
|
||||||
parent surface
|
|
||||||
:type parent:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param flags:
|
|
||||||
transient surface behavior
|
|
||||||
:type flags:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(4, parent, x, y, flags)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput, nullable=True),
|
|
||||||
)
|
|
||||||
def set_fullscreen(self, method: int, framerate: int, output: WlOutput | None) -> None:
|
|
||||||
"""Make the surface a fullscreen surface
|
|
||||||
|
|
||||||
Map the surface as a fullscreen surface.
|
|
||||||
|
|
||||||
If an output parameter is given then the surface will be made
|
|
||||||
fullscreen on that output. If the client does not specify the output
|
|
||||||
then the compositor will apply its policy - usually choosing the output
|
|
||||||
on which the surface has the biggest surface area.
|
|
||||||
|
|
||||||
The client may specify a method to resolve a size conflict between the
|
|
||||||
output size and the surface size - this is provided through the method
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
The framerate parameter is used only when the method is set to
|
|
||||||
"driver", to indicate the preferred framerate. A value of 0 indicates
|
|
||||||
that the client does not care about framerate. The framerate is
|
|
||||||
specified in mHz, that is framerate of 60000 is 60Hz.
|
|
||||||
|
|
||||||
A method of "scale" or "driver" implies a scaling operation of the
|
|
||||||
surface, either via a direct scaling operation or a change of the
|
|
||||||
output mode. This will override any kind of output scaling, so that
|
|
||||||
mapping a surface with a buffer size equal to the mode can fill the
|
|
||||||
screen independent of buffer_scale.
|
|
||||||
|
|
||||||
A method of "fill" means we don't scale up the buffer, however any
|
|
||||||
output scale is applied. This means that you may run into an edge case
|
|
||||||
where the application maps a buffer with the same size of the output
|
|
||||||
mode but buffer_scale 1 (thus making a surface larger than the output).
|
|
||||||
In this case it is allowed to downscale the results to fit the screen.
|
|
||||||
|
|
||||||
The compositor must reply to this request with a configure event with
|
|
||||||
the dimensions for the output on which the surface will be made
|
|
||||||
fullscreen.
|
|
||||||
|
|
||||||
:param method:
|
|
||||||
method for resolving size conflict
|
|
||||||
:type method:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param framerate:
|
|
||||||
framerate in mHz
|
|
||||||
:type framerate:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param output:
|
|
||||||
output on which the surface is to be fullscreen
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput` or `None`
|
|
||||||
"""
|
|
||||||
self._marshal(5, method, framerate, output)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def set_popup(self, seat: WlSeat, serial: int, parent: WlSurface, x: int, y: int, flags: int) -> None:
|
|
||||||
"""Make the surface a popup surface
|
|
||||||
|
|
||||||
Map the surface as a popup.
|
|
||||||
|
|
||||||
A popup surface is a transient surface with an added pointer grab.
|
|
||||||
|
|
||||||
An existing implicit grab will be changed to owner-events mode, and the
|
|
||||||
popup grab will continue after the implicit grab ends (i.e. releasing
|
|
||||||
the mouse button does not cause the popup to be unmapped).
|
|
||||||
|
|
||||||
The popup grab continues until the window is destroyed or a mouse
|
|
||||||
button is pressed in any other client's window. A click in any of the
|
|
||||||
client's surfaces is reported as normal, however, clicks in other
|
|
||||||
clients' surfaces will be discarded and trigger the callback.
|
|
||||||
|
|
||||||
The x and y arguments specify the location of the upper left corner of
|
|
||||||
the surface relative to the upper left corner of the parent surface, in
|
|
||||||
surface-local coordinates.
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
seat whose pointer is used
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:param serial:
|
|
||||||
serial number of the implicit grab on the pointer
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param parent:
|
|
||||||
parent surface
|
|
||||||
:type parent:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param flags:
|
|
||||||
transient surface behavior
|
|
||||||
:type flags:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._marshal(6, seat, serial, parent, x, y, flags)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput, nullable=True),
|
|
||||||
)
|
|
||||||
def set_maximized(self, output: WlOutput | None) -> None:
|
|
||||||
"""Make the surface a maximized surface
|
|
||||||
|
|
||||||
Map the surface as a maximized surface.
|
|
||||||
|
|
||||||
If an output parameter is given then the surface will be maximized on
|
|
||||||
that output. If the client does not specify the output then the
|
|
||||||
compositor will apply its policy - usually choosing the output on which
|
|
||||||
the surface has the biggest surface area.
|
|
||||||
|
|
||||||
The compositor will reply with a configure event telling the expected
|
|
||||||
new surface size. The operation is completed on the next buffer attach
|
|
||||||
to this surface.
|
|
||||||
|
|
||||||
A maximized surface typically fills the entire output it is bound to,
|
|
||||||
except for desktop elements such as panels. This is the main difference
|
|
||||||
between a maximized shell surface and a fullscreen shell surface.
|
|
||||||
|
|
||||||
The details depend on the compositor implementation.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
output on which the surface is to be maximized
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput` or `None`
|
|
||||||
"""
|
|
||||||
self._marshal(7, output)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def set_title(self, title: str) -> None:
|
|
||||||
"""Set surface title
|
|
||||||
|
|
||||||
Set a short title for the surface.
|
|
||||||
|
|
||||||
This string may be used to identify the surface in a task bar, window
|
|
||||||
list, or other user interface elements provided by the compositor.
|
|
||||||
|
|
||||||
The string must be encoded in UTF-8.
|
|
||||||
|
|
||||||
:param title:
|
|
||||||
surface title
|
|
||||||
:type title:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._marshal(8, title)
|
|
||||||
|
|
||||||
@WlShellSurface.request(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def set_class(self, class_: str) -> None:
|
|
||||||
"""Set surface class
|
|
||||||
|
|
||||||
Set a class for the surface.
|
|
||||||
|
|
||||||
The surface class identifies the general class of applications to which
|
|
||||||
the surface belongs. A common convention is to use the file name (or
|
|
||||||
the full path if it is a non-standard location) of the application's
|
|
||||||
.desktop file as the class.
|
|
||||||
|
|
||||||
:param class_:
|
|
||||||
surface class
|
|
||||||
:type class_:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._marshal(9, class_)
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellSurfaceResource(Resource):
|
|
||||||
interface = WlShellSurface
|
|
||||||
|
|
||||||
@WlShellSurface.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def ping(self, serial: int) -> None:
|
|
||||||
"""Ping client
|
|
||||||
|
|
||||||
Ping a client to check if it is receiving events and sending requests.
|
|
||||||
A client is expected to reply with a pong request.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the ping
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, serial)
|
|
||||||
|
|
||||||
@WlShellSurface.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def configure(self, edges: int, width: int, height: int) -> None:
|
|
||||||
"""Suggest resize
|
|
||||||
|
|
||||||
The configure event asks the client to resize its surface.
|
|
||||||
|
|
||||||
The size is a hint, in the sense that the client is free to ignore it
|
|
||||||
if it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
|
||||||
resize in steps of NxM pixels).
|
|
||||||
|
|
||||||
The edges parameter provides a hint about how the surface was resized.
|
|
||||||
The client may use this information to decide how to adjust its content
|
|
||||||
to the new size (e.g. a scrolling area might adjust its content
|
|
||||||
position to leave the viewable content unmoved).
|
|
||||||
|
|
||||||
The client is free to dismiss all but the last configure event it
|
|
||||||
received.
|
|
||||||
|
|
||||||
The width and height arguments specify the size of the window in
|
|
||||||
surface-local coordinates.
|
|
||||||
|
|
||||||
:param edges:
|
|
||||||
how the surface was resized
|
|
||||||
:type edges:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param width:
|
|
||||||
new width of the surface
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
new height of the surface
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(1, edges, width, height)
|
|
||||||
|
|
||||||
@WlShellSurface.event()
|
|
||||||
def popup_done(self) -> None:
|
|
||||||
"""Popup interaction is done
|
|
||||||
|
|
||||||
The popup_done event is sent out when a popup grab is broken, that is,
|
|
||||||
when the user clicks a surface that doesn't belong to the client owning
|
|
||||||
the popup surface.
|
|
||||||
"""
|
|
||||||
self._post_event(2)
|
|
||||||
|
|
||||||
|
|
||||||
class WlShellSurfaceGlobal(Global):
|
|
||||||
interface = WlShellSurface
|
|
||||||
|
|
||||||
|
|
||||||
WlShellSurface._gen_c()
|
|
||||||
WlShellSurface.proxy_class = WlShellSurfaceProxy
|
|
||||||
WlShellSurface.resource_class = WlShellSurfaceResource
|
|
||||||
WlShellSurface.global_class = WlShellSurfaceGlobal
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_shm_pool import WlShmPool
|
|
||||||
|
|
||||||
|
|
||||||
class WlShm(Interface):
|
|
||||||
"""Shared memory support
|
|
||||||
|
|
||||||
A singleton global object that provides support for shared memory.
|
|
||||||
|
|
||||||
Clients can create :class:`~pywayland.protocol.wayland.WlShmPool` objects
|
|
||||||
using the create_pool request.
|
|
||||||
|
|
||||||
On binding the :class:`WlShm` object one or more format events are emitted
|
|
||||||
to inform clients about the valid pixel formats that can be used for
|
|
||||||
buffers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_shm"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
invalid_format = 0
|
|
||||||
invalid_stride = 1
|
|
||||||
invalid_fd = 2
|
|
||||||
|
|
||||||
class format(enum.IntEnum):
|
|
||||||
argb8888 = 0
|
|
||||||
xrgb8888 = 1
|
|
||||||
c8 = 0x20203843
|
|
||||||
rgb332 = 0x38424752
|
|
||||||
bgr233 = 0x38524742
|
|
||||||
xrgb4444 = 0x32315258
|
|
||||||
xbgr4444 = 0x32314258
|
|
||||||
rgbx4444 = 0x32315852
|
|
||||||
bgrx4444 = 0x32315842
|
|
||||||
argb4444 = 0x32315241
|
|
||||||
abgr4444 = 0x32314241
|
|
||||||
rgba4444 = 0x32314152
|
|
||||||
bgra4444 = 0x32314142
|
|
||||||
xrgb1555 = 0x35315258
|
|
||||||
xbgr1555 = 0x35314258
|
|
||||||
rgbx5551 = 0x35315852
|
|
||||||
bgrx5551 = 0x35315842
|
|
||||||
argb1555 = 0x35315241
|
|
||||||
abgr1555 = 0x35314241
|
|
||||||
rgba5551 = 0x35314152
|
|
||||||
bgra5551 = 0x35314142
|
|
||||||
rgb565 = 0x36314752
|
|
||||||
bgr565 = 0x36314742
|
|
||||||
rgb888 = 0x34324752
|
|
||||||
bgr888 = 0x34324742
|
|
||||||
xbgr8888 = 0x34324258
|
|
||||||
rgbx8888 = 0x34325852
|
|
||||||
bgrx8888 = 0x34325842
|
|
||||||
abgr8888 = 0x34324241
|
|
||||||
rgba8888 = 0x34324152
|
|
||||||
bgra8888 = 0x34324142
|
|
||||||
xrgb2101010 = 0x30335258
|
|
||||||
xbgr2101010 = 0x30334258
|
|
||||||
rgbx1010102 = 0x30335852
|
|
||||||
bgrx1010102 = 0x30335842
|
|
||||||
argb2101010 = 0x30335241
|
|
||||||
abgr2101010 = 0x30334241
|
|
||||||
rgba1010102 = 0x30334152
|
|
||||||
bgra1010102 = 0x30334142
|
|
||||||
yuyv = 0x56595559
|
|
||||||
yvyu = 0x55595659
|
|
||||||
uyvy = 0x59565955
|
|
||||||
vyuy = 0x59555956
|
|
||||||
ayuv = 0x56555941
|
|
||||||
nv12 = 0x3231564E
|
|
||||||
nv21 = 0x3132564E
|
|
||||||
nv16 = 0x3631564E
|
|
||||||
nv61 = 0x3136564E
|
|
||||||
yuv410 = 0x39565559
|
|
||||||
yvu410 = 0x39555659
|
|
||||||
yuv411 = 0x31315559
|
|
||||||
yvu411 = 0x31315659
|
|
||||||
yuv420 = 0x32315559
|
|
||||||
yvu420 = 0x32315659
|
|
||||||
yuv422 = 0x36315559
|
|
||||||
yvu422 = 0x36315659
|
|
||||||
yuv444 = 0x34325559
|
|
||||||
yvu444 = 0x34325659
|
|
||||||
r8 = 0x20203852
|
|
||||||
r16 = 0x20363152
|
|
||||||
rg88 = 0x38384752
|
|
||||||
gr88 = 0x38385247
|
|
||||||
rg1616 = 0x32334752
|
|
||||||
gr1616 = 0x32335247
|
|
||||||
xrgb16161616f = 0x48345258
|
|
||||||
xbgr16161616f = 0x48344258
|
|
||||||
argb16161616f = 0x48345241
|
|
||||||
abgr16161616f = 0x48344241
|
|
||||||
xyuv8888 = 0x56555958
|
|
||||||
vuy888 = 0x34325556
|
|
||||||
vuy101010 = 0x30335556
|
|
||||||
y210 = 0x30313259
|
|
||||||
y212 = 0x32313259
|
|
||||||
y216 = 0x36313259
|
|
||||||
y410 = 0x30313459
|
|
||||||
y412 = 0x32313459
|
|
||||||
y416 = 0x36313459
|
|
||||||
xvyu2101010 = 0x30335658
|
|
||||||
xvyu12_16161616 = 0x36335658
|
|
||||||
xvyu16161616 = 0x38345658
|
|
||||||
y0l0 = 0x304C3059
|
|
||||||
x0l0 = 0x304C3058
|
|
||||||
y0l2 = 0x324C3059
|
|
||||||
x0l2 = 0x324C3058
|
|
||||||
yuv420_8bit = 0x38305559
|
|
||||||
yuv420_10bit = 0x30315559
|
|
||||||
xrgb8888_a8 = 0x38415258
|
|
||||||
xbgr8888_a8 = 0x38414258
|
|
||||||
rgbx8888_a8 = 0x38415852
|
|
||||||
bgrx8888_a8 = 0x38415842
|
|
||||||
rgb888_a8 = 0x38413852
|
|
||||||
bgr888_a8 = 0x38413842
|
|
||||||
rgb565_a8 = 0x38413552
|
|
||||||
bgr565_a8 = 0x38413542
|
|
||||||
nv24 = 0x3432564E
|
|
||||||
nv42 = 0x3234564E
|
|
||||||
p210 = 0x30313250
|
|
||||||
p010 = 0x30313050
|
|
||||||
p012 = 0x32313050
|
|
||||||
p016 = 0x36313050
|
|
||||||
axbxgxrx106106106106 = 0x30314241
|
|
||||||
nv15 = 0x3531564E
|
|
||||||
q410 = 0x30313451
|
|
||||||
q401 = 0x31303451
|
|
||||||
xrgb16161616 = 0x38345258
|
|
||||||
xbgr16161616 = 0x38344258
|
|
||||||
argb16161616 = 0x38345241
|
|
||||||
abgr16161616 = 0x38344241
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmProxy(Proxy[WlShm]):
|
|
||||||
interface = WlShm
|
|
||||||
|
|
||||||
@WlShm.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlShmPool),
|
|
||||||
Argument(ArgumentType.FileDescriptor),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def create_pool(self, fd: int, size: int) -> Proxy[WlShmPool]:
|
|
||||||
"""Create a shm pool
|
|
||||||
|
|
||||||
Create a new :class:`~pywayland.protocol.wayland.WlShmPool` object.
|
|
||||||
|
|
||||||
The pool can be used to create shared memory based buffer objects. The
|
|
||||||
server will mmap size bytes of the passed file descriptor, to use as
|
|
||||||
backing memory for the pool.
|
|
||||||
|
|
||||||
:param fd:
|
|
||||||
file descriptor for the pool
|
|
||||||
:type fd:
|
|
||||||
`ArgumentType.FileDescriptor`
|
|
||||||
:param size:
|
|
||||||
pool size, in bytes
|
|
||||||
:type size:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlShmPool` -- pool to create
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlShmPool, fd, size)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmResource(Resource):
|
|
||||||
interface = WlShm
|
|
||||||
|
|
||||||
@WlShm.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def format(self, format: int) -> None:
|
|
||||||
"""Pixel format description
|
|
||||||
|
|
||||||
Informs the client about a valid pixel format that can be used for
|
|
||||||
buffers. Known formats include argb8888 and xrgb8888.
|
|
||||||
|
|
||||||
:param format:
|
|
||||||
buffer pixel format
|
|
||||||
:type format:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, format)
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmGlobal(Global):
|
|
||||||
interface = WlShm
|
|
||||||
|
|
||||||
|
|
||||||
WlShm._gen_c()
|
|
||||||
WlShm.proxy_class = WlShmProxy
|
|
||||||
WlShm.resource_class = WlShmResource
|
|
||||||
WlShm.global_class = WlShmGlobal
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_buffer import WlBuffer
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmPool(Interface):
|
|
||||||
"""A shared memory pool
|
|
||||||
|
|
||||||
The :class:`WlShmPool` object encapsulates a piece of memory shared between
|
|
||||||
the compositor and client. Through the :class:`WlShmPool` object, the
|
|
||||||
client can allocate shared memory
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` objects. All objects created
|
|
||||||
through the same pool share the same underlying mapped memory. Reusing the
|
|
||||||
mapped memory avoids the setup/teardown overhead and is useful when
|
|
||||||
interactively resizing a surface or for many small buffers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_shm_pool"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmPoolProxy(Proxy[WlShmPool]):
|
|
||||||
interface = WlShmPool
|
|
||||||
|
|
||||||
@WlShmPool.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlBuffer),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def create_buffer(self, offset: int, width: int, height: int, stride: int, format: int) -> Proxy[WlBuffer]:
|
|
||||||
"""Create a buffer from the pool
|
|
||||||
|
|
||||||
Create a :class:`~pywayland.protocol.wayland.WlBuffer` object from the
|
|
||||||
pool.
|
|
||||||
|
|
||||||
The buffer is created offset bytes into the pool and has width and
|
|
||||||
height as specified. The stride argument specifies the number of bytes
|
|
||||||
from the beginning of one row to the beginning of the next. The format
|
|
||||||
is the pixel format of the buffer and must be one of those advertised
|
|
||||||
through the :func:`WlShm.format()
|
|
||||||
<pywayland.protocol.wayland.WlShm.format>` event.
|
|
||||||
|
|
||||||
A buffer will keep a reference to the pool it was created from so it is
|
|
||||||
valid to destroy the pool immediately after creating a buffer from it.
|
|
||||||
|
|
||||||
:param offset:
|
|
||||||
buffer byte offset within the pool
|
|
||||||
:type offset:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param width:
|
|
||||||
buffer width, in pixels
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
buffer height, in pixels
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param stride:
|
|
||||||
number of bytes from the beginning of one row to the beginning of
|
|
||||||
the next row
|
|
||||||
:type stride:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param format:
|
|
||||||
buffer pixel format
|
|
||||||
:type format:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` -- buffer to create
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(0, WlBuffer, offset, width, height, stride, format)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@WlShmPool.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the pool
|
|
||||||
|
|
||||||
Destroy the shared memory pool.
|
|
||||||
|
|
||||||
The mmapped memory will be released when all buffers that have been
|
|
||||||
created from this pool are gone.
|
|
||||||
"""
|
|
||||||
self._marshal(1)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlShmPool.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def resize(self, size: int) -> None:
|
|
||||||
"""Change the size of the pool mapping
|
|
||||||
|
|
||||||
This request will cause the server to remap the backing memory for the
|
|
||||||
pool from the file descriptor passed when the pool was created, but
|
|
||||||
using the new size. This request can only be used to make the pool
|
|
||||||
bigger.
|
|
||||||
|
|
||||||
This request only changes the amount of bytes that are mmapped by the
|
|
||||||
server and does not touch the file corresponding to the file descriptor
|
|
||||||
passed at creation time. It is the client's responsibility to ensure
|
|
||||||
that the file is at least as big as the new pool size.
|
|
||||||
|
|
||||||
:param size:
|
|
||||||
new size of the pool, in bytes
|
|
||||||
:type size:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(2, size)
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmPoolResource(Resource):
|
|
||||||
interface = WlShmPool
|
|
||||||
|
|
||||||
|
|
||||||
class WlShmPoolGlobal(Global):
|
|
||||||
interface = WlShmPool
|
|
||||||
|
|
||||||
|
|
||||||
WlShmPool._gen_c()
|
|
||||||
WlShmPool.proxy_class = WlShmPoolProxy
|
|
||||||
WlShmPool.resource_class = WlShmPoolResource
|
|
||||||
WlShmPool.global_class = WlShmPoolGlobal
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_subsurface import WlSubsurface
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubcompositor(Interface):
|
|
||||||
"""Sub-surface compositing
|
|
||||||
|
|
||||||
The global interface exposing sub-surface compositing capabilities. A
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`, that has sub-surfaces
|
|
||||||
associated, is called the parent surface. Sub-surfaces can be arbitrarily
|
|
||||||
nested and create a tree of sub-surfaces.
|
|
||||||
|
|
||||||
The root surface in a tree of sub-surfaces is the main surface. The main
|
|
||||||
surface cannot be a sub-surface, because sub-surfaces must always have a
|
|
||||||
parent.
|
|
||||||
|
|
||||||
A main surface with its sub-surfaces forms a (compound) window. For window
|
|
||||||
management purposes, this set of
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` objects is to be considered
|
|
||||||
as a single window, and it should also behave as such.
|
|
||||||
|
|
||||||
The aim of sub-surfaces is to offload some of the compositing work within a
|
|
||||||
window from clients to the compositor. A prime example is a video player
|
|
||||||
with decorations and video in separate
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` objects. This should allow
|
|
||||||
the compositor to pass YUV video buffer processing to dedicated overlay
|
|
||||||
hardware when possible.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_subcompositor"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
bad_surface = 0
|
|
||||||
bad_parent = 1
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubcompositorProxy(Proxy[WlSubcompositor]):
|
|
||||||
interface = WlSubcompositor
|
|
||||||
|
|
||||||
@WlSubcompositor.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Unbind from the subcompositor interface
|
|
||||||
|
|
||||||
Informs the server that the client will not be using this protocol
|
|
||||||
object anymore. This does not affect any other objects,
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSubsurface` objects included.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlSubcompositor.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlSubsurface),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def get_subsurface(self, surface: WlSurface, parent: WlSurface) -> Proxy[WlSubsurface]:
|
|
||||||
"""Give a surface the role sub-surface
|
|
||||||
|
|
||||||
Create a sub-surface interface for the given surface, and associate it
|
|
||||||
with the given parent surface. This turns a plain
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` into a sub-surface.
|
|
||||||
|
|
||||||
The to-be sub-surface must not already have another role, and it must
|
|
||||||
not have an existing :class:`~pywayland.protocol.wayland.WlSubsurface`
|
|
||||||
object. Otherwise the bad_surface protocol error is raised.
|
|
||||||
|
|
||||||
Adding sub-surfaces to a parent is a double-buffered operation on the
|
|
||||||
parent (see :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>`). The effect of adding a
|
|
||||||
sub-surface becomes visible on the next time the state of the parent
|
|
||||||
surface is applied.
|
|
||||||
|
|
||||||
The parent surface must not be one of the child surface's descendants,
|
|
||||||
and the parent must be different from the child surface, otherwise the
|
|
||||||
bad_parent protocol error is raised.
|
|
||||||
|
|
||||||
This request modifies the behaviour of :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` request on the sub-
|
|
||||||
surface, see the documentation on
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSubsurface` interface.
|
|
||||||
|
|
||||||
:param surface:
|
|
||||||
the surface to be turned into a sub-surface
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param parent:
|
|
||||||
the parent surface
|
|
||||||
:type parent:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSubsurface` -- the new sub-
|
|
||||||
surface object ID
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(1, WlSubsurface, surface, parent)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubcompositorResource(Resource):
|
|
||||||
interface = WlSubcompositor
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubcompositorGlobal(Global):
|
|
||||||
interface = WlSubcompositor
|
|
||||||
|
|
||||||
|
|
||||||
WlSubcompositor._gen_c()
|
|
||||||
WlSubcompositor.proxy_class = WlSubcompositorProxy
|
|
||||||
WlSubcompositor.resource_class = WlSubcompositorResource
|
|
||||||
WlSubcompositor.global_class = WlSubcompositorGlobal
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubsurface(Interface):
|
|
||||||
"""Sub-surface interface to a :class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
|
|
||||||
An additional interface to a :class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
object, which has been made a sub-surface. A sub-surface has one parent
|
|
||||||
surface. A sub-surface's size and position are not limited to that of the
|
|
||||||
parent. Particularly, a sub-surface is not automatically clipped to its
|
|
||||||
parent's area.
|
|
||||||
|
|
||||||
A sub-surface becomes mapped, when a non-NULL
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` is applied and the parent
|
|
||||||
surface is mapped. The order of which one happens first is irrelevant. A
|
|
||||||
sub-surface is hidden if the parent becomes hidden, or if a NULL
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` is applied. These rules apply
|
|
||||||
recursively through the tree of surfaces.
|
|
||||||
|
|
||||||
The behaviour of a :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` request on a sub-surface
|
|
||||||
depends on the sub-surface's mode. The possible modes are synchronized and
|
|
||||||
desynchronized, see methods :func:`WlSubsurface.set_sync()` and
|
|
||||||
:func:`WlSubsurface.set_desync()`. Synchronized mode caches the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` state to be applied when the
|
|
||||||
parent's state gets applied, and desynchronized mode applies the pending
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` state directly. A sub-
|
|
||||||
surface is initially in the synchronized mode.
|
|
||||||
|
|
||||||
Sub-surfaces also have another kind of state, which is managed by
|
|
||||||
:class:`WlSubsurface` requests, as opposed to
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` requests. This state
|
|
||||||
includes the sub-surface position relative to the parent surface
|
|
||||||
(:func:`WlSubsurface.set_position()`), and the stacking order of the parent
|
|
||||||
and its sub-surfaces (:func:`WlSubsurface.place_above()` and .place_below).
|
|
||||||
This state is applied when the parent surface's
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` state is applied, regardless
|
|
||||||
of the sub-surface's mode. As the exception, set_sync and set_desync are
|
|
||||||
effective immediately.
|
|
||||||
|
|
||||||
The main surface can be thought to be always in desynchronized mode, since
|
|
||||||
it does not have a parent in the sub-surfaces sense.
|
|
||||||
|
|
||||||
Even if a sub-surface is in desynchronized mode, it will behave as in
|
|
||||||
synchronized mode, if its parent surface behaves as in synchronized mode.
|
|
||||||
This rule is applied recursively throughout the tree of surfaces. This
|
|
||||||
means, that one can set a sub-surface into synchronized mode, and then
|
|
||||||
assume that all its child and grand-child sub-surfaces are synchronized,
|
|
||||||
too, without explicitly setting them.
|
|
||||||
|
|
||||||
Destroying a sub-surface takes effect immediately. If you need to
|
|
||||||
synchronize the removal of a sub-surface to the parent surface update,
|
|
||||||
unmap the sub-surface first by attaching a NULL
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`, update parent, and then
|
|
||||||
destroy the sub-surface.
|
|
||||||
|
|
||||||
If the parent :class:`~pywayland.protocol.wayland.WlSurface` object is
|
|
||||||
destroyed, the sub-surface is unmapped.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_subsurface"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
bad_surface = 0
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubsurfaceProxy(Proxy[WlSubsurface]):
|
|
||||||
interface = WlSubsurface
|
|
||||||
|
|
||||||
@WlSubsurface.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Remove sub-surface interface
|
|
||||||
|
|
||||||
The sub-surface interface is removed from the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` object that was turned
|
|
||||||
into a sub-surface with a :func:`WlSubcompositor.get_subsurface()
|
|
||||||
<pywayland.protocol.wayland.WlSubcompositor.get_subsurface>` request.
|
|
||||||
The wl_surface's association to the parent is deleted. The
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` is unmapped immediately.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlSubsurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def set_position(self, x: int, y: int) -> None:
|
|
||||||
"""Reposition the sub-surface
|
|
||||||
|
|
||||||
This schedules a sub-surface position change. The sub-surface will be
|
|
||||||
moved so that its origin (top left corner pixel) will be at the
|
|
||||||
location x, y of the parent surface coordinate system. The coordinates
|
|
||||||
are not restricted to the parent surface area. Negative values are
|
|
||||||
allowed.
|
|
||||||
|
|
||||||
The scheduled coordinates will take effect whenever the state of the
|
|
||||||
parent surface is applied. When this happens depends on whether the
|
|
||||||
parent surface is in synchronized mode or not. See
|
|
||||||
:func:`WlSubsurface.set_sync()` and :func:`WlSubsurface.set_desync()`
|
|
||||||
for details.
|
|
||||||
|
|
||||||
If more than one set_position request is invoked by the client before
|
|
||||||
the commit of the parent surface, the position of a new request always
|
|
||||||
replaces the scheduled position from any previous request.
|
|
||||||
|
|
||||||
The initial position is 0, 0.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
x coordinate in the parent surface
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
y coordinate in the parent surface
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(1, x, y)
|
|
||||||
|
|
||||||
@WlSubsurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def place_above(self, sibling: WlSurface) -> None:
|
|
||||||
"""Restack the sub-surface
|
|
||||||
|
|
||||||
This sub-surface is taken from the stack, and put back just above the
|
|
||||||
reference surface, changing the z-order of the sub-surfaces. The
|
|
||||||
reference surface must be one of the sibling surfaces, or the parent
|
|
||||||
surface. Using any other surface, including this sub-surface, will
|
|
||||||
cause a protocol error.
|
|
||||||
|
|
||||||
The z-order is double-buffered. Requests are handled in order and
|
|
||||||
applied immediately to a pending state. The final pending state is
|
|
||||||
copied to the active state the next time the state of the parent
|
|
||||||
surface is applied. When this happens depends on whether the parent
|
|
||||||
surface is in synchronized mode or not. See
|
|
||||||
:func:`WlSubsurface.set_sync()` and :func:`WlSubsurface.set_desync()`
|
|
||||||
for details.
|
|
||||||
|
|
||||||
A new sub-surface is initially added as the top-most in the stack of
|
|
||||||
its siblings and parent.
|
|
||||||
|
|
||||||
:param sibling:
|
|
||||||
the reference surface
|
|
||||||
:type sibling:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
"""
|
|
||||||
self._marshal(2, sibling)
|
|
||||||
|
|
||||||
@WlSubsurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
)
|
|
||||||
def place_below(self, sibling: WlSurface) -> None:
|
|
||||||
"""Restack the sub-surface
|
|
||||||
|
|
||||||
The sub-surface is placed just below the reference surface. See
|
|
||||||
:func:`WlSubsurface.place_above()`.
|
|
||||||
|
|
||||||
:param sibling:
|
|
||||||
the reference surface
|
|
||||||
:type sibling:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
"""
|
|
||||||
self._marshal(3, sibling)
|
|
||||||
|
|
||||||
@WlSubsurface.request()
|
|
||||||
def set_sync(self) -> None:
|
|
||||||
"""Set sub-surface to synchronized mode
|
|
||||||
|
|
||||||
Change the commit behaviour of the sub-surface to synchronized mode,
|
|
||||||
also described as the parent dependent mode.
|
|
||||||
|
|
||||||
In synchronized mode, :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` on a sub-surface will
|
|
||||||
accumulate the committed state in a cache, but the state will not be
|
|
||||||
applied and hence will not change the compositor output. The cached
|
|
||||||
state is applied to the sub-surface immediately after the parent
|
|
||||||
surface's state is applied. This ensures atomic updates of the parent
|
|
||||||
and all its synchronized sub-surfaces. Applying the cached state will
|
|
||||||
invalidate the cache, so further parent surface commits do not
|
|
||||||
(re-)apply old state.
|
|
||||||
|
|
||||||
See :class:`WlSubsurface` for the recursive effect of this mode.
|
|
||||||
"""
|
|
||||||
self._marshal(4)
|
|
||||||
|
|
||||||
@WlSubsurface.request()
|
|
||||||
def set_desync(self) -> None:
|
|
||||||
"""Set sub-surface to desynchronized mode
|
|
||||||
|
|
||||||
Change the commit behaviour of the sub-surface to desynchronized mode,
|
|
||||||
also described as independent or freely running mode.
|
|
||||||
|
|
||||||
In desynchronized mode, :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` on a sub-surface will
|
|
||||||
apply the pending state directly, without caching, as happens normally
|
|
||||||
with a :class:`~pywayland.protocol.wayland.WlSurface`. Calling
|
|
||||||
:func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` on the parent surface
|
|
||||||
has no effect on the sub-surface's
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface` state. This mode allows
|
|
||||||
a sub-surface to be updated on its own.
|
|
||||||
|
|
||||||
If cached state exists when :func:`WlSurface.commit()
|
|
||||||
<pywayland.protocol.wayland.WlSurface.commit>` is called in
|
|
||||||
desynchronized mode, the pending state is added to the cached state,
|
|
||||||
and applied as a whole. This invalidates the cache.
|
|
||||||
|
|
||||||
Note: even if a sub-surface is set to desynchronized, a parent sub-
|
|
||||||
surface may override it to behave as synchronized. For details, see
|
|
||||||
:class:`WlSubsurface`.
|
|
||||||
|
|
||||||
If a surface's parent surface behaves as desynchronized, then the
|
|
||||||
cached state is applied on set_desync.
|
|
||||||
"""
|
|
||||||
self._marshal(5)
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubsurfaceResource(Resource):
|
|
||||||
interface = WlSubsurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlSubsurfaceGlobal(Global):
|
|
||||||
interface = WlSubsurface
|
|
||||||
|
|
||||||
|
|
||||||
WlSubsurface._gen_c()
|
|
||||||
WlSubsurface.proxy_class = WlSubsurfaceProxy
|
|
||||||
WlSubsurface.resource_class = WlSubsurfaceResource
|
|
||||||
WlSubsurface.global_class = WlSubsurfaceGlobal
|
|
||||||
@@ -1,704 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_buffer import WlBuffer
|
|
||||||
from .wl_callback import WlCallback
|
|
||||||
from .wl_output import WlOutput
|
|
||||||
from .wl_region import WlRegion
|
|
||||||
|
|
||||||
|
|
||||||
class WlSurface(Interface):
|
|
||||||
"""An onscreen surface
|
|
||||||
|
|
||||||
A surface is a rectangular area that may be displayed on zero or more
|
|
||||||
outputs, and shown any number of times at the compositor's discretion. They
|
|
||||||
can present wl_buffers, receive user input, and define a local coordinate
|
|
||||||
system.
|
|
||||||
|
|
||||||
The size of a surface (and relative positions on it) is described in
|
|
||||||
surface-local coordinates, which may differ from the buffer coordinates of
|
|
||||||
the pixel content, in case a buffer_transform or a buffer_scale is used.
|
|
||||||
|
|
||||||
A surface without a "role" is fairly useless: a compositor does not know
|
|
||||||
where, when or how to present it. The role is the purpose of a
|
|
||||||
:class:`WlSurface`. Examples of roles are a cursor for a pointer (as set by
|
|
||||||
:func:`WlPointer.set_cursor()
|
|
||||||
<pywayland.protocol.wayland.WlPointer.set_cursor>`), a drag icon
|
|
||||||
(:func:`WlDataDevice.start_drag()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.start_drag>`), a sub-surface
|
|
||||||
(:func:`WlSubcompositor.get_subsurface()
|
|
||||||
<pywayland.protocol.wayland.WlSubcompositor.get_subsurface>`), and a window
|
|
||||||
as defined by a shell protocol (e.g. :func:`WlShell.get_shell_surface()
|
|
||||||
<pywayland.protocol.wayland.WlShell.get_shell_surface>`).
|
|
||||||
|
|
||||||
A surface can have only one role at a time. Initially a :class:`WlSurface`
|
|
||||||
does not have a role. Once a :class:`WlSurface` is given a role, it is set
|
|
||||||
permanently for the whole lifetime of the :class:`WlSurface` object. Giving
|
|
||||||
the current role again is allowed, unless explicitly forbidden by the
|
|
||||||
relevant interface specification.
|
|
||||||
|
|
||||||
Surface roles are given by requests in other interfaces such as
|
|
||||||
:func:`WlPointer.set_cursor()
|
|
||||||
<pywayland.protocol.wayland.WlPointer.set_cursor>`. The request should
|
|
||||||
explicitly mention that this request gives a role to a :class:`WlSurface`.
|
|
||||||
Often, this request also creates a new protocol object that represents the
|
|
||||||
role and adds additional functionality to :class:`WlSurface`. When a client
|
|
||||||
wants to destroy a :class:`WlSurface`, they must destroy this role object
|
|
||||||
before the :class:`WlSurface`, otherwise a defunct_role_object error is
|
|
||||||
sent.
|
|
||||||
|
|
||||||
Destroying the role object does not remove the role from the
|
|
||||||
:class:`WlSurface`, but it may stop the :class:`WlSurface` from "playing
|
|
||||||
the role". For instance, if a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSubsurface` object is destroyed, the
|
|
||||||
:class:`WlSurface` it was created for will be unmapped and forget its
|
|
||||||
position and z-order. It is allowed to create a
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSubsurface` for the same
|
|
||||||
:class:`WlSurface` again, but it is not allowed to use the
|
|
||||||
:class:`WlSurface` as a cursor (cursor is a different role than sub-
|
|
||||||
surface, and role switching is not allowed).
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_surface"
|
|
||||||
version = 6
|
|
||||||
|
|
||||||
class error(enum.IntEnum):
|
|
||||||
invalid_scale = 0
|
|
||||||
invalid_transform = 1
|
|
||||||
invalid_size = 2
|
|
||||||
invalid_offset = 3
|
|
||||||
defunct_role_object = 4
|
|
||||||
|
|
||||||
|
|
||||||
class WlSurfaceProxy(Proxy[WlSurface]):
|
|
||||||
interface = WlSurface
|
|
||||||
|
|
||||||
@WlSurface.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Delete surface
|
|
||||||
|
|
||||||
Deletes the surface and invalidates its object ID.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlBuffer, nullable=True),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def attach(self, buffer: WlBuffer | None, x: int, y: int) -> None:
|
|
||||||
"""Set the surface contents
|
|
||||||
|
|
||||||
Set a buffer as the content of this surface.
|
|
||||||
|
|
||||||
The new size of the surface is calculated based on the buffer size
|
|
||||||
transformed by the inverse buffer_transform and the inverse
|
|
||||||
buffer_scale. This means that at commit time the supplied buffer size
|
|
||||||
must be an integer multiple of the buffer_scale. If that's not the
|
|
||||||
case, an invalid_size error is sent.
|
|
||||||
|
|
||||||
The x and y arguments specify the location of the new pending buffer's
|
|
||||||
upper left corner, relative to the current buffer's upper left corner,
|
|
||||||
in surface-local coordinates. In other words, the x and y, combined
|
|
||||||
with the new surface size define in which directions the surface's size
|
|
||||||
changes. Setting anything other than 0 as x and y arguments is
|
|
||||||
discouraged, and should instead be replaced with using the separate
|
|
||||||
:func:`WlSurface.offset()` request.
|
|
||||||
|
|
||||||
When the bound :class:`WlSurface` version is 5 or higher, passing any
|
|
||||||
non-zero x or y is a protocol violation, and will result in an
|
|
||||||
'invalid_offset' error being raised. The x and y arguments are ignored
|
|
||||||
and do not change the pending state. To achieve equivalent semantics,
|
|
||||||
use :func:`WlSurface.offset()`.
|
|
||||||
|
|
||||||
Surface contents are double-buffered state, see
|
|
||||||
:func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
The initial surface contents are void; there is no content.
|
|
||||||
:func:`WlSurface.attach()` assigns the given
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` as the pending
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`.
|
|
||||||
:func:`WlSurface.commit()` makes the pending
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` the new surface contents,
|
|
||||||
and the size of the surface becomes the size calculated from the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`, as described above.
|
|
||||||
After commit, there is no pending buffer until the next attach.
|
|
||||||
|
|
||||||
Committing a pending :class:`~pywayland.protocol.wayland.WlBuffer`
|
|
||||||
allows the compositor to read the pixels in the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`. The compositor may
|
|
||||||
access the pixels at any time after the :func:`WlSurface.commit()`
|
|
||||||
request. When the compositor will not access the pixels anymore, it
|
|
||||||
will send the :func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` event. Only after
|
|
||||||
receiving :func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>`, the client may reuse
|
|
||||||
the :class:`~pywayland.protocol.wayland.WlBuffer`. A
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` that has been attached
|
|
||||||
and then replaced by another attach instead of committed will not
|
|
||||||
receive a release event, and is not used by the compositor.
|
|
||||||
|
|
||||||
If a pending :class:`~pywayland.protocol.wayland.WlBuffer` has been
|
|
||||||
committed to more than one :class:`WlSurface`, the delivery of
|
|
||||||
:func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` events becomes
|
|
||||||
undefined. A well behaved client should not rely on
|
|
||||||
:func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` events in this case.
|
|
||||||
Alternatively, a client could create multiple
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` objects from the same
|
|
||||||
backing storage or use wp_linux_buffer_release.
|
|
||||||
|
|
||||||
Destroying the :class:`~pywayland.protocol.wayland.WlBuffer` after
|
|
||||||
:func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` does not change the
|
|
||||||
surface contents. Destroying the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` before
|
|
||||||
:func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` is allowed as long as
|
|
||||||
the underlying buffer storage isn't re-used (this can happen e.g. on
|
|
||||||
client process termination). However, if the client destroys the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` before receiving the
|
|
||||||
:func:`WlBuffer.release()
|
|
||||||
<pywayland.protocol.wayland.WlBuffer.release>` event and mutates the
|
|
||||||
underlying buffer storage, the surface contents become undefined
|
|
||||||
immediately.
|
|
||||||
|
|
||||||
If :func:`WlSurface.attach()` is sent with a NULL
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`, the following
|
|
||||||
:func:`WlSurface.commit()` will remove the surface content.
|
|
||||||
|
|
||||||
:param buffer:
|
|
||||||
buffer of surface contents
|
|
||||||
:type buffer:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` or `None`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(1, buffer, x, y)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def damage(self, x: int, y: int, width: int, height: int) -> None:
|
|
||||||
"""Mark part of the surface damaged
|
|
||||||
|
|
||||||
This request is used to describe the regions where the pending buffer
|
|
||||||
is different from the current surface contents, and where the surface
|
|
||||||
therefore needs to be repainted. The compositor ignores the parts of
|
|
||||||
the damage that fall outside of the surface.
|
|
||||||
|
|
||||||
Damage is double-buffered state, see :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
The damage rectangle is specified in surface-local coordinates, where x
|
|
||||||
and y specify the upper left corner of the damage rectangle.
|
|
||||||
|
|
||||||
The initial value for pending damage is empty: no damage.
|
|
||||||
:func:`WlSurface.damage()` adds pending damage: the new pending damage
|
|
||||||
is the union of old pending damage and the given rectangle.
|
|
||||||
|
|
||||||
:func:`WlSurface.commit()` assigns pending damage as the current
|
|
||||||
damage, and clears pending damage. The server will clear the current
|
|
||||||
damage as it repaints the surface.
|
|
||||||
|
|
||||||
Note! New clients should not use this request. Instead damage can be
|
|
||||||
posted with :func:`WlSurface.damage_buffer()` which uses buffer
|
|
||||||
coordinates instead of surface coordinates.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param width:
|
|
||||||
width of damage rectangle
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
height of damage rectangle
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(2, x, y, width, height)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=WlCallback),
|
|
||||||
)
|
|
||||||
def frame(self) -> Proxy[WlCallback]:
|
|
||||||
"""Request a frame throttling hint
|
|
||||||
|
|
||||||
Request a notification when it is a good time to start drawing a new
|
|
||||||
frame, by creating a frame callback. This is useful for throttling
|
|
||||||
redrawing operations, and driving animations.
|
|
||||||
|
|
||||||
When a client is animating on a :class:`WlSurface`, it can use the
|
|
||||||
'frame' request to get notified when it is a good time to draw and
|
|
||||||
commit the next frame of animation. If the client commits an update
|
|
||||||
earlier than that, it is likely that some updates will not make it to
|
|
||||||
the display, and the client is wasting resources by drawing too often.
|
|
||||||
|
|
||||||
The frame request will take effect on the next
|
|
||||||
:func:`WlSurface.commit()`. The notification will only be posted for
|
|
||||||
one frame unless requested again. For a :class:`WlSurface`, the
|
|
||||||
notifications are posted in the order the frame requests were
|
|
||||||
committed.
|
|
||||||
|
|
||||||
The server must send the notifications so that a client will not send
|
|
||||||
excessive updates, while still allowing the highest possible update
|
|
||||||
rate for clients that wait for the reply before drawing again. The
|
|
||||||
server should give some time for the client to draw and commit after
|
|
||||||
sending the frame callback events to let it hit the next output
|
|
||||||
refresh.
|
|
||||||
|
|
||||||
A server should avoid signaling the frame callbacks if the surface is
|
|
||||||
not visible in any way, e.g. the surface is off-screen, or completely
|
|
||||||
obscured by other opaque surfaces.
|
|
||||||
|
|
||||||
The object returned by this request will be destroyed by the compositor
|
|
||||||
after the callback is fired and as such the client must not attempt to
|
|
||||||
use it after that point.
|
|
||||||
|
|
||||||
The callback_data passed in the callback is the current time, in
|
|
||||||
milliseconds, with an undefined base.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlCallback` -- callback object
|
|
||||||
for the frame request
|
|
||||||
"""
|
|
||||||
callback = self._marshal_constructor(3, WlCallback)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlRegion, nullable=True),
|
|
||||||
)
|
|
||||||
def set_opaque_region(self, region: WlRegion | None) -> None:
|
|
||||||
"""Set opaque region
|
|
||||||
|
|
||||||
This request sets the region of the surface that contains opaque
|
|
||||||
content.
|
|
||||||
|
|
||||||
The opaque region is an optimization hint for the compositor that lets
|
|
||||||
it optimize the redrawing of content behind opaque regions. Setting an
|
|
||||||
opaque region is not required for correct behaviour, but marking
|
|
||||||
transparent content as opaque will result in repaint artifacts.
|
|
||||||
|
|
||||||
The opaque region is specified in surface-local coordinates.
|
|
||||||
|
|
||||||
The compositor ignores the parts of the opaque region that fall outside
|
|
||||||
of the surface.
|
|
||||||
|
|
||||||
Opaque region is double-buffered state, see :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
:func:`WlSurface.set_opaque_region()` changes the pending opaque
|
|
||||||
region. :func:`WlSurface.commit()` copies the pending region to the
|
|
||||||
current region. Otherwise, the pending and current regions are never
|
|
||||||
changed.
|
|
||||||
|
|
||||||
The initial value for an opaque region is empty. Setting the pending
|
|
||||||
opaque region has copy semantics, and the
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegion` object can be destroyed
|
|
||||||
immediately. A NULL :class:`~pywayland.protocol.wayland.WlRegion`
|
|
||||||
causes the pending opaque region to be set to empty.
|
|
||||||
|
|
||||||
:param region:
|
|
||||||
opaque region of the surface
|
|
||||||
:type region:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegion` or `None`
|
|
||||||
"""
|
|
||||||
self._marshal(4, region)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlRegion, nullable=True),
|
|
||||||
)
|
|
||||||
def set_input_region(self, region: WlRegion | None) -> None:
|
|
||||||
"""Set input region
|
|
||||||
|
|
||||||
This request sets the region of the surface that can receive pointer
|
|
||||||
and touch events.
|
|
||||||
|
|
||||||
Input events happening outside of this region will try the next surface
|
|
||||||
in the server surface stack. The compositor ignores the parts of the
|
|
||||||
input region that fall outside of the surface.
|
|
||||||
|
|
||||||
The input region is specified in surface-local coordinates.
|
|
||||||
|
|
||||||
Input region is double-buffered state, see :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
:func:`WlSurface.set_input_region()` changes the pending input region.
|
|
||||||
:func:`WlSurface.commit()` copies the pending region to the current
|
|
||||||
region. Otherwise the pending and current regions are never changed,
|
|
||||||
except cursor and icon surfaces are special cases, see
|
|
||||||
:func:`WlPointer.set_cursor()
|
|
||||||
<pywayland.protocol.wayland.WlPointer.set_cursor>` and
|
|
||||||
:func:`WlDataDevice.start_drag()
|
|
||||||
<pywayland.protocol.wayland.WlDataDevice.start_drag>`.
|
|
||||||
|
|
||||||
The initial value for an input region is infinite. That means the whole
|
|
||||||
surface will accept input. Setting the pending input region has copy
|
|
||||||
semantics, and the :class:`~pywayland.protocol.wayland.WlRegion` object
|
|
||||||
can be destroyed immediately. A NULL
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegion` causes the input region
|
|
||||||
to be set to infinite.
|
|
||||||
|
|
||||||
:param region:
|
|
||||||
input region of the surface
|
|
||||||
:type region:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlRegion` or `None`
|
|
||||||
"""
|
|
||||||
self._marshal(5, region)
|
|
||||||
|
|
||||||
@WlSurface.request()
|
|
||||||
def commit(self) -> None:
|
|
||||||
"""Commit pending surface state
|
|
||||||
|
|
||||||
Surface state (input, opaque, and damage regions, attached buffers,
|
|
||||||
etc.) is double-buffered. Protocol requests modify the pending state,
|
|
||||||
as opposed to the current state in use by the compositor. A commit
|
|
||||||
request atomically applies all pending state, replacing the current
|
|
||||||
state. After commit, the new pending state is as documented for each
|
|
||||||
related request.
|
|
||||||
|
|
||||||
On commit, a pending :class:`~pywayland.protocol.wayland.WlBuffer` is
|
|
||||||
applied first, and all other state second. This means that all
|
|
||||||
coordinates in double-buffered state are relative to the new
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer` coming into use, except
|
|
||||||
for :func:`WlSurface.attach()` itself. If there is no pending
|
|
||||||
:class:`~pywayland.protocol.wayland.WlBuffer`, the coordinates are
|
|
||||||
relative to the current surface contents.
|
|
||||||
|
|
||||||
All requests that need a commit to become effective are documented to
|
|
||||||
affect double-buffered state.
|
|
||||||
|
|
||||||
Other interfaces may add further double-buffered surface state.
|
|
||||||
"""
|
|
||||||
self._marshal(6)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=2,
|
|
||||||
)
|
|
||||||
def set_buffer_transform(self, transform: int) -> None:
|
|
||||||
"""Sets the buffer transformation
|
|
||||||
|
|
||||||
This request sets an optional transformation on how the compositor
|
|
||||||
interprets the contents of the buffer attached to the surface. The
|
|
||||||
accepted values for the transform parameter are the values for
|
|
||||||
:func:`WlOutput.transform()
|
|
||||||
<pywayland.protocol.wayland.WlOutput.transform>`.
|
|
||||||
|
|
||||||
Buffer transform is double-buffered state, see
|
|
||||||
:func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
A newly created surface has its buffer transformation set to normal.
|
|
||||||
|
|
||||||
:func:`WlSurface.set_buffer_transform()` changes the pending buffer
|
|
||||||
transformation. :func:`WlSurface.commit()` copies the pending buffer
|
|
||||||
transformation to the current one. Otherwise, the pending and current
|
|
||||||
values are never changed.
|
|
||||||
|
|
||||||
The purpose of this request is to allow clients to render content
|
|
||||||
according to the output transform, thus permitting the compositor to
|
|
||||||
use certain optimizations even if the display is rotated. Using
|
|
||||||
hardware overlays and scanning out a client buffer for fullscreen
|
|
||||||
surfaces are examples of such optimizations. Those optimizations are
|
|
||||||
highly dependent on the compositor implementation, so the use of this
|
|
||||||
request should be considered on a case-by-case basis.
|
|
||||||
|
|
||||||
Note that if the transform value includes 90 or 270 degree rotation,
|
|
||||||
the width of the buffer will become the surface height and the height
|
|
||||||
of the buffer will become the surface width.
|
|
||||||
|
|
||||||
If transform is not one of the values from the
|
|
||||||
:func:`WlOutput.transform()
|
|
||||||
<pywayland.protocol.wayland.WlOutput.transform>` enum the
|
|
||||||
invalid_transform protocol error is raised.
|
|
||||||
|
|
||||||
:param transform:
|
|
||||||
transform for interpreting buffer contents
|
|
||||||
:type transform:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(7, transform)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def set_buffer_scale(self, scale: int) -> None:
|
|
||||||
"""Sets the buffer scaling factor
|
|
||||||
|
|
||||||
This request sets an optional scaling factor on how the compositor
|
|
||||||
interprets the contents of the buffer attached to the window.
|
|
||||||
|
|
||||||
Buffer scale is double-buffered state, see :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
A newly created surface has its buffer scale set to 1.
|
|
||||||
|
|
||||||
:func:`WlSurface.set_buffer_scale()` changes the pending buffer scale.
|
|
||||||
:func:`WlSurface.commit()` copies the pending buffer scale to the
|
|
||||||
current one. Otherwise, the pending and current values are never
|
|
||||||
changed.
|
|
||||||
|
|
||||||
The purpose of this request is to allow clients to supply higher
|
|
||||||
resolution buffer data for use on high resolution outputs. It is
|
|
||||||
intended that you pick the same buffer scale as the scale of the output
|
|
||||||
that the surface is displayed on. This means the compositor can avoid
|
|
||||||
scaling when rendering the surface on that output.
|
|
||||||
|
|
||||||
Note that if the scale is larger than 1, then you have to attach a
|
|
||||||
buffer that is larger (by a factor of scale in each dimension) than the
|
|
||||||
desired surface size.
|
|
||||||
|
|
||||||
If scale is not positive the invalid_scale protocol error is raised.
|
|
||||||
|
|
||||||
:param scale:
|
|
||||||
positive scale for interpreting buffer contents
|
|
||||||
:type scale:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(8, scale)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=4,
|
|
||||||
)
|
|
||||||
def damage_buffer(self, x: int, y: int, width: int, height: int) -> None:
|
|
||||||
"""Mark part of the surface damaged using buffer coordinates
|
|
||||||
|
|
||||||
This request is used to describe the regions where the pending buffer
|
|
||||||
is different from the current surface contents, and where the surface
|
|
||||||
therefore needs to be repainted. The compositor ignores the parts of
|
|
||||||
the damage that fall outside of the surface.
|
|
||||||
|
|
||||||
Damage is double-buffered state, see :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
The damage rectangle is specified in buffer coordinates, where x and y
|
|
||||||
specify the upper left corner of the damage rectangle.
|
|
||||||
|
|
||||||
The initial value for pending damage is empty: no damage.
|
|
||||||
:func:`WlSurface.damage_buffer()` adds pending damage: the new pending
|
|
||||||
damage is the union of old pending damage and the given rectangle.
|
|
||||||
|
|
||||||
:func:`WlSurface.commit()` assigns pending damage as the current
|
|
||||||
damage, and clears pending damage. The server will clear the current
|
|
||||||
damage as it repaints the surface.
|
|
||||||
|
|
||||||
This request differs from :func:`WlSurface.damage()` in only one way -
|
|
||||||
it takes damage in buffer coordinates instead of surface-local
|
|
||||||
coordinates. While this generally is more intuitive than surface
|
|
||||||
coordinates, it is especially desirable when using wp_viewport or when
|
|
||||||
a drawing library (like EGL) is unaware of buffer scale and buffer
|
|
||||||
transform.
|
|
||||||
|
|
||||||
Note: Because buffer transformation changes and damage requests may be
|
|
||||||
interleaved in the protocol stream, it is impossible to determine the
|
|
||||||
actual mapping between surface and buffer damage until
|
|
||||||
:func:`WlSurface.commit()` time. Therefore, compositors wishing to take
|
|
||||||
both kinds of damage into account will have to accumulate damage from
|
|
||||||
the two requests separately and only transform from one to the other
|
|
||||||
after receiving the :func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
buffer-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
buffer-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param width:
|
|
||||||
width of damage rectangle
|
|
||||||
:type width:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param height:
|
|
||||||
height of damage rectangle
|
|
||||||
:type height:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(9, x, y, width, height)
|
|
||||||
|
|
||||||
@WlSurface.request(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=5,
|
|
||||||
)
|
|
||||||
def offset(self, x: int, y: int) -> None:
|
|
||||||
"""Set the surface contents offset
|
|
||||||
|
|
||||||
The x and y arguments specify the location of the new pending buffer's
|
|
||||||
upper left corner, relative to the current buffer's upper left corner,
|
|
||||||
in surface-local coordinates. In other words, the x and y, combined
|
|
||||||
with the new surface size define in which directions the surface's size
|
|
||||||
changes.
|
|
||||||
|
|
||||||
Surface location offset is double-buffered state, see
|
|
||||||
:func:`WlSurface.commit()`.
|
|
||||||
|
|
||||||
This request is semantically equivalent to and the replaces the x and y
|
|
||||||
arguments in the :func:`WlSurface.attach()` request in
|
|
||||||
:class:`WlSurface` versions prior to 5. See :func:`WlSurface.attach()`
|
|
||||||
for details.
|
|
||||||
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._marshal(10, x, y)
|
|
||||||
|
|
||||||
|
|
||||||
class WlSurfaceResource(Resource):
|
|
||||||
interface = WlSurface
|
|
||||||
|
|
||||||
@WlSurface.event(
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput),
|
|
||||||
)
|
|
||||||
def enter(self, output: WlOutput) -> None:
|
|
||||||
"""Surface enters an output
|
|
||||||
|
|
||||||
This is emitted whenever a surface's creation, movement, or resizing
|
|
||||||
results in some part of it being within the scanout region of an
|
|
||||||
output.
|
|
||||||
|
|
||||||
Note that a surface may be overlapping with zero or more outputs.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
output entered by the surface
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
|
||||||
"""
|
|
||||||
self._post_event(0, output)
|
|
||||||
|
|
||||||
@WlSurface.event(
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput),
|
|
||||||
)
|
|
||||||
def leave(self, output: WlOutput) -> None:
|
|
||||||
"""Surface leaves an output
|
|
||||||
|
|
||||||
This is emitted whenever a surface's creation, movement, or resizing
|
|
||||||
results in it no longer having any part of it within the scanout region
|
|
||||||
of an output.
|
|
||||||
|
|
||||||
Clients should not use the number of outputs the surface is on for
|
|
||||||
frame throttling purposes. The surface might be hidden even if no leave
|
|
||||||
event has been sent, and the compositor might expect new surface
|
|
||||||
content updates even if no enter event has been sent. The frame event
|
|
||||||
should be used instead.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
output left by the surface
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
|
||||||
"""
|
|
||||||
self._post_event(1, output)
|
|
||||||
|
|
||||||
@WlSurface.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
version=6,
|
|
||||||
)
|
|
||||||
def preferred_buffer_scale(self, factor: int) -> None:
|
|
||||||
"""Preferred buffer scale for the surface
|
|
||||||
|
|
||||||
This event indicates the preferred buffer scale for this surface. It is
|
|
||||||
sent whenever the compositor's preference changes.
|
|
||||||
|
|
||||||
It is intended that scaling aware clients use this event to scale their
|
|
||||||
content and use :func:`WlSurface.set_buffer_scale()` to indicate the
|
|
||||||
scale they have rendered with. This allows clients to supply a higher
|
|
||||||
detail buffer.
|
|
||||||
|
|
||||||
:param factor:
|
|
||||||
preferred scaling factor
|
|
||||||
:type factor:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(2, factor)
|
|
||||||
|
|
||||||
@WlSurface.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=6,
|
|
||||||
)
|
|
||||||
def preferred_buffer_transform(self, transform: int) -> None:
|
|
||||||
"""Preferred buffer transform for the surface
|
|
||||||
|
|
||||||
This event indicates the preferred buffer transform for this surface.
|
|
||||||
It is sent whenever the compositor's preference changes.
|
|
||||||
|
|
||||||
It is intended that transform aware clients use this event to apply the
|
|
||||||
transform to their content and use
|
|
||||||
:func:`WlSurface.set_buffer_transform()` to indicate the transform they
|
|
||||||
have rendered with.
|
|
||||||
|
|
||||||
:param transform:
|
|
||||||
preferred transform
|
|
||||||
:type transform:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(3, transform)
|
|
||||||
|
|
||||||
|
|
||||||
class WlSurfaceGlobal(Global):
|
|
||||||
interface = WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
WlSurface._gen_c()
|
|
||||||
WlSurface.proxy_class = WlSurfaceProxy
|
|
||||||
WlSurface.resource_class = WlSurfaceResource
|
|
||||||
WlSurface.global_class = WlSurfaceGlobal
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
# Copyright © 2010-2011 Intel Corporation
|
|
||||||
# Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person
|
|
||||||
# obtaining a copy of this software and associated documentation files
|
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
|
||||||
# subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice (including the
|
|
||||||
# next paragraph) shall be included in all copies or substantial
|
|
||||||
# portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .wl_surface import WlSurface
|
|
||||||
|
|
||||||
|
|
||||||
class WlTouch(Interface):
|
|
||||||
"""Touchscreen input device
|
|
||||||
|
|
||||||
The :class:`WlTouch` interface represents a touchscreen associated with a
|
|
||||||
seat.
|
|
||||||
|
|
||||||
Touch interactions can consist of one or more contacts. For each contact, a
|
|
||||||
series of events is generated, starting with a down event, followed by zero
|
|
||||||
or more motion events, and ending with an up event. Events relating to the
|
|
||||||
same contact point can be identified by the ID of the sequence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "wl_touch"
|
|
||||||
version = 9
|
|
||||||
|
|
||||||
|
|
||||||
class WlTouchProxy(Proxy[WlTouch]):
|
|
||||||
interface = WlTouch
|
|
||||||
|
|
||||||
@WlTouch.request(version=3)
|
|
||||||
def release(self) -> None:
|
|
||||||
"""Release the touch object
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class WlTouchResource(Resource):
|
|
||||||
interface = WlTouch
|
|
||||||
|
|
||||||
@WlTouch.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSurface),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def down(self, serial: int, time: int, surface: WlSurface, id: int, x: float, y: float) -> None:
|
|
||||||
"""Touch down event and beginning of a touch sequence
|
|
||||||
|
|
||||||
A new touch point has appeared on the surface. This touch point is
|
|
||||||
assigned a unique ID. Future events from this touch point reference
|
|
||||||
this ID. The ID ceases to be valid after a touch up event and may be
|
|
||||||
reused in the future.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the touch down event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param surface:
|
|
||||||
surface touched
|
|
||||||
:type surface:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSurface`
|
|
||||||
:param id:
|
|
||||||
the unique ID of this touch point
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(0, serial, time, surface, id, x, y)
|
|
||||||
|
|
||||||
@WlTouch.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
)
|
|
||||||
def up(self, serial: int, time: int, id: int) -> None:
|
|
||||||
"""End of a touch event sequence
|
|
||||||
|
|
||||||
The touch point has disappeared. No further events will be sent for
|
|
||||||
this touch point and the touch point's ID is released and may be reused
|
|
||||||
in a future touch down event.
|
|
||||||
|
|
||||||
:param serial:
|
|
||||||
serial number of the touch up event
|
|
||||||
:type serial:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param id:
|
|
||||||
the unique ID of this touch point
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
"""
|
|
||||||
self._post_event(1, serial, time, id)
|
|
||||||
|
|
||||||
@WlTouch.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
)
|
|
||||||
def motion(self, time: int, id: int, x: float, y: float) -> None:
|
|
||||||
"""Update of touch point coordinates
|
|
||||||
|
|
||||||
A touch point has changed coordinates.
|
|
||||||
|
|
||||||
:param time:
|
|
||||||
timestamp with millisecond granularity
|
|
||||||
:type time:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
:param id:
|
|
||||||
the unique ID of this touch point
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param x:
|
|
||||||
surface-local x coordinate
|
|
||||||
:type x:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param y:
|
|
||||||
surface-local y coordinate
|
|
||||||
:type y:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(2, time, id, x, y)
|
|
||||||
|
|
||||||
@WlTouch.event()
|
|
||||||
def frame(self) -> None:
|
|
||||||
"""End of touch frame event
|
|
||||||
|
|
||||||
Indicates the end of a set of events that logically belong together. A
|
|
||||||
client is expected to accumulate the data in all events within the
|
|
||||||
frame before proceeding.
|
|
||||||
|
|
||||||
A :func:`WlTouch.frame()` terminates at least one event but otherwise
|
|
||||||
no guarantee is provided about the set of events within a frame. A
|
|
||||||
client must assume that any state not updated in a frame is unchanged
|
|
||||||
from the previously known state.
|
|
||||||
"""
|
|
||||||
self._post_event(3)
|
|
||||||
|
|
||||||
@WlTouch.event()
|
|
||||||
def cancel(self) -> None:
|
|
||||||
"""Touch session cancelled
|
|
||||||
|
|
||||||
Sent if the compositor decides the touch stream is a global gesture. No
|
|
||||||
further events are sent to the clients from that particular gesture.
|
|
||||||
Touch cancellation applies to all touch points currently active on this
|
|
||||||
client's surface. The client is responsible for finalizing the touch
|
|
||||||
points, future touch points on this surface may reuse the touch point
|
|
||||||
ID.
|
|
||||||
"""
|
|
||||||
self._post_event(4)
|
|
||||||
|
|
||||||
@WlTouch.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
version=6,
|
|
||||||
)
|
|
||||||
def shape(self, id: int, major: float, minor: float) -> None:
|
|
||||||
"""Update shape of touch point
|
|
||||||
|
|
||||||
Sent when a touchpoint has changed its shape.
|
|
||||||
|
|
||||||
This event does not occur on its own. It is sent before a
|
|
||||||
:func:`WlTouch.frame()` event and carries the new shape information for
|
|
||||||
any previously reported, or new touch points of that frame.
|
|
||||||
|
|
||||||
Other events describing the touch point such as :func:`WlTouch.down()`,
|
|
||||||
:func:`WlTouch.motion()` or :func:`WlTouch.orientation()` may be sent
|
|
||||||
within the same :func:`WlTouch.frame()`. A client should treat these
|
|
||||||
events as a single logical touch point update. The order of
|
|
||||||
:func:`WlTouch.shape()`, :func:`WlTouch.orientation()` and
|
|
||||||
:func:`WlTouch.motion()` is not guaranteed. A :func:`WlTouch.down()`
|
|
||||||
event is guaranteed to occur before the first :func:`WlTouch.shape()`
|
|
||||||
event for this touch ID but both events may occur within the same
|
|
||||||
:func:`WlTouch.frame()`.
|
|
||||||
|
|
||||||
A touchpoint shape is approximated by an ellipse through the major and
|
|
||||||
minor axis length. The major axis length describes the longer diameter
|
|
||||||
of the ellipse, while the minor axis length describes the shorter
|
|
||||||
diameter. Major and minor are orthogonal and both are specified in
|
|
||||||
surface-local coordinates. The center of the ellipse is always at the
|
|
||||||
touchpoint location as reported by :func:`WlTouch.down()` or
|
|
||||||
:func:`WlTouch.move()`.
|
|
||||||
|
|
||||||
This event is only sent by the compositor if the touch device supports
|
|
||||||
shape reports. The client has to make reasonable assumptions about the
|
|
||||||
shape if it did not receive this event.
|
|
||||||
|
|
||||||
:param id:
|
|
||||||
the unique ID of this touch point
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param major:
|
|
||||||
length of the major axis in surface-local coordinates
|
|
||||||
:type major:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
:param minor:
|
|
||||||
length of the minor axis in surface-local coordinates
|
|
||||||
:type minor:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(5, id, major, minor)
|
|
||||||
|
|
||||||
@WlTouch.event(
|
|
||||||
Argument(ArgumentType.Int),
|
|
||||||
Argument(ArgumentType.Fixed),
|
|
||||||
version=6,
|
|
||||||
)
|
|
||||||
def orientation(self, id: int, orientation: float) -> None:
|
|
||||||
"""Update orientation of touch point
|
|
||||||
|
|
||||||
Sent when a touchpoint has changed its orientation.
|
|
||||||
|
|
||||||
This event does not occur on its own. It is sent before a
|
|
||||||
:func:`WlTouch.frame()` event and carries the new shape information for
|
|
||||||
any previously reported, or new touch points of that frame.
|
|
||||||
|
|
||||||
Other events describing the touch point such as :func:`WlTouch.down()`,
|
|
||||||
:func:`WlTouch.motion()` or :func:`WlTouch.shape()` may be sent within
|
|
||||||
the same :func:`WlTouch.frame()`. A client should treat these events as
|
|
||||||
a single logical touch point update. The order of
|
|
||||||
:func:`WlTouch.shape()`, :func:`WlTouch.orientation()` and
|
|
||||||
:func:`WlTouch.motion()` is not guaranteed. A :func:`WlTouch.down()`
|
|
||||||
event is guaranteed to occur before the first
|
|
||||||
:func:`WlTouch.orientation()` event for this touch ID but both events
|
|
||||||
may occur within the same :func:`WlTouch.frame()`.
|
|
||||||
|
|
||||||
The orientation describes the clockwise angle of a touchpoint's major
|
|
||||||
axis to the positive surface y-axis and is normalized to the -180 to
|
|
||||||
+180 degree range. The granularity of orientation depends on the touch
|
|
||||||
device, some devices only support binary rotation values between 0 and
|
|
||||||
90 degrees.
|
|
||||||
|
|
||||||
This event is only sent by the compositor if the touch device supports
|
|
||||||
orientation reports.
|
|
||||||
|
|
||||||
:param id:
|
|
||||||
the unique ID of this touch point
|
|
||||||
:type id:
|
|
||||||
`ArgumentType.Int`
|
|
||||||
:param orientation:
|
|
||||||
angle between major axis and positive surface y-axis in degrees
|
|
||||||
:type orientation:
|
|
||||||
`ArgumentType.Fixed`
|
|
||||||
"""
|
|
||||||
self._post_event(6, id, orientation)
|
|
||||||
|
|
||||||
|
|
||||||
class WlTouchGlobal(Global):
|
|
||||||
interface = WlTouch
|
|
||||||
|
|
||||||
|
|
||||||
WlTouch._gen_c()
|
|
||||||
WlTouch.proxy_class = WlTouchProxy
|
|
||||||
WlTouch.resource_class = WlTouchResource
|
|
||||||
WlTouch.global_class = WlTouchGlobal
|
|
||||||
61
bar/main.py
Normal file
61
bar/main.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from fabric import Application
|
||||||
|
from fabric.system_tray.widgets import SystemTray
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
|
||||||
|
|
||||||
|
from fabric.utils import (
|
||||||
|
get_relative_path,
|
||||||
|
)
|
||||||
|
from .modules.bar import StatusBar
|
||||||
|
from .modules.window_fuzzy import FuzzyWindowFinder
|
||||||
|
from .services.river.widgets import get_river_connection
|
||||||
|
from .services.wlr.event_loop import WaylandEventLoopService
|
||||||
|
|
||||||
|
tray = SystemTray(name="system-tray", spacing=4)
|
||||||
|
wayland_event_loop = WaylandEventLoopService()
|
||||||
|
river = get_river_connection()
|
||||||
|
|
||||||
|
dummy = Window(visible=False)
|
||||||
|
finder = FuzzyWindowFinder()
|
||||||
|
|
||||||
|
bar_windows = []
|
||||||
|
|
||||||
|
app = Application("bar", dummy, finder)
|
||||||
|
app.set_stylesheet_from_file(get_relative_path("styles/main.css"))
|
||||||
|
|
||||||
|
|
||||||
|
def spawn_bars():
|
||||||
|
logger.info("[Bar] Spawning bars after river ready")
|
||||||
|
outputs = river.outputs
|
||||||
|
|
||||||
|
if not outputs:
|
||||||
|
logger.warning("[Bar] No outputs found — skipping bar spawn")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_ids = sorted(outputs.keys())
|
||||||
|
|
||||||
|
for i, output_id in enumerate(output_ids):
|
||||||
|
bar = StatusBar(
|
||||||
|
display=output_id,
|
||||||
|
tray=tray if i == 0 else None,
|
||||||
|
monitor=i,
|
||||||
|
river_service=river,
|
||||||
|
)
|
||||||
|
bar_windows.append(bar)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if river.ready:
|
||||||
|
spawn_bars()
|
||||||
|
else:
|
||||||
|
river.connect("notify::ready", lambda sender, pspec: spawn_bars())
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
133
bar/modules/bar.py
Normal file
133
bar/modules/bar.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import psutil
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.overlay import Overlay
|
||||||
|
from fabric.widgets.datetime import DateTime
|
||||||
|
from fabric.widgets.centerbox import CenterBox
|
||||||
|
from bar.modules.player import Player
|
||||||
|
from bar.modules.vinyl import VinylButton
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.system_tray.widgets import SystemTray
|
||||||
|
|
||||||
|
from fabric.utils import (
|
||||||
|
invoke_repeater,
|
||||||
|
)
|
||||||
|
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||||
|
|
||||||
|
from bar.config import VINYL
|
||||||
|
from bar.services.river.widgets import (
|
||||||
|
RiverWorkspaces,
|
||||||
|
RiverWorkspaceButton,
|
||||||
|
RiverActiveWindow,
|
||||||
|
get_river_connection,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StatusBar(Window):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
display: int,
|
||||||
|
tray: SystemTray | None = None,
|
||||||
|
monitor: int = 1,
|
||||||
|
river_service=None,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name="bar",
|
||||||
|
layer="top",
|
||||||
|
anchor="left top right",
|
||||||
|
margin="0px 0px -2px 0px",
|
||||||
|
exclusivity="auto",
|
||||||
|
visible=False,
|
||||||
|
all_visible=False,
|
||||||
|
monitor=monitor,
|
||||||
|
)
|
||||||
|
if river_service:
|
||||||
|
self.river = river_service
|
||||||
|
|
||||||
|
self.workspaces = RiverWorkspaces(
|
||||||
|
display,
|
||||||
|
name="workspaces",
|
||||||
|
spacing=4,
|
||||||
|
buttons_factory=lambda ws_id: RiverWorkspaceButton(id=ws_id, label=None),
|
||||||
|
river_service=self.river,
|
||||||
|
)
|
||||||
|
self.date_time = DateTime(name="date-time", formatters="%d %b - %H:%M")
|
||||||
|
self.system_tray = tray
|
||||||
|
|
||||||
|
self.active_window = RiverActiveWindow(
|
||||||
|
river_service=self.river,
|
||||||
|
name="active-window",
|
||||||
|
max_length=50,
|
||||||
|
style="color: #ffffff; font-size: 14px; font-weight: bold;",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ram_progress_bar = CircularProgressBar(
|
||||||
|
name="ram-progress-bar", pie=True, size=24
|
||||||
|
)
|
||||||
|
self.cpu_progress_bar = CircularProgressBar(
|
||||||
|
name="cpu-progress-bar", pie=True, size=24
|
||||||
|
)
|
||||||
|
|
||||||
|
self.progress_label = Label(
|
||||||
|
"", style="margin: 0px 6px 0px 0px; font-size: 12px"
|
||||||
|
)
|
||||||
|
self.progress_bars_overlay = Overlay(
|
||||||
|
child=self.ram_progress_bar,
|
||||||
|
overlays=[self.cpu_progress_bar, self.progress_label],
|
||||||
|
)
|
||||||
|
self.player = Player()
|
||||||
|
self.vinyl = None
|
||||||
|
if VINYL["enable"]:
|
||||||
|
self.vinyl = VinylButton()
|
||||||
|
|
||||||
|
self.status_container = Box(
|
||||||
|
name="widgets-container",
|
||||||
|
spacing=4,
|
||||||
|
orientation="h",
|
||||||
|
children=self.progress_bars_overlay,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_container_children = []
|
||||||
|
|
||||||
|
if self.vinyl:
|
||||||
|
end_container_children.append(self.vinyl)
|
||||||
|
|
||||||
|
end_container_children.append(self.status_container)
|
||||||
|
if self.system_tray:
|
||||||
|
end_container_children.append(self.system_tray)
|
||||||
|
|
||||||
|
end_container_children.append(self.date_time)
|
||||||
|
|
||||||
|
self.children = CenterBox(
|
||||||
|
name="bar-inner",
|
||||||
|
start_children=Box(
|
||||||
|
name="start-container",
|
||||||
|
spacing=6,
|
||||||
|
orientation="h",
|
||||||
|
children=[
|
||||||
|
Label(name="nixos-label", markup=""),
|
||||||
|
self.workspaces,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
center_children=Box(
|
||||||
|
name="center-container",
|
||||||
|
spacing=4,
|
||||||
|
orientation="h",
|
||||||
|
children=[self.active_window],
|
||||||
|
),
|
||||||
|
end_children=Box(
|
||||||
|
name="end-container",
|
||||||
|
spacing=4,
|
||||||
|
orientation="h",
|
||||||
|
children=end_container_children,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
invoke_repeater(1000, self.update_progress_bars)
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def update_progress_bars(self):
|
||||||
|
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
||||||
|
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
||||||
|
return True
|
||||||
274
bar/modules/cavalcade.py
Normal file
274
bar/modules/cavalcade.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
import ctypes
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from gi.repository import GLib, Gtk, Gdk
|
||||||
|
from loguru import logger
|
||||||
|
from math import pi
|
||||||
|
|
||||||
|
from fabric.widgets.overlay import Overlay
|
||||||
|
from fabric.utils.helpers import get_relative_path
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
def get_bars(file_path):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(file_path)
|
||||||
|
return int(config["general"]["bars"])
|
||||||
|
|
||||||
|
|
||||||
|
CAVA_CONFIG = get_relative_path("../config/cavalcade/cava.ini")
|
||||||
|
|
||||||
|
bars = get_bars(CAVA_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
def set_death_signal():
|
||||||
|
"""
|
||||||
|
Set the death signal of the child process to SIGTERM so that if the parent
|
||||||
|
process is killed, the child (cava) is automatically terminated.
|
||||||
|
"""
|
||||||
|
libc = ctypes.CDLL("libc.so.6")
|
||||||
|
PR_SET_PDEATHSIG = 1
|
||||||
|
libc.prctl(PR_SET_PDEATHSIG, signal.SIGTERM)
|
||||||
|
|
||||||
|
|
||||||
|
class Cava:
|
||||||
|
"""
|
||||||
|
CAVA wrapper.
|
||||||
|
Launch cava process with certain settings and read output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NONE = 0
|
||||||
|
RUNNING = 1
|
||||||
|
RESTARTING = 2
|
||||||
|
CLOSING = 3
|
||||||
|
|
||||||
|
def __init__(self, mainapp):
|
||||||
|
self.bars = bars
|
||||||
|
self.path = "/tmp/cava.fifo"
|
||||||
|
|
||||||
|
self.cava_config_file = CAVA_CONFIG
|
||||||
|
self.data_handler = mainapp.draw.update
|
||||||
|
self.command = ["cava", "-p", self.cava_config_file]
|
||||||
|
self.state = self.NONE
|
||||||
|
|
||||||
|
self.env = dict(os.environ)
|
||||||
|
self.env["LC_ALL"] = "en_US.UTF-8" # not sure if it's necessary
|
||||||
|
|
||||||
|
is_16bit = True
|
||||||
|
self.byte_type, self.byte_size, self.byte_norm = (
|
||||||
|
("H", 2, 65535) if is_16bit else ("B", 1, 255)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(self.path):
|
||||||
|
os.mkfifo(self.path)
|
||||||
|
|
||||||
|
self.fifo_fd = None
|
||||||
|
self.fifo_dummy_fd = None
|
||||||
|
self.io_watch_id = None
|
||||||
|
|
||||||
|
def _run_process(self):
|
||||||
|
logger.debug("Launching cava process...")
|
||||||
|
try:
|
||||||
|
self.process = subprocess.Popen(
|
||||||
|
self.command,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
env=self.env,
|
||||||
|
preexec_fn=set_death_signal, # Ensure cava gets killed when the parent dies.
|
||||||
|
)
|
||||||
|
logger.debug("cava successfully launched!")
|
||||||
|
self.state = self.RUNNING
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Fail to launch cava")
|
||||||
|
|
||||||
|
def _start_io_reader(self):
|
||||||
|
logger.debug("Activating GLib IO watch for cava stream handler")
|
||||||
|
# Open FIFO in non-blocking mode for reading
|
||||||
|
self.fifo_fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK)
|
||||||
|
# Open dummy write end to prevent getting an EOF on our FIFO
|
||||||
|
self.fifo_dummy_fd = os.open(self.path, os.O_WRONLY | os.O_NONBLOCK)
|
||||||
|
self.io_watch_id = GLib.io_add_watch(
|
||||||
|
self.fifo_fd, GLib.IO_IN, self._io_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
def _io_callback(self, source, condition):
|
||||||
|
chunk = self.byte_size * self.bars # number of bytes for given format
|
||||||
|
try:
|
||||||
|
data = os.read(self.fifo_fd, chunk)
|
||||||
|
except OSError as e:
|
||||||
|
# logger.error("Error reading FIFO: {}".format(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# When no data is read, do not remove the IO watch immediately.
|
||||||
|
if len(data) < chunk:
|
||||||
|
# Instead of closing the FIFO, we log a warning and continue.
|
||||||
|
# logger.warning("Incomplete data packet received (expected {} bytes, got {}). Waiting for more data...".format(chunk, len(data)))
|
||||||
|
# Returning True keeps the IO watch active. A real EOF will only occur when the writer closes.
|
||||||
|
return True
|
||||||
|
|
||||||
|
fmt = self.byte_type * self.bars # format string for struct.unpack
|
||||||
|
sample = [i / self.byte_norm for i in struct.unpack(fmt, data)]
|
||||||
|
GLib.idle_add(self.data_handler, sample)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _on_stop(self):
|
||||||
|
logger.debug("Cava stream handler deactivated")
|
||||||
|
if self.state == self.RESTARTING:
|
||||||
|
self.start()
|
||||||
|
elif self.state == self.RUNNING:
|
||||||
|
self.state = self.NONE
|
||||||
|
logger.error("Cava process was unexpectedly terminated.")
|
||||||
|
# self.restart() # May cause infinity loop, need more check
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Launch cava"""
|
||||||
|
self._start_io_reader()
|
||||||
|
self._run_process()
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""Restart cava process"""
|
||||||
|
if self.state == self.RUNNING:
|
||||||
|
logger.debug("Restarting cava process (normal mode) ...")
|
||||||
|
self.state = self.RESTARTING
|
||||||
|
if self.process.poll() is None:
|
||||||
|
self.process.kill()
|
||||||
|
elif self.state == self.NONE:
|
||||||
|
logger.warning("Restarting cava process (after crash) ...")
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Stop cava process"""
|
||||||
|
self.state = self.CLOSING
|
||||||
|
if self.process.poll() is None:
|
||||||
|
self.process.kill()
|
||||||
|
if self.io_watch_id:
|
||||||
|
GLib.source_remove(self.io_watch_id)
|
||||||
|
if self.fifo_fd:
|
||||||
|
os.close(self.fifo_fd)
|
||||||
|
if self.fifo_dummy_fd:
|
||||||
|
os.close(self.fifo_dummy_fd)
|
||||||
|
if os.path.exists(self.path):
|
||||||
|
os.remove(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeDict(dict):
|
||||||
|
"""Dictionary with keys as attributes. Does nothing but easy reading"""
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return self.get(attr, 3)
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
self[attr] = value
|
||||||
|
|
||||||
|
|
||||||
|
class Spectrum:
|
||||||
|
"""Spectrum drawing"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.silence_value = 0
|
||||||
|
self.audio_sample = []
|
||||||
|
self.color = None
|
||||||
|
|
||||||
|
self.area = Gtk.DrawingArea()
|
||||||
|
self.area.connect("draw", self.redraw)
|
||||||
|
self.area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||||
|
|
||||||
|
self.sizes = AttributeDict()
|
||||||
|
self.sizes.area = AttributeDict()
|
||||||
|
self.sizes.bar = AttributeDict()
|
||||||
|
|
||||||
|
self.silence = 10
|
||||||
|
self.max_height = 12
|
||||||
|
|
||||||
|
self.area.connect("configure-event", self.size_update)
|
||||||
|
self.color_update()
|
||||||
|
|
||||||
|
def is_silence(self, value):
|
||||||
|
"""Check if volume level critically low during last iterations"""
|
||||||
|
self.silence_value = 0 if value > 0 else self.silence_value + 1
|
||||||
|
return self.silence_value > self.silence
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
"""Audio data processing"""
|
||||||
|
self.color_update()
|
||||||
|
self.audio_sample = data
|
||||||
|
if not self.is_silence(self.audio_sample[0]):
|
||||||
|
self.area.queue_draw()
|
||||||
|
elif self.silence_value == (self.silence + 1):
|
||||||
|
self.audio_sample = [0] * self.sizes.number
|
||||||
|
self.area.queue_draw()
|
||||||
|
|
||||||
|
def redraw(self, widget, cr):
|
||||||
|
"""Draw spectrum graph"""
|
||||||
|
cr.set_source_rgba(*self.color)
|
||||||
|
dx = 3
|
||||||
|
|
||||||
|
center_y = self.sizes.area.height / 2 # center vertical of the drawing area
|
||||||
|
for i, value in enumerate(self.audio_sample):
|
||||||
|
width = self.sizes.area.width / self.sizes.number - self.sizes.padding
|
||||||
|
radius = width / 2
|
||||||
|
height = max(self.sizes.bar.height * min(value, 1), self.sizes.zero) / 2
|
||||||
|
if height == self.sizes.zero / 2 + 1:
|
||||||
|
height *= 0.5
|
||||||
|
|
||||||
|
height = min(height, self.max_height)
|
||||||
|
|
||||||
|
# Draw rectangle and arcs for rounded ends
|
||||||
|
cr.rectangle(dx, center_y - height, width, height * 2)
|
||||||
|
cr.arc(dx + radius, center_y - height, radius, 0, 2 * pi)
|
||||||
|
cr.arc(dx + radius, center_y + height, radius, 0, 2 * pi)
|
||||||
|
|
||||||
|
cr.close_path()
|
||||||
|
dx += width + self.sizes.padding
|
||||||
|
cr.fill()
|
||||||
|
|
||||||
|
def size_update(self, *args):
|
||||||
|
"""Update drawing geometry"""
|
||||||
|
self.sizes.number = bars
|
||||||
|
self.sizes.padding = 100 / bars
|
||||||
|
self.sizes.zero = 0
|
||||||
|
|
||||||
|
self.sizes.area.width = self.area.get_allocated_width()
|
||||||
|
self.sizes.area.height = self.area.get_allocated_height() - 2
|
||||||
|
|
||||||
|
tw = self.sizes.area.width - self.sizes.padding * (self.sizes.number - 1)
|
||||||
|
self.sizes.bar.width = max(int(tw / self.sizes.number), 1)
|
||||||
|
self.sizes.bar.height = self.sizes.area.height
|
||||||
|
|
||||||
|
def color_update(self):
|
||||||
|
"""Set drawing color according to current settings by reading primary color from CSS"""
|
||||||
|
color = "#a5c8ff" # default value
|
||||||
|
try:
|
||||||
|
with open(get_relative_path("../styles/colors.css"), "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
m = re.search(r"--primary:\s*(#[0-9a-fA-F]{6})", content)
|
||||||
|
if m:
|
||||||
|
color = m.group(1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to read primary color: {}".format(e))
|
||||||
|
red = int(color[1:3], 16) / 255
|
||||||
|
green = int(color[3:5], 16) / 255
|
||||||
|
blue = int(color[5:7], 16) / 255
|
||||||
|
self.color = Gdk.RGBA(red=red, green=green, blue=blue, alpha=1.0)
|
||||||
|
|
||||||
|
|
||||||
|
class SpectrumRender:
|
||||||
|
def __init__(self, mode=None, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
|
self.draw = Spectrum()
|
||||||
|
self.cava = Cava(self)
|
||||||
|
self.cava.start()
|
||||||
|
|
||||||
|
def get_spectrum_box(self):
|
||||||
|
# Get the spectrum box
|
||||||
|
box = Overlay(name="cavalcade", h_align="center", v_align="center")
|
||||||
|
box.set_size_request(180, 40)
|
||||||
|
box.add_overlay(self.draw.area)
|
||||||
|
return box
|
||||||
176
bar/modules/icons.py
Normal file
176
bar/modules/icons.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Parameters
|
||||||
|
font_family: str = "tabler-icons"
|
||||||
|
font_weight: str = "normal"
|
||||||
|
|
||||||
|
span: str = f"<span font-family='{font_family}' font-weight='{font_weight}'>"
|
||||||
|
|
||||||
|
# Panels
|
||||||
|
apps: str = ""
|
||||||
|
dashboard: str = ""
|
||||||
|
chat: str = ""
|
||||||
|
wallpapers: str = ""
|
||||||
|
windows: str = ""
|
||||||
|
|
||||||
|
# Bar
|
||||||
|
colorpicker: str = ""
|
||||||
|
media: str = ""
|
||||||
|
|
||||||
|
# Toolbox
|
||||||
|
|
||||||
|
toolbox: str = ""
|
||||||
|
ssfull: str = ""
|
||||||
|
ssregion: str = ""
|
||||||
|
sswindow: str = ""
|
||||||
|
screenshots: str = ""
|
||||||
|
screenrecord: str = ""
|
||||||
|
recordings: str = ""
|
||||||
|
ocr: str = "ﳃ"
|
||||||
|
gamemode: str = ""
|
||||||
|
gamemode_off: str = ""
|
||||||
|
close: str = ""
|
||||||
|
|
||||||
|
# Circles
|
||||||
|
temp: str = ""
|
||||||
|
disk: str = ""
|
||||||
|
battery: str = ""
|
||||||
|
memory: str = "流"
|
||||||
|
cpu: str = ""
|
||||||
|
gpu: str = ""
|
||||||
|
|
||||||
|
# AIchat
|
||||||
|
reload: str = ""
|
||||||
|
detach: str = ""
|
||||||
|
|
||||||
|
# Wallpapers
|
||||||
|
add: str = ""
|
||||||
|
sort: str = ""
|
||||||
|
circle: str = ""
|
||||||
|
|
||||||
|
# Chevrons
|
||||||
|
chevron_up: str = ""
|
||||||
|
chevron_down: str = ""
|
||||||
|
chevron_left: str = ""
|
||||||
|
chevron_right: str = ""
|
||||||
|
|
||||||
|
# Power
|
||||||
|
lock: str = ""
|
||||||
|
suspend: str = ""
|
||||||
|
logout: str = ""
|
||||||
|
reboot: str = ""
|
||||||
|
shutdown: str = ""
|
||||||
|
|
||||||
|
# Power Manager
|
||||||
|
power_saving: str = ""
|
||||||
|
power_balanced: str = "勺"
|
||||||
|
power_performance: str = ""
|
||||||
|
charging: str = ""
|
||||||
|
discharging: str = ""
|
||||||
|
alert: str = ""
|
||||||
|
bat_charging: str = ""
|
||||||
|
bat_discharging: str = ""
|
||||||
|
bat_low: str = "="
|
||||||
|
bat_full: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
# Applets
|
||||||
|
wifi_0: str = ""
|
||||||
|
wifi_1: str = ""
|
||||||
|
wifi_2: str = ""
|
||||||
|
wifi_3: str = ""
|
||||||
|
world: str = ""
|
||||||
|
world_off: str = ""
|
||||||
|
bluetooth: str = ""
|
||||||
|
night: str = ""
|
||||||
|
coffee: str = ""
|
||||||
|
notifications: str = ""
|
||||||
|
|
||||||
|
wifi_off: str = ""
|
||||||
|
bluetooth_off: str = ""
|
||||||
|
night_off: str = ""
|
||||||
|
notifications_off: str = ""
|
||||||
|
|
||||||
|
notifications_clear: str = ""
|
||||||
|
download: str = ""
|
||||||
|
upload: str = ""
|
||||||
|
|
||||||
|
# Bluetooth
|
||||||
|
bluetooth_connected: str = ""
|
||||||
|
bluetooth_disconnected: str = ""
|
||||||
|
|
||||||
|
# Player
|
||||||
|
pause: str = ""
|
||||||
|
play: str = ""
|
||||||
|
stop: str = ""
|
||||||
|
skip_back: str = ""
|
||||||
|
skip_forward: str = ""
|
||||||
|
prev: str = ""
|
||||||
|
next: str = ""
|
||||||
|
shuffle: str = ""
|
||||||
|
repeat: str = ""
|
||||||
|
music: str = ""
|
||||||
|
rewind_backward_5: str = "謹"
|
||||||
|
rewind_forward_5: str = "難"
|
||||||
|
|
||||||
|
# Volume
|
||||||
|
vol_off: str = ""
|
||||||
|
vol_mute: str = ""
|
||||||
|
vol_medium: str = ""
|
||||||
|
vol_high: str = ""
|
||||||
|
|
||||||
|
mic: str = ""
|
||||||
|
mic_mute: str = ""
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
circle_plus: str = ""
|
||||||
|
|
||||||
|
# Pins
|
||||||
|
paperclip: str = ""
|
||||||
|
|
||||||
|
# Clipboard Manager
|
||||||
|
clipboard: str = ""
|
||||||
|
clip_text: str = ""
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
accept: str = ""
|
||||||
|
cancel: str = ""
|
||||||
|
trash: str = ""
|
||||||
|
|
||||||
|
# Config
|
||||||
|
config: str = ""
|
||||||
|
|
||||||
|
# Icons
|
||||||
|
firefox: str = ""
|
||||||
|
chromium: str = ""
|
||||||
|
spotify: str = "ﺆ"
|
||||||
|
disc: str = "𐀾"
|
||||||
|
disc_off: str = ""
|
||||||
|
|
||||||
|
# Brightness
|
||||||
|
brightness_low: str = ""
|
||||||
|
brightness_medium: str = ""
|
||||||
|
brightness_high: str = ""
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
dot: str = ""
|
||||||
|
palette: str = ""
|
||||||
|
cloud_off: str = ""
|
||||||
|
loader: str = ""
|
||||||
|
radar: str = ""
|
||||||
|
emoji: str = ""
|
||||||
|
keyboard: str = ""
|
||||||
|
terminal: str = ""
|
||||||
|
timer_off: str = ""
|
||||||
|
timer_on: str = ""
|
||||||
|
spy: str = ""
|
||||||
|
|
||||||
|
exceptions: list[str] = ["font_family", "font_weight", "span"]
|
||||||
|
|
||||||
|
|
||||||
|
def apply_span() -> None:
|
||||||
|
global_dict = globals()
|
||||||
|
for key in global_dict:
|
||||||
|
if key not in exceptions and not key.startswith("__"):
|
||||||
|
global_dict[key] = f"{span}{global_dict[key]}</span>"
|
||||||
|
|
||||||
|
|
||||||
|
apply_span()
|
||||||
738
bar/modules/player.py
Normal file
738
bar/modules/player.py
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
import os
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
import tempfile
|
||||||
|
from gi.repository import Gtk, GLib, Gio, Gdk
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.centerbox import CenterBox
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.button import Button
|
||||||
|
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||||
|
from fabric.widgets.overlay import Overlay
|
||||||
|
from fabric.widgets.stack import Stack
|
||||||
|
from ..widgets.circle_image import CircleImage
|
||||||
|
import bar.modules.icons as icons
|
||||||
|
from bar.services.mpris import MprisPlayerManager, MprisPlayer
|
||||||
|
|
||||||
|
# from bar.modules.cavalcade import SpectrumRender
|
||||||
|
|
||||||
|
|
||||||
|
def get_player_icon_markup_by_name(player_name):
|
||||||
|
if player_name:
|
||||||
|
pn = player_name.lower()
|
||||||
|
if pn == "firefox":
|
||||||
|
return icons.firefox
|
||||||
|
elif pn == "spotify":
|
||||||
|
return icons.spotify
|
||||||
|
elif pn in ("chromium", "brave"):
|
||||||
|
return icons.chromium
|
||||||
|
return icons.disc
|
||||||
|
|
||||||
|
|
||||||
|
def add_hover_cursor(widget):
|
||||||
|
widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
||||||
|
widget.connect(
|
||||||
|
"enter-notify-event",
|
||||||
|
lambda w, event: w.get_window().set_cursor(
|
||||||
|
Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "pointer")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
widget.connect(
|
||||||
|
"leave-notify-event", lambda w, event: w.get_window().set_cursor(None)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerBox(Box):
|
||||||
|
def __init__(self, mpris_player=None):
|
||||||
|
super().__init__(
|
||||||
|
orientation="v", h_align="fill", spacing=0, h_expand=False, v_expand=True
|
||||||
|
)
|
||||||
|
self.mpris_player = mpris_player
|
||||||
|
self._progress_timer_id = None # Initialize timer ID
|
||||||
|
|
||||||
|
self.cover = CircleImage(
|
||||||
|
name="player-cover",
|
||||||
|
image_file=os.path.expanduser("~/Pictures/wallpaper/background.jpg"),
|
||||||
|
size=162,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
self.cover_placerholder = CircleImage(
|
||||||
|
name="player-cover",
|
||||||
|
size=198,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
self.title = Label(
|
||||||
|
name="player-title",
|
||||||
|
h_expand=True,
|
||||||
|
h_align="fill",
|
||||||
|
ellipsization="end",
|
||||||
|
max_chars_width=1,
|
||||||
|
)
|
||||||
|
self.album = Label(
|
||||||
|
name="player-album",
|
||||||
|
h_expand=True,
|
||||||
|
h_align="fill",
|
||||||
|
ellipsization="end",
|
||||||
|
max_chars_width=1,
|
||||||
|
)
|
||||||
|
self.artist = Label(
|
||||||
|
name="player-artist",
|
||||||
|
h_expand=True,
|
||||||
|
h_align="fill",
|
||||||
|
ellipsization="end",
|
||||||
|
max_chars_width=1,
|
||||||
|
)
|
||||||
|
self.progressbar = CircularProgressBar(
|
||||||
|
name="player-progress",
|
||||||
|
size=198,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
start_angle=180,
|
||||||
|
end_angle=360,
|
||||||
|
)
|
||||||
|
self.time = Label(name="player-time", label="--:-- / --:--")
|
||||||
|
self.overlay = Overlay(
|
||||||
|
child=self.cover_placerholder,
|
||||||
|
overlays=[self.progressbar, self.cover],
|
||||||
|
)
|
||||||
|
self.overlay_container = CenterBox(
|
||||||
|
name="player-overlay", center_children=[self.overlay]
|
||||||
|
)
|
||||||
|
self.title.set_label("Nothing Playing")
|
||||||
|
self.album.set_label("Enjoy the silence")
|
||||||
|
self.artist.set_label("¯\\_(ツ)_/¯")
|
||||||
|
self.progressbar.set_value(0.0)
|
||||||
|
self.prev = Button(
|
||||||
|
name="player-btn",
|
||||||
|
child=Label(name="player-btn-label", markup=icons.prev),
|
||||||
|
)
|
||||||
|
self.backward = Button(
|
||||||
|
name="player-btn",
|
||||||
|
child=Label(name="player-btn-label", markup=icons.skip_back),
|
||||||
|
)
|
||||||
|
self.play_pause = Button(
|
||||||
|
name="player-btn",
|
||||||
|
child=Label(name="player-btn-label", markup=icons.play),
|
||||||
|
)
|
||||||
|
self.forward = Button(
|
||||||
|
name="player-btn",
|
||||||
|
child=Label(name="player-btn-label", markup=icons.skip_forward),
|
||||||
|
)
|
||||||
|
self.next = Button(
|
||||||
|
name="player-btn",
|
||||||
|
child=Label(name="player-btn-label", markup=icons.next),
|
||||||
|
)
|
||||||
|
# Add hover effect to buttons
|
||||||
|
add_hover_cursor(self.prev)
|
||||||
|
add_hover_cursor(self.backward)
|
||||||
|
add_hover_cursor(self.play_pause)
|
||||||
|
add_hover_cursor(self.forward)
|
||||||
|
add_hover_cursor(self.next)
|
||||||
|
self.btn_box = CenterBox(
|
||||||
|
name="player-btn-box",
|
||||||
|
orientation="h",
|
||||||
|
center_children=[
|
||||||
|
Box(
|
||||||
|
orientation="h",
|
||||||
|
spacing=8,
|
||||||
|
h_expand=True,
|
||||||
|
h_align="fill",
|
||||||
|
children=[
|
||||||
|
self.prev,
|
||||||
|
self.backward,
|
||||||
|
self.play_pause,
|
||||||
|
self.forward,
|
||||||
|
self.next,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.player_box = Box(
|
||||||
|
name="player-box",
|
||||||
|
orientation="v",
|
||||||
|
spacing=8,
|
||||||
|
children=[
|
||||||
|
self.overlay_container,
|
||||||
|
self.title,
|
||||||
|
self.album,
|
||||||
|
self.artist,
|
||||||
|
self.btn_box,
|
||||||
|
self.time,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.add(self.player_box)
|
||||||
|
if mpris_player:
|
||||||
|
self._apply_mpris_properties() # This will handle starting the timer if needed
|
||||||
|
self.prev.connect("clicked", self._on_prev_clicked)
|
||||||
|
self.play_pause.connect("clicked", self._on_play_pause_clicked)
|
||||||
|
self.backward.connect("clicked", self._on_backward_clicked)
|
||||||
|
self.forward.connect("clicked", self._on_forward_clicked)
|
||||||
|
self.next.connect("clicked", self._on_next_clicked)
|
||||||
|
self.mpris_player.connect("changed", self._on_mpris_changed)
|
||||||
|
else:
|
||||||
|
self.play_pause.get_child().set_markup(icons.stop)
|
||||||
|
# Ensure buttons are disabled visually if no player
|
||||||
|
self.backward.add_style_class("disabled")
|
||||||
|
self.forward.add_style_class("disabled")
|
||||||
|
self.prev.add_style_class("disabled")
|
||||||
|
self.next.add_style_class("disabled")
|
||||||
|
self.progressbar.set_value(0.0)
|
||||||
|
self.time.set_text("--:-- / --:--")
|
||||||
|
|
||||||
|
def _apply_mpris_properties(self):
|
||||||
|
mp = self.mpris_player
|
||||||
|
self.title.set_visible(bool(mp.title and mp.title.strip()))
|
||||||
|
if mp.title and mp.title.strip():
|
||||||
|
self.title.set_text(mp.title)
|
||||||
|
self.album.set_visible(bool(mp.album and mp.album.strip()))
|
||||||
|
if mp.album and mp.album.strip():
|
||||||
|
self.album.set_text(mp.album)
|
||||||
|
self.artist.set_visible(bool(mp.artist and mp.artist.strip()))
|
||||||
|
if mp.artist and mp.artist.strip():
|
||||||
|
self.artist.set_text(mp.artist)
|
||||||
|
if mp.arturl:
|
||||||
|
parsed = urllib.parse.urlparse(mp.arturl)
|
||||||
|
if parsed.scheme == "file":
|
||||||
|
local_arturl = urllib.parse.unquote(parsed.path)
|
||||||
|
self._set_cover_image(local_arturl)
|
||||||
|
elif parsed.scheme in ("http", "https"):
|
||||||
|
GLib.Thread.new(
|
||||||
|
"download-artwork", self._download_and_set_artwork, mp.arturl
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._set_cover_image(mp.arturl)
|
||||||
|
else:
|
||||||
|
fallback = os.path.expanduser("~/Pictures/wallpaper/background.jpg")
|
||||||
|
self._set_cover_image(fallback)
|
||||||
|
file_obj = Gio.File.new_for_path(fallback)
|
||||||
|
monitor = file_obj.monitor_file(Gio.FileMonitorFlags.NONE, None)
|
||||||
|
monitor.connect("changed", self.on_wallpaper_changed)
|
||||||
|
self._wallpaper_monitor = monitor
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
# Keep progress bar and time visible always
|
||||||
|
self.progressbar.set_visible(True)
|
||||||
|
self.time.set_visible(True)
|
||||||
|
|
||||||
|
player_name = (
|
||||||
|
mp.player_name.lower()
|
||||||
|
if hasattr(mp, "player_name") and mp.player_name
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
can_seek = hasattr(mp, "can_seek") and mp.can_seek
|
||||||
|
|
||||||
|
if player_name == "firefox" or not can_seek:
|
||||||
|
# Disable seeking buttons and reset progress/time display
|
||||||
|
self.backward.add_style_class("disabled")
|
||||||
|
self.forward.add_style_class("disabled")
|
||||||
|
self.progressbar.set_value(0.0)
|
||||||
|
self.time.set_text("--:-- / --:--")
|
||||||
|
# Stop the timer if it's running
|
||||||
|
if self._progress_timer_id:
|
||||||
|
GLib.source_remove(self._progress_timer_id)
|
||||||
|
self._progress_timer_id = None
|
||||||
|
else:
|
||||||
|
# Enable seeking buttons
|
||||||
|
self.backward.remove_style_class("disabled")
|
||||||
|
self.forward.remove_style_class("disabled")
|
||||||
|
# Start the timer if it's not already running
|
||||||
|
if not self._progress_timer_id:
|
||||||
|
self._progress_timer_id = GLib.timeout_add(1000, self._update_progress)
|
||||||
|
# Initial progress update if possible
|
||||||
|
self._update_progress() # Call once for immediate update
|
||||||
|
|
||||||
|
# Enable/disable prev/next based on capabilities
|
||||||
|
if hasattr(mp, "can_go_previous") and mp.can_go_previous:
|
||||||
|
self.prev.remove_style_class("disabled")
|
||||||
|
else:
|
||||||
|
self.prev.add_style_class("disabled")
|
||||||
|
|
||||||
|
if hasattr(mp, "can_go_next") and mp.can_go_next:
|
||||||
|
self.next.remove_style_class("disabled")
|
||||||
|
else:
|
||||||
|
self.next.add_style_class("disabled")
|
||||||
|
|
||||||
|
def _set_cover_image(self, image_path):
|
||||||
|
if image_path and os.path.isfile(image_path):
|
||||||
|
self.cover.set_image_from_file(image_path)
|
||||||
|
else:
|
||||||
|
fallback = os.path.expanduser("~/Pictures/wallpaper/background.jpg")
|
||||||
|
self.cover.set_image_from_file(fallback)
|
||||||
|
file_obj = Gio.File.new_for_path(fallback)
|
||||||
|
monitor = file_obj.monitor_file(Gio.FileMonitorFlags.NONE, None)
|
||||||
|
monitor.connect("changed", self.on_wallpaper_changed)
|
||||||
|
self._wallpaper_monitor = monitor
|
||||||
|
|
||||||
|
def _download_and_set_artwork(self, arturl):
|
||||||
|
"""
|
||||||
|
Download the artwork from the given URL asynchronously and update the cover image
|
||||||
|
using GLib.idle_add to ensure UI updates occur on the main thread.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parsed = urllib.parse.urlparse(arturl)
|
||||||
|
suffix = os.path.splitext(parsed.path)[1] or ".png"
|
||||||
|
with urllib.request.urlopen(arturl) as response:
|
||||||
|
data = response.read()
|
||||||
|
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
|
||||||
|
temp_file.write(data)
|
||||||
|
temp_file.close()
|
||||||
|
local_arturl = temp_file.name
|
||||||
|
except Exception:
|
||||||
|
local_arturl = os.path.expanduser("~/.current.wall")
|
||||||
|
GLib.idle_add(self._set_cover_image, local_arturl)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_play_pause_icon(self):
|
||||||
|
if self.mpris_player.playback_status == "playing":
|
||||||
|
self.play_pause.get_child().set_markup(icons.pause)
|
||||||
|
else:
|
||||||
|
self.play_pause.get_child().set_markup(icons.play)
|
||||||
|
|
||||||
|
def on_wallpaper_changed(self, monitor, file, other_file, event):
|
||||||
|
self.cover.set_image_from_file(os.path.expanduser("~/.current.wall"))
|
||||||
|
|
||||||
|
# --- Control methods, defined only once each ---
|
||||||
|
def _on_prev_clicked(self, button):
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.previous()
|
||||||
|
|
||||||
|
def _on_play_pause_clicked(self, button):
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.play_pause()
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
|
||||||
|
def _on_backward_clicked(self, button):
|
||||||
|
# Only seek if player exists, can seek, and button is not disabled
|
||||||
|
if (
|
||||||
|
self.mpris_player
|
||||||
|
and self.mpris_player.can_seek
|
||||||
|
and "disabled" not in self.backward.get_style_context().list_classes()
|
||||||
|
):
|
||||||
|
new_pos = max(0, self.mpris_player.position - 5000000) # 5 seconds backward
|
||||||
|
self.mpris_player.position = new_pos
|
||||||
|
|
||||||
|
def _on_forward_clicked(self, button):
|
||||||
|
# Only seek if player exists, can seek, and button is not disabled
|
||||||
|
if (
|
||||||
|
self.mpris_player
|
||||||
|
and self.mpris_player.can_seek
|
||||||
|
and "disabled" not in self.forward.get_style_context().list_classes()
|
||||||
|
):
|
||||||
|
new_pos = self.mpris_player.position + 5000000 # 5 seconds forward
|
||||||
|
self.mpris_player.position = new_pos
|
||||||
|
|
||||||
|
def _on_next_clicked(self, button):
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.next()
|
||||||
|
|
||||||
|
def _update_progress(self):
|
||||||
|
# Timer is now only active if can_seek is true, so no need for the initial check
|
||||||
|
if not self.mpris_player: # Still need to check if player exists
|
||||||
|
# Should not happen if timer logic is correct, but good safeguard
|
||||||
|
if self._progress_timer_id:
|
||||||
|
GLib.source_remove(self._progress_timer_id)
|
||||||
|
self._progress_timer_id = None
|
||||||
|
return False # Stop timer
|
||||||
|
|
||||||
|
try:
|
||||||
|
current = self.mpris_player.position
|
||||||
|
except Exception:
|
||||||
|
current = 0
|
||||||
|
try:
|
||||||
|
total = int(self.mpris_player.length or 0)
|
||||||
|
except Exception:
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
# Prevent division by zero or invalid updates
|
||||||
|
if total <= 0:
|
||||||
|
progress = 0.0
|
||||||
|
self.time.set_text("--:-- / --:--")
|
||||||
|
# Don't stop the timer here, length might become available later
|
||||||
|
else:
|
||||||
|
progress = current / total
|
||||||
|
self.time.set_text(
|
||||||
|
f"{self._format_time(current)} / {self._format_time(total)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.progressbar.set_value(progress)
|
||||||
|
return True # Continue the timer
|
||||||
|
|
||||||
|
def _format_time(self, us):
|
||||||
|
seconds = int(us / 1000000)
|
||||||
|
minutes = seconds // 60
|
||||||
|
seconds = seconds % 60
|
||||||
|
return f"{minutes}:{seconds:02}"
|
||||||
|
|
||||||
|
def _update_metadata(self):
|
||||||
|
if not self.mpris_player:
|
||||||
|
return False
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _on_mpris_changed(self, *args):
|
||||||
|
# Debounce metadata updates to avoid excessive work on the main thread.
|
||||||
|
if not hasattr(self, "_update_pending") or not self._update_pending:
|
||||||
|
self._update_pending = True
|
||||||
|
# Use idle_add for potentially faster UI response than timeout_add(100)
|
||||||
|
GLib.idle_add(self._apply_mpris_properties_debounced)
|
||||||
|
|
||||||
|
def _apply_mpris_properties_debounced(self):
|
||||||
|
# Ensure player still exists before applying properties
|
||||||
|
if self.mpris_player:
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
else:
|
||||||
|
# Player vanished, ensure timer is stopped if it was running
|
||||||
|
if self._progress_timer_id:
|
||||||
|
GLib.source_remove(self._progress_timer_id)
|
||||||
|
self._progress_timer_id = None
|
||||||
|
self._update_pending = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Player(Box):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
name="player",
|
||||||
|
orientation="v",
|
||||||
|
h_align="fill",
|
||||||
|
spacing=0,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=True,
|
||||||
|
)
|
||||||
|
self.player_stack = Stack(
|
||||||
|
name="player-stack",
|
||||||
|
transition_type="slide-left-right",
|
||||||
|
transition_duration=500,
|
||||||
|
v_align="center",
|
||||||
|
v_expand=True,
|
||||||
|
)
|
||||||
|
self.switcher = Gtk.StackSwitcher(
|
||||||
|
name="player-switcher",
|
||||||
|
spacing=8,
|
||||||
|
)
|
||||||
|
self.switcher.set_stack(self.player_stack)
|
||||||
|
self.switcher.set_halign(Gtk.Align.CENTER)
|
||||||
|
self.mpris_manager = MprisPlayerManager()
|
||||||
|
players = self.mpris_manager.players
|
||||||
|
if players:
|
||||||
|
for p in players:
|
||||||
|
mp = MprisPlayer(p)
|
||||||
|
pb = PlayerBox(mpris_player=mp)
|
||||||
|
self.player_stack.add_titled(pb, mp.player_name, mp.player_name)
|
||||||
|
else:
|
||||||
|
pb = PlayerBox(mpris_player=None)
|
||||||
|
self.player_stack.add_titled(pb, "nothing", "Nothing Playing")
|
||||||
|
self.mpris_manager.connect("player-appeared", self.on_player_appeared)
|
||||||
|
self.mpris_manager.connect("player-vanished", self.on_player_vanished)
|
||||||
|
self.switcher.set_visible(True)
|
||||||
|
self.add(self.player_stack)
|
||||||
|
self.add(self.switcher)
|
||||||
|
GLib.idle_add(self._replace_switcher_labels)
|
||||||
|
|
||||||
|
def on_player_appeared(self, manager, player):
|
||||||
|
children = self.player_stack.get_children()
|
||||||
|
if len(children) == 1 and not getattr(children[0], "mpris_player", None):
|
||||||
|
self.player_stack.remove(children[0])
|
||||||
|
mp = MprisPlayer(player)
|
||||||
|
pb = PlayerBox(mpris_player=mp)
|
||||||
|
self.player_stack.add_titled(pb, mp.player_name, mp.player_name)
|
||||||
|
# Timer is now started conditionally within PlayerBox.__init__
|
||||||
|
self.switcher.set_visible(True)
|
||||||
|
GLib.idle_add(lambda: self._update_switcher_for_player(mp.player_name))
|
||||||
|
GLib.idle_add(self._replace_switcher_labels)
|
||||||
|
|
||||||
|
def on_player_vanished(self, manager, player_name):
|
||||||
|
for child in self.player_stack.get_children():
|
||||||
|
if (
|
||||||
|
hasattr(child, "mpris_player")
|
||||||
|
and child.mpris_player
|
||||||
|
and child.mpris_player.player_name == player_name
|
||||||
|
):
|
||||||
|
self.player_stack.remove(child)
|
||||||
|
break
|
||||||
|
if not any(
|
||||||
|
getattr(child, "mpris_player", None)
|
||||||
|
for child in self.player_stack.get_children()
|
||||||
|
):
|
||||||
|
pb = PlayerBox(mpris_player=None)
|
||||||
|
self.player_stack.add_titled(pb, "nothing", "Nothing Playing")
|
||||||
|
self.switcher.set_visible(True)
|
||||||
|
GLib.idle_add(self._replace_switcher_labels)
|
||||||
|
|
||||||
|
def _replace_switcher_labels(self):
|
||||||
|
buttons = self.switcher.get_children()
|
||||||
|
for btn in buttons:
|
||||||
|
if isinstance(btn, Gtk.ToggleButton):
|
||||||
|
default_label = None
|
||||||
|
for child in btn.get_children():
|
||||||
|
if isinstance(child, Gtk.Label):
|
||||||
|
default_label = child
|
||||||
|
break
|
||||||
|
if default_label:
|
||||||
|
label_player_name = getattr(
|
||||||
|
default_label, "player_name", default_label.get_text().lower()
|
||||||
|
)
|
||||||
|
icon_markup = get_player_icon_markup_by_name(label_player_name)
|
||||||
|
btn.remove(default_label)
|
||||||
|
new_label = Label(name="player-label", markup=icon_markup)
|
||||||
|
new_label.player_name = label_player_name
|
||||||
|
btn.add(new_label)
|
||||||
|
new_label.show_all()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _update_switcher_for_player(self, player_name):
|
||||||
|
for btn in self.switcher.get_children():
|
||||||
|
if isinstance(btn, Gtk.ToggleButton):
|
||||||
|
default_label = None
|
||||||
|
for child in btn.get_children():
|
||||||
|
if isinstance(child, Gtk.Label):
|
||||||
|
default_label = child
|
||||||
|
break
|
||||||
|
if default_label:
|
||||||
|
label_player_name = getattr(
|
||||||
|
default_label, "player_name", default_label.get_text().lower()
|
||||||
|
)
|
||||||
|
if label_player_name == player_name.lower():
|
||||||
|
icon_markup = get_player_icon_markup_by_name(player_name)
|
||||||
|
btn.remove(default_label)
|
||||||
|
new_label = Label(name="player-label", markup=icon_markup)
|
||||||
|
new_label.player_name = player_name.lower()
|
||||||
|
btn.add(new_label)
|
||||||
|
new_label.show_all()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerSmall(CenterBox):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
name="player-small", orientation="h", h_align="fill", v_align="center"
|
||||||
|
)
|
||||||
|
self._show_artist = False # toggle flag
|
||||||
|
self._display_options = ["cavalcade", "title", "artist"]
|
||||||
|
self._display_index = 0
|
||||||
|
self._current_display = "cavalcade"
|
||||||
|
|
||||||
|
self.mpris_icon = Button(
|
||||||
|
name="compact-mpris-icon",
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
child=Label(name="compact-mpris-icon-label", markup=icons.disc),
|
||||||
|
)
|
||||||
|
# Remove scroll events; instead, add button press events.
|
||||||
|
self.mpris_icon.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||||
|
self.mpris_icon.connect("button-press-event", self._on_icon_button_press)
|
||||||
|
# Prevent the child from propagating events.
|
||||||
|
child = self.mpris_icon.get_child()
|
||||||
|
child.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||||
|
child.connect("button-press-event", lambda widget, event: True)
|
||||||
|
# Add hover effect
|
||||||
|
add_hover_cursor(self.mpris_icon)
|
||||||
|
|
||||||
|
self.mpris_label = Label(
|
||||||
|
name="compact-mpris-label",
|
||||||
|
label="Nothing Playing",
|
||||||
|
ellipsization="end",
|
||||||
|
max_chars_width=26,
|
||||||
|
h_align="center",
|
||||||
|
)
|
||||||
|
self.mpris_button = Button(
|
||||||
|
name="compact-mpris-button",
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
child=Label(name="compact-mpris-button-label", markup=icons.play),
|
||||||
|
)
|
||||||
|
self.mpris_button.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||||
|
self.mpris_button.connect(
|
||||||
|
"button-press-event", self._on_play_pause_button_press
|
||||||
|
)
|
||||||
|
# Add hover effect
|
||||||
|
add_hover_cursor(self.mpris_button)
|
||||||
|
|
||||||
|
# self.cavalcade = SpectrumRender()
|
||||||
|
# self.cavalcade_box = self.cavalcade.get_spectrum_box()
|
||||||
|
|
||||||
|
self.center_stack = Stack(
|
||||||
|
name="compact-mpris",
|
||||||
|
transition_type="crossfade",
|
||||||
|
transition_duration=100,
|
||||||
|
v_align="center",
|
||||||
|
v_expand=False,
|
||||||
|
children=[
|
||||||
|
# self.cavalcade_box,
|
||||||
|
self.mpris_label,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
# self.center_stack.set_visible_child(self.cavalcade_box) # default to cavalcade
|
||||||
|
|
||||||
|
# Create additional compact view.
|
||||||
|
self.mpris_small = CenterBox(
|
||||||
|
name="compact-mpris",
|
||||||
|
orientation="h",
|
||||||
|
h_expand=True,
|
||||||
|
h_align="fill",
|
||||||
|
v_align="center",
|
||||||
|
v_expand=False,
|
||||||
|
start_children=self.mpris_icon,
|
||||||
|
center_children=self.center_stack, # Changed to center_stack to handle stack switching
|
||||||
|
end_children=self.mpris_button,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add(self.mpris_small)
|
||||||
|
|
||||||
|
self.mpris_manager = MprisPlayerManager()
|
||||||
|
self.mpris_player = None
|
||||||
|
# Almacenar el índice del reproductor actual
|
||||||
|
self.current_index = 0
|
||||||
|
|
||||||
|
players = self.mpris_manager.players
|
||||||
|
if players:
|
||||||
|
mp = MprisPlayer(players[self.current_index])
|
||||||
|
self.mpris_player = mp
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
self.mpris_player.connect("changed", self._on_mpris_changed)
|
||||||
|
else:
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
|
||||||
|
self.mpris_manager.connect("player-appeared", self.on_player_appeared)
|
||||||
|
self.mpris_manager.connect("player-vanished", self.on_player_vanished)
|
||||||
|
self.mpris_button.connect("clicked", self._on_play_pause_clicked)
|
||||||
|
|
||||||
|
def _apply_mpris_properties(self):
|
||||||
|
if not self.mpris_player:
|
||||||
|
self.mpris_label.set_text("Nothing Playing")
|
||||||
|
self.mpris_button.get_child().set_markup(icons.stop)
|
||||||
|
self.mpris_icon.get_child().set_markup(icons.disc)
|
||||||
|
if self._current_display != "cavalcade":
|
||||||
|
self.center_stack.set_visible_child(
|
||||||
|
self.mpris_label
|
||||||
|
) # if was title or artist, keep showing label
|
||||||
|
# else:
|
||||||
|
# self.center_stack.set_visible_child(
|
||||||
|
# self.cavalcade_box
|
||||||
|
# ) # default to cavalcade if no player
|
||||||
|
return
|
||||||
|
|
||||||
|
mp = self.mpris_player
|
||||||
|
|
||||||
|
# Choose icon based on player name.
|
||||||
|
player_name = (
|
||||||
|
mp.player_name.lower()
|
||||||
|
if hasattr(mp, "player_name") and mp.player_name
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
icon_markup = get_player_icon_markup_by_name(player_name)
|
||||||
|
self.mpris_icon.get_child().set_markup(icon_markup)
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
|
||||||
|
if self._current_display == "title":
|
||||||
|
text = mp.title if mp.title and mp.title.strip() else "Nothing Playing"
|
||||||
|
self.mpris_label.set_text(text)
|
||||||
|
self.center_stack.set_visible_child(self.mpris_label)
|
||||||
|
elif self._current_display == "artist":
|
||||||
|
text = mp.artist if mp.artist else "Nothing Playing"
|
||||||
|
self.mpris_label.set_text(text)
|
||||||
|
self.center_stack.set_visible_child(self.mpris_label)
|
||||||
|
# else: # default cavalcade
|
||||||
|
# self.center_stack.set_visible_child(self.cavalcade_box)
|
||||||
|
|
||||||
|
def _on_icon_button_press(self, widget, event):
|
||||||
|
from gi.repository import Gdk
|
||||||
|
|
||||||
|
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
|
players = self.mpris_manager.players
|
||||||
|
if not players:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if event.button == 2: # Middle-click: cycle display
|
||||||
|
self._display_index = (self._display_index + 1) % len(
|
||||||
|
self._display_options
|
||||||
|
)
|
||||||
|
self._current_display = self._display_options[self._display_index]
|
||||||
|
self._apply_mpris_properties() # Re-apply to update label/cavalcade
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Cambiar de reproductor según el botón presionado.
|
||||||
|
if event.button == 1: # Left-click: next player
|
||||||
|
self.current_index = (self.current_index + 1) % len(players)
|
||||||
|
elif event.button == 3: # Right-click: previous player
|
||||||
|
self.current_index = (self.current_index - 1) % len(players)
|
||||||
|
if self.current_index < 0:
|
||||||
|
self.current_index = len(players) - 1
|
||||||
|
|
||||||
|
mp_new = MprisPlayer(players[self.current_index])
|
||||||
|
self.mpris_player = mp_new
|
||||||
|
# Conectar el evento "changed" para que se actualice
|
||||||
|
self.mpris_player.connect("changed", self._on_mpris_changed)
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
return True # Se consume el evento
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _on_play_pause_button_press(self, widget, event):
|
||||||
|
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
|
if event.button == 1: # Click izquierdo -> track anterior
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.previous()
|
||||||
|
self.mpris_button.get_child().set_markup(icons.prev)
|
||||||
|
GLib.timeout_add(500, self._restore_play_pause_icon)
|
||||||
|
elif event.button == 3: # Click derecho -> siguiente track
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.next()
|
||||||
|
self.mpris_button.get_child().set_markup(icons.next)
|
||||||
|
GLib.timeout_add(500, self._restore_play_pause_icon)
|
||||||
|
elif event.button == 2: # Click medio -> play/pausa
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.play_pause()
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
return True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _restore_play_pause_icon(self):
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _on_icon_clicked(
|
||||||
|
self, widget
|
||||||
|
): # No longer used, logic moved to _on_icon_button_press
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_play_pause_icon(self):
|
||||||
|
if self.mpris_player and self.mpris_player.playback_status == "playing":
|
||||||
|
self.mpris_button.get_child().set_markup(icons.pause)
|
||||||
|
else:
|
||||||
|
self.mpris_button.get_child().set_markup(icons.play)
|
||||||
|
|
||||||
|
def _on_play_pause_clicked(self, button):
|
||||||
|
if self.mpris_player:
|
||||||
|
self.mpris_player.play_pause()
|
||||||
|
self.update_play_pause_icon()
|
||||||
|
|
||||||
|
def _on_mpris_changed(self, *args):
|
||||||
|
# Update properties when the player's state changes.
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
|
||||||
|
def on_player_appeared(self, manager, player):
|
||||||
|
# When a new player appears, use it if no player is active.
|
||||||
|
if not self.mpris_player:
|
||||||
|
mp = MprisPlayer(player)
|
||||||
|
self.mpris_player = mp
|
||||||
|
self._apply_mpris_properties()
|
||||||
|
self.mpris_player.connect("changed", self._on_mpris_changed)
|
||||||
|
|
||||||
|
def on_player_vanished(self, manager, player_name):
|
||||||
|
players = self.mpris_manager.players
|
||||||
|
if (
|
||||||
|
players
|
||||||
|
and self.mpris_player
|
||||||
|
and self.mpris_player.player_name == player_name
|
||||||
|
):
|
||||||
|
if players: # Check if players is not empty after vanishing
|
||||||
|
self.current_index = self.current_index % len(players)
|
||||||
|
new_player = MprisPlayer(players[self.current_index])
|
||||||
|
self.mpris_player = new_player
|
||||||
|
self.mpris_player.connect("changed", self._on_mpris_changed)
|
||||||
|
else:
|
||||||
|
self.mpris_player = None # No players left
|
||||||
|
elif not players:
|
||||||
|
self.mpris_player = None
|
||||||
|
self._apply_mpris_properties()
|
||||||
112
bar/modules/power.py
Normal file
112
bar/modules/power.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.button import Button
|
||||||
|
from fabric.utils.helpers import exec_shell_command_async
|
||||||
|
import modules.icons as icons
|
||||||
|
|
||||||
|
|
||||||
|
class PowerMenu(Box):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
name="power-menu",
|
||||||
|
orientation="h",
|
||||||
|
spacing=4,
|
||||||
|
v_align="center",
|
||||||
|
h_align="center",
|
||||||
|
visible=True,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.notch = kwargs["notch"]
|
||||||
|
|
||||||
|
self.btn_lock = Button(
|
||||||
|
name="power-menu-button",
|
||||||
|
child=Label(name="button-label", markup=icons.lock),
|
||||||
|
on_clicked=self.lock,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.btn_suspend = Button(
|
||||||
|
name="power-menu-button",
|
||||||
|
child=Label(name="button-label", markup=icons.suspend),
|
||||||
|
on_clicked=self.suspend,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.btn_logout = Button(
|
||||||
|
name="power-menu-button",
|
||||||
|
child=Label(name="button-label", markup=icons.logout),
|
||||||
|
on_clicked=self.logout,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.btn_reboot = Button(
|
||||||
|
name="power-menu-button",
|
||||||
|
child=Label(name="button-label", markup=icons.reboot),
|
||||||
|
on_clicked=self.reboot,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.btn_shutdown = Button(
|
||||||
|
name="power-menu-button",
|
||||||
|
child=Label(name="button-label", markup=icons.shutdown),
|
||||||
|
on_clicked=self.poweroff,
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
h_align="center",
|
||||||
|
v_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.buttons = [
|
||||||
|
self.btn_lock,
|
||||||
|
self.btn_suspend,
|
||||||
|
self.btn_logout,
|
||||||
|
self.btn_reboot,
|
||||||
|
self.btn_shutdown,
|
||||||
|
]
|
||||||
|
|
||||||
|
for button in self.buttons:
|
||||||
|
self.add(button)
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def close_menu(self):
|
||||||
|
self.notch.close_notch()
|
||||||
|
|
||||||
|
# Métodos de acción
|
||||||
|
def lock(self, *args):
|
||||||
|
print("Locking screen...")
|
||||||
|
exec_shell_command_async("loginctl lock-session")
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
|
def suspend(self, *args):
|
||||||
|
print("Suspending system...")
|
||||||
|
exec_shell_command_async("systemctl suspend")
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
|
def logout(self, *args):
|
||||||
|
print("Logging out...")
|
||||||
|
exec_shell_command_async("hyprctl dispatch exit")
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
|
def reboot(self, *args):
|
||||||
|
print("Rebooting system...")
|
||||||
|
exec_shell_command_async("systemctl reboot")
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
|
def poweroff(self, *args):
|
||||||
|
print("Powering off...")
|
||||||
|
exec_shell_command_async("systemctl poweroff")
|
||||||
|
self.close_menu()
|
||||||
92
bar/modules/vinyl.py
Normal file
92
bar/modules/vinyl.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.eventbox import EventBox
|
||||||
|
from fabric.widgets.overlay import Overlay
|
||||||
|
from fabric.core.service import Property
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class VinylButton(Box):
|
||||||
|
@Property(bool, "read-write", default_value=False)
|
||||||
|
def active(self) -> bool:
|
||||||
|
return self._active
|
||||||
|
|
||||||
|
@active.setter
|
||||||
|
def active(self, value: bool):
|
||||||
|
self._active = value
|
||||||
|
# Update appearance based on state
|
||||||
|
self._update_appearance()
|
||||||
|
|
||||||
|
# Execute shell command based on new state
|
||||||
|
if self._active:
|
||||||
|
self._execute_active_command()
|
||||||
|
else:
|
||||||
|
self._execute_inactive_command()
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
active_command="""pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0
|
||||||
|
pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1""",
|
||||||
|
inactive_command="""pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0
|
||||||
|
pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1 """,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# Initialize properties
|
||||||
|
self._active = False
|
||||||
|
self._active_command = active_command
|
||||||
|
self._inactive_command = inactive_command
|
||||||
|
|
||||||
|
# Set up the icon
|
||||||
|
self.icon = Label(
|
||||||
|
label="", # CD icon
|
||||||
|
name="vinyl-icon",
|
||||||
|
style="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up event box to handle clicks
|
||||||
|
self.event_box = EventBox(
|
||||||
|
events="button-press",
|
||||||
|
child=Overlay(
|
||||||
|
child=self.icon,
|
||||||
|
),
|
||||||
|
name="vinyl-button",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Connect click event
|
||||||
|
self.event_box.connect("button-press-event", self._on_clicked)
|
||||||
|
|
||||||
|
# Add to parent box
|
||||||
|
self.add(self.event_box)
|
||||||
|
|
||||||
|
# Initialize appearance
|
||||||
|
self._update_appearance()
|
||||||
|
|
||||||
|
def _update_appearance(self):
|
||||||
|
"""Update CSS class based on active state"""
|
||||||
|
if self._active:
|
||||||
|
self.add_style_class("active")
|
||||||
|
else:
|
||||||
|
self.remove_style_class("active")
|
||||||
|
|
||||||
|
def _on_clicked(self, _, event):
|
||||||
|
"""Handle button click event"""
|
||||||
|
if event.button == 1: # Left click
|
||||||
|
# Toggle active state
|
||||||
|
self.active = not self.active
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _execute_active_command(self):
|
||||||
|
"""Execute shell command when button is activated"""
|
||||||
|
try:
|
||||||
|
subprocess.Popen(self._active_command, shell=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error executing active command: {e}")
|
||||||
|
|
||||||
|
def _execute_inactive_command(self):
|
||||||
|
"""Execute shell command when button is deactivated"""
|
||||||
|
try:
|
||||||
|
subprocess.Popen(self._inactive_command, shell=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error executing inactive command: {e}")
|
||||||
48
bar/modules/volume.py
Normal file
48
bar/modules/volume.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||||
|
from fabric.audio.service import Audio
|
||||||
|
from fabric.widgets.eventbox import EventBox
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.overlay import Overlay
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeWidget(Box):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.audio = Audio()
|
||||||
|
|
||||||
|
self.progress_bar = CircularProgressBar(
|
||||||
|
name="volume-progress-bar", pie=True, size=24
|
||||||
|
)
|
||||||
|
|
||||||
|
self.event_box = EventBox(
|
||||||
|
events="scroll",
|
||||||
|
child=Overlay(
|
||||||
|
child=self.progress_bar,
|
||||||
|
overlays=Label(
|
||||||
|
label="",
|
||||||
|
style="margin: 0px 6px 0px 0px; font-size: 12px", # to center the icon glyph
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.audio.connect("notify::speaker", self.on_speaker_changed)
|
||||||
|
self.event_box.connect("scroll-event", self.on_scroll)
|
||||||
|
self.add(self.event_box)
|
||||||
|
|
||||||
|
def on_scroll(self, _, event):
|
||||||
|
match event.direction:
|
||||||
|
case 0:
|
||||||
|
self.audio.speaker.volume += 8
|
||||||
|
case 1:
|
||||||
|
self.audio.speaker.volume -= 8
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_speaker_changed(self, *_):
|
||||||
|
if not self.audio.speaker:
|
||||||
|
return
|
||||||
|
self.progress_bar.value = self.audio.speaker.volume / 100
|
||||||
|
self.audio.speaker.bind(
|
||||||
|
"volume", "value", self.progress_bar, lambda _, v: v / 100
|
||||||
|
)
|
||||||
|
return
|
||||||
88
bar/modules/window_fuzzy.py
Normal file
88
bar/modules/window_fuzzy.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import operator
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.entry import Entry
|
||||||
|
from gi.repository import Gdk
|
||||||
|
from bar.services.wlr.service import WaylandWindowTracker, Window as WaylandWindow
|
||||||
|
from pywayland.client import Display
|
||||||
|
|
||||||
|
|
||||||
|
class FuzzyWindowFinder(Window):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
monitor: int = 0,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name="finder",
|
||||||
|
anchor="center",
|
||||||
|
monitor=monitor,
|
||||||
|
keyboard_mode="on-demand",
|
||||||
|
type="popup",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
self.window_tracker = WaylandWindowTracker()
|
||||||
|
self.window_tracker.ready_signal.connect(lambda *_: print("Tracker is ready"))
|
||||||
|
self._all_windows: list[WaylandWindow] = []
|
||||||
|
|
||||||
|
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
||||||
|
|
||||||
|
self.search_entry = Entry(
|
||||||
|
name="search-entry",
|
||||||
|
placeholder="Search Windows...",
|
||||||
|
h_expand=True,
|
||||||
|
editable=True,
|
||||||
|
notify_text=self.notify_text,
|
||||||
|
on_activate=lambda entry, *_: self.on_search_entry_activate(
|
||||||
|
entry.get_text()
|
||||||
|
),
|
||||||
|
on_key_press_event=self.on_search_entry_key_press,
|
||||||
|
)
|
||||||
|
self.picker_box = Box(
|
||||||
|
name="picker-box",
|
||||||
|
spacing=4,
|
||||||
|
orientation="v",
|
||||||
|
children=[self.search_entry, self.viewport],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add(self.picker_box)
|
||||||
|
self.arrange_viewport("")
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
self._all_windows = self.window_tracker.windows
|
||||||
|
print(self._all_windows[0])
|
||||||
|
self.arrange_viewport("")
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def notify_text(self, entry, *_):
|
||||||
|
text = entry.get_text()
|
||||||
|
self.arrange_viewport(text) # Update list on typing
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def on_search_entry_key_press(self, widget, event):
|
||||||
|
# if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
|
||||||
|
# self.move_selection_2d(event.keyval)
|
||||||
|
# return True
|
||||||
|
print(event.keyval)
|
||||||
|
if event.keyval == Gdk.KEY_Return:
|
||||||
|
self.window_tracker.activate_window(self._filtered[0])
|
||||||
|
if event.keyval in [Gdk.KEY_Escape, 103]:
|
||||||
|
self.hide()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def on_search_entry_activate(self, text):
|
||||||
|
print(f"activate {text}")
|
||||||
|
|
||||||
|
def arrange_viewport(self, query: str = ""):
|
||||||
|
self.viewport.children = [] # Clear previous entries
|
||||||
|
|
||||||
|
self._filtered = [
|
||||||
|
w for w in self._all_windows if query.lower() in w.title.lower()
|
||||||
|
]
|
||||||
|
titles = [w.title for w in self._filtered]
|
||||||
|
|
||||||
|
for window in titles:
|
||||||
|
self.viewport.add(
|
||||||
|
Box(name="slot-box", orientation="h", children=[Label(label=window)])
|
||||||
|
)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .service import River
|
|
||||||
|
|
||||||
__all__ = ["River"]
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import logging
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from fabric.core.service import Service, Signal, Property
|
|
||||||
from pywayland.client import Display
|
|
||||||
from pywayland.protocol.wayland import WlOutput, WlSeat
|
|
||||||
from ..generated.river_status_unstable_v1 import (
|
|
||||||
ZriverStatusManagerV1,
|
|
||||||
ZriverOutputStatusV1,
|
|
||||||
ZriverSeatStatusV1,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OutputState:
|
|
||||||
id: int
|
|
||||||
output: WlOutput
|
|
||||||
status: ZriverOutputStatusV1 = None
|
|
||||||
focused_tags: List[int] = field(default_factory=list)
|
|
||||||
view_tags: List[int] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
class River(Service):
|
|
||||||
@Property(bool, "readable", "is-ready", default_value=False)
|
|
||||||
def ready(self) -> bool:
|
|
||||||
return self._ready
|
|
||||||
|
|
||||||
@Signal
|
|
||||||
def ready(self):
|
|
||||||
return self.notify("ready")
|
|
||||||
|
|
||||||
@Signal("event", flags="detailed")
|
|
||||||
def event(self, event: object): ...
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._ready = False
|
|
||||||
self.display = None
|
|
||||||
self.registry = None
|
|
||||||
self.outputs: Dict[int, OutputState] = {}
|
|
||||||
self.status_mgr = None
|
|
||||||
self.seat = None
|
|
||||||
self.seat_status: ZriverSeatStatusV1 = None
|
|
||||||
self.ready.emit()
|
|
||||||
|
|
||||||
def on_start(self):
|
|
||||||
print("✅ River.on_start called")
|
|
||||||
|
|
||||||
logger.info("[RiverService] Starting River service...")
|
|
||||||
self.display = Display()
|
|
||||||
self.display.connect()
|
|
||||||
self.registry = self.display.get_registry()
|
|
||||||
|
|
||||||
self.registry.dispatcher["global"] = self._on_global
|
|
||||||
self.registry.dispatcher["global_remove"] = lambda *_: None
|
|
||||||
|
|
||||||
self.display.roundtrip()
|
|
||||||
|
|
||||||
if self.seat and self.status_mgr:
|
|
||||||
self.seat_status = self.status_mgr.get_river_seat_status(self.seat)
|
|
||||||
|
|
||||||
for id, output_state in self.outputs.items():
|
|
||||||
status = self.status_mgr.get_river_output_status(output_state.output)
|
|
||||||
output_state.status = status
|
|
||||||
status.dispatcher["focused_tags"] = self._make_focused_handler(id)
|
|
||||||
status.dispatcher["view_tags"] = self._make_view_handler(id)
|
|
||||||
|
|
||||||
self.display.roundtrip()
|
|
||||||
|
|
||||||
self._ready = True
|
|
||||||
self.ready.emit()
|
|
||||||
logger.info("[RiverService] Ready. Monitoring tags.")
|
|
||||||
|
|
||||||
def on_tick(self):
|
|
||||||
# Periodic poll
|
|
||||||
self.display.roundtrip()
|
|
||||||
|
|
||||||
def _on_global(self, registry, name, interface, version):
|
|
||||||
if interface == "wl_output":
|
|
||||||
output = registry.bind(name, WlOutput, version)
|
|
||||||
self.outputs[name] = OutputState(id=name, output=output)
|
|
||||||
|
|
||||||
elif interface == "wl_seat":
|
|
||||||
self.seat = registry.bind(name, WlSeat, version)
|
|
||||||
|
|
||||||
elif interface == "zriver_status_manager_v1":
|
|
||||||
self.status_mgr = registry.bind(name, ZriverStatusManagerV1, version)
|
|
||||||
|
|
||||||
def _make_focused_handler(self, output_id):
|
|
||||||
def handler(_, bitfield):
|
|
||||||
tags = self._decode_bitfield(bitfield)
|
|
||||||
self.outputs[output_id].focused_tags = tags
|
|
||||||
logger.debug(f"[RiverService] Output {output_id} focused: {tags}")
|
|
||||||
self.emit(f"event::focused_tags::{output_id}", tags)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _make_view_handler(self, output_id):
|
|
||||||
def handler(_, array):
|
|
||||||
tags = self._decode_array_bitfields(array)
|
|
||||||
self.outputs[output_id].view_tags = tags
|
|
||||||
logger.debug(f"[RiverService] Output {output_id} views: {tags}")
|
|
||||||
self.emit(f"event::view_tags::{output_id}", tags)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _decode_bitfield(self, bits: int) -> List[int]:
|
|
||||||
return [i for i in range(32) if bits & (1 << i)]
|
|
||||||
|
|
||||||
def _decode_array_bitfields(self, array) -> List[int]:
|
|
||||||
tags = set()
|
|
||||||
for bits in array:
|
|
||||||
tags.update(self._decode_bitfield(bits))
|
|
||||||
return sorted(tags)
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
from fabric.core.service import Property
|
|
||||||
from fabric.widgets.button import Button
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from fabric.widgets.eventbox import EventBox
|
|
||||||
from fabric.utils.helpers import bulk_connect
|
|
||||||
from .service import River
|
|
||||||
|
|
||||||
|
|
||||||
from gi.repository import Gdk
|
|
||||||
|
|
||||||
_connection: River | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_river_connection() -> River:
|
|
||||||
global _connection
|
|
||||||
if not _connection:
|
|
||||||
_connection = River()
|
|
||||||
return _connection
|
|
||||||
|
|
||||||
|
|
||||||
class RiverWorkspaceButton(Button):
|
|
||||||
@Property(int, "readable")
|
|
||||||
def id(self) -> int:
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@Property(bool, "read-write", default_value=False)
|
|
||||||
def active(self) -> bool:
|
|
||||||
return self._active
|
|
||||||
|
|
||||||
@active.setter
|
|
||||||
def active(self, value: bool):
|
|
||||||
self._active = value
|
|
||||||
(self.remove_style_class if not value else self.add_style_class)("active")
|
|
||||||
|
|
||||||
@Property(bool, "read-write", default_value=False)
|
|
||||||
def empty(self) -> bool:
|
|
||||||
return self._empty
|
|
||||||
|
|
||||||
@empty.setter
|
|
||||||
def empty(self, value: bool):
|
|
||||||
self._empty = value
|
|
||||||
(self.remove_style_class if not value else self.add_style_class)("empty")
|
|
||||||
|
|
||||||
def __init__(self, id: int, label: str = None, **kwargs):
|
|
||||||
super().__init__(label or str(id), **kwargs)
|
|
||||||
self._id = id
|
|
||||||
self._active = False
|
|
||||||
self._empty = True
|
|
||||||
|
|
||||||
|
|
||||||
class RiverWorkspaces(EventBox):
|
|
||||||
def __init__(self, output_id: int, max_tags: int = 9, **kwargs):
|
|
||||||
super().__init__(events="scroll")
|
|
||||||
self.output_id = output_id
|
|
||||||
self.max_tags = max_tags
|
|
||||||
self.service = get_river_connection()
|
|
||||||
self._box = Box(**kwargs)
|
|
||||||
self.children = self._box
|
|
||||||
|
|
||||||
self._buttons = {i: RiverWorkspaceButton(i) for i in range(max_tags)}
|
|
||||||
for btn in self._buttons.values():
|
|
||||||
btn.connect("clicked", self.on_workspace_click)
|
|
||||||
self._box.add(btn)
|
|
||||||
|
|
||||||
# hook into River signals
|
|
||||||
self.service.connect(f"event::focused_tags::{output_id}", self.on_focus_change)
|
|
||||||
self.service.connect(f"event::view_tags::{output_id}", self.on_view_change)
|
|
||||||
if self.service.ready:
|
|
||||||
self.on_ready(None)
|
|
||||||
else:
|
|
||||||
self.service.connect("event::ready", self.on_ready)
|
|
||||||
|
|
||||||
self.connect("scroll-event", self.on_scroll)
|
|
||||||
|
|
||||||
def on_ready(self, _):
|
|
||||||
print(self.service.outputs)
|
|
||||||
|
|
||||||
def on_focus_change(self, _, tags: list[int]):
|
|
||||||
print(tags)
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.active = i in tags
|
|
||||||
|
|
||||||
def on_view_change(self, _, tags: list[int]):
|
|
||||||
print(tags)
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.empty = i not in tags
|
|
||||||
|
|
||||||
def on_workspace_click(self, btn: RiverWorkspaceButton):
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
subprocess.run(["riverctl", "tag", str(btn.id)])
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_scroll(self, _, event: Gdk.EventScroll):
|
|
||||||
direction = event.direction # UP or DOWN
|
|
||||||
cmd = "tag +1" if direction == Gdk.ScrollDirection.DOWN else "tag -1"
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
subprocess.run(["riverctl", *cmd.split()])
|
|
||||||
0
bar/services/__init__.py
Normal file
0
bar/services/__init__.py
Normal file
282
bar/services/mpris.py
Normal file
282
bar/services/mpris.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Standard library imports
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
# Third-party imports
|
||||||
|
import gi
|
||||||
|
from gi.repository import GLib # type: ignore
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Fabric imports
|
||||||
|
from fabric.core.service import Property, Service, Signal
|
||||||
|
from fabric.utils import bulk_connect
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerctlImportError(ImportError):
|
||||||
|
"""An error to raise when playerctl is not installed."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__(
|
||||||
|
"Playerctl is not installed, please install it first",
|
||||||
|
*args,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Try to import Playerctl, raise custom error if not available
|
||||||
|
try:
|
||||||
|
gi.require_version("Playerctl", "2.0")
|
||||||
|
from gi.repository import Playerctl
|
||||||
|
except ValueError:
|
||||||
|
raise PlayerctlImportError
|
||||||
|
|
||||||
|
|
||||||
|
class MprisPlayer(Service):
|
||||||
|
"""A service to manage a mpris player."""
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def exit(self, value: bool) -> bool: ...
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def changed(self) -> None: ...
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
player: Playerctl.Player,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self._signal_connectors: dict = {}
|
||||||
|
self._player: Playerctl.Player = player
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
for sn in ["playback-status", "loop-status", "shuffle", "volume", "seeked"]:
|
||||||
|
self._signal_connectors[sn] = self._player.connect(
|
||||||
|
sn,
|
||||||
|
lambda *args, sn=sn: self.notifier(sn, args),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._signal_connectors["exit"] = self._player.connect(
|
||||||
|
"exit",
|
||||||
|
self.on_player_exit,
|
||||||
|
)
|
||||||
|
self._signal_connectors["metadata"] = self._player.connect(
|
||||||
|
"metadata",
|
||||||
|
lambda *args: self.update_status(),
|
||||||
|
)
|
||||||
|
GLib.idle_add(lambda *args: self.update_status_once())
|
||||||
|
|
||||||
|
def update_status(self):
|
||||||
|
# schedule each notifier asynchronously.
|
||||||
|
def notify_property(prop):
|
||||||
|
if self.get_property(prop) is not None:
|
||||||
|
self.notifier(prop)
|
||||||
|
|
||||||
|
for prop in [
|
||||||
|
"metadata",
|
||||||
|
"title",
|
||||||
|
"artist",
|
||||||
|
"arturl",
|
||||||
|
"length",
|
||||||
|
]:
|
||||||
|
GLib.idle_add(lambda p=prop: (notify_property(p), False))
|
||||||
|
for prop in [
|
||||||
|
"can-seek",
|
||||||
|
"can-pause",
|
||||||
|
"can-shuffle",
|
||||||
|
"can-go-next",
|
||||||
|
"can-go-previous",
|
||||||
|
]:
|
||||||
|
GLib.idle_add(lambda p=prop: (self.notifier(p), False))
|
||||||
|
|
||||||
|
def update_status_once(self):
|
||||||
|
# schedule notifier calls for each property
|
||||||
|
def notify_all():
|
||||||
|
for prop in self.list_properties(): # type: ignore
|
||||||
|
self.notifier(prop.name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
GLib.idle_add(notify_all, priority=GLib.PRIORITY_DEFAULT_IDLE)
|
||||||
|
|
||||||
|
def notifier(self, name: str, args=None):
|
||||||
|
def notify_and_emit():
|
||||||
|
self.notify(name)
|
||||||
|
self.emit("changed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
GLib.idle_add(notify_and_emit, priority=GLib.PRIORITY_DEFAULT_IDLE)
|
||||||
|
|
||||||
|
def on_player_exit(self, player):
|
||||||
|
for id in list(self._signal_connectors.values()):
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
self._player.disconnect(id)
|
||||||
|
del self._signal_connectors
|
||||||
|
GLib.idle_add(lambda: (self.emit("exit", True), False))
|
||||||
|
del self._player
|
||||||
|
|
||||||
|
def toggle_shuffle(self):
|
||||||
|
if self.can_shuffle:
|
||||||
|
# schedule the shuffle toggle in the GLib idle loop
|
||||||
|
GLib.idle_add(lambda: (setattr(self, "shuffle", not self.shuffle), False))
|
||||||
|
# else do nothing
|
||||||
|
|
||||||
|
def play_pause(self):
|
||||||
|
if self.can_pause:
|
||||||
|
GLib.idle_add(lambda: (self._player.play_pause(), False))
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self.can_go_next:
|
||||||
|
GLib.idle_add(lambda: (self._player.next(), False))
|
||||||
|
|
||||||
|
def previous(self):
|
||||||
|
if self.can_go_previous:
|
||||||
|
GLib.idle_add(lambda: (self._player.previous(), False))
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
@Property(str, "readable")
|
||||||
|
def player_name(self) -> int:
|
||||||
|
return self._player.get_property("player-name") # type: ignore
|
||||||
|
|
||||||
|
@Property(int, "read-write", default_value=0)
|
||||||
|
def position(self) -> int:
|
||||||
|
return self._player.get_property("position") # type: ignore
|
||||||
|
|
||||||
|
@position.setter
|
||||||
|
def position(self, new_pos: int):
|
||||||
|
self._player.set_position(new_pos)
|
||||||
|
|
||||||
|
@Property(object, "readable")
|
||||||
|
def metadata(self) -> dict:
|
||||||
|
return self._player.get_property("metadata") # type: ignore
|
||||||
|
|
||||||
|
@Property(str or None, "readable")
|
||||||
|
def arturl(self) -> str | None:
|
||||||
|
if "mpris:artUrl" in self.metadata.keys(): # type: ignore # noqa: SIM118
|
||||||
|
return self.metadata["mpris:artUrl"] # type: ignore
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Property(str or None, "readable")
|
||||||
|
def length(self) -> str | None:
|
||||||
|
if "mpris:length" in self.metadata.keys(): # type: ignore # noqa: SIM118
|
||||||
|
return self.metadata["mpris:length"] # type: ignore
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Property(str, "readable")
|
||||||
|
def artist(self) -> str:
|
||||||
|
artist = self._player.get_artist() # type: ignore
|
||||||
|
if isinstance(artist, (list, tuple)):
|
||||||
|
return ", ".join(artist)
|
||||||
|
return artist
|
||||||
|
|
||||||
|
@Property(str, "readable")
|
||||||
|
def album(self) -> str:
|
||||||
|
return self._player.get_album() # type: ignore
|
||||||
|
|
||||||
|
@Property(str, "readable")
|
||||||
|
def title(self):
|
||||||
|
return self._player.get_title()
|
||||||
|
|
||||||
|
@Property(bool, "read-write", default_value=False)
|
||||||
|
def shuffle(self) -> bool:
|
||||||
|
return self._player.get_property("shuffle") # type: ignore
|
||||||
|
|
||||||
|
@shuffle.setter
|
||||||
|
def shuffle(self, do_shuffle: bool):
|
||||||
|
self.notifier("shuffle")
|
||||||
|
return self._player.set_shuffle(do_shuffle)
|
||||||
|
|
||||||
|
@Property(str, "readable")
|
||||||
|
def playback_status(self) -> str:
|
||||||
|
return {
|
||||||
|
Playerctl.PlaybackStatus.PAUSED: "paused",
|
||||||
|
Playerctl.PlaybackStatus.PLAYING: "playing",
|
||||||
|
Playerctl.PlaybackStatus.STOPPED: "stopped",
|
||||||
|
}.get(self._player.get_property("playback_status"), "unknown") # type: ignore
|
||||||
|
|
||||||
|
@Property(str, "read-write")
|
||||||
|
def loop_status(self) -> str:
|
||||||
|
return {
|
||||||
|
Playerctl.LoopStatus.NONE: "none",
|
||||||
|
Playerctl.LoopStatus.TRACK: "track",
|
||||||
|
Playerctl.LoopStatus.PLAYLIST: "playlist",
|
||||||
|
}.get(self._player.get_property("loop_status"), "unknown") # type: ignore
|
||||||
|
|
||||||
|
@loop_status.setter
|
||||||
|
def loop_status(self, status: str):
|
||||||
|
loop_status = {
|
||||||
|
"none": Playerctl.LoopStatus.NONE,
|
||||||
|
"track": Playerctl.LoopStatus.TRACK,
|
||||||
|
"playlist": Playerctl.LoopStatus.PLAYLIST,
|
||||||
|
}.get(status)
|
||||||
|
self._player.set_loop_status(loop_status) if loop_status else None
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_go_next(self) -> bool:
|
||||||
|
return self._player.get_property("can_go_next") # type: ignore
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_go_previous(self) -> bool:
|
||||||
|
return self._player.get_property("can_go_previous") # type: ignore
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_seek(self) -> bool:
|
||||||
|
return self._player.get_property("can_seek") # type: ignore
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_pause(self) -> bool:
|
||||||
|
return self._player.get_property("can_pause") # type: ignore
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_shuffle(self) -> bool:
|
||||||
|
try:
|
||||||
|
self._player.set_shuffle(self._player.get_property("shuffle"))
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@Property(bool, "readable", default_value=False)
|
||||||
|
def can_loop(self) -> bool:
|
||||||
|
try:
|
||||||
|
self._player.set_shuffle(self._player.get_property("shuffle"))
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class MprisPlayerManager(Service):
|
||||||
|
"""A service to manage mpris players."""
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def player_appeared(self, player: Playerctl.Player) -> Playerctl.Player: ...
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def player_vanished(self, player_name: str) -> str: ...
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self._manager = Playerctl.PlayerManager.new()
|
||||||
|
bulk_connect(
|
||||||
|
self._manager,
|
||||||
|
{
|
||||||
|
"name-appeared": self.on_name_appeard,
|
||||||
|
"name-vanished": self.on_name_vanished,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.add_players()
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def on_name_appeard(self, manager, player_name: Playerctl.PlayerName):
|
||||||
|
logger.info(f"[MprisPlayer] {player_name.name} appeared")
|
||||||
|
new_player = Playerctl.Player.new_from_name(player_name)
|
||||||
|
manager.manage_player(new_player)
|
||||||
|
self.emit("player-appeared", new_player) # type: ignore
|
||||||
|
|
||||||
|
def on_name_vanished(self, manager, player_name: Playerctl.PlayerName):
|
||||||
|
logger.info(f"[MprisPlayer] {player_name.name} vanished")
|
||||||
|
self.emit("player-vanished", player_name.name) # type: ignore
|
||||||
|
|
||||||
|
def add_players(self):
|
||||||
|
for player in self._manager.get_property("player-names"): # type: ignore
|
||||||
|
self._manager.manage_player(Playerctl.Player.new_from_name(player)) # type: ignore
|
||||||
|
|
||||||
|
@Property(object, "readable")
|
||||||
|
def players(self):
|
||||||
|
return self._manager.get_property("players") # type: ignore
|
||||||
3
bar/services/river/__init__.py
Normal file
3
bar/services/river/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .service import River, RiverEvent
|
||||||
|
|
||||||
|
__all__ = ["River", "RiverEvent"]
|
||||||
0
bar/services/river/protocols/__init__.py
Normal file
0
bar/services/river/protocols/__init__.py
Normal file
0
bar/services/river/protocols/generated/__init__.py
Normal file
0
bar/services/river/protocols/generated/__init__.py
Normal file
@@ -16,14 +16,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||||
Argument,
|
Proxy, Resource)
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverCommandCallbackV1(Interface):
|
class ZriverCommandCallbackV1(Interface):
|
||||||
@@ -25,7 +25,7 @@ from pywayland.protocol_core import (
|
|||||||
Resource,
|
Resource,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..wayland import WlSeat
|
from pywayland.protocol.wayland import WlSeat
|
||||||
from .zriver_command_callback_v1 import ZriverCommandCallbackV1
|
from .zriver_command_callback_v1 import ZriverCommandCallbackV1
|
||||||
|
|
||||||
|
|
||||||
@@ -16,14 +16,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||||
Argument,
|
Proxy, Resource)
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverOutputStatusV1(Interface):
|
class ZriverOutputStatusV1(Interface):
|
||||||
@@ -16,16 +16,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
from pywayland.protocol.wayland import WlOutput
|
||||||
Argument,
|
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||||
ArgumentType,
|
Proxy, Resource)
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..wayland import WlOutput
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverSeatStatusV1(Interface):
|
class ZriverSeatStatusV1(Interface):
|
||||||
@@ -16,17 +16,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||||
Argument,
|
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||||
ArgumentType,
|
Proxy, Resource)
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..wayland import WlOutput
|
|
||||||
from ..wayland import WlSeat
|
|
||||||
from .zriver_output_status_v1 import ZriverOutputStatusV1
|
from .zriver_output_status_v1 import ZriverOutputStatusV1
|
||||||
from .zriver_seat_status_v1 import ZriverSeatStatusV1
|
from .zriver_seat_status_v1 import ZriverSeatStatusV1
|
||||||
|
|
||||||
352
bar/services/river/service.py
Normal file
352
bar/services/river/service.py
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
|
from fabric.core.service import Property, Service, Signal
|
||||||
|
from fabric.utils.helpers import idle_add
|
||||||
|
from gi.repository import GLib
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Import pywayland components - ensure these imports are correct
|
||||||
|
from pywayland.client import Display
|
||||||
|
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||||
|
|
||||||
|
from .protocols.generated.river_control_unstable_v1 import ZriverControlV1
|
||||||
|
from .protocols.generated.river_status_unstable_v1 import ZriverStatusManagerV1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OutputInfo:
|
||||||
|
"""Information about a River output"""
|
||||||
|
|
||||||
|
name: int
|
||||||
|
output: WlOutput
|
||||||
|
status: Any = None # ZriverOutputStatusV1
|
||||||
|
tags_view: List[int] = field(default_factory=list)
|
||||||
|
tags_focused: List[int] = field(default_factory=list)
|
||||||
|
tags_urgent: List[int] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RiverEvent:
|
||||||
|
"""Event data from River compositor"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
data: List[Any]
|
||||||
|
output_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class River(Service):
|
||||||
|
"""Connection to River Wayland compositor via river-status protocol"""
|
||||||
|
|
||||||
|
@Property(bool, "readable", "is-ready", default_value=False)
|
||||||
|
def ready(self) -> bool:
|
||||||
|
return self._ready
|
||||||
|
|
||||||
|
@Property(str, "readable", "active-window", default_value="")
|
||||||
|
def active_window(self) -> str:
|
||||||
|
"""Get the title of the currently active window"""
|
||||||
|
return self._active_window_title
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def ready_signal(self):
|
||||||
|
return self.notify("ready")
|
||||||
|
|
||||||
|
@Signal("event", flags="detailed")
|
||||||
|
def event(self, event: object): ...
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Initialize the River service"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._ready = False
|
||||||
|
self._active_window_title = ""
|
||||||
|
self.outputs: Dict[int, OutputInfo] = {}
|
||||||
|
self._display = None
|
||||||
|
self.river_status_mgr = None
|
||||||
|
self.river_control = None
|
||||||
|
self.seat = None
|
||||||
|
self.seat_status = None
|
||||||
|
|
||||||
|
# Start the connection in a separate thread
|
||||||
|
self.river_thread = GLib.Thread.new(
|
||||||
|
"river-status-service", self._river_connection_task
|
||||||
|
)
|
||||||
|
|
||||||
|
def _river_connection_task(self):
|
||||||
|
"""Main thread that connects to River and listens for events"""
|
||||||
|
try:
|
||||||
|
logger.info("[RiverService] Starting connection to River")
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] XDG_RUNTIME_DIR={os.environ.get('XDG_RUNTIME_DIR', 'Not set')}"
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] WAYLAND_DISPLAY={os.environ.get('WAYLAND_DISPLAY', 'Not set')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._display = Display()
|
||||||
|
self._display.connect()
|
||||||
|
|
||||||
|
# Get the registry
|
||||||
|
registry = self._display.get_registry()
|
||||||
|
logger.debug("[RiverService] Registry obtained")
|
||||||
|
|
||||||
|
# Create state object to hold our data
|
||||||
|
state = {
|
||||||
|
"display": self._display,
|
||||||
|
"registry": registry,
|
||||||
|
"outputs": {},
|
||||||
|
"river_status_mgr": None,
|
||||||
|
"river_control": None,
|
||||||
|
"seat": None,
|
||||||
|
"seat_status": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_global(registry, name, iface, version):
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] Global: {iface} (v{version}, name={name})"
|
||||||
|
)
|
||||||
|
if iface == "zriver_status_manager_v1":
|
||||||
|
state["river_status_mgr"] = registry.bind(
|
||||||
|
name, ZriverStatusManagerV1, version
|
||||||
|
)
|
||||||
|
logger.info("[RiverService] Found river status manager")
|
||||||
|
elif iface == "zriver_control_v1":
|
||||||
|
state["river_control"] = registry.bind(
|
||||||
|
name, ZriverControlV1, version
|
||||||
|
)
|
||||||
|
logger.info("[RiverService] Found river control interface")
|
||||||
|
elif iface == "wl_output":
|
||||||
|
output = registry.bind(name, WlOutput, version)
|
||||||
|
state["outputs"][name] = OutputInfo(name=name, output=output)
|
||||||
|
logger.info(f"[RiverService] Found output {name}")
|
||||||
|
elif iface == "wl_seat":
|
||||||
|
state["seat"] = registry.bind(name, WlSeat, version)
|
||||||
|
logger.info("[RiverService] Found seat")
|
||||||
|
|
||||||
|
def handle_global_remove(registry, name):
|
||||||
|
if name in state["outputs"]:
|
||||||
|
logger.info(f"[RiverService] Output {name} removed")
|
||||||
|
del state["outputs"][name]
|
||||||
|
idle_add(
|
||||||
|
lambda: self.emit(
|
||||||
|
"event::output_removed",
|
||||||
|
RiverEvent("output_removed", [name]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up the dispatchers
|
||||||
|
registry.dispatcher["global"] = handle_global
|
||||||
|
registry.dispatcher["global_remove"] = handle_global_remove
|
||||||
|
|
||||||
|
# Discover globals
|
||||||
|
logger.debug("[RiverService] Performing initial roundtrip")
|
||||||
|
self._display.roundtrip()
|
||||||
|
|
||||||
|
# Check if we found the river status manager
|
||||||
|
if not state["river_status_mgr"]:
|
||||||
|
logger.error("[RiverService] River status manager not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle the window title updates through seat status
|
||||||
|
|
||||||
|
if not state["river_control"]:
|
||||||
|
logger.error(
|
||||||
|
"[RiverService] River control interface not found - falling back to riverctl"
|
||||||
|
)
|
||||||
|
# You could still fall back to the old riverctl method here if needed
|
||||||
|
|
||||||
|
def focused_view_handler(_, title):
|
||||||
|
logger.debug(f"[RiverService] Focused view title: {title}")
|
||||||
|
self._active_window_title = title
|
||||||
|
idle_add(lambda: self._emit_active_window(title))
|
||||||
|
|
||||||
|
# Get the seat status to track active window
|
||||||
|
|
||||||
|
if state["seat"]:
|
||||||
|
seat_status = state["river_status_mgr"].get_river_seat_status(
|
||||||
|
state["seat"]
|
||||||
|
)
|
||||||
|
seat_status.dispatcher["focused_view"] = focused_view_handler
|
||||||
|
state["seat_status"] = seat_status
|
||||||
|
logger.info("[RiverService] Set up seat status for window tracking")
|
||||||
|
|
||||||
|
# Create view tags and focused tags handlers
|
||||||
|
def make_view_tags_handler(output_id):
|
||||||
|
def handler(_, tags):
|
||||||
|
decoded = self._decode_bitfields(tags)
|
||||||
|
state["outputs"][output_id].tags_view = decoded
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] Output {output_id} view tags: {decoded}"
|
||||||
|
)
|
||||||
|
idle_add(lambda: self._emit_view_tags(output_id, decoded))
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def make_focused_tags_handler(output_id):
|
||||||
|
def handler(_, tags):
|
||||||
|
decoded = self._decode_bitfields(tags)
|
||||||
|
state["outputs"][output_id].tags_focused = decoded
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] Output {output_id} focused tags: {decoded}"
|
||||||
|
)
|
||||||
|
idle_add(lambda: self._emit_focused_tags(output_id, decoded))
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def make_urgent_tags_handler(output_id):
|
||||||
|
def handler(_, tags):
|
||||||
|
decoded = self._decode_bitfields(tags)
|
||||||
|
state["outputs"][output_id].tags_urgent = decoded
|
||||||
|
logger.debug(
|
||||||
|
f"[RiverService] Output {output_id} urgent tags: {decoded}"
|
||||||
|
)
|
||||||
|
idle_add(lambda: self._emit_urgent_tags(output_id, decoded))
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
# Bind output status listeners
|
||||||
|
for name, info in list(state["outputs"].items()):
|
||||||
|
status = state["river_status_mgr"].get_river_output_status(info.output)
|
||||||
|
status.dispatcher["view_tags"] = make_view_tags_handler(name)
|
||||||
|
status.dispatcher["focused_tags"] = make_focused_tags_handler(name)
|
||||||
|
status.dispatcher["urgent_tags"] = make_urgent_tags_handler(name)
|
||||||
|
info.status = status
|
||||||
|
logger.info(f"[RiverService] Set up status for output {name}")
|
||||||
|
|
||||||
|
# Initial data fetch
|
||||||
|
logger.debug("[RiverService] Performing second roundtrip")
|
||||||
|
self._display.roundtrip()
|
||||||
|
|
||||||
|
# Update our outputs dictionary
|
||||||
|
self.outputs.update(state["outputs"])
|
||||||
|
self.river_status_mgr = state["river_status_mgr"]
|
||||||
|
self.river_control = state["river_control"]
|
||||||
|
self.seat = state["seat"]
|
||||||
|
self.seat_status = state.get("seat_status")
|
||||||
|
|
||||||
|
# Mark service as ready
|
||||||
|
idle_add(self._set_ready)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self._display.dispatch(block=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[RiverService] Error in River connection: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _set_ready(self):
|
||||||
|
"""Set the service as ready (called on main thread via idle_add)"""
|
||||||
|
self._ready = True
|
||||||
|
logger.info("[RiverService] Service ready")
|
||||||
|
self.ready_signal.emit()
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
def _emit_view_tags(self, output_id, tags):
|
||||||
|
"""Emit view_tags events (called on main thread)"""
|
||||||
|
event = RiverEvent("view_tags", tags, output_id)
|
||||||
|
self.emit("event::view_tags", event)
|
||||||
|
self.emit(f"event::view_tags::{output_id}", tags)
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
def _emit_focused_tags(self, output_id, tags):
|
||||||
|
"""Emit focused_tags events (called on main thread)"""
|
||||||
|
event = RiverEvent("focused_tags", tags, output_id)
|
||||||
|
self.emit("event::focused_tags", event)
|
||||||
|
self.emit(f"event::focused_tags::{output_id}", tags)
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
def _emit_active_window(self, title):
|
||||||
|
"""Emit active window title events (called on main thread)"""
|
||||||
|
event = RiverEvent("active_window", [title])
|
||||||
|
self.emit("event::active_window", event)
|
||||||
|
self.notify("active-window")
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
def _emit_urgent_tags(self, output_id, tags):
|
||||||
|
"""Emit urgent_tags events (called on main thread)"""
|
||||||
|
event = RiverEvent("urgent_tags", tags, output_id)
|
||||||
|
self.emit("event::urgent_tags", event)
|
||||||
|
self.emit(f"event::urgent_tags::{output_id}", tags)
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _decode_bitfields(bitfields) -> List[int]:
|
||||||
|
"""Decode River's tag bitfields into a list of tag indices"""
|
||||||
|
tags: Set[int] = set()
|
||||||
|
|
||||||
|
# Ensure we have an iterable
|
||||||
|
if not hasattr(bitfields, "__iter__"):
|
||||||
|
bitfields = [bitfields]
|
||||||
|
|
||||||
|
for bits in bitfields:
|
||||||
|
for i in range(32):
|
||||||
|
if bits & (1 << i):
|
||||||
|
tags.add(i)
|
||||||
|
|
||||||
|
return sorted(tags)
|
||||||
|
|
||||||
|
def run_command(self, command, *args, callback=None):
|
||||||
|
"""Run a riverctl command"""
|
||||||
|
if not self.river_control or not self.seat:
|
||||||
|
logger.warning(
|
||||||
|
"[RiverService] River control or seat not available, falling back to riverctl"
|
||||||
|
)
|
||||||
|
return self._run_command_fallback(command, *args)
|
||||||
|
|
||||||
|
self.river_control.add_argument(command)
|
||||||
|
for arg in args:
|
||||||
|
self.river_control.add_argument(str(arg))
|
||||||
|
|
||||||
|
# Execute the command
|
||||||
|
command_callback = self.river_control.run_command(self.seat)
|
||||||
|
|
||||||
|
# Set up callback handlers
|
||||||
|
result = {"stdout": None, "stderr": None, "success": None}
|
||||||
|
|
||||||
|
def handle_success(_, output):
|
||||||
|
logger.debug(f"[RiverService] Command success: {output}")
|
||||||
|
result["stdout"] = output
|
||||||
|
result["success"] = True
|
||||||
|
if callback:
|
||||||
|
idle_add(lambda: callback(True, output, None))
|
||||||
|
|
||||||
|
def handle_failure(_, failure_message):
|
||||||
|
logger.debug(f"[RiverService] Command failure: {failure_message}")
|
||||||
|
result["stderr"] = failure_message
|
||||||
|
result["success"] = False
|
||||||
|
if callback:
|
||||||
|
idle_add(lambda: callback(False, None, failure_message))
|
||||||
|
|
||||||
|
command_callback.dispatcher["success"] = handle_success
|
||||||
|
command_callback.dispatcher["failure"] = handle_failure
|
||||||
|
|
||||||
|
if hasattr(self, "_display"):
|
||||||
|
self._display.flush()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _run_command_fallback(self, command, *args):
|
||||||
|
"""Fallback to riverctl"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
cmd = ["riverctl", command] + [str(arg) for arg in args]
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
logger.info(f"[RiverService] Ran command: {' '.join(cmd)}")
|
||||||
|
return result.stdout.strip()
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(
|
||||||
|
f"[RiverService] Command failed: {' '.join(cmd)}, error: {e.stderr}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def toggle_focused_tag(self, tag, callback=None):
|
||||||
|
"""Toggle a tag in the focused tags"""
|
||||||
|
tag_mask = 1 << int(tag)
|
||||||
|
self.run_command("set-focused-tags", str(tag_mask), callback=callback)
|
||||||
266
bar/services/river/widgets.py
Normal file
266
bar/services/river/widgets.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
from fabric.core.service import Property
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.button import Button
|
||||||
|
from fabric.widgets.eventbox import EventBox
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from gi.repository import Gdk
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .service import River
|
||||||
|
|
||||||
|
connection: River | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_river_connection() -> River:
|
||||||
|
global connection
|
||||||
|
if not connection:
|
||||||
|
connection = River()
|
||||||
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
class RiverWorkspaceButton(Button):
|
||||||
|
@Property(int, "readable")
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@Property(bool, "read-write", default_value=False)
|
||||||
|
def active(self) -> bool:
|
||||||
|
return self._active
|
||||||
|
|
||||||
|
@active.setter
|
||||||
|
def active(self, value: bool):
|
||||||
|
self._active = value
|
||||||
|
(self.remove_style_class if not value else self.add_style_class)("active")
|
||||||
|
|
||||||
|
@Property(bool, "read-write", default_value=False)
|
||||||
|
def empty(self) -> bool:
|
||||||
|
return self._empty
|
||||||
|
|
||||||
|
@empty.setter
|
||||||
|
def empty(self, value: bool):
|
||||||
|
self._empty = value
|
||||||
|
(self.remove_style_class if not value else self.add_style_class)("empty")
|
||||||
|
|
||||||
|
@Property(bool, "read-write", default_value=False)
|
||||||
|
def urgent(self) -> bool:
|
||||||
|
return self._urgent
|
||||||
|
|
||||||
|
@urgent.setter
|
||||||
|
def urgent(self, value: bool):
|
||||||
|
self._urgent = value
|
||||||
|
self._update_style()
|
||||||
|
|
||||||
|
def __init__(self, id: int, label: str = None, **kwargs):
|
||||||
|
super().__init__(label or str(id), **kwargs)
|
||||||
|
self._id = id
|
||||||
|
self._active = False
|
||||||
|
self._empty = True
|
||||||
|
self._urgent = False
|
||||||
|
|
||||||
|
def _update_style(self):
|
||||||
|
"""Update button styles based on states"""
|
||||||
|
# Remove all state-related styles first
|
||||||
|
self.remove_style_class("active")
|
||||||
|
self.remove_style_class("empty")
|
||||||
|
self.remove_style_class("urgent")
|
||||||
|
|
||||||
|
# Then apply current states
|
||||||
|
if self._active:
|
||||||
|
self.add_style_class("active")
|
||||||
|
if self._empty:
|
||||||
|
self.add_style_class("empty")
|
||||||
|
if self._urgent:
|
||||||
|
self.add_style_class("urgent")
|
||||||
|
|
||||||
|
|
||||||
|
class RiverWorkspaces(EventBox):
|
||||||
|
def __init__(self, output_id, river_service=None, max_tags=9, **kwargs):
|
||||||
|
super().__init__(events="scroll")
|
||||||
|
self._box = Box(**kwargs)
|
||||||
|
self.children = self._box
|
||||||
|
|
||||||
|
if river_service:
|
||||||
|
self.river = river_service
|
||||||
|
|
||||||
|
# Store output_id as received
|
||||||
|
self.output_id = output_id
|
||||||
|
|
||||||
|
self.max_tags = max_tags
|
||||||
|
# Create buttons for tags 0 to max_tags-1 (to match River's 0-based tag indexing)
|
||||||
|
self._buttons = {i: RiverWorkspaceButton(i) for i in range(max_tags)}
|
||||||
|
|
||||||
|
for btn in self._buttons.values():
|
||||||
|
btn.connect("clicked", self.on_workspace_click)
|
||||||
|
self._box.add(btn)
|
||||||
|
|
||||||
|
# Connect to service events
|
||||||
|
self.river.connect("event::focused_tags", self.on_focus_change_general)
|
||||||
|
self.river.connect("event::view_tags", self.on_view_change_general)
|
||||||
|
self.river.connect("event::urgent_tags", self.on_urgent_change_general)
|
||||||
|
self.river.connect("event::output_removed", self.on_output_removed)
|
||||||
|
|
||||||
|
# Initial setup when service is ready
|
||||||
|
if self.river.ready:
|
||||||
|
self.on_ready(None)
|
||||||
|
else:
|
||||||
|
self.river.connect("event::ready", self.on_ready)
|
||||||
|
|
||||||
|
self.connect("scroll-event", self.on_scroll)
|
||||||
|
|
||||||
|
def on_ready(self, _):
|
||||||
|
"""Initialize widget state when service is ready"""
|
||||||
|
|
||||||
|
if self.output_id is None and self.river.outputs:
|
||||||
|
self.output_id = next(iter(self.river.outputs.keys()))
|
||||||
|
logger.info(f"[RiverWorkspaces] Selected output {self.output_id}")
|
||||||
|
|
||||||
|
if self.output_id is not None and self.output_id in self.river.outputs:
|
||||||
|
output_info = self.river.outputs[self.output_id]
|
||||||
|
|
||||||
|
focused_tags = output_info.tags_focused
|
||||||
|
view_tags = output_info.tags_view
|
||||||
|
urgent_tags = output_info.tags_urgent
|
||||||
|
|
||||||
|
for i, btn in self._buttons.items():
|
||||||
|
btn.active = i in focused_tags
|
||||||
|
btn.empty = i not in view_tags
|
||||||
|
btn.urgent = i in urgent_tags
|
||||||
|
|
||||||
|
def on_focus_change(self, _, tags):
|
||||||
|
"""Handle focused tags change for our specific output"""
|
||||||
|
logger.info(
|
||||||
|
f"[RiverWorkspaces] Focus change on output {self.output_id}: {tags}"
|
||||||
|
)
|
||||||
|
for i, btn in self._buttons.items():
|
||||||
|
btn.active = i in tags
|
||||||
|
|
||||||
|
def on_view_change(self, _, tags):
|
||||||
|
"""Handle view tags change for our specific output"""
|
||||||
|
logger.info(f"[RiverWorkspaces] View change on output {self.output_id}: {tags}")
|
||||||
|
for i, btn in self._buttons.items():
|
||||||
|
btn.empty = i not in tags
|
||||||
|
|
||||||
|
def on_focus_change_general(self, _, event):
|
||||||
|
"""Handle general focused tags event"""
|
||||||
|
# Only handle event if it's for our output
|
||||||
|
if event.output_id == self.output_id:
|
||||||
|
logger.info(
|
||||||
|
f"[RiverWorkspaces] General focus change for output {self.output_id}"
|
||||||
|
)
|
||||||
|
self.on_focus_change(_, event.data)
|
||||||
|
|
||||||
|
def on_view_change_general(self, _, event):
|
||||||
|
"""Handle general view tags event"""
|
||||||
|
# Only handle event if it's for our output
|
||||||
|
if event.output_id == self.output_id:
|
||||||
|
logger.info(
|
||||||
|
f"[RiverWorkspaces] General view change for output {self.output_id}"
|
||||||
|
)
|
||||||
|
self.on_view_change(_, event.data)
|
||||||
|
|
||||||
|
def on_urgent_change(self, _, tags):
|
||||||
|
"""Handle urgent tags change for our specific output"""
|
||||||
|
logger.info(
|
||||||
|
f"[RiverWorkspaces] Urgent change on output {self.output_id}: {tags}"
|
||||||
|
)
|
||||||
|
for i, btn in self._buttons.items():
|
||||||
|
btn.urgent = i in tags
|
||||||
|
|
||||||
|
def on_urgent_change_general(self, _, event):
|
||||||
|
"""Handle general urgent tags event"""
|
||||||
|
# Only handle event if it's for our output
|
||||||
|
if event.output_id == self.output_id:
|
||||||
|
logger.info(
|
||||||
|
f"[RiverWorkspaces] General urgent change for output {self.output_id}"
|
||||||
|
)
|
||||||
|
self.on_urgent_change(_, event.data)
|
||||||
|
|
||||||
|
def on_output_removed(self, _, event):
|
||||||
|
"""Handle output removal"""
|
||||||
|
removed_id = event.data[0]
|
||||||
|
|
||||||
|
if removed_id == self.output_id:
|
||||||
|
logger.info(f"[RiverWorkspaces] Our output {self.output_id} was removed")
|
||||||
|
|
||||||
|
# Try to find another output
|
||||||
|
if self.river.outputs:
|
||||||
|
self.output_id = next(iter(self.river.outputs.keys()))
|
||||||
|
logger.info(f"[RiverWorkspaces] Switching to output {self.output_id}")
|
||||||
|
|
||||||
|
# Update state for new output
|
||||||
|
if self.output_id in self.river.outputs:
|
||||||
|
output_info = self.river.outputs[self.output_id]
|
||||||
|
# Access fields directly on the OutputInfo dataclass
|
||||||
|
focused_tags = output_info.tags_focused
|
||||||
|
view_tags = output_info.tags_view
|
||||||
|
|
||||||
|
for i, btn in self._buttons.items():
|
||||||
|
btn.active = i in focused_tags
|
||||||
|
btn.empty = i not in view_tags
|
||||||
|
|
||||||
|
def on_workspace_click(self, btn):
|
||||||
|
"""Handle workspace button click"""
|
||||||
|
logger.info(f"[RiverWorkspaces] Clicked on workspace {btn.id}")
|
||||||
|
self.river.toggle_focused_tag(btn.id)
|
||||||
|
|
||||||
|
def on_scroll(self, _, event):
|
||||||
|
"""Handle scroll events"""
|
||||||
|
direction = event.direction
|
||||||
|
if direction == Gdk.ScrollDirection.DOWN:
|
||||||
|
logger.info("[RiverWorkspaces] Scroll down - focusing next view")
|
||||||
|
self.river.run_command("focus-view", "next")
|
||||||
|
elif direction == Gdk.ScrollDirection.UP:
|
||||||
|
logger.info("[RiverWorkspaces] Scroll up - focusing previous view")
|
||||||
|
self.river.run_command("focus-view", "previous")
|
||||||
|
|
||||||
|
|
||||||
|
class RiverActiveWindow(Label):
|
||||||
|
"""Widget to display the currently active window's title"""
|
||||||
|
|
||||||
|
def __init__(self, max_length=None, ellipsize="end", river_service=None, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
if river_service:
|
||||||
|
self.river = river_service
|
||||||
|
|
||||||
|
self.max_length = max_length
|
||||||
|
self.ellipsize = ellipsize
|
||||||
|
|
||||||
|
# Set initial state
|
||||||
|
if self.river.ready:
|
||||||
|
self.on_ready(None)
|
||||||
|
else:
|
||||||
|
self.river.connect("event::ready", self.on_ready)
|
||||||
|
|
||||||
|
# Connect to active window changes
|
||||||
|
self.river.connect("event::active_window", self.on_active_window_changed)
|
||||||
|
|
||||||
|
def on_ready(self, _):
|
||||||
|
"""Initialize widget when service is ready"""
|
||||||
|
logger.info("[RiverActiveWindow] Connected to service")
|
||||||
|
self.update_title(self.river.active_window)
|
||||||
|
|
||||||
|
def on_active_window_changed(self, _, event):
|
||||||
|
"""Update widget when active window changes"""
|
||||||
|
title = event.data[0] if event.data else ""
|
||||||
|
logger.debug(f"[RiverActiveWindow] Window changed to: {title}")
|
||||||
|
self.update_title(title)
|
||||||
|
|
||||||
|
def update_title(self, title):
|
||||||
|
"""Update the label with the window title"""
|
||||||
|
if not title:
|
||||||
|
self.label = ""
|
||||||
|
self.set_label(self.label)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.max_length and len(title) > self.max_length:
|
||||||
|
if self.ellipsize == "end":
|
||||||
|
title = title[: self.max_length] + "..."
|
||||||
|
elif self.ellipsize == "middle":
|
||||||
|
half = (self.max_length - 3) // 2
|
||||||
|
title = title[:half] + "..." + title[-half:]
|
||||||
|
elif self.ellipsize == "start":
|
||||||
|
title = "..." + title[-self.max_length :]
|
||||||
|
|
||||||
|
self.label = title
|
||||||
|
self.set_label(self.label)
|
||||||
21
bar/services/wlr/event_loop.py
Normal file
21
bar/services/wlr/event_loop.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from fabric.core.service import Service, Property
|
||||||
|
from pywayland.client import Display
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
|
||||||
|
class WaylandEventLoopService(Service):
|
||||||
|
@Property(object, "readable", "display")
|
||||||
|
def display_property(self):
|
||||||
|
return self._display
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._display = Display()
|
||||||
|
self._display.connect()
|
||||||
|
|
||||||
|
self.thread = GLib.Thread.new("wayland-loop", self._loop)
|
||||||
|
|
||||||
|
def _loop(self):
|
||||||
|
while True:
|
||||||
|
self._display.dispatch(block=True)
|
||||||
|
print("DISPATCHING...")
|
||||||
233
bar/services/wlr/protocol/windows.py
Normal file
233
bar/services/wlr/protocol/windows.py
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
import pywayland
|
||||||
|
from pywayland.client import Display
|
||||||
|
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||||
|
|
||||||
|
# Import the protocol interfaces from your files
|
||||||
|
from wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_manager_v1 import (
|
||||||
|
ZwlrForeignToplevelManagerV1,
|
||||||
|
)
|
||||||
|
from wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_handle_v1 import (
|
||||||
|
ZwlrForeignToplevelHandleV1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Window:
|
||||||
|
"""Represents a toplevel window in the compositor."""
|
||||||
|
|
||||||
|
def __init__(self, handle: ZwlrForeignToplevelHandleV1):
|
||||||
|
self.handle = handle
|
||||||
|
self.title: str = "Unknown"
|
||||||
|
self.app_id: str = "Unknown"
|
||||||
|
self.states: List[str] = []
|
||||||
|
self.outputs: List[WlOutput] = []
|
||||||
|
self.parent: Optional["Window"] = None
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
state_str = (
|
||||||
|
", ".join([ZwlrForeignToplevelHandleV1.state(s).name for s in self.states])
|
||||||
|
if self.states
|
||||||
|
else "normal"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"Window(title='{self.title}', app_id='{self.app_id}', state={state_str})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WaylandWindowManager:
|
||||||
|
"""Manages Wayland windows using the foreign toplevel protocol."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.display = Display()
|
||||||
|
self.windows: Dict[ZwlrForeignToplevelHandleV1, Window] = {}
|
||||||
|
self.manager = None
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
"""Connect to the Wayland display and bind to the toplevel manager."""
|
||||||
|
try:
|
||||||
|
self.display.connect()
|
||||||
|
print("Connected to Wayland display")
|
||||||
|
|
||||||
|
# Get the registry to find the foreign toplevel manager
|
||||||
|
registry = self.display.get_registry()
|
||||||
|
registry.dispatcher["global"] = self._registry_global_handler
|
||||||
|
|
||||||
|
# Roundtrip to process registry events
|
||||||
|
self.display.roundtrip()
|
||||||
|
|
||||||
|
if not self.manager:
|
||||||
|
print(
|
||||||
|
"Foreign toplevel manager not found. Is wlr-foreign-toplevel-management protocol supported?"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _registry_global_handler(self, registry, id, interface, version):
|
||||||
|
"""Handle registry global objects."""
|
||||||
|
if interface == ZwlrForeignToplevelManagerV1.name:
|
||||||
|
print(f"Found foreign toplevel manager (id={id}, version={version})")
|
||||||
|
self.manager = registry.bind(
|
||||||
|
id, ZwlrForeignToplevelManagerV1, min(version, 3)
|
||||||
|
)
|
||||||
|
self.manager.dispatcher["toplevel"] = self._handle_toplevel
|
||||||
|
self.manager.dispatcher["finished"] = self._handle_manager_finished
|
||||||
|
|
||||||
|
def _handle_toplevel(self, manager, toplevel):
|
||||||
|
"""Handle a new toplevel window."""
|
||||||
|
window = Window(toplevel)
|
||||||
|
self.windows[toplevel] = window
|
||||||
|
print(window)
|
||||||
|
|
||||||
|
# Setup event dispatchers for the toplevel
|
||||||
|
toplevel.dispatcher["title"] = self._handle_title
|
||||||
|
toplevel.dispatcher["app_id"] = self._handle_app_id
|
||||||
|
toplevel.dispatcher["state"] = self._handle_state
|
||||||
|
toplevel.dispatcher["done"] = self._handle_done
|
||||||
|
toplevel.dispatcher["closed"] = self._handle_closed
|
||||||
|
toplevel.dispatcher["output_enter"] = self._handle_output_enter
|
||||||
|
toplevel.dispatcher["output_leave"] = self._handle_output_leave
|
||||||
|
|
||||||
|
def _handle_title(self, toplevel, title):
|
||||||
|
"""Handle toplevel title changes."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.title = title
|
||||||
|
|
||||||
|
def _handle_app_id(self, toplevel, app_id):
|
||||||
|
"""Handle toplevel app_id changes."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.app_id = app_id
|
||||||
|
|
||||||
|
def _handle_state(self, toplevel, states):
|
||||||
|
"""Handle toplevel state changes."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.states = states
|
||||||
|
|
||||||
|
def _handle_done(self, toplevel):
|
||||||
|
"""Handle toplevel done event."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window and not window.closed:
|
||||||
|
print(f"Window updated: {window}")
|
||||||
|
|
||||||
|
def _handle_closed(self, toplevel):
|
||||||
|
"""Handle toplevel closed event."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.closed = True
|
||||||
|
print(f"Window closed: {window}")
|
||||||
|
# Clean up the toplevel object
|
||||||
|
toplevel.destroy()
|
||||||
|
# Remove from our dictionary
|
||||||
|
del self.windows[toplevel]
|
||||||
|
|
||||||
|
def _handle_output_enter(self, toplevel, output):
|
||||||
|
"""Handle toplevel entering an output."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window and output not in window.outputs:
|
||||||
|
window.outputs.append(output)
|
||||||
|
|
||||||
|
def _handle_output_leave(self, toplevel, output):
|
||||||
|
"""Handle toplevel leaving an output."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window and output in window.outputs:
|
||||||
|
window.outputs.remove(output)
|
||||||
|
|
||||||
|
def _handle_parent(self, toplevel, parent):
|
||||||
|
"""Handle toplevel parent changes."""
|
||||||
|
window = self.windows.get(toplevel)
|
||||||
|
if window:
|
||||||
|
if parent is None:
|
||||||
|
window.parent = None
|
||||||
|
else:
|
||||||
|
parent_window = self.windows.get(parent)
|
||||||
|
if parent_window:
|
||||||
|
window.parent = parent_window
|
||||||
|
|
||||||
|
def _handle_manager_finished(self, manager):
|
||||||
|
"""Handle manager finished event."""
|
||||||
|
print("Foreign toplevel manager finished")
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def get_windows(self) -> List[Window]:
|
||||||
|
"""Get all currently active windows."""
|
||||||
|
# Filter out closed windows
|
||||||
|
active_windows = [
|
||||||
|
window for window in self.windows.values() if not window.closed
|
||||||
|
]
|
||||||
|
return active_windows
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run the event loop to receive window updates."""
|
||||||
|
self.running = True
|
||||||
|
print("Listening for window events (press Ctrl+C to exit)...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while self.running:
|
||||||
|
self.display.dispatch(block=True)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nExiting...")
|
||||||
|
finally:
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Clean up resources."""
|
||||||
|
print("cleanup")
|
||||||
|
if self.manager:
|
||||||
|
self.manager.stop()
|
||||||
|
|
||||||
|
# Destroy all toplevel handles
|
||||||
|
for toplevel, window in list(self.windows.items()):
|
||||||
|
if not window.closed:
|
||||||
|
toplevel.destroy()
|
||||||
|
|
||||||
|
# Disconnect from display
|
||||||
|
if self.display:
|
||||||
|
self.display.disconnect()
|
||||||
|
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
manager = WaylandWindowManager()
|
||||||
|
|
||||||
|
if not manager.connect():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# # Run for a short time to collect initial windows
|
||||||
|
for _ in range(1):
|
||||||
|
manager.display.dispatch(block=True)
|
||||||
|
|
||||||
|
# Print all windows
|
||||||
|
windows = manager.get_windows()
|
||||||
|
print("\nActive windows:")
|
||||||
|
if windows:
|
||||||
|
for i, window in enumerate(windows, 1):
|
||||||
|
print(f"{i}. {window}")
|
||||||
|
else:
|
||||||
|
print("No windows found")
|
||||||
|
|
||||||
|
# # Option to keep monitoring window events
|
||||||
|
# if len(sys.argv) > 1 and sys.argv[1] == "--monitor":
|
||||||
|
# manager.run()
|
||||||
|
# else:
|
||||||
|
manager.cleanup()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2018 Ilia Bozhinov
|
||||||
|
|
||||||
|
Permission to use, copy, modify, distribute, and sell this
|
||||||
|
software and its documentation for any purpose is hereby granted
|
||||||
|
without fee, provided that the above copyright notice appear in
|
||||||
|
all copies and that both that copyright notice and this permission
|
||||||
|
notice appear in supporting documentation, and that the name of
|
||||||
|
the copyright holders not be used in advertising or publicity
|
||||||
|
pertaining to distribution of the software without specific,
|
||||||
|
written prior permission. The copyright holders make no
|
||||||
|
representations about the suitability of this software for any
|
||||||
|
purpose. It is provided "as is" without express or implied
|
||||||
|
warranty.
|
||||||
|
|
||||||
|
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
|
||||||
|
<description summary="list and control opened apps">
|
||||||
|
The purpose of this protocol is to enable the creation of taskbars
|
||||||
|
and docks by providing them with a list of opened applications and
|
||||||
|
letting them request certain actions on them, like maximizing, etc.
|
||||||
|
|
||||||
|
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
|
||||||
|
toplevel window will be sent via the toplevel event
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="toplevel">
|
||||||
|
<description summary="a toplevel has been created">
|
||||||
|
This event is emitted whenever a new toplevel window is created. It
|
||||||
|
is emitted for all toplevels, regardless of the app that has created
|
||||||
|
them.
|
||||||
|
|
||||||
|
All initial details of the toplevel(title, app_id, states, etc.) will
|
||||||
|
be sent immediately after this event via the corresponding events in
|
||||||
|
zwlr_foreign_toplevel_handle_v1.
|
||||||
|
</description>
|
||||||
|
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="stop">
|
||||||
|
<description summary="stop sending events">
|
||||||
|
Indicates the client no longer wishes to receive events for new toplevels.
|
||||||
|
However the compositor may emit further toplevel_created events, until
|
||||||
|
the finished event is emitted.
|
||||||
|
|
||||||
|
The client must not send any more requests after this one.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="finished" type="destructor">
|
||||||
|
<description summary="the compositor has finished with the toplevel manager">
|
||||||
|
This event indicates that the compositor is done sending events to the
|
||||||
|
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
|
||||||
|
immediately after sending this request, so it will become invalid and
|
||||||
|
the client should free any resources associated with it.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
|
||||||
|
<description summary="an opened toplevel">
|
||||||
|
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
|
||||||
|
window. Each app may have multiple opened toplevels.
|
||||||
|
|
||||||
|
Each toplevel has a list of outputs it is visible on, conveyed to the
|
||||||
|
client with the output_enter and output_leave events.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="title">
|
||||||
|
<description summary="title change">
|
||||||
|
This event is emitted whenever the title of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="title" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="app_id">
|
||||||
|
<description summary="app-id change">
|
||||||
|
This event is emitted whenever the app-id of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="app_id" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_enter">
|
||||||
|
<description summary="toplevel entered an output">
|
||||||
|
This event is emitted whenever the toplevel becomes visible on
|
||||||
|
the given output. A toplevel may be visible on multiple outputs.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_leave">
|
||||||
|
<description summary="toplevel left an output">
|
||||||
|
This event is emitted whenever the toplevel stops being visible on
|
||||||
|
the given output. It is guaranteed that an entered-output event
|
||||||
|
with the same output has been emitted before this event.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="set_maximized">
|
||||||
|
<description summary="requests that the toplevel be maximized">
|
||||||
|
Requests that the toplevel be maximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_maximized">
|
||||||
|
<description summary="requests that the toplevel be unmaximized">
|
||||||
|
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_minimized">
|
||||||
|
<description summary="requests that the toplevel be minimized">
|
||||||
|
Requests that the toplevel be minimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_minimized">
|
||||||
|
<description summary="requests that the toplevel be unminimized">
|
||||||
|
Requests that the toplevel be unminimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="activate">
|
||||||
|
<description summary="activate the toplevel">
|
||||||
|
Request that this toplevel be activated on the given seat.
|
||||||
|
There is no guarantee the toplevel will be actually activated.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="state">
|
||||||
|
<description summary="types of states on the toplevel">
|
||||||
|
The different states that a toplevel can have. These have the same meaning
|
||||||
|
as the states with the same names defined in xdg-toplevel
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||||
|
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||||
|
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||||
|
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="state">
|
||||||
|
<description summary="the toplevel state changed">
|
||||||
|
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
|
||||||
|
is created and each time the toplevel state changes, either because of a
|
||||||
|
compositor action or because of a request in this protocol.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="state" type="array"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="done">
|
||||||
|
<description summary="all information about the toplevel has been sent">
|
||||||
|
This event is sent after all changes in the toplevel state have been
|
||||||
|
sent.
|
||||||
|
|
||||||
|
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
|
||||||
|
to be seen as atomic, even if they happen via multiple events.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="close">
|
||||||
|
<description summary="request that the toplevel be closed">
|
||||||
|
Send a request to the toplevel to close itself. The compositor would
|
||||||
|
typically use a shell-specific method to carry out this request, for
|
||||||
|
example by sending the xdg_toplevel.close event. However, this gives
|
||||||
|
no guarantees the toplevel will actually be destroyed. If and when
|
||||||
|
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
|
||||||
|
be emitted.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_rectangle">
|
||||||
|
<description summary="the rectangle which represents the toplevel">
|
||||||
|
The rectangle of the surface specified in this request corresponds to
|
||||||
|
the place where the app using this protocol represents the given toplevel.
|
||||||
|
It can be used by the compositor as a hint for some operations, e.g
|
||||||
|
minimizing. The client is however not required to set this, in which
|
||||||
|
case the compositor is free to decide some default value.
|
||||||
|
|
||||||
|
If the client specifies more than one rectangle, only the last one is
|
||||||
|
considered.
|
||||||
|
|
||||||
|
The dimensions are given in surface-local coordinates.
|
||||||
|
Setting width=height=0 removes the already-set rectangle.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_rectangle" value="0"
|
||||||
|
summary="the provided rectangle is invalid"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="closed">
|
||||||
|
<description summary="this toplevel has been destroyed">
|
||||||
|
This event means the toplevel has been destroyed. It is guaranteed there
|
||||||
|
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
|
||||||
|
toplevel itself becomes inert so any requests will be ignored except the
|
||||||
|
destroy request.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||||
|
Destroys the zwlr_foreign_toplevel_handle_v1 object.
|
||||||
|
|
||||||
|
This request should be called either when the client does not want to
|
||||||
|
use the toplevel anymore or after the closed event to finalize the
|
||||||
|
destruction of the object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 2 additions -->
|
||||||
|
|
||||||
|
<request name="set_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be fullscreened">
|
||||||
|
Requests that the toplevel be fullscreened on the given output. If the
|
||||||
|
fullscreen state and/or the outputs the toplevel is visible on actually
|
||||||
|
change, this will be indicated by the state and output_enter/leave
|
||||||
|
events.
|
||||||
|
|
||||||
|
The output parameter is only a hint to the compositor. Also, if output
|
||||||
|
is NULL, the compositor should decide which output the toplevel will be
|
||||||
|
fullscreened on, if at all.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be unfullscreened">
|
||||||
|
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 3 additions -->
|
||||||
|
|
||||||
|
<event name="parent" since="3">
|
||||||
|
<description summary="parent change">
|
||||||
|
This event is emitted whenever the parent of the toplevel changes.
|
||||||
|
|
||||||
|
No event is emitted when the parent handle is destroyed by the client.
|
||||||
|
</description>
|
||||||
|
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# This file has been autogenerated by the pywayland scanner
|
||||||
|
|
||||||
|
# Copyright © 2018 Ilia Bozhinov
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, distribute, and sell this
|
||||||
|
# software and its documentation for any purpose is hereby granted
|
||||||
|
# without fee, provided that the above copyright notice appear in
|
||||||
|
# all copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of
|
||||||
|
# the copyright holders not be used in advertising or publicity
|
||||||
|
# pertaining to distribution of the software without specific,
|
||||||
|
# written prior permission. The copyright holders make no
|
||||||
|
# representations about the suitability of this software for any
|
||||||
|
# purpose. It is provided "as is" without express or implied
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
# FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
# THIS SOFTWARE.
|
||||||
|
|
||||||
|
from .zwlr_foreign_toplevel_handle_v1 import ZwlrForeignToplevelHandleV1 # noqa: F401
|
||||||
|
from .zwlr_foreign_toplevel_manager_v1 import ZwlrForeignToplevelManagerV1 # noqa: F401
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
# This file has been autogenerated by the pywayland scanner
|
||||||
|
|
||||||
|
# Copyright © 2018 Ilia Bozhinov
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, distribute, and sell this
|
||||||
|
# software and its documentation for any purpose is hereby granted
|
||||||
|
# without fee, provided that the above copyright notice appear in
|
||||||
|
# all copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of
|
||||||
|
# the copyright holders not be used in advertising or publicity
|
||||||
|
# pertaining to distribution of the software without specific,
|
||||||
|
# written prior permission. The copyright holders make no
|
||||||
|
# representations about the suitability of this software for any
|
||||||
|
# purpose. It is provided "as is" without express or implied
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
# FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
# THIS SOFTWARE.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import enum
|
||||||
|
|
||||||
|
from pywayland.protocol_core import (
|
||||||
|
Argument,
|
||||||
|
ArgumentType,
|
||||||
|
Global,
|
||||||
|
Interface,
|
||||||
|
Proxy,
|
||||||
|
Resource,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pywayland.protocol.wayland import WlOutput
|
||||||
|
from pywayland.protocol.wayland import WlSeat
|
||||||
|
from pywayland.protocol.wayland import WlSurface
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelHandleV1(Interface):
|
||||||
|
"""An opened toplevel
|
||||||
|
|
||||||
|
A :class:`ZwlrForeignToplevelHandleV1` object represents an opened toplevel
|
||||||
|
window. Each app may have multiple opened toplevels.
|
||||||
|
|
||||||
|
Each toplevel has a list of outputs it is visible on, conveyed to the
|
||||||
|
client with the output_enter and output_leave events.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "zwlr_foreign_toplevel_handle_v1"
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
class state(enum.IntEnum):
|
||||||
|
maximized = 0
|
||||||
|
minimized = 1
|
||||||
|
activated = 2
|
||||||
|
fullscreen = 3
|
||||||
|
|
||||||
|
class error(enum.IntEnum):
|
||||||
|
invalid_rectangle = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelHandleV1Proxy(Proxy[ZwlrForeignToplevelHandleV1]):
|
||||||
|
interface = ZwlrForeignToplevelHandleV1
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def set_maximized(self) -> None:
|
||||||
|
"""Requests that the toplevel be maximized
|
||||||
|
|
||||||
|
Requests that the toplevel be maximized. If the maximized state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
"""
|
||||||
|
self._marshal(0)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def unset_maximized(self) -> None:
|
||||||
|
"""Requests that the toplevel be unmaximized
|
||||||
|
|
||||||
|
Requests that the toplevel be unmaximized. If the maximized state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
"""
|
||||||
|
self._marshal(1)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def set_minimized(self) -> None:
|
||||||
|
"""Requests that the toplevel be minimized
|
||||||
|
|
||||||
|
Requests that the toplevel be minimized. If the minimized state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
"""
|
||||||
|
self._marshal(2)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def unset_minimized(self) -> None:
|
||||||
|
"""Requests that the toplevel be unminimized
|
||||||
|
|
||||||
|
Requests that the toplevel be unminimized. If the minimized state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
"""
|
||||||
|
self._marshal(3)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request(
|
||||||
|
Argument(ArgumentType.Object, interface=WlSeat),
|
||||||
|
)
|
||||||
|
def activate(self, seat: WlSeat) -> None:
|
||||||
|
"""Activate the toplevel
|
||||||
|
|
||||||
|
Request that this toplevel be activated on the given seat. There is no
|
||||||
|
guarantee the toplevel will be actually activated.
|
||||||
|
|
||||||
|
:param seat:
|
||||||
|
:type seat:
|
||||||
|
:class:`~pywayland.protocol.wayland.WlSeat`
|
||||||
|
"""
|
||||||
|
self._marshal(4, seat)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def close(self) -> None:
|
||||||
|
"""Request that the toplevel be closed
|
||||||
|
|
||||||
|
Send a request to the toplevel to close itself. The compositor would
|
||||||
|
typically use a shell-specific method to carry out this request, for
|
||||||
|
example by sending the xdg_toplevel.close event. However, this gives no
|
||||||
|
guarantees the toplevel will actually be destroyed. If and when this
|
||||||
|
happens, the :func:`ZwlrForeignToplevelHandleV1.closed()` event will be
|
||||||
|
emitted.
|
||||||
|
"""
|
||||||
|
self._marshal(5)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request(
|
||||||
|
Argument(ArgumentType.Object, interface=WlSurface),
|
||||||
|
Argument(ArgumentType.Int),
|
||||||
|
Argument(ArgumentType.Int),
|
||||||
|
Argument(ArgumentType.Int),
|
||||||
|
Argument(ArgumentType.Int),
|
||||||
|
)
|
||||||
|
def set_rectangle(
|
||||||
|
self, surface: WlSurface, x: int, y: int, width: int, height: int
|
||||||
|
) -> None:
|
||||||
|
"""The rectangle which represents the toplevel
|
||||||
|
|
||||||
|
The rectangle of the surface specified in this request corresponds to
|
||||||
|
the place where the app using this protocol represents the given
|
||||||
|
toplevel. It can be used by the compositor as a hint for some
|
||||||
|
operations, e.g minimizing. The client is however not required to set
|
||||||
|
this, in which case the compositor is free to decide some default
|
||||||
|
value.
|
||||||
|
|
||||||
|
If the client specifies more than one rectangle, only the last one is
|
||||||
|
considered.
|
||||||
|
|
||||||
|
The dimensions are given in surface-local coordinates. Setting
|
||||||
|
width=height=0 removes the already-set rectangle.
|
||||||
|
|
||||||
|
:param surface:
|
||||||
|
:type surface:
|
||||||
|
:class:`~pywayland.protocol.wayland.WlSurface`
|
||||||
|
:param x:
|
||||||
|
:type x:
|
||||||
|
`ArgumentType.Int`
|
||||||
|
:param y:
|
||||||
|
:type y:
|
||||||
|
`ArgumentType.Int`
|
||||||
|
:param width:
|
||||||
|
:type width:
|
||||||
|
`ArgumentType.Int`
|
||||||
|
:param height:
|
||||||
|
:type height:
|
||||||
|
`ArgumentType.Int`
|
||||||
|
"""
|
||||||
|
self._marshal(6, surface, x, y, width, height)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request()
|
||||||
|
def destroy(self) -> None:
|
||||||
|
"""Destroy the :class:`ZwlrForeignToplevelHandleV1` object
|
||||||
|
|
||||||
|
Destroys the :class:`ZwlrForeignToplevelHandleV1` object.
|
||||||
|
|
||||||
|
This request should be called either when the client does not want to
|
||||||
|
use the toplevel anymore or after the closed event to finalize the
|
||||||
|
destruction of the object.
|
||||||
|
"""
|
||||||
|
self._marshal(7)
|
||||||
|
self._destroy()
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request(
|
||||||
|
Argument(ArgumentType.Object, interface=WlOutput, nullable=True),
|
||||||
|
version=2,
|
||||||
|
)
|
||||||
|
def set_fullscreen(self, output: WlOutput | None) -> None:
|
||||||
|
"""Request that the toplevel be fullscreened
|
||||||
|
|
||||||
|
Requests that the toplevel be fullscreened on the given output. If the
|
||||||
|
fullscreen state and/or the outputs the toplevel is visible on actually
|
||||||
|
change, this will be indicated by the state and output_enter/leave
|
||||||
|
events.
|
||||||
|
|
||||||
|
The output parameter is only a hint to the compositor. Also, if output
|
||||||
|
is NULL, the compositor should decide which output the toplevel will be
|
||||||
|
fullscreened on, if at all.
|
||||||
|
|
||||||
|
:param output:
|
||||||
|
:type output:
|
||||||
|
:class:`~pywayland.protocol.wayland.WlOutput` or `None`
|
||||||
|
"""
|
||||||
|
self._marshal(8, output)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.request(version=2)
|
||||||
|
def unset_fullscreen(self) -> None:
|
||||||
|
"""Request that the toplevel be unfullscreened
|
||||||
|
|
||||||
|
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
"""
|
||||||
|
self._marshal(9)
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelHandleV1Resource(Resource):
|
||||||
|
interface = ZwlrForeignToplevelHandleV1
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(ArgumentType.String),
|
||||||
|
)
|
||||||
|
def title(self, title: str) -> None:
|
||||||
|
"""Title change
|
||||||
|
|
||||||
|
This event is emitted whenever the title of the toplevel changes.
|
||||||
|
|
||||||
|
:param title:
|
||||||
|
:type title:
|
||||||
|
`ArgumentType.String`
|
||||||
|
"""
|
||||||
|
self._post_event(0, title)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(ArgumentType.String),
|
||||||
|
)
|
||||||
|
def app_id(self, app_id: str) -> None:
|
||||||
|
"""App-id change
|
||||||
|
|
||||||
|
This event is emitted whenever the app-id of the toplevel changes.
|
||||||
|
|
||||||
|
:param app_id:
|
||||||
|
:type app_id:
|
||||||
|
`ArgumentType.String`
|
||||||
|
"""
|
||||||
|
self._post_event(1, app_id)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(ArgumentType.Object, interface=WlOutput),
|
||||||
|
)
|
||||||
|
def output_enter(self, output: WlOutput) -> None:
|
||||||
|
"""Toplevel entered an output
|
||||||
|
|
||||||
|
This event is emitted whenever the toplevel becomes visible on the
|
||||||
|
given output. A toplevel may be visible on multiple outputs.
|
||||||
|
|
||||||
|
:param output:
|
||||||
|
:type output:
|
||||||
|
:class:`~pywayland.protocol.wayland.WlOutput`
|
||||||
|
"""
|
||||||
|
self._post_event(2, output)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(ArgumentType.Object, interface=WlOutput),
|
||||||
|
)
|
||||||
|
def output_leave(self, output: WlOutput) -> None:
|
||||||
|
"""Toplevel left an output
|
||||||
|
|
||||||
|
This event is emitted whenever the toplevel stops being visible on the
|
||||||
|
given output. It is guaranteed that an entered-output event with the
|
||||||
|
same output has been emitted before this event.
|
||||||
|
|
||||||
|
:param output:
|
||||||
|
:type output:
|
||||||
|
:class:`~pywayland.protocol.wayland.WlOutput`
|
||||||
|
"""
|
||||||
|
self._post_event(3, output)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(ArgumentType.Array),
|
||||||
|
)
|
||||||
|
def state(self, state: list) -> None:
|
||||||
|
"""The toplevel state changed
|
||||||
|
|
||||||
|
This event is emitted immediately after the
|
||||||
|
zlw_foreign_toplevel_handle_v1 is created and each time the toplevel
|
||||||
|
state changes, either because of a compositor action or because of a
|
||||||
|
request in this protocol.
|
||||||
|
|
||||||
|
:param state:
|
||||||
|
:type state:
|
||||||
|
`ArgumentType.Array`
|
||||||
|
"""
|
||||||
|
self._post_event(4, state)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event()
|
||||||
|
def done(self) -> None:
|
||||||
|
"""All information about the toplevel has been sent
|
||||||
|
|
||||||
|
This event is sent after all changes in the toplevel state have been
|
||||||
|
sent.
|
||||||
|
|
||||||
|
This allows changes to the :class:`ZwlrForeignToplevelHandleV1`
|
||||||
|
properties to be seen as atomic, even if they happen via multiple
|
||||||
|
events.
|
||||||
|
"""
|
||||||
|
self._post_event(5)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event()
|
||||||
|
def closed(self) -> None:
|
||||||
|
"""This toplevel has been destroyed
|
||||||
|
|
||||||
|
This event means the toplevel has been destroyed. It is guaranteed
|
||||||
|
there won't be any more events for this
|
||||||
|
:class:`ZwlrForeignToplevelHandleV1`. The toplevel itself becomes inert
|
||||||
|
so any requests will be ignored except the destroy request.
|
||||||
|
"""
|
||||||
|
self._post_event(6)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelHandleV1.event(
|
||||||
|
Argument(
|
||||||
|
ArgumentType.Object, interface=ZwlrForeignToplevelHandleV1, nullable=True
|
||||||
|
),
|
||||||
|
version=3,
|
||||||
|
)
|
||||||
|
def parent(self, parent: ZwlrForeignToplevelHandleV1 | None) -> None:
|
||||||
|
"""Parent change
|
||||||
|
|
||||||
|
This event is emitted whenever the parent of the toplevel changes.
|
||||||
|
|
||||||
|
No event is emitted when the parent handle is destroyed by the client.
|
||||||
|
|
||||||
|
:param parent:
|
||||||
|
:type parent:
|
||||||
|
:class:`ZwlrForeignToplevelHandleV1` or `None`
|
||||||
|
"""
|
||||||
|
self._post_event(7, parent)
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelHandleV1Global(Global):
|
||||||
|
interface = ZwlrForeignToplevelHandleV1
|
||||||
|
|
||||||
|
|
||||||
|
ZwlrForeignToplevelHandleV1._gen_c()
|
||||||
|
ZwlrForeignToplevelHandleV1.proxy_class = ZwlrForeignToplevelHandleV1Proxy
|
||||||
|
ZwlrForeignToplevelHandleV1.resource_class = ZwlrForeignToplevelHandleV1Resource
|
||||||
|
ZwlrForeignToplevelHandleV1.global_class = ZwlrForeignToplevelHandleV1Global
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# This file has been autogenerated by the pywayland scanner
|
||||||
|
|
||||||
|
# Copyright © 2018 Ilia Bozhinov
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, distribute, and sell this
|
||||||
|
# software and its documentation for any purpose is hereby granted
|
||||||
|
# without fee, provided that the above copyright notice appear in
|
||||||
|
# all copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of
|
||||||
|
# the copyright holders not be used in advertising or publicity
|
||||||
|
# pertaining to distribution of the software without specific,
|
||||||
|
# written prior permission. The copyright holders make no
|
||||||
|
# representations about the suitability of this software for any
|
||||||
|
# purpose. It is provided "as is" without express or implied
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
# FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
# THIS SOFTWARE.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pywayland.protocol_core import (
|
||||||
|
Argument,
|
||||||
|
ArgumentType,
|
||||||
|
Global,
|
||||||
|
Interface,
|
||||||
|
Proxy,
|
||||||
|
Resource,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .zwlr_foreign_toplevel_handle_v1 import ZwlrForeignToplevelHandleV1
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelManagerV1(Interface):
|
||||||
|
"""List and control opened apps
|
||||||
|
|
||||||
|
The purpose of this protocol is to enable the creation of taskbars and
|
||||||
|
docks by providing them with a list of opened applications and letting them
|
||||||
|
request certain actions on them, like maximizing, etc.
|
||||||
|
|
||||||
|
After a client binds the :class:`ZwlrForeignToplevelManagerV1`, each opened
|
||||||
|
toplevel window will be sent via the toplevel event
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "zwlr_foreign_toplevel_manager_v1"
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelManagerV1Proxy(Proxy[ZwlrForeignToplevelManagerV1]):
|
||||||
|
interface = ZwlrForeignToplevelManagerV1
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelManagerV1.request()
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Stop sending events
|
||||||
|
|
||||||
|
Indicates the client no longer wishes to receive events for new
|
||||||
|
toplevels. However the compositor may emit further toplevel_created
|
||||||
|
events, until the finished event is emitted.
|
||||||
|
|
||||||
|
The client must not send any more requests after this one.
|
||||||
|
"""
|
||||||
|
self._marshal(0)
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelManagerV1Resource(Resource):
|
||||||
|
interface = ZwlrForeignToplevelManagerV1
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelManagerV1.event(
|
||||||
|
Argument(ArgumentType.NewId, interface=ZwlrForeignToplevelHandleV1),
|
||||||
|
)
|
||||||
|
def toplevel(self, toplevel: ZwlrForeignToplevelHandleV1) -> None:
|
||||||
|
"""A toplevel has been created
|
||||||
|
|
||||||
|
This event is emitted whenever a new toplevel window is created. It is
|
||||||
|
emitted for all toplevels, regardless of the app that has created them.
|
||||||
|
|
||||||
|
All initial details of the toplevel(title, app_id, states, etc.) will
|
||||||
|
be sent immediately after this event via the corresponding events in
|
||||||
|
:class:`~pywayland.protocol.wlr_foreign_toplevel_management_unstable_v1.ZwlrForeignToplevelHandleV1`.
|
||||||
|
|
||||||
|
:param toplevel:
|
||||||
|
:type toplevel:
|
||||||
|
:class:`~pywayland.protocol.wlr_foreign_toplevel_management_unstable_v1.ZwlrForeignToplevelHandleV1`
|
||||||
|
"""
|
||||||
|
self._post_event(0, toplevel)
|
||||||
|
|
||||||
|
@ZwlrForeignToplevelManagerV1.event()
|
||||||
|
def finished(self) -> None:
|
||||||
|
"""The compositor has finished with the toplevel manager
|
||||||
|
|
||||||
|
This event indicates that the compositor is done sending events to the
|
||||||
|
:class:`ZwlrForeignToplevelManagerV1`. The server will destroy the
|
||||||
|
object immediately after sending this request, so it will become
|
||||||
|
invalid and the client should free any resources associated with it.
|
||||||
|
"""
|
||||||
|
self._post_event(1)
|
||||||
|
|
||||||
|
|
||||||
|
class ZwlrForeignToplevelManagerV1Global(Global):
|
||||||
|
interface = ZwlrForeignToplevelManagerV1
|
||||||
|
|
||||||
|
|
||||||
|
ZwlrForeignToplevelManagerV1._gen_c()
|
||||||
|
ZwlrForeignToplevelManagerV1.proxy_class = ZwlrForeignToplevelManagerV1Proxy
|
||||||
|
ZwlrForeignToplevelManagerV1.resource_class = ZwlrForeignToplevelManagerV1Resource
|
||||||
|
ZwlrForeignToplevelManagerV1.global_class = ZwlrForeignToplevelManagerV1Global
|
||||||
238
bar/services/wlr/service.py
Normal file
238
bar/services/wlr/service.py
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
import time
|
||||||
|
from gi.repository import GLib
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
from pywayland.client import Display
|
||||||
|
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||||
|
|
||||||
|
from fabric.core.service import Property, Service, Signal
|
||||||
|
from fabric.utils.helpers import idle_add
|
||||||
|
|
||||||
|
from bar.services.wlr.protocol.wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_manager_v1 import (
|
||||||
|
ZwlrForeignToplevelManagerV1,
|
||||||
|
)
|
||||||
|
from bar.services.wlr.protocol.wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_handle_v1 import (
|
||||||
|
ZwlrForeignToplevelHandleV1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Window:
|
||||||
|
"""Represents a toplevel window in the compositor."""
|
||||||
|
|
||||||
|
def __init__(self, handle: ZwlrForeignToplevelHandleV1):
|
||||||
|
self.handle = handle
|
||||||
|
self.title: str = "Unknown"
|
||||||
|
self.app_id: str = "Unknown"
|
||||||
|
self.states: List[str] = []
|
||||||
|
self.outputs: List[WlOutput] = []
|
||||||
|
self.parent: Optional["Window"] = None
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
state_str = (
|
||||||
|
", ".join([ZwlrForeignToplevelHandleV1.state(s).name for s in self.states])
|
||||||
|
if self.states
|
||||||
|
else "normal"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"Window(title='{self.title}', app_id='{self.app_id}', state={state_str})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WaylandWindowTracker(Service):
|
||||||
|
"""Track Wayland windows in the background and provide access on demand."""
|
||||||
|
|
||||||
|
@Property(bool, "readable", "is-ready", default_value=False)
|
||||||
|
def ready(self) -> bool:
|
||||||
|
return self._ready
|
||||||
|
|
||||||
|
@Signal
|
||||||
|
def ready_signal(self):
|
||||||
|
return self.notify("ready")
|
||||||
|
|
||||||
|
@Property(list[Window], "readable", "windows")
|
||||||
|
def windows(self) -> list[Window]:
|
||||||
|
return [window for window in self._window_dict.values() if not window.closed]
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.display = None
|
||||||
|
self._window_dict: Dict[ZwlrForeignToplevelHandleV1, Window] = {}
|
||||||
|
self._windows = []
|
||||||
|
self.manager = None
|
||||||
|
self.seat: Optional[WlSeat] = None
|
||||||
|
|
||||||
|
self.thread = GLib.Thread.new(
|
||||||
|
"wayland-window-service", self._run_display_thread
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_display_thread(self):
|
||||||
|
"""Run the Wayland event loop in a background thread."""
|
||||||
|
try:
|
||||||
|
self.display = Display()
|
||||||
|
self.display.connect()
|
||||||
|
|
||||||
|
# Get the registry to find the foreign toplevel manager
|
||||||
|
registry = self.display.get_registry()
|
||||||
|
registry.dispatcher["global"] = self._registry_global_handler
|
||||||
|
|
||||||
|
# Process registry events
|
||||||
|
self.display.roundtrip()
|
||||||
|
|
||||||
|
if not self.manager:
|
||||||
|
print("Foreign toplevel manager not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process more events to get initial windows
|
||||||
|
for _ in range(5):
|
||||||
|
self.display.roundtrip()
|
||||||
|
|
||||||
|
idle_add(self._set_ready)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.display.dispatch(block=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Display thread error: {e}")
|
||||||
|
finally:
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def _registry_global_handler(self, registry, id, interface, version):
|
||||||
|
"""Handle registry global objects."""
|
||||||
|
if interface == WlSeat.name:
|
||||||
|
self.seat = registry.bind(id, WlSeat, version)
|
||||||
|
print(f"Found seat (id={id}, version={version})")
|
||||||
|
elif interface == ZwlrForeignToplevelManagerV1.name:
|
||||||
|
self.manager = registry.bind(
|
||||||
|
id, ZwlrForeignToplevelManagerV1, min(version, 3)
|
||||||
|
)
|
||||||
|
self.manager.dispatcher["toplevel"] = self._handle_toplevel
|
||||||
|
self.manager.dispatcher["finished"] = self._handle_manager_finished
|
||||||
|
|
||||||
|
def _handle_toplevel(self, manager, toplevel):
|
||||||
|
"""Handle a new toplevel window."""
|
||||||
|
print("TOPLEVEL IS TRIGGERD")
|
||||||
|
window = Window(toplevel)
|
||||||
|
|
||||||
|
self._window_dict[toplevel] = window
|
||||||
|
|
||||||
|
# Setup event dispatchers for the toplevel
|
||||||
|
toplevel.dispatcher["title"] = self._handle_title
|
||||||
|
toplevel.dispatcher["app_id"] = self._handle_app_id
|
||||||
|
toplevel.dispatcher["state"] = self._handle_state
|
||||||
|
toplevel.dispatcher["done"] = self._handle_done
|
||||||
|
toplevel.dispatcher["closed"] = self._handle_closed
|
||||||
|
toplevel.dispatcher["output_enter"] = self._handle_output_enter
|
||||||
|
toplevel.dispatcher["output_leave"] = self._handle_output_leave
|
||||||
|
|
||||||
|
def _handle_title(self, toplevel, title):
|
||||||
|
"""Handle toplevel title changes."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window:
|
||||||
|
print("there is a window, putting title")
|
||||||
|
window.title = title
|
||||||
|
|
||||||
|
def _handle_app_id(self, toplevel, app_id):
|
||||||
|
"""Handle toplevel app_id changes."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.app_id = app_id
|
||||||
|
|
||||||
|
def _handle_state(self, toplevel, states):
|
||||||
|
"""Handle toplevel state changes."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.states = states
|
||||||
|
|
||||||
|
def _handle_done(self, toplevel):
|
||||||
|
"""Handle toplevel done event."""
|
||||||
|
# We don't need to print anything here as we're just tracking silently
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _handle_closed(self, toplevel):
|
||||||
|
"""Handle toplevel closed event."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window:
|
||||||
|
window.closed = True
|
||||||
|
# Remove from our dictionary
|
||||||
|
del self._window_dict[toplevel]
|
||||||
|
|
||||||
|
# Clean up the toplevel object
|
||||||
|
toplevel.destroy()
|
||||||
|
|
||||||
|
def _handle_output_enter(self, toplevel, output):
|
||||||
|
"""Handle toplevel entering an output."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window and output not in window.outputs:
|
||||||
|
window.outputs.append(output)
|
||||||
|
|
||||||
|
def _handle_output_leave(self, toplevel, output):
|
||||||
|
"""Handle toplevel leaving an output."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window and output in window.outputs:
|
||||||
|
window.outputs.remove(output)
|
||||||
|
|
||||||
|
def _handle_parent(self, toplevel, parent):
|
||||||
|
"""Handle toplevel parent changes."""
|
||||||
|
window = self._window_dict.get(toplevel)
|
||||||
|
if window:
|
||||||
|
if parent is None:
|
||||||
|
window.parent = None
|
||||||
|
else:
|
||||||
|
parent_window = self._window_dict.get(parent)
|
||||||
|
if parent_window:
|
||||||
|
window.parent = parent_window
|
||||||
|
|
||||||
|
def _handle_manager_finished(self, manager):
|
||||||
|
"""Handle manager finished event."""
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def _set_ready(self):
|
||||||
|
print("IM READY")
|
||||||
|
self._ready = True
|
||||||
|
self.ready_signal.emit()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_windows(self) -> List[Window]:
|
||||||
|
"""Get all currently active windows."""
|
||||||
|
print([window for window in self._window_dict.values()])
|
||||||
|
print("YOU CALLED WINDOWS")
|
||||||
|
return [window for window in self._window_dict.values() if not window.closed]
|
||||||
|
|
||||||
|
def activate_window(self, window: Window):
|
||||||
|
if self.seat is None:
|
||||||
|
print("Cannot activate window: no seat available")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Activating window: {window.title}")
|
||||||
|
window.handle.activate(self.seat)
|
||||||
|
self.display.flush() # flush the request to the Wayland server
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Clean up resources."""
|
||||||
|
self.running = False
|
||||||
|
print("Cleanup")
|
||||||
|
|
||||||
|
if self.manager:
|
||||||
|
try:
|
||||||
|
self.manager.stop()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Disconnect from display
|
||||||
|
if self.display:
|
||||||
|
try:
|
||||||
|
self.display.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def print_windows(tracker):
|
||||||
|
"""Print the current list of windows."""
|
||||||
|
windows = tracker.get_windows()
|
||||||
|
print(f"\nCurrent windows ({len(windows)}):")
|
||||||
|
if windows:
|
||||||
|
for i, window in enumerate(windows, 1):
|
||||||
|
print(f"{i}. {window}")
|
||||||
|
else:
|
||||||
|
print("No windows found")
|
||||||
51
bar/styles/bar.css
Normal file
51
bar/styles/bar.css
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#bar-inner {
|
||||||
|
padding: 4px;
|
||||||
|
border-bottom: solid 2px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#center-container {
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-window {
|
||||||
|
color: var(--foreground);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu-progress-bar,
|
||||||
|
#ram-progress-bar,
|
||||||
|
#volume-progress-bar {
|
||||||
|
color: transparent;
|
||||||
|
background-color: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu-progress-bar {
|
||||||
|
border: solid 0px alpha(var(--violet), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ram-progress-bar,
|
||||||
|
#volume-progress-bar {
|
||||||
|
border: solid 0px var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#widgets-container {
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nixos-label {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip {
|
||||||
|
border: solid 2px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip>* {
|
||||||
|
padding: 2px 4px
|
||||||
|
}
|
||||||
28
bar/styles/colors.css
Normal file
28
bar/styles/colors.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
:vars {
|
||||||
|
--background: #17181C;
|
||||||
|
--mid-bg: #1E1F24;
|
||||||
|
--light-bg: #26272B;
|
||||||
|
--dark-grey: #333438;
|
||||||
|
--light-grey: #8F9093;
|
||||||
|
--dark-fg: #B0B1B4;
|
||||||
|
--mid-fg: #CBCCCE;
|
||||||
|
--foreground: #E4E5E7;
|
||||||
|
|
||||||
|
--pink: #FA3867;
|
||||||
|
--orange: #F3872F;
|
||||||
|
--gold: #FEBD16;
|
||||||
|
--lime: #3FD43B;
|
||||||
|
--turquoise: #47E7CE;
|
||||||
|
--blue: #53ADE1;
|
||||||
|
--violet: #AD60FF;
|
||||||
|
--red: #FC3F2C;
|
||||||
|
|
||||||
|
--window-bg: alpha(var(--background), 0.9);
|
||||||
|
--module-bg: alpha(var(--mid-bg), 0.8);
|
||||||
|
--border-color: var(--light-bg);
|
||||||
|
--ws-active: var(--pink);
|
||||||
|
--ws-inactive: var(--blue);
|
||||||
|
--ws-empty: var(--dark-grey);
|
||||||
|
--ws-hover: var(--turquoise);
|
||||||
|
--ws-urgent: var(--red);
|
||||||
|
}
|
||||||
29
bar/styles/finder.css
Normal file
29
bar/styles/finder.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#picker-box {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: rgba(40, 40, 40, 0.95); /* darker for contrast */
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#viewport {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(30, 30, 30, 0.9); /* dark background for contrast */
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: white; /* ensure contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewport > * {
|
||||||
|
padding: 6px 10px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewport:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15); /* hover feedback */
|
||||||
|
}
|
||||||
20
bar/styles/main.css
Normal file
20
bar/styles/main.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@import url("./colors.css");
|
||||||
|
@import url("./workspaces.css");
|
||||||
|
@import url("./menu.css");
|
||||||
|
@import url("./vinyl.css");
|
||||||
|
@import url("./bar.css");
|
||||||
|
@import url("./finder.css");
|
||||||
|
|
||||||
|
|
||||||
|
/* unset so we can style everything from the ground up. */
|
||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Jost*", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
38
bar/styles/menu.css
Normal file
38
bar/styles/menu.css
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#date-time,
|
||||||
|
menu>menuitem>label,
|
||||||
|
#date-time>label,
|
||||||
|
/* system tray */
|
||||||
|
#system-tray {
|
||||||
|
padding: 2px 4px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* menu and menu items (written for the system tray) */
|
||||||
|
menu {
|
||||||
|
border: solid 2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem {
|
||||||
|
border-radius: 0px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
padding: 6px;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:first-child {
|
||||||
|
margin-top: 1px;
|
||||||
|
border-radius: 8px 8px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:last-child {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
border-radius: 0px 0px 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:hover {
|
||||||
|
background-color: var(--pink);
|
||||||
|
}
|
||||||
41
bar/styles/vinyl.css
Normal file
41
bar/styles/vinyl.css
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* Vinyl button styling */
|
||||||
|
#vinyl-button {
|
||||||
|
padding: 0px 8px;
|
||||||
|
transition: padding 0.05s steps(8);
|
||||||
|
background-color: rgba(180, 180, 180, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state styling */
|
||||||
|
.active #vinyl-button {
|
||||||
|
background-color: rgba(108, 158, 175, 0.7);
|
||||||
|
padding: 0px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon styling */
|
||||||
|
#vinyl-icon {
|
||||||
|
color: #555555;
|
||||||
|
min-width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label styling */
|
||||||
|
#vinyl-label {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state changes for icon and label */
|
||||||
|
.active #vinyl-icon,
|
||||||
|
.active #vinyl-label {
|
||||||
|
color: var(--pink);
|
||||||
|
padding: 0px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover effect */
|
||||||
|
#vinyl-button:hover {
|
||||||
|
background-color: rgba(180, 180, 180, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active #vinyl-button:hover {
|
||||||
|
background-color: rgba(108, 158, 175, 0.9);
|
||||||
|
}
|
||||||
42
bar/styles/workspaces.css
Normal file
42
bar/styles/workspaces.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#workspaces {
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 0px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button {
|
||||||
|
padding: 0px 8px;
|
||||||
|
transition: padding 0.05s steps(8);
|
||||||
|
background-color: var(--foreground);
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button>label {
|
||||||
|
font-size: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.hover {
|
||||||
|
background-color: var(--ws-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.urgent {
|
||||||
|
background-color: var(--ws-urgent);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-weight: bold;
|
||||||
|
animation: urgent-blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes urgent-blink {
|
||||||
|
0% { opacity: 1.0; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
100% { opacity: 1.0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button.empty {
|
||||||
|
background-color: var(--ws-empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button.active {
|
||||||
|
padding: 0px 32px;
|
||||||
|
background-color: var(--ws-active);
|
||||||
|
}
|
||||||
122
bar/test.py
122
bar/test.py
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Callable
|
|
||||||
from pywayland.client import Display
|
|
||||||
from pywayland.protocol.wayland import WlOutput, WlRegistry, WlSeat
|
|
||||||
from .generated.river_status_unstable_v1 import (
|
|
||||||
ZriverStatusManagerV1,
|
|
||||||
ZriverOutputStatusV1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OutputInfo:
|
|
||||||
name: int
|
|
||||||
output: WlOutput
|
|
||||||
status: ZriverOutputStatusV1
|
|
||||||
tags_view: list[int] = field(default_factory=list)
|
|
||||||
tags_focused: list[int] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class State:
|
|
||||||
display: Display
|
|
||||||
registry: WlRegistry
|
|
||||||
outputs: dict[int, OutputInfo] = field(default_factory=dict)
|
|
||||||
river_status_mgr: ZriverStatusManagerV1 | None = None
|
|
||||||
seat: WlSeat | None = None
|
|
||||||
seat_status: ZriverSeatStatusV1 | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def decode_bitfields(bitfields: list[int] | int) -> list[int]:
|
|
||||||
tags = set()
|
|
||||||
if isinstance(bitfields, int):
|
|
||||||
bitfields = [bitfields]
|
|
||||||
for bits in bitfields:
|
|
||||||
for i in range(32):
|
|
||||||
if bits & (1 << i):
|
|
||||||
tags.add(i)
|
|
||||||
return sorted(tags)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_global(
|
|
||||||
state: State, registry: WlRegistry, name: int, iface: str, version: int
|
|
||||||
) -> None:
|
|
||||||
if iface == "zriver_status_manager_v1":
|
|
||||||
state.river_status_mgr = registry.bind(name, ZriverStatusManagerV1, version)
|
|
||||||
|
|
||||||
elif iface == "wl_output":
|
|
||||||
output = registry.bind(name, WlOutput, version)
|
|
||||||
state.outputs[name] = OutputInfo(name=name, output=output, status=None)
|
|
||||||
elif iface == "wl_seat":
|
|
||||||
seat = registry.bind(name, WlSeat, version)
|
|
||||||
state.seat = seat
|
|
||||||
|
|
||||||
|
|
||||||
def handle_global_remove(state: State, registry: WlRegistry, name: int) -> None:
|
|
||||||
if name in state.outputs:
|
|
||||||
print(f"Output {name} removed.")
|
|
||||||
del state.outputs[name]
|
|
||||||
|
|
||||||
|
|
||||||
def make_view_tags_handler(state: State, name: int) -> Callable:
|
|
||||||
def handler(self, tags: list[int]) -> None:
|
|
||||||
decoded = decode_bitfields(tags)
|
|
||||||
state.outputs[name].tags_view = decoded
|
|
||||||
print(f"[Output {name}] View tags: {decoded}")
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
|
|
||||||
def make_focused_tags_handler(state: State, name: int) -> Callable:
|
|
||||||
def handler(self, tags: int) -> None:
|
|
||||||
decoded = decode_bitfields(tags)
|
|
||||||
state.outputs[name].tags_focused = decoded
|
|
||||||
print(f"[Output {name}] Focused tags: {decoded}")
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
with Display() as display:
|
|
||||||
registry = display.get_registry()
|
|
||||||
state = State(display=display, registry=registry)
|
|
||||||
|
|
||||||
registry.dispatcher["global"] = lambda reg, name, iface, ver: handle_global(
|
|
||||||
state, reg, name, iface, ver
|
|
||||||
)
|
|
||||||
registry.dispatcher["global_remove"] = lambda reg, name: handle_global_remove(
|
|
||||||
state, reg, name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Discover globals
|
|
||||||
display.roundtrip()
|
|
||||||
|
|
||||||
if not state.river_status_mgr:
|
|
||||||
print("❌ River status manager not found.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Bind output status listeners
|
|
||||||
for name, info in state.outputs.items():
|
|
||||||
status = state.river_status_mgr.get_river_output_status(info.output)
|
|
||||||
status.dispatcher["view_tags"] = make_view_tags_handler(state, name)
|
|
||||||
status.dispatcher["focused_tags"] = make_focused_tags_handler(state, name)
|
|
||||||
info.status = status
|
|
||||||
|
|
||||||
if state.seat:
|
|
||||||
state.seat_status = state.river_status_mgr.get_river_seat_status(state.seat)
|
|
||||||
print("✅ Bound seat status")
|
|
||||||
|
|
||||||
# Initial data
|
|
||||||
display.roundtrip()
|
|
||||||
|
|
||||||
print("🟢 Listening for tag changes. Press Ctrl+C to exit.")
|
|
||||||
while True:
|
|
||||||
display.roundtrip()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
550
bar/utils/icons.py
Normal file
550
bar/utils/icons.py
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
common_text_icons = {
|
||||||
|
"playing": "",
|
||||||
|
"paused": "",
|
||||||
|
"power": "",
|
||||||
|
"cpu": "",
|
||||||
|
"memory": "",
|
||||||
|
"storage": "",
|
||||||
|
"updates": "",
|
||||||
|
"thermometer": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
distro_text_icons = {
|
||||||
|
"deepin": "",
|
||||||
|
"fedora": "",
|
||||||
|
"arch": "",
|
||||||
|
"nixos": "",
|
||||||
|
"debian": "",
|
||||||
|
"opensuse-tumbleweed": "",
|
||||||
|
"ubuntu": "",
|
||||||
|
"endeavouros": "",
|
||||||
|
"manjaro": "",
|
||||||
|
"popos": "",
|
||||||
|
"garuda": "",
|
||||||
|
"zorin": "",
|
||||||
|
"mxlinux": "",
|
||||||
|
"arcolinux": "",
|
||||||
|
"gentoo": "",
|
||||||
|
"artix": "",
|
||||||
|
"centos": "",
|
||||||
|
"hyperbola": "",
|
||||||
|
"kubuntu": "",
|
||||||
|
"mandriva": "",
|
||||||
|
"xerolinux": "",
|
||||||
|
"parabola": "",
|
||||||
|
"void": "",
|
||||||
|
"linuxmint": "",
|
||||||
|
"archlabs": "",
|
||||||
|
"devuan": "",
|
||||||
|
"freebsd": "",
|
||||||
|
"openbsd": "",
|
||||||
|
"slackware": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
# sourced from wttr.in
|
||||||
|
weather_text_icons = {
|
||||||
|
"113": {"description": "Sunny", "icon": ""},
|
||||||
|
"116": {"description": "PartlyCloudy", "icon": ""},
|
||||||
|
"119": {"description": "Cloudy", "icon": ""},
|
||||||
|
"122": {"description": "VeryCloudy", "icon": ""},
|
||||||
|
"143": {"description": "Fog", "icon": ""},
|
||||||
|
"176": {"description": "LightShowers", "icon": ""},
|
||||||
|
"179": {"description": "LightSleetShowers", "icon": ""},
|
||||||
|
"182": {"description": "LightSleet", "icon": ""},
|
||||||
|
"185": {"description": "LightSleet", "icon": ""},
|
||||||
|
"200": {"description": "ThunderyShowers", "icon": ""},
|
||||||
|
"227": {"description": "LightSnow", "icon": ""},
|
||||||
|
"230": {"description": "HeavySnow", "icon": ""},
|
||||||
|
"248": {"description": "Fog", "icon": ""},
|
||||||
|
"260": {"description": "Fog", "icon": ""},
|
||||||
|
"263": {"description": "LightShowers", "icon": ""},
|
||||||
|
"266": {"description": "LightRain", "icon": ""},
|
||||||
|
"281": {"description": "LightSleet", "icon": ""},
|
||||||
|
"284": {"description": "LightSleet", "icon": ""},
|
||||||
|
"293": {"description": "LightRain", "icon": ""},
|
||||||
|
"296": {"description": "LightRain", "icon": ""},
|
||||||
|
"299": {"description": "HeavyShowers", "icon": ""},
|
||||||
|
"302": {"description": "HeavyRain", "icon": ""},
|
||||||
|
"305": {"description": "HeavyShowers", "icon": ""},
|
||||||
|
"308": {"description": "HeavyRain", "icon": ""},
|
||||||
|
"311": {"description": "LightSleet", "icon": ""},
|
||||||
|
"314": {"description": "LightSleet", "icon": ""},
|
||||||
|
"317": {"description": "LightSleet", "icon": ""},
|
||||||
|
"320": {"description": "LightSnow", "icon": ""},
|
||||||
|
"323": {"description": "LightSnowShowers", "icon": ""},
|
||||||
|
"326": {"description": "LightSnowShowers", "icon": ""},
|
||||||
|
"329": {"description": "HeavySnow", "icon": ""},
|
||||||
|
"332": {"description": "HeavySnow", "icon": ""},
|
||||||
|
"335": {"description": "HeavySnowShowers", "icon": ""},
|
||||||
|
"338": {"description": "HeavySnow", "icon": ""},
|
||||||
|
"350": {"description": "LightSleet", "icon": ""},
|
||||||
|
"353": {"description": "LightShowers", "icon": ""},
|
||||||
|
"356": {"description": "HeavyShowers", "icon": ""},
|
||||||
|
"359": {"description": "HeavyRain", "icon": ""},
|
||||||
|
"362": {"description": "LightSleetShowers", "icon": ""},
|
||||||
|
"365": {"description": "LightSleetShowers", "icon": ""},
|
||||||
|
"368": {"description": "LightSnowShowers", "icon": ""},
|
||||||
|
"371": {"description": "HeavySnowShowers", "icon": ""},
|
||||||
|
"374": {"description": "LightSleetShowers", "icon": ""},
|
||||||
|
"377": {"description": "LightSleet", "icon": ""},
|
||||||
|
"386": {"description": "ThunderyShowers", "icon": ""},
|
||||||
|
"389": {"description": "ThunderyHeavyRain", "icon": ""},
|
||||||
|
"392": {"description": "ThunderySnowShowers", "icon": ""},
|
||||||
|
"395": {"description": "HeavySnowShowers", "icon": ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
weather_text_icons_v2 = {
|
||||||
|
"113": {
|
||||||
|
"description": "Sunny",
|
||||||
|
"icon": "",
|
||||||
|
"image": "clear-day",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "clear-night",
|
||||||
|
},
|
||||||
|
"116": {
|
||||||
|
"description": "PartlyCloudy",
|
||||||
|
"icon": "",
|
||||||
|
"image": "cloudy",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "cloudy",
|
||||||
|
},
|
||||||
|
"119": {
|
||||||
|
"description": "Cloudy",
|
||||||
|
"icon": "",
|
||||||
|
"image": "cloudy",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "cloudy",
|
||||||
|
},
|
||||||
|
"122": {
|
||||||
|
"description": "VeryCloudy",
|
||||||
|
"icon": "",
|
||||||
|
"image": "cloudy",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "cloudy",
|
||||||
|
},
|
||||||
|
"143": {
|
||||||
|
"description": "Fog",
|
||||||
|
"icon": "",
|
||||||
|
"image": "fog",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "fog",
|
||||||
|
},
|
||||||
|
"176": {
|
||||||
|
"description": "LightShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"179": {
|
||||||
|
"description": "LightSleetShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"182": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"185": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"description": "ThunderyShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "thunderstorms",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "thunderstorms",
|
||||||
|
},
|
||||||
|
"227": {
|
||||||
|
"description": "LightSnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"230": {
|
||||||
|
"description": "HeavySnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"248": {
|
||||||
|
"description": "Fog",
|
||||||
|
"icon": "",
|
||||||
|
"image": "fog",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "fog",
|
||||||
|
},
|
||||||
|
"260": {
|
||||||
|
"description": "Fog",
|
||||||
|
"icon": "",
|
||||||
|
"image": "fog",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "fog",
|
||||||
|
},
|
||||||
|
"263": {
|
||||||
|
"description": "LightShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"266": {
|
||||||
|
"description": "LightRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"281": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"284": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"293": {
|
||||||
|
"description": "LightRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"296": {
|
||||||
|
"description": "LightRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"299": {
|
||||||
|
"description": "HeavyShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"302": {
|
||||||
|
"description": "HeavyRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"305": {
|
||||||
|
"description": "HeavyShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"308": {
|
||||||
|
"description": "HeavyRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"311": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"314": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"317": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"320": {
|
||||||
|
"description": "LightSnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"323": {
|
||||||
|
"description": "LightSnowShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"326": {
|
||||||
|
"description": "LightSnowShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"329": {
|
||||||
|
"description": "HeavySnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"332": {
|
||||||
|
"description": "HeavySnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"335": {
|
||||||
|
"description": "HeavySnowShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"338": {
|
||||||
|
"description": "HeavySnow",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"350": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"353": {
|
||||||
|
"description": "LightShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"356": {
|
||||||
|
"description": "HeavyShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"359": {
|
||||||
|
"description": "HeavyRain",
|
||||||
|
"icon": "",
|
||||||
|
"image": "rain",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "rain",
|
||||||
|
},
|
||||||
|
"362": {
|
||||||
|
"description": "LightSleetShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"365": {
|
||||||
|
"description": "HeavySleetShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"368": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"371": {
|
||||||
|
"description": "HeavySleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
"374": {
|
||||||
|
"description": "HeavySnowShowers",
|
||||||
|
"icon": "",
|
||||||
|
"image": "snow",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "snow",
|
||||||
|
},
|
||||||
|
"377": {
|
||||||
|
"description": "LightSleet",
|
||||||
|
"icon": "",
|
||||||
|
"image": "sleet",
|
||||||
|
"icon-night": "",
|
||||||
|
"image-night": "sleet",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_text_icons = {
|
||||||
|
"overamplified": "",
|
||||||
|
"high": "",
|
||||||
|
"medium": "",
|
||||||
|
"low": "",
|
||||||
|
"muted": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_text_icons = {
|
||||||
|
"overamplified": "",
|
||||||
|
"high": "",
|
||||||
|
"medium": "",
|
||||||
|
"low": "",
|
||||||
|
"muted": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
brightness_text_icons = {
|
||||||
|
"off": "", # lowest brightness
|
||||||
|
"low": "",
|
||||||
|
"medium": "",
|
||||||
|
"high": "", # highest brightness
|
||||||
|
}
|
||||||
|
|
||||||
|
icons = {
|
||||||
|
"missing": "image-missing-symbolic",
|
||||||
|
"nix": {
|
||||||
|
"nix": "nix-snowflake-symbolic",
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"terminal": "terminal-symbolic",
|
||||||
|
},
|
||||||
|
"fallback": {
|
||||||
|
"executable": "application-x-executable",
|
||||||
|
"notification": "dialog-information-symbolic",
|
||||||
|
"video": "video-x-generic-symbolic",
|
||||||
|
"audio": "audio-x-generic-symbolic",
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"close": "window-close-symbolic",
|
||||||
|
"colorpicker": "color-select-symbolic",
|
||||||
|
"info": "info-symbolic",
|
||||||
|
"link": "external-link-symbolic",
|
||||||
|
"lock": "system-lock-screen-symbolic",
|
||||||
|
"menu": "open-menu-symbolic",
|
||||||
|
"refresh": "view-refresh-symbolic",
|
||||||
|
"search": "system-search-symbolic",
|
||||||
|
"settings": "emblem-system-symbolic",
|
||||||
|
"themes": "preferences-desktop-theme-symbolic",
|
||||||
|
"tick": "object-select-symbolic",
|
||||||
|
"time": "hourglass-symbolic",
|
||||||
|
"toolbars": "toolbars-symbolic",
|
||||||
|
"warning": "dialog-warning-symbolic",
|
||||||
|
"avatar": "avatar-default-symbolic",
|
||||||
|
"arrow": {
|
||||||
|
"right": "pan-end-symbolic",
|
||||||
|
"left": "pan-start-symbolic",
|
||||||
|
"down": "pan-down-symbolic",
|
||||||
|
"up": "pan-up-symbolic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"mic": {
|
||||||
|
"muted": "microphone-disabled-symbolic",
|
||||||
|
"low": "microphone-sensitivity-low-symbolic",
|
||||||
|
"medium": "microphone-sensitivity-medium-symbolic",
|
||||||
|
"high": "microphone-sensitivity-high-symbolic",
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"muted": "audio-volume-muted-symbolic",
|
||||||
|
"low": "audio-volume-low-symbolic",
|
||||||
|
"medium": "audio-volume-medium-symbolic",
|
||||||
|
"high": "audio-volume-high-symbolic",
|
||||||
|
"overamplified": "audio-volume-overamplified-symbolic",
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"headset": "audio-headphones-symbolic",
|
||||||
|
"speaker": "audio-speakers-symbolic",
|
||||||
|
"card": "audio-card-symbolic",
|
||||||
|
},
|
||||||
|
"mixer": "mixer-symbolic",
|
||||||
|
},
|
||||||
|
"powerprofile": {
|
||||||
|
"balanced": "power-profile-balanced-symbolic",
|
||||||
|
"power-saver": "power-profile-power-saver-symbolic",
|
||||||
|
"performance": "power-profile-performance-symbolic",
|
||||||
|
},
|
||||||
|
"battery": {
|
||||||
|
"charging": "battery-flash-symbolic",
|
||||||
|
"warning": "battery-empty-symbolic",
|
||||||
|
},
|
||||||
|
"bluetooth": {
|
||||||
|
"enabled": "bluetooth-active-symbolic",
|
||||||
|
"disabled": "bluetooth-disabled-symbolic",
|
||||||
|
},
|
||||||
|
"brightness": {
|
||||||
|
"indicator": "display-brightness-symbolic",
|
||||||
|
"keyboard": "keyboard-brightness-symbolic",
|
||||||
|
"screen": "display-brightness-symbolic",
|
||||||
|
},
|
||||||
|
"powermenu": {
|
||||||
|
"sleep": "weather-clear-night-symbolic",
|
||||||
|
"reboot": "system-reboot-symbolic",
|
||||||
|
"logout": "system-log-out-symbolic",
|
||||||
|
"shutdown": "system-shutdown-symbolic",
|
||||||
|
},
|
||||||
|
"recorder": {
|
||||||
|
"recording": "media-record-symbolic",
|
||||||
|
"stopped": "media-record-symbolic",
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"noisy": "org.gnome.Settings-notifications-symbolic",
|
||||||
|
"silent": "notifications-disabled-symbolic",
|
||||||
|
"message": "chat-bubbles-symbolic",
|
||||||
|
},
|
||||||
|
"trash": {
|
||||||
|
"full": "user-trash-full-symbolic",
|
||||||
|
"empty": "user-trash-symbolic",
|
||||||
|
},
|
||||||
|
"mpris": {
|
||||||
|
"shuffle": {
|
||||||
|
"enabled": "media-playlist-shuffle-symbolic",
|
||||||
|
"disabled": "media-playlist-consecutive-symbolic",
|
||||||
|
},
|
||||||
|
"loop": {
|
||||||
|
"none": "media-playlist-repeat-symbolic",
|
||||||
|
"track": "media-playlist-repeat-song-symbolic",
|
||||||
|
"playlist": "media-playlist-repeat-symbolic",
|
||||||
|
},
|
||||||
|
"playing": "media-playback-pause-symbolic",
|
||||||
|
"paused": "media-playback-start-symbolic",
|
||||||
|
"stopped": "media-playback-start-symbolic",
|
||||||
|
"prev": "media-skip-backward-symbolic",
|
||||||
|
"next": "media-skip-forward-symbolic",
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"cpu": "org.gnome.SystemMonitor-symbolic",
|
||||||
|
"ram": "drive-harddisk-solidstate-symbolic",
|
||||||
|
"temp": "temperature-symbolic",
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"dark": "dark-mode-symbolic",
|
||||||
|
"light": "light-mode-symbolic",
|
||||||
|
},
|
||||||
|
}
|
||||||
126
bar/widgets/circle_image.py
Normal file
126
bar/widgets/circle_image.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import math
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
import cairo
|
||||||
|
import gi
|
||||||
|
from fabric.core.service import Property
|
||||||
|
from fabric.widgets.widget import Widget
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gdk, GdkPixbuf, Gtk # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class CircleImage(Gtk.DrawingArea, Widget):
|
||||||
|
"""A widget that displays an image in a circular shape with a 1:1 aspect ratio."""
|
||||||
|
|
||||||
|
@Property(int, "read-write")
|
||||||
|
def angle(self) -> int:
|
||||||
|
return self._angle
|
||||||
|
|
||||||
|
@angle.setter
|
||||||
|
def angle(self, value: int):
|
||||||
|
self._angle = value % 360
|
||||||
|
self.queue_draw()
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
image_file: str | None = None,
|
||||||
|
pixbuf: GdkPixbuf.Pixbuf | None = None,
|
||||||
|
name: str | None = None,
|
||||||
|
visible: bool = True,
|
||||||
|
all_visible: bool = False,
|
||||||
|
style: str | None = None,
|
||||||
|
tooltip_text: str | None = None,
|
||||||
|
tooltip_markup: str | None = None,
|
||||||
|
h_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||||
|
| Gtk.Align
|
||||||
|
| None = None,
|
||||||
|
v_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||||
|
| Gtk.Align
|
||||||
|
| None = None,
|
||||||
|
h_expand: bool = False,
|
||||||
|
v_expand: bool = False,
|
||||||
|
size: int | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
Gtk.DrawingArea.__init__(self)
|
||||||
|
Widget.__init__(
|
||||||
|
self,
|
||||||
|
name=name,
|
||||||
|
visible=visible,
|
||||||
|
all_visible=all_visible,
|
||||||
|
style=style,
|
||||||
|
tooltip_text=tooltip_text,
|
||||||
|
tooltip_markup=tooltip_markup,
|
||||||
|
h_align=h_align,
|
||||||
|
v_align=v_align,
|
||||||
|
h_expand=h_expand,
|
||||||
|
v_expand=v_expand,
|
||||||
|
size=size,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self.size = size if size is not None else 100 # Default size if not provided
|
||||||
|
self._angle = 0
|
||||||
|
self._orig_image: GdkPixbuf.Pixbuf | None = (
|
||||||
|
None # Original image for reprocessing
|
||||||
|
)
|
||||||
|
self._image: GdkPixbuf.Pixbuf | None = None
|
||||||
|
if image_file:
|
||||||
|
pix = GdkPixbuf.Pixbuf.new_from_file(image_file)
|
||||||
|
self._orig_image = pix
|
||||||
|
self._image = self._process_image(pix)
|
||||||
|
elif pixbuf:
|
||||||
|
self._orig_image = pixbuf
|
||||||
|
self._image = self._process_image(pixbuf)
|
||||||
|
self.connect("draw", self.on_draw)
|
||||||
|
|
||||||
|
def _process_image(self, pixbuf: GdkPixbuf.Pixbuf) -> GdkPixbuf.Pixbuf:
|
||||||
|
"""Crop the image to a centered square and scale it to the widget’s size."""
|
||||||
|
width, height = pixbuf.get_width(), pixbuf.get_height()
|
||||||
|
if width != height:
|
||||||
|
square_size = min(width, height)
|
||||||
|
x_offset = (width - square_size) // 2
|
||||||
|
y_offset = (height - square_size) // 2
|
||||||
|
pixbuf = pixbuf.new_subpixbuf(x_offset, y_offset, square_size, square_size)
|
||||||
|
else:
|
||||||
|
square_size = width
|
||||||
|
if square_size != self.size:
|
||||||
|
pixbuf = pixbuf.scale_simple(
|
||||||
|
self.size, self.size, GdkPixbuf.InterpType.BILINEAR
|
||||||
|
)
|
||||||
|
return pixbuf
|
||||||
|
|
||||||
|
def on_draw(self, widget: "CircleImage", ctx: cairo.Context):
|
||||||
|
if self._image:
|
||||||
|
ctx.save()
|
||||||
|
# Create a circular clipping path
|
||||||
|
ctx.arc(self.size / 2, self.size / 2, self.size / 2, 0, 2 * math.pi)
|
||||||
|
ctx.clip()
|
||||||
|
# Rotate around the center of the square image
|
||||||
|
ctx.translate(self.size / 2, self.size / 2)
|
||||||
|
ctx.rotate(self._angle * math.pi / 180.0)
|
||||||
|
ctx.translate(-self.size / 2, -self.size / 2)
|
||||||
|
Gdk.cairo_set_source_pixbuf(ctx, self._image, 0, 0)
|
||||||
|
ctx.paint()
|
||||||
|
ctx.restore()
|
||||||
|
|
||||||
|
def set_image_from_file(self, new_image_file: str):
|
||||||
|
if not new_image_file:
|
||||||
|
return
|
||||||
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(new_image_file)
|
||||||
|
self._orig_image = pixbuf
|
||||||
|
self._image = self._process_image(pixbuf)
|
||||||
|
self.queue_draw()
|
||||||
|
|
||||||
|
def set_image_from_pixbuf(self, pixbuf: GdkPixbuf.Pixbuf):
|
||||||
|
if not pixbuf:
|
||||||
|
return
|
||||||
|
self._orig_image = pixbuf
|
||||||
|
self._image = self._process_image(pixbuf)
|
||||||
|
self.queue_draw()
|
||||||
|
|
||||||
|
def set_image_size(self, size: int):
|
||||||
|
self.size = size
|
||||||
|
if self._orig_image:
|
||||||
|
self._image = self._process_image(self._orig_image)
|
||||||
|
self.queue_draw()
|
||||||
2
example.yaml
Normal file
2
example.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
vinyl:
|
||||||
|
enabled: true
|
||||||
57
flake.lock
generated
57
flake.lock
generated
@@ -6,47 +6,67 @@
|
|||||||
"utils": "utils"
|
"utils": "utils"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725442219,
|
"lastModified": 1745289078,
|
||||||
"narHash": "sha256-xgTjqwlAgfY0Kv6G6CogOV2pN6U0wllRYteVAAZs7BU=",
|
"narHash": "sha256-1dZTqsWPaHyWjZkX4MaJdwUAQoMXwr8hhHymxQIwFrY=",
|
||||||
"owner": "wholikeel",
|
"owner": "Fabric-Development",
|
||||||
"repo": "fabric-nix",
|
"repo": "fabric",
|
||||||
"rev": "3bc86cfb8c988ff5488526a47e1914f03a34a87c",
|
"rev": "1831ced4d9bb9f4be3893be55a8d502b47bff29e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "wholikeel",
|
"owner": "Fabric-Development",
|
||||||
"repo": "fabric-nix",
|
"repo": "fabric",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1746369725,
|
||||||
|
"narHash": "sha256-m3ai7LLFYsymMK0uVywCceWfUhP0k3CALyFOfcJACqE=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "1a1793f6d940d22c6e49753548c5b6cb7dc5545d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717179513,
|
"lastModified": 1733261153,
|
||||||
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
|
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
|
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "24.05",
|
"ref": "nixos-24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717179513,
|
"lastModified": 1731603435,
|
||||||
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
|
"narHash": "sha256-CqCX4JG7UiHvkrBTpYC3wcEurvbtTADLbo3Ns2CEoL8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
|
"rev": "8b27c1239e5c421a2bbc2c65d52e4a6fbf2ff296",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "24.05",
|
"ref": "24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -54,6 +74,7 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fabric": "fabric",
|
"fabric": "fabric",
|
||||||
|
"home-manager": "home-manager",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"unstable": "unstable",
|
"unstable": "unstable",
|
||||||
"utils": "utils_2"
|
"utils": "utils_2"
|
||||||
@@ -110,11 +131,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
92
flake.nix
92
flake.nix
@@ -2,10 +2,12 @@
|
|||||||
description = "Fabric Bar Example";
|
description = "Fabric Bar Example";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/24.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/24.11";
|
||||||
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
utils.url = "github:numtide/flake-utils";
|
utils.url = "github:numtide/flake-utils";
|
||||||
fabric.url = "github:wholikeel/fabric-nix";
|
fabric.url = "github:Fabric-Development/fabric";
|
||||||
|
home-manager.url = "github:nix-community/home-manager";
|
||||||
|
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
@@ -29,12 +31,86 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
formatter = pkgs.nixfmt-rfc-style;
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
devShells.default = pkgs.callPackage ./shell.nix { inherit pkgs; };
|
devShells.default = pkgs.callPackage ./nix/shell.nix { inherit pkgs; };
|
||||||
packages.default = pkgs.callPackage ./derivation.nix { inherit (pkgs) lib python3Packages; };
|
packages = {
|
||||||
apps.default = {
|
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
||||||
type = "app";
|
makku = pkgs.writeShellScriptBin "makku" ''
|
||||||
program = "${self.packages.${system}.default}/bin/bar";
|
dbus-send --session --print-reply --dest=org.Fabric.fabric.bar /org/Fabric/fabric org.Fabric.fabric.Evaluate string:"finder.open()" > /dev/null 2>&1
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
apps = {
|
||||||
|
default = {
|
||||||
|
type = "app";
|
||||||
|
program = "${self.packages.${system}.default}/bin/bar";
|
||||||
|
};
|
||||||
|
show = {
|
||||||
|
type = "app";
|
||||||
|
program = "${self.packages.${system}.makku}/bin/makku";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
// {
|
||||||
|
homeManagerModules.makku-bar =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.makku-bar;
|
||||||
|
|
||||||
|
settingsFormat = pkgs.formats.yaml { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.makku-bar = {
|
||||||
|
enable = lib.mkEnableOption "makku-bar status bar";
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = self.packages.${pkgs.system}.default;
|
||||||
|
description = "The makku-bar package to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
vinyl = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = {
|
||||||
|
vinyl.enable = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf config.services.makku-bar.enable {
|
||||||
|
systemd.user.services.makku-bar =
|
||||||
|
let
|
||||||
|
configFile = settingsFormat.generate "config.yaml" cfg.settings;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
Unit = {
|
||||||
|
Description = "Makku Status Bar";
|
||||||
|
After = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${config.services.makku-bar.package}/bin/bar --config ${configFile}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
|
||||||
|
Install = {
|
||||||
|
WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
gobject-introspection,
|
gobject-introspection,
|
||||||
libdbusmenu-gtk3,
|
libdbusmenu-gtk3,
|
||||||
gdk-pixbuf,
|
gdk-pixbuf,
|
||||||
gnome,
|
gnome-bluetooth,
|
||||||
cinnamon,
|
cinnamon-desktop,
|
||||||
wrapGAppsHook3,
|
wrapGAppsHook3,
|
||||||
|
playerctl,
|
||||||
|
webp-pixbuf-loader,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -18,29 +20,49 @@ python3Packages.buildPythonApplication {
|
|||||||
version = "0.0.1";
|
version = "0.0.1";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = ./.;
|
src = ../.;
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
wrapGAppsHook3
|
wrapGAppsHook3
|
||||||
gtk3
|
gtk3
|
||||||
gobject-introspection
|
gobject-introspection
|
||||||
|
python3Packages.pygobject3
|
||||||
cairo
|
cairo
|
||||||
|
playerctl
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
|
libdbusmenu-gtk3
|
||||||
|
gtk-layer-shell
|
||||||
|
gnome-bluetooth
|
||||||
|
cinnamon-desktop
|
||||||
|
gdk-pixbuf
|
||||||
|
playerctl
|
||||||
|
webp-pixbuf-loader
|
||||||
];
|
];
|
||||||
# buildInputs = [
|
|
||||||
# libdbusmenu-gtk3
|
|
||||||
# gtk-layer-shell
|
|
||||||
# gnome.gnome-bluetooth
|
|
||||||
# cinnamon.cinnamon-desktop
|
|
||||||
# gdk-pixbuf
|
|
||||||
# ];
|
|
||||||
|
|
||||||
dependencies = with python3Packages; [
|
dependencies = with python3Packages; [
|
||||||
python-fabric
|
python-fabric
|
||||||
pywayland
|
pywayland
|
||||||
|
pyyaml
|
||||||
|
platformdirs
|
||||||
];
|
];
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
dontWrapGApps = true;
|
dontWrapGApps = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/${python3Packages.python.sitePackages}
|
||||||
|
cp -r bar $out/${python3Packages.python.sitePackages}/
|
||||||
|
|
||||||
|
# If you have any scripts to install
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp scripts/launcher.py $out/bin/bar
|
||||||
|
chmod +x $out/bin/bar
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
||||||
'';
|
'';
|
||||||
@@ -22,11 +22,13 @@ pkgs.mkShell {
|
|||||||
gobject-introspection
|
gobject-introspection
|
||||||
libdbusmenu-gtk3
|
libdbusmenu-gtk3
|
||||||
gdk-pixbuf
|
gdk-pixbuf
|
||||||
gnome.gnome-bluetooth
|
gnome-bluetooth
|
||||||
cinnamon.cinnamon-desktop
|
cinnamon-desktop
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
wayland
|
wayland
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
|
playerctl
|
||||||
|
|
||||||
(python3.withPackages (
|
(python3.withPackages (
|
||||||
ps: with ps; [
|
ps: with ps; [
|
||||||
setuptools
|
setuptools
|
||||||
@@ -39,6 +41,8 @@ pkgs.mkShell {
|
|||||||
pylsp-mypy
|
pylsp-mypy
|
||||||
pyls-isort
|
pyls-isort
|
||||||
python-lsp-ruff
|
python-lsp-ruff
|
||||||
|
pyyaml
|
||||||
|
platformdirs
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
@@ -14,11 +14,11 @@ description = "Fabric using Nix example."
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
[project.scripts]
|
[tool.setuptools]
|
||||||
bar = "bar.bar:main"
|
include-package-data = true
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages]
|
||||||
where = ["."]
|
find = { namespaces = true }
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
bar = ["bar.css"]
|
"*" = ["*.css", "styles"]
|
||||||
|
|||||||
21
scripts/launcher.py
Normal file
21
scripts/launcher.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
site_packages_dir = os.path.join(
|
||||||
|
script_dir,
|
||||||
|
os.pardir,
|
||||||
|
"lib",
|
||||||
|
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
||||||
|
"site-packages",
|
||||||
|
)
|
||||||
|
|
||||||
|
if site_packages_dir not in sys.path:
|
||||||
|
sys.path.insert(0, site_packages_dir)
|
||||||
|
|
||||||
|
|
||||||
|
from bar.main import *
|
||||||
|
|
||||||
|
sys.argv[0] = os.path.join(script_dir, os.path.basename(__file__))
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user