🔧 chore: general improvements
- save night light filter data in `userData` - better click detection in control center tiles - continue development of the native polkit agent - start night light module on shell init, drop hyprsunset scripts
This commit is contained in:
@@ -35,6 +35,7 @@ import GObject, { register } from "ags/gobject";
|
|||||||
import GLib from "gi://GLib?version=2.0";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
import Gio from "gi://Gio?version=2.0";
|
import Gio from "gi://Gio?version=2.0";
|
||||||
import Adw from "gi://Adw?version=1";
|
import Adw from "gi://Adw?version=1";
|
||||||
|
import { NightLight } from "./modules/nightlight";
|
||||||
|
|
||||||
|
|
||||||
const runnerPlugins: Array<Runner.Plugin> = [
|
const runnerPlugins: Array<Runner.Plugin> = [
|
||||||
@@ -73,6 +74,7 @@ export class Shell extends Adw.Application {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setConsoleLogDomain("colorshell");
|
setConsoleLogDomain("colorshell");
|
||||||
|
GLib.set_application_name("colorshell");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getDefault(): Shell {
|
public static getDefault(): Shell {
|
||||||
@@ -274,6 +276,8 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re
|
|||||||
this.#connections.set(this, this.connect("shutdown", () => dispose()));
|
this.#connections.set(this, this.connect("shutdown", () => dispose()));
|
||||||
this.#scope = getScope();
|
this.#scope = getScope();
|
||||||
|
|
||||||
|
NightLight.getDefault();
|
||||||
|
|
||||||
initPlayer();
|
initPlayer();
|
||||||
Clipboard.getDefault();
|
Clipboard.getDefault();
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -47,13 +47,22 @@ const generalConfigDefaults = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const userDataDefaults = {
|
const userDataDefaults = {
|
||||||
|
/** last default adapter */
|
||||||
|
bluetooth_default_adapter: undefined,
|
||||||
|
|
||||||
control_center: {
|
control_center: {
|
||||||
/** last default backlight */
|
/** last default backlight */
|
||||||
default_backlight: undefined
|
default_backlight: undefined
|
||||||
},
|
},
|
||||||
|
|
||||||
/** last default adapter */
|
night_light: {
|
||||||
bluetooth_default_adapter: undefined
|
/** last blue light filter temperature */
|
||||||
|
temperature: 6000,
|
||||||
|
/** last gamma filter value */
|
||||||
|
gamma: 100,
|
||||||
|
/** wheter to enable identity filters("disables" the filters) */
|
||||||
|
identity: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userData = new Config<
|
export const userData = new Config<
|
||||||
|
|||||||
+65
-26
@@ -1,7 +1,6 @@
|
|||||||
import { execAsync } from "ags/process";
|
import { exec, execAsync } from "ags/process";
|
||||||
import { register } from "ags/gobject";
|
import { register } from "ags/gobject";
|
||||||
import { EntryPopup, EntryPopupProps } from "../widget/EntryPopup";
|
import { AuthPopup } from "../widget/AuthPopup";
|
||||||
import { AskPopup, AskPopupProps } from "../widget/AskPopup";
|
|
||||||
|
|
||||||
import AstalAuth from "gi://AstalAuth";
|
import AstalAuth from "gi://AstalAuth";
|
||||||
import Polkit from "gi://Polkit";
|
import Polkit from "gi://Polkit";
|
||||||
@@ -14,39 +13,48 @@ import GLib from "gi://GLib?version=2.0";
|
|||||||
export class Auth extends PolkitAgent.Listener {
|
export class Auth extends PolkitAgent.Listener {
|
||||||
private static instance: Auth;
|
private static instance: Auth;
|
||||||
#subject: Polkit.Subject;
|
#subject: Polkit.Subject;
|
||||||
|
#pam: AstalAuth.Pam;
|
||||||
|
#handle: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.#subject = Polkit.UnixSession.new(GLib.get_user_name());
|
this.#subject = Polkit.UnixSession.new(""); // TODO find how to get session id (for some reason, i can't find a session ID that works)
|
||||||
|
this.#pam = new AstalAuth.Pam();
|
||||||
|
|
||||||
this.register(PolkitAgent.RegisterFlags.NONE,
|
this.#handle = this.register(
|
||||||
|
PolkitAgent.RegisterFlags.RUN_IN_THREAD,
|
||||||
this.#subject,
|
this.#subject,
|
||||||
"/io/github/retrozinndev/Colorshell/PolicyKit/AuthAgent",
|
"/io/github/retrozinndev/colorshell/PolicyKit/AuthAgent",
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_dispose() {
|
vfunc_dispose() {
|
||||||
PolkitAgent.Listener.unregister();
|
PolkitAgent.Listener.unregister(this.#handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
static initiate_authentication(action_id: string, message: string, icon_name: string, details: Polkit.Details, cookie: string, identities: Array<Polkit.Identity>, cancellable?: Gio.Cancellable, callback?: Gio.AsyncReadyCallback): void | Promise<boolean> {
|
public static initiate_authentication(action_id: string, message: string, icon_name: string, details: Polkit.Details, cookie: string, identities: Array<Polkit.Identity>, cancellable: Gio.Cancellable|null, callback: Gio.AsyncReadyCallback<Auth>|null): void {
|
||||||
const authPopup = EntryPopup({
|
const task = Gio.Task.new(
|
||||||
title: "Authentication",
|
this.getDefault(),
|
||||||
|
cancellable,
|
||||||
|
callback as Gio.AsyncReadyCallback|null
|
||||||
|
);
|
||||||
|
|
||||||
|
AuthPopup({
|
||||||
text: message,
|
text: message,
|
||||||
isPassword: true,
|
iconName: icon_name,
|
||||||
onFinish: callback,
|
onContinue: (data, reject, approve) => {
|
||||||
onCancel: () => cancellable?.cancel(),
|
this.getDefault().validateAuth(data.passwd, data.user).then((success) => {
|
||||||
closeOnAccept: false,
|
approve();
|
||||||
onAccept: (input: string) => {
|
task.return_boolean(success);
|
||||||
if(this.validatePasswd(input)) {
|
}).catch((error: GLib.Error) => {
|
||||||
authPopup.close();
|
// TODO implement a number of tries (usually it's 3)
|
||||||
|
reject(`Authentication failed: ${error.message}`);
|
||||||
|
task.return_error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
AskPopup({
|
});
|
||||||
|
|
||||||
} as AskPopupProps)
|
|
||||||
}
|
|
||||||
} as EntryPopupProps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,15 +65,39 @@ export class Auth extends PolkitAgent.Listener {
|
|||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static validatePasswd(passwd: string): boolean {
|
// TODO: support fingerprint/facial auth
|
||||||
return AstalAuth.Pam.authenticate(passwd, null);
|
/** @returns true if data are correct, rejects promise otherwise */
|
||||||
|
public validateAuth(passwd: string, user?: string): Promise<boolean> {
|
||||||
|
if(user !== undefined)
|
||||||
|
this.#pam.username = user;
|
||||||
|
|
||||||
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
|
const connections: Array<number> = [];
|
||||||
|
connections.push(
|
||||||
|
this.#pam.connect("fail", () => {
|
||||||
|
reject(
|
||||||
|
`Auth: Authentication has failed for user ${this.#pam.username}`
|
||||||
|
);
|
||||||
|
connections.forEach(id => this.#pam.disconnect(id));
|
||||||
|
}),
|
||||||
|
this.#pam.connect("success", () => {
|
||||||
|
resolve(true);
|
||||||
|
connections.forEach(id => this.#pam.disconnect(id));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.#pam.start_authenticate();
|
||||||
|
this.#pam.supply_secret(passwd);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns if successful, true, or else, false */
|
/** @returns true if successful */
|
||||||
public async polkitExecute(cmd: string | Array<string>): Promise<boolean> {
|
public async polkitExecute(cmd: string | Array<string>): Promise<boolean> {
|
||||||
let success: boolean = true;
|
let success: boolean = true;
|
||||||
await execAsync([ "pkexec", "--", ...(Array.isArray(cmd) ?
|
await execAsync([
|
||||||
cmd as Array<string> : [ cmd as string ]) ]
|
"pkexec",
|
||||||
|
"--",
|
||||||
|
...(Array.isArray(cmd) ? cmd : [ cmd ]) ]
|
||||||
).catch((r) => {
|
).catch((r) => {
|
||||||
success = false;
|
success = false;
|
||||||
console.error(`Polkit: Couldn't authenticate. Stderr: ${r}`);
|
console.error(`Polkit: Couldn't authenticate. Stderr: ${r}`);
|
||||||
@@ -73,4 +105,11 @@ export class Auth extends PolkitAgent.Listener {
|
|||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getDefault(): Auth {
|
||||||
|
if(!this.instance)
|
||||||
|
this.instance = new Auth();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-28
@@ -1,20 +1,22 @@
|
|||||||
import { execAsync, exec } from "ags/process";
|
import { execAsync, exec } from "ags/process";
|
||||||
import { interval } from "ags/time";
|
import { userData } from "../config";
|
||||||
import GObject, { getter, register, setter } from "ags/gobject";
|
import GObject, { getter, register, setter } from "ags/gobject";
|
||||||
|
|
||||||
import AstalIO from "gi://AstalIO";
|
|
||||||
import GLib from "gi://GLib?version=2.0";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
|
|
||||||
|
|
||||||
export { NightLight };
|
|
||||||
|
|
||||||
@register({ GTypeName: "NightLight" })
|
@register({ GTypeName: "NightLight" })
|
||||||
class NightLight extends GObject.Object {
|
export class NightLight extends GObject.Object {
|
||||||
private static instance: NightLight;
|
private static instance: NightLight;
|
||||||
|
|
||||||
#watchInterval: (AstalIO.Time|null) = null;
|
public readonly maxTemperature = 20000;
|
||||||
#temperature: number = 4500;
|
public readonly minTemperature = 1000;
|
||||||
#gamma: number = 100;
|
public readonly identityTemperature = 6000;
|
||||||
|
public readonly maxGamma = 100;
|
||||||
|
|
||||||
|
#watchInterval: GLib.Source;
|
||||||
|
#temperature: number = this.identityTemperature;
|
||||||
|
#gamma: number = this.maxGamma;
|
||||||
#identity: boolean = false;
|
#identity: boolean = false;
|
||||||
|
|
||||||
@getter(Number)
|
@getter(Number)
|
||||||
@@ -25,11 +27,6 @@ class NightLight extends GObject.Object {
|
|||||||
public get gamma() { return this.#gamma; }
|
public get gamma() { return this.#gamma; }
|
||||||
public set gamma(newValue: number) { this.setGamma(newValue); }
|
public set gamma(newValue: number) { this.setGamma(newValue); }
|
||||||
|
|
||||||
public readonly maxTemperature = 20000;
|
|
||||||
public readonly minTemperature = 1000;
|
|
||||||
public readonly identityTemperature = 6000;
|
|
||||||
public readonly maxGamma = 100;
|
|
||||||
|
|
||||||
@getter(Boolean)
|
@getter(Boolean)
|
||||||
public get identity() { return this.#identity; }
|
public get identity() { return this.#identity; }
|
||||||
|
|
||||||
@@ -43,7 +40,8 @@ class NightLight extends GObject.Object {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.#watchInterval = interval(10000, () => {
|
this.loadData();
|
||||||
|
this.#watchInterval = setInterval(() => {
|
||||||
execAsync("hyprctl hyprsunset temperature").then(t => {
|
execAsync("hyprctl hyprsunset temperature").then(t => {
|
||||||
if(t.trim() !== "" && t.trim().length <= 5) {
|
if(t.trim() !== "" && t.trim().length <= 5) {
|
||||||
const val = Number.parseInt(t.trim());
|
const val = Number.parseInt(t.trim());
|
||||||
@@ -54,7 +52,8 @@ class NightLight extends GObject.Object {
|
|||||||
this.notify("temperature");
|
this.notify("temperature");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch((r) => console.error(r));
|
}).catch((r: Error) => console.error(`Night Light: Couldn't sync temperature. Stderr: ${
|
||||||
|
r.message}\n${r.stack}`));
|
||||||
|
|
||||||
execAsync("hyprctl hyprsunset gamma").then(g => {
|
execAsync("hyprctl hyprsunset gamma").then(g => {
|
||||||
if(g.trim() !== "" && g.trim().length <= 5) {
|
if(g.trim() !== "" && g.trim().length <= 5) {
|
||||||
@@ -66,11 +65,13 @@ class NightLight extends GObject.Object {
|
|||||||
this.notify("gamma");
|
this.notify("gamma");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch((r) => console.error(r));
|
}).catch((r: Error) => console.error(`Night Light: Couldn't sync. Stderr: ${
|
||||||
});
|
r.message}\n${r.stack}`));
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
this.vfunc_dispose = () => this.#watchInterval &&
|
vfunc_dispose(): void {
|
||||||
this.#watchInterval.cancel();
|
this.#watchInterval?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getDefault(): NightLight {
|
public static getDefault(): NightLight {
|
||||||
@@ -84,7 +85,7 @@ class NightLight extends GObject.Object {
|
|||||||
if(value === this.temperature && !this.identity) return;
|
if(value === this.temperature && !this.identity) return;
|
||||||
|
|
||||||
if(value > this.maxTemperature || value < 1000) {
|
if(value > this.maxTemperature || value < 1000) {
|
||||||
console.error(`Night Light(hyprsunset): provided temperatue ${value
|
console.error(`Night Light: provided temperatue ${value
|
||||||
} is out of bounds (min: 1000; max: ${this.maxTemperature})`);
|
} is out of bounds (min: 1000; max: ${this.maxTemperature})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -94,8 +95,8 @@ class NightLight extends GObject.Object {
|
|||||||
this.notify("temperature");
|
this.notify("temperature");
|
||||||
|
|
||||||
this.identity = false;
|
this.identity = false;
|
||||||
}).catch((r) => console.error(
|
}).catch((r: Error) => console.error(
|
||||||
`Night Light(hyprsunset): Couldn't set temperature. Stderr: ${r}`
|
`Night Light: Couldn't set temperature. Stderr: ${r.message}\n${r.stack}`
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ class NightLight extends GObject.Object {
|
|||||||
if(value === this.gamma && !this.identity) return;
|
if(value === this.gamma && !this.identity) return;
|
||||||
|
|
||||||
if(value > this.maxGamma || value < 0) {
|
if(value > this.maxGamma || value < 0) {
|
||||||
console.error(`Night Light(hyprsunset): provided gamma ${value
|
console.error(`Night Light: provided gamma ${value
|
||||||
} is out of bounds (min: 0; max: ${this.maxTemperature})`);
|
} is out of bounds (min: 0; max: ${this.maxTemperature})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,24 +114,33 @@ class NightLight extends GObject.Object {
|
|||||||
this.notify("gamma");
|
this.notify("gamma");
|
||||||
|
|
||||||
this.identity = false;
|
this.identity = false;
|
||||||
}).catch((r) => console.error(
|
}).catch((r: Error) => console.error(
|
||||||
`Night Light(hyprsunset): Couldn't set gamma. Stderr: ${r}`
|
`Night Light: Couldn't set gamma. Stderr: ${r.message}\n${r.stack}`
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public applyIdentity(): void {
|
public applyIdentity(): void {
|
||||||
this.dispatch("identity");
|
this.dispatch("identity");
|
||||||
|
|
||||||
if(!this.#identity) {
|
if(!this.#identity) {
|
||||||
this.#identity = true;
|
this.#identity = true;
|
||||||
this.notify("identity");
|
this.notify("identity");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dispatch(call: "temperature", val: number): string;
|
||||||
|
private dispatch(call: "gamma", val: number): string;
|
||||||
|
private dispatch(call: "identity"): string;
|
||||||
|
|
||||||
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
|
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
|
||||||
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async dispatchAsync(...[call, val]: Parameters<typeof this.dispatch>): Promise<string> {
|
private async dispatchAsync(call: "temperature", val: number): Promise<string>;
|
||||||
|
private async dispatchAsync(call: "gamma", val: number): Promise<string>;
|
||||||
|
private async dispatchAsync(call: "identity"): Promise<string>;
|
||||||
|
|
||||||
|
private async dispatchAsync(call: "temperature"|"gamma"|"identity", val?: number): Promise<string> {
|
||||||
return await execAsync(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
return await execAsync(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +155,22 @@ class NightLight extends GObject.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public saveData(): void {
|
public saveData(): void {
|
||||||
exec(`sh ${GLib.get_user_config_dir()}/hypr/scripts/save-hyprsunset.sh`);
|
userData.setProperty("night_light.temperature", this.#temperature);
|
||||||
|
userData.setProperty("night_light.gamma", this.#gamma);
|
||||||
|
userData.setProperty("night_light.identity", this.#identity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** load temperature, gamma and identity(off/on) properties from the user configuration */
|
||||||
public loadData(): void {
|
public loadData(): void {
|
||||||
exec(`sh ${GLib.get_user_config_dir()}/hypr/scripts/load-hyprsunset.sh`);
|
const identity = userData.getProperty("night_light.identity", "boolean");
|
||||||
|
const temperature = userData.getProperty("night_light.temperature", "number");
|
||||||
|
const gamma = userData.getProperty("night_light.gamma", "number");
|
||||||
|
|
||||||
|
this.#temperature = temperature;
|
||||||
|
this.notify("temperature");
|
||||||
|
this.#gamma = gamma;
|
||||||
|
this.notify("gamma");
|
||||||
|
|
||||||
|
this.identity = identity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
|
import { CustomDialog, getContainerCustomDialog } from "./CustomDialog";
|
||||||
|
|
||||||
|
import GLib from "gi://GLib?version=2.0";
|
||||||
|
|
||||||
|
|
||||||
|
export type AuthPopupData = {
|
||||||
|
user: string;
|
||||||
|
hidePassword: boolean;
|
||||||
|
passwd: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AuthPopup(props: {
|
||||||
|
/** hide password on showup. @default true */
|
||||||
|
hidePassword?: boolean;
|
||||||
|
/** icon name of the application that's requesting this popup */
|
||||||
|
iconName?: string;
|
||||||
|
/** popup body */
|
||||||
|
text: string;
|
||||||
|
/** selected user by default */
|
||||||
|
user?: string;
|
||||||
|
/** approve data after the user clicks the "grant permission" button */
|
||||||
|
onContinue: (data: AuthPopupData, reject: (message: string) => void, approve: () => void) => void;
|
||||||
|
}): Astal.Window {
|
||||||
|
const data = {
|
||||||
|
passwd: "",
|
||||||
|
user: props.user ?? GLib.get_user_name(),
|
||||||
|
hidePassword: props.hidePassword ?? true
|
||||||
|
} satisfies AuthPopupData;
|
||||||
|
const allowUserChange = props.user === undefined;
|
||||||
|
|
||||||
|
const dialog = <CustomDialog title={"Authentication"} text={props.text}
|
||||||
|
namespace={"auth-popup"} options={[
|
||||||
|
{ text: "Deny" }, // will close and call onFinish by default
|
||||||
|
{
|
||||||
|
text: "Grant permission",
|
||||||
|
onClick: () => {
|
||||||
|
if(allowUserChange)
|
||||||
|
data.user = userEntry!.text;
|
||||||
|
|
||||||
|
data.passwd = passwordEntry.text;
|
||||||
|
data.hidePassword = passwordEntry.showPeekIcon;
|
||||||
|
|
||||||
|
props.onContinue(data,
|
||||||
|
// rejected by checker function
|
||||||
|
(m) => {
|
||||||
|
// show error to user
|
||||||
|
!messageLabel.is_visible &&
|
||||||
|
messageLabel.set_visible(true);
|
||||||
|
messageLabel.set_label(m);
|
||||||
|
|
||||||
|
// clear password entry
|
||||||
|
passwordEntry.set_text("");
|
||||||
|
},
|
||||||
|
// approved by the checker
|
||||||
|
dialog.close
|
||||||
|
);
|
||||||
|
},
|
||||||
|
closeOnClick: false
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<Gtk.Entry class={"user"} placeholderText={"User"} visible={allowUserChange} />
|
||||||
|
<Gtk.PasswordEntry class={"password"} showPeekIcon placeholderText={"Password"} />
|
||||||
|
|
||||||
|
<Gtk.Label class={"message"} label={""} />
|
||||||
|
</CustomDialog> as Astal.Window;
|
||||||
|
const messageLabel = getContainerCustomDialog(dialog).get_last_child() as Gtk.Label;
|
||||||
|
const userEntry = allowUserChange ? getContainerCustomDialog(dialog).get_first_child() as Gtk.Entry : undefined;
|
||||||
|
const passwordEntry = getContainerCustomDialog(dialog).get_first_child()?.get_next_sibling() as Gtk.PasswordEntry;
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Astal, Gtk } from "ags/gtk4";
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
import { Windows } from "../windows";
|
import { Windows } from "../windows";
|
||||||
import { PopupWindow } from "./PopupWindow";
|
import { getPopupWindowContainer, PopupWindow } from "./PopupWindow";
|
||||||
import { Separator } from "./Separator";
|
import { Separator } from "./Separator";
|
||||||
import { tr } from "../i18n/intl";
|
import { tr } from "../i18n/intl";
|
||||||
import { Accessor } from "ags";
|
import { Accessor } from "ags";
|
||||||
@@ -74,3 +74,7 @@ export function CustomDialog({ options = [{ text: tr("accept") }], ...props}: Cu
|
|||||||
return popup;
|
return popup;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContainerCustomDialog(dialog: Astal.Window): Gtk.Box {
|
||||||
|
return getPopupWindowContainer(dialog).get_first_child()?.get_last_child()?.get_prev_sibling() as Gtk.Box;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { register } from "ags/gobject";
|
import { register } from "ags/gobject";
|
||||||
import { Gtk } from "ags/gtk4";
|
import { Gtk } from "ags/gtk4";
|
||||||
import { Page } from "../Page";
|
import { Page } from "../Page";
|
||||||
import { timeout } from "ags/time";
|
|
||||||
|
|
||||||
import AstalIO from "gi://AstalIO";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
|
|
||||||
|
|
||||||
export { Pages };
|
|
||||||
export type PagesProps = {
|
export type PagesProps = {
|
||||||
initialPage?: Page;
|
initialPage?: Page;
|
||||||
transitionDuration?: number;
|
transitionDuration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@register({ GTypeName: "Pages" })
|
@register({ GTypeName: "Pages" })
|
||||||
class Pages extends Gtk.Box {
|
export class Pages extends Gtk.Box {
|
||||||
#timeouts: Array<[AstalIO.Time, (() => void)|undefined]> = [];
|
#timeouts: Array<[GLib.Source, (() => void)|undefined]> = [];
|
||||||
#page: (Page|undefined);
|
#page: (Page|undefined);
|
||||||
#transDuration: number;
|
#transDuration: number;
|
||||||
#transType: Gtk.RevealerTransitionType = Gtk.RevealerTransitionType.SLIDE_DOWN;
|
#transType: Gtk.RevealerTransitionType = Gtk.RevealerTransitionType.SLIDE_DOWN;
|
||||||
@@ -40,7 +38,7 @@ class Pages extends Gtk.Box {
|
|||||||
const destroyId = this.connect("destroy", () => {
|
const destroyId = this.connect("destroy", () => {
|
||||||
this.disconnect(destroyId);
|
this.disconnect(destroyId);
|
||||||
this.#timeouts.forEach((tmout) => {
|
this.#timeouts.forEach((tmout) => {
|
||||||
tmout[0].cancel();
|
tmout[0].destroy();
|
||||||
(async () => tmout[1]?.())().catch((err: Error) => {
|
(async () => tmout[1]?.())().catch((err: Error) => {
|
||||||
console.error(`${err.message}\n${err.stack}`);
|
console.error(`${err.message}\n${err.stack}`);
|
||||||
});
|
});
|
||||||
@@ -89,10 +87,10 @@ class Pages extends Gtk.Box {
|
|||||||
|
|
||||||
page.set_reveal_child(false);
|
page.set_reveal_child(false);
|
||||||
this.#timeouts.push([
|
this.#timeouts.push([
|
||||||
timeout(page.transitionDuration, () => {
|
setTimeout(() => {
|
||||||
this.remove(page);
|
this.remove(page);
|
||||||
onClosed?.();
|
onClosed?.();
|
||||||
}),
|
}, page.transitionDuration),
|
||||||
onClosed
|
onClosed
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const TileBluetooth = () =>
|
|||||||
onEnabled={() => Bluetooth.getDefault().adapter?.set_powered(true)}
|
onEnabled={() => Bluetooth.getDefault().adapter?.set_powered(true)}
|
||||||
onDisabled={() => Bluetooth.getDefault().adapter?.set_powered(false)}
|
onDisabled={() => Bluetooth.getDefault().adapter?.set_powered(false)}
|
||||||
onClicked={() => TilesPages?.toggle(BluetoothPage)}
|
onClicked={() => TilesPages?.toggle(BluetoothPage)}
|
||||||
enableOnClicked hasArrow
|
hasArrow
|
||||||
state={createBinding(AstalBluetooth.get_default(), "isPowered")}
|
state={createBinding(AstalBluetooth.get_default(), "isPowered")}
|
||||||
icon={createComputed([
|
icon={createComputed([
|
||||||
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export const TileNightLight = () =>
|
|||||||
hasArrow visible={isInstalled("hyprsunset")}
|
hasArrow visible={isInstalled("hyprsunset")}
|
||||||
onDisabled={() => NightLight.getDefault().identity = true}
|
onDisabled={() => NightLight.getDefault().identity = true}
|
||||||
onEnabled={() => NightLight.getDefault().identity = false}
|
onEnabled={() => NightLight.getDefault().identity = false}
|
||||||
enableOnClicked
|
|
||||||
onClicked={() => TilesPages?.toggle(PageNightLight)}
|
onClicked={() => TilesPages?.toggle(PageNightLight)}
|
||||||
state={createBinding(NightLight.getDefault(), "identity").as(identity => !identity)}
|
state={createBinding(NightLight.getDefault(), "identity").as(identity => !identity)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export class Tile extends Gtk.Box {
|
|||||||
@signal(Boolean) toggled(_state: boolean) {}
|
@signal(Boolean) toggled(_state: boolean) {}
|
||||||
@signal() enabled() {}
|
@signal() enabled() {}
|
||||||
@signal() disabled() {}
|
@signal() disabled() {}
|
||||||
@signal() clicked() {}
|
@signal() clicked() {
|
||||||
|
if(this.enableOnClicked)
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
|
||||||
@property(String)
|
@property(String)
|
||||||
public icon: string;
|
public icon: string;
|
||||||
@@ -20,7 +23,7 @@ export class Tile extends Gtk.Box {
|
|||||||
@property(String)
|
@property(String)
|
||||||
public description: string = "";
|
public description: string = "";
|
||||||
@property(Boolean)
|
@property(Boolean)
|
||||||
public enableOnClicked: boolean = true;
|
public enableOnClicked: boolean = false;
|
||||||
@property(Boolean)
|
@property(Boolean)
|
||||||
public state: boolean = false;
|
public state: boolean = false;
|
||||||
@property(Boolean)
|
@property(Boolean)
|
||||||
@@ -39,6 +42,7 @@ export class Tile extends Gtk.Box {
|
|||||||
this.state = true;
|
this.state = true;
|
||||||
!this.has_css_class("enabled") &&
|
!this.has_css_class("enabled") &&
|
||||||
this.add_css_class("enabled");
|
this.add_css_class("enabled");
|
||||||
|
|
||||||
this.emit("toggled", true);
|
this.emit("toggled", true);
|
||||||
this.emit("enabled");
|
this.emit("enabled");
|
||||||
}
|
}
|
||||||
@@ -69,25 +73,34 @@ export class Tile extends Gtk.Box {
|
|||||||
]));
|
]));
|
||||||
|
|
||||||
this.add_css_class("tile");
|
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.icon = props.icon;
|
||||||
this.title = props.title;
|
this.title = props.title;
|
||||||
this.hexpand = true;
|
this.hexpand = true;
|
||||||
|
|
||||||
if(props.hasArrow != null)
|
if(props.hasArrow !== undefined)
|
||||||
this.hasArrow = props.hasArrow;
|
this.hasArrow = props.hasArrow;
|
||||||
|
|
||||||
if(props.description != null)
|
if(props.description !== undefined)
|
||||||
this.description = props.description;
|
this.description = props.description;
|
||||||
|
|
||||||
if(props.state != null)
|
if(props.state !== undefined)
|
||||||
this.state = props.state;
|
this.state = props.state;
|
||||||
|
|
||||||
if(props.enableOnClicked != null)
|
if(props.enableOnClicked !== undefined)
|
||||||
this.enableOnClicked = props.enableOnClicked;
|
this.enableOnClicked = props.enableOnClicked;
|
||||||
|
|
||||||
if(this.state)
|
this.state &&
|
||||||
this.add_css_class("enabled"); // fix no highlight with state = true on construct
|
this.add_css_class("enabled"); // fix no highlight when enabled on init
|
||||||
|
|
||||||
this.prepend(
|
this.prepend(
|
||||||
<Gtk.Box hexpand={false} vexpand class={"icon"}>
|
<Gtk.Box hexpand={false} vexpand class={"icon"}>
|
||||||
@@ -110,28 +123,14 @@ export class Tile extends Gtk.Box {
|
|||||||
variableToBoolean(createBinding(this, "description"))
|
variableToBoolean(createBinding(this, "description"))
|
||||||
} maxWidthChars={12} hexpand={false}
|
} maxWidthChars={12} hexpand={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Gtk.GestureClick onReleased={() => {
|
|
||||||
this.emit("clicked");
|
|
||||||
if(this.enableOnClicked && !this.state)
|
|
||||||
this.enable();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}} />
|
|
||||||
</Gtk.Box> as Gtk.Box
|
</Gtk.Box> as Gtk.Box
|
||||||
);
|
);
|
||||||
|
|
||||||
if(this.hasArrow)
|
if(this.hasArrow)
|
||||||
this.append(
|
this.append(
|
||||||
<Gtk.Image class={"arrow"} iconName={"go-next-symbolic"} halign={Gtk.Align.END}>
|
<Gtk.Image class={"arrow"} iconName={"go-next-symbolic"}
|
||||||
<Gtk.GestureClick onReleased={() => {
|
halign={Gtk.Align.END}
|
||||||
this.emit("clicked");
|
/> as Gtk.Image
|
||||||
if(this.enableOnClicked && !this.state)
|
|
||||||
this.enable();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}} />
|
|
||||||
</Gtk.Image> as Gtk.Image
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user