summaryrefslogtreecommitdiff
path: root/examples/gtk4/simple-bar/py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/gtk4/simple-bar/py')
-rw-r--r--examples/gtk4/simple-bar/py/.gitignore3
-rw-r--r--examples/gtk4/simple-bar/py/README.md45
-rw-r--r--examples/gtk4/simple-bar/py/flake.nix55
-rw-r--r--examples/gtk4/simple-bar/py/meson.build12
-rw-r--r--examples/gtk4/simple-bar/py/src/gresource.xml7
-rwxr-xr-xexamples/gtk4/simple-bar/py/src/main.in.py19
-rw-r--r--examples/gtk4/simple-bar/py/src/main.scss1
-rw-r--r--examples/gtk4/simple-bar/py/src/meson.build59
-rw-r--r--examples/gtk4/simple-bar/py/src/py/App.py41
-rw-r--r--examples/gtk4/simple-bar/py/src/py/Bar.py184
-rw-r--r--examples/gtk4/simple-bar/py/src/py/__init__.py15
-rw-r--r--examples/gtk4/simple-bar/py/src/scss/Bar.scss19
-rw-r--r--examples/gtk4/simple-bar/py/src/ui/Bar.blp84
13 files changed, 544 insertions, 0 deletions
diff --git a/examples/gtk4/simple-bar/py/.gitignore b/examples/gtk4/simple-bar/py/.gitignore
new file mode 100644
index 0000000..bb252c6
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/.gitignore
@@ -0,0 +1,3 @@
+__pycache__/
+build/
+result
diff --git a/examples/gtk4/simple-bar/py/README.md b/examples/gtk4/simple-bar/py/README.md
new file mode 100644
index 0000000..25f296b
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/README.md
@@ -0,0 +1,45 @@
+# Simple Astal Bar example in Python
+
+This example shows you how to get a Python+Blueprint+Sass project going.
+
+## Dependencies
+
+- python3
+- pygobject
+- meson
+- blueprint-compiler
+- sass
+- astal4
+- astal-battery
+- astal-wireplumber
+- astak-network
+- astal-mpris
+- astak-powerprofiles
+- astal-tray
+- astal-bluetooth
+
+## How to use
+
+> [!NOTE]
+> If you are on Nix, there is an example flake included
+> otherwise feel free to `rm flake.nix`
+
+- developing
+
+ ```sh
+ meson setup build --wipe --prefix "$pwd/result"
+ meson install -C build
+ ./result/bin/simple-bar
+ ```
+
+- installing
+
+ ```sh
+ meson setup build --wipe --prefix /usr
+ meson install -C build
+ simple-bar
+ ```
+
+- adding new python files requires no additional steps
+- adding new scss files requires no additional steps as long as they are imported from `main.scss`
+- adding new ui (blueprint) files will also have to be listed in `meson.build` and in `gresource.xml`
diff --git a/examples/gtk4/simple-bar/py/flake.nix b/examples/gtk4/simple-bar/py/flake.nix
new file mode 100644
index 0000000..4002826
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/flake.nix
@@ -0,0 +1,55 @@
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ astal = {
+ url = "github:aylur/astal";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ astal,
+ }: let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+
+ python = pkgs.python3.withPackages (ps: [
+ ps.pygobject3
+ ]);
+
+ nativeBuildInputs = with pkgs; [
+ meson
+ ninja
+ pkg-config
+ gobject-introspection
+ wrapGAppsHook4
+ blueprint-compiler
+ dart-sass
+ ];
+
+ astalPackages = with astal.packages.${system}; [
+ io
+ astal4
+ battery
+ wireplumber
+ network
+ mpris
+ powerprofiles
+ tray
+ bluetooth
+ ];
+ in {
+ packages.${system}.default = pkgs.stdenv.mkDerivation {
+ name = "simple-bar";
+ src = ./.;
+ inherit nativeBuildInputs;
+ buildInputs = astalPackages ++ [python];
+ };
+
+ devShells.${system}.default = pkgs.mkShell {
+ packages = nativeBuildInputs ++ astalPackages ++ [python];
+ };
+ };
+}
diff --git a/examples/gtk4/simple-bar/py/meson.build b/examples/gtk4/simple-bar/py/meson.build
new file mode 100644
index 0000000..f49af2e
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/meson.build
@@ -0,0 +1,12 @@
+project('simple-bar')
+
+dependency('astal-4-4.0')
+dependency('astal-battery-0.1')
+dependency('astal-wireplumber-0.1')
+dependency('astal-network-0.1')
+dependency('astal-mpris-0.1')
+dependency('astal-power-profiles-0.1')
+dependency('astal-tray-0.1')
+dependency('astal-bluetooth-0.1')
+
+subdir('src')
diff --git a/examples/gtk4/simple-bar/py/src/gresource.xml b/examples/gtk4/simple-bar/py/src/gresource.xml
new file mode 100644
index 0000000..36dad8c
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/">
+ <file>ui/Bar.ui</file>
+ <file>main.css</file>
+ </gresource>
+</gresources>
diff --git a/examples/gtk4/simple-bar/py/src/main.in.py b/examples/gtk4/simple-bar/py/src/main.in.py
new file mode 100755
index 0000000..58042f4
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/main.in.py
@@ -0,0 +1,19 @@
+#!@PYTHON@
+
+import gi
+
+gi.require_version("Gio", "2.0")
+
+from gi.repository import Gio
+from sys import argv, path
+from ctypes import CDLL
+
+CDLL("@LAYER_SHELL_PREFIX@/lib/libgtk4-layer-shell.so")
+path.insert(1, "@PKGDATADIR@")
+Gio.Resource.load("@PKGDATADIR@/data.gresource")._register()
+
+
+if __name__ == "__main__":
+ from py.App import App
+
+ App.main(argv[2:])
diff --git a/examples/gtk4/simple-bar/py/src/main.scss b/examples/gtk4/simple-bar/py/src/main.scss
new file mode 100644
index 0000000..c37695a
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/main.scss
@@ -0,0 +1 @@
+@use "./scss/Bar.scss";
diff --git a/examples/gtk4/simple-bar/py/src/meson.build b/examples/gtk4/simple-bar/py/src/meson.build
new file mode 100644
index 0000000..54ecd17
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/meson.build
@@ -0,0 +1,59 @@
+pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name()
+bindir = get_option('prefix') / get_option('bindir')
+
+blp = find_program('blueprint-compiler', required: true)
+sass = find_program('sass', required: true)
+python = find_program('python3', required: true)
+layer_shell = dependency('gtk4-layer-shell-0')
+
+blueprint_sources = files(
+ 'ui/Bar.blp',
+)
+
+# transplie blueprints
+ui = custom_target(
+ 'blueprint',
+ input: blueprint_sources,
+ output: '.',
+ command: [
+ blp,
+ 'batch-compile',
+ '@OUTPUT@',
+ '@CURRENT_SOURCE_DIR@',
+ '@INPUT@',
+ ],
+)
+
+# bundle styles
+css = custom_target(
+ 'scss',
+ input: files('main.scss'),
+ command: [sass, '@INPUT@', '@OUTPUT@'],
+ output: 'main.css',
+)
+
+# compiling ui and css into a binary
+import('gnome').compile_resources(
+ 'data',
+ files('gresource.xml'),
+ dependencies: [ui, css],
+ gresource_bundle: true,
+ install: true,
+ install_dir: pkgdatadir,
+)
+
+# install python sources
+install_subdir('py', install_dir: pkgdatadir)
+
+# configure the main python entry file
+configure_file(
+ input: 'main.in.py',
+ output: meson.project_name(),
+ configuration: {
+ 'PYTHON': python.full_path(),
+ 'LAYER_SHELL_PREFIX': layer_shell.get_variable('prefix'),
+ 'PKGDATADIR': pkgdatadir,
+ },
+ install: true,
+ install_dir: bindir,
+)
diff --git a/examples/gtk4/simple-bar/py/src/py/App.py b/examples/gtk4/simple-bar/py/src/py/App.py
new file mode 100644
index 0000000..678fb5f
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/py/App.py
@@ -0,0 +1,41 @@
+from pathlib import Path
+from gi.repository import Gio, GLib, Astal, GLib, AstalIO
+from py.Bar import Bar
+
+
+class App(Astal.Application):
+ __gtype_name__ = "App"
+ instance_name = "simple-bar"
+
+ # this is where request handlers can be implemented
+ # that will be used to handle `astal` cli invocations
+ def do_astal_application_request(self, request, conn):
+ print("incoming request", request)
+ AstalIO.write_sock(conn, "response", None)
+
+ # this is the method that will be invoked on `app.run()`
+ # this is where everything should be initialized and instantiated
+ def do_activate(self):
+ self.apply_css("resource:///main.css", False)
+ self.add_window(Bar())
+
+ @staticmethod
+ def main(argv):
+ GLib.set_prgname(App.instance_name)
+ App.instance = App(instance_name=App.instance_name)
+
+ try:
+ # `app.acquire_socket()` needed for the request API to work
+ App.instance.acquire_socket()
+
+ # if it succeeds we can run the app
+ return App.instance.run([])
+ except Exception:
+ # if it throws an error it means there is already an instance
+ # with `instance_name` running, so we just send a request instead
+ response = AstalIO.send_request(
+ App.instance_name,
+ argv.join(" "),
+ )
+ print(response)
+ return 0
diff --git a/examples/gtk4/simple-bar/py/src/py/Bar.py b/examples/gtk4/simple-bar/py/src/py/Bar.py
new file mode 100644
index 0000000..cf521a9
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/py/Bar.py
@@ -0,0 +1,184 @@
+import math
+from gi.repository import (
+ Astal,
+ AstalIO,
+ GObject,
+ GLib,
+ Gtk,
+ GObject,
+ AstalBattery,
+ AstalWp,
+ AstalNetwork,
+ AstalMpris,
+ AstalPowerProfiles,
+ AstalTray,
+ AstalBluetooth,
+)
+
+SYNC = GObject.BindingFlags.SYNC_CREATE
+
+
[email protected](resource_path="/ui/Bar.ui")
+class Bar(Astal.Window):
+ __gtype_name__ = "Bar"
+
+ clock = GObject.Property(type=str)
+ volume_icon = GObject.Property(type=str)
+ battery_visible = GObject.Property(type=bool, default=False)
+ battery_label = GObject.Property(type=str)
+ battery_icon = GObject.Property(type=str)
+ volume = GObject.Property(type=float)
+ network_icon = GObject.Property(type=str)
+ mpris_visible = GObject.Property(type=bool, default=False)
+ mpris_label = GObject.Property(type=str)
+ mpris_art = GObject.Property(type=str)
+ power_profile_icon = GObject.Property(type=str)
+ bluetooth_visible = GObject.Property(type=bool, default=False)
+
+ popover = Gtk.Template.Child()
+ calendar = Gtk.Template.Child()
+ traybox = Gtk.Template.Child()
+
+ def __init__(self):
+ super().__init__(
+ anchor=Astal.WindowAnchor.TOP
+ | Astal.WindowAnchor.LEFT
+ | Astal.WindowAnchor.RIGHT,
+ exclusivity=Astal.Exclusivity.EXCLUSIVE,
+ css_classes=["Bar"],
+ visible=True,
+ )
+
+ # clock
+ timer = AstalIO.Time.interval(1000, self.set_clock)
+ self.connect("destroy", lambda _: timer.cancel())
+
+ # everytime popover is opened, select current day
+ self.popover.connect("notify::visible", self.on_popover_visible)
+
+ # network
+ nw = AstalNetwork.get_default()
+ self._network_binding = None
+
+ nw.bind_property(
+ "primary",
+ self,
+ "network-icon",
+ SYNC,
+ self.on_nm_primary,
+ None,
+ )
+
+ # battery
+ bat = AstalBattery.get_default()
+ bat.bind_property("is-present", self, "battery-visible", SYNC)
+ bat.bind_property("icon-name", self, "battery-icon", SYNC)
+ bat.bind_property(
+ "percentage",
+ self,
+ "battery-label",
+ SYNC,
+ lambda _, percentage: f"{math.floor(percentage * 100)}%",
+ None,
+ )
+
+ # volume
+ speaker = AstalWp.get_default().get_default_speaker()
+ speaker.bind_property("volume-icon", self, "volume-icon", SYNC)
+ speaker.bind_property("volume", self, "volume", SYNC)
+
+ # mpris
+ player = AstalMpris.Player.new("spotify")
+ player.bind_property("available", self, "mpris-visible", SYNC)
+ player.bind_property("cover-art", self, "mpris-art", SYNC)
+ player.bind_property(
+ "metadata",
+ self,
+ "mpris-label",
+ SYNC,
+ lambda *_: f"{player.get_artist()} - {player.get_title()}",
+ None,
+ )
+
+ # powerprofiles
+ powerprofile = AstalPowerProfiles.get_default()
+ powerprofile.bind_property("icon-name", self, "power-profile-icon", SYNC)
+
+ # bluetooth
+ bt = AstalBluetooth.get_default()
+ bt.bind_property("is-connected", self, "bluetooth-visible", SYNC)
+
+ # tray
+ tray = AstalTray.get_default()
+ self._tray_items = {}
+
+ def on_tray_item_added(tray, id):
+ item = tray.get_item(id)
+ popover = Gtk.PopoverMenu.new_from_model(item.get_menu_model())
+ icon = Gtk.Image()
+ button = Gtk.MenuButton(popover=popover, child=icon)
+
+ item.bind_property("gicon", icon, "gicon", SYNC)
+ popover.insert_action_group("dbusmenu", item.get_action_group())
+ item.connect(
+ "notify::action-group",
+ lambda *_: popover.insert_action_group(
+ "dbusmenu", item.get_action_group()
+ ),
+ )
+
+ self._tray_items[id] = button
+ self.traybox.append(button)
+
+ def on_tray_item_removed(_tray, id):
+ button = self._tray_items.get(id)
+ if button:
+ self.traybox.remove(button)
+ button.run_dispose()
+ del self._tray_items[id]
+
+ tray.connect("item_added", on_tray_item_added)
+ tray.connect("item_removed", on_tray_item_removed)
+ self.connect(
+ "destroy",
+ lambda *_: (
+ tray.disconnect(on_tray_item_added),
+ tray.disconnect(on_tray_item_removed),
+ ),
+ )
+
+ def on_popover_visible(self, popover, _pspec):
+ if popover.get_visible():
+ self.calendar.select_day(GLib.DateTime.new_now_local())
+
+ def on_nm_primary(self, _binding, primary):
+ nw = AstalNetwork.get_default()
+ if self._network_binding is not None:
+ self._network_binding.unbind()
+
+ match primary:
+ case AstalNetwork.Primary.WIRED:
+ self._network_binding = nw.get_wired().bind_property(
+ "icon-name",
+ self,
+ "network-icon",
+ SYNC,
+ )
+ return nw.get_wired().get_icon_name()
+ case AstalNetwork.Primary.WIFI:
+ self._network_binding = nw.get_wifi().bind_property(
+ "icon-name",
+ self,
+ "network-icon",
+ SYNC,
+ )
+ return nw.get_wifi().get_icon_name()
+ case _:
+ return "network-idle-symbolic"
+
+ def set_clock(self):
+ self.clock = GLib.DateTime.new_now_local().format("%H:%M:%S")
+
+ @Gtk.Template.Callback()
+ def change_volume(self, _scale, _type, value):
+ AstalWp.get_default().get_default_speaker().set_volume(value)
diff --git a/examples/gtk4/simple-bar/py/src/py/__init__.py b/examples/gtk4/simple-bar/py/src/py/__init__.py
new file mode 100644
index 0000000..57f4b1d
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/py/__init__.py
@@ -0,0 +1,15 @@
+import gi
+
+gi.require_version("GObject", "2.0")
+gi.require_version("GLib", "2.0")
+gi.require_version("Gtk", "4.0")
+gi.require_version("AstalIO", "0.1")
+gi.require_version("Astal", "4.0")
+
+gi.require_version("AstalBattery", "0.1")
+gi.require_version("AstalWp", "0.1")
+gi.require_version("AstalNetwork", "0.1")
+gi.require_version("AstalMpris", "0.1")
+gi.require_version("AstalPowerProfiles", "0.1")
+gi.require_version("AstalTray", "0.1")
+gi.require_version("AstalBluetooth", "0.1")
diff --git a/examples/gtk4/simple-bar/py/src/scss/Bar.scss b/examples/gtk4/simple-bar/py/src/scss/Bar.scss
new file mode 100644
index 0000000..86ea856
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/scss/Bar.scss
@@ -0,0 +1,19 @@
+// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gtk/theme/Default/_colors-public.scss
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
+
+window.Bar {
+ > box {
+ background: $bg-color;
+ color: $fg-color;
+ font-weight: bold;
+ }
+
+ button {
+ min-height: 0;
+ min-width: 0;
+ border-radius: 8px;
+ margin: 4px;
+ padding: 4px 8px;
+ }
+}
diff --git a/examples/gtk4/simple-bar/py/src/ui/Bar.blp b/examples/gtk4/simple-bar/py/src/ui/Bar.blp
new file mode 100644
index 0000000..6e401e7
--- /dev/null
+++ b/examples/gtk4/simple-bar/py/src/ui/Bar.blp
@@ -0,0 +1,84 @@
+using Gtk 4.0;
+using Astal 4.0;
+
+template $Bar: Astal.Window {
+ CenterBox centerbox {
+ start-widget: Box {
+ MenuButton {
+ Label {
+ label: bind template.clock;
+ }
+
+ popover: Popover popover {
+ Calendar calendar {
+ show-day-names: true;
+ show-heading: true;
+ show-week-numbers: true;
+ }
+ };
+ }
+ };
+
+ center-widget: Box {
+ Box {
+ visible: bind template.mpris-visible;
+
+ Image {
+ file: bind template.mpris-art;
+ }
+
+ Label {
+ label: bind template.mpris-label;
+ }
+ }
+ };
+
+ end-widget: Box {
+ spacing: 4;
+
+ Image {
+ visible: bind template.bluetooth-visible;
+ icon-name: "bluetooth-symbolic";
+ }
+
+ Image {
+ icon-name: bind template.power-profile-icon;
+ }
+
+ Image {
+ icon-name: bind template.network-icon;
+ }
+
+ Box {
+ Image {
+ icon-name: bind template.volume-icon;
+ }
+
+ Scale {
+ width-request: 100;
+ change-value => $change_volume();
+
+ adjustment: Adjustment {
+ value: bind template.volume;
+ lower: 0;
+ upper: 1;
+ };
+ }
+ }
+
+ Box {
+ Image {
+ icon-name: bind template.battery-icon;
+ }
+
+ Label {
+ label: bind template.battery-label;
+ }
+ }
+
+ Box traybox {
+ spacing: 4;
+ }
+ };
+ }
+}