🔧 chore(control-center/tiles): better implementation for the network tile

This commit is contained in:
retrozinndev
2025-10-17 20:02:01 -03:00
parent 6793a68bb8
commit 4861337067
5 changed files with 202 additions and 97 deletions
+33 -3
View File
@@ -1,5 +1,6 @@
import { Scope } from "ags"; import { Scope } from "ags";
import { createScopedConnection, decoder, encoder } from "../modules/utils"; import { createScopedConnection, decoder, encoder } from "../modules/utils";
import { showWorkspaceNumber } from "../window/bar/widgets/Workspaces";
import windows from "./modules/windows"; import windows from "./modules/windows";
import volume from "./modules/volume"; import volume from "./modules/volume";
@@ -9,6 +10,7 @@ import Gio from "gi://Gio?version=2.0";
import GLib from "gi://GLib?version=2.0"; import GLib from "gi://GLib?version=2.0";
/** cli implementation for colorshell */
export namespace Cli { export namespace Cli {
let rootScope: Scope; let rootScope: Scope;
let initialized: boolean = false; let initialized: boolean = false;
@@ -27,6 +29,14 @@ export namespace Cli {
type: "out" type: "out"
}; };
} }
},
{
name: "peek-workspace-num",
help: "peek the workspace numbers in the workspace indicator. (optional: time in millis)",
onCalled: () => {
showWorkspaceNumber(true);
return "Peeking workspace IDs...";
}
} }
], ],
arguments: [ arguments: [
@@ -214,13 +224,33 @@ export namespace Cli {
); );
} }
for(let i = 0; i < args.length; i++) { function handleCommandArguments(cmd: Module|Command, args: Array<string>, index: number, printFun: (out: Output) => void): void {
const arg = args[i]; const argNameRegEx = /^--/, argAliasRegEx = /^-/;
let argName: string;
if(i === 0) { if(args[index].startsWith("--")) {
} }
} }
const firstFoundMod = modules.filter(mod => mod.prefix === args[0])[0];
mod = firstFoundMod ?? modules[0];
if(!mod) {
print({
content: `No command module found with the name ${args[0]}!`,
type: "err"
});
return;
}
for(let i = 1; i < args.length; i++) {
const arg = args[i];
if(/^-/.test(arg)) {
handleCommandArguments(command ?? mod, args, i, print);
continue;
}
} }
function outputToString(out: Output): string { function outputToString(out: Output): string {
+10 -10
View File
@@ -1,8 +1,11 @@
import { CompositorHyprland } from "./hyprland"; import { CompositorHyprland } from "./hyprland";
import GObject, { getter, gtype, register } from "ags/gobject"; import GObject, { getter, gtype, property, register } from "ags/gobject";
import GLib from "gi://GLib?version=2.0"; import GLib from "gi://GLib?version=2.0";
export default Compositors;
/** WIP modular implementation of a system that supports implementing /** WIP modular implementation of a system that supports implementing
* a variety of Wayland Compositors * a variety of Wayland Compositors
* @todo implement more general compositor info + a lot of stuff * @todo implement more general compositor info + a lot of stuff
@@ -14,7 +17,6 @@ export namespace Compositors {
export class Monitor extends GObject.Object { export class Monitor extends GObject.Object {
#width: number; #width: number;
#height: number; #height: number;
#scaling: number;
@getter(Number) @getter(Number)
get width() { return this.#width; } get width() { return this.#width; }
@@ -22,15 +24,15 @@ export namespace Compositors {
@getter(Number) @getter(Number)
get height() { return this.#height; } get height() { return this.#height; }
@getter(Number) @property(Number)
get scaling() { return this.#scaling; } scaling: number;
constructor(width: number, height: number, scaling: number = 1) { constructor(width: number, height: number, scaling: number = 1) {
super(); super();
this.#width = width; this.#width = width;
this.#height = height; this.#height = height;
this.#scaling = scaling; this.scaling = scaling;
} }
} }
@@ -49,7 +51,6 @@ export namespace Compositors {
super(); super();
this.#monitor = monitor; this.#monitor = monitor;
this.notify("monitor");
this.#id = id; this.#id = id;
} }
} }
@@ -110,10 +111,9 @@ export namespace Compositors {
if(props.position !== undefined) if(props.position !== undefined)
this.#position = props.position; this.#position = props.position;
if(props.initialClass !== undefined) this.#initialClass = props.initialClass !== undefined ?
this.#initialClass = props.initialClass; props.initialClass
else : props.class;
this.#initialClass = props.class;
} }
} }
@@ -1,85 +1,153 @@
import { execAsync } from "ags/process";
import { Tile } from "./Tile"; import { Tile } from "./Tile";
import { execAsync } from "ags/process";
import { PageNetwork } from "../pages/Network"; import { PageNetwork } from "../pages/Network";
import { tr } from "../../../../i18n/intl"; import { tr } from "../../../../i18n/intl";
import { TilesPages } from "../tiles"; import { TilesPages } from "../tiles";
import { Gtk } from "ags/gtk4"; import { Accessor, createBinding, createComputed } from "ags";
import { createBinding, createComputed, With } from "ags"; import { secureBaseBinding } from "../../../../modules/utils";
import AstalNetwork from "gi://AstalNetwork"; import AstalNetwork from "gi://AstalNetwork";
import { Notifications } from "../../../../modules/notifications";
export const TileNetwork = () => <Gtk.Box> const { WIFI, WIRED } = AstalNetwork.Primary,
<With value={createComputed([ { CONNECTED, CONNECTING } = AstalNetwork.Internet;
createBinding(AstalNetwork.get_default(), "primary"),
createBinding(AstalNetwork.get_default(), "wired"),
createBinding(AstalNetwork.get_default(), "wifi")
])}>
{([primary, wired, wifi]: [AstalNetwork.Primary, AstalNetwork.Wired, AstalNetwork.Wifi]) => { const wiredInternet = secureBaseBinding<AstalNetwork.Wired>(
if(primary === AstalNetwork.Primary.WIFI) { createBinding(AstalNetwork.get_default(), "wired"),
return <Tile title={tr("control_center.tiles.network.wireless")} "internet",
description={createComputed([ AstalNetwork.Internet.DISCONNECTED
createBinding(wifi, "ssid"), createBinding(wifi, "internet") ) as Accessor<AstalNetwork.Internet>;
], (ssid, internet) => ssid ? ssid : (() => {
switch(internet) {
case AstalNetwork.Internet.CONNECTED:
return tr("connected");
case AstalNetwork.Internet.DISCONNECTED:
return tr("disconnected");
case AstalNetwork.Internet.CONNECTING:
return tr("connecting") + "...";
}
})()
)} onEnabled={() => wifi.set_enabled(true)}
onDisabled={() => wifi.set_enabled(false)}
hasArrow onClicked={() => TilesPages?.toggle(PageNetwork)}
icon={"network-wireless-signal-excellent-symbolic"}
state={createBinding(wifi, "enabled")}
/>
} else if(primary === AstalNetwork.Primary.WIRED) { const wifiInternet = secureBaseBinding<AstalNetwork.Wifi>(
return <Tile title={tr("control_center.tiles.network.wired")} createBinding(AstalNetwork.get_default(), "wifi"),
description={createBinding(wired, "internet").as((internet: AstalNetwork.Internet) => { "internet",
switch(internet) { AstalNetwork.Internet.DISCONNECTED
case AstalNetwork.Internet.CONNECTED: ) as Accessor<AstalNetwork.Internet>;
return tr("connected");
case AstalNetwork.Internet.DISCONNECTED:
return tr("disconnected");
case AstalNetwork.Internet.CONNECTING:
return tr("connecting") + "...";
}
})}
hasArrow onEnabled={() => execAsync("nmcli n on")}
onDisabled={() => execAsync("nmcli n off")}
onClicked={() => TilesPages?.toggle(PageNetwork)}
icon={createBinding(wired, "internet").as((internet: AstalNetwork.Internet) => {
switch(internet) {
case AstalNetwork.Internet.CONNECTED:
return "network-wired-symbolic";
case AstalNetwork.Internet.DISCONNECTED:
return "network-wired-disconnected-symbolic";
}
return "network-wired-no-route-symbolic"; const wifiSSID = secureBaseBinding<AstalNetwork.Wifi>(
})} createBinding(AstalNetwork.get_default(), "wifi"),
state={createBinding(wired, "internet").as((internet: AstalNetwork.Internet) => "ssid",
internet === AstalNetwork.Internet.CONNECTING "Unknown"
|| internet === AstalNetwork.Internet.CONNECTED ) as Accessor<string>;
)}
/> const wifiIcon = secureBaseBinding<AstalNetwork.Wifi>(
} createBinding(AstalNetwork.get_default(), "wifi"),
"iconName",
return <Tile "network-wireless-symbolic"
title={tr("control_center.tiles.network.network")} );
description={tr("disconnected")}
onEnabled={() => execAsync("nmcli n on")} const wiredIcon = secureBaseBinding<AstalNetwork.Wired>(
onDisabled={() => execAsync("nmcli n off")} createBinding(AstalNetwork.get_default(), "wired"),
hasArrow onClicked={() => TilesPages?.toggle(PageNetwork)} "iconName",
icon={"network-wired-disconnected-symbolic"} "network-wired-symbolic"
state={createBinding(wired, "internet").as((internet: AstalNetwork.Internet) => );
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)}
/> const primary = createBinding(AstalNetwork.get_default(), "primary");
}}
</With> export const TileNetwork = () =>
</Gtk.Box>; <Tile hasArrow title={createComputed([
primary,
wifiInternet,
wifiSSID
], (primary, wiInternet, wiSSID) => {
switch(primary) {
case WIFI:
if(wiInternet === CONNECTED)
return wiSSID;
return tr("control_center.tiles.network.wireless");
case WIRED:
return tr("control_center.tiles.network.wired");
}
return tr("control_center.tiles.network.network");
})}
onClicked={() => TilesPages?.toggle(PageNetwork)}
icon={createComputed([
primary,
wifiIcon,
wiredIcon
], (primary, wifiIcon, wiredIcon) => {
switch(primary) {
case WIFI:
return wifiIcon;
case WIRED:
return wiredIcon;
}
return "network-wired-no-route-symbolic";
})}
state={createComputed([
primary,
secureBaseBinding<AstalNetwork.Wifi>(
createBinding(AstalNetwork.get_default(), "wifi"),
"enabled",
false
),
wiredInternet.as(internet => internet === CONNECTED || internet === CONNECTING)
], (primary, wifiEnabled, wiredEnabled) => {
switch(primary) {
case WIFI:
return wifiEnabled;
case WIRED:
return wiredEnabled;
}
return false;
})}
description={createComputed([
primary,
wifiInternet,
wiredInternet
], (primary, wifiInternet, wiredInternet) => {
switch(primary) {
case WIFI:
return internetToTranslatedString(wifiInternet);
case WIRED:
return internetToTranslatedString(wiredInternet);
}
return tr("disconnected");
})}
onToggled={(self, state) => {
switch(AstalNetwork.get_default().primary) {
case WIFI:
AstalNetwork.get_default().wifi.set_enabled(state);
return;
case WIRED:
(state ?
execAsync("nmcli n off")
: execAsync("nmcli n on")
).catch(e => {
Notifications.getDefault().sendNotification({
appName: "network",
summary: "Couldn't turn off network",
body: `Turning off networking with nmcli failed: ${
e?.message ?? "(no error message)"}`
});
});
return;
}
// disable if no device available
self.state = false;
}}
/>;
function internetToTranslatedString(internet: AstalNetwork.Internet): string {
switch(internet) {
case AstalNetwork.Internet.CONNECTED:
return tr("connected");
case AstalNetwork.Internet.CONNECTING:
return tr("connecting") + "...";
}
return tr("disconnected");
}
@@ -30,7 +30,7 @@ export class Tile extends Gtk.Box {
public hasArrow: boolean = false; public hasArrow: boolean = false;
declare $signals: Gtk.Box.SignalSignatures & { declare $signals: Gtk.Box.SignalSignatures & {
"toggled": (_state: boolean) => void; "toggled": (state: boolean) => void;
"enabled": () => void; "enabled": () => void;
"disabled": () => void; "disabled": () => void;
"clicked": () => void; "clicked": () => void;
+16 -9
View File
@@ -16,34 +16,41 @@ export const FloatingNotifications = (mon: number) =>
generalConfig.bindProperty("notifications.position_h", "string"), generalConfig.bindProperty("notifications.position_h", "string"),
generalConfig.bindProperty("notifications.position_v", "string") generalConfig.bindProperty("notifications.position_v", "string")
]).as(([posH, posV]) => { ]).as(([posH, posV]) => {
let horizontal: Astal.WindowAnchor = Astal.WindowAnchor.RIGHT, const pos: Array<Astal.WindowAnchor> = [];
vertical: Astal.WindowAnchor = Astal.WindowAnchor.TOP;
switch(posH) { switch(posH) {
case "left": case "left":
horizontal = Astal.WindowAnchor.LEFT; pos.push(Astal.WindowAnchor.LEFT);
break; break;
case "center": case "center":
horizontal = Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT; pos.push(Astal.WindowAnchor.LEFT);
pos.push(Astal.WindowAnchor.RIGHT);
break; break;
case "right": case "right":
horizontal = Astal.WindowAnchor.RIGHT; pos.push(Astal.WindowAnchor.RIGHT);
break; break;
} }
switch(posV) { switch(posV) {
case "top": case "top":
vertical = Astal.WindowAnchor.TOP; pos.push(Astal.WindowAnchor.TOP);
break; break;
case "center": case "center":
vertical = Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM; pos.push(Astal.WindowAnchor.TOP);
pos.push(Astal.WindowAnchor.BOTTOM);
break; break;
case "bottom": case "bottom":
vertical = Astal.WindowAnchor.BOTTOM; pos.push(Astal.WindowAnchor.BOTTOM);
break; break;
} }
return horizontal | vertical; let finalPos: Astal.WindowAnchor;
pos.forEach(pos => finalPos = (finalPos !== undefined ?
finalPos | pos
: pos));
return finalPos!;
})} exclusivity={Astal.Exclusivity.NORMAL} })} exclusivity={Astal.Exclusivity.NORMAL}
resizable={false} widthRequest={450}> resizable={false} widthRequest={450}>