diff --git a/config/general/ags/.gitignore b/config/general/ags/.gitignore
new file mode 100644
index 0000000..298eb4d
--- /dev/null
+++ b/config/general/ags/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+@girs/
diff --git a/config/general/ags/app.ts b/config/general/ags/app.ts
new file mode 100644
index 0000000..7a5e816
--- /dev/null
+++ b/config/general/ags/app.ts
@@ -0,0 +1,12 @@
+import { App } from "astal/gtk3"
+import style from "./style.scss"
+import Bar from "./bar/Bar"
+import OSD from "./osd/OSD"
+
+App.start({
+ css: style,
+ main() {
+ App.get_monitors().map(Bar);
+ OSD(App.get_monitors()[0]);
+ },
+})
diff --git a/config/general/ags/bar/Bar.tsx b/config/general/ags/bar/Bar.tsx
new file mode 100644
index 0000000..0384535
--- /dev/null
+++ b/config/general/ags/bar/Bar.tsx
@@ -0,0 +1,31 @@
+import { App, Astal, Gtk, Gdk } from "astal/gtk3"
+import { Variable } from "astal"
+
+const time = Variable("").poll(1000, "date")
+
+export default function Bar(gdkmonitor: Gdk.Monitor) {
+ const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
+
+ return
+
+
+
+
+
+
+}
diff --git a/config/general/ags/env.d.ts b/config/general/ags/env.d.ts
new file mode 100644
index 0000000..467c0a4
--- /dev/null
+++ b/config/general/ags/env.d.ts
@@ -0,0 +1,21 @@
+declare 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/scripts/compile.js b/config/general/ags/launcher/handler.ts
similarity index 100%
rename from scripts/compile.js
rename to config/general/ags/launcher/handler.ts
diff --git a/config/general/ags/launcher/launcher.scss b/config/general/ags/launcher/launcher.scss
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/launcher/launcher.tsx b/config/general/ags/launcher/launcher.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/notifications-preset/Notification.scss b/config/general/ags/notifications-preset/Notification.scss
new file mode 100644
index 0000000..a32f08b
--- /dev/null
+++ b/config/general/ags/notifications-preset/Notification.scss
@@ -0,0 +1,125 @@
+@use "sass:string";
+
+@function gtkalpha($c, $a) {
+ @return string.unquote("alpha(#{$c},#{$a})");
+}
+
+// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
+$error: red;
+
+window.NotificationPopups {
+ all: unset;
+}
+
+eventbox.Notification {
+
+ &:first-child>box {
+ margin-top: 1rem;
+ }
+
+ &:last-child>box {
+ margin-bottom: 1rem;
+ }
+
+ // eventboxes can not take margins so we style its inner box instead
+ >box {
+ min-width: 400px;
+ border-radius: 13px;
+ background-color: $bg-color;
+ margin: .5rem 1rem .5rem 1rem;
+ box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
+ border: 1pt solid gtkalpha($fg-color, .03);
+ }
+
+ &.critical>box {
+ border: 1pt solid gtkalpha($error, .4);
+
+ .header {
+
+ .app-name {
+ color: gtkalpha($error, .8);
+
+ }
+
+ .app-icon {
+ color: gtkalpha($error, .6);
+ }
+ }
+ }
+
+ .header {
+ padding: .5rem;
+ color: gtkalpha($fg-color, 0.5);
+
+ .app-icon {
+ margin: 0 .4rem;
+ }
+
+ .app-name {
+ margin-right: .3rem;
+ font-weight: bold;
+
+ &:first-child {
+ margin-left: .4rem;
+ }
+ }
+
+ .time {
+ margin: 0 .4rem;
+ }
+
+ button {
+ padding: .2rem;
+ min-width: 0;
+ min-height: 0;
+ }
+ }
+
+ separator {
+ margin: 0 .4rem;
+ background-color: gtkalpha($fg-color, .1);
+ }
+
+ .content {
+ margin: 1rem;
+ margin-top: .5rem;
+
+ .summary {
+ font-size: 1.2em;
+ color: $fg-color;
+ }
+
+ .body {
+ color: gtkalpha($fg-color, 0.8);
+ }
+
+ .image {
+ border: 1px solid gtkalpha($fg-color, .02);
+ margin-right: .5rem;
+ border-radius: 9px;
+ min-width: 100px;
+ min-height: 100px;
+ background-size: cover;
+ background-position: center;
+ }
+ }
+
+ .actions {
+ margin: 1rem;
+ margin-top: 0;
+
+ button {
+ margin: 0 .3rem;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/config/general/ags/notifications-preset/Notification.tsx b/config/general/ags/notifications-preset/Notification.tsx
new file mode 100644
index 0000000..5149d5b
--- /dev/null
+++ b/config/general/ags/notifications-preset/Notification.tsx
@@ -0,0 +1,107 @@
+import { GLib } from "astal"
+import { Gtk, Astal } from "astal/gtk3"
+import { type EventBox } from "astal/gtk3/widget"
+import Notifd from "gi://AstalNotifd"
+
+const isIcon = (icon: string) =>
+ !!Astal.Icon.lookup_icon(icon)
+
+const fileExists = (path: string) =>
+ GLib.file_test(path, GLib.FileTest.EXISTS)
+
+const time = (time: number, format = "%H:%M") => GLib.DateTime
+ .new_from_unix_local(time)
+ .format(format)!
+
+const urgency = (n: Notifd.Notification) => {
+ const { LOW, NORMAL, CRITICAL } = Notifd.Urgency
+ // match operator when?
+ switch (n.urgency) {
+ case LOW: return "low"
+ case CRITICAL: return "critical"
+ case NORMAL:
+ default: return "normal"
+ }
+}
+
+type Props = {
+ setup(self: EventBox): void
+ onHoverLost(self: EventBox): void
+ notification: Notifd.Notification
+}
+
+export default function Notification(props: Props) {
+ const { notification: n, onHoverLost, setup } = props
+ const { START, CENTER, END } = Gtk.Align
+
+ return
+
+
+ {(n.appIcon || n.desktopEntry) && }
+
+
+
+
+
+
+ {n.image && fileExists(n.image) && }
+ {n.image && isIcon(n.image) &&
+
+ }
+
+
+ {n.body && }
+
+
+ {n.get_actions().length > 0 &&
+ {n.get_actions().map(({ label, id }) => (
+
+ ))}
+ }
+
+
+}
diff --git a/config/general/ags/notifications-preset/NotificationPopups.tsx b/config/general/ags/notifications-preset/NotificationPopups.tsx
new file mode 100644
index 0000000..13fdd88
--- /dev/null
+++ b/config/general/ags/notifications-preset/NotificationPopups.tsx
@@ -0,0 +1,105 @@
+import { Astal, Gtk, Gdk } from "astal/gtk3"
+import Notifd from "gi://AstalNotifd"
+import Notification from "./Notification"
+import { type Subscribable } from "astal/binding"
+import { Variable, bind, timeout } from "astal"
+
+// see comment below in constructor
+const TIMEOUT_DELAY = 5000
+
+// The purpose if this class is to replace Variable>
+// with a Map type in order to track notification widgets
+// by their id, while making it conviniently bindable as an array
+class NotifiationMap implements Subscribable {
+ // the underlying map to keep track of id widget pairs
+ private map: Map = new Map()
+
+ // it makes sense to use a Variable under the hood and use its
+ // reactivity implementation instead of keeping track of subscribers ourselves
+ private var: Variable> = Variable([])
+
+ // notify subscribers to rerender when state changes
+ private notifiy() {
+ this.var.set([...this.map.values()].reverse())
+ }
+
+ constructor() {
+ const notifd = Notifd.get_default()
+
+ /**
+ * uncomment this if you want to
+ * ignore timeout by senders and enforce our own timeout
+ * note that if the notification has any actions
+ * they might not work, since the sender already treats them as resolved
+ */
+ // notifd.ignoreTimeout = true
+
+ notifd.connect("notified", (_, id) => {
+ this.set(id, Notification({
+ notification: notifd.get_notification(id)!,
+
+ // once hovering over the notification is done
+ // destroy the widget without calling notification.dismiss()
+ // so that it acts as a "popup" and we can still display it
+ // in a notification center like widget
+ // but clicking on the close button will close it
+ onHoverLost: () => this.delete(id),
+
+ // notifd by default does not close notifications
+ // until user input or the timeout specified by sender
+ // which we set to ignore above
+ setup: () => timeout(TIMEOUT_DELAY, () => {
+ /**
+ * uncomment this if you want to "hide" the notifications
+ * after TIMEOUT_DELAY
+ */
+ // this.delete(id)
+ })
+ }))
+ })
+
+ // notifications can be closed by the outside before
+ // any user input, which have to be handled too
+ notifd.connect("resolved", (_, id) => {
+ this.delete(id)
+ })
+ }
+
+ private set(key: number, value: Gtk.Widget) {
+ // in case of replacecment destroy previous widget
+ this.map.get(key)?.destroy()
+ this.map.set(key, value)
+ this.notifiy()
+ }
+
+ private delete(key: number) {
+ this.map.get(key)?.destroy()
+ this.map.delete(key)
+ this.notifiy()
+ }
+
+ // needed by the Subscribable interface
+ get() {
+ return this.var.get()
+ }
+
+ // needed by the Subscribable interface
+ subscribe(callback: (list: Array) => void) {
+ return this.var.subscribe(callback)
+ }
+}
+
+export default function NotificationPopups(gdkmonitor: Gdk.Monitor) {
+ const { TOP, RIGHT } = Astal.WindowAnchor
+ const notifs = new NotifiationMap()
+
+ return
+
+ {bind(notifs)}
+
+
+}
diff --git a/config/general/ags/notifications/handler.ts b/config/general/ags/notifications/handler.ts
new file mode 100644
index 0000000..46ac4f5
--- /dev/null
+++ b/config/general/ags/notifications/handler.ts
@@ -0,0 +1,20 @@
+/*
+* dotfiles - handler.ts
+*
+* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
+* https://janishutz.com, development@janishutz.com
+*
+*
+*/
+
+// Handle incoming notifications and keep a list that can be consumed by
+// other parts of the astal setup
+
+import Notifd from "gi://AstalNotifd";
+const notifd = Notifd.get_default();
+const notifications: Notifd.Notification[] = [];
+
+notifd.connect( 'notified', ( _, id ) => {
+
+} );
+
diff --git a/config/general/ags/notifications/notifications.scss b/config/general/ags/notifications/notifications.scss
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/notifications/notifications.tsx b/config/general/ags/notifications/notifications.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/osd/OSD.scss b/config/general/ags/osd/OSD.scss
new file mode 100644
index 0000000..d0fe4d1
--- /dev/null
+++ b/config/general/ags/osd/OSD.scss
@@ -0,0 +1,30 @@
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
+
+window.OSD {
+ box.OSD {
+ border-radius: 100px;
+ background-color: $bg-color;
+ padding: 13px 16px;
+ margin: 13px;
+ box-shadow: 3px 3px 7px 0 rgba(0,0,0,.4);
+ }
+
+ icon {
+ font-size: 4rem;
+ }
+
+ label {
+ font-size: 2.4rem;
+ }
+
+ levelbar {
+ trough {
+ margin: 1 .6rem;
+ }
+
+ block {
+ min-height: 2rem;
+ }
+ }
+}
diff --git a/config/general/ags/osd/OSD.tsx b/config/general/ags/osd/OSD.tsx
new file mode 100644
index 0000000..fbbd9ee
--- /dev/null
+++ b/config/general/ags/osd/OSD.tsx
@@ -0,0 +1,71 @@
+// From examples on astal github page, since it looks good by default
+
+import { App, Astal, Gdk, Gtk } from "astal/gtk3"
+import { timeout } from "astal/time"
+import Variable from "astal/variable"
+import Brightness from "./brightness"
+import Wp from "gi://AstalWp"
+
+function OnScreenProgress({ visible }: { visible: Variable }) {
+ const brightness = Brightness.get_default()
+ const speaker = Wp.get_default()!.get_default_speaker()
+
+ const iconName = Variable("")
+ const value = Variable(0)
+
+ let count = 0
+ function show(v: number, icon: string) {
+ visible.set(true)
+ value.set(v)
+ iconName.set(icon)
+ count++
+ timeout(2000, () => {
+ count--
+ if (count === 0) visible.set(false)
+ })
+ }
+
+ return (
+ {
+ self.hook(brightness, "notify::screen", () =>
+ show(brightness.screen, "display-brightness-symbolic"),
+ )
+
+ if (speaker) {
+ self.hook(speaker, "notify::volume", () =>
+ show(speaker.volume, speaker.volumeIcon),
+ )
+ }
+ }}
+ revealChild={visible()}
+ transitionType={Gtk.RevealerTransitionType.SLIDE_UP}
+ >
+
+
+
+
+
+ )
+}
+
+export default function OSD(monitor: Gdk.Monitor) {
+ const visible = Variable(false)
+
+ return (
+
+ visible.set(false)}>
+
+
+
+ )
+}
diff --git a/config/general/ags/osd/brightness.ts b/config/general/ags/osd/brightness.ts
new file mode 100644
index 0000000..6e37c7e
--- /dev/null
+++ b/config/general/ags/osd/brightness.ts
@@ -0,0 +1,47 @@
+// From examples on Astal GitHub
+
+import GObject, { register, property } from "astal/gobject"
+import { monitorFile, readFileAsync } from "astal/file"
+import { exec, execAsync } from "astal/process"
+
+const get = (args: string) => Number(exec(`brightnessctl ${args}`))
+const screen = exec(`bash -c "ls -w1 /sys/class/backlight | head -1"`)
+
+@register({ GTypeName: "Brightness" })
+export default class Brightness extends GObject.Object {
+ static instance: Brightness
+ static get_default() {
+ if (!this.instance)
+ this.instance = new Brightness()
+
+ return this.instance
+ }
+
+ #screenMax = get("max")
+ #screen = get("get") / (get("max") || 1)
+
+ @property(Number)
+ get screen() { return this.#screen }
+
+ set screen(percent) {
+ if (percent < 0)
+ percent = 0
+
+ if (percent > 1)
+ percent = 1
+
+ execAsync(`brightnessctl set ${Math.floor(percent * 100)}% -q`).then(() => {
+ this.#screen = percent
+ this.notify("screen")
+ })
+ }
+
+ constructor() {
+ super()
+ monitorFile(`/sys/class/backlight/${screen}/brightness`, async f => {
+ const v = await readFileAsync(f)
+ this.#screen = Number(v) / this.#screenMax
+ this.notify("screen")
+ })
+ }
+}
diff --git a/config/general/ags/package.json b/config/general/ags/package.json
new file mode 100644
index 0000000..44226f2
--- /dev/null
+++ b/config/general/ags/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "astal-shell",
+ "dependencies": {
+ "astal": "/usr/share/astal/gjs"
+ }
+}
diff --git a/config/general/ags/quickActions/handler.ts b/config/general/ags/quickActions/handler.ts
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/quickActions/quickActions.scss b/config/general/ags/quickActions/quickActions.scss
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/quickActions/quickActions.tsx b/config/general/ags/quickActions/quickActions.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/config/general/ags/style.scss b/config/general/ags/style.scss
new file mode 100644
index 0000000..0b4c58f
--- /dev/null
+++ b/config/general/ags/style.scss
@@ -0,0 +1,23 @@
+// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
+
+// Load stylesheet for notifications
+@use "./osd/OSD.scss";
+
+window.Bar {
+ background: transparent;
+ color: $fg-color;
+ font-weight: bold;
+
+ >centerbox {
+ background: $bg-color;
+ border-radius: 10px;
+ margin: 8px;
+ }
+
+ button {
+ border-radius: 8px;
+ margin: 2px;
+ }
+}
diff --git a/config/general/ags/tsconfig.json b/config/general/ags/tsconfig.json
new file mode 100644
index 0000000..9471e35
--- /dev/null
+++ b/config/general/ags/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "$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": "astal/gtk3",
+ }
+}
diff --git a/config/general/ags/widgets/button.tsx b/config/general/ags/widgets/button.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/install b/install
index 4895251..f277d62 100755
--- a/install
+++ b/install
@@ -2,3 +2,5 @@
# Read platform to install on (only if no platform file present in ~/.config/)
read -p "Choose the configs to install, Laptop or Desktop (l/D): " platform
+
+# Packages to be added to install: aylurs-gtk-shell-git, brightnessctl
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 0000000..e69de29
diff --git a/setup b/setup
index 13f4793..993d789 100755
--- a/setup
+++ b/setup
@@ -1,2 +1,5 @@
#!/bin/sh
+# Read platform to install on (only if no platform file present in ~/.config/)
+read -p "Choose the configs to install, Laptop or Desktop (l/D): " platform
+