From 4269d6b0bc849a5939aac62152501c4b75170ede Mon Sep 17 00:00:00 2001 From: Franek Date: Sun, 20 Apr 2025 13:02:58 +0200 Subject: [PATCH] Initial commit --- ags/.gitignore | 3 + ags/app.ts | 67 ++++++++ ags/env.d.ts | 21 +++ ags/lib/icons.ts | 17 ++ ags/lib/utils.ts | 24 +++ ags/lib/widgets/datetime.tsx | 24 +++ ags/package.json | 6 + ags/settings.json | 5 + ags/styles/_variables.scss | 2 + ags/styles/classes.scss | 6 + ags/styles/colors.scss | 26 +++ ags/styles/components/_bar.scss | 32 ++++ ags/styles/components/_battery_info.scss | 56 +++++++ ags/styles/components/_index.scss | 4 + ags/styles/components/_launcher.scss | 60 +++++++ ags/styles/components/_quick_settings.scss | 70 ++++++++ ags/styles/mixins.scss | 8 + ags/styles/style.scss | 29 ++++ ags/tsconfig.json | 19 +++ ags/widget/bar/bar.tsx | 45 +++++ ags/widget/bar/battery_info.tsx | 53 ++++++ ags/widget/bar/components/battery.tsx | 26 +++ ags/widget/bar/tray.tsx | 30 ++++ ags/widget/bar/workspace.tsx | 18 ++ ags/widget/launcher/launcher.tsx | 82 +++++++++ ags/widget/quick_settings/bluetooth.tsx | 71 ++++++++ ags/widget/quick_settings/quick_settings.tsx | 167 +++++++++++++++++++ hypr/.gitignore | 1 + hypr/config/animations.conf | 20 +++ hypr/config/autostart.conf | 12 ++ hypr/config/binds.conf | 78 +++++++++ hypr/config/io.conf | 17 ++ hypr/config/misc.conf | 38 +++++ hypr/config/rules.conf | 20 +++ hypr/config/workspaces.conf | 10 ++ hypr/hypridle.conf | 30 ++++ hypr/hyprlock.conf | 65 ++++++++ hypr/scripts/idle-inhibitor.py | 81 +++++++++ hypr/scripts/random-wallpaper.sh | 31 ++++ hypr/scripts/toggle-bar-visibility.sh | 7 + 40 files changed, 1381 insertions(+) create mode 100644 ags/.gitignore create mode 100644 ags/app.ts create mode 100644 ags/env.d.ts create mode 100644 ags/lib/icons.ts create mode 100644 ags/lib/utils.ts create mode 100644 ags/lib/widgets/datetime.tsx create mode 100644 ags/package.json create mode 100644 ags/settings.json create mode 100644 ags/styles/_variables.scss create mode 100644 ags/styles/classes.scss create mode 100644 ags/styles/colors.scss create mode 100644 ags/styles/components/_bar.scss create mode 100644 ags/styles/components/_battery_info.scss create mode 100644 ags/styles/components/_index.scss create mode 100644 ags/styles/components/_launcher.scss create mode 100644 ags/styles/components/_quick_settings.scss create mode 100644 ags/styles/mixins.scss create mode 100644 ags/styles/style.scss create mode 100644 ags/tsconfig.json create mode 100644 ags/widget/bar/bar.tsx create mode 100644 ags/widget/bar/battery_info.tsx create mode 100644 ags/widget/bar/components/battery.tsx create mode 100644 ags/widget/bar/tray.tsx create mode 100644 ags/widget/bar/workspace.tsx create mode 100644 ags/widget/launcher/launcher.tsx create mode 100644 ags/widget/quick_settings/bluetooth.tsx create mode 100644 ags/widget/quick_settings/quick_settings.tsx create mode 100644 hypr/.gitignore create mode 100644 hypr/config/animations.conf create mode 100644 hypr/config/autostart.conf create mode 100644 hypr/config/binds.conf create mode 100644 hypr/config/io.conf create mode 100644 hypr/config/misc.conf create mode 100644 hypr/config/rules.conf create mode 100644 hypr/config/workspaces.conf create mode 100644 hypr/hypridle.conf create mode 100644 hypr/hyprlock.conf create mode 100755 hypr/scripts/idle-inhibitor.py create mode 100755 hypr/scripts/random-wallpaper.sh create mode 100755 hypr/scripts/toggle-bar-visibility.sh diff --git a/ags/.gitignore b/ags/.gitignore new file mode 100644 index 0000000..f7587cb --- /dev/null +++ b/ags/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +@girs/ +styles/colors/index.scss \ No newline at end of file diff --git a/ags/app.ts b/ags/app.ts new file mode 100644 index 0000000..40c1804 --- /dev/null +++ b/ags/app.ts @@ -0,0 +1,67 @@ +import style from "./styles/style.scss" + +import { GLib, monitorFile, exec } from "astal"; +import { App, Gtk } from "astal/gtk4"; +import Hyprland from "gi://AstalHyprland"; + +import QuickSettings from "widget/quick_settings/quick_settings"; +import BluetoothWindow from "widget/quick_settings/bluetooth"; +import Launcher from "widget/launcher/launcher"; +import BatteryInfo from "@/widget/bar/battery_info"; +import Bar from "widget/bar/bar"; + +const hypr = Hyprland.get_default(); +const windows = new Map(); +const components = [ + Bar, + Launcher, + QuickSettings, + BluetoothWindow, + BatteryInfo +]; + +const setupBars = async (monitor_id: number) => { + const windows = await Promise.all( + components.map(item => Promise.resolve(item(monitor_id)) as Promise) + ); + + return windows; +}; + +const STYLES = `${GLib.get_user_config_dir()}/ags/styles`; +const monitorCSS = () => monitorFile( + STYLES + '/colors.scss', + () => { + exec(`sass ${STYLES}/style.scss /tmp/ags-style.css`); + App.apply_css('/tmp/ags-style.css', true); + } +); + +App.start({ + css: style, + async main() { + App.add_icons(`${GLib.get_user_data_dir()}/icons/Astal`); + monitorCSS(); + + const monitors = App.get_monitors(); + for (const monitor of monitors) { + const index = monitors.indexOf(monitor); + windows.set(index, await setupBars(index)); + + hypr.connect("monitor-added", async (_, monitor: Hyprland.Monitor) => { + if (!windows.has(monitor.id)) windows.set(monitor.id, await setupBars(monitor.id)) + }); + + hypr.connect("monitor-removed", (_, monitor_id: number) => { + const monitorWindows = windows.get(monitor_id) + if (monitorWindows) { + for (const monitorWindow of monitorWindows) { + monitorWindow.destroy(); + }; + + windows.delete(monitor_id); + }; + }); + } + }, +}) diff --git a/ags/env.d.ts b/ags/env.d.ts new file mode 100644 index 0000000..467c0a4 --- /dev/null +++ b/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/ags/lib/icons.ts b/ags/lib/icons.ts new file mode 100644 index 0000000..3e30ea8 --- /dev/null +++ b/ags/lib/icons.ts @@ -0,0 +1,17 @@ +import { Binding } from "astal"; + +type Substitution = { charging?: string, idle?: string } + +export const getBatteryIcon = (percentage: number, charging: Binding) => { + const levels = Array.from({ length: 10 }, (_, i) => (i + 1) * 10); + const level = levels.find((level) => percentage <= level)!; + + const substitutions: Record = { + 100: { charging: "battery-level-100-charged-symbolic" } + }; + + return charging.as(c => c + ? substitutions[level]?.charging || `battery-level-${level}-charging-symbolic` + : substitutions[level]?.idle || `battery-level-${level}-symbolic` + ); +}; \ No newline at end of file diff --git a/ags/lib/utils.ts b/ags/lib/utils.ts new file mode 100644 index 0000000..1c58da2 --- /dev/null +++ b/ags/lib/utils.ts @@ -0,0 +1,24 @@ +import { Gtk, Gdk } from "astal/gtk4"; + +export type If = Condition extends true ? Then : Else; +export type Belongs = { + [K in keyof T]: T[K] extends U ? K : never; +}[keyof T]; + +export const hideWindow = (self: Gtk.Window, keyval: number) => { + const keys = ["quick_settings"].includes(self.name) + ? [Gdk.KEY_Escape] + : [ + Gdk.KEY_Escape, + Gdk.KEY_Super_L, + Gdk.KEY_Super_R + ]; + + if (keys.some(key => key === keyval)) self.hide(); +} + +export const openOnButton = (event: Gdk.ButtonEvent, keyval: number) => (action: () => void) => { + if (event.get_button() !== keyval) return; + + action(); +} \ No newline at end of file diff --git a/ags/lib/widgets/datetime.tsx b/ags/lib/widgets/datetime.tsx new file mode 100644 index 0000000..8ae205e --- /dev/null +++ b/ags/lib/widgets/datetime.tsx @@ -0,0 +1,24 @@ +import { Variable, GLib } from "astal"; +import { Widget } from "astal/gtk4"; + +type Props = { + format: string, + interval?: number +} & Widget.LabelProps; + +export default function DateTime({ format, interval, ...props }: Props) { + const shouldPoll = typeof interval === "number" && interval >= 1; + + const currentTime = () => { + const dateTime = GLib.DateTime.new_now_local(); + return dateTime.format(format)!; + } + + if (shouldPoll) { + const pollTime = new Variable(currentTime()).poll(interval || 1000, currentTime); + return