diff --git a/config/astal/app.ts b/config/astal/app.ts index 6a02a3f..856547e 100644 --- a/config/astal/app.ts +++ b/config/astal/app.ts @@ -1,21 +1,23 @@ import { App } from "astal/gtk4" import style from "./style.scss" - -// import notifications from "./components/notifications/handler"; -import Bar from "./components/bar/ui/Bar"; +import Bar from "./components/bar/Bar"; +import AstalHyprland from "gi://AstalHyprland?version=0.1"; App.start({ instanceName: "runner", css: style, main() { - // notifications.startNotificationHandler( App.get_monitors()[0] ); - // TODO: Monitor handling - Bar.Bar( App.get_monitors()[0] ); + const hypr = AstalHyprland.get_default(); + const monitors = App.get_monitors(); + for (let index = 0; index < monitors.length; index++) { + Bar.Bar( monitors[ index ] ); + } + + // TODO: Handle monitor add }, requestHandler(request, res) { const args = request.trimStart().split( ' ' ); - // Notifications (TODO: Handle the arguments in the components themselves) if ( args[ 0 ] === 'notifier' ) { res( 'Not available here yet, run astal -i notifier ' + args[ 1 ] ); // res( notifications.cliHandler( args ) ); diff --git a/config/astal/btconf b/config/astal/btconf new file mode 100644 index 0000000..02e4a84 --- /dev/null +++ b/config/astal/btconf @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/config/astal/components/QuickActions/QuickActions.tsx b/config/astal/components/QuickActions/QuickActions.tsx new file mode 100644 index 0000000..dd97f62 --- /dev/null +++ b/config/astal/components/QuickActions/QuickActions.tsx @@ -0,0 +1,74 @@ +import { Gtk } from "astal/gtk4"; +import Power from "./modules/Power"; +import Audio from "./modules/Audio/Audio"; +import Bluetooth from "./modules/Bluetooth/Bluetooth"; +import Brightness from "./modules/Brightness/Brightness"; +import Player from "./modules/Player/Player"; +import { BatteryBox } from "./modules/Battery"; +import { exec } from "astal"; +import Network from "./modules/Networking/Network"; + +const QuickActions = () => { + const popover = new Gtk.Popover({ cssClasses: ["quick-actions-wrapper"] }); + popover.set_child(renderQuickActions()); + return popover; +}; + +const renderQuickActions = () => { + const user = exec("/bin/sh -c whoami"); + const profile = exec("/bin/fish -c get-profile-picture"); + const cwd = exec("pwd"); + const um = Power.UserMenu(); + + return ( + + um.popup()} + cssClasses={["stealthy-button"]} + child={ + + {um} + + } + > + + + } + > + } + endWidget={ + + + + + } + > + + + + + + + + + + + ); +}; + +// TODO: Expose additional functions to be usable through CLI +export default { + QuickActions, +}; diff --git a/config/astal/components/QuickActions/dump b/config/astal/components/QuickActions/dump new file mode 100644 index 0000000..6305c6f --- /dev/null +++ b/config/astal/components/QuickActions/dump @@ -0,0 +1,35 @@ +import { Gtk } from "astal/gtk4"; +import Power from "./modules/Power"; +import Audio from "./modules/Audio/Audio"; +import Bluetooth from "./modules/Bluetooth/Bluetooth"; +import Brightness from "./modules/Brightness/Brightness"; +import Player from "./modules/Player/Player"; +import { BatteryBox } from "./modules/Battery"; + +const QuickActions = () => { + const popover = new Gtk.Overlay( { cssClasses: [ 'quick-actions-wrapper' ] } ); + popover.set_child(renderQuickActions()); + return popover; +}; + +const renderQuickActions = () => { + return ( + + + + + + + + + + + + + ); +}; + +// TODO: Expose additional functions to be usable through CLI +export default { + QuickActions, +}; diff --git a/config/astal/components/QuickActions/modules/Audio/Audio.scss b/config/astal/components/QuickActions/modules/Audio/Audio.scss new file mode 100644 index 0000000..2d35eb1 --- /dev/null +++ b/config/astal/components/QuickActions/modules/Audio/Audio.scss @@ -0,0 +1,3 @@ +.audio-box { + min-width: 320px; +} diff --git a/config/astal/components/bar/ui/QuickActions/modules/Audio/Audio.tsx b/config/astal/components/QuickActions/modules/Audio/Audio.tsx similarity index 93% rename from config/astal/components/bar/ui/QuickActions/modules/Audio/Audio.tsx rename to config/astal/components/QuickActions/modules/Audio/Audio.tsx index de5a95f..3f2d4b4 100644 --- a/config/astal/components/bar/ui/QuickActions/modules/Audio/Audio.tsx +++ b/config/astal/components/QuickActions/modules/Audio/Audio.tsx @@ -18,13 +18,14 @@ const AudioModule = () => { return ( - + @@ -42,11 +43,13 @@ const AudioModule = () => { max={100} min={0} step={1} - widthRequest={100} + hexpand + vexpand onChangeValue={self => setVolumeSpeaker(self.value)} > - + @@ -70,15 +74,44 @@ const openBTPicker = () => { }; const BluetoothPickerList = () => { + let btEnableState = readFile("./btconf") === "true" ? true : false; + bt.adapter.set_powered(btEnableState); + + const updateState = () => { + btEnableState = !btEnableState; + writeFile("./btconf", "" + btEnableState); + }; + return ( - bt.adapter.stop_discovery()}> - + bt.adapter.stop_discovery()} + cssClasses={["popover-box"]} + > + - + } + endWidget={ + updateState()} + > + } + > + + + {bind(bt, "devices").as(devices => { return devices .filter(device => { - if (device.get_connected()) { + if (device.get_connected() || device.get_paired()) { return device; } }) @@ -91,13 +124,13 @@ const BluetoothPickerList = () => { visible={bind(bt, "devices").as(devices => { return ( devices.filter(device => { - if (device.get_connected()) { + if (device.get_connected() || device.get_paired()) { return device; } }).length === 0 ); })} - label={"No connected devices"} + label={"No connected / trusted devices"} cssClasses={["bt-no-found", "bt-conn-list"]} > } > } onClicked={() => { - // TODO: Make sure to check if device was previously paired and otherwise do some pairing shenanigans - device.connect_device( () => {} ); + connectOrPair( device ); }} > ); }; +const connectOrPair = (device: AstalBluetooth.Device) => { + if ( device.get_paired() ) { + device.connect_device(() => { }); + // Show failed message if tried to connect and failed + } else { + device.pair(); + } +}; export default BTDevice; diff --git a/config/astal/components/QuickActions/modules/Brightness/Brightness.tsx b/config/astal/components/QuickActions/modules/Brightness/Brightness.tsx new file mode 100644 index 0000000..5ddaa36 --- /dev/null +++ b/config/astal/components/QuickActions/modules/Brightness/Brightness.tsx @@ -0,0 +1,18 @@ +import { bind } from "astal"; +import Brightness from "../../../../util/brightness"; + +const brightness = Brightness.get_default(); + +const BrightnessModule = () => { + return ( + + + + + + ); +}; + +export default { + BrightnessModule +}; diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking-old/Networking.tsx b/config/astal/components/QuickActions/modules/Networking-old/Networking.tsx similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking-old/Networking.tsx rename to config/astal/components/QuickActions/modules/Networking-old/Networking.tsx diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking-old/README.md b/config/astal/components/QuickActions/modules/Networking-old/README.md similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking-old/README.md rename to config/astal/components/QuickActions/modules/Networking-old/README.md diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking-old/network.d.ts b/config/astal/components/QuickActions/modules/Networking-old/network.d.ts similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking-old/network.d.ts rename to config/astal/components/QuickActions/modules/Networking-old/network.d.ts diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking-old/networkinghelper.ts b/config/astal/components/QuickActions/modules/Networking-old/networkinghelper.ts similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking-old/networkinghelper.ts rename to config/astal/components/QuickActions/modules/Networking-old/networkinghelper.ts diff --git a/config/astal/components/QuickActions/modules/Networking/Network.tsx b/config/astal/components/QuickActions/modules/Networking/Network.tsx new file mode 100644 index 0000000..fe16d05 --- /dev/null +++ b/config/astal/components/QuickActions/modules/Networking/Network.tsx @@ -0,0 +1,97 @@ +import { bind } from "astal"; +import { Gtk } from "astal/gtk4"; +import AstalNetwork from "gi://AstalNetwork"; +import networkHelper from "./network-helper"; +import NetworkMenu from "./NetworkMenu"; + +const net = AstalNetwork.get_default(); +const STATE = AstalNetwork.DeviceState; + +const Network = () => { + const netMenu = NetworkMenu.NetworkMenu(); + return ( + + + + + ); +}; + +export default { + Network, +}; diff --git a/config/astal/components/QuickActions/modules/Networking/NetworkMenu.tsx b/config/astal/components/QuickActions/modules/Networking/NetworkMenu.tsx new file mode 100644 index 0000000..6a02e31 --- /dev/null +++ b/config/astal/components/QuickActions/modules/Networking/NetworkMenu.tsx @@ -0,0 +1,18 @@ +import { Gtk } from "astal/gtk4"; + +const NetworkMenu = () => { + const popover = new Gtk.Popover(); + popover.set_child( renderMenu() ); + return popover; +}; + +const renderMenu = () => { + return + + + ; +}; + +export default { + NetworkMenu, +}; diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/Network.tsx b/config/astal/components/QuickActions/modules/Networking/dump similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking/Network.tsx rename to config/astal/components/QuickActions/modules/Networking/dump diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/network-helper.ts b/config/astal/components/QuickActions/modules/Networking/network-helper.ts similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking/network-helper.ts rename to config/astal/components/QuickActions/modules/Networking/network-helper.ts diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts b/config/astal/components/QuickActions/modules/Networking/network.d.ts similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts rename to config/astal/components/QuickActions/modules/Networking/network.d.ts diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/wifi-helper.ts b/config/astal/components/QuickActions/modules/Networking/wifi-helper.ts similarity index 100% rename from config/astal/components/bar/ui/QuickActions/modules/Networking/wifi-helper.ts rename to config/astal/components/QuickActions/modules/Networking/wifi-helper.ts diff --git a/config/astal/components/QuickActions/modules/Player/Player.scss b/config/astal/components/QuickActions/modules/Player/Player.scss new file mode 100644 index 0000000..a26faac --- /dev/null +++ b/config/astal/components/QuickActions/modules/Player/Player.scss @@ -0,0 +1,56 @@ +$fg-color: #{"@theme_fg_color"}; +$bg-color: #{"@theme_bg_color"}; + +box.players-box { + margin-top: 20px; +} + +box.player { + padding: 0.6rem; + + .cover-art { + min-width: 100px; + min-height: 100px; + border-radius: 9px; + margin-right: 0.6rem; + background-size: contain; + background-position: center; + } + + .title { + font-weight: bold; + font-size: 1.1em; + } + + scale { + padding: 0; + margin: 0.4rem 0; + border-radius: 20px; + + trough { + min-height: 8px; + border-radius: 20px; + } + + highlight { + background-color: $fg-color; + border-radius: 20px; + } + + slider { + all: unset; + border-radius: 20px; + } + } + + centerbox.actions { + min-width: 220px; + + button { + min-width: 0; + min-height: 0; + padding: 0.4rem; + margin: 0 0.2rem; + } + } +} diff --git a/config/astal/components/QuickActions/modules/Player/Player.tsx b/config/astal/components/QuickActions/modules/Player/Player.tsx new file mode 100644 index 0000000..7e3516b --- /dev/null +++ b/config/astal/components/QuickActions/modules/Player/Player.tsx @@ -0,0 +1,154 @@ +import { bind } from "astal"; +import { Gtk } from "astal/gtk4"; +import AstalMpris from "gi://AstalMpris"; +import Pango from "gi://Pango?version=1.0"; +const ALIGN = Gtk.Align; + +const mpris = AstalMpris.get_default(); +mpris.connect("player-added", p => { + print("Player added:", p); +}); + +const PlayerModule = () => { + return ( + + + + + {bind(mpris, "players").as(players => { + return players.map(player => { + return ; + }); + })} + + + + ); +}; + +// TODO: Update widths +const pbStatus = AstalMpris.PlaybackStatus; +const PlayerItem = ({ player }: { player: AstalMpris.Player }) => { + return ( + + + + + + l > 0)} + value={bind(player, "position")} + min={0} + max={bind(player, "length")} + onChangeValue={v => + player.set_position(v.get_value()) + } + > + + secondsToFriendlyTime(v), + )} + hexpand + cssClasses={["position"]} + > + } + centerWidget={ + + + + + + } + endWidget={ + + } + > + + + ); +}; + +const secondsToFriendlyTime = (time: number) => { + const minutes = Math.floor(time / 60); + const hours = Math.floor(minutes / 60); + const seconds = Math.floor(time % 60); + if (hours > 0) { + return `${hours}:${expandTime(minutes)}:${expandTime(seconds)}`; + } else { + return `${minutes}:${expandTime(seconds)}`; + } +}; + +const expandTime = (time: number): string => { + return time < 10 ? `0${time}` : "" + time; +}; + +export default { + PlayerModule, +}; diff --git a/config/astal/components/bar/ui/QuickActions/modules/Power.tsx b/config/astal/components/QuickActions/modules/Power.tsx similarity index 66% rename from config/astal/components/bar/ui/QuickActions/modules/Power.tsx rename to config/astal/components/QuickActions/modules/Power.tsx index 36442c0..5402073 100644 --- a/config/astal/components/bar/ui/QuickActions/modules/Power.tsx +++ b/config/astal/components/QuickActions/modules/Power.tsx @@ -12,29 +12,17 @@ const PowerMenu = (): Gtk.Popover => { child={ } - onClicked={() => exec("shutdown now")} + onClicked={() => exec("/bin/sh -c 'shutdown now'")} > - - ); @@ -48,7 +36,10 @@ const Power = () => { const pm = PowerMenu(); return ( + + + ); + }; + + popover.set_child(powerMenuBox()); + return popover; +}; + +export default { + Power, + UserMenu +}; diff --git a/config/astal/components/QuickActions/quickactions.scss b/config/astal/components/QuickActions/quickactions.scss new file mode 100644 index 0000000..8f0302d --- /dev/null +++ b/config/astal/components/QuickActions/quickactions.scss @@ -0,0 +1,56 @@ +@use "./modules/Player/Player.scss"; +@use "./modules/Audio/Audio.scss"; +@use "../../util/colours.scss" as *; + +.quick-actions-wrapper { + min-width: 520px; +} + +box.quick-actions { + padding: 10px; +} + +popover * { + border-radius: 20px; +} + +button { + margin: 4px; +} + +.button-no-margin { + margin-top: 0; + margin-bottom: 0; +} + +.devices-list { + margin-bottom: 20px; +} + +button.toggle-button { + min-width: 220px; + border-radius: 50px; + + &.toggle-on { + min-width: 190px; + margin-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + background-color: $accent-color; + } +} + +button.actions-button { + margin-left: 0; + border-radius: 0; + background-color: $accent-color; + border-top-right-radius: 50px; + border-bottom-right-radius: 50px; +} + +.avatar-icon { + border-radius: 100px; + min-height: 40px; + min-width: 40px; + margin-right: 10px; +} diff --git a/config/astal/components/bar/ui/Bar.tsx b/config/astal/components/bar/Bar.tsx similarity index 97% rename from config/astal/components/bar/ui/Bar.tsx rename to config/astal/components/bar/Bar.tsx index 1f38694..3a1c273 100644 --- a/config/astal/components/bar/ui/Bar.tsx +++ b/config/astal/components/bar/Bar.tsx @@ -23,7 +23,6 @@ const Bar = (gdkmonitor: Gdk.Monitor) => { diff --git a/config/astal/components/bar/bar.scss b/config/astal/components/bar/bar.scss new file mode 100644 index 0000000..156653d --- /dev/null +++ b/config/astal/components/bar/bar.scss @@ -0,0 +1,62 @@ +@use "../../util/colours.scss" as *; + +window.Bar { + font-family: "Comfortaa, sans-serif"; + background: transparent; + color: $fg-color; + font-weight: bold; + + /* >centerbox { */ + /* background: $bg-color; */ + /* border-radius: 10px; */ + /* margin: 8px; */ + /* } */ + + .bar-button { + border-radius: 20px; + margin: 2px; + padding-left: 10px; + padding-right: 10px; + background-color: $bg-color; + + & button { + background-color: $bg-color; + } + } + + .quick-action-button { + border-radius: 20px; + margin: 2px; + padding-left: 10px; + padding-right: 10px; + background-color: $bg-color; + } + + button.workspace-button { + border-radius: 20px; + margin: 1px; + + &.focused-workspace-button { + color: $accent-color-2; + } + } + + .tray-item { + margin: 0; + padding: 0; + + & button { + margin: 2px; + box-shadow: none; + } + } + + .time { + min-width: 11rem; + padding: 3px; + & button { + box-shadow: none; + padding: 0; + } + } +} diff --git a/config/astal/components/bar/modules/Calendar.tsx b/config/astal/components/bar/modules/Calendar.tsx new file mode 100644 index 0000000..db6d06b --- /dev/null +++ b/config/astal/components/bar/modules/Calendar.tsx @@ -0,0 +1,26 @@ +import { GLib, Variable } from "astal"; +import { Gtk } from "astal/gtk4"; + +const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => { + const time = Variable("").poll( + 1000, + () => GLib.DateTime.new_now_local().format(format)!, + ); + + return ( + + + + + + + ); +}; + +export default { + Time, +}; diff --git a/config/astal/components/bar/modules/Hyprland.tsx b/config/astal/components/bar/modules/Hyprland.tsx new file mode 100644 index 0000000..d540b5e --- /dev/null +++ b/config/astal/components/bar/modules/Hyprland.tsx @@ -0,0 +1,197 @@ +import AstalTray from "gi://AstalTray"; +import { bind, GObject } from "astal"; +import AstalHyprland from "gi://AstalHyprland"; +import { Gtk } from "astal/gtk4"; + +const SYNC = GObject.BindingFlags.SYNC_CREATE; + +const SysTray = () => { + const trayBox = new Gtk.Box({ cssClasses: ["bar-button"] }); + const tray = AstalTray.get_default(); + + const trayItems = new Map(); + const trayAddedHandler = 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, + cssClasses: ["tray-item"], + }); + + 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); + trayBox.append(button); + }); + + const trayRemovedHandler = tray.connect("item-removed", (_, id) => { + const button = trayItems.get(id); + if (button) { + trayBox.remove(button); + button.run_dispose(); + trayItems.delete(id); + } + }); + + trayBox.connect("destroy", () => { + tray.disconnect(trayAddedHandler); + tray.disconnect(trayRemovedHandler); + }); + + return trayBox; +}; + +const Workspace = () => { + const hypr = AstalHyprland.get_default(); + + return ( + + {bind(hypr, "workspaces").as(wss => + wss + .filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces + .sort((a, b) => a.id - b.id) + .map(ws => ( + + )), + )} + + ); +}; + +/** + * Displays the name of the currently active window and provides a popover for + * displaying all available clients + */ +const ActiveWindow = () => { + const hypr = AstalHyprland.get_default(); + const focused = bind(hypr, "focusedClient"); + + const WindowPopover = (): Gtk.Popover => { + // Set up boxes + Popover + const clients = new Map(); + const popover = new Gtk.Popover(); + const popoverBox = new Gtk.Box({ + orientation: Gtk.Orientation.VERTICAL, + }); + + const widgetTitle = new Gtk.Label({ + cssClasses: ["title-2"], + label: "Available Windows", + }); + + popoverBox.append(widgetTitle); + + const seaparator = new Gtk.Separator({ + marginTop: 5, + marginBottom: 10, + }); + + popoverBox.append(seaparator); + + const addClient = (client: AstalHyprland.Client) => { + const clientBox = new Gtk.Box(); + + // Workspace description + const descWS = new Gtk.Label({ label: "(WS " }); + + // Workpsace information + const workspace = new Gtk.Label(); + client.workspace.bind_property("name", workspace, "label", SYNC); + + const windowClassDesc = new Gtk.Label({ label: ") [" }); + + const windowClass = new Gtk.Label(); + windowClass.label = client.get_initial_class(); + + const titleDesc = new Gtk.Label({ label: "] " }); + titleDesc.set_margin_end(2); + + const title = new Gtk.Label(); + client.bind_property("title", title, "label", SYNC); + + clientBox.append(descWS); + clientBox.append(workspace); + clientBox.append(windowClassDesc); + clientBox.append(windowClass); + clientBox.append(titleDesc); + clientBox.append(title); + + const button = new Gtk.Button(); + button.connect( 'clicked', () => { + client.workspace.focus(); + } ); + button.set_child(clientBox); + + popoverBox.append(button); + + clients.set(client.get_address(), button); + }; + + // Populate with already added clients + const c = hypr.get_clients(); + for (let index = 0; index < c.length; index++) { + addClient(c[index]); + } + + hypr.connect("client-added", (_, client) => { + addClient(client); + }); + + hypr.connect("client-removed", (_, client) => { + const c = clients.get(client); + if (c) { + popoverBox.remove(c); + c.run_dispose(); + clients.delete(client); + } + }); + + popover.set_child(popoverBox); + return popover; + }; + + const windowPopover = WindowPopover(); + + // ─────────────────────────────────────────────────────────────────── + // Return fully assembled HyprlandFocusedClient box + // ─────────────────────────────────────────────────────────────────── + return ( + + + {windowPopover} + + ); +}; + +export default { + Workspace, + ActiveWindow, + SysTray, +}; diff --git a/config/astal/components/bar/modules/QuickView.tsx b/config/astal/components/bar/modules/QuickView.tsx new file mode 100644 index 0000000..f8c8177 --- /dev/null +++ b/config/astal/components/bar/modules/QuickView.tsx @@ -0,0 +1,186 @@ +import { bind } from "astal"; +import AstalBattery from "gi://AstalBattery"; +import AstalBluetooth from "gi://AstalBluetooth"; +import AstalNetwork from "gi://AstalNetwork"; +import AstalWp from "gi://AstalWp"; +import { Gtk } from "astal/gtk4"; +import Brightness from "../../../util/brightness"; +import QuickActions from "../../QuickActions/QuickActions"; + +const STATE = AstalNetwork.DeviceState; + +const QuickView = () => { + const qa = QuickActions.QuickActions(); + const showQuickActions = () => { + qa.popup(); + }; + + return ( + + ); +}; + +const NetworkWidget = () => { + const network = AstalNetwork.get_default(); + + return ( + + { + if (state === AstalNetwork.State.CONNECTING) { + return "chronometer-reset-symbolic"; + } else if ( + state === AstalNetwork.State.CONNECTED_LOCAL || + state === AstalNetwork.State.CONNECTED_SITE || + state === AstalNetwork.State.CONNECTED_GLOBAL + ) { + return "network-wired-activated-symbolic"; + } else { + return "paint-unknown-symbolic"; + } + })} + cssClasses={["network-widget", "quick-view-symbol"]} + visible={bind(network.wifi, "state").as( + state => state !== STATE.ACTIVATED, + )} + > + { + if (state === STATE.ACTIVATED) { + return network.wifi.iconName; + } else { + return ""; + } + })} + cssClasses={["network-widget", "quick-view-symbol"]} + visible={bind(network.wifi, "state").as( + state => state === STATE.ACTIVATED, + )} + > + + ); +}; + +const BluetoothWidget = () => { + const bluetooth = AstalBluetooth.get_default(); + const enabled = bind(bluetooth.adapter, "powered"); + const connected = bind(bluetooth, "isConnected"); + + // For each connected BT device, render status + return ( + + e)}> + c)} + > + !c)} + > + + !e)} + > + + {bind(bluetooth, "devices").as(devices => { + return devices.map(device => { + return ( + c)}> + icon, + )} + > + + + ); + }); + })} + + + ); +}; + +const BatteryWidget = () => { + const battery = AstalBattery.get_default(); + if (battery.get_is_present()) { + return ( + icon)} + cssClasses={["quick-view-symbol"]} + > + ); + } else { + return ; + } + // Else, no battery available -> Don't show the widget +}; + +const BrightnessWidget = () => { + const brightness = Brightness.get_default(); + const screen_brightness = bind(brightness, "screen"); + + return ( + + ); +}; + +const Audio = () => { + const wireplumber = AstalWp.get_default(); + if (wireplumber) { + return ( + + icon, + )} + cssClasses={["quick-view-symbol"]} + > + icon)} + cssClasses={["quick-view-symbol"]} + > + + ); + } else { + print( + "[ WirePlumber ] Could not connect, Audio support in bar will be missing", + ); + return ; + } +}; + +// cssClasses={[ 'quick-view-symbol' ]} + +export default { + QuickView, +}; diff --git a/config/astal/components/bar/modules/SystemInfo.tsx b/config/astal/components/bar/modules/SystemInfo.tsx new file mode 100644 index 0000000..f63c1f6 --- /dev/null +++ b/config/astal/components/bar/modules/SystemInfo.tsx @@ -0,0 +1,211 @@ +import { exec, execAsync, GLib, interval, Variable } from "astal"; +import { Gtk } from "astal/gtk4"; +import AstalBattery from "gi://AstalBattery?version=0.1"; + +const FETCH_INTERVAL = 2000; + +const cpuUtil = Variable("0%"); +const ramUtil = Variable("0%"); +const ramUsed = Variable("0MiB"); +const gpuUtil = Variable("0%"); +let gpuName = "card1"; +let enabled = false; + +const refreshStats = (): Stats => { + gpuName = exec(`/bin/bash -c "ls /sys/class/drm/ | grep '^card[0-9]*$'"`); + const cpuNameInSensors = "CPUTIN"; + const stats = { + kernel: exec("uname -sr"), + netSpeed: exec( + `/bin/bash -c "interface=$(ip route get 8.8.8.8 | awk '{print $5; exit}') && cat \"/sys/class/net/$interface/speed\""`, + ), + cpuTemp: exec( + `/bin/bash -c "sensors | grep -m1 ${cpuNameInSensors} | awk '{print $2}'"`, + ), + cpuClk: exec( + `awk '/cpu MHz/ {sum+=$4; ++n} END {print sum/n " MHz"}' /proc/cpuinfo`, + ), + gpuTemp: exec( + `/bin/bash -c "sensors | grep -E 'edge' | awk '{print $2}'"`, + ), + gpuClk: exec( + `/bin/bash -c "cat /sys/class/drm/${gpuName}/device/pp_dpm_sclk | grep '\\*' | awk '{print $2 $3}'"`, + ), + vram: + Math.round( + parseInt( + exec( + `cat /sys/class/drm/${gpuName}/device/mem_info_vram_used`, + ), + ) / + 1024 / + 1024, + ) + "MiB", + availableVRAM: + Math.round( + parseInt( + exec( + `cat /sys/class/drm/${gpuName}/device/mem_info_vram_total`, + ), + ) / + 1024 / + 1024, + ) + "MiB", + }; + + return stats; +}; + +const systemStats: Variable = Variable(refreshStats()); + +const availableFeatures = { + cpu: true, + ram: true, +}; + +const featureTest = () => { + // Check if awk & sed are available + try { + exec("awk -V"); + exec("sed --version"); + enabled = true; + } catch (e) { + printerr( + "[ SysInfo ] AWK or SED missing! No system info will be available", + ); + enabled = false; + return; + } + + // Check if mpstat is available + try { + exec("mpstat -V"); + } catch (e) { + availableFeatures.cpu = false; + printerr( + "[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!", + ); + } +}; + +const info = () => { + return ( + + + + + + + + + ); +}; + +const SystemInformationPanel = () => { + const popover = new Gtk.Popover(); + + popover.set_child(info()); + + return popover; +}; + +const sysInfoFetcher = () => { + if (enabled) { + if (availableFeatures.cpu) { + cpuUtil.set( + "" + + Math.round( + parseFloat(exec(`/bin/fish -c cpu-utilization`)), + ), + ); + } + if (availableFeatures.ram) { + ramUtil.set( + "" + + Math.round( + parseFloat( + exec( + `/bin/bash -c "free | awk '/Mem/ { printf(\\"%.2f\\\\n\\", ($3/$2)*100) }'"`, + ), + ), + ), + ); + ramUsed.set( + exec( + `/bin/bash -c \"free -h | awk '/^Mem:/ {print $3 \\" used of \\" $2}'\"`, + ) + .replaceAll("Gi", "GiB") + .replaceAll("Mi", "MiB"), + ); + } + gpuUtil.set(exec("cat /sys/class/drm/card1/device/gpu_busy_percent")); + } +}; + +const panel = SystemInformationPanel(); + +const SystemInfo = () => { + featureTest(); + + const openSysInfo = async () => { + panel.popup(); + systemStats.set(refreshStats()); + }; + + if (enabled) { + sysInfoFetcher(); + interval(FETCH_INTERVAL, sysInfoFetcher); + + return ( + + ); + } else { + return ; + } +}; + +export default { + SystemInfo, + panel, +}; diff --git a/config/astal/components/bar/ui/modules/stats.d.ts b/config/astal/components/bar/modules/stats.d.ts similarity index 100% rename from config/astal/components/bar/ui/modules/stats.d.ts rename to config/astal/components/bar/modules/stats.d.ts diff --git a/config/astal/components/bar/ui/QuickActions/QuickActions.tsx b/config/astal/components/bar/ui/QuickActions/QuickActions.tsx deleted file mode 100644 index 096af5f..0000000 --- a/config/astal/components/bar/ui/QuickActions/QuickActions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Gtk } from "astal/gtk4" -import Power from "./modules/Power"; -import Audio from "./modules/Audio/Audio"; -import Bluetooth from "./modules/Bluetooth/Bluetooth"; - -const QuickActions = () => { - const popover = new Gtk.Popover( { cssClasses: [ 'quick-actions-popover' ] } ); - - popover.set_child( createQuickActionMenu() ); - - return popover; -} - - -const createQuickActionMenu = () => { - // TODO: For the future add WiFi / Networking back, for the time being remove, as unnecessary effort - return - - - - -} - - -// TODO: Expose additional functions to be usable through CLI -export default { - QuickActions -}; diff --git a/config/astal/components/bar/ui/QuickActions/modules/Bluetooth/bthelper.ts b/config/astal/components/bar/ui/QuickActions/modules/Bluetooth/bthelper.ts deleted file mode 100644 index e69de29..0000000 diff --git a/config/astal/components/bar/ui/QuickActions/modules/Brightness/Brightness.tsx b/config/astal/components/bar/ui/QuickActions/modules/Brightness/Brightness.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/config/astal/components/bar/ui/QuickActions/modules/Player/Player.tsx b/config/astal/components/bar/ui/QuickActions/modules/Player/Player.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/config/astal/components/bar/ui/QuickActions/quickactions.scss b/config/astal/components/bar/ui/QuickActions/quickactions.scss deleted file mode 100644 index bc4a9b1..0000000 --- a/config/astal/components/bar/ui/QuickActions/quickactions.scss +++ /dev/null @@ -1,13 +0,0 @@ -.title-2 { - font-size: 1.2rem; - font-weight: bold; -} - -.bt-conn-list { - margin-bottom: 20px; -} - -popover>box { - margin: 10px; - border-radius: 50px; -} diff --git a/config/astal/components/bar/ui/bar.scss b/config/astal/components/bar/ui/bar.scss deleted file mode 100644 index 7b19d10..0000000 --- a/config/astal/components/bar/ui/bar.scss +++ /dev/null @@ -1,19 +0,0 @@ -$fg-color: #{"@theme_fg_color"}; -$bg-color: #{"@theme_bg_color"}; - -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/astal/components/bar/ui/modules/Calendar.tsx b/config/astal/components/bar/ui/modules/Calendar.tsx deleted file mode 100644 index abe16c7..0000000 --- a/config/astal/components/bar/ui/modules/Calendar.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { GLib, Variable } from "astal" -import { Gtk } from "astal/gtk4" - -const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => { - const time = Variable("").poll(1000, () => - GLib.DateTime.new_now_local().format(format)!) - - return - - - - - -} - -const Calendar = () => { - -} - - -export default { - Time -} diff --git a/config/astal/components/bar/ui/modules/Hyprland.tsx b/config/astal/components/bar/ui/modules/Hyprland.tsx deleted file mode 100644 index 0933a78..0000000 --- a/config/astal/components/bar/ui/modules/Hyprland.tsx +++ /dev/null @@ -1,157 +0,0 @@ - -import AstalTray from "gi://AstalTray"; -import { bind, GObject } from "astal"; -import AstalHyprland from "gi://AstalHyprland"; -import { Gtk } from "astal/gtk4"; - -const SYNC = GObject.BindingFlags.SYNC_CREATE; - -const SysTray = () => { - const trayBox = new Gtk.Box(); - const tray = AstalTray.get_default(); - - const trayItems = new Map(); - const trayAddedHandler = 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); - trayBox.append(button); - }) - - const trayRemovedHandler = tray.connect("item-removed", (_, id) => { - const button = trayItems.get(id); - if (button) { - trayBox.remove(button); - button.run_dispose(); - trayItems.delete(id); - } - }); - - trayBox.connect("destroy", () => { - tray.disconnect(trayAddedHandler); - tray.disconnect(trayRemovedHandler); - }); - - return trayBox; -} - - -const Workspace = () => { - const hypr = AstalHyprland.get_default() - - return - {bind(hypr, "workspaces").as(wss => wss - .filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces - .sort((a, b) => a.id - b.id) - .map(ws => ( - - )) - )} - -} - - -/** - * Displays the name of the currently active window and provides a popover for - * displaying all available clients - */ -const ActiveWindow = () => { - const hypr = AstalHyprland.get_default(); - const focused = bind( hypr, "focusedClient" ); - - const WindowPopover = (): Gtk.Popover => { - // Set up boxes + Popover - const clients = new Map(); - const popover = new Gtk.Popover(); - const popoverBox = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL } ); - - const addClient = ( client: AstalHyprland.Client ) => { - const clientBox = new Gtk.Box(); - - // Workspace description - const descWS = new Gtk.Label( { label: '(WS ' } ); - - // Workpsace information - const workspace = new Gtk.Label(); - client.workspace.bind_property( 'name', workspace, 'label', SYNC ); - - const windowClassDesc = new Gtk.Label( { label: ') [' } ); - - const windowClass = new Gtk.Label(); - windowClass.label = client.get_initial_class(); - - const titleDesc = new Gtk.Label( { label: '] ' } ); - titleDesc.set_margin_end( 2 ); - - const title = new Gtk.Label(); - client.bind_property( 'title', title, 'label', SYNC ); - - clientBox.append( descWS ); - clientBox.append( workspace ); - clientBox.append( windowClassDesc ); - clientBox.append( windowClass ); - clientBox.append( titleDesc ); - clientBox.append( title ); - - popoverBox.append( clientBox ); - - clients.set( client.get_address(), clientBox ); - } - - // Populate with already added clients - const c = hypr.get_clients(); - for ( let index = 0; index < c.length; index++ ) { - addClient( c[ index ] ); - } - - hypr.connect( 'client-added', ( _, client ) => { - addClient( client ); - } ); - - hypr.connect( 'client-removed', ( _, client ) => { - const c = clients.get( client ); - if ( c ) { - popoverBox.remove( c ); - c.run_dispose(); - clients.delete( client ); - } - } ); - - popover.set_child( popoverBox ); - return popover; - } - - const windowPopover = WindowPopover(); - - // ─────────────────────────────────────────────────────────────────── - // Return fully assembled HyprlandFocusedClient box - // ─────────────────────────────────────────────────────────────────── - return - - { windowPopover } - -} - - -export default { - Workspace, - ActiveWindow, - SysTray -} diff --git a/config/astal/components/bar/ui/modules/QuickView.tsx b/config/astal/components/bar/ui/modules/QuickView.tsx deleted file mode 100644 index f6f7d42..0000000 --- a/config/astal/components/bar/ui/modules/QuickView.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { bind } from "astal"; -import AstalBattery from "gi://AstalBattery"; -import AstalBluetooth from "gi://AstalBluetooth"; -import AstalNetwork from "gi://AstalNetwork" -import AstalWp from "gi://AstalWp"; -import Brightness from "../../util/brightness"; -import { Gtk } from "astal/gtk4"; -import QuickActions from "../QuickActions/QuickActions"; - -const STATE = AstalNetwork.DeviceState; - - -const QuickView = () => { - const quickActions = QuickActions.QuickActions(); - - return -} - - -const NetworkWidget = () => { - const network = AstalNetwork.get_default(); - - return - { - if ( state === AstalNetwork.State.CONNECTING ) { - return 'chronometer-reset-symbolic'; - } else if ( state === AstalNetwork.State.CONNECTED_LOCAL || state === AstalNetwork.State.CONNECTED_SITE || state === AstalNetwork.State.CONNECTED_GLOBAL ) { - return 'network-wired-activated-symbolic'; - } else { - return 'paint-unknown-symbolic'; - } - } )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED )}> - { - if ( state === STATE.ACTIVATED ) { - return network.wifi.iconName - } else { - return ''; - } - } )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED )}> - - - -} - -const BluetoothWidget = () => { - const bluetooth = AstalBluetooth.get_default(); - const enabled = bind( bluetooth.adapter, "powered" ); - const connected = bind( bluetooth, "isConnected" ); - - // For each connected BT device, render status - return - e )}> - c )}> - !c )}> - - !e )}> - - {bind( bluetooth, 'devices' ).as( devices => { - return devices.map( device => { - return c )}> - icon )}> - - - } ); - } )} - - -} - - -const BatteryWidget = () => { - const battery = AstalBattery.get_default(); - if ( battery.get_is_present() ) { - return icon )} cssClasses={[ 'quick-view-symbol' ]}> - } else { - return - } - // Else, no battery available -> Don't show the widget -} - - -const BrightnessWidget = () => { - const brightness = Brightness.get_default(); - const screen_brightness = bind( brightness, "screen" ); - - return -} - - -const Audio = () => { - const wireplumber = AstalWp.get_default(); - if ( wireplumber ) { - return - icon )} cssClasses={[ 'quick-view-symbol' ]}> - icon )} cssClasses={[ 'quick-view-symbol' ]}> - - } else { - print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing' ); - return ; - } -} - -// cssClasses={[ 'quick-view-symbol' ]} - -export default { - QuickView -} diff --git a/config/astal/components/bar/ui/modules/SystemInfo.tsx b/config/astal/components/bar/ui/modules/SystemInfo.tsx deleted file mode 100644 index d909b2c..0000000 --- a/config/astal/components/bar/ui/modules/SystemInfo.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { exec, GLib, interval, Variable } from "astal" -import { Gtk } from "astal/gtk4"; -import AstalBattery from "gi://AstalBattery?version=0.1"; - - -const FETCH_INTERVAL = 2000; - - -const cpuUtil = Variable( '0%' ); -const ramUtil = Variable( '0%' ); -const ramUsed = Variable( '0MiB' ); -const gpuUtil = Variable( '0%' ); -let gpuName = 'card1'; -let enabled = false; - -const refreshStats = (): Stats => { - gpuName = exec( `/bin/bash -c "ls /sys/class/drm/ | grep '^card[0-9]*$'"` ); - const cpuNameInSensors = 'CPUTIN' - const stats = { - kernel: exec( 'uname -sr' ), - netSpeed: exec( `/bin/bash -c "interface=$(ip route get 8.8.8.8 | awk '{print $5; exit}') && cat \"/sys/class/net/$interface/speed\""` ), - cpuTemp: exec( `/bin/bash -c "sensors | grep -m1 ${cpuNameInSensors} | awk '{print $2}'"` ), - cpuClk: exec( `awk '/cpu MHz/ {sum+=$4; ++n} END {print sum/n " MHz"}' /proc/cpuinfo` ), - gpuTemp: exec( `/bin/bash -c "sensors | grep -E 'edge' | awk '{print $2}'"` ), - gpuClk: exec( `/bin/bash -c "cat /sys/class/drm/${gpuName}/device/pp_dpm_sclk | grep '\\*' | awk '{print $2 $3}'"` ), - vram: Math.round( parseInt( exec( `cat /sys/class/drm/${gpuName}/device/mem_info_vram_used` ) ) / 1024 / 1024 ) + 'MiB', - availableVRAM: Math.round( parseInt( exec( `cat /sys/class/drm/${gpuName}/device/mem_info_vram_total` ) ) / 1024 / 1024 ) + 'MiB', - } - - return stats; -} - -const systemStats: Variable = Variable( refreshStats() ); - - -const availableFeatures = { - cpu: true, - ram: true, -} - - -const featureTest = () => { - // Check if awk & sed are available - try { - exec( 'awk -V' ); - exec( 'sed --version' ); - enabled = true; - } catch ( e ) { - printerr( '[ SysInfo ] AWK or SED missing! No system info will be available' ); - enabled = false; - return; - } - - // Check if mpstat is available - try { - exec( 'mpstat -V' ); - } catch ( e ) { - availableFeatures.cpu = false; - printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' ); - } -} - -const info = () => { - return - - - ; -} - - -const SystemInformationPanel = () => { - const popover = new Gtk.Popover(); - - popover.set_child( info() ); - - return popover; -} - - -const sysInfoFetcher = () => { - if ( enabled ) { - if ( availableFeatures.cpu ) { - cpuUtil.set( '' + Math.round( parseFloat( exec( `/bin/fish -c cpu-utilization` ) ) ) ); - } - if ( availableFeatures.ram ) { - ramUtil.set( '' + Math.round( parseFloat( exec( `/bin/bash -c "free | awk '/Mem/ { printf(\\"%.2f\\\\n\\", ($3/$2)*100) }'"` ) ) ) ); - ramUsed.set( exec( `/bin/bash -c \"free -h | awk '/^Mem:/ {print $3 \\" used of \\" $2}'\"` ).replaceAll( 'Gi', 'GiB' ).replaceAll( 'Mi', 'MiB' ) ); - } - gpuUtil.set( exec( 'cat /sys/class/drm/card1/device/gpu_busy_percent' ) ); - } -} - - -const panel = SystemInformationPanel(); - - -const SystemInfo = () => { - featureTest(); - - const openSysInfo = async () => { - panel.popup(); - systemStats.set( refreshStats() ); - } - - if ( enabled ) { - sysInfoFetcher(); - interval( FETCH_INTERVAL, sysInfoFetcher ); - - return - } else { - return - } -} - - -export default { - SystemInfo, - panel -} diff --git a/config/astal/components/bar/util/sysinfo.ts b/config/astal/components/bar/util/sysinfo.ts deleted file mode 100644 index e69de29..0000000 diff --git a/config/astal/no-avatar-icon.jpg b/config/astal/no-avatar-icon.jpg new file mode 100644 index 0000000..87ecabf Binary files /dev/null and b/config/astal/no-avatar-icon.jpg differ diff --git a/config/astal/style.scss b/config/astal/style.scss index f474652..3eebbed 100644 --- a/config/astal/style.scss +++ b/config/astal/style.scss @@ -1,6 +1,7 @@ -@use './components/notifications/notifications.scss'; -@use './components/bar/ui/bar.scss'; -@use './components/bar/ui/QuickActions/quickactions.scss'; +/* @use './components/notifications/notifications.scss'; */ +@use "./components/bar/bar.scss"; +@use "./components/QuickActions/quickactions.scss"; +@use "./util/colours.scss" as *; * { font-size: 1rem; @@ -10,3 +11,13 @@ empty { min-width: 0; background-color: transparent; } + +.title { + font-size: 1.5rem; + font-weight: bold; +} + +.title-2 { + font-size: 1.2rem; + font-weight: bold; +} diff --git a/config/astal/components/bar/util/brightness.ts b/config/astal/util/brightness.ts similarity index 100% rename from config/astal/components/bar/util/brightness.ts rename to config/astal/util/brightness.ts diff --git a/config/astal/util/colours.scss b/config/astal/util/colours.scss new file mode 100644 index 0000000..e4a7257 --- /dev/null +++ b/config/astal/util/colours.scss @@ -0,0 +1,4 @@ +$fg-color: #{"@theme_fg_color"}; +$bg-color: #{"@theme_bg_color"}; +$accent-color: #202050; +$accent-color-2: #202080; diff --git a/config/astal/util/state.ts b/config/astal/util/state.ts new file mode 100644 index 0000000..c1472c9 --- /dev/null +++ b/config/astal/util/state.ts @@ -0,0 +1,3 @@ +import { Variable } from "astal"; + +export const quickActionsState = Variable( false ); diff --git a/scripts/get-profile-picture b/scripts/get-profile-picture new file mode 100755 index 0000000..4240b2c --- /dev/null +++ b/scripts/get-profile-picture @@ -0,0 +1,20 @@ +#!/bin/bash + +USER_NAME=$(whoami) + +# Common paths to check +paths=( + "/var/lib/AccountsService/icons/$USER_NAME" + "$HOME/.face" + "$HOME/.face.icon" +) + +for path in "${paths[@]}"; do + if [ -f "$path" ]; then + echo "$path" + exit 0 + fi +done + +# Nothing found — exit silently or optionally with an error code +exit 0