This commit is contained in:
Ezri Brimhall 2025-02-14 17:41:28 -07:00
parent c8d260892c
commit d4b818029e
Signed by: ezri
GPG Key ID: 058A78E5680C6F24
31 changed files with 828 additions and 10 deletions

0
README.md Normal file
View File

View File

@ -1,5 +0,0 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk

View File

@ -23,3 +23,6 @@ black = "^24.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
VoidShell = 'voidshell.entrypoints.shell:main'

0
voidshell/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1,6 @@
from .link import (
OrgFreedesktopNetwork1DHCPv4ClientInterface as DHCPv4ClientInterface,
OrgFreedesktopNetwork1DHCPv6ClientInterface as DHCPv6ClientInterface,
OrgFreedesktopNetwork1LinkInterface as LinkInterface,
)
from .network1 import OrgFreedesktopNetwork1ManagerInterface as NetworkManagerInterface

View File

@ -0,0 +1,267 @@
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from sdbus import (
DbusDeprecatedFlag,
DbusInterfaceCommonAsync,
DbusNoReplyFlag,
DbusPropertyConstFlag,
DbusPropertyEmitsChangeFlag,
DbusPropertyEmitsInvalidationFlag,
DbusPropertyExplicitFlag,
DbusUnprivilegedFlag,
dbus_method_async,
dbus_property_async,
dbus_signal_async,
)
class OrgFreedesktopNetwork1DHCPv6ClientInterface(
DbusInterfaceCommonAsync,
interface_name="org.freedesktop.network1.DHCPv6Client",
):
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def state(self) -> str:
raise NotImplementedError
class OrgFreedesktopNetwork1DHCPv4ClientInterface(
DbusInterfaceCommonAsync,
interface_name="org.freedesktop.network1.DHCPv4Client",
):
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def state(self) -> str:
raise NotImplementedError
class OrgFreedesktopNetwork1LinkInterface(
DbusInterfaceCommonAsync,
interface_name="org.freedesktop.network1.Link",
):
@dbus_method_async(
input_signature="as",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_ntp(
self,
servers: List[str],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="a(iay)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_dns(
self,
addresses: List[Tuple[int, bytes]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="a(iayqs)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_dnsex(
self,
addresses: List[Tuple[int, bytes, int, str]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="a(sb)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_domains(
self,
domains: List[Tuple[str, bool]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="b",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_default_route(
self,
enable: bool,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="s",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_llmnr(
self,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="s",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_multicast_dns(
self,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="s",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_dnsover_tls(
self,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="s",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_dnssec(
self,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="as",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_dnssecnegative_trust_anchors(
self,
names: List[str],
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def revert_ntp(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def revert_dns(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def renew(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def force_renew(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def reconfigure(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_signature="s",
result_args_names=('json',),
flags=DbusUnprivilegedFlag,
)
async def describe(
self,
) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def operational_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def carrier_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def ipv4_address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def ipv6_address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def online_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def administrative_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="(tt)",
)
def bit_rates(self) -> Tuple[int, int]:
raise NotImplementedError

View File

@ -0,0 +1,318 @@
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from sdbus import (
DbusDeprecatedFlag,
DbusInterfaceCommonAsync,
DbusNoReplyFlag,
DbusPropertyConstFlag,
DbusPropertyEmitsChangeFlag,
DbusPropertyEmitsInvalidationFlag,
DbusPropertyExplicitFlag,
DbusUnprivilegedFlag,
dbus_method_async,
dbus_property_async,
dbus_signal_async,
)
class OrgFreedesktopNetwork1ManagerInterface(
DbusInterfaceCommonAsync,
interface_name="org.freedesktop.network1.Manager",
):
@dbus_method_async(
result_signature="a(iso)",
result_args_names=('links',),
flags=DbusUnprivilegedFlag,
)
async def list_links(
self,
) -> List[Tuple[int, str, str]]:
raise NotImplementedError
@dbus_method_async(
input_signature="s",
result_signature="io",
result_args_names=('ifindex', 'path'),
flags=DbusUnprivilegedFlag,
)
async def get_link_by_name(
self,
name: str,
) -> Tuple[int, str]:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_signature="so",
result_args_names=('name', 'path'),
flags=DbusUnprivilegedFlag,
)
async def get_link_by_index(
self,
ifindex: int,
) -> Tuple[str, str]:
raise NotImplementedError
@dbus_method_async(
input_signature="ias",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_ntp(
self,
ifindex: int,
servers: List[str],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="ia(iay)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_dns(
self,
ifindex: int,
addresses: List[Tuple[int, bytes]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="ia(iayqs)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_dnsex(
self,
ifindex: int,
addresses: List[Tuple[int, bytes, int, str]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="ia(sb)",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_domains(
self,
ifindex: int,
domains: List[Tuple[str, bool]],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="ib",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_default_route(
self,
ifindex: int,
enable: bool,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="is",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_llmnr(
self,
ifindex: int,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="is",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_multicast_dns(
self,
ifindex: int,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="is",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_dnsover_tls(
self,
ifindex: int,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="is",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_dnssec(
self,
ifindex: int,
mode: str,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="ias",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def set_link_dnssecnegative_trust_anchors(
self,
ifindex: int,
names: List[str],
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def revert_link_ntp(
self,
ifindex: int,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def revert_link_dns(
self,
ifindex: int,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def renew_link(
self,
ifindex: int,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def force_renew_link(
self,
ifindex: int,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def reconfigure_link(
self,
ifindex: int,
) -> None:
raise NotImplementedError
@dbus_method_async(
result_args_names=(),
flags=DbusUnprivilegedFlag,
)
async def reload(
self,
) -> None:
raise NotImplementedError
@dbus_method_async(
input_signature="i",
result_signature="s",
result_args_names=('json',),
flags=DbusUnprivilegedFlag,
)
async def describe_link(
self,
ifindex: int,
) -> str:
raise NotImplementedError
@dbus_method_async(
result_signature="s",
result_args_names=('json',),
flags=DbusUnprivilegedFlag,
)
async def describe(
self,
) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def operational_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def carrier_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def ipv4_address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def ipv6_address_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="s",
flags=DbusPropertyEmitsChangeFlag,
)
def online_state(self) -> str:
raise NotImplementedError
@dbus_property_async(
property_signature="t",
flags=DbusPropertyConstFlag,
)
def namespace_id(self) -> int:
raise NotImplementedError
@dbus_property_async(
property_signature="u",
)
def namespace_nsid(self) -> int:
raise NotImplementedError

View File

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
from .. import versions
from ..utilities.config import Configuration
from gi.repository import Astal, Gio, AstalIO
from gi.events import GLibEventLoopPolicy
from pathlib import Path
import sys
import logging
import asyncio
from sdbus import request_default_bus_name_async, sd_bus_open_user, set_default_bus
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
logger = logging.getLogger(__name__)
class App(Astal.Application):
def do_astal_application_request(self, msg: str, conn: Gio.SocketConnection):
print(msg)
AstalIO.write_sock(conn, "hello")
def do_activate(self) -> None:
self.hold()
logger.info("Loading configuration")
config = Configuration(Path("~/.config/VoidShell/config").expanduser())
self.apply_css(str(config.css_file), True)
set_default_bus(sd_bus_open_user())
asyncio.get_event_loop().create_task(
request_default_bus_name_async("dev.ezri.VoidShell")
)
logger.info("VoidShell up and running")
# TODO: implement application startup logic
def doStartup(app: App):
logging.basicConfig(level=logging.INFO)
app.acquire_socket()
app.run(None)
def main():
app = App(instance_name="VoidShell")
try:
doStartup(app)
except Exception:
print("Error: VoidShell is already running")
sys.exit(1)

View File

View File

@ -0,0 +1,11 @@
from gi.repository import GObject
from voidshell.dbus_clients.systemd_networkd import (
NetworkManagerInterface,
LinkInterface,
)
class NetworkService(GObject):
"""
Collects and formats network data from a variety of sources
"""

View File

@ -47,6 +47,11 @@ class AbstractWorkspaceBackend(ABC):
"""Configure an output"""
pass
@abstractmethod
async def disableOutput(self, output: str):
"""Disable an output"""
pass
@abstractmethod
def onWorkspaceChange(self, callback):
"""Register a callback to be called when the workspace changes"""
@ -62,6 +67,10 @@ class AbstractWorkspaceBackend(ABC):
"""Register a callback to be called when the binding mode changes, in compositors that support it"""
pass
@abstractmethod
async def getOutputs(self) -> list[OutputAdapter]:
"""Get the currently connected outputs"""
@abstractmethod
@property
def name(self) -> str:

View File

@ -5,8 +5,9 @@ from sdbus import (
dbus_property_async,
dbus_signal_async,
)
from .config import WorkspaceConfig, GroupConfig, ContextConfig, Config
from .backend import getBackend, WorkspaceAdapter
from .config import WorkspaceConfig, GroupConfig, Context as ContextConfig, Config
from .backend import getBackend, WorkspaceAdapter, OutputAdapter
from . import scoring
import logging
logger = logging.getLogger(__name__)
@ -292,9 +293,11 @@ class Group(GObject.Object):
def __init__(self, name: str):
super().__init__()
if "/" in name:
self._proxify(name)
self._proxify("dev.ezri.voidshell", name)
else:
self._proxify(f"/dev/ezri/voidshell/workspaces/group/{name}")
self._proxify(
"dev.ezri.voidshell", f"/dev/ezri/voidshell/workspaces/group/{name}"
)
def __init__(self, definition: GroupConfig):
super().__init__()
@ -305,12 +308,124 @@ class Group(GObject.Object):
manager._getWorkspaceByCompositorIndex(ws)
for ws in definition.get("workspaces")
]
for workspace in self.workspaces:
workspace.addGroup(self)
self.output_name = None
self.focused = False
self._focused_workspace = None
self.context = None
self._dbusObject = Group.DBusObject(self)
async def configure(self, output: str = None) -> str:
"""Configure the group. If no output is provided, the group will be reconfigured on its
current output. If it is not currently active, an output is required."""
adapter = getBackend()
if output is None:
if self.output_name is None:
return "Output required for inactive group"
output = self.output_name
self.output_name = output
try:
await adapter.configureOutput(output, self.definition)
except Exception as e:
self.output_name = None
return str(e)
return "OK"
async def focus(self):
"""Focus the group."""
adapter = getBackend()
if self.output_name is None:
return "Group not active, cannot focus"
try:
await adapter.focusOutput(self.output_name)
except Exception as e:
return str(e)
return "OK"
async def disable(self):
"""Disable the group."""
adapter = getBackend()
if self.output_name is None:
return "Don't have an output name, cannot disable group."
try:
await adapter.disableOutput(self.output_name)
except Exception as e:
return str(e)
return "OK"
class Context(
GObject.Object,
):
class DBusInterface(
DbusInterfaceCommonAsync,
interface_name="dev.ezri.voidshell.workspaces.Context",
):
def __init__(self, context: "Context" = None):
super().__init__()
self.context = context
@dbus_signal_async()
async def Activated(self):
raise NotImplementedError
@dbus_signal_async()
async def Deactivated(self):
raise NotImplementedError
@dbus_method_async(input_signature="", result_signature="i")
async def GetCompatabilityScore(self):
raise NotImplementedError
@dbus_property_async("b")
def Active(self):
raise NotImplementedError
@dbus_property_async("ao")
def Groups(self):
raise NotImplementedError
class DBusObject(DBusInterface):
def __init__(self, context: "Context"):
super().__init__(context)
self.export_to_dbus(
f"/dev/ezri/voidshell/workspaces/context/{context.name}"
)
class DBusProxy(DBusInterface):
def __init__(self, name: str):
super().__init__()
if "/" in name:
self._proxify("dev.ezri.voidshell", name)
else:
self._proxify(
"dev.ezri.voidshell",
f"/dev/ezri/voidshell/workspaces/context/{name}",
)
def __init__(self, definition: ContextConfig):
super().__init__()
self.definition = definition
self.name = definition["name"]
self.groups = {group["name"]: Group(group) for group in definition["groups"]}
self.priority = int(self.definition.get("priority", 0))
async def score(self, outputs: list[OutputAdapter] = None):
if outputs is None:
adapter = getBackend()
outputs = await adapter.getOutputs()
return scoring.computePerfectScore(self.definition, outputs)
async def activate(self):
pass
class Manager(
GObject.Object,
@ -326,7 +441,7 @@ class Manager(
self.tree = tree
@dbus_signal_async()
def Updated(self):
async def Updated(self):
raise NotImplementedError
@dbus_method_async(input_signature="s", result_signature="s")

Binary file not shown.

View File

@ -0,0 +1,32 @@
"""
Configuration management for VoidShell
"""
import configparser
from pathlib import Path
from gi.repository import AstalIO
class Configuration:
_scss_file: Path | None
css_file: Path
def compile_scss(self):
if self._scss_file.is_file():
AstalIO.Process.execv(["sass", str(self._scss_file), str(self.css_file)])
def __init__(self, config_file: Path):
parser = configparser.ConfigParser()
parser.read(config_file)
if "style" in parser and "stylesheet" in parser["style"]:
self.css_file = config_file / Path(parser["style"])
else:
self.css_file = config_file / "style.css"
if self.css_file.suffix == "scss":
self._scss_file = self.css_file
self.css_file = self._scss_file.with_suffix("css")
self.compile_scss()

13
voidshell/versions.py Normal file
View File

@ -0,0 +1,13 @@
import gi
gi.require_version("AstalIO", "0.1")
gi.require_version("Astal", "3.0")
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_version("Gio", "2.0")
gi.require_version("GObject", "2.0")
_astal_libs = ["Apps", "Battery", "Bluetooth", "Mpris", "Notifd", "PowerProfiles", "Wp"]
for lib in _astal_libs:
gi.require_version(f"Astal{lib}", "0.1")