Compare commits
1 Commits
main
...
wlr-finder
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ecec8768d |
16
bar/main.py
16
bar/main.py
@ -3,17 +3,18 @@ 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.river.widgets import (
|
||||
get_river_connection,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
@ -36,7 +37,12 @@ def spawn_bars():
|
||||
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)
|
||||
bar = StatusBar(
|
||||
display=output_id,
|
||||
tray=tray if i == 0 else None,
|
||||
monitor=i,
|
||||
river_service=river,
|
||||
)
|
||||
bar_windows.append(bar)
|
||||
|
||||
return False
|
||||
|
||||
@ -8,18 +8,19 @@ 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.river.widgets import (
|
||||
RiverWorkspaces,
|
||||
RiverWorkspaceButton,
|
||||
RiverActiveWindow,
|
||||
get_river_connection,
|
||||
)
|
||||
|
||||
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):
|
||||
@ -42,8 +43,6 @@ class StatusBar(Window):
|
||||
)
|
||||
if river_service:
|
||||
self.river = river_service
|
||||
else:
|
||||
self.river = get_river_connection()
|
||||
|
||||
self.workspaces = RiverWorkspaces(
|
||||
display,
|
||||
@ -56,6 +55,7 @@ class StatusBar(Window):
|
||||
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;",
|
||||
|
||||
@ -3,8 +3,9 @@ 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 fabric.utils import idle_add
|
||||
from gi.repository import Gdk
|
||||
from bar.services.wlr.service import WaylandWindowTracker, Window as WaylandWindow
|
||||
from pywayland.client import Display
|
||||
|
||||
|
||||
class FuzzyWindowFinder(Window):
|
||||
@ -20,8 +21,9 @@ class FuzzyWindowFinder(Window):
|
||||
type="popup",
|
||||
visible=False,
|
||||
)
|
||||
|
||||
self._all_windows = ["Test", "Uwu", "Tidal"]
|
||||
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")
|
||||
|
||||
@ -46,6 +48,12 @@ class FuzzyWindowFinder(Window):
|
||||
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
|
||||
@ -56,6 +64,8 @@ class FuzzyWindowFinder(Window):
|
||||
# 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
|
||||
@ -67,9 +77,12 @@ class FuzzyWindowFinder(Window):
|
||||
def arrange_viewport(self, query: str = ""):
|
||||
self.viewport.children = [] # Clear previous entries
|
||||
|
||||
filtered = [w for w in self._all_windows if query.lower() in w.lower()]
|
||||
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 filtered:
|
||||
for window in titles:
|
||||
self.viewport.add(
|
||||
Box(name="slot-box", orientation="h", children=[Label(label=window)])
|
||||
)
|
||||
|
||||
0
bar/services/__init__.py
Normal file
0
bar/services/__init__.py
Normal file
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
@ -0,0 +1,18 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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 .zriver_command_callback_v1 import ZriverCommandCallbackV1 # noqa: F401
|
||||
from .zriver_control_v1 import ZriverControlV1 # noqa: F401
|
||||
@ -0,0 +1,84 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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)
|
||||
|
||||
|
||||
class ZriverCommandCallbackV1(Interface):
|
||||
"""Callback object
|
||||
|
||||
This object is created by the run_command request. Exactly one of the
|
||||
success or failure events will be sent. This object will be destroyed by
|
||||
the compositor after one of the events is sent.
|
||||
"""
|
||||
|
||||
name = "zriver_command_callback_v1"
|
||||
version = 1
|
||||
|
||||
|
||||
class ZriverCommandCallbackV1Proxy(Proxy[ZriverCommandCallbackV1]):
|
||||
interface = ZriverCommandCallbackV1
|
||||
|
||||
|
||||
class ZriverCommandCallbackV1Resource(Resource):
|
||||
interface = ZriverCommandCallbackV1
|
||||
|
||||
@ZriverCommandCallbackV1.event(
|
||||
Argument(ArgumentType.String),
|
||||
)
|
||||
def success(self, output: str) -> None:
|
||||
"""Command successful
|
||||
|
||||
Sent when the command has been successfully received and executed by
|
||||
the compositor. Some commands may produce output, in which case the
|
||||
output argument will be a non-empty string.
|
||||
|
||||
:param output:
|
||||
the output of the command
|
||||
:type output:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._post_event(0, output)
|
||||
|
||||
@ZriverCommandCallbackV1.event(
|
||||
Argument(ArgumentType.String),
|
||||
)
|
||||
def failure(self, failure_message: str) -> None:
|
||||
"""Command failed
|
||||
|
||||
Sent when the command could not be carried out. This could be due to
|
||||
sending a non-existent command, no command, not enough arguments, too
|
||||
many arguments, invalid arguments, etc.
|
||||
|
||||
:param failure_message:
|
||||
a message explaining why failure occurred
|
||||
:type failure_message:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._post_event(1, failure_message)
|
||||
|
||||
|
||||
class ZriverCommandCallbackV1Global(Global):
|
||||
interface = ZriverCommandCallbackV1
|
||||
|
||||
|
||||
ZriverCommandCallbackV1._gen_c()
|
||||
ZriverCommandCallbackV1.proxy_class = ZriverCommandCallbackV1Proxy
|
||||
ZriverCommandCallbackV1.resource_class = ZriverCommandCallbackV1Resource
|
||||
ZriverCommandCallbackV1.global_class = ZriverCommandCallbackV1Global
|
||||
@ -0,0 +1,111 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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 pywayland.protocol.wayland import WlSeat
|
||||
from .zriver_command_callback_v1 import ZriverCommandCallbackV1
|
||||
|
||||
|
||||
class ZriverControlV1(Interface):
|
||||
"""Run compositor commands
|
||||
|
||||
This interface allows clients to run compositor commands and receive a
|
||||
success/failure response with output or a failure message respectively.
|
||||
|
||||
Each command is built up in a series of add_argument requests and executed
|
||||
with a run_command request. The first argument is the command to be run.
|
||||
|
||||
A complete list of commands should be made available in the man page of the
|
||||
compositor.
|
||||
"""
|
||||
|
||||
name = "zriver_control_v1"
|
||||
version = 1
|
||||
|
||||
|
||||
class ZriverControlV1Proxy(Proxy[ZriverControlV1]):
|
||||
interface = ZriverControlV1
|
||||
|
||||
@ZriverControlV1.request()
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the river_control object
|
||||
|
||||
This request indicates that the client will not use the river_control
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
"""
|
||||
self._marshal(0)
|
||||
self._destroy()
|
||||
|
||||
@ZriverControlV1.request(
|
||||
Argument(ArgumentType.String),
|
||||
)
|
||||
def add_argument(self, argument: str) -> None:
|
||||
"""Add an argument to the current command
|
||||
|
||||
Arguments are stored by the server in the order they were sent until
|
||||
the run_command request is made.
|
||||
|
||||
:param argument:
|
||||
the argument to add
|
||||
:type argument:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._marshal(1, argument)
|
||||
|
||||
@ZriverControlV1.request(
|
||||
Argument(ArgumentType.Object, interface=WlSeat),
|
||||
Argument(ArgumentType.NewId, interface=ZriverCommandCallbackV1),
|
||||
)
|
||||
def run_command(self, seat: WlSeat) -> Proxy[ZriverCommandCallbackV1]:
|
||||
"""Run the current command
|
||||
|
||||
Execute the command built up using the add_argument request for the
|
||||
given seat.
|
||||
|
||||
:param seat:
|
||||
:type seat:
|
||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
||||
:returns:
|
||||
:class:`~pywayland.protocol.river_control_unstable_v1.ZriverCommandCallbackV1`
|
||||
-- callback object
|
||||
"""
|
||||
callback = self._marshal_constructor(2, ZriverCommandCallbackV1, seat)
|
||||
return callback
|
||||
|
||||
|
||||
class ZriverControlV1Resource(Resource):
|
||||
interface = ZriverControlV1
|
||||
|
||||
|
||||
class ZriverControlV1Global(Global):
|
||||
interface = ZriverControlV1
|
||||
|
||||
|
||||
ZriverControlV1._gen_c()
|
||||
ZriverControlV1.proxy_class = ZriverControlV1Proxy
|
||||
ZriverControlV1.resource_class = ZriverControlV1Resource
|
||||
ZriverControlV1.global_class = ZriverControlV1Global
|
||||
@ -0,0 +1,19 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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 .zriver_output_status_v1 import ZriverOutputStatusV1 # noqa: F401
|
||||
from .zriver_seat_status_v1 import ZriverSeatStatusV1 # noqa: F401
|
||||
from .zriver_status_manager_v1 import ZriverStatusManagerV1 # noqa: F401
|
||||
@ -0,0 +1,134 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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)
|
||||
|
||||
|
||||
class ZriverOutputStatusV1(Interface):
|
||||
"""Track output tags and focus
|
||||
|
||||
This interface allows clients to receive information about the current
|
||||
windowing state of an output.
|
||||
"""
|
||||
|
||||
name = "zriver_output_status_v1"
|
||||
version = 4
|
||||
|
||||
|
||||
class ZriverOutputStatusV1Proxy(Proxy[ZriverOutputStatusV1]):
|
||||
interface = ZriverOutputStatusV1
|
||||
|
||||
@ZriverOutputStatusV1.request()
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the river_output_status object
|
||||
|
||||
This request indicates that the client will not use the
|
||||
river_output_status object any more.
|
||||
"""
|
||||
self._marshal(0)
|
||||
self._destroy()
|
||||
|
||||
|
||||
class ZriverOutputStatusV1Resource(Resource):
|
||||
interface = ZriverOutputStatusV1
|
||||
|
||||
@ZriverOutputStatusV1.event(
|
||||
Argument(ArgumentType.Uint),
|
||||
)
|
||||
def focused_tags(self, tags: int) -> None:
|
||||
"""Focused tags of the output
|
||||
|
||||
Sent once binding the interface and again whenever the tag focus of the
|
||||
output changes.
|
||||
|
||||
:param tags:
|
||||
32-bit bitfield
|
||||
:type tags:
|
||||
`ArgumentType.Uint`
|
||||
"""
|
||||
self._post_event(0, tags)
|
||||
|
||||
@ZriverOutputStatusV1.event(
|
||||
Argument(ArgumentType.Array),
|
||||
)
|
||||
def view_tags(self, tags: list) -> None:
|
||||
"""Tag state of an output's views
|
||||
|
||||
Sent once on binding the interface and again whenever the tag state of
|
||||
the output changes.
|
||||
|
||||
:param tags:
|
||||
array of 32-bit bitfields
|
||||
:type tags:
|
||||
`ArgumentType.Array`
|
||||
"""
|
||||
self._post_event(1, tags)
|
||||
|
||||
@ZriverOutputStatusV1.event(
|
||||
Argument(ArgumentType.Uint),
|
||||
version=2,
|
||||
)
|
||||
def urgent_tags(self, tags: int) -> None:
|
||||
"""Tags of the output with an urgent view
|
||||
|
||||
Sent once on binding the interface and again whenever the set of tags
|
||||
with at least one urgent view changes.
|
||||
|
||||
:param tags:
|
||||
32-bit bitfield
|
||||
:type tags:
|
||||
`ArgumentType.Uint`
|
||||
"""
|
||||
self._post_event(2, tags)
|
||||
|
||||
@ZriverOutputStatusV1.event(
|
||||
Argument(ArgumentType.String),
|
||||
version=4,
|
||||
)
|
||||
def layout_name(self, name: str) -> None:
|
||||
"""Name of the layout
|
||||
|
||||
Sent once on binding the interface should a layout name exist and again
|
||||
whenever the name changes.
|
||||
|
||||
:param name:
|
||||
layout name
|
||||
:type name:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._post_event(3, name)
|
||||
|
||||
@ZriverOutputStatusV1.event(version=4)
|
||||
def layout_name_clear(self) -> None:
|
||||
"""Name of the layout
|
||||
|
||||
Sent when the current layout name has been removed without a new one
|
||||
being set, for example when the active layout generator disconnects.
|
||||
"""
|
||||
self._post_event(4)
|
||||
|
||||
|
||||
class ZriverOutputStatusV1Global(Global):
|
||||
interface = ZriverOutputStatusV1
|
||||
|
||||
|
||||
ZriverOutputStatusV1._gen_c()
|
||||
ZriverOutputStatusV1.proxy_class = ZriverOutputStatusV1Proxy
|
||||
ZriverOutputStatusV1.resource_class = ZriverOutputStatusV1Resource
|
||||
ZriverOutputStatusV1.global_class = ZriverOutputStatusV1Global
|
||||
@ -0,0 +1,124 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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.wayland import WlOutput
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
|
||||
class ZriverSeatStatusV1(Interface):
|
||||
"""Track seat focus
|
||||
|
||||
This interface allows clients to receive information about the current
|
||||
focus of a seat. Note that (un)focused_output events will only be sent if
|
||||
the client has bound the relevant
|
||||
:class:`~pywayland.protocol.wayland.WlOutput` globals.
|
||||
"""
|
||||
|
||||
name = "zriver_seat_status_v1"
|
||||
version = 3
|
||||
|
||||
|
||||
class ZriverSeatStatusV1Proxy(Proxy[ZriverSeatStatusV1]):
|
||||
interface = ZriverSeatStatusV1
|
||||
|
||||
@ZriverSeatStatusV1.request()
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the river_seat_status object
|
||||
|
||||
This request indicates that the client will not use the
|
||||
river_seat_status object any more.
|
||||
"""
|
||||
self._marshal(0)
|
||||
self._destroy()
|
||||
|
||||
|
||||
class ZriverSeatStatusV1Resource(Resource):
|
||||
interface = ZriverSeatStatusV1
|
||||
|
||||
@ZriverSeatStatusV1.event(
|
||||
Argument(ArgumentType.Object, interface=WlOutput),
|
||||
)
|
||||
def focused_output(self, output: WlOutput) -> None:
|
||||
"""The seat focused an output
|
||||
|
||||
Sent on binding the interface and again whenever an output gains focus.
|
||||
|
||||
:param output:
|
||||
:type output:
|
||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
||||
"""
|
||||
self._post_event(0, output)
|
||||
|
||||
@ZriverSeatStatusV1.event(
|
||||
Argument(ArgumentType.Object, interface=WlOutput),
|
||||
)
|
||||
def unfocused_output(self, output: WlOutput) -> None:
|
||||
"""The seat unfocused an output
|
||||
|
||||
Sent whenever an output loses focus.
|
||||
|
||||
:param output:
|
||||
:type output:
|
||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
||||
"""
|
||||
self._post_event(1, output)
|
||||
|
||||
@ZriverSeatStatusV1.event(
|
||||
Argument(ArgumentType.String),
|
||||
)
|
||||
def focused_view(self, title: str) -> None:
|
||||
"""Information on the focused view
|
||||
|
||||
Sent once on binding the interface and again whenever the focused view
|
||||
or a property thereof changes. The title may be an empty string if no
|
||||
view is focused or the focused view did not set a title.
|
||||
|
||||
:param title:
|
||||
title of the focused view
|
||||
:type title:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._post_event(2, title)
|
||||
|
||||
@ZriverSeatStatusV1.event(
|
||||
Argument(ArgumentType.String),
|
||||
version=3,
|
||||
)
|
||||
def mode(self, name: str) -> None:
|
||||
"""The active mode changed
|
||||
|
||||
Sent once on binding the interface and again whenever a new mode is
|
||||
entered (e.g. with riverctl enter-mode foobar).
|
||||
|
||||
:param name:
|
||||
name of the mode
|
||||
:type name:
|
||||
`ArgumentType.String`
|
||||
"""
|
||||
self._post_event(3, name)
|
||||
|
||||
|
||||
class ZriverSeatStatusV1Global(Global):
|
||||
interface = ZriverSeatStatusV1
|
||||
|
||||
|
||||
ZriverSeatStatusV1._gen_c()
|
||||
ZriverSeatStatusV1.proxy_class = ZriverSeatStatusV1Proxy
|
||||
ZriverSeatStatusV1.resource_class = ZriverSeatStatusV1Resource
|
||||
ZriverSeatStatusV1.global_class = ZriverSeatStatusV1Global
|
||||
@ -0,0 +1,102 @@
|
||||
# This file has been autogenerated by the pywayland scanner
|
||||
|
||||
# Copyright 2020 The River Developers
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, 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.wayland import WlOutput, WlSeat
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
from .zriver_output_status_v1 import ZriverOutputStatusV1
|
||||
from .zriver_seat_status_v1 import ZriverSeatStatusV1
|
||||
|
||||
|
||||
class ZriverStatusManagerV1(Interface):
|
||||
"""Manage river status objects
|
||||
|
||||
A global factory for objects that receive status information specific to
|
||||
river. It could be used to implement, for example, a status bar.
|
||||
"""
|
||||
|
||||
name = "zriver_status_manager_v1"
|
||||
version = 4
|
||||
|
||||
|
||||
class ZriverStatusManagerV1Proxy(Proxy[ZriverStatusManagerV1]):
|
||||
interface = ZriverStatusManagerV1
|
||||
|
||||
@ZriverStatusManagerV1.request()
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the river_status_manager object
|
||||
|
||||
This request indicates that the client will not use the
|
||||
river_status_manager object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
"""
|
||||
self._marshal(0)
|
||||
self._destroy()
|
||||
|
||||
@ZriverStatusManagerV1.request(
|
||||
Argument(ArgumentType.NewId, interface=ZriverOutputStatusV1),
|
||||
Argument(ArgumentType.Object, interface=WlOutput),
|
||||
)
|
||||
def get_river_output_status(self, output: WlOutput) -> Proxy[ZriverOutputStatusV1]:
|
||||
"""Create an output status object
|
||||
|
||||
This creates a new river_output_status object for the given
|
||||
:class:`~pywayland.protocol.wayland.WlOutput`.
|
||||
|
||||
:param output:
|
||||
:type output:
|
||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
||||
:returns:
|
||||
:class:`~pywayland.protocol.river_status_unstable_v1.ZriverOutputStatusV1`
|
||||
"""
|
||||
id = self._marshal_constructor(1, ZriverOutputStatusV1, output)
|
||||
return id
|
||||
|
||||
@ZriverStatusManagerV1.request(
|
||||
Argument(ArgumentType.NewId, interface=ZriverSeatStatusV1),
|
||||
Argument(ArgumentType.Object, interface=WlSeat),
|
||||
)
|
||||
def get_river_seat_status(self, seat: WlSeat) -> Proxy[ZriverSeatStatusV1]:
|
||||
"""Create a seat status object
|
||||
|
||||
This creates a new river_seat_status object for the given
|
||||
:class:`~pywayland.protocol.wayland.WlSeat`.
|
||||
|
||||
:param seat:
|
||||
:type seat:
|
||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
||||
:returns:
|
||||
:class:`~pywayland.protocol.river_status_unstable_v1.ZriverSeatStatusV1`
|
||||
"""
|
||||
id = self._marshal_constructor(2, ZriverSeatStatusV1, seat)
|
||||
return id
|
||||
|
||||
|
||||
class ZriverStatusManagerV1Resource(Resource):
|
||||
interface = ZriverStatusManagerV1
|
||||
|
||||
|
||||
class ZriverStatusManagerV1Global(Global):
|
||||
interface = ZriverStatusManagerV1
|
||||
|
||||
|
||||
ZriverStatusManagerV1._gen_c()
|
||||
ZriverStatusManagerV1.proxy_class = ZriverStatusManagerV1Proxy
|
||||
ZriverStatusManagerV1.resource_class = ZriverStatusManagerV1Resource
|
||||
ZriverStatusManagerV1.global_class = ZriverStatusManagerV1Global
|
||||
85
bar/services/river/protocols/river-control-unstable-v1.xml
Normal file
85
bar/services/river/protocols/river-control-unstable-v1.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_control_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, 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="zriver_control_v1" version="1">
|
||||
<description summary="run compositor commands">
|
||||
This interface allows clients to run compositor commands and receive a
|
||||
success/failure response with output or a failure message respectively.
|
||||
|
||||
Each command is built up in a series of add_argument requests and
|
||||
executed with a run_command request. The first argument is the command
|
||||
to be run.
|
||||
|
||||
A complete list of commands should be made available in the man page of
|
||||
the compositor.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_control object">
|
||||
This request indicates that the client will not use the
|
||||
river_control object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add_argument">
|
||||
<description summary="add an argument to the current command">
|
||||
Arguments are stored by the server in the order they were sent until
|
||||
the run_command request is made.
|
||||
</description>
|
||||
<arg name="argument" type="string" summary="the argument to add"/>
|
||||
</request>
|
||||
|
||||
<request name="run_command">
|
||||
<description summary="run the current command">
|
||||
Execute the command built up using the add_argument request for the
|
||||
given seat.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
|
||||
summary="callback object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_command_callback_v1" version="1">
|
||||
<description summary="callback object">
|
||||
This object is created by the run_command request. Exactly one of the
|
||||
success or failure events will be sent. This object will be destroyed
|
||||
by the compositor after one of the events is sent.
|
||||
</description>
|
||||
|
||||
<event name="success" type="destructor">
|
||||
<description summary="command successful">
|
||||
Sent when the command has been successfully received and executed by
|
||||
the compositor. Some commands may produce output, in which case the
|
||||
output argument will be a non-empty string.
|
||||
</description>
|
||||
<arg name="output" type="string" summary="the output of the command"/>
|
||||
</event>
|
||||
|
||||
<event name="failure" type="destructor">
|
||||
<description summary="command failed">
|
||||
Sent when the command could not be carried out. This could be due to
|
||||
sending a non-existent command, no command, not enough arguments, too
|
||||
many arguments, invalid arguments, etc.
|
||||
</description>
|
||||
<arg name="failure_message" type="string"
|
||||
summary="a message explaining why failure occurred"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
148
bar/services/river/protocols/river-status-unstable-v1.xml
Normal file
148
bar/services/river/protocols/river-status-unstable-v1.xml
Normal file
@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_status_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, 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="zriver_status_manager_v1" version="4">
|
||||
<description summary="manage river status objects">
|
||||
A global factory for objects that receive status information specific
|
||||
to river. It could be used to implement, for example, a status bar.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_status_manager object">
|
||||
This request indicates that the client will not use the
|
||||
river_status_manager object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="get_river_output_status">
|
||||
<description summary="create an output status object">
|
||||
This creates a new river_output_status object for the given wl_output.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zriver_output_status_v1"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="get_river_seat_status">
|
||||
<description summary="create a seat status object">
|
||||
This creates a new river_seat_status object for the given wl_seat.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_output_status_v1" version="4">
|
||||
<description summary="track output tags and focus">
|
||||
This interface allows clients to receive information about the current
|
||||
windowing state of an output.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_output_status object">
|
||||
This request indicates that the client will not use the
|
||||
river_output_status object any more.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="focused_tags">
|
||||
<description summary="focused tags of the output">
|
||||
Sent once binding the interface and again whenever the tag focus of
|
||||
the output changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
|
||||
<event name="view_tags">
|
||||
<description summary="tag state of an output's views">
|
||||
Sent once on binding the interface and again whenever the tag state
|
||||
of the output changes.
|
||||
</description>
|
||||
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
|
||||
</event>
|
||||
|
||||
<event name="urgent_tags" since="2">
|
||||
<description summary="tags of the output with an urgent view">
|
||||
Sent once on binding the interface and again whenever the set of
|
||||
tags with at least one urgent view changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent once on binding the interface should a layout name exist and again
|
||||
whenever the name changes.
|
||||
</description>
|
||||
<arg name="name" type="string" summary="layout name"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name_clear" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent when the current layout name has been removed without a new one
|
||||
being set, for example when the active layout generator disconnects.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_seat_status_v1" version="3">
|
||||
<description summary="track seat focus">
|
||||
This interface allows clients to receive information about the current
|
||||
focus of a seat. Note that (un)focused_output events will only be sent
|
||||
if the client has bound the relevant wl_output globals.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_seat_status object">
|
||||
This request indicates that the client will not use the
|
||||
river_seat_status object any more.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="focused_output">
|
||||
<description summary="the seat focused an output">
|
||||
Sent on binding the interface and again whenever an output gains focus.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="unfocused_output">
|
||||
<description summary="the seat unfocused an output">
|
||||
Sent whenever an output loses focus.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="focused_view">
|
||||
<description summary="information on the focused view">
|
||||
Sent once on binding the interface and again whenever the focused
|
||||
view or a property thereof changes. The title may be an empty string
|
||||
if no view is focused or the focused view did not set a title.
|
||||
</description>
|
||||
<arg name="title" type="string" summary="title of the focused view"/>
|
||||
</event>
|
||||
|
||||
<event name="mode" since="3">
|
||||
<description summary="the active mode changed">
|
||||
Sent once on binding the interface and again whenever a new mode
|
||||
is entered (e.g. with riverctl enter-mode foobar).
|
||||
</description>
|
||||
<arg name="name" type="string" summary="name of the mode"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
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")
|
||||
10
flake.lock
generated
10
flake.lock
generated
@ -6,15 +6,15 @@
|
||||
"utils": "utils"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1747045720,
|
||||
"narHash": "sha256-2Z0F4hnluJZunwRfx80EQXpjGLhunV2wrseT42nzh7M=",
|
||||
"owner": "Makesesama",
|
||||
"lastModified": 1745289078,
|
||||
"narHash": "sha256-1dZTqsWPaHyWjZkX4MaJdwUAQoMXwr8hhHymxQIwFrY=",
|
||||
"owner": "Fabric-Development",
|
||||
"repo": "fabric",
|
||||
"rev": "dae50c763e8bf2b4e5807b49b9e62425e0725cfa",
|
||||
"rev": "1831ced4d9bb9f4be3893be55a8d502b47bff29e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Makesesama",
|
||||
"owner": "Fabric-Development",
|
||||
"repo": "fabric",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
16
flake.nix
16
flake.nix
@ -5,7 +5,7 @@
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/24.11";
|
||||
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
fabric.url = "github:Makesesama/fabric";
|
||||
fabric.url = "github:Fabric-Development/fabric";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
@ -35,12 +35,18 @@
|
||||
packages = {
|
||||
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
||||
makku = pkgs.writeShellScriptBin "makku" ''
|
||||
dbus-send --session --print-reply --dest=org.Fabric.fabric.bar /org/Fabric/fabric org.Fabric.fabric.Evaluate string:"finder.show()" > /dev/null 2>&1
|
||||
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";
|
||||
apps = {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.default}/bin/bar";
|
||||
};
|
||||
show = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.makku}/bin/makku";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user