summaryrefslogtreecommitdiff
path: root/examples/gtk4/simple-bar/js/src/ts
diff options
context:
space:
mode:
authorAylur <[email protected]>2025-03-01 20:59:09 +0100
committerAylur <[email protected]>2025-03-01 21:02:29 +0100
commit23cdbc8088b5c308a068b432a6b03213ede68f07 (patch)
tree1e8bd6ffde5273fcd80aca0d30cbb38dbe5f9461 /examples/gtk4/simple-bar/js/src/ts
parentdfd1f23c7562694e571d44c45aa74fcea9b1ba01 (diff)
add gtk4 examples
Diffstat (limited to 'examples/gtk4/simple-bar/js/src/ts')
-rw-r--r--examples/gtk4/simple-bar/js/src/ts/App.ts52
-rw-r--r--examples/gtk4/simple-bar/js/src/ts/Bar.ts189
-rw-r--r--examples/gtk4/simple-bar/js/src/ts/props.ts45
3 files changed, 286 insertions, 0 deletions
diff --git a/examples/gtk4/simple-bar/js/src/ts/App.ts b/examples/gtk4/simple-bar/js/src/ts/App.ts
new file mode 100644
index 0000000..012fafa
--- /dev/null
+++ b/examples/gtk4/simple-bar/js/src/ts/App.ts
@@ -0,0 +1,52 @@
+import GObject from "gi://GObject"
+import Astal from "gi://Astal?version=4.0"
+import Gio from "gi://Gio"
+import GLib from "gi://GLib"
+import AstalIO from "gi://AstalIO"
+import Bar from "./Bar"
+
+export default class App extends Astal.Application {
+ static {
+ GObject.registerClass(this)
+ }
+
+ static instance: App
+ static instanceName = "simple-bar"
+
+ // this is where request handlers can be implemented
+ // that will be used to handle `astal` cli invocations
+ vfunc_request(request: string, conn: Gio.SocketConnection): void {
+ print("incoming request", request)
+ AstalIO.write_sock(conn, "response", null)
+ }
+
+ // this is the method that will be invoked on `app.runAsync()`
+ // this is where everything should be initialized and instantiated
+ vfunc_activate(): void {
+ this.apply_css("resource:///main.css", false)
+ this.add_window(new Bar())
+ }
+
+ // entry point of our app
+ static async main(argv: string[]): Promise<number> {
+ GLib.set_prgname(App.instanceName)
+ App.instance = new App({ instanceName: App.instanceName })
+
+ try {
+ // `app.acquire_socket()` needed for the request API to work
+ App.instance.acquire_socket()
+
+ // if it succeeds we can run the app
+ return await App.instance.runAsync([])
+ } catch (error) {
+ // if it throws an error it means there is already an instance
+ // with `instanceName` running, so we just send a request instead
+ const response = AstalIO.send_request(
+ App.instanceName,
+ argv.join(" "),
+ )
+ print(response)
+ return 0
+ }
+ }
+}
diff --git a/examples/gtk4/simple-bar/js/src/ts/Bar.ts b/examples/gtk4/simple-bar/js/src/ts/Bar.ts
new file mode 100644
index 0000000..a81623c
--- /dev/null
+++ b/examples/gtk4/simple-bar/js/src/ts/Bar.ts
@@ -0,0 +1,189 @@
+import Astal from "gi://Astal?version=4.0"
+import AstalIO from "gi://AstalIO"
+import GLib from "gi://GLib"
+import Gtk from "gi://Gtk?version=4.0"
+import GObject from "gi://GObject?version=2.0"
+import AstalBattery from "gi://AstalBattery"
+import AstalWp from "gi://AstalWp"
+import AstalNetwork from "gi://AstalNetwork"
+import AstalMpris from "gi://AstalMpris"
+import AstalPowerProfiles from "gi://AstalPowerProfiles"
+import AstalTray from "gi://AstalTray"
+import AstalBluetooth from "gi://AstalBluetooth"
+import { string, number, boolean } from "./props"
+
+const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
+const SYNC = GObject.BindingFlags.SYNC_CREATE
+
+export default class Bar extends Astal.Window {
+ static {
+ GObject.registerClass(
+ {
+ GTypeName: "Bar",
+ Template: "resource:///ui/Bar.ui",
+ InternalChildren: ["popover", "calendar", "traybox"],
+ Properties: {
+ ...string("clock"),
+ ...string("volume-icon"),
+ ...boolean("battery-visible"),
+ ...string("battery-label"),
+ ...string("battery-icon"),
+ ...number("volume", 0, 1),
+ ...string("network-icon"),
+ ...boolean("mpris-visible"),
+ ...string("mpris-label"),
+ ...string("mpris-art"),
+ ...string("power-profile-icon"),
+ ...boolean("bluetooth-visible"),
+ },
+ },
+ this,
+ )
+ }
+
+ declare clock: string
+ declare battery_label: string
+ declare mpris_label: string
+ declare bluetooth_visible: string
+
+ declare _popover: Gtk.Popover
+ declare _calendar: Gtk.Calendar
+ declare _traybox: Gtk.Box
+
+ constructor() {
+ super({
+ visible: true,
+ exclusivity: Astal.Exclusivity.EXCLUSIVE,
+ anchor: TOP | LEFT | RIGHT,
+ cssClasses: ["Bar"],
+ })
+
+ // clock
+ const timer = AstalIO.Time.interval(1000, () => {
+ this.clock = GLib.DateTime.new_now_local().format("%H:%M:%S")!
+ })
+ this.connect("destroy", () => timer.cancel())
+
+ // everytime popover is opened, select current day
+ this._popover.connect("notify::visible", ({ visible }) => {
+ if (visible) {
+ this._calendar.select_day(GLib.DateTime.new_now_local())
+ }
+ })
+
+ // network
+ const nw = AstalNetwork.get_default()
+ let networkBinding: GObject.Binding
+
+ // @ts-expect-error mistyped
+ nw.bind_property_full(
+ "primary",
+ this,
+ "network-icon",
+ SYNC,
+ (_, primary: AstalNetwork.Primary) => {
+ networkBinding?.unbind()
+
+ switch (primary) {
+ case AstalNetwork.Primary.WIRED:
+ networkBinding = nw.wired.bind_property(
+ "icon-name",
+ this,
+ "network-icon",
+ SYNC,
+ )
+ return [false, ""]
+ case AstalNetwork.Primary.WIFI:
+ networkBinding = nw.wifi.bind_property(
+ "icon-name",
+ this,
+ "network-icon",
+ SYNC,
+ )
+ return [false, ""]
+ default:
+ return [true, "network-idle-symbolic"]
+ }
+ },
+ null,
+ )
+
+ // battery
+ const bat = AstalBattery.get_default()
+
+ bat.bind_property("is-present", this, "battery-visible", SYNC)
+ bat.bind_property("icon-name", this, "battery-icon", SYNC)
+
+ this.battery_label = `${Math.floor(bat.percentage * 100)}%`
+ const batteryId = bat.connect("notify::percentage", () => {
+ this.battery_label = `${Math.floor(bat.percentage * 100)}%`
+ })
+ this.connect("destroy", () => bat.disconnect(batteryId))
+
+ // volume
+ const speaker = AstalWp.get_default()!.defaultSpeaker
+ speaker.bind_property("volume-icon", this, "volume-icon", SYNC)
+ speaker.bind_property("volume", this, "volume", SYNC)
+
+ // mpris
+ const player = AstalMpris.Player.new("spotify")
+ player.bind_property("available", this, "mpris-visible", SYNC)
+ player.bind_property("cover-art", this, "mpris-art", SYNC)
+
+ this.mpris_label = `${player.artist} - ${player.title}`
+ const playerId = player.connect("notify::metadata", () => {
+ this.mpris_label = `${player.artist} - ${player.title}`
+ })
+ this.connect("destroy", () => player.disconnect(playerId))
+
+ // powerprofiles
+ const powerprofile = AstalPowerProfiles.get_default()
+ powerprofile.bind_property(
+ "icon-name",
+ this,
+ "power-profile-icon",
+ SYNC,
+ )
+
+ // tray
+ const tray = AstalTray.get_default()
+ const trayItems = new Map<string, Gtk.MenuButton>()
+ const trayId1 = tray.connect("item-added", (_, id) => {
+ const item = tray.get_item(id)
+ const popover = Gtk.PopoverMenu.new_from_model(item.menu_model)
+ const icon = new Gtk.Image()
+ const button = new Gtk.MenuButton({ popover, child: icon })
+
+ item.bind_property("gicon", icon, "gicon", SYNC)
+ popover.insert_action_group("dbusmenu", item.action_group)
+ item.connect("notify::action-group", () => {
+ popover.insert_action_group("dbusmenu", item.action_group)
+ })
+
+ trayItems.set(id, button)
+ this._traybox.append(button)
+ })
+
+ const trayId2 = tray.connect("item-removed", (_, id) => {
+ const button = trayItems.get(id)
+ if (button) {
+ this._traybox.remove(button)
+ button.run_dispose()
+ trayItems.delete(id)
+ }
+ })
+
+ this.connect("destroy", () => {
+ tray.disconnect(trayId1)
+ tray.disconnect(trayId2)
+ })
+
+ // bluetooth
+ const bt = AstalBluetooth.get_default()
+ bt.bind_property("is-connected", this, "bluetooth-visible", SYNC)
+ }
+
+ change_volume(_scale: Gtk.Scale, _type: Gtk.ScrollType, value: number) {
+ AstalWp.get_default()?.defaultSpeaker.set_volume(value)
+ }
+}
diff --git a/examples/gtk4/simple-bar/js/src/ts/props.ts b/examples/gtk4/simple-bar/js/src/ts/props.ts
new file mode 100644
index 0000000..dfa79ee
--- /dev/null
+++ b/examples/gtk4/simple-bar/js/src/ts/props.ts
@@ -0,0 +1,45 @@
+import GObject from "gi://GObject?version=2.0"
+
+export function string(name: string, defaultValue = "") {
+ return {
+ [name]: GObject.ParamSpec.string(
+ name,
+ null,
+ null,
+ GObject.ParamFlags.READWRITE,
+ defaultValue,
+ ),
+ }
+}
+
+export function number(
+ name: string,
+ min = -Number.MIN_SAFE_INTEGER,
+ max = Number.MAX_SAFE_INTEGER,
+ defaultValue = 0,
+) {
+ return {
+ [name]: GObject.ParamSpec.double(
+ name,
+ null,
+ null,
+ GObject.ParamFlags.READWRITE,
+ min,
+ max,
+ defaultValue,
+ ),
+ }
+}
+
+export function boolean(name: string, defaultValue = false) {
+ return {
+ [name]: GObject.ParamSpec.boolean(
+ name,
+ null,
+ null,
+ GObject.ParamFlags.READWRITE,
+ defaultValue,
+ ),
+ }
+}
+