mirror of
https://github.com/zephrynis/nix-flake.git
synced 2026-02-19 04:21:55 +00:00
feat: make AGS colorshell configuration fully declarative
- Add complete colorshell v2.0.3 configuration to home/ags-config/ - Disable runner plugin and NightLight tile (incompatible with NixOS) - Customize SCSS with full opacity (no transparency) - Add dark pale blue color scheme in home/pywal-colors/ - Configure Papirus-Dark icon theme via home-manager - Make ~/.config/ags/ immutable and managed by Nix store - Auto-deploy pywal colors to ~/.cache/wal/colors.json All AGS configuration is now reproducible and version controlled.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { Tile } from "./Tile";
|
||||
import { BluetoothPage } from "../pages/Bluetooth";
|
||||
import { TilesPages } from "../tiles";
|
||||
import { createBinding, createComputed } from "ags";
|
||||
import { Bluetooth } from "../../../../modules/bluetooth";
|
||||
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
|
||||
|
||||
export const TileBluetooth = () =>
|
||||
<Tile title={"Bluetooth"} visible={createBinding(Bluetooth.getDefault(), "isAvailable")}
|
||||
description={createComputed([
|
||||
createBinding(Bluetooth.getDefault(), "adapter"),
|
||||
createBinding(AstalBluetooth.get_default(), "devices")
|
||||
], (adapter, devices) => {
|
||||
const lastConnectedDevice = devices.filter(d => d.connected)[devices.length - 1];
|
||||
|
||||
if(!adapter || !lastConnectedDevice)
|
||||
return "";
|
||||
|
||||
return lastConnectedDevice.alias;
|
||||
})}
|
||||
onEnabled={() => Bluetooth.getDefault().adapter?.set_powered(true)}
|
||||
onDisabled={() => Bluetooth.getDefault().adapter?.set_powered(false)}
|
||||
onClicked={() => TilesPages?.toggle(BluetoothPage)}
|
||||
hasArrow
|
||||
state={createBinding(AstalBluetooth.get_default(), "isPowered")}
|
||||
icon={createComputed([
|
||||
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
||||
createBinding(AstalBluetooth.get_default(), "isConnected")
|
||||
],
|
||||
(powered: boolean, isConnected: boolean) =>
|
||||
powered ? ( isConnected ?
|
||||
"bluetooth-active-symbolic"
|
||||
: "bluetooth-symbolic"
|
||||
) : "bluetooth-disabled-symbolic")
|
||||
}
|
||||
/>;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Notifications } from "../../../../modules/notifications";
|
||||
import { Tile } from "./Tile";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { createBinding } from "ags";
|
||||
|
||||
export const TileDND = () =>
|
||||
<Tile title={tr("control_center.tiles.dnd.title")}
|
||||
description={createBinding(Notifications.getDefault().getNotifd(), "dontDisturb").as(
|
||||
(dnd: boolean) => dnd ? tr("control_center.tiles.enabled") : tr("control_center.tiles.disabled"))}
|
||||
onDisabled={() => Notifications.getDefault().getNotifd().dontDisturb = false}
|
||||
onEnabled={() => Notifications.getDefault().getNotifd().dontDisturb = true}
|
||||
icon={"minus-circle-filled-symbolic"}
|
||||
state={Notifications.getDefault().getNotifd().dontDisturb}
|
||||
/>;
|
||||
168
home/ags-config/window/control-center/widgets/tiles/Network.tsx
Normal file
168
home/ags-config/window/control-center/widgets/tiles/Network.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import { Tile } from "./Tile";
|
||||
import { execAsync } from "ags/process";
|
||||
import { PageNetwork } from "../pages/Network";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { TilesPages } from "../tiles";
|
||||
import { Accessor, createBinding, createComputed } from "ags";
|
||||
import { secureBaseBinding } from "../../../../modules/utils";
|
||||
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
import { Notifications } from "../../../../modules/notifications";
|
||||
|
||||
|
||||
const { WIFI, WIRED } = AstalNetwork.Primary,
|
||||
{ CONNECTED, CONNECTING, DISCONNECTED } = AstalNetwork.Internet;
|
||||
|
||||
const wiredInternet = secureBaseBinding<AstalNetwork.Wired>(
|
||||
createBinding(AstalNetwork.get_default(), "wired"),
|
||||
"internet",
|
||||
AstalNetwork.Internet.DISCONNECTED
|
||||
) as Accessor<AstalNetwork.Internet>;
|
||||
|
||||
const wifiInternet = secureBaseBinding<AstalNetwork.Wifi>(
|
||||
createBinding(AstalNetwork.get_default(), "wifi"),
|
||||
"internet",
|
||||
AstalNetwork.Internet.DISCONNECTED
|
||||
) as Accessor<AstalNetwork.Internet>;
|
||||
|
||||
const wifiSSID = secureBaseBinding<AstalNetwork.Wifi>(
|
||||
createBinding(AstalNetwork.get_default(), "wifi"),
|
||||
"ssid",
|
||||
"Unknown"
|
||||
) as Accessor<string>;
|
||||
|
||||
const wifiIcon = secureBaseBinding<AstalNetwork.Wifi>(
|
||||
createBinding(AstalNetwork.get_default(), "wifi"),
|
||||
"iconName",
|
||||
"network-wireless-symbolic"
|
||||
);
|
||||
|
||||
const wiredIcon = secureBaseBinding<AstalNetwork.Wired>(
|
||||
createBinding(AstalNetwork.get_default(), "wired"),
|
||||
"iconName",
|
||||
"network-wired-symbolic"
|
||||
);
|
||||
|
||||
const primary = createBinding(AstalNetwork.get_default(), "primary");
|
||||
|
||||
export const TileNetwork = () =>
|
||||
<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) => {
|
||||
const wifi = AstalNetwork.get_default().wifi,
|
||||
wired = AstalNetwork.get_default().wired;
|
||||
|
||||
switch(AstalNetwork.get_default().primary) {
|
||||
case WIFI:
|
||||
wifi.set_enabled(state);
|
||||
return;
|
||||
|
||||
case WIRED:
|
||||
setNetworking(state);
|
||||
return;
|
||||
}
|
||||
|
||||
if(wired && wired.internet === DISCONNECTED) {
|
||||
setNetworking(true);
|
||||
return;
|
||||
} else if(wifi && !wifi.enabled) {
|
||||
wifi.set_enabled(true);
|
||||
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");
|
||||
}
|
||||
|
||||
function setNetworking(state: boolean): void {
|
||||
(!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 !== undefined ? `: ${e?.message}` : ""}`
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Tile } from "./Tile";
|
||||
import { NightLight } from "../../../../modules/nightlight";
|
||||
import { PageNightLight } from "../pages/NightLight";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { TilesPages } from "../tiles";
|
||||
import { isInstalled } from "../../../../modules/utils";
|
||||
import { createBinding, createComputed } from "ags";
|
||||
|
||||
|
||||
export const TileNightLight = () =>
|
||||
<Tile title={tr("control_center.tiles.night_light.title")}
|
||||
icon={"weather-clear-night-symbolic"}
|
||||
description={createComputed([
|
||||
createBinding(NightLight.getDefault(), "identity"),
|
||||
createBinding(NightLight.getDefault(), "temperature"),
|
||||
createBinding(NightLight.getDefault(), "gamma")
|
||||
], (identity, temp, gamma) => !identity ?
|
||||
`${temp === NightLight.getDefault().identityTemperature ?
|
||||
tr("control_center.tiles.night_light.default_desc") : `${temp}K`
|
||||
} ${gamma < NightLight.getDefault().maxGamma ? `(${gamma}%)` : ""}`
|
||||
: tr("control_center.tiles.disabled")
|
||||
)}
|
||||
hasArrow visible={isInstalled("hyprsunset")}
|
||||
onDisabled={() => NightLight.getDefault().identity = true}
|
||||
onEnabled={() => NightLight.getDefault().identity = false}
|
||||
onClicked={() => TilesPages?.toggle(PageNightLight)}
|
||||
state={createBinding(NightLight.getDefault(), "identity").as(identity => !identity)}
|
||||
/>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Tile } from "./Tile";
|
||||
import { Recording } from "../../../../modules/recording";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { isInstalled } from "../../../../modules/utils";
|
||||
import { createBinding, createComputed } from "ags";
|
||||
|
||||
|
||||
export const TileRecording = () =>
|
||||
<Tile title={tr("control_center.tiles.recording.title")}
|
||||
description={createComputed([
|
||||
createBinding(Recording.getDefault(), "recording"),
|
||||
createBinding(Recording.getDefault(), "recordingTime")
|
||||
], (recording, time) => {
|
||||
if(!recording || !Recording.getDefault().startedAt)
|
||||
return tr("control_center.tiles.recording.disabled_desc") || "Start recording";
|
||||
|
||||
return time;
|
||||
})}
|
||||
icon={"media-record-symbolic"}
|
||||
visible={isInstalled("wf-recorder")}
|
||||
onDisabled={() => Recording.getDefault().stopRecording()}
|
||||
onEnabled={() => Recording.getDefault().startRecording()}
|
||||
state={createBinding(Recording.getDefault(), "recording")}
|
||||
/>;
|
||||
150
home/ags-config/window/control-center/widgets/tiles/Tile.tsx
Normal file
150
home/ags-config/window/control-center/widgets/tiles/Tile.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { createBinding } from "ags";
|
||||
import { omitObjectKeys, variableToBoolean } from "../../../../modules/utils";
|
||||
import { property, register, signal } from "ags/gobject";
|
||||
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
|
||||
|
||||
@register({ GTypeName: "Tile" })
|
||||
export class Tile extends Gtk.Box {
|
||||
@signal(Boolean) toggled(_state: boolean) {}
|
||||
@signal() enabled() {}
|
||||
@signal() disabled() {}
|
||||
@signal() clicked() {
|
||||
if(this.enableOnClicked)
|
||||
this.enable();
|
||||
}
|
||||
|
||||
@property(String)
|
||||
public icon: string;
|
||||
@property(String)
|
||||
public title: string;
|
||||
@property(String)
|
||||
public description: string = "";
|
||||
@property(Boolean)
|
||||
public enableOnClicked: boolean = false;
|
||||
@property(Boolean)
|
||||
public state: boolean = false;
|
||||
@property(Boolean)
|
||||
public hasArrow: boolean = false;
|
||||
|
||||
declare $signals: Gtk.Box.SignalSignatures & {
|
||||
"toggled": (state: boolean) => void;
|
||||
"enabled": () => void;
|
||||
"disabled": () => void;
|
||||
"clicked": () => void;
|
||||
};
|
||||
|
||||
public enable(): void {
|
||||
if(this.state) return;
|
||||
|
||||
this.state = true;
|
||||
!this.has_css_class("enabled") &&
|
||||
this.add_css_class("enabled");
|
||||
|
||||
this.emit("toggled", true);
|
||||
this.emit("enabled");
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
if(!this.state) return;
|
||||
|
||||
this.state = false;
|
||||
this.remove_css_class("enabled");
|
||||
this.emit("toggled", false);
|
||||
this.emit("disabled");
|
||||
}
|
||||
|
||||
constructor(props: Partial<Omit<Gtk.Box.ConstructorProps, "orientation">> & {
|
||||
icon: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
state?: boolean;
|
||||
enableOnClicked?: boolean;
|
||||
hasArrow?: boolean;
|
||||
}) {
|
||||
super(omitObjectKeys(props, [
|
||||
"icon",
|
||||
"title",
|
||||
"description",
|
||||
"state",
|
||||
"enableOnClicked"
|
||||
]));
|
||||
|
||||
this.add_css_class("tile");
|
||||
this.add_controller(
|
||||
<Gtk.GestureClick onReleased={(_, __, px, py) => {
|
||||
// gets the icon part of the tile
|
||||
const { x, y, width, height } = this.get_first_child()!.get_allocation();
|
||||
|
||||
if((px < x || px > x+width) || (py < y || y > py+height))
|
||||
this.emit("clicked");
|
||||
}} /> as Gtk.GestureClick
|
||||
);
|
||||
|
||||
this.icon = props.icon;
|
||||
this.title = props.title;
|
||||
this.hexpand = true;
|
||||
|
||||
if(props.hasArrow !== undefined)
|
||||
this.hasArrow = props.hasArrow;
|
||||
|
||||
if(props.description !== undefined)
|
||||
this.description = props.description;
|
||||
|
||||
if(props.state !== undefined)
|
||||
this.state = props.state;
|
||||
|
||||
if(props.enableOnClicked !== undefined)
|
||||
this.enableOnClicked = props.enableOnClicked;
|
||||
|
||||
this.state &&
|
||||
this.add_css_class("enabled"); // fix no highlight when enabled on init
|
||||
|
||||
this.prepend(
|
||||
<Gtk.Box hexpand={false} vexpand class={"icon"}>
|
||||
<Gtk.Image iconName={createBinding(this, "icon")} halign={Gtk.Align.CENTER} />
|
||||
<Gtk.GestureClick onReleased={() => {
|
||||
this.state ? this.disable() : this.enable();
|
||||
}} />
|
||||
</Gtk.Box> as Gtk.Box
|
||||
);
|
||||
|
||||
this.append(
|
||||
<Gtk.Box class={"content"} orientation={Gtk.Orientation.VERTICAL} vexpand
|
||||
valign={Gtk.Align.CENTER} hexpand>
|
||||
|
||||
<Gtk.Label class={"title"} label={createBinding(this, "title")}
|
||||
xalign={0} ellipsize={Pango.EllipsizeMode.END} hexpand={false}
|
||||
maxWidthChars={10} />
|
||||
<Gtk.Label class={"description"} label={createBinding(this, "description")}
|
||||
xalign={0} ellipsize={Pango.EllipsizeMode.END} visible={
|
||||
variableToBoolean(createBinding(this, "description"))
|
||||
} maxWidthChars={12} hexpand={false}
|
||||
/>
|
||||
</Gtk.Box> as Gtk.Box
|
||||
);
|
||||
|
||||
if(this.hasArrow)
|
||||
this.append(
|
||||
<Gtk.Image class={"arrow"} iconName={"go-next-symbolic"}
|
||||
halign={Gtk.Align.END}
|
||||
/> as Gtk.Image
|
||||
);
|
||||
}
|
||||
|
||||
emit<Signal extends keyof typeof this.$signals>(
|
||||
signal: Signal,
|
||||
...args: Parameters<(typeof this.$signals)[Signal]>
|
||||
): void {
|
||||
super.emit(signal, ...args);
|
||||
}
|
||||
|
||||
connect<Signal extends keyof typeof this.$signals>(
|
||||
signal: Signal,
|
||||
callback: (typeof this.$signals)[Signal]
|
||||
): number {
|
||||
return super.connect(signal, callback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { TileNetwork } from "./Network";
|
||||
import { TileBluetooth } from "./Bluetooth";
|
||||
import { TileDND } from "./DoNotDisturb";
|
||||
import { TileRecording } from "./Recording";
|
||||
// import { TileNightLight } from "./NightLight";
|
||||
import { Pages } from "../pages";
|
||||
import { createRoot, getScope } from "ags";
|
||||
|
||||
|
||||
export let TilesPages: Pages|undefined;
|
||||
export const tileList: Array<() => JSX.Element|Gtk.Widget> = [
|
||||
TileNetwork,
|
||||
TileBluetooth,
|
||||
TileRecording,
|
||||
TileDND,
|
||||
// TileNightLight
|
||||
] as Array<() => Gtk.Widget>;
|
||||
|
||||
export function Tiles(): Gtk.Widget {
|
||||
return createRoot((dispose) => {
|
||||
getScope().onCleanup(() => TilesPages = undefined);
|
||||
|
||||
return <Gtk.Box class={"tiles-container"} orientation={Gtk.Orientation.VERTICAL}
|
||||
onDestroy={() => dispose()}>
|
||||
|
||||
<Gtk.FlowBox orientation={Gtk.Orientation.HORIZONTAL} rowSpacing={6}
|
||||
columnSpacing={6} minChildrenPerLine={2} activateOnSingleClick
|
||||
maxChildrenPerLine={2} hexpand homogeneous>
|
||||
|
||||
{tileList.map(t => t())}
|
||||
</Gtk.FlowBox>
|
||||
|
||||
<Pages class={"tile-pages"} $={(self) => TilesPages = self} />
|
||||
</Gtk.Box> as Gtk.Box;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user