summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasper Ras <jras@hostnet.nl>2024-11-28 10:03:42 +0100
committerJasper Ras <jras@hostnet.nl>2024-11-28 10:03:42 +0100
commit7f729d18d9d85f914b6cb1f4df81207caeee4b61 (patch)
tree76bc911d06112b6ca7593fec83b4ed48bea8cde7
init
-rw-r--r--.gitignore2
-rw-r--r--app.ts10
-rw-r--r--env.d.ts21
-rw-r--r--flake.lock70
-rw-r--r--flake.nix61
-rw-r--r--style.scss20
-rw-r--r--tsconfig.json22
-rw-r--r--widget/Bar.tsx162
-rw-r--r--widget/Battery.tsx12
-rw-r--r--widget/Button.tsx34
10 files changed, 414 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c5d317c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+@girs/
+node_modules/
diff --git a/app.ts b/app.ts
new file mode 100644
index 0000000..83217ef
--- /dev/null
+++ b/app.ts
@@ -0,0 +1,10 @@
+import { App } from "astal/gtk3"
+import style from "./style.scss"
+import Bar from "./widget/Bar"
+
+App.start({
+ css: style,
+ main() {
+ App.get_monitors().map(Bar)
+ },
+})
diff --git a/env.d.ts b/env.d.ts
new file mode 100644
index 0000000..4e7e508
--- /dev/null
+++ b/env.d.ts
@@ -0,0 +1,21 @@
+const SRC: string
+
+declare module "inline:*" {
+ const content: string
+ export default content
+}
+
+declare module "*.scss" {
+ const content: string
+ export default content
+}
+
+declare module "*.blp" {
+ const content: string
+ export default content
+}
+
+declare module "*.css" {
+ const content: string
+ export default content
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..5e628c1
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,70 @@
+{
+ "nodes": {
+ "ags": {
+ "inputs": {
+ "astal": "astal",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1731966249,
+ "narHash": "sha256-9hDeMy6S2q1wWBFGiJic5yUms19zW8LhAX9MlCtuN6k=",
+ "owner": "aylur",
+ "repo": "ags",
+ "rev": "12e0bfefd2051c43d7450123fcb095f655b891e8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "aylur",
+ "repo": "ags",
+ "type": "github"
+ }
+ },
+ "astal": {
+ "inputs": {
+ "nixpkgs": [
+ "ags",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1731952585,
+ "narHash": "sha256-Sh1E7sJd8JJM3PCU1ZOei/QWz97OLCENIi2rTRoaniw=",
+ "owner": "aylur",
+ "repo": "astal",
+ "rev": "664c7a4ddfcf48c6e8accd3c33bb94424b0e8609",
+ "type": "github"
+ },
+ "original": {
+ "owner": "aylur",
+ "repo": "astal",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1732014248,
+ "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "ags": "ags",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..ae0c3c5
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,61 @@
+{
+ description = "My Awesome Desktop Shell";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
+
+ ags = {
+ url = "github:aylur/ags";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ ags,
+ }: let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ packages.${system} = {
+ default = ags.lib.bundle {
+ inherit pkgs;
+ src = ./.;
+ name = "my-shell";
+ entry = "app.ts";
+
+ # additional libraries and executables to add to gjs' runtime
+ extraPackages = [
+ ags.packages.${system}.battery
+ ags.packages.${system}.hyprland
+ ags.packages.${system}.mpris
+ ags.packages.${system}.wireplumber
+ ags.packages.${system}.network
+ ags.packages.${system}.tray
+ ];
+ };
+ };
+
+ devShells.${system} = {
+ default = pkgs.mkShell {
+ buildInputs = [
+ # includes all Astal libraries
+ # ags.packages.${system}.agsFull
+
+ # includes astal3 astal4 astal-io by default
+ (ags.packages.${system}.default.override {
+ extraPackages = [
+ ags.packages.${system}.battery
+ ags.packages.${system}.hyprland
+ ags.packages.${system}.mpris
+ ags.packages.${system}.wireplumber
+ ags.packages.${system}.network
+ ags.packages.${system}.tray
+ ];
+ })
+ ];
+ };
+ };
+ };
+}
diff --git a/style.scss b/style.scss
new file mode 100644
index 0000000..9ae9604
--- /dev/null
+++ b/style.scss
@@ -0,0 +1,20 @@
+// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
+$theme_fg_color: "@theme_fg_color";
+$theme_bg_color: "@theme_bg_color";
+
+window.Bar {
+ background: transparent;
+ color: #{$theme_bg_color};
+ font-weight: bold;
+
+ >centerbox {
+ background: #{$theme_bg_color};
+ border-radius: 10px;
+ margin: 8px;
+ }
+
+ button {
+ border-radius: 8px;
+ margin: 2px;
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..fc32463
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "strict": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
+ // "checkJs": true,
+ // "allowJs": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "/nix/store/1pd4fdq90f4vla3zghmfci9axsbvkd3w-astal-gjs/share/astal/gjs/gtk3",
+ "paths": {
+ "astal": [
+ "/nix/store/1pd4fdq90f4vla3zghmfci9axsbvkd3w-astal-gjs/share/astal/gjs"
+ ],
+ "astal/*": [
+ "/nix/store/1pd4fdq90f4vla3zghmfci9axsbvkd3w-astal-gjs/share/astal/gjs/*"
+ ]
+ },
+ }
+}
diff --git a/widget/Bar.tsx b/widget/Bar.tsx
new file mode 100644
index 0000000..efc065a
--- /dev/null
+++ b/widget/Bar.tsx
@@ -0,0 +1,162 @@
+import { App } from "astal/gtk3"
+import { Variable, GLib, bind } from "astal"
+import { Astal, Gtk, Gdk } from "astal/gtk3"
+import Hyprland from "gi://AstalHyprland"
+import Mpris from "gi://AstalMpris"
+import Battery from "gi://AstalBattery"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+import Tray from "gi://AstalTray"
+
+function SysTray() {
+ const tray = Tray.get_default()
+
+ return <box>
+ {bind(tray, "items").as(items => items.map(item => {
+ if (item.iconThemePath)
+ App.add_icons(item.iconThemePath)
+
+ const menu = item.create_menu()
+
+ return <button
+ tooltipMarkup={bind(item, "tooltipMarkup")}
+ onDestroy={() => menu?.destroy()}
+ onClickRelease={self => {
+ menu?.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null)
+ }}>
+ <icon gIcon={bind(item, "gicon")} />
+ </button>
+ }))}
+ </box>
+}
+
+function Wifi() {
+ const { wifi } = Network.get_default()
+
+ return <icon
+ tooltipText={bind(wifi, "ssid").as(String)}
+ className="Wifi"
+ icon={bind(wifi, "iconName")}
+ />
+}
+
+function AudioSlider() {
+ const speaker = Wp.get_default()?.audio.defaultSpeaker!
+
+ return <box className="AudioSlider" css="min-width: 140px">
+ <icon icon={bind(speaker, "volumeIcon")} />
+ <slider
+ hexpand
+ onDragged={({ value }) => speaker.volume = value}
+ value={bind(speaker, "volume")}
+ />
+ </box>
+}
+
+function BatteryLevel() {
+ const bat = Battery.get_default()
+
+ return <box className="Battery"
+ visible={bind(bat, "isPresent")}>
+ <icon icon={bind(bat, "batteryIconName")} />
+ <label label={bind(bat, "percentage").as(p =>
+ `${Math.floor(p * 100)} %`
+ )} />
+ </box>
+}
+
+function Media() {
+ const mpris = Mpris.get_default()
+
+ return <box className="Media">
+ {bind(mpris, "players").as(ps => ps[0] ? (
+ <box>
+ <box
+ className="Cover"
+ valign={Gtk.Align.CENTER}
+ css={bind(ps[0], "coverArt").as(cover =>
+ `background-image: url('${cover}');`
+ )}
+ />
+ <label
+ label={bind(ps[0], "title").as(() =>
+ `${ps[0].title} - ${ps[0].artist}`
+ )}
+ />
+ </box>
+ ) : (
+ "Nothing Playing"
+ ))}
+ </box>
+}
+
+function Workspaces() {
+ const hypr = Hyprland.get_default()
+
+ return <box className="Workspaces">
+ {bind(hypr, "workspaces").as(wss => wss
+ .sort((a, b) => a.id - b.id)
+ .map(ws => (
+ <button
+ className={bind(hypr, "focusedWorkspace").as(fw =>
+ ws === fw ? "focused" : "")}
+ onClicked={() => ws.focus()}>
+ {ws.id}
+ </button>
+ ))
+ )}
+ </box>
+}
+
+function FocusedClient() {
+ const hypr = Hyprland.get_default()
+ const focused = bind(hypr, "focusedClient")
+
+ return <box
+ className="Focused"
+ visible={focused.as(Boolean)}>
+ {focused.as(client => (
+ client && <label label={bind(client, "title").as(String)} />
+ ))}
+ </box>
+}
+
+function Time({ format = "%H:%M - %A %e." }) {
+ const time = Variable<string>("").poll(1000, () =>
+ GLib.DateTime.new_now_local().format(format)!)
+
+ return <label
+ className="Time"
+ onDestroy={() => time.drop()}
+ label={time()}
+ />
+}
+
+export default function Bar(monitor: Gdk.Monitor) {
+ const anchor = Astal.WindowAnchor.TOP
+ | Astal.WindowAnchor.LEFT
+ | Astal.WindowAnchor.RIGHT
+
+ return <window
+ className="Bar"
+ gdkmonitor={monitor}
+ exclusivity={Astal.Exclusivity.EXCLUSIVE}
+ anchor={anchor}>
+ <centerbox>
+ <box hexpand halign={Gtk.Align.START}>
+ <Workspaces />
+ <FocusedClient />
+ </box>
+ <box>
+ <Media />
+ </box>
+ <box hexpand halign={Gtk.Align.END} >
+ <SysTray />
+ <Wifi />
+ <AudioSlider />
+ <BatteryLevel />
+ <Time />
+ </box>
+ </centerbox>
+ </window>
+}
diff --git a/widget/Battery.tsx b/widget/Battery.tsx
new file mode 100644
index 0000000..d1b70d6
--- /dev/null
+++ b/widget/Battery.tsx
@@ -0,0 +1,12 @@
+import Battery from "gi://AstalBattery"
+import { bind } from "astal"
+
+export default function BatteryPercentage() {
+ const bat = Battery.get_default()
+
+ return <box>
+ <button onClick="echo hello" />
+ <label label="hi" />
+ <label label={bind(bat, "percentage").as((p) => p * 100 + "%")} />
+ </box>
+}
diff --git a/widget/Button.tsx b/widget/Button.tsx
new file mode 100644
index 0000000..abc24ca
--- /dev/null
+++ b/widget/Button.tsx
@@ -0,0 +1,34 @@
+import { Variable, bind } from "astal"
+
+type Props = {
+ p: string
+ child?: JSX.Element
+ children?: Array<JSX.Element>
+}
+
+export default function MyButton({p, child, children}: Props) {
+ const count = Variable(0)
+ const labels = [p +"1", p +"2"]
+
+ if (child) {
+ console.log(child.label)
+ }
+
+ function increment() {
+ count.set(count.get() + 1)
+ }
+
+ function handleClick(self, ...args) {
+ console.log(self, "was clicked")
+ }
+ return <box>
+ {labels.map(label => (
+ <button onClick={handleClick}>
+ <label label={label}></label>
+ </button>
+ ))}
+ <button onClick={increment}>
+ <label label={bind(count).as(num => num.toString())}></label>
+ </button>
+ </box>
+}