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,84 @@
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { Backlights } from "../../../../modules/backlight";
|
||||
import { Page, PageButton } from "../Page";
|
||||
import { createBinding, For, With } from "ags";
|
||||
import { addSliderMarksFromMinMax } from "../../../../modules/utils";
|
||||
import { userData } from "../../../../config";
|
||||
|
||||
|
||||
export const PageBacklight = <Page
|
||||
id={"backlight"}
|
||||
title={tr("control_center.pages.backlight.title")}
|
||||
description={tr("control_center.pages.backlight.description")}
|
||||
actionOpen={() => {
|
||||
const dataDefaultBacklight = userData.getProperty("control_center.default_backlight", "any");
|
||||
if(typeof dataDefaultBacklight === "string" &&
|
||||
Backlights.getDefault().default?.name !== dataDefaultBacklight) {
|
||||
|
||||
const bk = Backlights.getDefault().backlights.filter(b => b.name === dataDefaultBacklight)[0];
|
||||
if(!bk) return;
|
||||
|
||||
Backlights.getDefault().setDefault(bk);
|
||||
}
|
||||
}}
|
||||
content={() => (
|
||||
<With value={createBinding(Backlights.getDefault(), "backlights")}>
|
||||
{(bklights: Array<Backlights.Backlight>) => bklights.length > 0 &&
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
||||
<Gtk.Box class={"list"} visible={createBinding(Backlights.getDefault(), "backlights")
|
||||
.as((bklights) => bklights.length > 1)}>
|
||||
|
||||
<Gtk.Label label={"Default"} />
|
||||
<For each={createBinding(Backlights.getDefault(), "backlights")}>
|
||||
{(bk: Backlights.Backlight) =>
|
||||
<PageButton class={createBinding(bk, "isDefault").as(is => is ? "highlight" : "")}
|
||||
title={bk.name}
|
||||
icon={"video-display-symbolic"}
|
||||
actionClicked={() => {
|
||||
if(Backlights.getDefault().default?.path !== bk.path) {
|
||||
Backlights.getDefault().setDefault(bk);
|
||||
// save data
|
||||
userData.setProperty(
|
||||
"control_center.default_backlight",
|
||||
bk.name,
|
||||
true
|
||||
);
|
||||
}
|
||||
}}
|
||||
endWidget={
|
||||
<Gtk.Image iconName={"object-select-symbolic"}
|
||||
visible={createBinding(bk, "isDefault")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
<Gtk.Box class={"sliders"} orientation={Gtk.Orientation.VERTICAL} spacing={6}>
|
||||
{bklights.map((bklight, i) =>
|
||||
<Gtk.Box class={"bklight"} orientation={Gtk.Orientation.VERTICAL}
|
||||
spacing={4}>
|
||||
|
||||
<Gtk.Label class={"subheader"} label={`Backlight ${i+1} (${bklight.name})`}
|
||||
xalign={0} />
|
||||
<Astal.Slider $={(self) => addSliderMarksFromMinMax(self)}
|
||||
min={0} max={bklight.maxBrightness}
|
||||
value={createBinding(bklight, "brightness")}
|
||||
onChangeValue={(_, __, value) => {
|
||||
bklight.brightness = value
|
||||
}}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
)}
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
}
|
||||
</With>
|
||||
)}
|
||||
headerButtons={[{
|
||||
icon: "arrow-circular-top-right",
|
||||
tooltipText: tr("control_center.pages.backlight.refresh"),
|
||||
actionClicked: () => Backlights.getDefault().scan()
|
||||
}]}
|
||||
/> as Page;
|
||||
@@ -0,0 +1,205 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Page, PageButton } from "../Page";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { Windows } from "../../../../windows";
|
||||
import { Notifications } from "../../../../modules/notifications";
|
||||
import { execApp } from "../../../../modules/apps";
|
||||
import { createBinding, createComputed, For, With } from "ags";
|
||||
import { variableToBoolean } from "../../../../modules/utils";
|
||||
import { Bluetooth } from "../../../../modules/bluetooth";
|
||||
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
import Adw from "gi://Adw?version=1";
|
||||
import Gio from "gi://Gio?version=2.0";
|
||||
|
||||
|
||||
export const BluetoothPage = <Page
|
||||
id={"bluetooth"}
|
||||
title={tr("control_center.pages.bluetooth.title")}
|
||||
spacing={6}
|
||||
description={tr("control_center.pages.bluetooth.description")}
|
||||
headerButtons={createBinding(Bluetooth.getDefault(), "adapter").as(adapter => adapter ? [{
|
||||
icon: createBinding(adapter, "discovering")
|
||||
.as(discovering => !discovering ?
|
||||
"arrow-circular-top-right-symbolic"
|
||||
: "media-playback-stop-symbolic"
|
||||
),
|
||||
tooltipText: createBinding(adapter, "discovering")
|
||||
.as((discovering) => !discovering ?
|
||||
tr("control_center.pages.bluetooth.start_discovering")
|
||||
: tr("control_center.pages.bluetooth.stop_discovering")),
|
||||
actionClicked: () => {
|
||||
if(adapter.discovering) {
|
||||
adapter.stop_discovery();
|
||||
return;
|
||||
}
|
||||
|
||||
adapter.start_discovery();
|
||||
}
|
||||
}]: [])}
|
||||
actionClosed={() => Bluetooth.getDefault().adapter?.discovering &&
|
||||
Bluetooth.getDefault().adapter?.stop_discovery()}
|
||||
bottomButtons={[{
|
||||
title: tr("control_center.pages.more_settings"),
|
||||
actionClicked: () => {
|
||||
Windows.getDefault().close("control-center");
|
||||
execApp("overskride", "[float; animation slide right]");
|
||||
}
|
||||
}]}
|
||||
content={() => {
|
||||
const adapters = createBinding(AstalBluetooth.get_default(), "adapters");
|
||||
const devices = createBinding(AstalBluetooth.get_default(), "devices");
|
||||
const knownDevices = devices.as(devs => devs.filter(dev =>
|
||||
dev.trusted || dev.paired || dev.connected
|
||||
).sort(dev => dev.connected ? 1 : 0));
|
||||
const discoveredDevices = devices.as(devs => devs.filter(dev =>
|
||||
!dev.trusted && !dev.paired && !dev.connected)
|
||||
);
|
||||
|
||||
return [
|
||||
<Gtk.Box class={"adapters"} visible={adapters.as(adptrs => adptrs.length > 1)
|
||||
} spacing={2} orientation={Gtk.Orientation.VERTICAL}>
|
||||
|
||||
<Gtk.Label class={"sub-header"} label={tr("control_center.pages.bluetooth.adapters")}
|
||||
xalign={0} />
|
||||
<With value={adapters.as(adpts => adpts.length > 1)}>
|
||||
{(hasMoreAdapters: boolean) => hasMoreAdapters &&
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={2}>
|
||||
<For each={adapters}>
|
||||
{(adapter: AstalBluetooth.Adapter) => {
|
||||
const isSelected = createBinding(Bluetooth.getDefault(), "adapter").as(a =>
|
||||
adapter.address === a?.address);
|
||||
|
||||
return <PageButton class={isSelected.as(is => is ? "selected" : "")}
|
||||
title={adapter.alias ?? "Adapter"} icon={"bluetooth-active-symbolic"}
|
||||
description={createBinding(adapter, "address")}
|
||||
actionClicked={() => {
|
||||
if(adapter.address !== Bluetooth.getDefault().adapter?.address)
|
||||
Bluetooth.getDefault().adapter = adapter;
|
||||
}}
|
||||
endWidget={
|
||||
<Gtk.Image iconName={"object-select-symbolic"} visible={isSelected} />
|
||||
}
|
||||
/>;
|
||||
}}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
}
|
||||
</With>
|
||||
</Gtk.Box>,
|
||||
<Gtk.Box class={"connections"} orientation={Gtk.Orientation.VERTICAL} hexpand
|
||||
spacing={2}>
|
||||
|
||||
<Gtk.Box class={"paired"} orientation={Gtk.Orientation.VERTICAL} spacing={4}
|
||||
visible={variableToBoolean(knownDevices)}>
|
||||
|
||||
<Gtk.Label class={"sub-header"} label={tr("devices")} xalign={0} />
|
||||
<For each={knownDevices}>
|
||||
{(dev: AstalBluetooth.Device) => <DeviceWidget device={dev} />}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
<Gtk.Box class={"discovered"} orientation={Gtk.Orientation.VERTICAL} spacing={4}
|
||||
visible={variableToBoolean(discoveredDevices)}>
|
||||
|
||||
<Gtk.Label class={"sub-header"} label={tr("control_center.pages.bluetooth.new_devices")}
|
||||
xalign={0} />
|
||||
<For each={discoveredDevices}>
|
||||
{(dev: AstalBluetooth.Device) => <DeviceWidget device={dev} />}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
];
|
||||
}}
|
||||
/> as Page;
|
||||
|
||||
function DeviceWidget({ device }: { device: AstalBluetooth.Device }): Gtk.Widget {
|
||||
const pair = async () => {
|
||||
if(device.paired) return;
|
||||
|
||||
device.pair();
|
||||
device.set_trusted(true);
|
||||
};
|
||||
|
||||
return <PageButton class={createBinding(device, "connected").as(conn =>
|
||||
conn ? "selected" : "")} title={
|
||||
createBinding(device, "alias").as(alias => alias ?? "Unknown Device")}
|
||||
icon={createBinding(device, "icon").as(ico => ico ?? "bluetooth-active-symbolic")}
|
||||
tooltipText={
|
||||
createBinding(device, "connected").as(connected =>
|
||||
!connected ? tr("connect") : "")
|
||||
} actionClicked={() => {
|
||||
if(device.connected) return;
|
||||
|
||||
pair().then(() => {
|
||||
device.connect_device((_, res) => {
|
||||
|
||||
// get error
|
||||
try { device.connect_device_finish(res); }
|
||||
catch(e: any) {
|
||||
Notifications.getDefault().sendNotification({
|
||||
appName: "bluetooth",
|
||||
summary: "Connection Error",
|
||||
body: `An error occurred while attempting to connect to ${
|
||||
device.alias ?? device.name}: ${(e as Gio.IOErrorEnum).message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch((err: Gio.IOErrorEnum) =>
|
||||
Notifications.getDefault().sendNotification({
|
||||
appName: "bluetooth",
|
||||
summary: "Pairing Error",
|
||||
body: `Couldn't pair with ${device.alias ?? device.name}: ${err.message}`,
|
||||
urgency: AstalNotifd.Urgency.NORMAL
|
||||
})
|
||||
);
|
||||
}}
|
||||
endWidget={<Gtk.Box spacing={6}>
|
||||
<Adw.Spinner visible={createBinding(device, "connecting")} />
|
||||
<Gtk.Box visible={createComputed([
|
||||
createBinding(device, "batteryPercentage"),
|
||||
createBinding(device, "connected")
|
||||
]).as(([batt, connected]) => connected && (batt > -1))
|
||||
} spacing={4}>
|
||||
<Gtk.Label halign={Gtk.Align.END} label={
|
||||
createBinding(device, "batteryPercentage").as(batt =>
|
||||
`${Math.floor(batt * 100)}%`)
|
||||
} visible={createBinding(device, "connected")}
|
||||
/>
|
||||
|
||||
<Gtk.Image iconName={
|
||||
createBinding(device, "batteryPercentage").as(batt =>
|
||||
`battery-level-${Math.floor(batt * 100)}-symbolic`)
|
||||
} css={"font-size: 16px; margin-left: 6px;"} />
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>} extraButtons={<With value={createComputed([
|
||||
createBinding(device, "connected"),
|
||||
createBinding(device, "trusted")
|
||||
])}>
|
||||
{([connected, trusted]: [boolean, boolean]) =>
|
||||
<Gtk.Box visible={connected || trusted}>
|
||||
{<Gtk.Button iconName={connected ?
|
||||
"list-remove-symbolic"
|
||||
: "user-trash-symbolic"} tooltipText={tr(connected ?
|
||||
"disconnect"
|
||||
: "control_center.pages.bluetooth.unpair_device"
|
||||
)} onClicked={() => {
|
||||
if(!connected) {
|
||||
Bluetooth.getDefault().adapter?.remove_device(device);
|
||||
return;
|
||||
}
|
||||
|
||||
device.disconnect_device(null);
|
||||
}} />}
|
||||
|
||||
<Gtk.Button iconName={trusted ?
|
||||
"shield-safe-symbolic"
|
||||
: "shield-danger-symbolic"} tooltipText={tr(
|
||||
`control_center.pages.bluetooth.${trusted ? "un" : ""}trust_device`
|
||||
)} onClicked={() => device.set_trusted(!trusted)}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
}
|
||||
</With>}
|
||||
/> as Gtk.Widget;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Page, PageButton } from "../Page";
|
||||
import { Wireplumber } from "../../../../modules/volume";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { createBinding, For } from "ags";
|
||||
import { lookupIcon } from "../../../../modules/apps";
|
||||
|
||||
import AstalWp from "gi://AstalWp?version=0.1";
|
||||
|
||||
|
||||
export const PageMicrophone = <Page
|
||||
id={"microphone"}
|
||||
title={tr("control_center.pages.microphone.title")}
|
||||
description={tr("control_center.pages.microphone.description")}
|
||||
content={() => [
|
||||
<Gtk.Label class={"sub-header"} label={tr("devices")} xalign={0} />,
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
||||
<For each={createBinding(Wireplumber.getWireplumber().get_audio()!, "microphones")}>
|
||||
{(source: AstalWp.Endpoint) => <PageButton class={
|
||||
createBinding(source, "isDefault").as(isDefault => isDefault ? "selected" : "")
|
||||
} icon={createBinding(source, "icon").as(ico => lookupIcon(ico) ?
|
||||
ico : "audio-input-microphone-symbolic")} title={
|
||||
createBinding(source, "description").as(desc => desc ?? "Microphone")
|
||||
} actionClicked={() => !source.isDefault && source.set_is_default(true)}
|
||||
endWidget={
|
||||
<Gtk.Image iconName={"object-select-symbolic"} visible={
|
||||
createBinding(source, "isDefault")} css={"font-size: 18px;"}
|
||||
/>
|
||||
}
|
||||
/>}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
]}
|
||||
/> as Page;
|
||||
170
home/ags-config/window/control-center/widgets/pages/Network.tsx
Normal file
170
home/ags-config/window/control-center/widgets/pages/Network.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Page, PageButton } from "../Page";
|
||||
import { Windows } from "../../../../windows";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { execApp } from "../../../../modules/apps";
|
||||
import { Notifications } from "../../../../modules/notifications";
|
||||
import { AskPopup, AskPopupProps } from "../../../../widget/AskPopup";
|
||||
import { encoder, variableToBoolean } from "../../../../modules/utils";
|
||||
import { createBinding, For, With } from "ags";
|
||||
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
import NM from "gi://NM";
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
|
||||
|
||||
export const PageNetwork = <Page
|
||||
id={"network"}
|
||||
title={tr("control_center.pages.network.title")}
|
||||
headerButtons={createBinding(AstalNetwork.get_default(), "primary").as(primary =>
|
||||
primary === AstalNetwork.Primary.WIFI ? [{
|
||||
icon: "arrow-circular-top-right-symbolic",
|
||||
tooltipText: "Re-scan networks",
|
||||
actionClicked: () => AstalNetwork.get_default().wifi.scan()
|
||||
}] : []
|
||||
)}
|
||||
bottomButtons={[{
|
||||
title: tr("control_center.pages.more_settings"),
|
||||
actionClicked: () => {
|
||||
Windows.getDefault().close("control-center");
|
||||
execApp("nm-connection-editor", "[animationstyle gnomed]");
|
||||
}
|
||||
}]}
|
||||
content={() => [
|
||||
<Gtk.Box class={"devices"} hexpand orientation={Gtk.Orientation.VERTICAL}
|
||||
visible={variableToBoolean(createBinding(AstalNetwork.get_default().client, "devices"))}
|
||||
spacing={4}>
|
||||
|
||||
<Gtk.Label label={tr("devices")} xalign={0} class={"sub-header"} />
|
||||
<For each={createBinding(AstalNetwork.get_default().client, "devices").as(devs =>
|
||||
devs.filter(dev => dev.interface !== "lo" && dev.real /* filter local device */))}>
|
||||
|
||||
{(device: NM.Device) => <PageButton title={createBinding(device, "interface").as(iface =>
|
||||
iface ?? tr("control_center.pages.network.interface"))} class={"device"}
|
||||
icon={createBinding(device, "deviceType").as(type => type === NM.DeviceType.WIFI ?
|
||||
"network-wireless-symbolic" : "network-wired-symbolic")} extraButtons={[
|
||||
|
||||
<Gtk.Button iconName={"view-more-symbolic"} onClicked={() => {
|
||||
Windows.getDefault().close("control-center");
|
||||
execApp(
|
||||
`nm-connection-editor --edit ${device.activeConnection?.connection.get_uuid()}`,
|
||||
"[animationstyle gnomed; float]"
|
||||
);
|
||||
}} />
|
||||
]}
|
||||
/>}
|
||||
</For>
|
||||
</Gtk.Box>,
|
||||
<With value={createBinding(AstalNetwork.get_default(), "primary").as(primary =>
|
||||
primary === AstalNetwork.Primary.WIFI)}>
|
||||
|
||||
{(isWifi: boolean) => isWifi && <Gtk.Box class={"wireless-aps"} hexpand={true}
|
||||
orientation={Gtk.Orientation.VERTICAL}>
|
||||
|
||||
<Gtk.Label class={"sub-header"} label={"Wi-Fi"} />
|
||||
<For each={createBinding(AstalNetwork.get_default().wifi, "accessPoints")}>
|
||||
{(ap: AstalNetwork.AccessPoint) => <PageButton class={
|
||||
createBinding(AstalNetwork.get_default().wifi, "activeAccessPoint").as(activeAP =>
|
||||
activeAP.ssid === ap.ssid ? "active" : "")
|
||||
} title={createBinding(ap, "ssid").as(ssid => ssid ?? "No SSID")}
|
||||
icon={createBinding(ap, "iconName")} endWidget={<Gtk.Image iconName={
|
||||
createBinding(ap, "flags").as(flags =>
|
||||
// @ts-ignore
|
||||
flags & NM["80211ApFlags"].PRIVACY ?
|
||||
"channel-secure-symbolic"
|
||||
: "channel-insecure-symbolic")}
|
||||
css={"font-size: 18px;"}
|
||||
/>} extraButtons={[
|
||||
<Gtk.Button iconName={"window-close-symbolic"} visible={
|
||||
createBinding(AstalNetwork.get_default().wifi, "activeAccessPoint").as(activeAp =>
|
||||
activeAp.ssid === ap.ssid)
|
||||
} css={"font-size: 18px;"} onClicked={() => {
|
||||
const active = AstalNetwork.get_default().wifi.activeAccessPoint;
|
||||
|
||||
if(active?.ssid === ap.ssid) {
|
||||
AstalNetwork.get_default().wifi.deactivate_connection((_, res) => {
|
||||
try {
|
||||
AstalNetwork.get_default().wifi.deactivate_connection_finish(res);
|
||||
} catch(e: any) {
|
||||
e = e as Error;
|
||||
|
||||
console.error(
|
||||
`Network: couldn't deactivate connection with access point(SSID: ${
|
||||
ap.ssid}. Stderr: \n${e.message}\n${e.stack}`
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}}/>
|
||||
]} actionClicked={() => {
|
||||
const uuid = NM.utils_uuid_generate();
|
||||
const ssidBytes = GLib.Bytes.new(encoder.encode(ap.ssid));
|
||||
|
||||
const connection = NM.SimpleConnection.new();
|
||||
const connSetting = NM.SettingConnection.new();
|
||||
const wifiSetting = NM.SettingWireless.new();
|
||||
const wifiSecuritySetting = NM.SettingWirelessSecurity.new();
|
||||
const setting8021x = NM.Setting8021x.new();
|
||||
|
||||
// @ts-ignore yep, type-gen issues again
|
||||
if(ap.rsnFlags !& NM["80211ApSecurityFlags"].KEY_MGMT_802_1X &&
|
||||
// @ts-ignore
|
||||
ap.wpaFlags !& NM["80211ApSecurityFlags"].KEY_MGMT_802_1X) {
|
||||
return;
|
||||
}
|
||||
|
||||
connSetting.uuid = uuid;
|
||||
connection.add_setting(connSetting);
|
||||
|
||||
connection.add_setting(wifiSetting);
|
||||
wifiSetting.ssid = ssidBytes;
|
||||
|
||||
wifiSecuritySetting.keyMgmt = "wpa-eap";
|
||||
connection.add_setting(wifiSecuritySetting);
|
||||
|
||||
setting8021x.add_eap_method("ttls");
|
||||
setting8021x.phase2Auth = "mschapv2";
|
||||
connection.add_setting(setting8021x);
|
||||
}}
|
||||
/>}
|
||||
</For>
|
||||
</Gtk.Box>}
|
||||
</With>
|
||||
]}
|
||||
/> as Page;
|
||||
|
||||
function activateWirelessConnection(connection: NM.RemoteConnection, ssid: string): void {
|
||||
AstalNetwork.get_default().get_client().activate_connection_async(
|
||||
connection, AstalNetwork.get_default().wifi.get_device(), null, null, (_, asyncRes) => {
|
||||
const activeConnection = AstalNetwork.get_default().get_client().activate_connection_finish(asyncRes);
|
||||
if(!activeConnection) {
|
||||
Notifications.getDefault().sendNotification({
|
||||
appName: "network",
|
||||
summary: "Couldn't activate wireless connection",
|
||||
body: `An error occurred while activating the wireless connection "${ssid}"`
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function notifyConnectionError(ssid: string): void {
|
||||
Notifications.getDefault().sendNotification({
|
||||
appName: "network",
|
||||
summary: "Coudn't connect Wi-Fi",
|
||||
body: `An error occurred while trying to connect to the "${ssid}" access point. \nMaybe the password is invalid?`
|
||||
});
|
||||
}
|
||||
function saveToDisk(remoteConnection: NM.RemoteConnection, ssid: string): void {
|
||||
AskPopup({
|
||||
text: `Save password for connection "${ssid}"?`,
|
||||
acceptText: "Yes",
|
||||
onAccept: () => remoteConnection.commit_changes_async(true, null, (_, asyncRes) =>
|
||||
!remoteConnection.commit_changes_finish(asyncRes) && Notifications.getDefault().sendNotification({
|
||||
appName: "network",
|
||||
summary: "Couldn't save Wi-Fi password",
|
||||
body: `An error occurred while trying to write the password for "${ssid}" to disk`
|
||||
}))
|
||||
} as AskPopupProps);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Page } from "../Page";
|
||||
import { NightLight } from "../../../../modules/nightlight";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
import { addSliderMarksFromMinMax } from "../../../../modules/utils";
|
||||
import { createBinding } from "ags";
|
||||
|
||||
|
||||
export const PageNightLight = <Page
|
||||
id={"night-light"}
|
||||
title={tr("control_center.pages.night_light.title")}
|
||||
description={tr("control_center.pages.night_light.description")}
|
||||
content={() => [
|
||||
<Gtk.Label class={"sub-header"} label={tr(
|
||||
"control_center.pages.night_light.temperature"
|
||||
)} xalign={0} />,
|
||||
<Astal.Slider class={"temperature"} $={(self) => {
|
||||
self.value = NightLight.getDefault().temperature;
|
||||
addSliderMarksFromMinMax(self, 5, "{}K");
|
||||
}} value={createBinding(NightLight.getDefault(), "temperature")}
|
||||
tooltipText={createBinding(NightLight.getDefault(), "temperature").as(temp =>
|
||||
`${temp}K`)} min={NightLight.getDefault().minTemperature}
|
||||
max={NightLight.getDefault().maxTemperature}
|
||||
onChangeValue={(_, type, value) => {
|
||||
if(type != undefined && type !== null)
|
||||
NightLight.getDefault().temperature = Math.floor(value)
|
||||
}}
|
||||
/>,
|
||||
<Gtk.Label class={"sub-header"} label={tr(
|
||||
"control_center.pages.night_light.gamma"
|
||||
)} xalign={0} />,
|
||||
<Astal.Slider class={"gamma"} $={(self) => {
|
||||
self.value = NightLight.getDefault().gamma;
|
||||
addSliderMarksFromMinMax(self, 5, "{}%");
|
||||
}} value={createBinding(NightLight.getDefault(), "gamma")}
|
||||
tooltipText={createBinding(NightLight.getDefault(), "gamma").as(gamma =>
|
||||
`${gamma}%`)} max={NightLight.getDefault().maxGamma}
|
||||
onChangeValue={(_, type, value) => {
|
||||
if(type != undefined && type !== null)
|
||||
NightLight.getDefault().gamma = Math.floor(value)
|
||||
}}
|
||||
/>
|
||||
]}
|
||||
/> as Page;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Page, PageButton } from "../Page";
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
import { getAppIcon, lookupIcon } from "../../../../modules/apps";
|
||||
import { Wireplumber } from "../../../../modules/volume";
|
||||
import { tr } from "../../../../i18n/intl";
|
||||
import { createBinding, For } from "ags";
|
||||
import { createScopedConnection, variableToBoolean } from "../../../../modules/utils";
|
||||
|
||||
import AstalWp from "gi://AstalWp";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
|
||||
|
||||
export const PageSound = <Page
|
||||
id={"sound"}
|
||||
title={tr("control_center.pages.sound.title")}
|
||||
description={tr("control_center.pages.sound.description")}
|
||||
content={() => [
|
||||
<Gtk.Label class={"sub-header"} label={tr("devices")} xalign={0} />,
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
||||
<For each={createBinding(Wireplumber.getWireplumber().audio!, "speakers")}>
|
||||
{(sink: AstalWp.Endpoint) =>
|
||||
<PageButton class={createBinding(sink, "isDefault").as(isDefault =>
|
||||
isDefault ? "selected" : "")}
|
||||
icon={createBinding(sink, "icon").as(ico =>
|
||||
lookupIcon(ico) ? ico : "audio-card-symbolic")}
|
||||
title={createBinding(sink, "description").as(desc =>
|
||||
desc ?? "Speaker")}
|
||||
actionClicked={() => !sink.isDefault && sink.set_is_default(true)}
|
||||
endWidget={
|
||||
<Gtk.Image iconName={"object-select-symbolic"}
|
||||
visible={createBinding(sink, "isDefault")}
|
||||
css={"font-size: 18px;"}
|
||||
/>
|
||||
}
|
||||
/>}
|
||||
</For>
|
||||
</Gtk.Box>,
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||
<Gtk.Label class={"sub-header"} label={tr("apps")} xalign={0}
|
||||
visible={variableToBoolean(
|
||||
createBinding(Wireplumber.getWireplumber().audio!, "streams")
|
||||
)}
|
||||
/>
|
||||
<For each={createBinding(Wireplumber.getWireplumber().audio!, "streams")}>
|
||||
{(stream: AstalWp.Stream) =>
|
||||
<Gtk.Box hexpand $={(self) => {
|
||||
const controllerMotion = Gtk.EventControllerMotion.new();
|
||||
|
||||
self.add_controller(controllerMotion);
|
||||
createScopedConnection(controllerMotion, "enter", () => {
|
||||
const revealer = self.get_last_child()!.get_first_child() as Gtk.Revealer;
|
||||
revealer.set_reveal_child(true);
|
||||
});
|
||||
createScopedConnection(controllerMotion, "leave", () => {
|
||||
const revealer = self.get_last_child()!.get_first_child() as Gtk.Revealer;
|
||||
revealer.set_reveal_child(false);
|
||||
});
|
||||
}}>
|
||||
<Gtk.Image iconName={createBinding(stream, "name").as(name =>
|
||||
getAppIcon(name.split(' ')[0]) ?? "application-x-executable-symbolic")}
|
||||
css={"font-size: 18px; margin-right: 6px;"} />
|
||||
|
||||
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} hexpand={true}>
|
||||
<Gtk.Revealer transitionDuration={180}
|
||||
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}>
|
||||
|
||||
<Gtk.Label label={createBinding(stream, "description").as(desc =>
|
||||
desc ?? "Unnamed audio stream")}
|
||||
ellipsize={Pango.EllipsizeMode.END}
|
||||
tooltipText={createBinding(stream, "name")}
|
||||
class={"name"} xalign={0}
|
||||
/>
|
||||
</Gtk.Revealer>
|
||||
|
||||
<Astal.Slider drawValue={false} value={createBinding(stream, "volume")}
|
||||
onChangeValue={(_, __, value) => stream.set_volume(value)}
|
||||
hexpand min={0} max={1.5}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
]}
|
||||
/> as Page;
|
||||
100
home/ags-config/window/control-center/widgets/pages/index.tsx
Normal file
100
home/ags-config/window/control-center/widgets/pages/index.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import GObject, { getter, gtype, register } from "ags/gobject";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Page } from "../Page";
|
||||
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
|
||||
|
||||
@register({ GTypeName: "Pages" })
|
||||
export class Pages extends Gtk.Box {
|
||||
#timeouts: Array<[GLib.Source, (() => void)|undefined]> = [];
|
||||
#page: Page|undefined;
|
||||
#transDuration: number;
|
||||
#transType: Gtk.RevealerTransitionType = Gtk.RevealerTransitionType.SLIDE_DOWN;
|
||||
|
||||
@getter(Boolean)
|
||||
get isOpen() { return Boolean(this.#page); }
|
||||
|
||||
@getter(gtype<Page|undefined>(Page))
|
||||
get page() { return this.#page; }
|
||||
|
||||
constructor(props?: {
|
||||
initialPage?: Page;
|
||||
transitionDuration?: number;
|
||||
}) {
|
||||
super({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
cssName: "pages",
|
||||
name: "pages"
|
||||
});
|
||||
|
||||
this.add_css_class("pages");
|
||||
|
||||
this.#transDuration = props?.transitionDuration ?? 280;
|
||||
|
||||
if(props?.initialPage)
|
||||
this.open(props.initialPage);
|
||||
|
||||
|
||||
const destroyId = this.connect("destroy", () => {
|
||||
GObject.signal_handler_is_connected(this, destroyId) &&
|
||||
this.disconnect(destroyId);
|
||||
|
||||
this.#timeouts.forEach((tmout) => {
|
||||
tmout[0].destroy();
|
||||
(async () => tmout[1]?.())().catch((err: Error) => {
|
||||
console.error(`${err.message}\n${err.stack}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggle(newPage?: Page, onToggled?: () => void): void {
|
||||
if(!newPage || (this.#page?.id === newPage.id)) {
|
||||
this.close(onToggled);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.isOpen) {
|
||||
newPage && this.open(newPage, onToggled);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.#page?.id !== newPage.id) {
|
||||
this.close();
|
||||
this.open(newPage, onToggled);
|
||||
}
|
||||
}
|
||||
|
||||
open(newPage: Page, onOpen?: () => void) {
|
||||
this.#page = newPage;
|
||||
|
||||
this.prepend(
|
||||
<Gtk.Revealer revealChild={false} transitionType={this.#transType}
|
||||
transitionDuration={this.#transDuration}>
|
||||
|
||||
{newPage.create()}
|
||||
</Gtk.Revealer> as Gtk.Revealer
|
||||
);
|
||||
|
||||
(this.get_first_child() as Gtk.Revealer)?.set_reveal_child(true);
|
||||
onOpen?.();
|
||||
}
|
||||
|
||||
close(onClosed?: () => void): void {
|
||||
const page = this.get_first_child() as Gtk.Revealer|null;
|
||||
if(!page) return;
|
||||
|
||||
this.#page?.actionClosed?.();
|
||||
this.#page = undefined;
|
||||
|
||||
page.set_reveal_child(false);
|
||||
this.#timeouts.push([
|
||||
setTimeout(() => {
|
||||
this.remove(page);
|
||||
onClosed?.();
|
||||
}, page.transitionDuration),
|
||||
onClosed
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user