[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
This commit is contained in:
parent
8b70f80e60
commit
8a2180e120
@ -6,6 +6,10 @@ const os = require( 'os' );
|
|||||||
const render = require( './render' );
|
const render = require( './render' );
|
||||||
const { treeWalker } = require('./util');
|
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)
|
// Prompt user to select a wallpaper (if no path is passed as argument)
|
||||||
const wallpapers = treeWalker( path.join( os.homedir(), '/NextCloud/Wallpapers' ), '*' );
|
const wallpapers = treeWalker( path.join( os.homedir(), '/NextCloud/Wallpapers' ), '*' );
|
||||||
|
@ -2,7 +2,7 @@ import { App } from "astal/gtk4"
|
|||||||
import style from "./style.scss"
|
import style from "./style.scss"
|
||||||
|
|
||||||
import notifications from "./components/notifications/handler";
|
import notifications from "./components/notifications/handler";
|
||||||
// import Bar from "./components/bar/ui/Bar";
|
import Bar from "./components/bar/ui/Bar";
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
instanceName: "runner",
|
instanceName: "runner",
|
||||||
@ -10,7 +10,7 @@ App.start({
|
|||||||
main() {
|
main() {
|
||||||
notifications.startNotificationHandler( App.get_monitors()[0] );
|
notifications.startNotificationHandler( App.get_monitors()[0] );
|
||||||
// TODO: Monitor handling
|
// TODO: Monitor handling
|
||||||
// Bar.Bar( App.get_monitors()[0] );
|
Bar.Bar( App.get_monitors()[0] );
|
||||||
},
|
},
|
||||||
requestHandler(request, res) {
|
requestHandler(request, res) {
|
||||||
const args = request.trimStart().split( ' ' );
|
const args = request.trimStart().split( ' ' );
|
||||||
@ -19,7 +19,7 @@ App.start({
|
|||||||
if ( args[ 0 ] === 'notifier' ) {
|
if ( args[ 0 ] === 'notifier' ) {
|
||||||
res( notifications.cliHandler( args ) );
|
res( notifications.cliHandler( args ) );
|
||||||
} else if ( args[ 0 ] === 'bar' ) {
|
} else if ( args[ 0 ] === 'bar' ) {
|
||||||
// res( Bar.cliHandler( args ) );
|
res( Bar.cliHandler( args ) );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -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 Hyprland from "./modules/Hyprland";
|
||||||
import Calendar from "./modules/Calendar";
|
import Calendar from "./modules/Calendar";
|
||||||
import QuickView from "./modules/QuickView";
|
import QuickView from "./modules/QuickView";
|
||||||
@ -11,19 +11,23 @@ const Bar = (gdkmonitor: Gdk.Monitor) => {
|
|||||||
<window gdkmonitor={gdkmonitor}
|
<window gdkmonitor={gdkmonitor}
|
||||||
cssClasses={["Bar"]}
|
cssClasses={["Bar"]}
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
anchor={TOP | LEFT | RIGHT}>
|
anchor={TOP | LEFT | RIGHT}
|
||||||
<box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}>
|
visible
|
||||||
<box hexpand halign={Gtk.Align.START}>
|
application={App}
|
||||||
|
child={
|
||||||
|
<box orientation={Gtk.Orientation.HORIZONTAL}>
|
||||||
|
<box hexpand halign={Gtk.Align.START} cssClasses={["BarLeft"]}>
|
||||||
<Calendar.Time />
|
<Calendar.Time />
|
||||||
<SystemInfo.SystemInfo />
|
<SystemInfo.SystemInfo />
|
||||||
<Hyprland.Workspace />
|
<Hyprland.Workspace />
|
||||||
</box>
|
</box>
|
||||||
<Hyprland.ActiveWindow />
|
<Hyprland.ActiveWindow />
|
||||||
<box hexpand halign={Gtk.Align.END}>
|
<box hexpand halign={Gtk.Align.END} cssClasses={["BarRight"]}>
|
||||||
<Hyprland.SysTray />
|
<Hyprland.SysTray />
|
||||||
<QuickView.QuickView />
|
<QuickView.QuickView />
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
}>
|
||||||
</window>
|
</window>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 = (
|
|
||||||
<menu>
|
|
||||||
<item label="📶 WiFi" onActivate={toggleWiFi} />
|
|
||||||
<item label="→ Pick Network" onActivate={pickWiFi} />
|
|
||||||
|
|
||||||
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
|
|
||||||
<item label="→ Pick Device" onActivate={pickBluetooth} />
|
|
||||||
|
|
||||||
<item label="🔊 Volume" onActivate={adjustVolume} />
|
|
||||||
<item label="🎙️ Microphone" onActivate={adjustMic} />
|
|
||||||
<item label="🔌 Power" onActivate={showPowerMenu} />
|
|
||||||
</menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
return quickActionsMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the function
|
|
||||||
export { createQuickActionsMenu };
|
|
31
config/astal/components/bar/ui/QuickActions/QuickActions.tsx
Normal file
31
config/astal/components/bar/ui/QuickActions/QuickActions.tsx
Normal file
@ -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 (
|
||||||
|
<window
|
||||||
|
name="system-menu"
|
||||||
|
application={App}
|
||||||
|
layer={Astal.Layer.OVERLAY}
|
||||||
|
anchor={TOP | RIGHT}
|
||||||
|
keymode={Astal.Keymode.ON_DEMAND}
|
||||||
|
visible={visible()}
|
||||||
|
>
|
||||||
|
<box cssClasses={["system-menu"]} vertical>
|
||||||
|
<Toggles />
|
||||||
|
{hasProfiles && <PowerProfileBox />}
|
||||||
|
<Sliders />
|
||||||
|
<BatteryBox />
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
@ -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 (
|
||||||
|
<box vertical cssClasses={["wifi-menu", "toggle"]}>
|
||||||
|
{/* WiFi Toggle Header */}
|
||||||
|
<box cssClasses={["toggle", "wifi-toggle"]}>
|
||||||
|
<button
|
||||||
|
onClicked={() => {
|
||||||
|
if (network.wifi.enabled) {
|
||||||
|
network.wifi.set_enabled(false);
|
||||||
|
} else network.wifi.set_enabled(true);
|
||||||
|
}}
|
||||||
|
cssClasses={bind(network.wifi, "enabled").as((enabled) =>
|
||||||
|
enabled ? ["button"] : ["button-disabled"],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<image iconName={bind(network.wifi, "icon_name")} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
hexpand={true}
|
||||||
|
onClicked={() => {
|
||||||
|
if (network.wifi.enabled) {
|
||||||
|
isExpanded.set(!isExpanded.get());
|
||||||
|
if (isExpanded.get()) {
|
||||||
|
scanNetworks();
|
||||||
|
getSavedNetworks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<box hexpand={true}>
|
||||||
|
<label
|
||||||
|
hexpand={true}
|
||||||
|
xalign={0}
|
||||||
|
label={bind(network.wifi, "ssid").as(
|
||||||
|
(ssid) =>
|
||||||
|
ssid || (network.wifi.enabled ? "Not Connected" : "WiFi Off"),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
iconName="pan-end-symbolic"
|
||||||
|
halign={Gtk.Align.END}
|
||||||
|
cssClasses={bind(isExpanded).as((expanded) =>
|
||||||
|
expanded
|
||||||
|
? ["arrow-indicator", "arrow-down"]
|
||||||
|
: ["arrow-indicator"],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
|
||||||
|
{/* Networks List Revealer */}
|
||||||
|
<revealer
|
||||||
|
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
|
||||||
|
transitionDuration={300}
|
||||||
|
revealChild={bind(isExpanded)}
|
||||||
|
setup={() => {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<box vertical cssClasses={["network-list"]}>
|
||||||
|
<box visible={showPasswordDialog( v => v )}>
|
||||||
|
<PasswordDialog />
|
||||||
|
</box>
|
||||||
|
|
||||||
|
<label label="Available Networks" cssClasses={["section-label"]} />
|
||||||
|
<label label="No networks found" cssClasses={["empty-label"]} visible={availableNetworks( net => net.length === 0 )}/>
|
||||||
|
<box visible={availableNetworks( networks => networks.length > 1 )}>
|
||||||
|
{availableNetworks( networks =>
|
||||||
|
networks.map( (network) => <NetworkItem network={network} />)
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
|
||||||
|
{savedNetworks((networks) => {
|
||||||
|
// Filter out networks already shown in available networks
|
||||||
|
const filteredNetworks = networks.filter(
|
||||||
|
(ssid) => !availableNetworks.get().some((n) => n.ssid === ssid)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only render the section if there are filtered networks to show
|
||||||
|
return filteredNetworks.length > 0 ? (
|
||||||
|
<box vertical>
|
||||||
|
<label label="Saved Networks" cssClasses={["section-label"]} />
|
||||||
|
{filteredNetworks.map((ssid) => (
|
||||||
|
<box cssClasses={["saved-network"]}>
|
||||||
|
<label label={ssid} />
|
||||||
|
<box hexpand={true} />
|
||||||
|
<button
|
||||||
|
label="Forget"
|
||||||
|
cssClasses={["forget-button", "button"]}
|
||||||
|
onClicked={() => forgetNetwork(ssid)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
) : (
|
||||||
|
<box></box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<box hexpand>
|
||||||
|
<button
|
||||||
|
halign={Gtk.Align.START}
|
||||||
|
cssClasses={["refresh-button"]}
|
||||||
|
onClicked={() => {
|
||||||
|
scanNetworks();
|
||||||
|
getSavedNetworks();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<image iconName="view-refresh-symbolic" />
|
||||||
|
</button>
|
||||||
|
{/* Connected Network Options */}
|
||||||
|
<box hexpand>
|
||||||
|
{activeNetwork((active) =>
|
||||||
|
active ? (
|
||||||
|
<box vertical cssClasses={["connected-network"]} hexpand>
|
||||||
|
<button
|
||||||
|
label="Disconnect"
|
||||||
|
cssClasses={["disconnect-button"]}
|
||||||
|
onClicked={() => disconnectNetwork(active.ssid)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
<button
|
||||||
|
cssClasses={["settings-button"]}
|
||||||
|
halign={Gtk.Align.END}
|
||||||
|
hexpand={false}
|
||||||
|
onClicked={() => {
|
||||||
|
execAsync([
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"XDG_CURRENT_DESKTOP=GNOME gnome-control-center wifi",
|
||||||
|
]);
|
||||||
|
isExpanded.set(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<image iconName={"emblem-system-symbolic"} />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</revealer>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
# Source
|
||||||
|
This is a modified version from [MatShell](https://github.com/Neurian/matshell)
|
14
config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts
vendored
Normal file
14
config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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<WiFiDetails[]> = Variable([]);
|
||||||
|
export const savedNetworks: Variable<string[]> = Variable([]);
|
||||||
|
export const activeNetwork: Variable<CurrentWiFi | null> = Variable(null);
|
||||||
|
export const isConnecting: Variable<boolean> = Variable(false);
|
||||||
|
export const showPasswordDialog: Variable<boolean> = Variable(false);
|
||||||
|
export const errorMessage: Variable<string> = Variable("");
|
||||||
|
export const isExpanded: Variable<boolean> = Variable(false);
|
||||||
|
export const passwordInput: Variable<string> = Variable("");
|
||||||
|
export const selectedNetwork: Variable<null | WiFiDetails> = Variable(null);
|
||||||
|
export const refreshIntervalId: Variable<
|
||||||
|
number | null | ReturnType<typeof setTimeout>
|
||||||
|
> = 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);
|
||||||
|
});
|
||||||
|
};
|
@ -1,14 +1,16 @@
|
|||||||
import { GLib, Variable } from "astal"
|
import { GLib, Variable } from "astal"
|
||||||
|
import { Gtk } from "astal/gtk4"
|
||||||
|
|
||||||
const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => {
|
const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => {
|
||||||
const time = Variable<string>("").poll(1000, () =>
|
const time = Variable<string>("").poll(1000, () =>
|
||||||
GLib.DateTime.new_now_local().format(format)!)
|
GLib.DateTime.new_now_local().format(format)!)
|
||||||
|
|
||||||
return <label
|
return <menubutton cssClasses={["Time"]} hexpand halign={Gtk.Align.CENTER}>
|
||||||
className="Time"
|
<label onDestroy={() => time.drop()} label={time()}></label>
|
||||||
onDestroy={() => time.drop()}
|
<popover>
|
||||||
label={time()}
|
<Gtk.Calendar />
|
||||||
/>
|
</popover>
|
||||||
|
</menubutton>
|
||||||
}
|
}
|
||||||
|
|
||||||
const Calendar = () => {
|
const Calendar = () => {
|
||||||
|
@ -1,38 +1,62 @@
|
|||||||
|
|
||||||
import AstalTray from "gi://AstalTray";
|
import AstalTray from "gi://AstalTray";
|
||||||
import { bind, Variable } from "astal";
|
import { bind, GObject } from "astal";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
import { Gtk } from "astal/gtk4";
|
import { Gtk } from "astal/gtk4";
|
||||||
|
|
||||||
|
const SYNC = GObject.BindingFlags.SYNC_CREATE;
|
||||||
|
|
||||||
const SysTray = () => {
|
const SysTray = () => {
|
||||||
|
const trayBox = new Gtk.Box();
|
||||||
const tray = AstalTray.get_default();
|
const tray = AstalTray.get_default();
|
||||||
|
|
||||||
return <box className="SysTray">
|
const trayItems = new Map<string, Gtk.MenuButton>();
|
||||||
{bind(tray, "items").as( items => items.map( item => (
|
const trayAddedHandler = tray.connect("item-added", (_, id) => {
|
||||||
<button
|
const item = tray.get_item(id);
|
||||||
tooltipMarkup={bind(item, "tooltipMarkup")}
|
const popover = Gtk.PopoverMenu.new_from_model(item.menu_model);
|
||||||
usePopover={false}
|
const icon = new Gtk.Image();
|
||||||
actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])}
|
const button = new Gtk.MenuButton({ popover, child: icon });
|
||||||
menuModel={bind(item, "menuModel")}>
|
|
||||||
<icon gicon={bind(item, "gicon")} />
|
item.bind_property("gicon", icon, "gicon", SYNC);
|
||||||
</button>
|
popover.insert_action_group("dbusmenu", item.action_group);
|
||||||
) ) ) }
|
item.connect("notify::action-group", () => {
|
||||||
</box>
|
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 Workspace = () => {
|
||||||
const hypr = AstalHyprland.get_default()
|
const hypr = AstalHyprland.get_default()
|
||||||
|
|
||||||
return <box className={"HyprlandWorkspaces"}>
|
return <box cssClasses={["HyprlandWorkspaces"]}>
|
||||||
{bind(hypr, "workspaces").as(wss => wss
|
{bind(hypr, "workspaces").as(wss => wss
|
||||||
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
|
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
|
||||||
.sort((a, b) => a.id - b.id)
|
.sort((a, b) => a.id - b.id)
|
||||||
.map(ws => (
|
.map(ws => (
|
||||||
<button
|
<button
|
||||||
className={bind(hypr, "focusedWorkspace").as(fw =>
|
cssClasses={bind(hypr, "focusedWorkspace").as(fw =>
|
||||||
ws === fw ? "HyprlandFocusedWorkspace" : "")}
|
ws === fw ? ["HyprlandFocusedWorkspace"] : [""])}
|
||||||
onClicked={() => ws.focus()}>
|
onButtonPressed={() => ws.focus()} child={<label label={String(ws.id)}></label>}>
|
||||||
{ws.id}
|
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@ -40,24 +64,88 @@ const Workspace = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the name of the currently active window and provides a popover for
|
||||||
|
* displaying all available clients
|
||||||
|
*/
|
||||||
const ActiveWindow = () => {
|
const ActiveWindow = () => {
|
||||||
const hypr = AstalHyprland.get_default();
|
const hypr = AstalHyprland.get_default();
|
||||||
const focused = bind( hypr, "focusedClient" );
|
const focused = bind( hypr, "focusedClient" );
|
||||||
let visible = Variable( false );
|
|
||||||
|
|
||||||
const toggleOverlay = () => {
|
const WindowPopover = (): Gtk.Popover => {
|
||||||
visible.set( !visible.get() );
|
// Set up boxes + Popover
|
||||||
|
const clients = new Map<string, Gtk.Box>();
|
||||||
|
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 <box cssName={"HyprlandFocusedClients"} visible={focused.as(Boolean)}>
|
return <box cssName={"HyprlandFocusedClients"} visible={focused.as(Boolean)}>
|
||||||
<Gtk.Button onClicked={toggleOverlay}>
|
<button onClicked={() => windowPopover.popup()}>
|
||||||
{focused.as( client => (
|
{focused.as( client => (
|
||||||
client && <label label={bind( client, "title" ).as( String )} />
|
client && <label label={bind( client, "title" ).as( String )} />
|
||||||
))}
|
))}
|
||||||
</Gtk.Button>
|
</button>
|
||||||
<eventbox visible={bind(visible).as( v => v )} name="popover-container">
|
{ windowPopover }
|
||||||
<label label="This is a test"></label>
|
|
||||||
</eventbox>
|
|
||||||
</box>
|
</box>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { bind } from "astal";
|
import { bind } from "astal";
|
||||||
import AstalBattery from "gi://AstalBattery?version=0.1";
|
import AstalBattery from "gi://AstalBattery";
|
||||||
import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
import AstalNetwork from "gi://AstalNetwork?version=0.1"
|
import AstalNetwork from "gi://AstalNetwork"
|
||||||
import AstalWp from "gi://AstalWp?version=0.1";
|
import AstalWp from "gi://AstalWp";
|
||||||
import Brightness from "../../util/brightness";
|
import Brightness from "../../util/brightness";
|
||||||
import { Gtk } from "astal/gtk3";
|
import { Gtk } from "astal/gtk4";
|
||||||
|
|
||||||
const QuickView = () => {
|
const QuickView = () => {
|
||||||
return <box>
|
return <box>
|
||||||
|
@ -18,7 +18,6 @@ const featureTest = () => {
|
|||||||
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
|
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen brightness... CTL might be available, but no screen controllable
|
|
||||||
// Battery... acpi might be present, but potentially no bat
|
// Battery... acpi might be present, but potentially no bat
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,8 +27,6 @@ const availableFeatures = {
|
|||||||
cpu: true,
|
cpu: true,
|
||||||
ram: true,
|
ram: true,
|
||||||
bat: true,
|
bat: true,
|
||||||
net: false,
|
|
||||||
brightness: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sysInfoFetcher = () => {
|
const sysInfoFetcher = () => {
|
||||||
@ -44,17 +41,10 @@ const sysInfoFetcher = () => {
|
|||||||
const acpi = exec( `acpi -i | grep 'Battery'` );
|
const acpi = exec( `acpi -i | grep 'Battery'` );
|
||||||
// TODO: Parse acpi output
|
// TODO: Parse acpi output
|
||||||
}
|
}
|
||||||
// if ( availableFeatures.net ) {
|
|
||||||
// // Using ifstat or vnstat probably, get current network de
|
|
||||||
// }
|
|
||||||
if ( availableFeatures.brightness ) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SystemInfo = () => {
|
const SystemInfo = () => {
|
||||||
|
|
||||||
return <box></box>
|
return <box></box>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Astal, Gdk } from "astal/gtk4"
|
import { App, Astal, Gdk } from "astal/gtk4"
|
||||||
import Notifd from "gi://AstalNotifd";
|
import Notifd from "gi://AstalNotifd";
|
||||||
import Notification from "./notifications";
|
import Notification from "./notifications";
|
||||||
import { timeout, Variable } from "astal"
|
import { timeout, Variable } from "astal"
|
||||||
@ -31,8 +31,7 @@ interface NotificationDetails {
|
|||||||
// │ Handler │
|
// │ Handler │
|
||||||
// ╰───────────────────────────────────────────────╯
|
// ╰───────────────────────────────────────────────╯
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
let ShownNotifications: number[] = [];
|
let ShownNotifications: Variable<number[]> = Variable( [] );
|
||||||
const ShownNotificationsCount: Variable<number> = Variable( 0 );
|
|
||||||
let Notifications: NotificationDetails[] = [];
|
let Notifications: NotificationDetails[] = [];
|
||||||
|
|
||||||
const notifd = Notifd.get_default();
|
const notifd = Notifd.get_default();
|
||||||
@ -46,6 +45,9 @@ notifd.ignoreTimeout = true;
|
|||||||
const deleteNotification = ( index: number ): void => {
|
const deleteNotification = ( index: number ): void => {
|
||||||
hideNotification( index );
|
hideNotification( index );
|
||||||
Notifications.splice( index, 1 );
|
Notifications.splice( index, 1 );
|
||||||
|
if ( Notifications.length === 0 ) {
|
||||||
|
notificationMenuOpen = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
@ -133,9 +135,9 @@ const hookToNotificationDaemon = (): void => {
|
|||||||
*/
|
*/
|
||||||
const showNotification = ( id: number, removeAgain: boolean = true ) => {
|
const showNotification = ( id: number, removeAgain: boolean = true ) => {
|
||||||
// Add notification to UI for display
|
// Add notification to UI for display
|
||||||
ShownNotifications.reverse().push( id );
|
const not = [...ShownNotifications.get()].reverse();
|
||||||
ShownNotifications.reverse();
|
not.push( id );
|
||||||
ShownNotificationsCount.set( ShownNotifications.length );
|
ShownNotifications.set( not.reverse() );
|
||||||
|
|
||||||
// Set delay to remove the notification again
|
// Set delay to remove the notification again
|
||||||
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
|
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
|
||||||
@ -154,7 +156,9 @@ const showNotification = ( id: number, removeAgain: boolean = true ) => {
|
|||||||
*/
|
*/
|
||||||
const hideNotification = ( id: number ) => {
|
const hideNotification = ( id: number ) => {
|
||||||
if ( !notificationMenuOpen ) {
|
if ( !notificationMenuOpen ) {
|
||||||
ShownNotifications.splice( ShownNotifications.indexOf( id ), 1 );
|
const not = [...ShownNotifications.get()];
|
||||||
|
not.splice( not.indexOf( id ), 1 );
|
||||||
|
ShownNotifications.set( not );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,10 +171,11 @@ const hideNotification = ( id: number ) => {
|
|||||||
const openNotificationMenu = () => {
|
const openNotificationMenu = () => {
|
||||||
// Simply show all notifications
|
// Simply show all notifications
|
||||||
notificationMenuOpen = true;
|
notificationMenuOpen = true;
|
||||||
const ShownNotifications = [];
|
const not = [];
|
||||||
for (let index = 0; index < Notifications.length; index++) {
|
for (let index = 0; index < Notifications.length; index++) {
|
||||||
ShownNotifications.push( index );
|
not.push( index );
|
||||||
}
|
}
|
||||||
|
ShownNotifications.set( not.reverse() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
@ -182,9 +187,7 @@ const openNotificationMenu = () => {
|
|||||||
const closeNotificationMenu = () => {
|
const closeNotificationMenu = () => {
|
||||||
// Hide all notifications
|
// Hide all notifications
|
||||||
notificationMenuOpen = true;
|
notificationMenuOpen = true;
|
||||||
|
ShownNotifications.set( [] );
|
||||||
ShownNotifications = [];
|
|
||||||
ShownNotificationsCount.set( 0 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
@ -193,11 +196,13 @@ const closeNotificationMenu = () => {
|
|||||||
/**
|
/**
|
||||||
* Toggle the notification menu (i.e. show all notifications)
|
* Toggle the notification menu (i.e. show all notifications)
|
||||||
*/
|
*/
|
||||||
const toggleNotificationMenu = () => {
|
const toggleNotificationMenu = (): string => {
|
||||||
if ( notificationMenuOpen ) {
|
if ( notificationMenuOpen ) {
|
||||||
closeNotificationMenu();
|
closeNotificationMenu();
|
||||||
|
return 'Toggle notification menu closed';
|
||||||
} else {
|
} else {
|
||||||
openNotificationMenu();
|
openNotificationMenu();
|
||||||
|
return 'Toggled notification menu open';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,8 +214,9 @@ const toggleNotificationMenu = () => {
|
|||||||
*/
|
*/
|
||||||
const clearAllNotifications = () => {
|
const clearAllNotifications = () => {
|
||||||
Notifications = [];
|
Notifications = [];
|
||||||
ShownNotifications = [];
|
ShownNotifications.set( [] );
|
||||||
ShownNotificationsCount.set( 0 );
|
// TODO: Hiding for each individual deleteNotification
|
||||||
|
notificationMenuOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
@ -220,7 +226,9 @@ const clearAllNotifications = () => {
|
|||||||
* Delete the newest notifications
|
* Delete the newest notifications
|
||||||
*/
|
*/
|
||||||
const clearNewestNotifications = () => {
|
const clearNewestNotifications = () => {
|
||||||
ShownNotifications.splice( 0, 1 );
|
const not = [...ShownNotifications.get()];
|
||||||
|
not.splice( 0, 1 );
|
||||||
|
ShownNotifications.set( not );
|
||||||
|
|
||||||
Notifications.splice( Notifications.length - 1, 1 );
|
Notifications.splice( Notifications.length - 1, 1 );
|
||||||
}
|
}
|
||||||
@ -236,16 +244,16 @@ const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
|
|||||||
const { TOP, RIGHT } = Astal.WindowAnchor
|
const { TOP, RIGHT } = Astal.WindowAnchor
|
||||||
|
|
||||||
hookToNotificationDaemon();
|
hookToNotificationDaemon();
|
||||||
const range = (n: number) => [...Array(n).keys()];
|
|
||||||
|
|
||||||
return <window
|
return <window
|
||||||
cssClasses={["NotificationHandler"]}
|
cssClasses={["NotificationHandler"]}
|
||||||
gdkmonitor={gdkmonitor}
|
gdkmonitor={gdkmonitor}
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
anchor={TOP | RIGHT}>
|
anchor={TOP | RIGHT}
|
||||||
<box vertical noImplicitDestroy>
|
visible={ShownNotifications( list => list.length > 0 )}
|
||||||
{ShownNotificationsCount( n => range( n ).map( i => {
|
application={App}>
|
||||||
print( 'Rendering' );
|
<box vertical>
|
||||||
|
{ShownNotifications( list => list.map( i => {
|
||||||
// i is index in ShownNotifications array
|
// i is index in ShownNotifications array
|
||||||
return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification>
|
return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification>
|
||||||
} ) ) }
|
} ) ) }
|
||||||
@ -267,8 +275,19 @@ const cliHandler = ( args: string[] ): string => {
|
|||||||
clearNewestNotifications();
|
clearNewestNotifications();
|
||||||
return 'Cleared newest notification';
|
return 'Cleared newest notification';
|
||||||
} else if ( args[ 1 ] == 'toggle' ) {
|
} else if ( args[ 1 ] == 'toggle' ) {
|
||||||
toggleNotificationMenu();
|
return toggleNotificationMenu();
|
||||||
return 'Toggled notifications';
|
} else if ( args[ 1 ] == 'list' ){
|
||||||
|
if ( Notifications.length > 0 ) {
|
||||||
|
let list = 'Currently unviewed notifications: ';
|
||||||
|
for (let index = 0; index < Notifications.length; index++) {
|
||||||
|
const element = Notifications[index];
|
||||||
|
|
||||||
|
list += `\n - (${element.notifdID}) ${element.notification.get_app_name()}: ${element.notification.get_summary()}`;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return 'No currently unviewed notifications'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return 'Unknown command!';
|
return 'Unknown command!';
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,25 @@ window.NotificationHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
box.Notification {
|
box.Notification {
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
background-color: $bg-color;
|
background-color: $bg-color;
|
||||||
margin: .5rem 1rem .5rem 1rem;
|
margin: .5rem 1rem .5rem 1rem;
|
||||||
box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
|
box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
|
||||||
border: 1pt solid gtkalpha($fg-color, .03);
|
border: 1pt solid gtkalpha($fg-color, .03);
|
||||||
|
|
||||||
&:first-child>box {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child>box {
|
&.critical {
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.critical>box {
|
|
||||||
border: 1pt solid gtkalpha($error, .4);
|
border: 1pt solid gtkalpha($error, .4);
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -24,9 +24,9 @@ const urgency = (n: Notifd.Notification) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
delete( id: number ): void
|
delete: ( id: number ) => void;
|
||||||
notification: Notifd.Notification
|
notification: Notifd.Notification
|
||||||
id: number
|
id: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
export default function Notification(props: Props) {
|
||||||
@ -34,54 +34,54 @@ export default function Notification(props: Props) {
|
|||||||
const { START, CENTER, END } = Gtk.Align
|
const { START, CENTER, END } = Gtk.Align
|
||||||
|
|
||||||
return <box vertical
|
return <box vertical
|
||||||
cssClasses={['Notification', urgency(n)]}>
|
cssClasses={['Notification', `${urgency(n)}`]}>
|
||||||
<box cssName="header">
|
<box cssClasses={["header"]}>
|
||||||
{(n.appIcon || n.desktopEntry) ? <Gtk.Image
|
{(n.appIcon || n.desktopEntry) ? <Gtk.Image
|
||||||
cssName="app-icon"
|
cssClasses={["app-icon"]}
|
||||||
visible={Boolean(n.appIcon || n.desktopEntry)}
|
visible={Boolean(n.appIcon || n.desktopEntry)}
|
||||||
iconName={n.appIcon || n.desktopEntry}
|
iconName={n.appIcon || n.desktopEntry}
|
||||||
/> : <Gtk.Image iconName={'window-close-symbolic'}></Gtk.Image>}
|
/> : <image iconName={'window-close-symbolic'}></image>}
|
||||||
<label
|
<label
|
||||||
cssName="app-name"
|
cssClasses={["app-name"]}
|
||||||
halign={START}
|
halign={START}
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
// ellipsize={Pango.EllipsizeMode.END}
|
||||||
label={n.appName || "Unknown"}
|
label={n.appName || "Unknown"}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
cssName="time"
|
cssClasses={["time"]}
|
||||||
hexpand
|
hexpand
|
||||||
halign={END}
|
halign={END}
|
||||||
label={time(n.time)}
|
label={time(n.time)}
|
||||||
/>
|
/>
|
||||||
<button onClicked={() => del( id )}>
|
<button onClicked={() => { del( id ) }}>
|
||||||
<Gtk.Image iconName="window-close-symbolic" />
|
<image iconName="window-close-symbolic" />
|
||||||
</button>
|
</button>
|
||||||
</box>
|
</box>
|
||||||
<Gtk.Separator visible />
|
<Gtk.Separator visible />
|
||||||
<box cssName="content">
|
<box cssClasses={["content"]}>
|
||||||
{n.image && fileExists(n.image) ? <box
|
{n.image && fileExists(n.image) ? <box
|
||||||
valign={START}
|
valign={START}
|
||||||
cssName="image">
|
cssClasses={["image"]}>
|
||||||
<Gtk.Image file={n.image}></Gtk.Image>
|
<image file={n.image}></image>
|
||||||
</box>
|
</box>
|
||||||
: <box></box>}
|
: <box></box>}
|
||||||
{n.image ? <box
|
{n.image ? <box
|
||||||
expand={false}
|
expand={false}
|
||||||
valign={START}
|
valign={START}
|
||||||
className="icon-image">
|
cssClasses={["icon-image"]}>
|
||||||
<Gtk.Image iconName={n.image} expand halign={CENTER} valign={CENTER} />
|
<image iconName={n.image} expand halign={CENTER} valign={CENTER} />
|
||||||
</box>
|
</box>
|
||||||
: <box></box>}
|
: <box></box>}
|
||||||
<box vertical>
|
<box vertical>
|
||||||
<Gtk.Label
|
<label
|
||||||
cssName="summary"
|
cssClasses={["summary"]}
|
||||||
halign={START}
|
halign={START}
|
||||||
xalign={0}
|
xalign={0}
|
||||||
label={n.summary}
|
label={n.summary}
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
// ellipsize={Pango.EllipsizeMode.END}
|
||||||
/>
|
/>
|
||||||
{n.body ? <label
|
{n.body ? <label
|
||||||
cssName="body"
|
cssClasses={["body"]}
|
||||||
wrap
|
wrap
|
||||||
useMarkup
|
useMarkup
|
||||||
halign={START}
|
halign={START}
|
||||||
@ -90,7 +90,7 @@ export default function Notification(props: Props) {
|
|||||||
/> : <label></label>}
|
/> : <label></label>}
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
{n.get_actions().length > 0 ? <box cssName="actions">
|
{n.get_actions().length > 0 ? <box cssClasses={["actions"]}>
|
||||||
{n.get_actions().map(({ label, id }) => (
|
{n.get_actions().map(({ label, id }) => (
|
||||||
<button
|
<button
|
||||||
hexpand
|
hexpand
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
|
||||||
/* $fg-color: #{"@theme_fg_color"}; */
|
@use './components/notifications/notifications.scss';
|
||||||
/* $bg-color: #{"@theme_bg_color"}; */
|
$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; */
|
|
||||||
/* } */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
@use './components/notifications/notifications.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
16
prompts.md
16
prompts.md
@ -25,3 +25,19 @@ Now, can you also provide scss for the bar, such that:
|
|||||||
- it is very rounded (the corners)
|
- it is very rounded (the corners)
|
||||||
- it has a very sleek, but modern design, with only a single accent colour
|
- it has a very sleek, but modern design, with only a single accent colour
|
||||||
- Hovering over something clickable uses a hover colour
|
- Hovering over something clickable uses a hover colour
|
||||||
|
|
||||||
|
|
||||||
|
## Attempt 2
|
||||||
|
Using AstalNetwork, can you write a UI (that can be integrated into a popover) and a corresponding backend that allows me to:
|
||||||
|
- Turn on/off networking and WiFi
|
||||||
|
- Select a WiFi Network
|
||||||
|
- Some way of adding a new connection (can be a special UI or simply opens up another tool like nm-applet's network config)
|
||||||
|
- WiFi-Auto-Connect
|
||||||
|
- Exposes a function (or multiple) through which I can get (as a bindable porperty):
|
||||||
|
- Current up/down speed
|
||||||
|
- Connection type
|
||||||
|
- WiFi SSID (if applicable)
|
||||||
|
- WiFi Signal Strength (if applicable)
|
||||||
|
- Local IP
|
||||||
|
|
||||||
|
I would prefer if you could split up the logic from the UI, i.e. have the logic in a separate TS file and have the UI as its own TSX file and simply import the logic
|
||||||
|
Loading…
x
Reference in New Issue
Block a user