diff options
Diffstat (limited to 'examples/py/simple-bar')
-rw-r--r-- | examples/py/simple-bar/README.md | 14 | ||||
-rw-r--r-- | examples/py/simple-bar/__init__.py | 0 | ||||
-rwxr-xr-x | examples/py/simple-bar/app.py | 32 | ||||
-rw-r--r-- | examples/py/simple-bar/style.scss | 88 | ||||
-rw-r--r-- | examples/py/simple-bar/versions.py | 14 | ||||
-rw-r--r-- | examples/py/simple-bar/widget/Bar.py | 257 | ||||
-rw-r--r-- | examples/py/simple-bar/widget/__init__.py | 0 |
7 files changed, 405 insertions, 0 deletions
diff --git a/examples/py/simple-bar/README.md b/examples/py/simple-bar/README.md new file mode 100644 index 0000000..d33e57d --- /dev/null +++ b/examples/py/simple-bar/README.md @@ -0,0 +1,14 @@ +# Simple Bar Example + + + +A simple bar for Hyprland using + +- [Audio library](https://aylur.github.io/astal/libraries/audio). +- [Battery library](https://aylur.github.io/astal/libraries/battery). +- [Hyprland library](https://aylur.github.io/astal/libraries/hyprland). +- [Mpris library](https://aylur.github.io/astal/libraries/mpris). +- [Network library](https://aylur.github.io/astal/libraries/network). +- [Tray library](https://aylur.github.io/astal/libraries/tray). +- [WirePlumber library](https://aylur.github.io/astal/libraries/wireplumber). +- [dart-sass](https://sass-lang.com/dart-sass/) as the css precompiler diff --git a/examples/py/simple-bar/__init__.py b/examples/py/simple-bar/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/py/simple-bar/__init__.py diff --git a/examples/py/simple-bar/app.py b/examples/py/simple-bar/app.py new file mode 100755 index 0000000..f5a8a80 --- /dev/null +++ b/examples/py/simple-bar/app.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import sys +import versions +from gi.repository import Astal, Gio +from widget.Bar import Bar +from pathlib import Path + +scss = str(Path(__file__).parent.resolve() / "style.scss") +css = "/tmp/style.css" + + +class App(Astal.Application): + def do_request(self, msg: str, conn: Gio.SocketConnection) -> None: + print(msg) + Astal.write_sock(conn, "hello") + + def do_activate(self) -> None: + self.hold() + Astal.Process.execv(["sass", scss, css]) + self.apply_css(css, True) + for mon in self.get_monitors(): + self.add_window(Bar(mon)) + + +instance_name = "simple-bar" +app = App(instance_name=instance_name) + +if __name__ == "__main__": + if app.acquire_socket(): + app.run(None) + else: + print(Astal.Application.send_message(instance_name, "".join(sys.argv[1:]))) diff --git a/examples/py/simple-bar/style.scss b/examples/py/simple-bar/style.scss new file mode 100644 index 0000000..f98286e --- /dev/null +++ b/examples/py/simple-bar/style.scss @@ -0,0 +1,88 @@ +$bg: #212223; +$fg: #f1f1f1; +$accent: #378DF7; +$radius: 7px; + +window.Bar { + border: none; + box-shadow: none; + background-color: $bg; + color: $fg; + font-size: 1.1em; + font-weight: bold; + + button { + all: unset; + background-color: transparent; + + &:hover label { + background-color: transparentize($fg, 0.84); + border-color: transparentize($accent, 0.8); + } + + &:active label { + background-color: transparentize($fg, 0.8) + } + } + + label { + transition: 200ms; + padding: 0 8px; + margin: 2px; + border-radius: $radius; + border: 1pt solid transparent; + } + + .Workspaces .focused label { + color: $accent; + border-color: $accent; + } + + .FocusedClient { + color: $accent; + } + + .Media .Cover { + min-height: 1.2em; + min-width: 1.2em; + border-radius: $radius; + background-position: center; + background-size: contain; + } + + .Battery label { + padding-left: 0; + margin-left: 0; + } + + .AudioSlider { + * { + all: unset; + } + + icon { + margin-right: .6em; + } + + margin: 0 1em; + + trough { + background-color: transparentize($fg, 0.8); + border-radius: $radius; + } + + highlight { + background-color: $accent; + min-height: .8em; + border-radius: $radius; + } + + slider { + background-color: $fg; + border-radius: $radius; + min-height: 1em; + min-width: 1em; + margin: -.2em; + } + } +} diff --git a/examples/py/simple-bar/versions.py b/examples/py/simple-bar/versions.py new file mode 100644 index 0000000..a8a1ab8 --- /dev/null +++ b/examples/py/simple-bar/versions.py @@ -0,0 +1,14 @@ +import gi + +gi.require_version("Astal", "0.1") +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") +gi.require_version("Gio", "2.0") +gi.require_version("GObject", "2.0") + +gi.require_version("AstalBattery", "0.1") +gi.require_version("AstalWp", "0.1") +gi.require_version("AstalNetwork", "0.1") +gi.require_version("AstalTray", "0.1") +gi.require_version("AstalMpris", "0.1") +gi.require_version("AstalHyprland", "0.1") diff --git a/examples/py/simple-bar/widget/Bar.py b/examples/py/simple-bar/widget/Bar.py new file mode 100644 index 0000000..89581f1 --- /dev/null +++ b/examples/py/simple-bar/widget/Bar.py @@ -0,0 +1,257 @@ +import math +from gi.repository import ( + Astal, + Gtk, + Gdk, + GLib, + GObject, + AstalBattery as Battery, + AstalWp as Wp, + AstalNetwork as Network, + AstalTray as Tray, + AstalMpris as Mpris, + AstalHyprland as Hyprland, +) + +SYNC = GObject.BindingFlags.SYNC_CREATE + + +class Workspaces(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Workspaces"]) + hypr = Hyprland.get_default() + hypr.connect("notify::workspaces", self.sync) + hypr.connect("notify::focused-workspace", self.sync) + self.sync() + + def sync(self, *_): + hypr = Hyprland.get_default() + for child in self.get_children(): + child.destroy() + + for ws in hypr.get_workspaces(): + self.add(self.button(ws)) + + def button(self, ws): + hypr = Hyprland.get_default() + btn = Gtk.Button(visible=True) + btn.add(Gtk.Label(visible=True, label=ws.get_id())) + + if hypr.get_focused_workspace() == ws: + Astal.widget_set_class_names(btn, ["focused"]) + + btn.connect("clicked", lambda *_: ws.focus()) + return btn + + +class FocusedClient(Gtk.Label): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Focused"]) + Hyprland.get_default().connect("notify::focused-client", self.sync) + self.sync() + + def sync(self, *_): + client = Hyprland.get_default().get_focused_client() + if client is None: + return self.set_label("") + + client.bind_property("title", self, "label", SYNC) + + +class Media(Gtk.Box): + def __init__(self) -> None: + super().__init__() + self.players = {} + mpris = Mpris.get_default() + Astal.widget_set_class_names(self, ["Media"]) + mpris.connect("notify::players", self.sync) + self.sync() + + def sync(self): + mpris = Mpris.get_default() + for child in self.get_children(): + child.destroy() + + if len(mpris.get_players()) == 0: + self.add(Gtk.Label(visible=True, label="Nothing Playing")) + return + + player = mpris.get_players()[0] + label = Gtk.Label(visible=True) + cover = Gtk.Box(valign=Gtk.Align.CENTER) + Astal.widget_set_class_names(cover, ["Cover"]) + + self.add(cover) + self.add(label) + + player.bind_property( + "title", + label, + "label", + SYNC, + lambda *_: f"{player.get_artist()} - {player.get_title()}", + ) + + def on_cover_art(*_): + Astal.widget_set_css( + cover, f"background-image: url('{player.get_cover_art()}')" + ) + + id = player.connect("notify::cover-art", on_cover_art) + cover.connect("destroy", lambda _: player.disconnect(id)) + on_cover_art() + + +class SysTray(Gtk.Box): + def __init__(self) -> None: + super().__init__() + self.items = {} + tray = Tray.get_default() + tray.connect("item_added", self.add_item) + tray.connect("item_removed", self.remove_item) + + def add_item(self, _: Tray.Tray, id: str): + if id in self.items: + return + + item = Tray.get_default().get_item(id) + theme = item.get_icon_theme_path() + + if theme is not None: + from app import app + + app.add_icons(theme) + + menu = item.create_menu() + btn = Astal.Button() + icon = Astal.Icon() + + def on_clicked(btn): + if menu: + menu.popup_at_widget(btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, None) + + def on_destroy(btn): + if menu: + menu.destroy() + + btn.connect("clicked", on_clicked) + btn.connect("destroy", on_destroy) + + item.bind_property("tooltip-markup", btn, "tooltip-markup", SYNC) + item.bind_property("gicon", icon, "gicon", SYNC) + self.add(btn) + self.items[id] = btn + + def remove_item(self, _: Tray.Tray, id: str): + if id in self.items: + del self.items[id] + + +class Wifi(Astal.Icon): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Wifi"]) + wifi = Network.get_default().get_wifi() + wifi.bind_property("ssid", self, "tooltip-text", SYNC) + wifi.bind_property("icon-name", self, "icon", SYNC) + + +class AudioSlider(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["AudioSlider"]) + Astal.widget_set_css(self, "min-width: 140px") + + icon = Astal.Icon() + slider = Astal.Slider(hexpand=True) + + self.add(icon) + self.add(slider) + + speaker = Wp.get_default().get_audio().get_default_speaker() + speaker.bind_property("volume-icon", icon, "icon", SYNC) + speaker.bind_property("volume", slider, "value", SYNC) + slider.connect("dragged", lambda *_: speaker.set_volume(slider.get_value())) + + +class BatteryLevel(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Battery"]) + + icon = Astal.Icon() + label = Astal.Label() + + self.add(icon) + self.add(label) + + bat = Battery.get_default() + bat.bind_property("is-present", self, "visible", SYNC) + bat.bind_property("battery-icon-name", icon, "icon", SYNC) + bat.bind_property( + "percentage", + label, + "label", + SYNC, + lambda _, value: f"{math.floor(value * 100)}%", + ) + + +class Time(Astal.Label): + def __init__(self, format="%H:%M - %A %e."): + super().__init__() + self.format = format + self.interval = Astal.Time.interval(1000, self.sync) + self.connect("destroy", self.interval.cancel) + Astal.widget_set_class_names(self, ["Time"]) + + def sync(self): + self.set_label(GLib.DateTime.new_now_local().format(self.format)) + + +class Left(Gtk.Box): + def __init__(self) -> None: + super().__init__(hexpand=True, halign=Gtk.Align.START) + self.add(Workspaces()) + self.add(FocusedClient()) + + +class Center(Gtk.Box): + def __init__(self) -> None: + super().__init__() + self.add(Media()) + + +class Right(Gtk.Box): + def __init__(self) -> None: + super().__init__(hexpand=True, halign=Gtk.Align.END) + self.add(SysTray()) + self.add(Wifi()) + self.add(AudioSlider()) + self.add(BatteryLevel()) + self.add(Time()) + + +class Bar(Astal.Window): + def __init__(self, monitor: Gdk.Monitor): + super().__init__( + anchor=Astal.WindowAnchor.LEFT + | Astal.WindowAnchor.RIGHT + | Astal.WindowAnchor.TOP, + gdkmonitor=monitor, + exclusivity=Astal.Exclusivity.EXCLUSIVE, + ) + + Astal.widget_set_class_names(self, ["Bar"]) + + self.add( + Astal.CenterBox( + start_widget=Left(), + center_widget=Center(), + end_widget=Right(), + ) + ) + + self.show_all() diff --git a/examples/py/simple-bar/widget/__init__.py b/examples/py/simple-bar/widget/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/py/simple-bar/widget/__init__.py |