From 8a2180e120923a2b4a14256912c2836b3dfceb6f Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Tue, 22 Apr 2025 15:30:41 +0200 Subject: [PATCH] [AGS] GTK 4 Migration: Done, Start adding QuickActions Probably gonna abandon the QuickActions, as that is just way too much effort for what it does. Will be providing keybinds for doing what I wanted to do there in Hyprland --- build/build.js | 4 + config/astal/app.ts | 6 +- config/astal/components/bar/ui/Bar.tsx | 14 +- .../astal/components/bar/ui/QuickActions.tsx | 76 ------ .../bar/ui/QuickActions/QuickActions.tsx | 31 +++ .../modules/Networking/Networking.tsx | 221 ++++++++++++++++++ .../QuickActions/modules/Networking/README.md | 2 + .../modules/Networking/network.d.ts | 14 ++ .../modules/Networking/networkinghelper.ts | 161 +++++++++++++ .../ui/{ => QuickActions}/quickactions.scss | 0 .../components/bar/ui/modules/Calendar.tsx | 12 +- .../components/bar/ui/modules/Hyprland.tsx | 138 +++++++++-- .../components/bar/ui/modules/QuickView.tsx | 10 +- .../components/bar/ui/modules/SystemInfo.tsx | 10 - .../components/notifications/handler.tsx | 65 ++++-- .../notifications/notifications.scss | 21 +- .../notifications/notifications.tsx | 38 +-- config/astal/style.scss | 39 ++-- prompts.md | 16 ++ 19 files changed, 678 insertions(+), 200 deletions(-) delete mode 100644 config/astal/components/bar/ui/QuickActions.tsx create mode 100644 config/astal/components/bar/ui/QuickActions/QuickActions.tsx create mode 100644 config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx create mode 100644 config/astal/components/bar/ui/QuickActions/modules/Networking/README.md create mode 100644 config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts create mode 100644 config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts rename config/astal/components/bar/ui/{ => QuickActions}/quickactions.scss (100%) diff --git a/build/build.js b/build/build.js index d775909..10218bc 100644 --- a/build/build.js +++ b/build/build.js @@ -6,6 +6,10 @@ const os = require( 'os' ); const render = require( './render' ); const { treeWalker } = require('./util'); +// TODO: Better comments +// TODO: Split up configs and buildables +// TODO: Make user choose if to use borders or not (otherwise border colour = background colour) + // Prompt user to select a wallpaper (if no path is passed as argument) const wallpapers = treeWalker( path.join( os.homedir(), '/NextCloud/Wallpapers' ), '*' ); diff --git a/config/astal/app.ts b/config/astal/app.ts index b0f7fed..d711b7c 100644 --- a/config/astal/app.ts +++ b/config/astal/app.ts @@ -2,7 +2,7 @@ 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/ui/Bar"; App.start({ instanceName: "runner", @@ -10,7 +10,7 @@ App.start({ main() { notifications.startNotificationHandler( App.get_monitors()[0] ); // TODO: Monitor handling - // Bar.Bar( App.get_monitors()[0] ); + Bar.Bar( App.get_monitors()[0] ); }, requestHandler(request, res) { const args = request.trimStart().split( ' ' ); @@ -19,7 +19,7 @@ App.start({ if ( args[ 0 ] === 'notifier' ) { res( notifications.cliHandler( args ) ); } else if ( args[ 0 ] === 'bar' ) { - // res( Bar.cliHandler( args ) ); + res( Bar.cliHandler( args ) ); } }, }) diff --git a/config/astal/components/bar/ui/Bar.tsx b/config/astal/components/bar/ui/Bar.tsx index 7c9c7da..8970216 100644 --- a/config/astal/components/bar/ui/Bar.tsx +++ b/config/astal/components/bar/ui/Bar.tsx @@ -1,4 +1,4 @@ -import { Astal, Gdk, Gtk } from "astal/gtk3"; +import { App, Astal, Gdk, Gtk } from "astal/gtk4"; import Hyprland from "./modules/Hyprland"; import Calendar from "./modules/Calendar"; import QuickView from "./modules/QuickView"; @@ -11,19 +11,23 @@ const Bar = (gdkmonitor: Gdk.Monitor) => { - - + anchor={TOP | LEFT | RIGHT} + visible + application={App} + child={ + + - + + }> ); } diff --git a/config/astal/components/bar/ui/QuickActions.tsx b/config/astal/components/bar/ui/QuickActions.tsx deleted file mode 100644 index 052ce48..0000000 --- a/config/astal/components/bar/ui/QuickActions.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import Gtk from "gi://Gtk"; -import AstalNetwork from "gi://AstalNetwork"; -import AstalBluetooth from "gi://AstalBluetooth"; -import { exec } from "gi://GLib"; - -let quickActionsMenu; - -// Toggle WiFi connection -function toggleWiFi() { - const network = AstalNetwork.get_default(); - const wifi = network.get_wifi()!; - const state = wifi.get_state(); - - if (state === AstalNetwork.WifiState.DISCONNECTED) { - wifi.connect(); - } else { - wifi.disconnect(); - } -} - -// Toggle Bluetooth power state -function toggleBluetooth() { - const bluetooth = AstalBluetooth.get_default(); - const adapter = bluetooth.get_adapter(); - const powered = adapter?.get_powered(); - - adapter?.set_powered(!powered); -} - -// Adjust volume or microphone (opens pavucontrol) -function adjustVolume() { - exec("pavucontrol"); -} - -function adjustMic() { - exec("pavucontrol"); -} - -// Power menu -function showPowerMenu() { - exec("power-menu"); -} - -// Show WiFi network picker -function pickWiFi() { - const wifi = AstalNetwork.get_default().get_wifi(); - wifi.show_picker(); -} - -// Show Bluetooth device picker -function pickBluetooth() { - const bluetooth = AstalBluetooth.get_default(); - bluetooth.show_picker(); -} - -// Create menu using JSX -function createQuickActionsMenu() { - quickActionsMenu = ( - - - - - - - - - - - - ); - - return quickActionsMenu; -} - -// Export the function -export { createQuickActionsMenu }; diff --git a/config/astal/components/bar/ui/QuickActions/QuickActions.tsx b/config/astal/components/bar/ui/QuickActions/QuickActions.tsx new file mode 100644 index 0000000..d08cec9 --- /dev/null +++ b/config/astal/components/bar/ui/QuickActions/QuickActions.tsx @@ -0,0 +1,31 @@ +import { Astal, App } from "astal/gtk4"; +import PowerProfiles from "gi://AstalPowerProfiles"; +import { Variable } from "astal"; +import { Sliders } from "./modules/Sliders"; +import { Toggles } from "./modules/Toggles"; +import { PowerProfileBox } from "./modules/PowerProfileBox"; +import { BatteryBox } from "./modules/BatteryBox"; + +export default function QuickActions() { + const powerprofiles = PowerProfiles.get_default(); + const hasProfiles = powerprofiles?.get_profiles()?.length > 0; + const { TOP, RIGHT } = Astal.WindowAnchor; + const visible = Variable(false); + return ( + + + + {hasProfiles && } + + + + + ); +} diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx b/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx new file mode 100644 index 0000000..0b6acd0 --- /dev/null +++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx @@ -0,0 +1,221 @@ +import { execAsync, bind } from "astal"; +import Network from "gi://AstalNetwork"; +import { App, Gtk } from "astal/gtk4"; +import { NetworkItem } from "./modules/NetworkItem"; +import { PasswordDialog } from "./modules/PasswordDialog"; +import { + availableNetworks, + savedNetworks, + activeNetwork, + showPasswordDialog, + scanNetworks, + getSavedNetworks, + disconnectNetwork, + forgetNetwork, + isExpanded, + refreshIntervalId, +} from "./networkinghelper"; + +// Main WiFi Box component +export const WiFiBox = () => { + const network = Network.get_default(); + + // Initial scan when component is first used + setTimeout(() => { + scanNetworks(); + getSavedNetworks(); + }, 100); + + return ( + + {/* WiFi Toggle Header */} + + + + + + {/* Networks List Revealer */} + { + const clearScanInterval = () => { + if (refreshIntervalId.get()) { + clearInterval( parseInt( '' + refreshIntervalId.get() )); + refreshIntervalId.set(null); + } + }; + bind(isExpanded).subscribe((expanded) => { + // Clear existing interval + clearScanInterval(); + + if (expanded) { + // Scan networks + network.wifi?.scan(); + + // Set up new interval if WiFi is enabled + if (network.wifi?.enabled) { + refreshIntervalId.set( + setInterval(() => { + scanNetworks(); + getSavedNetworks(); + print("updated"); + }, 10000), + ); + } + } else { + // Apply revealer bug fix when collapsed + App.toggle_window("system-menu"); + App.toggle_window("system-menu"); + } + }); + + // Monitor window toggling + const windowListener = App.connect("window-toggled", (_, window) => { + if (window.name === "system-menu" && isExpanded.get()) { + isExpanded.set(false); + } + }); + + // Clean up resources when component is destroyed + return () => { + App.disconnect(windowListener); + clearScanInterval(); + }; + }} + > + + v )}> + + + + + + ); +}; diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md b/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md new file mode 100644 index 0000000..ad95173 --- /dev/null +++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md @@ -0,0 +1,2 @@ +# Source +This is a modified version from [MatShell](https://github.com/Neurian/matshell) diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts b/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts new file mode 100644 index 0000000..4be5e37 --- /dev/null +++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts @@ -0,0 +1,14 @@ +import AstalNetwork from "gi://AstalNetwork?version=0.1"; + +interface CurrentWiFi { + ssid: string; + strength: number; + secured: boolean; +} + +interface WiFiDetails extends CurrentWiFi { + active: boolean; + accessPoint: AstalNetwork.AccessPoint; + iconName: string; +} + diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts b/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts new file mode 100644 index 0000000..b4bd6aa --- /dev/null +++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts @@ -0,0 +1,161 @@ +// From https://github.com/Neurarian/matshell/blob/master/utils/wifi.ts + +import { execAsync, Variable } from "astal"; +import Network from "gi://AstalNetwork"; +import { CurrentWiFi, WiFiDetails } from "./network"; + +// State trackers +export const availableNetworks: Variable = Variable([]); +export const savedNetworks: Variable = Variable([]); +export const activeNetwork: Variable = Variable(null); +export const isConnecting: Variable = Variable(false); +export const showPasswordDialog: Variable = Variable(false); +export const errorMessage: Variable = Variable(""); +export const isExpanded: Variable = Variable(false); +export const passwordInput: Variable = Variable(""); +export const selectedNetwork: Variable = Variable(null); +export const refreshIntervalId: Variable< + number | null | ReturnType +> = Variable(null); + +// Function to scan for available networks +export const scanNetworks = () => { + const network = Network.get_default(); + if (network && network.wifi) { + network.wifi.scan(); + + // Get available networks from access points + const networks: WiFiDetails[] = network.wifi.accessPoints + .map(ap => ({ + ssid: ap.ssid, + strength: ap.strength, + secured: ap.flags !== 0, + active: network.wifi.activeAccessPoint?.ssid === ap.ssid, + accessPoint: ap, + iconName: ap.iconName, + })) + .filter(n => n.ssid); + + // Sort by signal strength + networks.sort((a, b) => b.strength - a.strength); + + // Remove duplicates (same SSID) + const uniqueNetworks: WiFiDetails[] = []; + const seen = new Set(); + networks.forEach(network => { + if (!seen.has(network.ssid)) { + seen.add(network.ssid); + uniqueNetworks.push(network); + } + }); + + availableNetworks.set(uniqueNetworks); + + // Update active network + if (network.wifi.activeAccessPoint) { + activeNetwork.set({ + ssid: network.wifi.activeAccessPoint.ssid, + strength: network.wifi.activeAccessPoint.strength, + secured: network.wifi.activeAccessPoint.flags !== 0, + }); + } else { + activeNetwork.set(null); + } + } +}; + +// Function to list saved networks +export const getSavedNetworks = () => { + execAsync(["bash", "-c", "nmcli -t -f NAME,TYPE connection show"]) + .then(output => { + if (typeof output === "string") { + const savedWifiNetworks = output + .split("\n") + .filter(line => line.includes("802-11-wireless")) + .map(line => line.split(":")[0].trim()); + savedNetworks.set(savedWifiNetworks); + } + }) + .catch(error => console.error("Error fetching saved networks:", error)); +}; + +// Function to connect to a network + +export const connectToNetwork = ( + ssid: string, + password: null | string = null, +) => { + isConnecting.set(true); + errorMessage.set(""); + const network = Network.get_default(); + const currentSsid = network.wifi.ssid; + + // Function to perform the actual connection + const performConnection = () => { + let command = ""; + if (password) { + // Connect with password + command = `echo '${password}' | nmcli device wifi connect "${ssid}" --ask`; + } else { + // Connect without password (saved or open network) + command = `nmcli connection up "${ssid}" || nmcli device wifi connect "${ssid}"`; + } + + execAsync(["bash", "-c", command]) + .then(() => { + showPasswordDialog.set(false); + isConnecting.set(false); + scanNetworks(); // Refresh network list + }) + .catch(error => { + console.error("Connection error:", error); + errorMessage.set("Failed to connect. Check password."); + isConnecting.set(false); + }); + }; + + // If already connected to a network, disconnect first + if (currentSsid && currentSsid !== ssid) { + console.log( + `Disconnecting from ${currentSsid} before connecting to ${ssid}`, + ); + execAsync(["bash", "-c", `nmcli connection down "${currentSsid}"`]) + .then(() => { + // Wait a moment for the disconnection to complete fully + setTimeout(() => { + performConnection(); + }, 500); // 500ms delay for clean disconnection + }) + .catch(error => { + console.error("Disconnect error:", error); + // Continue with connection attempt even if disconnect fails + performConnection(); + }); + } else { + // No active connection or connecting to same network (reconnect case) + performConnection(); + } +}; + +// Function to disconnect from a network +export const disconnectNetwork = (ssid: string) => { + execAsync(["bash", "-c", `nmcli connection down "${ssid}"`]) + .then(() => { + scanNetworks(); // Refresh network list + }) + .catch(error => { + console.error("Disconnect error:", error); + }); +}; + +// Function to forget a saved network +export const forgetNetwork = (ssid: string) => { + execAsync(["bash", "-c", `nmcli connection delete "${ssid}"`]) + .then(() => { + getSavedNetworks(); // Refresh saved networks list + scanNetworks(); // Refresh network list + }) + .catch(error => { + console.error("Forget network error:", error); + }); +}; diff --git a/config/astal/components/bar/ui/quickactions.scss b/config/astal/components/bar/ui/QuickActions/quickactions.scss similarity index 100% rename from config/astal/components/bar/ui/quickactions.scss rename to config/astal/components/bar/ui/QuickActions/quickactions.scss diff --git a/config/astal/components/bar/ui/modules/Calendar.tsx b/config/astal/components/bar/ui/modules/Calendar.tsx index 9784b95..abe16c7 100644 --- a/config/astal/components/bar/ui/modules/Calendar.tsx +++ b/config/astal/components/bar/ui/modules/Calendar.tsx @@ -1,14 +1,16 @@ 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