move river service to fabric fork
so i could request merge to upstream later
This commit is contained in:
parent
743e1ed0c5
commit
72c76c9fda
@ -5,7 +5,7 @@ from loguru import logger
|
||||
from fabric import Application
|
||||
from fabric.system_tray.widgets import SystemTray
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from .river.widgets import (
|
||||
from fabric.river.widgets import (
|
||||
get_river_connection,
|
||||
)
|
||||
from fabric.utils import (
|
||||
|
||||
@ -8,7 +8,7 @@ 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 ..river.widgets import (
|
||||
from fabric.river.widgets import (
|
||||
RiverWorkspaces,
|
||||
RiverWorkspaceButton,
|
||||
RiverActiveWindow,
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
from .service import River
|
||||
|
||||
__all__ = ["River"]
|
||||
@ -1,18 +0,0 @@
|
||||
# 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
|
||||
@ -1,84 +0,0 @@
|
||||
# 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
|
||||
@ -1,111 +0,0 @@
|
||||
# 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
|
||||
@ -1,19 +0,0 @@
|
||||
# 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
|
||||
@ -1,134 +0,0 @@
|
||||
# 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
|
||||
@ -1,124 +0,0 @@
|
||||
# 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
|
||||
@ -1,102 +0,0 @@
|
||||
# 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
|
||||
@ -1,85 +0,0 @@
|
||||
<?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>
|
||||
@ -1,148 +0,0 @@
|
||||
<?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>
|
||||
@ -1,356 +0,0 @@
|
||||
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 .generated.river_control_unstable_v1 import ZriverControlV1
|
||||
from .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.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')}"
|
||||
)
|
||||
|
||||
display = Display()
|
||||
display.connect()
|
||||
logger.debug("[RiverService] Display connection created")
|
||||
|
||||
# Get the registry
|
||||
registry = display.get_registry()
|
||||
logger.debug("[RiverService] Registry obtained")
|
||||
|
||||
# Create state object to hold our data
|
||||
state = {
|
||||
"display": 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")
|
||||
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")
|
||||
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")
|
||||
self._display = display
|
||||
|
||||
# Mark service as ready
|
||||
idle_add(self._set_ready)
|
||||
|
||||
# Main event loop
|
||||
logger.info("[RiverService] Entering main event loop")
|
||||
while True:
|
||||
display.dispatch(block=True)
|
||||
time.sleep(0.01) # Small sleep to prevent CPU spinning
|
||||
|
||||
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)
|
||||
@ -1,284 +0,0 @@
|
||||
from fabric.core.service import Property
|
||||
from fabric.utils.helpers import bulk_connect
|
||||
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
|
||||
else:
|
||||
self.river = get_river_connection()
|
||||
|
||||
# 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"""
|
||||
logger.debug(
|
||||
f"[RiverWorkspaces] Service ready, outputs: {list(self.river.outputs.keys())}"
|
||||
)
|
||||
|
||||
# If no output_id was specified, take the first one
|
||||
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}")
|
||||
|
||||
# Initialize state from selected output
|
||||
if self.output_id is not None and self.output_id in self.river.outputs:
|
||||
output_info = self.river.outputs[self.output_id]
|
||||
|
||||
# Initialize buttons with current state
|
||||
# Access fields directly on the OutputInfo dataclass
|
||||
focused_tags = output_info.tags_focused
|
||||
view_tags = output_info.tags_view
|
||||
urgent_tags = output_info.tags_urgent
|
||||
|
||||
logger.debug(
|
||||
f"[RiverWorkspaces] Initial state - focused: {focused_tags}, view: {view_tags}, urgent: {urgent_tags}"
|
||||
)
|
||||
|
||||
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.debug(
|
||||
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.debug(
|
||||
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.debug(
|
||||
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.debug(
|
||||
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.debug(
|
||||
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.debug(
|
||||
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
|
||||
else:
|
||||
self.river = get_river_connection()
|
||||
|
||||
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.debug("[RiverActiveWindow] Service ready")
|
||||
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)
|
||||
22
flake.lock
generated
22
flake.lock
generated
@ -6,15 +6,15 @@
|
||||
"utils": "utils"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745289078,
|
||||
"narHash": "sha256-1dZTqsWPaHyWjZkX4MaJdwUAQoMXwr8hhHymxQIwFrY=",
|
||||
"owner": "Fabric-Development",
|
||||
"lastModified": 1747045720,
|
||||
"narHash": "sha256-2Z0F4hnluJZunwRfx80EQXpjGLhunV2wrseT42nzh7M=",
|
||||
"owner": "Makesesama",
|
||||
"repo": "fabric",
|
||||
"rev": "1831ced4d9bb9f4be3893be55a8d502b47bff29e",
|
||||
"rev": "dae50c763e8bf2b4e5807b49b9e62425e0725cfa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Fabric-Development",
|
||||
"owner": "Makesesama",
|
||||
"repo": "fabric",
|
||||
"type": "github"
|
||||
}
|
||||
@ -41,11 +41,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1746810718,
|
||||
"narHash": "sha256-VljtYzyttmvkWUKTVJVW93qAsJsrBbgAzy7DdnJaQfI=",
|
||||
"lastModified": 1733261153,
|
||||
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0c0bf9c057382d5f6f63d54fd61f1abd5e1c2f63",
|
||||
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -131,11 +131,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@ -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:Fabric-Development/fabric";
|
||||
fabric.url = "github:Makesesama/fabric";
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user