✨ ags: add ask popup, make notifications work(finally :3) and more improvements
This commit is contained in:
+8
-9
@@ -1,21 +1,21 @@
|
|||||||
import { App } from "astal/gtk3"
|
import { App } from "astal/gtk3"
|
||||||
|
import { Windows } from "./windows";
|
||||||
import { OSD, OSDModes, setOSDMode } from "./window/OSD";
|
import { Wireplumber } from "./scripts/volume";
|
||||||
import { ControlCenter } from "./window/ControlCenter";
|
|
||||||
|
|
||||||
import { runStyleHandler } from "./scripts/style-handler";
|
import { runStyleHandler } from "./scripts/style-handler";
|
||||||
import { handleArguments } from "./scripts/arg-handler";
|
import { handleArguments } from "./scripts/arg-handler";
|
||||||
import { Wireplumber } from "./scripts/volume";
|
|
||||||
import { Windows } from "./windows";
|
|
||||||
import { Time, timeout } from "astal/time";
|
import { Time, timeout } from "astal/time";
|
||||||
|
|
||||||
|
import { OSD, OSDModes, setOSDMode } from "./window/OSD";
|
||||||
|
import { ControlCenter } from "./window/ControlCenter";
|
||||||
|
|
||||||
let osdTimer: (Time|undefined);
|
let osdTimer: (Time|undefined);
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
instanceName: "astal",
|
instanceName: "astal",
|
||||||
requestHandler(request: string, res: (result: any) => void) {
|
requestHandler(request: string, response: (result: any) => void) {
|
||||||
console.log(`[LOG] Arguments received: ${request}`)
|
console.log(`[LOG] Arguments received: ${request}`);
|
||||||
res(handleArguments(request));
|
response(handleArguments(request));
|
||||||
},
|
},
|
||||||
main() {
|
main() {
|
||||||
console.log(`[LOG] Initialized astal instance as: ${ App.instanceName || "astal" }`);
|
console.log(`[LOG] Initialized astal instance as: ${ App.instanceName || "astal" }`);
|
||||||
@@ -26,7 +26,6 @@ App.start({
|
|||||||
|
|
||||||
Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () =>
|
Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () =>
|
||||||
!Windows.isVisible(ControlCenter) && triggerOSD(OSDModes.SINK));
|
!Windows.isVisible(ControlCenter) && triggerOSD(OSDModes.SINK));
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -1,14 +1,15 @@
|
|||||||
//TODO use I18n system >.<
|
//TODO use I18n system >.<
|
||||||
|
import en_US from "./lang/en_US";
|
||||||
|
import pt_BR from "./lang/pt_BR";
|
||||||
|
|
||||||
import { GLib } from "astal";
|
import { GLib } from "astal";
|
||||||
|
|
||||||
const i18nKeys = {
|
const i18nKeys = {
|
||||||
"en_US": (() => import("./lang/en_US")!)(),
|
"en_US": en_US,
|
||||||
"pt_BR": (() => import("./lang/pt_BR")!)()
|
"pt_BR": pt_BR
|
||||||
}
|
};
|
||||||
|
|
||||||
const languages: Array<string> = (() =>
|
const languages: Array<string> = Object.keys(i18nKeys);
|
||||||
Object.keys(i18nKeys))()
|
|
||||||
|
|
||||||
const defaultLanguage: string = languages[0];
|
const defaultLanguage: string = languages[0];
|
||||||
let language: string = getSystemLanguage();
|
let language: string = getSystemLanguage();
|
||||||
@@ -17,16 +18,16 @@ export function getSystemLanguage(): string {
|
|||||||
const sysLanguage: (string|null|undefined) = GLib.getenv("LANG") || GLib.getenv("LANGUAGE");
|
const sysLanguage: (string|null|undefined) = GLib.getenv("LANG") || GLib.getenv("LANGUAGE");
|
||||||
|
|
||||||
if(!sysLanguage) {
|
if(!sysLanguage) {
|
||||||
console.log(`[WARNING] Couldn't get system language, fallback to default ${defaultLanguage || "en_US"}`);
|
console.log(`[WARNING] Couldn't get system language, fallback to default ${defaultLanguage}`);
|
||||||
console.log("[TIP] Please set the LANG or LANGUAGE environment variable");
|
console.log("[TIP] Please set the LANG or LANGUAGE environment variable");
|
||||||
|
|
||||||
return defaultLanguage || "en_US";
|
return "en_US";
|
||||||
}
|
}
|
||||||
|
|
||||||
return sysLanguage.split('.')[0];
|
return sysLanguage.split('.')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setLanguage(lang: keyof typeof i18nKeys): (string|Error) {
|
export function setLanguage(lang: keyof typeof i18nKeys): string {
|
||||||
languages.map((cur: string) => {
|
languages.map((cur: string) => {
|
||||||
if(cur === lang) {
|
if(cur === lang) {
|
||||||
language = lang;
|
language = lang;
|
||||||
@@ -34,20 +35,19 @@ export function setLanguage(lang: keyof typeof i18nKeys): (string|Error) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(`[i18n/intl] Couldn't set language: ${lang}`, {
|
throw new Error(`(i18n/intl) Couldn't set language: ${lang}`, {
|
||||||
cause: `Language ${lang} not found in languages of type ${typeof languages}`
|
cause: `Language ${lang} not found in languages of type ${typeof languages}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tr(key: string): (string|undefined) {
|
export function tr(key: string): string {
|
||||||
let result = i18nKeys[language as keyof typeof i18nKeys],
|
let result = i18nKeys[language as keyof typeof i18nKeys],
|
||||||
defResult = i18nKeys[defaultLanguage as keyof typeof i18nKeys];
|
defResult = i18nKeys[defaultLanguage as keyof typeof i18nKeys];
|
||||||
|
|
||||||
for(const keyString in key.split('.')) {
|
for(const keyString in key.split('.')) {
|
||||||
console.log(result);
|
result = result[keyString as keyof typeof result] as never;
|
||||||
result = result[keyString as keyof typeof result];
|
defResult = defResult[keyString as keyof typeof defResult] as never;
|
||||||
defResult = defResult[keyString as keyof typeof defResult];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result as never) || (defResult as never) || undefined;
|
return (result as never) || (defResult as never) || "couldn't find i18n key";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export function updateApps(): void {
|
|||||||
appsList = astalApps.get_list();
|
appsList = astalApps.get_list();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAstalApps(): AstalApps.Apps {
|
||||||
|
return astalApps;
|
||||||
|
}
|
||||||
|
|
||||||
export function getAppsByName(appName: string): (Array<AstalApps.Application>|undefined) {
|
export function getAppsByName(appName: string): (Array<AstalApps.Application>|undefined) {
|
||||||
let found: Array<AstalApps.Application> = [];
|
let found: Array<AstalApps.Application> = [];
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { Gtk } from "astal/gtk3";
|
|||||||
import { Windows } from "../windows";
|
import { Windows } from "../windows";
|
||||||
import { restartInstance } from "./reload-handler";
|
import { restartInstance } from "./reload-handler";
|
||||||
import { Wireplumber } from "./volume";
|
import { Wireplumber } from "./volume";
|
||||||
|
import { startRunnerDefault } from "../window/Runner";
|
||||||
|
import { AskPopup } from "../widget/AskPopup";
|
||||||
|
import { execAsync } from "astal";
|
||||||
|
|
||||||
export function handleArguments(request: string): any {
|
export function handleArguments(request: string): any {
|
||||||
const args: Array<string> = request.split(" ");
|
const args: Array<string> = request.split(" ");
|
||||||
@@ -20,7 +23,18 @@ export function handleArguments(request: string): any {
|
|||||||
|
|
||||||
case "reload":
|
case "reload":
|
||||||
restartInstance();
|
restartInstance();
|
||||||
return "Reloading instance..."
|
return "Restarting instance..."
|
||||||
|
|
||||||
|
case "runner":
|
||||||
|
startRunnerDefault();
|
||||||
|
return "Opening runner..."
|
||||||
|
|
||||||
|
case "test":
|
||||||
|
return AskPopup({
|
||||||
|
onAccept: () => execAsync("notify-send -u normal haha dumb"),
|
||||||
|
text: "Would you accept?",
|
||||||
|
title: "Dumb Question"
|
||||||
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "command not found! try checking help";
|
return "command not found! try checking help";
|
||||||
@@ -143,6 +157,7 @@ Options:
|
|||||||
toggle [window_name]: toggles visibility of specified window.
|
toggle [window_name]: toggles visibility of specified window.
|
||||||
reload: creates a new astal instance and removes this one.
|
reload: creates a new astal instance and removes this one.
|
||||||
volume: wireplumber volume controller, see "volume help".
|
volume: wireplumber volume controller, see "volume help".
|
||||||
|
runner: open the application runner.
|
||||||
help, -h, --help: shows this help message.
|
help, -h, --help: shows this help message.
|
||||||
|
|
||||||
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
|
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { exec, execAsync, GObject, monitorFile, Process, readFileAsync, register, signal } from "astal";
|
import { exec, execAsync, GObject, monitorFile, readFileAsync, register, signal } from "astal";
|
||||||
import { Connectable } from "astal/binding";
|
import { Connectable } from "astal/binding";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
import AstalNotifd from "gi://AstalNotifd";
|
|
||||||
import { timeout } from "astal/time";
|
|
||||||
import { Subscribable } from "astal/binding";
|
|
||||||
import { GObject, property, register, Variable } from "astal";
|
|
||||||
import { Windows } from "../windows";
|
|
||||||
import { FloatingNotifications } from "../window/FloatingNotifications";
|
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
|
||||||
|
|
||||||
@register({ GTypeName: "Notifications" })
|
|
||||||
class NotificationsClass extends GObject.Object implements Subscribable {
|
|
||||||
|
|
||||||
private static instance: NotificationsClass;
|
|
||||||
|
|
||||||
@property(AstalNotifd.Notifd)
|
|
||||||
private notifd: AstalNotifd.Notifd;
|
|
||||||
|
|
||||||
@property(Boolean)
|
|
||||||
private doNotDisturb: boolean = false;
|
|
||||||
|
|
||||||
@property()
|
|
||||||
public notificationHistory: Array<AstalNotifd.Notification> = [];
|
|
||||||
|
|
||||||
@property()
|
|
||||||
public notifications: Variable<Array<AstalNotifd.Notification>> = new Variable<Array<AstalNotifd.Notification>>([]);
|
|
||||||
|
|
||||||
public static getDefault(): NotificationsClass {
|
|
||||||
if(!NotificationsClass.instance) {
|
|
||||||
NotificationsClass.instance = new NotificationsClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotificationsClass.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.notifd = new AstalNotifd.Notifd({
|
|
||||||
ignoreTimeout: true,
|
|
||||||
dontDisturb: false
|
|
||||||
} as AstalNotifd.Notifd.ConstructorProps);
|
|
||||||
|
|
||||||
this.getNotifd().connect("notified", (daemon: AstalNotifd.Notifd, id: number) => {
|
|
||||||
const notification: (AstalNotifd.Notification|null) = daemon.get_notification(id);
|
|
||||||
if(!notification) {
|
|
||||||
console.log("[LOG] Notification is null, ignoring");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.doNotDisturb) {
|
|
||||||
this.handleNotification(notification);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addHistory(notification);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleNotification(notification: AstalNotifd.Notification): void {
|
|
||||||
Windows.open(FloatingNotifications);
|
|
||||||
|
|
||||||
let tmpArray = this.notifications.get().reverse();
|
|
||||||
tmpArray.push(notification);
|
|
||||||
this.notifications.set(tmpArray.reverse());
|
|
||||||
|
|
||||||
// default timeout if undefined
|
|
||||||
let notificationTimeout = 4000;
|
|
||||||
|
|
||||||
switch(notification.urgency) {
|
|
||||||
case AstalNotifd.Urgency.LOW:
|
|
||||||
notificationTimeout = 2000;
|
|
||||||
break;
|
|
||||||
case AstalNotifd.Urgency.NORMAL:
|
|
||||||
notificationTimeout = 4000;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.urgency !== AstalNotifd.Urgency.CRITICAL &&
|
|
||||||
timeout(notificationTimeout, () => {
|
|
||||||
this.notifications.set(this.notifications.get().filter((item) => item.id !== notification.id));
|
|
||||||
this.addHistory(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public addHistory(notification: AstalNotifd.Notification): void {
|
|
||||||
let tmpArray: Array<AstalNotifd.Notification> = this.notificationHistory.reverse()
|
|
||||||
.filter((item: AstalNotifd.Notification) => item.id !== notification.id);
|
|
||||||
tmpArray.push(notification);
|
|
||||||
this.notificationHistory = tmpArray.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeHistory(notification: AstalNotifd.Notification) {
|
|
||||||
this.notificationHistory = this.notificationHistory.filter((curNotification: AstalNotifd.Notification) =>
|
|
||||||
curNotification.id !== notification.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNotifd(): AstalNotifd.Notifd {
|
|
||||||
return this.notifd;
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.notifications.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(callback: (list: Array<AstalNotifd.Notification>) => void) {
|
|
||||||
return this.notifications.subscribe(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function NotificationWidget(notification: AstalNotifd.Notification): Gtk.Widget {
|
|
||||||
return new Widget.Box({
|
|
||||||
className: "notification",
|
|
||||||
homogeneous: false,
|
|
||||||
expand: false,
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
children: [
|
|
||||||
new Widget.Box({
|
|
||||||
className: "top",
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
hexpand: true,
|
|
||||||
vexpand: false,
|
|
||||||
children: [
|
|
||||||
new Widget.Icon({
|
|
||||||
className: "icon",
|
|
||||||
visible: notification.appIcon !== "",
|
|
||||||
icon: notification.appIcon || "image-missing",
|
|
||||||
iconSize: Gtk.IconSize.DND,
|
|
||||||
css: ".icon { font-size: 24px; }"
|
|
||||||
}),
|
|
||||||
new Widget.Label({
|
|
||||||
className: "app-name",
|
|
||||||
halign: Gtk.Align.START,
|
|
||||||
label: notification.appName || "Unknown Application"
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
new Widget.Button({
|
|
||||||
className: "close nf",
|
|
||||||
onClick: () => notification.dismiss(),
|
|
||||||
label: ""
|
|
||||||
} as Widget.ButtonProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
new Widget.Box({
|
|
||||||
className: "content",
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
children: [
|
|
||||||
new Widget.Box({
|
|
||||||
className: "image",
|
|
||||||
visible: notification.image !== "",
|
|
||||||
css: `box.image { background-image: url('${notification.image}'); }`
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
new Widget.Box({
|
|
||||||
className: "text",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
children: [
|
|
||||||
new Widget.Label({
|
|
||||||
className: "summary",
|
|
||||||
useMarkup: true,
|
|
||||||
label: notification.summary
|
|
||||||
}),
|
|
||||||
new Widget.Label({
|
|
||||||
className: "body",
|
|
||||||
useMarkup: true,
|
|
||||||
label: notification.body
|
|
||||||
} as Widget.LabelProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Notifications = new NotificationsClass();
|
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { GObject, property, register, signal, timeout } from "astal";
|
||||||
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
|
|
||||||
|
@register({ GTypeName: "Notifications" })
|
||||||
|
class Notifications extends GObject.Object {
|
||||||
|
private static instance: (Notifications|null) = null;
|
||||||
|
|
||||||
|
#notifications: Array<AstalNotifd.Notification> = [];
|
||||||
|
#history: Array<AstalNotifd.Notification> = [];
|
||||||
|
#connections: Array<number>;
|
||||||
|
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public get notifications() { return this.#notifications };
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public get history() { return this.#history };
|
||||||
|
|
||||||
|
|
||||||
|
@signal(AstalNotifd.Notification)
|
||||||
|
declare notificationAdded: (notification: AstalNotifd.Notification) => void;
|
||||||
|
|
||||||
|
@signal(Number)
|
||||||
|
declare notificationRemoved: (id: number) => void;
|
||||||
|
|
||||||
|
@signal(AstalNotifd.Notification)
|
||||||
|
declare historyAdded: (notification: AstalNotifd.Notification) => void;
|
||||||
|
|
||||||
|
@signal(Number)
|
||||||
|
declare historyRemoved: (id: number) => void;
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#connections = [
|
||||||
|
AstalNotifd.get_default().connect("notified", (notifd, id, _replaced) => {
|
||||||
|
const notification = notifd.get_notification(id);
|
||||||
|
const notifTimeout = 4000;
|
||||||
|
|
||||||
|
this.addNotification(notification, () => {
|
||||||
|
if(notification.urgency !== AstalNotifd.Urgency.CRITICAL)
|
||||||
|
timeout(notifTimeout, () => {
|
||||||
|
this.removeNotification(id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
AstalNotifd.get_default().connect("resolved", (notifd, id, _reason) => {
|
||||||
|
this.removeNotification(id);
|
||||||
|
this.addHistory(notifd.get_notification(id));
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
this.vfunc_dispose = () => {
|
||||||
|
this.#connections.map((id: number) =>
|
||||||
|
AstalNotifd.get_default().disconnect(id));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDefault(): Notifications {
|
||||||
|
if(!this.instance)
|
||||||
|
this.instance = new Notifications();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addHistory(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void {
|
||||||
|
const newArray = this.#history.reverse().filter((item) => item.id !== notif.id);
|
||||||
|
newArray.push(notif);
|
||||||
|
this.#history = newArray.reverse();
|
||||||
|
this.notify("history");
|
||||||
|
this.emit("history-added", notif);
|
||||||
|
onAdded && onAdded(notif);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeHistory(notif: (AstalNotifd.Notification|number)): void {
|
||||||
|
const notifId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif;
|
||||||
|
this.#history = this.#history.filter((item: AstalNotifd.Notification) =>
|
||||||
|
item.id !== notifId);
|
||||||
|
|
||||||
|
this.notify("history");
|
||||||
|
this.emit("history-removed", notifId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addNotification(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void {
|
||||||
|
const newArray = this.#notifications.reverse().filter((item) => item.id !== notif.id);
|
||||||
|
newArray.push(notif);
|
||||||
|
this.#notifications = newArray.reverse();
|
||||||
|
this.notify("notifications");
|
||||||
|
this.emit("notification-added", notif);
|
||||||
|
onAdded && onAdded(notif);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeNotification(notif: (AstalNotifd.Notification|number)): void {
|
||||||
|
const notifId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif;
|
||||||
|
this.#notifications = this.#notifications.filter((item: AstalNotifd.Notification) =>
|
||||||
|
item.id !== notifId);
|
||||||
|
this.notify("notifications");
|
||||||
|
this.emit("notification-removed", notifId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Notifications };
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { execAsync, GLib, GObject, register, signal, writeFile } from "astal";
|
||||||
|
import { Subscribable } from "astal/binding";
|
||||||
|
import { Gdk } from "astal/gtk3";
|
||||||
|
import { getDateTime } from "./time";
|
||||||
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
|
@register({ GTypeName: "ScreenRecording" })
|
||||||
|
class Recording extends GObject.Object implements Subscribable {
|
||||||
|
|
||||||
|
private static instance: Recording;
|
||||||
|
|
||||||
|
@signal()
|
||||||
|
declare started: () => void;
|
||||||
|
@signal(String)
|
||||||
|
declare stopped: (outputFile: string) => void;
|
||||||
|
@signal(String)
|
||||||
|
declare outputChanged: (newPath: string) => void;
|
||||||
|
|
||||||
|
#recording: boolean = false;
|
||||||
|
#subs = new Set<(isRec: boolean) => void>();
|
||||||
|
#path: string = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS) || `${GLib.get_home_dir()}/Recordings`;
|
||||||
|
/** Default extension: mp4(h264) */
|
||||||
|
#extension: string = "mp4";
|
||||||
|
#recordAudio: boolean|AstalWp.Endpoint = false; // TODO
|
||||||
|
|
||||||
|
private notifySub() {
|
||||||
|
const subs = this.#subs;
|
||||||
|
for(const sub of subs) {
|
||||||
|
sub(this.recording);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get recording() { return this.#recording; }
|
||||||
|
private set recording(newValue: boolean) { this.#recording = newValue; }
|
||||||
|
|
||||||
|
public get path() { return this.#path; }
|
||||||
|
public set path(newPath: string) { this.#path = newPath; }
|
||||||
|
|
||||||
|
public get extension() { return this.#extension; }
|
||||||
|
public set extension(newExt: string) { this.#extension = newExt; }
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDefault() {
|
||||||
|
if(!this.instance)
|
||||||
|
this.instance = new Recording();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get() {
|
||||||
|
return this.recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit(id: string, ...args: any[]) {
|
||||||
|
super.emit(id, ...args);
|
||||||
|
this.notifySub();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public startRecording(area?: Gdk.Rectangle) {
|
||||||
|
const output = `${getDateTime().get().format("%Y-%m-%d-%H%M%S")}_rec.${this.extension}`;
|
||||||
|
execAsync([ "wf-recorder",
|
||||||
|
`${Boolean(area) ?
|
||||||
|
`-g ${area?.x || 0},${area?.y || 0} ${area?.width || 1}x${area?.height || 1}`
|
||||||
|
: ""}`,
|
||||||
|
"-f", output ]
|
||||||
|
).then(() => {
|
||||||
|
this.emit("stopped", `${this.path}/${output}`);
|
||||||
|
});
|
||||||
|
writeFile("", "");
|
||||||
|
this.emit("started");
|
||||||
|
this.notifySub();
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopRecording() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(callback: (isRec: boolean) => void) {
|
||||||
|
this.#subs.add(callback);
|
||||||
|
return () => this.#subs.delete(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Recording };
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
import { getAstalApps } from "../apps";
|
||||||
|
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||||
|
import AstalApps from "gi://AstalApps";
|
||||||
|
|
||||||
|
export function handleApplications(search: string): (Array<ResultWidget>|null) {
|
||||||
|
return getAstalApps().fuzzy_query(search).map((app: AstalApps.Application) =>
|
||||||
|
new ResultWidget({
|
||||||
|
title: app.get_name(),
|
||||||
|
description: app.get_description(),
|
||||||
|
icon: app.iconName,
|
||||||
|
onClick: () => AstalHyprland.get_default().dispatch("exec", app.get_executable())
|
||||||
|
} as ResultWidgetProps)
|
||||||
|
) || null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
import { GLib } from "astal";
|
||||||
|
|
||||||
|
export function handleShell(command: string): ResultWidget {
|
||||||
|
const userShell = GLib.getenv("SHELL") || "/usr/bin/env bash";
|
||||||
|
|
||||||
|
return new ResultWidget({
|
||||||
|
onClick: () => AstalHyprland.get_default().dispatch("exec", `${userShell} -c "${command}"`),
|
||||||
|
title: `Run: \`${command}\``,
|
||||||
|
description: userShell,
|
||||||
|
icon: "utilities-terminal-symbolic"
|
||||||
|
} as ResultWidgetProps);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||||
|
|
||||||
|
export enum SearchEngine {
|
||||||
|
GOOGLE,
|
||||||
|
DUCKDUCKGO,
|
||||||
|
YAHOO
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchEngineMap: Map<SearchEngine, string> = new Map([
|
||||||
|
[ SearchEngine.DUCKDUCKGO, "https://duckduckgo.com/?q=" ],
|
||||||
|
[ SearchEngine.GOOGLE, "https://google.com/search?q=" ],
|
||||||
|
[ SearchEngine.YAHOO, "https://search.yahoo.com/search?p=" ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
let searchEngine: SearchEngine = SearchEngine.GOOGLE;
|
||||||
|
|
||||||
|
export function handleWebSearch(search: string): ResultWidget {
|
||||||
|
|
||||||
|
let engineString: string;
|
||||||
|
|
||||||
|
switch(searchEngine as SearchEngine) {
|
||||||
|
case SearchEngine.GOOGLE:
|
||||||
|
engineString = "Google";
|
||||||
|
case SearchEngine.YAHOO:
|
||||||
|
engineString = "Yahoo";
|
||||||
|
case SearchEngine.DUCKDUCKGO:
|
||||||
|
engineString = "DuckDuckGo";
|
||||||
|
default: engineString = "Web";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResultWidget({
|
||||||
|
icon: "system-search-symbolic",
|
||||||
|
title: search || "",
|
||||||
|
description: `Search with ${engineString}`,
|
||||||
|
onClick: () => AstalHyprland.get_default().dispatch(
|
||||||
|
"exec",
|
||||||
|
`xdg-open "${SearchEngineMap.get(searchEngine)! + search.replaceAll(" ", "%20")}"`
|
||||||
|
)
|
||||||
|
} as ResultWidgetProps);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
// handles reloading stylesheet and pywal colors
|
// handles reloading stylesheet and pywal colors
|
||||||
|
|
||||||
import { readFile, monitorFile, Process } from "astal";
|
import { readFile, monitorFile, AstalIO, exec, timeout } from "astal";
|
||||||
import { App } from "astal/gtk3";
|
import { App } from "astal/gtk3";
|
||||||
import { getUserDirs } from "./user";
|
import { getUserDirs } from "./utils";
|
||||||
|
|
||||||
|
let watchDelay: (AstalIO.Time|null);
|
||||||
|
|
||||||
const stylePath = `${getUserDirs().state}/ags/style`
|
const stylePath = `${getUserDirs().state}/ags/style`
|
||||||
const watchPaths = [
|
const watchPaths = [
|
||||||
@@ -22,8 +24,8 @@ export function reloadStyle(): void {
|
|||||||
|
|
||||||
export function compileStyle(): void {
|
export function compileStyle(): void {
|
||||||
console.log("[LOG] Compiling sass (stylesheet)");
|
console.log("[LOG] Compiling sass (stylesheet)");
|
||||||
Process.exec(`mkdir -p ${stylePath}`);
|
exec(`mkdir -p ${stylePath}`);
|
||||||
Process.exec(`sh -c "sass -I ./style ./style.scss ${stylePath}/style.css"`);
|
exec(`sh -c "sass -I ./style ./style.scss ${stylePath}/style.css"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyStyle(): void {
|
export function applyStyle(): void {
|
||||||
@@ -34,14 +36,15 @@ export function applyStyle(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Monitor changes on stylesheet at runtime */
|
||||||
function watch(): void {
|
function watch(): void {
|
||||||
// Monitor changes on stylesheet at runtime
|
|
||||||
watchPaths.map((path: string) =>
|
watchPaths.map((path: string) =>
|
||||||
monitorFile(
|
monitorFile(
|
||||||
`${path}`,
|
`${path}`,
|
||||||
(file: string) => {
|
(file: string) => {
|
||||||
// Ignore tmp files
|
// Ignore tmp files
|
||||||
if(!file.endsWith('~') && !Number.isNaN(file)) {
|
if(!watchDelay && !file.endsWith('~') && !Number.isNaN(file)) {
|
||||||
|
watchDelay = timeout(250, () => watchDelay = null);
|
||||||
console.log(`[LOG] Stylesheet ${file} file updated`)
|
console.log(`[LOG] Stylesheet ${file} file updated`)
|
||||||
compileStyle();
|
compileStyle();
|
||||||
applyStyle();
|
applyStyle();
|
||||||
@@ -54,7 +57,7 @@ function watch(): void {
|
|||||||
monitorFile(
|
monitorFile(
|
||||||
`${getUserDirs().cache}/wal/colors.scss`,
|
`${getUserDirs().cache}/wal/colors.scss`,
|
||||||
(file: string) => {
|
(file: string) => {
|
||||||
Process.exec(`bash -c "cp -f ${file} ./style/_wal.scss"`)
|
exec(`bash -c "cp -f ${file} ./style/_wal.scss"`)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GLib } from "astal";
|
import { execAsync, GLib } from "astal";
|
||||||
|
|
||||||
export function getUserDirs() {
|
export function getUserDirs() {
|
||||||
return {
|
return {
|
||||||
@@ -7,5 +7,13 @@ export function getUserDirs() {
|
|||||||
cache: GLib.getenv("XDG_CACHE_HOME"),
|
cache: GLib.getenv("XDG_CACHE_HOME"),
|
||||||
config: GLib.getenv("XDG_CONFIG_HOME"),
|
config: GLib.getenv("XDG_CONFIG_HOME"),
|
||||||
data: GLib.getenv("XDG_DATA_HOME")
|
data: GLib.getenv("XDG_DATA_HOME")
|
||||||
} as const;
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeDirectory(dir: string): void {
|
||||||
|
execAsync([ "mkdir", "-p", dir ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteFile(path: string): void {
|
||||||
|
execAsync([ "rm", "-r", path ]);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Subscribable } from "astal/binding";
|
||||||
|
|
||||||
|
export class VarMap<K, V> implements Subscribable {
|
||||||
|
|
||||||
|
#subs = new Set<(v: Map<K, V>) => void>();
|
||||||
|
#map: Map<K, V>;
|
||||||
|
|
||||||
|
constructor(initial?: Map<K, V>) {
|
||||||
|
this.#map = initial || new Map<K, V>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyMap() {
|
||||||
|
const subs = this.#subs;
|
||||||
|
for(const sub of subs) {
|
||||||
|
sub(this.#map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(): Map<K, V> {
|
||||||
|
return this.#map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get size(): number {
|
||||||
|
return this.#map.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValue(key: K): (V|undefined) {
|
||||||
|
return this.#map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeyAt(index: number): (K|undefined) {
|
||||||
|
return [...this.#map.keys()][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValueAt(index: number): (V|undefined) {
|
||||||
|
return [...this.#map.values()][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(key: K, value: V): Map<K, V> {
|
||||||
|
const newMap: Map<K, V> = this.#map.set(key, value);
|
||||||
|
this.notifyMap();
|
||||||
|
|
||||||
|
return newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(key: K): boolean {
|
||||||
|
const deleted: boolean = this.#map.delete(key);
|
||||||
|
this.notifyMap();
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(key: K): boolean {
|
||||||
|
return this.#map.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.#map.clear();
|
||||||
|
this.notifyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public entries(): MapIterator<[K, V]> {
|
||||||
|
return this.#map.entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
public keys(): MapIterator<K> {
|
||||||
|
return this.#map.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public values(): MapIterator<V> {
|
||||||
|
return this.#map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public forEach<ReturnType = any> (callback: (value: V, key: K, map: Map<K, V>) => ReturnType): ReturnType[] {
|
||||||
|
const result: Array<ReturnType> = [];
|
||||||
|
for(const entry of this.#map.entries()) {
|
||||||
|
result.push(callback(entry[1], entry[0], this.#map));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(callback: (v: Map<K, V>) => void): () => void {
|
||||||
|
this.#subs.add(callback);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.#subs.delete(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+130
@@ -9,6 +9,9 @@
|
|||||||
@use "./style/control-center";
|
@use "./style/control-center";
|
||||||
@use "./style/center-window";
|
@use "./style/center-window";
|
||||||
@use "./style/float-notifications";
|
@use "./style/float-notifications";
|
||||||
|
@use "./style/logout-menu";
|
||||||
|
@use "./style/apps-window";
|
||||||
|
@use "./style/runner";
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -19,6 +22,96 @@ window * {
|
|||||||
@include mixins.default-styles;
|
@include mixins.default-styles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.ask-popup {
|
||||||
|
background: rgba(black, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ask-popup-box {
|
||||||
|
background: colors.$bg-translucent;
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: 24px;
|
||||||
|
|
||||||
|
& .title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .buttons {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
& button {
|
||||||
|
background: colors.$bg-primary;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
margin: {
|
||||||
|
left: 4px;
|
||||||
|
right: 4px;
|
||||||
|
};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: colors.$bg-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background: colors.$bg-primary;
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
& > .top {
|
||||||
|
padding: 8px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
& .app-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .app-name {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& button.close {
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: colors.$bg-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& icon.close {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .content {
|
||||||
|
padding: 4px;
|
||||||
|
padding-top: 0;
|
||||||
|
& .image {
|
||||||
|
$size: 78px;
|
||||||
|
min-width: $size;
|
||||||
|
min-height: $size;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
margin: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .summary {
|
||||||
|
font-size: 17.3px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .body {
|
||||||
|
font-size: 14.5px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tooltip {
|
tooltip {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
@@ -32,3 +125,40 @@ tooltip {
|
|||||||
box-shadow: 0 1px 4px 1px rgba(colors.$bg-primary, .6);
|
box-shadow: 0 1px 4px 1px rgba(colors.$bg-primary, .6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu {
|
||||||
|
padding: 4px;
|
||||||
|
background: wal.$background;
|
||||||
|
border-radius: 14px;
|
||||||
|
|
||||||
|
& separator {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: wal.$background;
|
||||||
|
}
|
||||||
|
|
||||||
|
& menuitem {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: wal.$color1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar trough {
|
||||||
|
@include mixins.reset-props;
|
||||||
|
background: colors.$bg-translucent;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
padding: 0 2px;
|
||||||
|
|
||||||
|
& slider {
|
||||||
|
@include mixins.reset-props;
|
||||||
|
min-width: .85em;
|
||||||
|
background: colors.$bg-tertiary;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.apps-window.container {
|
||||||
|
& > entry {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
& > .content {
|
& > .content {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
padding-right: 0;
|
|
||||||
& > .icon {
|
& > .icon {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > .description {
|
& > .description {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
|
color: colors.$fg-disabled;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,3 +124,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pages > .page {
|
||||||
|
background: colors.$bg-secondary;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 24px;
|
||||||
|
|
||||||
|
& .header {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
& > .title:first-child {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .description {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: colors.$fg-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bluetooth {
|
||||||
|
.connections button {
|
||||||
|
@include mixins.hover-shadow;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
&.connected {
|
||||||
|
background: colors.$bg-tertiary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@use "./colors";
|
@use "./colors";
|
||||||
|
@use "./mixins";
|
||||||
|
|
||||||
.floating-notifications-container {
|
.floating-notifications-container {
|
||||||
padding: {
|
padding: {
|
||||||
@@ -6,28 +7,11 @@
|
|||||||
top: 6px;
|
top: 6px;
|
||||||
};
|
};
|
||||||
|
|
||||||
& > .notification {
|
& revealer {
|
||||||
background: colors.$bg-primary;
|
padding: 6px;
|
||||||
border-radius: 16px;
|
|
||||||
padding: 12px;
|
|
||||||
margin: 6px 0;
|
|
||||||
|
|
||||||
& > .top {
|
& > .notification {
|
||||||
& .app-name {
|
box-shadow: 0 0 4px .5px colors.$bg-translucent;
|
||||||
font-size: 12px;
|
|
||||||
color: colors.$fg-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .content {
|
|
||||||
& .image {
|
|
||||||
$size: 78px;
|
|
||||||
min-width: $size;
|
|
||||||
min-height: $size;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center 0;
|
|
||||||
margin: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.logout-menu {
|
||||||
|
.top {
|
||||||
|
.time {
|
||||||
|
font-size: 128px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-row {
|
||||||
|
margin: 0 150px;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
& label {
|
||||||
|
font-size: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin: {
|
||||||
|
left: 4px;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 28px;
|
||||||
|
border-bottom-left-radius: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: 28px;
|
||||||
|
border-bottom-right-radius: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-22
@@ -14,6 +14,12 @@
|
|||||||
color: colors.$fg-primary;
|
color: colors.$fg-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin hover-shadow {
|
||||||
|
&:hover {
|
||||||
|
box-shadow: inset 0 0 0 500px rgba(colors.$fg-primary, .1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin default-styles {
|
@mixin default-styles {
|
||||||
.button-row {
|
.button-row {
|
||||||
& > button {
|
& > button {
|
||||||
@@ -38,6 +44,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selection {
|
||||||
|
background: colors.$bg-tertiary;
|
||||||
|
}
|
||||||
|
|
||||||
label.nf,
|
label.nf,
|
||||||
button.nf label {
|
button.nf label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -47,28 +57,6 @@
|
|||||||
"Font Awesome";
|
"Font Awesome";
|
||||||
}
|
}
|
||||||
|
|
||||||
& menu {
|
|
||||||
padding: 4px;
|
|
||||||
background: wal.$background;
|
|
||||||
border-radius: 14px;
|
|
||||||
|
|
||||||
& separator {
|
|
||||||
margin: 0 4px;
|
|
||||||
color: wal.$background;
|
|
||||||
}
|
|
||||||
|
|
||||||
& menuitem {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: wal.$color1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& trough {
|
& trough {
|
||||||
background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%));
|
background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
@use "./colors";
|
||||||
|
|
||||||
|
.runner.main {
|
||||||
|
background: colors.$bg-translucent;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 24px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& entry {
|
||||||
|
background: colors.$bg-primary;
|
||||||
|
padding: 10px 9px;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 2px colors.$bg-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
& image.left {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& list {
|
||||||
|
& eventbox:focus > box,
|
||||||
|
& eventbox:hover > box,
|
||||||
|
& listboxchild:selected eventbox > box,
|
||||||
|
& listboxchild:active eventbox > box {
|
||||||
|
background: colors.$bg-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& list eventbox > .result {
|
||||||
|
padding: 10px;
|
||||||
|
background: colors.$bg-primary;
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 14px;
|
||||||
|
|
||||||
|
& icon {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: colors.$fg-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .not-found {
|
||||||
|
padding-top: 24px;
|
||||||
|
|
||||||
|
& icon {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: .4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& label {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-20
@@ -1,26 +1,26 @@
|
|||||||
// SCSS Variables
|
// SCSS Variables
|
||||||
// Generated by 'wal'
|
// Generated by 'wal'
|
||||||
$wallpaper: "/home/joaov/wallpapers/Bocchi The Rock!.png";
|
$wallpaper: "/home/joaov/wallpapers/Miku Guitar.jpg";
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
$background: #0a0a0c;
|
$background: #171418;
|
||||||
$foreground: #c1c1c2;
|
$foreground: #c5c4c5;
|
||||||
$cursor: #c1c1c2;
|
$cursor: #c5c4c5;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
$color0: #0a0a0c;
|
$color0: #171418;
|
||||||
$color1: #935d6d;
|
$color1: #607985;
|
||||||
$color2: #967e84;
|
$color2: #208FB6;
|
||||||
$color3: #ac8486;
|
$color3: #4C9CB4;
|
||||||
$color4: #bcae7a;
|
$color4: #63ADC9;
|
||||||
$color5: #a49c9c;
|
$color5: #C3B49C;
|
||||||
$color6: #bcb79c;
|
$color6: #89BBCF;
|
||||||
$color7: #8a8a96;
|
$color7: #96909b;
|
||||||
$color8: #565669;
|
$color8: #715c71;
|
||||||
$color9: #C57C92;
|
$color9: #6aa4bf;
|
||||||
$color10: #C9A9B0;
|
$color10: #62a5bc;
|
||||||
$color11: #E6B1B3;
|
$color11: #78b3c5;
|
||||||
$color12: #FBE8A3;
|
$color12: #90becf;
|
||||||
$color13: #DBD1D0;
|
$color13: #dac7ab;
|
||||||
$color14: #FBF5D1;
|
$color14: #a7cbd9;
|
||||||
$color15: #c1c1c2;
|
$color15: #c5c4c5;
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"module": "ES2022",
|
"module": "ES2022",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"allowJs": true,
|
"allowJs": false,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "astal/gtk3"
|
"jsxImportSource": "astal/gtk3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { Binding } from "astal";
|
||||||
|
import { PopupWindow, PopupWindowProps } from "./PopupWindow";
|
||||||
|
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||||
|
import { Separator } from "./Separator";
|
||||||
|
|
||||||
|
export type AskPopupProps = {
|
||||||
|
title?: string | Binding<string | undefined>;
|
||||||
|
text: string | Binding<string | undefined>;
|
||||||
|
cancelText?: string;
|
||||||
|
acceptText?: string;
|
||||||
|
onAccept: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Popup Widget that asks yes or no to a certain question.
|
||||||
|
* Runs onAccept() when user accepts or else onDecline() when
|
||||||
|
* user doesn't accept or closes window.
|
||||||
|
*/
|
||||||
|
export function AskPopup(props: AskPopupProps) {
|
||||||
|
const buttons = [
|
||||||
|
new Widget.Button({
|
||||||
|
className: "cancel",
|
||||||
|
hexpand: true,
|
||||||
|
label: props.cancelText || "Cancel",
|
||||||
|
onClick: (_) => {
|
||||||
|
window.destroy();
|
||||||
|
props.onCancel && props.onCancel();
|
||||||
|
}
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "accept",
|
||||||
|
hexpand: true,
|
||||||
|
label: props.acceptText || "Ok",
|
||||||
|
onClick: (_) => {
|
||||||
|
window.destroy();
|
||||||
|
props.onAccept && props.onAccept();
|
||||||
|
}
|
||||||
|
} as Widget.ButtonProps)
|
||||||
|
];
|
||||||
|
|
||||||
|
const window = PopupWindow({
|
||||||
|
namespace: "ask-popup",
|
||||||
|
visible: true,
|
||||||
|
className: "ask-popup",
|
||||||
|
exclusivity: Astal.Exclusivity.IGNORE,
|
||||||
|
widthRequest: 350,
|
||||||
|
heightRequest: 200,
|
||||||
|
onClose: (_) => {
|
||||||
|
props.onCancel && props.onCancel();
|
||||||
|
_.destroy();
|
||||||
|
},
|
||||||
|
child: new Widget.Box({
|
||||||
|
className: "ask-popup-box",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Label({
|
||||||
|
className: "title",
|
||||||
|
visible: Boolean(props.title),
|
||||||
|
label: props.title || ""
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
Separator({
|
||||||
|
alpha: .2,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL
|
||||||
|
}),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "text",
|
||||||
|
label: props.text,
|
||||||
|
yalign: 0,
|
||||||
|
expand: true
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "buttons",
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
hexpand: true,
|
||||||
|
heightRequest: 38,
|
||||||
|
homogeneous: true,
|
||||||
|
children: buttons
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as PopupWindowProps);
|
||||||
|
}
|
||||||
+33
-10
@@ -1,5 +1,4 @@
|
|||||||
//TODO Needs more work
|
import { register, Variable } from "astal";
|
||||||
|
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
|
|
||||||
type CalendarProps = Pick<Widget.BoxProps,
|
type CalendarProps = Pick<Widget.BoxProps,
|
||||||
@@ -10,14 +9,38 @@ type CalendarProps = Pick<Widget.BoxProps,
|
|||||||
| "halign"
|
| "halign"
|
||||||
| "valign"> & {
|
| "valign"> & {
|
||||||
|
|
||||||
showWeekDays: boolean;
|
showWeekDays?: boolean;
|
||||||
showHeader: boolean;
|
showHeader?: boolean;
|
||||||
fillGrid: boolean; // I need a better name for this LMAOOO
|
fillGrid?: boolean; // I need a better name for this LMAOOO
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Calendar(props?: Partial<CalendarProps>): Gtk.Widget {
|
@register({ GTypeName: "Calendar" })
|
||||||
return new Widget.Box({
|
class Calendar extends Gtk.Box {
|
||||||
...props,
|
#showWeekDays = new Variable<boolean>(true);
|
||||||
children: []
|
#showHeader = new Variable<boolean>(true);
|
||||||
} as Widget.BoxProps);
|
#fillGrid = new Variable<boolean>(false);
|
||||||
|
|
||||||
|
set fillGrid(newValue: boolean) { this.#fillGrid.set(newValue); }
|
||||||
|
get fillGrid() { return this.#fillGrid.get(); }
|
||||||
|
set showHeader(newValue: boolean) { this.#showHeader.set(newValue); }
|
||||||
|
get showHeader() { return this.#showHeader.get(); }
|
||||||
|
set showWeekDays(newValue: boolean) { this.#showWeekDays.set(newValue); }
|
||||||
|
get showWeekDays() { return this.#showWeekDays.get(); }
|
||||||
|
|
||||||
|
constructor(props?: CalendarProps) {
|
||||||
|
super();
|
||||||
|
this.add(new Widget.Box({
|
||||||
|
...props,
|
||||||
|
widthRequest: 128,
|
||||||
|
heightRequest: 128,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "header",
|
||||||
|
heightRequest: 24,
|
||||||
|
hexpand: true,
|
||||||
|
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { astalify, Gtk } from "astal/gtk3";
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
export class FlowBox extends astalify(Gtk.FlowBox) {}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||||
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
|
import { Separator } from "./Separator";
|
||||||
|
|
||||||
|
export function getUrgencyString(notif: AstalNotifd.Notification) {
|
||||||
|
switch(notif.urgency) {
|
||||||
|
case AstalNotifd.Urgency.LOW:
|
||||||
|
return "low";
|
||||||
|
case AstalNotifd.Urgency.CRITICAL:
|
||||||
|
return "critical";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "normal";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotificationWidget(notification: AstalNotifd.Notification|number,
|
||||||
|
onClose?: (notif: AstalNotifd.Notification) => void): Gtk.Widget {
|
||||||
|
|
||||||
|
notification = (notification instanceof AstalNotifd.Notification) ?
|
||||||
|
notification
|
||||||
|
: AstalNotifd.get_default().get_notification(notification);
|
||||||
|
|
||||||
|
return new Widget.Box({
|
||||||
|
className: `notification ${getUrgencyString(notification)}`,
|
||||||
|
homogeneous: false,
|
||||||
|
expand: false,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "top",
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
hexpand: true,
|
||||||
|
vexpand: false,
|
||||||
|
children: [
|
||||||
|
new Widget.Icon({
|
||||||
|
className: "icon app-icon",
|
||||||
|
icon: Astal.Icon.lookup_icon(notification.appIcon) ?
|
||||||
|
notification.appIcon
|
||||||
|
: (Astal.Icon.lookup_icon(notification.appName.toLowerCase()) ?
|
||||||
|
notification.appName.toLowerCase()
|
||||||
|
: "image-missing"
|
||||||
|
),
|
||||||
|
setup: (_) => _.get_icon() === "image-missing" &&
|
||||||
|
_.set_visible(false),
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
css: "font-size: 16px;"
|
||||||
|
}),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "app-name",
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
hexpand: true,
|
||||||
|
label: notification.appName || "Unknown Application"
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "close nf",
|
||||||
|
halign: Gtk.Align.END,
|
||||||
|
onClick: () => onClose && onClose(notification),
|
||||||
|
image: new Widget.Icon({
|
||||||
|
className: "close icon",
|
||||||
|
icon: "window-close-symbolic"
|
||||||
|
} as Widget.IconProps)
|
||||||
|
} as Widget.ButtonProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
Separator({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
alpha: 10
|
||||||
|
}),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "content",
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "image",
|
||||||
|
visible: Boolean(notification.image),
|
||||||
|
css: `box.image { background-image: url('${notification.image}'); }`
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "text",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Label({
|
||||||
|
className: "summary",
|
||||||
|
useMarkup: true,
|
||||||
|
xalign: 0,
|
||||||
|
truncate: true,
|
||||||
|
label: notification.summary
|
||||||
|
}),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "body",
|
||||||
|
useMarkup: true,
|
||||||
|
xalign: 0,
|
||||||
|
expand: true,
|
||||||
|
wrap: true,
|
||||||
|
label: notification.body
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
}
|
||||||
+46
-29
@@ -4,33 +4,40 @@ import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
|||||||
|
|
||||||
const { TOP, BOTTOM, LEFT, RIGHT }: typeof Astal.WindowAnchor = Astal.WindowAnchor;
|
const { TOP, BOTTOM, LEFT, RIGHT }: typeof Astal.WindowAnchor = Astal.WindowAnchor;
|
||||||
|
|
||||||
export interface PopupWindowProps {
|
export type PopupWindowProps = Pick<Widget.WindowProps,
|
||||||
className?: string | Binding<string | undefined>;
|
"namespace"
|
||||||
namespace: string | Binding<string | undefined>;
|
| "visible"
|
||||||
visible?: boolean | Binding<boolean | undefined>;
|
| "className"
|
||||||
halign?: Gtk.Align | Binding<Gtk.Align | undefined>;
|
| "hexpand"
|
||||||
valign?: Gtk.Align | Binding<Gtk.Align | undefined>;
|
| "vexpand"
|
||||||
hexpand?: boolean | Binding<boolean | undefined>;
|
| "halign"
|
||||||
vexpand?: boolean | Binding<boolean | undefined>;
|
| "valign"
|
||||||
expand?: boolean | Binding<boolean | undefined>;
|
| "expand"
|
||||||
monitor?: number | Binding<number | undefined>;
|
| "layer"
|
||||||
marginTop?: number | Binding<number | undefined>;
|
| "widthRequest"
|
||||||
marginBottom?: number | Binding<number | undefined>;
|
| "heightRequest"
|
||||||
marginLeft?: number | Binding<number | undefined>;
|
| "child"
|
||||||
marginRight?: number | Binding<number | undefined>;
|
| "monitor"
|
||||||
widthRequest?: number | Binding<number | undefined>;
|
| "exclusivity"> & {
|
||||||
heightRequest?: number | Binding<number | undefined>;
|
marginTop?: number;
|
||||||
layer?: Astal.Layer | Binding<Astal.Layer | undefined>;
|
marginLeft?: number;
|
||||||
onClose?: () => void;
|
marginBottom?: number;
|
||||||
child: Gtk.Widget;
|
marginRight?: number;
|
||||||
}
|
onKeyPressEvent?: (self: Widget.Window, event: Gdk.Event) => void;
|
||||||
|
/** Do something else instead of hiding window on close action(clicking outside conent / pressing Escape)
|
||||||
|
* Observation: onClose() function will still be ran after close action if defined.
|
||||||
|
*/
|
||||||
|
closeAction?: (self: Widget.Window) => void;
|
||||||
|
onClose?: (self: Widget.Window) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||||
return new Widget.Window({
|
return new Widget.Window({
|
||||||
namespace: props?.namespace || "popup-window",
|
namespace: props?.namespace || "popup-window",
|
||||||
className: "popup-window",
|
className: `popup-window ${(props.namespace instanceof Binding ?
|
||||||
|
props.namespace.get() : props.namespace) || ""}`,
|
||||||
anchor: TOP | BOTTOM | LEFT | RIGHT,
|
anchor: TOP | BOTTOM | LEFT | RIGHT,
|
||||||
exclusivity: Astal.Exclusivity.NORMAL,
|
exclusivity: props.exclusivity || Astal.Exclusivity.NORMAL,
|
||||||
keymode: Astal.Keymode.EXCLUSIVE,
|
keymode: Astal.Keymode.EXCLUSIVE,
|
||||||
layer: props?.layer || Astal.Layer.OVERLAY,
|
layer: props?.layer || Astal.Layer.OVERLAY,
|
||||||
focusOnMap: true,
|
focusOnMap: true,
|
||||||
@@ -44,11 +51,18 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
|||||||
if((posX < childAllocation.x || posX > (childAllocation.x + childAllocation.width)) ||
|
if((posX < childAllocation.x || posX > (childAllocation.x + childAllocation.width)) ||
|
||||||
(posY < childAllocation.y || posY > (childAllocation.y + childAllocation.height))) {
|
(posY < childAllocation.y || posY > (childAllocation.y + childAllocation.height))) {
|
||||||
_.hide();
|
_.hide();
|
||||||
props?.onClose && props.onClose();
|
props?.onClose && props.onClose(_);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyPressEvent: (_, event: Gdk.Event) =>
|
onKeyPressEvent: (_, event: Gdk.Event) => {
|
||||||
event.get_keyval()[1] === Gdk.KEY_Escape && _.hide(),
|
if(event.get_keyval()[1] === Gdk.KEY_Escape) {
|
||||||
|
!props.closeAction ? _.hide() : props.closeAction(_);
|
||||||
|
props.onClose && props.onClose(_);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.onKeyPressEvent &&
|
||||||
|
props.onKeyPressEvent(_, event);
|
||||||
|
},
|
||||||
child: new Widget.Box({
|
child: new Widget.Box({
|
||||||
className: (props?.className instanceof Binding) ?
|
className: (props?.className instanceof Binding) ?
|
||||||
props.className.as((clsName: string|undefined) =>
|
props.className.as((clsName: string|undefined) =>
|
||||||
@@ -62,10 +76,13 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
|||||||
hexpand: props?.hexpand || false,
|
hexpand: props?.hexpand || false,
|
||||||
visible: true,
|
visible: true,
|
||||||
vexpand: props?.vexpand || false,
|
vexpand: props?.vexpand || false,
|
||||||
marginTop: props?.marginTop || 0,
|
css: `.popup {
|
||||||
marginBottom: props?.marginBottom || 0,
|
margin-top: ${props.marginTop || 0}px;
|
||||||
marginLeft: props?.marginLeft || 0,
|
margin-bottom: ${props.marginBottom || 0}px;
|
||||||
marginRight: props?.marginRight || 0,
|
margin-left: ${props.marginLeft || 0}px;
|
||||||
|
margin-right: ${props.marginRight || 0}px;
|
||||||
|
}`,
|
||||||
|
onButtonPressEvent: () => true,
|
||||||
child: props.child
|
child: props.child
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
} as Widget.WindowProps);;
|
} as Widget.WindowProps);;
|
||||||
|
|||||||
+11
-4
@@ -11,19 +11,26 @@ export interface SeparatorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Separator(props: SeparatorProps) {
|
export function Separator(props: SeparatorProps) {
|
||||||
|
const alpha: number = props.alpha ?
|
||||||
|
(props.alpha > 1) ?
|
||||||
|
props.alpha / 100
|
||||||
|
: props.alpha
|
||||||
|
: 1;
|
||||||
|
|
||||||
return new Widget.Box({
|
return new Widget.Box({
|
||||||
className: `separator separator-${ props.orientation == Gtk.Orientation.VERTICAL ? "vertical" : "horizontal" } ${ props.class && props.class }`,
|
className: `separator separator-${ props.orientation == Gtk.Orientation.VERTICAL ?
|
||||||
|
"vertical" : "horizontal" } ${ props.class && props.class }`,
|
||||||
visible: props.visible,
|
visible: props.visible,
|
||||||
css: `.separator {
|
css: `.separator {
|
||||||
background: ${ props.cssColor || "lightgray" };
|
background: ${ props.cssColor || "lightgray" };
|
||||||
opacity: ${ props.alpha || 1 };
|
opacity: ${alpha};
|
||||||
}
|
}
|
||||||
.separator-horizontal {
|
.separator-horizontal {
|
||||||
padding-bottom: ${props.size || 1 }px;
|
min-width: ${ props.size || 1 }px;
|
||||||
margin: 4px 4px;
|
margin: 4px 4px;
|
||||||
}
|
}
|
||||||
.separator-vertical {
|
.separator-vertical {
|
||||||
padding-right: ${props.size || 1 }px;
|
min-height: ${ props.size || 1 }px;
|
||||||
margin: 7px 7px;
|
margin: 7px 7px;
|
||||||
}`,
|
}`,
|
||||||
} as Widget.BoxProps);
|
} as Widget.BoxProps);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { bind, Process } from "astal";
|
import { bind, Process } from "astal";
|
||||||
import { Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import { Wireplumber } from "../../scripts/volume";
|
import { Wireplumber } from "../../scripts/volume";
|
||||||
import { ControlCenter } from "../../window/ControlCenter";
|
import { ControlCenter } from "../../window/ControlCenter";
|
||||||
|
|
||||||
export function Audio() {
|
export function Audio(): Gtk.Widget {
|
||||||
return new Widget.EventBox({
|
return new Widget.EventBox({
|
||||||
className: bind(ControlCenter, "visible").as((visible: boolean) =>
|
className: bind(ControlCenter, "visible").as((visible: boolean) =>
|
||||||
visible ? "audio open" : "audio"),
|
visible ? "audio open" : "audio"),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import { getDateTime } from "../../scripts/time";
|
import { getDateTime } from "../../scripts/time";
|
||||||
import { bind, GLib } from "astal";
|
import { bind, GLib } from "astal";
|
||||||
import { Windows } from "../../windows";
|
import { Windows } from "../../windows";
|
||||||
import { CenterWindow } from "../../window/CenterWindow";
|
import { CenterWindow } from "../../window/CenterWindow";
|
||||||
|
|
||||||
export function Clock(): JSX.Element {
|
export function Clock(): Gtk.Widget {
|
||||||
return new Widget.Box({
|
return new Widget.Box({
|
||||||
className: bind(CenterWindow, "visible").as((visible: boolean) =>
|
className: bind(CenterWindow, "visible").as((visible: boolean) =>
|
||||||
visible ? "clock open" : "clock"),
|
visible ? "clock open" : "clock"),
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function FocusedClient(): Gtk.Widget {
|
|||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "class",
|
className: "class",
|
||||||
xalign: 0,
|
xalign: 0,
|
||||||
|
visible: bind(focusedClient, "class").as(Boolean),
|
||||||
maxWidthChars: 55,
|
maxWidthChars: 55,
|
||||||
truncate: true,
|
truncate: true,
|
||||||
tooltipText: bind(focusedClient, "class").as((clientClass: string) =>
|
tooltipText: bind(focusedClient, "class").as((clientClass: string) =>
|
||||||
@@ -41,6 +42,7 @@ export function FocusedClient(): Gtk.Widget {
|
|||||||
className: "title",
|
className: "title",
|
||||||
xalign: 0,
|
xalign: 0,
|
||||||
maxWidthChars: 50,
|
maxWidthChars: 50,
|
||||||
|
visible: bind(focusedClient, "title").as(Boolean),
|
||||||
truncate: true,
|
truncate: true,
|
||||||
tooltipText: bind(focusedClient, "title").as((clientTitle: string) =>
|
tooltipText: bind(focusedClient, "title").as((clientTitle: string) =>
|
||||||
clientTitle.length > 55 ? clientTitle : ""),
|
clientTitle.length > 55 ? clientTitle : ""),
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
export function Logo() {
|
export function Logo(): Gtk.Widget {
|
||||||
return new Widget.EventBox({
|
return new Widget.EventBox({
|
||||||
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
|
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
|
||||||
className: "logo",
|
className: "logo",
|
||||||
child: new Widget.Box({
|
child: new Widget.Box({
|
||||||
child: new Widget.Label({
|
child: new Widget.Label({
|
||||||
className: "nf",
|
className: "nf",
|
||||||
label: "",
|
label: ""
|
||||||
} as Widget.LabelProps)
|
} as Widget.LabelProps)
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
} as Widget.EventBoxProps);
|
} as Widget.EventBoxProps);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function menuFromModel(model: Gio.MenuModel, actionGroup: Gio.ActionGroup | null
|
|||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tray() {
|
export function Tray(): Gtk.Widget {
|
||||||
return new Widget.Box({
|
return new Widget.Box({
|
||||||
className: "tray",
|
className: "tray",
|
||||||
visible: bind(astalTray, "items").as((items: Array<AstalTray.TrayItem>) => items.length > 0),
|
visible: bind(astalTray, "items").as((items: Array<AstalTray.TrayItem>) => items.length > 0),
|
||||||
@@ -29,9 +29,10 @@ export function Tray() {
|
|||||||
tooltipMarkup: bind(item, "tooltipMarkup"),
|
tooltipMarkup: bind(item, "tooltipMarkup"),
|
||||||
onClick: (_, event: Astal.ClickEvent) => {
|
onClick: (_, event: Astal.ClickEvent) => {
|
||||||
if(event.button === Astal.MouseButton.SECONDARY) {
|
if(event.button === Astal.MouseButton.SECONDARY) {
|
||||||
|
item.about_to_show();
|
||||||
menu.popup_at_widget(_, Gdk.Gravity.NORTH, Gdk.Gravity.SOUTH_WEST, null);
|
menu.popup_at_widget(_, Gdk.Gravity.NORTH, Gdk.Gravity.SOUTH_WEST, null);
|
||||||
} else if(event.button === Astal.MouseButton.PRIMARY)
|
} else if(event.button === Astal.MouseButton.PRIMARY)
|
||||||
item.secondary_activate(event.x, event.y);
|
item.activate(event.x, event.y);
|
||||||
},
|
},
|
||||||
halign: Gtk.Align.CENTER,
|
halign: Gtk.Align.CENTER,
|
||||||
child: new Widget.Icon({
|
child: new Widget.Icon({
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { bind } from "astal";
|
import { bind } from "astal";
|
||||||
import { Gdk, Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
const hyprland = AstalHyprland.get_default();
|
const hyprland = AstalHyprland.get_default();
|
||||||
|
|
||||||
export function Workspaces() {
|
export function Workspaces(): Gtk.Widget {
|
||||||
const workspacesEventBox = new Widget.EventBox({
|
const workspacesEventBox = new Widget.EventBox({
|
||||||
onScroll: (_, event) =>
|
onScroll: (_, event) =>
|
||||||
event.delta_y > 0 ? hyprland.dispatch("workspace", "e-1") : hyprland.dispatch("workspace", "e+1"),
|
event.delta_y > 0 ? hyprland.dispatch("workspace", "e-1") : hyprland.dispatch("workspace", "e+1"),
|
||||||
|
|||||||
@@ -4,163 +4,165 @@ import AstalMpris from "gi://AstalMpris";
|
|||||||
|
|
||||||
let dragTimer: (AstalIO.Time|undefined);
|
let dragTimer: (AstalIO.Time|undefined);
|
||||||
|
|
||||||
export const BigMedia: Gtk.Widget = new Widget.Box({
|
export function BigMedia(): Gtk.Widget {
|
||||||
className: "big-media",
|
return new Widget.Box({
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
className: "big-media",
|
||||||
homogeneous: false,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
width_request: 250,
|
homogeneous: false,
|
||||||
visible: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
width_request: 250,
|
||||||
players[0] ? true : false),
|
visible: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||||
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
players[0] ? true : false),
|
||||||
players[0] && [
|
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||||
new Widget.Box({
|
players[0] && [
|
||||||
halign: Gtk.Align.CENTER,
|
new Widget.Box({
|
||||||
child: new Widget.Box({
|
halign: Gtk.Align.CENTER,
|
||||||
className: "image",
|
child: new Widget.Box({
|
||||||
hexpand: false,
|
className: "image",
|
||||||
|
hexpand: false,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
visible: getAlbumArt(players[0]).as(Boolean),
|
||||||
|
css: getAlbumArt(players[0]).as((artUrl: string|undefined) =>
|
||||||
|
artUrl ? `.image { background-image: url('${artUrl}'); }` : undefined),
|
||||||
|
width_request: 132,
|
||||||
|
height_request: 128
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "info",
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
visible: getAlbumArt(players[0]).as(Boolean),
|
|
||||||
css: getAlbumArt(players[0]).as((artUrl: string|undefined) =>
|
|
||||||
artUrl ? `.image { background-image: url('${artUrl}'); }` : undefined),
|
|
||||||
width_request: 132,
|
|
||||||
height_request: 128
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
new Widget.Box({
|
|
||||||
className: "info",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
children: [
|
|
||||||
new Widget.Label({
|
|
||||||
className: "title",
|
|
||||||
tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
|
||||||
label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
|
||||||
truncate: true,
|
|
||||||
maxWidthChars: 25,
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
new Widget.Label({
|
|
||||||
className: "artist",
|
|
||||||
tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
|
||||||
label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
|
||||||
maxWidthChars: 28,
|
|
||||||
truncate: true,
|
|
||||||
} as Widget.LabelProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
new Widget.Box({
|
|
||||||
className: "progress",
|
|
||||||
hexpand: true,
|
|
||||||
visible: bind(players[0], "canSeek"),
|
|
||||||
children: [
|
|
||||||
new Widget.Slider({
|
|
||||||
min: 0,
|
|
||||||
hexpand: true,
|
|
||||||
max: bind(players[0], "length").as((length: number) =>
|
|
||||||
Math.floor(length)),
|
|
||||||
value: bind(players[0], "position").as((position: number) =>
|
|
||||||
Math.floor(position)),
|
|
||||||
onDragged: (slider: Widget.Slider) => {
|
|
||||||
if(dragTimer === undefined)
|
|
||||||
dragTimer = timeout(600, () =>
|
|
||||||
players[0].set_position(Math.round(slider.value)));
|
|
||||||
else {
|
|
||||||
dragTimer.cancel();
|
|
||||||
dragTimer = timeout(600, () =>
|
|
||||||
players[0].set_position(Math.round(slider.value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
new Widget.CenterBox({
|
|
||||||
className: "bottom",
|
|
||||||
homogeneous: false,
|
|
||||||
hexpand: true,
|
|
||||||
startWidget: new Widget.Label({
|
|
||||||
className: "elapsed",
|
|
||||||
valign: Gtk.Align.START,
|
|
||||||
halign: Gtk.Align.START,
|
|
||||||
label: bind(players[0], "position").as((pos: number) => {
|
|
||||||
const sec: number = Math.floor(pos % 60);
|
|
||||||
return pos > 0 && players[0].length > 0 ?
|
|
||||||
`${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
|
||||||
: `0:00`;
|
|
||||||
})
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
centerWidget: new Widget.Box({
|
|
||||||
className: "controls button-row",
|
|
||||||
children: [
|
children: [
|
||||||
new Widget.Button({
|
new Widget.Label({
|
||||||
className: "link nf",
|
className: "title",
|
||||||
label: "",
|
tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||||
tooltipText: "Copy link to Clipboard",
|
label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||||
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
|
truncate: true,
|
||||||
players[0].get_meta("xesam:url") === null),
|
maxWidthChars: 25,
|
||||||
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
|
} as Widget.LabelProps),
|
||||||
} as Widget.ButtonProps),
|
new Widget.Label({
|
||||||
new Widget.Button({
|
className: "artist",
|
||||||
className: "shuffle nf",
|
tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||||
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||||
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
|
maxWidthChars: 28,
|
||||||
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
truncate: true,
|
||||||
shuffleStatus === AstalMpris.Shuffle.ON ? "" : ""),
|
} as Widget.LabelProps)
|
||||||
tooltipText: "Toggle Shuffle",
|
|
||||||
onClick: () => players[0].shuffle()
|
|
||||||
} as Widget.ButtonProps),
|
|
||||||
new Widget.Button({
|
|
||||||
className: "previous nf",
|
|
||||||
label: "",
|
|
||||||
tooltipText: "Previous",
|
|
||||||
onClick: () => players[0].canGoPrevious && players[0].previous()
|
|
||||||
} as Widget.ButtonProps),
|
|
||||||
new Widget.Button({
|
|
||||||
className: "pause nf",
|
|
||||||
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
|
|
||||||
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
|
|
||||||
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
|
|
||||||
status === AstalMpris.PlaybackStatus.PLAYING ? "" : ""),
|
|
||||||
onClick: () => {
|
|
||||||
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
|
|
||||||
players[0].play()
|
|
||||||
:
|
|
||||||
players[0].pause()
|
|
||||||
}
|
|
||||||
} as Widget.ButtonProps),
|
|
||||||
new Widget.Button({
|
|
||||||
className: "next nf",
|
|
||||||
label: "",
|
|
||||||
tooltipText: "Next",
|
|
||||||
onClick: () => players[0].canGoNext && players[0].next()
|
|
||||||
} as Widget.ButtonProps),
|
|
||||||
new Widget.Button({
|
|
||||||
className: "repeat nf",
|
|
||||||
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
|
|
||||||
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
|
|
||||||
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
|
|
||||||
switch(loopStatus) {
|
|
||||||
case AstalMpris.Loop.TRACK: return "";
|
|
||||||
case AstalMpris.Loop.PLAYLIST: return "";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
tooltipText: "Toggle Loop",
|
|
||||||
onClick: () => players[0].loop()
|
|
||||||
} as Widget.ButtonProps)
|
|
||||||
]
|
]
|
||||||
} as Widget.BoxProps),
|
} as Widget.BoxProps),
|
||||||
endWidget: new Widget.Label({
|
new Widget.Box({
|
||||||
className: "length",
|
className: "progress",
|
||||||
valign: Gtk.Align.START,
|
hexpand: true,
|
||||||
halign: Gtk.Align.END,
|
visible: bind(players[0], "canSeek"),
|
||||||
label: bind(players[0], "length").as((len/* bananananananana */: number) => {
|
children: [
|
||||||
const sec: number = Math.floor(len % 60);
|
new Widget.Slider({
|
||||||
return len > 0 ?
|
min: 0,
|
||||||
`${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
hexpand: true,
|
||||||
: "0:00";
|
max: bind(players[0], "length").as((length: number) =>
|
||||||
})
|
Math.floor(length)),
|
||||||
} as Widget.LabelProps)
|
value: bind(players[0], "position").as((position: number) =>
|
||||||
})
|
Math.floor(position)),
|
||||||
])
|
onDragged: (slider: Widget.Slider) => {
|
||||||
} as Widget.BoxProps);
|
if(dragTimer === undefined)
|
||||||
|
dragTimer = timeout(600, () =>
|
||||||
|
players[0].set_position(Math.round(slider.value)));
|
||||||
|
else {
|
||||||
|
dragTimer.cancel();
|
||||||
|
dragTimer = timeout(600, () =>
|
||||||
|
players[0].set_position(Math.round(slider.value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
new Widget.CenterBox({
|
||||||
|
className: "bottom",
|
||||||
|
homogeneous: false,
|
||||||
|
hexpand: true,
|
||||||
|
startWidget: new Widget.Label({
|
||||||
|
className: "elapsed",
|
||||||
|
valign: Gtk.Align.START,
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
label: bind(players[0], "position").as((pos: number) => {
|
||||||
|
const sec: number = Math.floor(pos % 60);
|
||||||
|
return pos > 0 && players[0].length > 0 ?
|
||||||
|
`${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||||
|
: `0:00`;
|
||||||
|
})
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
centerWidget: new Widget.Box({
|
||||||
|
className: "controls button-row",
|
||||||
|
children: [
|
||||||
|
new Widget.Button({
|
||||||
|
className: "link nf",
|
||||||
|
label: "",
|
||||||
|
tooltipText: "Copy link to Clipboard",
|
||||||
|
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
|
||||||
|
players[0].get_meta("xesam:url") === null),
|
||||||
|
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "shuffle nf",
|
||||||
|
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||||
|
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
|
||||||
|
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||||
|
shuffleStatus === AstalMpris.Shuffle.ON ? "" : ""),
|
||||||
|
tooltipText: "Toggle Shuffle",
|
||||||
|
onClick: () => players[0].shuffle()
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "previous nf",
|
||||||
|
label: "",
|
||||||
|
tooltipText: "Previous",
|
||||||
|
onClick: () => players[0].canGoPrevious && players[0].previous()
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "pause nf",
|
||||||
|
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
|
||||||
|
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
|
||||||
|
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
|
||||||
|
status === AstalMpris.PlaybackStatus.PLAYING ? "" : ""),
|
||||||
|
onClick: () => {
|
||||||
|
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
|
||||||
|
players[0].play()
|
||||||
|
:
|
||||||
|
players[0].pause()
|
||||||
|
}
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "next nf",
|
||||||
|
label: "",
|
||||||
|
tooltipText: "Next",
|
||||||
|
onClick: () => players[0].canGoNext && players[0].next()
|
||||||
|
} as Widget.ButtonProps),
|
||||||
|
new Widget.Button({
|
||||||
|
className: "repeat nf",
|
||||||
|
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
|
||||||
|
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
|
||||||
|
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
|
||||||
|
switch(loopStatus) {
|
||||||
|
case AstalMpris.Loop.TRACK: return "";
|
||||||
|
case AstalMpris.Loop.PLAYLIST: return "";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tooltipText: "Toggle Loop",
|
||||||
|
onClick: () => players[0].loop()
|
||||||
|
} as Widget.ButtonProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
endWidget: new Widget.Label({
|
||||||
|
className: "length",
|
||||||
|
valign: Gtk.Align.START,
|
||||||
|
halign: Gtk.Align.END,
|
||||||
|
label: bind(players[0], "length").as((len/* bananananananana */: number) => {
|
||||||
|
const sec: number = Math.floor(len % 60);
|
||||||
|
return len > 0 ?
|
||||||
|
`${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||||
|
: "0:00";
|
||||||
|
})
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
} as Widget.BoxProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-5
@@ -1,15 +1,16 @@
|
|||||||
import { bind } from "astal";
|
import { bind } from "astal";
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import { Notifications } from "../../scripts/notification-handler";
|
import { Notifications } from "../../scripts/notifications";
|
||||||
|
|
||||||
export const NotificationHistory: Gtk.Widget = new Widget.Scrollable({
|
export const NotifHistory: Gtk.Widget = new Widget.Scrollable({
|
||||||
hscroll: Gtk.PolicyType.NEVER,
|
hscroll: Gtk.PolicyType.NEVER,
|
||||||
vscroll: Gtk.PolicyType.AUTOMATIC,
|
vscroll: Gtk.PolicyType.AUTOMATIC,
|
||||||
|
expand: true,
|
||||||
child: new Widget.Box({
|
child: new Widget.Box({
|
||||||
className: "notifications",
|
className: "notifications",
|
||||||
children: bind(Notifications, "notificationHistory").as((history: Array<AstalNotifd.Notification>) =>
|
children: bind(Notifications.getDefault(), "history").as((history: Array<AstalNotifd.Notification>) =>
|
||||||
history && history.length > 0 && history.map((notification: AstalNotifd.Notification) =>
|
history.map((notification: AstalNotifd.Notification) =>
|
||||||
new Widget.Box({
|
new Widget.Box({
|
||||||
className: "notification",
|
className: "notification",
|
||||||
hexpand: true,
|
hexpand: true,
|
||||||
@@ -35,7 +36,7 @@ export const NotificationHistory: Gtk.Widget = new Widget.Scrollable({
|
|||||||
new Widget.Button({
|
new Widget.Button({
|
||||||
className: "remove",
|
className: "remove",
|
||||||
label: "",
|
label: "",
|
||||||
onClick: () => Notifications.removeFromNotificationHistory(notification.id)
|
onClick: () => Notifications.getDefault().removeHistory(notification.id)
|
||||||
} as Widget.ButtonProps)
|
} as Widget.ButtonProps)
|
||||||
]
|
]
|
||||||
} as Widget.BoxProps),
|
} as Widget.BoxProps),
|
||||||
@@ -1,40 +1,45 @@
|
|||||||
import { timeout, Variable } from "astal";
|
import { timeout, Variable } from "astal";
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
|
import { Page } from "./pages/Page";
|
||||||
|
|
||||||
const empty = new Widget.Box();
|
const currentPage = new Variable<Page|undefined>(undefined);
|
||||||
const page = new Variable<Gtk.Widget>(empty);
|
|
||||||
let connectionId: (number|undefined);
|
|
||||||
|
|
||||||
export const PagesWidget: Widget.Revealer = new Widget.Revealer({
|
export const PagesWidget: Widget.Revealer = new Widget.Revealer({
|
||||||
revealChild: false,
|
revealChild: false,
|
||||||
|
className: "pages",
|
||||||
transitionType: Gtk.RevealerTransitionType.SLIDE_DOWN,
|
transitionType: Gtk.RevealerTransitionType.SLIDE_DOWN,
|
||||||
transitionDuration: 250,
|
transitionDuration: 360,
|
||||||
child: page()
|
child: currentPage((page: (Page|undefined)) =>
|
||||||
|
!page ? new Widget.Box() : page.getPage())
|
||||||
} as Widget.RevealerProps);
|
} as Widget.RevealerProps);
|
||||||
|
|
||||||
export function showPages(child: Gtk.Widget, onShow?: (self: Widget.Revealer) => void): void {
|
export function showPages(page: Page): void {
|
||||||
page.set(child);
|
currentPage.set(page);
|
||||||
PagesWidget.set_reveal_child(true);
|
PagesWidget.set_reveal_child(true);
|
||||||
connectionId !== undefined && PagesWidget.disconnect(connectionId);
|
page.props.onOpen && page.props.onOpen();
|
||||||
connectionId = PagesWidget.connect("show", (_) =>
|
|
||||||
onShow && onShow(_));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPage(): (Gtk.Widget|null) {
|
export function getPage(): (Page|undefined) {
|
||||||
return page.get();
|
return currentPage.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function togglePage(page: Gtk.Widget): void {
|
export function togglePage(page: Page): void {
|
||||||
PagesWidget.revealChild ?
|
if(!PagesWidget.revealChild) {
|
||||||
hidePages()
|
showPages(page);
|
||||||
: showPages(page);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hidePages();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hidePages(onHide?: () => void) {
|
export function hidePages() {
|
||||||
PagesWidget.set_reveal_child(false);
|
PagesWidget.set_reveal_child(false);
|
||||||
console.log("heyyyyy");
|
if(!currentPage.get()) return;
|
||||||
timeout(300, () => {
|
|
||||||
page.set(empty);
|
timeout(500, () => {
|
||||||
onHide && onHide();
|
if(currentPage.get() && currentPage.get()?.props.onClose)
|
||||||
|
currentPage.get()!.props.onClose!();
|
||||||
|
|
||||||
|
currentPage.set(undefined);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const Sliders: Gtk.Widget = new Widget.Box({
|
|||||||
]
|
]
|
||||||
} as Widget.BoxProps),
|
} as Widget.BoxProps),
|
||||||
/*new Widget.Box({
|
/*new Widget.Box({
|
||||||
className: "brightness screen",
|
className: "brightness",
|
||||||
children: [
|
children: [
|
||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "icon nf",
|
className: "icon nf",
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import { TileInternet } from "./tiles/Internet";
|
import { TileNetwork } from "./tiles/Network";
|
||||||
import { TileBluetooth } from "./tiles/Bluetooth";
|
import { TileBluetooth } from "./tiles/Bluetooth";
|
||||||
|
import { TileRecording } from "./tiles/Recording";
|
||||||
|
|
||||||
export const tileList: Array<any> = [
|
export const tileList: Array<any> = [
|
||||||
TileInternet,
|
TileNetwork,
|
||||||
TileBluetooth
|
TileBluetooth,
|
||||||
|
TileRecording
|
||||||
];
|
];
|
||||||
|
|
||||||
export function TilesWidget(): Gtk.Widget {
|
export function TilesWidget(): Gtk.Widget {
|
||||||
|
|||||||
@@ -1,98 +1,108 @@
|
|||||||
import { bind, timeout } from "astal";
|
import { bind, timeout } from "astal";
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
|
import { Page } from "./Page";
|
||||||
|
import { Separator, SeparatorProps } from "../../Separator";
|
||||||
|
|
||||||
let watchingDevices: boolean = false;
|
let watchingDevices: boolean = false;
|
||||||
|
|
||||||
export function BluetoothPage() {
|
export const BluetoothPage: Page = new Page({
|
||||||
watchNewDevices();
|
title: "Bluetooth Devices",
|
||||||
|
description: "Manage your Bluetooth devices and add new ones.",
|
||||||
return new Widget.Box({
|
className: "bluetooth",
|
||||||
className: "page bluetooth container",
|
setup: () => {
|
||||||
|
watchingDevices = true;
|
||||||
|
watchNewDevices();
|
||||||
|
},
|
||||||
|
onClose: stopBluetoothDevicesWatch,
|
||||||
|
pageChild: () => new Widget.Box({
|
||||||
|
className: "connections",
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
expand: true,
|
||||||
hexpand: true,
|
hexpand: true,
|
||||||
children: [
|
children: [
|
||||||
new Widget.Box({
|
new Widget.Box({
|
||||||
className: "header",
|
className: "paired",
|
||||||
children: [
|
|
||||||
new Widget.Label({
|
|
||||||
hexpand: true,
|
|
||||||
className: "title",
|
|
||||||
label: "Bluetooth",
|
|
||||||
halign: Gtk.Align.START
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
new Widget.Box({
|
|
||||||
className: "connections",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
expand: true,
|
|
||||||
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||||
devices.filter((device: AstalBluetooth.Device) => device.connected
|
devices.filter((device: AstalBluetooth.Device) => device.connected || device.paired)
|
||||||
).map((dev: AstalBluetooth.Device) =>
|
.map((dev: AstalBluetooth.Device) =>
|
||||||
new Widget.Button({
|
DeviceWidget(dev)
|
||||||
onClick: () => dev.connected ? dev.disconnect_device(null) : dev.connect_device(null),
|
)
|
||||||
child: new Widget.Box({
|
)
|
||||||
className: "device",
|
} as Widget.BoxProps),
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
Separator({
|
||||||
expand: true,
|
size: .5,
|
||||||
children: [
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
new Widget.Label({
|
alpha: .7
|
||||||
className: "alias",
|
} as SeparatorProps),
|
||||||
halign: Gtk.Align.START,
|
new Widget.Box({
|
||||||
label: bind(dev, "alias")
|
className: "discovered",
|
||||||
} as Widget.LabelProps),
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
new Widget.Label({
|
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||||
className: "battery",
|
devices.filter((device: AstalBluetooth.Device) => !device.connected && !device.paired)
|
||||||
halign: Gtk.Align.END,
|
.map((dev: AstalBluetooth.Device) =>
|
||||||
label: bind(dev, "batteryPercentage").as(String)
|
DeviceWidget(dev)
|
||||||
} as Widget.LabelProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
} as Widget.ButtonProps)).concat(
|
|
||||||
devices.filter((device: AstalBluetooth.Device) => !device.connected
|
|
||||||
).map((dev: AstalBluetooth.Device) =>
|
|
||||||
new Widget.Button({
|
|
||||||
onClick: () => dev.connect_device(() => {}),
|
|
||||||
child: new Widget.Box({
|
|
||||||
className: "device",
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
expand: true,
|
|
||||||
children: [
|
|
||||||
new Widget.Label({
|
|
||||||
className: "alias",
|
|
||||||
halign: Gtk.Align.START,
|
|
||||||
label: bind(dev, "alias")
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
new Widget.Label({
|
|
||||||
className: "battery",
|
|
||||||
halign: Gtk.Align.END,
|
|
||||||
label: bind(dev, "batteryPercentage").as(String)
|
|
||||||
} as Widget.LabelProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
} as Widget.ButtonProps))
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
]
|
]
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
|
})
|
||||||
|
|
||||||
|
function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
|
||||||
|
return new Widget.Button({
|
||||||
|
onClick: () => dev.connected ? dev.disconnect_device(null) : dev.connect_device(null),
|
||||||
|
className: bind(dev, "connected").as((connected) => connected ? "connected" : ""),
|
||||||
|
child: new Widget.Box({
|
||||||
|
className: "device",
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
expand: true,
|
||||||
|
children: [
|
||||||
|
new Widget.Icon({
|
||||||
|
className: "icon",
|
||||||
|
icon: bind(dev, "icon").as((icon: string) =>
|
||||||
|
icon ? icon : "bluetooth-active-symbolic"),
|
||||||
|
css: "font-size: 20px; margin-right: 6px;"
|
||||||
|
} as Widget.IconProps),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "alias",
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
hexpand: true,
|
||||||
|
label: bind(dev, "alias")
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "battery",
|
||||||
|
halign: Gtk.Align.END,
|
||||||
|
visible: bind(dev, "batteryPercentage").as((bat: number) =>
|
||||||
|
bat <= -1 ? false : true),
|
||||||
|
label: bind(dev, "batteryPercentage").as((bat: number) =>
|
||||||
|
` ${Math.floor(bat * 100)}%`)
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as Widget.ButtonProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchNewDevices(): void {
|
function watchNewDevices(): void {
|
||||||
if(watchingDevices) {
|
if(watchingDevices) {
|
||||||
timeout(10000, () => {
|
timeout(8000, () => {
|
||||||
reloadDevicesList();
|
reloadBluetoothDevicesList();
|
||||||
watchNewDevices();
|
watchNewDevices();
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopBluetoothDevicesWatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopDeviceWatch(): void {
|
export function stopBluetoothDevicesWatch(): void {
|
||||||
watchingDevices = false;
|
watchingDevices = false;
|
||||||
|
AstalBluetooth.get_default().adapter.discovering &&
|
||||||
|
AstalBluetooth.get_default().adapter.stop_discovery();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadDevicesList(): void {
|
export function reloadBluetoothDevicesList(): void {
|
||||||
AstalBluetooth.get_default().adapter.start_discovery();
|
AstalBluetooth.get_default().adapter.start_discovery();
|
||||||
timeout(5000, () => AstalBluetooth.get_default().adapter.stop_discovery());
|
timeout(4000, () => AstalBluetooth.get_default().adapter.stop_discovery());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { bind } from "astal";
|
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
|
||||||
import AstalBluetooth from "gi://AstalBluetooth";
|
|
||||||
|
|
||||||
export function WifiPage() {
|
|
||||||
return new Widget.Box({
|
|
||||||
className: "page bluetooth container",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
hexpand: true,
|
|
||||||
children: [
|
|
||||||
new Widget.Box({
|
|
||||||
className: "connections",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
expand: true,
|
|
||||||
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
|
||||||
devices && devices.filter((device: AstalBluetooth.Device) => device.connected
|
|
||||||
).map((dev: AstalBluetooth.Device) =>
|
|
||||||
new Widget.Box({
|
|
||||||
className: "device",
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
expand: true,
|
|
||||||
children: [
|
|
||||||
new Widget.Label({
|
|
||||||
className: "alias",
|
|
||||||
halign: Gtk.Align.START,
|
|
||||||
label: bind(dev, "alias")
|
|
||||||
} as Widget.LabelProps),
|
|
||||||
new Widget.Label({
|
|
||||||
className: "battery",
|
|
||||||
halign: Gtk.Align.END,
|
|
||||||
} as Widget.LabelProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
))
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Widget } from "astal/gtk3";
|
||||||
|
import { Page } from "./Page";
|
||||||
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
|
import { bind } from "astal";
|
||||||
|
|
||||||
|
export const PageNetwork = new Page({
|
||||||
|
title: "Network",
|
||||||
|
className: "network",
|
||||||
|
headerButtons: () => [
|
||||||
|
new Widget.Button({
|
||||||
|
className: "reload nf",
|
||||||
|
label: "",
|
||||||
|
visible: bind(AstalNetwork.get_default(), "primary").as(
|
||||||
|
(primary: AstalNetwork.Primary) => primary === AstalNetwork.Primary.WIFI
|
||||||
|
),
|
||||||
|
tooltipText: "Re-scan connections",
|
||||||
|
onClick: () => AstalNetwork.get_default().wifi.scan()
|
||||||
|
} as Widget.ButtonProps)
|
||||||
|
],
|
||||||
|
pageChild: () => new Widget.Box({
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Binding, GObject, register } from "astal";
|
||||||
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
|
|
||||||
|
export type PageProps = {
|
||||||
|
setup?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
onOpen?: () => void;
|
||||||
|
className?: string | Binding<string | undefined>;
|
||||||
|
title: string | Binding<string | undefined>;
|
||||||
|
description?: string | Binding<string | undefined>;
|
||||||
|
headerButtons?: () => Array<Gtk.Widget>;
|
||||||
|
pageChild: () => Gtk.Widget;
|
||||||
|
};
|
||||||
|
|
||||||
|
@register({ GTypeName: "Page" })
|
||||||
|
class Page extends GObject.Object {
|
||||||
|
readonly #props: PageProps;
|
||||||
|
|
||||||
|
get props() { return this.#props; }
|
||||||
|
|
||||||
|
constructor(props: PageProps) {
|
||||||
|
super();
|
||||||
|
this.#props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeaderButtons(): (Array<Gtk.Widget>|null) {
|
||||||
|
return this.props.headerButtons ?
|
||||||
|
this.props.headerButtons()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPage(): Gtk.Widget {
|
||||||
|
return new Widget.Box({
|
||||||
|
className: (this.props.className instanceof Binding) ?
|
||||||
|
this.props.className.as((clsName: (string|undefined)) => `page ${ clsName || "" }`) : `page ${this.#props.className || ""}`,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
hexpand: true,
|
||||||
|
setup: this.props.setup,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "header",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
hexpand: true,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "title",
|
||||||
|
children: [
|
||||||
|
new Widget.Label({
|
||||||
|
hexpand: true,
|
||||||
|
className: "title",
|
||||||
|
truncate: true,
|
||||||
|
visible: (this.props.title instanceof Binding) ?
|
||||||
|
this.props.title.as(Boolean)
|
||||||
|
: (this.props.title ? true : false),
|
||||||
|
label: this.props.title,
|
||||||
|
halign: Gtk.Align.START
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "button-row",
|
||||||
|
visible: Boolean(this.getHeaderButtons()),
|
||||||
|
children: this.getHeaderButtons() || undefined
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "description",
|
||||||
|
hexpand: true,
|
||||||
|
truncate: true,
|
||||||
|
xalign: 0,
|
||||||
|
visible: (this.props.description instanceof Binding) ?
|
||||||
|
this.props.description.as(Boolean)
|
||||||
|
: this.props.description ? true : false,
|
||||||
|
label: this.props.description
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "content",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
expand: true,
|
||||||
|
setup: (_) => _.add(this.props.pageChild())
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Page };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { bind } from "astal";
|
import { bind, Variable } from "astal";
|
||||||
import { Tile, TileProps } from "./Tile";
|
import { Tile, TileProps } from "./Tile";
|
||||||
import AstalBluetooth from "gi://AstalBluetooth";
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
import { togglePage } from "../Pages";
|
import { togglePage } from "../Pages";
|
||||||
@@ -6,16 +6,18 @@ import { BluetoothPage } from "../pages/Bluetooth";
|
|||||||
|
|
||||||
export const TileBluetooth = Tile({
|
export const TileBluetooth = Tile({
|
||||||
title: "Bluetooth",
|
title: "Bluetooth",
|
||||||
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) => {
|
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||||
const connected: Array<AstalBluetooth.Device> = devices.filter(
|
devices.filter((dev: AstalBluetooth.Device) => dev.connected)[0]?.get_alias()),
|
||||||
(dev: AstalBluetooth.Device) => dev.connected);
|
|
||||||
|
|
||||||
return connected[0] ? connected[0].get_alias() : undefined;
|
|
||||||
}),
|
|
||||||
onToggledOn: () => AstalBluetooth.get_default().adapter.set_powered(true),
|
onToggledOn: () => AstalBluetooth.get_default().adapter.set_powered(true),
|
||||||
onToggledOff: () => AstalBluetooth.get_default().adapter.set_powered(false),
|
onToggledOff: () => AstalBluetooth.get_default().adapter.set_powered(false),
|
||||||
onClickMore: () => togglePage(BluetoothPage()),
|
onClickMore: () => togglePage(BluetoothPage),
|
||||||
icon: "",
|
icon: Variable.derive([
|
||||||
|
bind(AstalBluetooth.get_default().adapter, "powered"),
|
||||||
|
bind(AstalBluetooth.get_default(), "isConnected")
|
||||||
|
],
|
||||||
|
(powered: boolean, isConnected: boolean) =>
|
||||||
|
powered ? ( isConnected ? "" : "" ) : ""
|
||||||
|
)(),
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
toggleState: bind(AstalBluetooth.get_default().adapter, "powered")
|
toggleState: bind(AstalBluetooth.get_default().adapter, "powered")
|
||||||
} as TileProps);
|
} as TileProps);
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { bind, execAsync } from "astal";
|
|
||||||
import { Tile, TileProps } from "./Tile";
|
|
||||||
import AstalNetwork from "gi://AstalNetwork";
|
|
||||||
import { Widget } from "astal/gtk3";
|
|
||||||
|
|
||||||
export const TileInternet = new Widget.Box({
|
|
||||||
child: bind(AstalNetwork.get_default(), "wired").as((wired: AstalNetwork.Wired) => Tile({
|
|
||||||
title: "Wired",
|
|
||||||
description: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
|
||||||
switch(internet) {
|
|
||||||
case AstalNetwork.Internet.CONNECTED:
|
|
||||||
return "Connected";
|
|
||||||
case AstalNetwork.Internet.DISCONNECTED:
|
|
||||||
return "Disconnected";
|
|
||||||
case AstalNetwork.Internet.CONNECTING:
|
|
||||||
return "Connecting...";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
onToggledOn: () => execAsync("nmcli n on"),
|
|
||||||
onToggledOff: () => execAsync("nmcli n off"),
|
|
||||||
icon: "",
|
|
||||||
iconSize: 16,
|
|
||||||
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
|
||||||
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
|
|
||||||
} as TileProps))
|
|
||||||
} as Widget.BoxProps);
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { bind, execAsync, Variable } from "astal";
|
||||||
|
import { Tile, TileProps } from "./Tile";
|
||||||
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
|
import { Widget } from "astal/gtk3";
|
||||||
|
import { showPages, togglePage } from "../Pages";
|
||||||
|
import { PageNetwork } from "../pages/Network";
|
||||||
|
|
||||||
|
export const TileNetwork = new Widget.Box({
|
||||||
|
child: Variable.derive([
|
||||||
|
bind(AstalNetwork.get_default(), "primary"),
|
||||||
|
bind(AstalNetwork.get_default(), "wired"),
|
||||||
|
bind(AstalNetwork.get_default(), "wifi")
|
||||||
|
],
|
||||||
|
(primary: AstalNetwork.Primary, wired: AstalNetwork.Wired, wifi: AstalNetwork.Wifi) => {
|
||||||
|
if(primary === AstalNetwork.Primary.WIFI) {
|
||||||
|
return Tile({
|
||||||
|
title: "Wireless",
|
||||||
|
description: Variable.derive(
|
||||||
|
[ bind(wifi, "ssid"), bind(wifi, "internet") ],
|
||||||
|
(ssid: string, internet: AstalNetwork.Internet) =>
|
||||||
|
ssid ? ssid : (() => {
|
||||||
|
switch(internet) {
|
||||||
|
case AstalNetwork.Internet.CONNECTED:
|
||||||
|
return "Connected";
|
||||||
|
case AstalNetwork.Internet.DISCONNECTED:
|
||||||
|
return "Disconnected";
|
||||||
|
case AstalNetwork.Internet.CONNECTING:
|
||||||
|
return "Connecting...";
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
)(),
|
||||||
|
onToggledOn: () => wifi.set_enabled(true),
|
||||||
|
onToggledOff: () => wifi.set_enabled(false),
|
||||||
|
onClickMore: () => togglePage(PageNetwork),
|
||||||
|
icon: "",
|
||||||
|
iconSize: 16,
|
||||||
|
toggleState: bind(wifi, "enabled")
|
||||||
|
} as TileProps);
|
||||||
|
|
||||||
|
} else if(primary === AstalNetwork.Primary.WIRED) {
|
||||||
|
return Tile({
|
||||||
|
title: "Wired",
|
||||||
|
description: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
||||||
|
switch(internet) {
|
||||||
|
case AstalNetwork.Internet.CONNECTED:
|
||||||
|
return "Connected";
|
||||||
|
case AstalNetwork.Internet.DISCONNECTED:
|
||||||
|
return "Disconnected";
|
||||||
|
case AstalNetwork.Internet.CONNECTING:
|
||||||
|
return "Connecting...";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
onToggledOn: () => execAsync("nmcli n on"),
|
||||||
|
onToggledOff: () => execAsync("nmcli n off"),
|
||||||
|
onClickMore: () => togglePage(PageNetwork),
|
||||||
|
icon: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
||||||
|
switch(internet) {
|
||||||
|
case AstalNetwork.Internet.CONNECTED:
|
||||||
|
return '';
|
||||||
|
case AstalNetwork.Internet.DISCONNECTED:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}),
|
||||||
|
iconSize: 16,
|
||||||
|
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
||||||
|
internet === AstalNetwork.Internet.CONNECTING
|
||||||
|
|| internet === AstalNetwork.Internet.CONNECTED
|
||||||
|
)
|
||||||
|
} as TileProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tile({
|
||||||
|
title: "Network",
|
||||||
|
description: "Disconnected",
|
||||||
|
onToggledOn: () => execAsync("nmcli n on"),
|
||||||
|
onToggledOff: () => execAsync("nmcli n off"),
|
||||||
|
onClickMore: () => togglePage(PageNetwork),
|
||||||
|
icon: "",
|
||||||
|
iconSize: 16,
|
||||||
|
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
||||||
|
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
|
||||||
|
} as TileProps);
|
||||||
|
})()
|
||||||
|
} as Widget.BoxProps);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Tile, TileProps } from "./Tile";
|
||||||
|
import { Recording } from "../../../scripts/recording";
|
||||||
|
import { bind } from "astal";
|
||||||
|
|
||||||
|
export const TileRecording = Tile({
|
||||||
|
title: "Screen Recording",
|
||||||
|
description: bind(Recording.getDefault(), "recording").as(
|
||||||
|
(isRecording: boolean) => isRecording ?
|
||||||
|
"Recording {time}"
|
||||||
|
: "Start a Screen Record"
|
||||||
|
),
|
||||||
|
icon: "",
|
||||||
|
onToggledOff: () => Recording.getDefault().stopRecording(),
|
||||||
|
onToggledOn: () => Recording.getDefault().startRecording(),
|
||||||
|
iconSize: 16,
|
||||||
|
toggleState: bind(Recording.getDefault(), "recording"),
|
||||||
|
} as TileProps);
|
||||||
@@ -55,23 +55,38 @@ export function Tile(props: TileProps): Widget.EventBox {
|
|||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "icon nf",
|
className: "icon nf",
|
||||||
label: props.icon || "icon",
|
label: props.icon || "icon",
|
||||||
css: `.icon { font-size: ${props.iconSize || "12px"} }`
|
css: `label { font-size: ${props.iconSize || "12"}px; }`
|
||||||
} as Widget.LabelProps),
|
} as Widget.LabelProps),
|
||||||
new Widget.Box({
|
new Widget.Box({
|
||||||
className: "text",
|
className: "text",
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
vexpand: true,
|
vexpand: true,
|
||||||
|
hexpand: true,
|
||||||
valign: Gtk.Align.CENTER,
|
valign: Gtk.Align.CENTER,
|
||||||
children: [
|
children: [
|
||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "title",
|
className: "title",
|
||||||
xalign: 0,
|
xalign: 0,
|
||||||
|
halign: Gtk.Align.START,
|
||||||
truncate: true,
|
truncate: true,
|
||||||
label: props.title
|
label: props.title
|
||||||
} as Widget.LabelProps),
|
} as Widget.LabelProps),
|
||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "description",
|
className: "description",
|
||||||
visible: props.description,
|
visible: Boolean(props.description),
|
||||||
|
setup: (label: Widget.Label) => {
|
||||||
|
if(props.description instanceof Binding) {
|
||||||
|
const sub = props.description.subscribe((value) => {
|
||||||
|
label.set_visible(Boolean(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
const destroyId = label.connect("destroy-event", () => {
|
||||||
|
label.disconnect(destroyId);
|
||||||
|
sub();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
halign: Gtk.Align.START,
|
||||||
truncate: true,
|
truncate: true,
|
||||||
xalign: 0,
|
xalign: 0,
|
||||||
label: props.description
|
label: props.description
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { register } from "astal";
|
||||||
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
|
import { closeRunner } from "../../window/Runner";
|
||||||
|
|
||||||
|
export { ResultWidget, ResultWidgetProps };
|
||||||
|
|
||||||
|
type ResultWidgetProps = {
|
||||||
|
icon?: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
closeOnClick?: boolean;
|
||||||
|
setup?: () => void;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
@register({ GTypeName: "ResultWidget" })
|
||||||
|
class ResultWidget extends Widget.EventBox {
|
||||||
|
private readonly connections: Array<number>;
|
||||||
|
public readonly onClick: ((() => void)|undefined);
|
||||||
|
public readonly icon: (string|undefined);
|
||||||
|
public readonly setup: ((() => void)|undefined);
|
||||||
|
public readonly closeOnClick: boolean = true;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(props: ResultWidgetProps) {
|
||||||
|
super();
|
||||||
|
if(props.icon)
|
||||||
|
this.icon = props.icon;
|
||||||
|
if(props.onClick)
|
||||||
|
this.onClick = props.onClick;
|
||||||
|
if(props.setup)
|
||||||
|
this.setup = props.setup;
|
||||||
|
if(props.closeOnClick !== undefined)
|
||||||
|
this.closeOnClick = props.closeOnClick;
|
||||||
|
|
||||||
|
this.connections = [
|
||||||
|
this.connect("click", () => {
|
||||||
|
this.onClick && this.onClick();
|
||||||
|
this.closeOnClick && closeRunner();
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.connect("destroy-event", () => this.connections.map((id: number) =>
|
||||||
|
this.disconnect(id)))
|
||||||
|
];
|
||||||
|
|
||||||
|
this.add(new Widget.Box({
|
||||||
|
className: "result",
|
||||||
|
hexpand: true,
|
||||||
|
children: [
|
||||||
|
new Widget.Icon({
|
||||||
|
visible: Boolean(props.icon),
|
||||||
|
icon: props.icon || "image-missing"
|
||||||
|
} as Widget.IconProps),
|
||||||
|
new Widget.Box({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
children: [
|
||||||
|
new Widget.Label({
|
||||||
|
className: "title",
|
||||||
|
xalign: 0,
|
||||||
|
truncate: true,
|
||||||
|
label: props.title
|
||||||
|
} as Widget.LabelProps),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "description",
|
||||||
|
visible: Boolean(props.description),
|
||||||
|
truncate: true,
|
||||||
|
xalign: 0,
|
||||||
|
label: props.description || ""
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { Variable } from "astal";
|
||||||
|
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||||
|
import { getAstalApps } from "../scripts/apps";
|
||||||
|
import AstalApps from "gi://AstalApps";
|
||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
|
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||||
|
const searchString = new Variable<string>("");
|
||||||
|
const appsArray = new Variable<Array<AstalApps.Application>>([]);
|
||||||
|
let searchSubscription: () => void;
|
||||||
|
|
||||||
|
export const AppsWindow = new Widget.Window({
|
||||||
|
namespace: "apps-window",
|
||||||
|
layer: Astal.Layer.OVERLAY,
|
||||||
|
exclusivity: Astal.Exclusivity.IGNORE,
|
||||||
|
anchor: TOP | LEFT | RIGHT | BOTTOM,
|
||||||
|
visible: false,
|
||||||
|
keymode: Astal.Keymode.EXCLUSIVE,
|
||||||
|
onKeyPressEvent: (_, event: Gdk.Event) => {
|
||||||
|
event.get_keyval()[1] === Gdk.KEY_Escape &&
|
||||||
|
hideAppsWindow(_);
|
||||||
|
},
|
||||||
|
setup: () => {
|
||||||
|
searchSubscription = searchString.subscribe((str: string) => {
|
||||||
|
appsArray.set(getAstalApps().fuzzy_query(str));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: new Widget.Box({
|
||||||
|
className: "apps-window container",
|
||||||
|
expand: true,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Entry({
|
||||||
|
className: "entry",
|
||||||
|
hexpand: true,
|
||||||
|
vexpand: false,
|
||||||
|
onDraw: (_) => _.grab_focus(),
|
||||||
|
onChanged: (entry) => {
|
||||||
|
searchString.set(entry.text);
|
||||||
|
}
|
||||||
|
} as Widget.EntryProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "apps",
|
||||||
|
hexpand: true,
|
||||||
|
vexpand: true,
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: appsArray((apps: Array<AstalApps.Application>) =>
|
||||||
|
apps.map((app: AstalApps.Application) =>
|
||||||
|
new Widget.Button({
|
||||||
|
className: "app",
|
||||||
|
onClickRelease: (_) => {
|
||||||
|
_.get_window()?.hide();
|
||||||
|
AstalHyprland.get_default().dispatch("exec", app.get_executable());
|
||||||
|
},
|
||||||
|
child: new Widget.Box({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
new Widget.Icon({
|
||||||
|
className: "icon",
|
||||||
|
iconName: app.get_icon_name()
|
||||||
|
} as Widget.IconProps),
|
||||||
|
new Widget.Label({
|
||||||
|
className: "name",
|
||||||
|
label: app.get_name()
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as Widget.ButtonProps)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as Widget.WindowProps);
|
||||||
|
|
||||||
|
function hideAppsWindow(window: Widget.Window) {
|
||||||
|
searchString.set("");
|
||||||
|
window.hide();
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import { BigMedia } from "../widget/center-window/BigMedia";
|
|||||||
import { Separator, SeparatorProps } from "../widget/Separator";
|
import { Separator, SeparatorProps } from "../widget/Separator";
|
||||||
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
||||||
|
|
||||||
|
const BigMediaWidget = BigMedia();
|
||||||
|
|
||||||
export const CenterWindow: Widget.Window = PopupWindow({
|
export const CenterWindow: Widget.Window = PopupWindow({
|
||||||
className: "center-window",
|
className: "center-window",
|
||||||
namespace: "center-window",
|
namespace: "center-window",
|
||||||
@@ -53,7 +55,7 @@ export const CenterWindow: Widget.Window = PopupWindow({
|
|||||||
]
|
]
|
||||||
} as Widget.BoxProps),
|
} as Widget.BoxProps),
|
||||||
Separator({
|
Separator({
|
||||||
visible: bind(BigMedia, "visible"),
|
visible: bind(BigMediaWidget, "visible"),
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
alpha: .5,
|
alpha: .5,
|
||||||
cssColor: "gray",
|
cssColor: "gray",
|
||||||
@@ -63,7 +65,7 @@ export const CenterWindow: Widget.Window = PopupWindow({
|
|||||||
className: "vertical right",
|
className: "vertical right",
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
children: [
|
children: [
|
||||||
BigMedia
|
BigMediaWidget
|
||||||
]
|
]
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
]
|
]
|
||||||
|
|||||||
+27
-10
@@ -4,16 +4,9 @@ import { Tiles } from "../widget/control-center/Tiles";
|
|||||||
import { Sliders } from "../widget/control-center/Sliders";
|
import { Sliders } from "../widget/control-center/Sliders";
|
||||||
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
||||||
import { hidePages, PagesWidget } from "../widget/control-center/Pages";
|
import { hidePages, PagesWidget } from "../widget/control-center/Pages";
|
||||||
|
import { NotifHistory } from "../widget/control-center/NotifHistory";
|
||||||
|
|
||||||
const widgetsContainer: Widget.Box = new Widget.Box({
|
const connections: Array<number> = [];
|
||||||
className: "control-center-container",
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
widthRequest: 400,
|
|
||||||
} as Widget.BoxProps,
|
|
||||||
QuickActions,
|
|
||||||
Sliders,
|
|
||||||
Tiles,
|
|
||||||
PagesWidget);
|
|
||||||
|
|
||||||
export const ControlCenter: Widget.Window = PopupWindow({
|
export const ControlCenter: Widget.Window = PopupWindow({
|
||||||
className: "control-center",
|
className: "control-center",
|
||||||
@@ -25,5 +18,29 @@ export const ControlCenter: Widget.Window = PopupWindow({
|
|||||||
halign: Gtk.Align.END,
|
halign: Gtk.Align.END,
|
||||||
valign: Gtk.Align.START,
|
valign: Gtk.Align.START,
|
||||||
visible: false,
|
visible: false,
|
||||||
child: widgetsContainer
|
vexpand: true,
|
||||||
|
child: new Widget.Box({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
vexpand: true,
|
||||||
|
children: [
|
||||||
|
new Widget.Box({
|
||||||
|
className: "control-center-container",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
widthRequest: 400,
|
||||||
|
vexpand: false,
|
||||||
|
hexpand: true,
|
||||||
|
children: [
|
||||||
|
QuickActions,
|
||||||
|
Sliders,
|
||||||
|
Tiles,
|
||||||
|
PagesWidget
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
NotifHistory
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
} as PopupWindowProps);
|
} as PopupWindowProps);
|
||||||
|
|
||||||
|
connections.push(ControlCenter.connect("hide", (_) => {
|
||||||
|
hidePages();
|
||||||
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import { Notifications } from "../scripts/notification-handler";
|
import { bind } from "astal/binding";
|
||||||
|
import { Notifications } from "../scripts/notifications";
|
||||||
|
import { NotificationWidget } from "../widget/Notification";
|
||||||
|
import { timeout } from "astal";
|
||||||
|
import { VarMap } from "../scripts/varmap";
|
||||||
|
|
||||||
|
const connections: Array<number> = [];
|
||||||
|
const notifWidgets = new VarMap<number, Widget.Revealer>();
|
||||||
|
|
||||||
export const FloatingNotifications: Widget.Window = new Widget.Window({
|
export const FloatingNotifications: Widget.Window = new Widget.Window({
|
||||||
namespace: "floating-notifications",
|
namespace: "floating-notifications",
|
||||||
@@ -9,14 +16,43 @@ export const FloatingNotifications: Widget.Window = new Widget.Window({
|
|||||||
monitor: 0,
|
monitor: 0,
|
||||||
layer: Astal.Layer.OVERLAY,
|
layer: Astal.Layer.OVERLAY,
|
||||||
visible: false,
|
visible: false,
|
||||||
width_request: 350,
|
widthRequest: 450,
|
||||||
exclusivity: Astal.Exclusivity.NORMAL,
|
exclusivity: Astal.Exclusivity.NORMAL,
|
||||||
|
setup: (window) => {
|
||||||
|
connections.push(
|
||||||
|
Notifications.getDefault().connect("notification-added", (_, notif: AstalNotifd.Notification) => {
|
||||||
|
!window.is_visible() && window.show();
|
||||||
|
|
||||||
|
notifWidgets.set(notif.id, new Widget.Revealer({
|
||||||
|
revealChild: false,
|
||||||
|
transitionDuration: 320,
|
||||||
|
transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT,
|
||||||
|
child: NotificationWidget(notif,
|
||||||
|
() => Notifications.getDefault().removeNotification(notif.id)),
|
||||||
|
} as Widget.RevealerProps));
|
||||||
|
|
||||||
|
notifWidgets.getValue(notif.id)!.revealChild = true;
|
||||||
|
}),
|
||||||
|
|
||||||
|
Notifications.getDefault().connect("notification-removed", (_, id: number) => {
|
||||||
|
notifWidgets.getValue(id)!.revealChild = false;
|
||||||
|
timeout(
|
||||||
|
(notifWidgets.getValue(id)?.get_transition_duration() || 0) + 50,
|
||||||
|
() => {
|
||||||
|
notifWidgets.delete(id);
|
||||||
|
Notifications.getDefault().notifications.length === 0 &&
|
||||||
|
window.is_visible() && window.hide();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onDestroy: () => connections.map(id => Notifications.getDefault().disconnect(id)),
|
||||||
child: new Widget.Box({
|
child: new Widget.Box({
|
||||||
className: "floating-notifications-container",
|
className: "floating-notifications-container",
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
homogeneous: false,
|
homogeneous: false,
|
||||||
children: Notifications.notifications().as((notifications: Array<AstalNotifd.Notification>) =>
|
visible: bind(Notifications.getDefault(), "notifications").as(notifs => notifs.length > 0),
|
||||||
notifications.map((item: AstalNotifd.Notification) =>
|
children: bind(notifWidgets).as((map) => [...map.values()].map((revealer) => revealer))
|
||||||
NotificationWidget(item)))
|
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
} as Widget.WindowProps);
|
} as Widget.WindowProps);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||||
import { getDateTime } from "../scripts/time";
|
import { getDateTime } from "../scripts/time";
|
||||||
import { execAsync, GLib } from "astal";
|
import { execAsync, GLib } from "astal";
|
||||||
|
import { AskPopup } from "../widget/AskPopup";
|
||||||
|
|
||||||
|
|
||||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||||
@@ -26,7 +27,8 @@ export const LogoutMenu: Widget.Window = new Widget.Window({
|
|||||||
children: [
|
children: [
|
||||||
new Widget.Box({
|
new Widget.Box({
|
||||||
className: "top",
|
className: "top",
|
||||||
expand: false,
|
hexpand: true,
|
||||||
|
vexpand: false,
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
valign: Gtk.Align.START,
|
valign: Gtk.Align.START,
|
||||||
children: [
|
children: [
|
||||||
@@ -45,28 +47,53 @@ export const LogoutMenu: Widget.Window = new Widget.Window({
|
|||||||
new Widget.Box({
|
new Widget.Box({
|
||||||
className: "button-row",
|
className: "button-row",
|
||||||
homogeneous: true,
|
homogeneous: true,
|
||||||
expand: true,
|
vexpand: true,
|
||||||
valign: Gtk.Align.CENTER,
|
valign: Gtk.Align.CENTER,
|
||||||
|
height_request: 360,
|
||||||
children: [
|
children: [
|
||||||
new Widget.Button({
|
new Widget.Button({
|
||||||
className: "poweroff nf",
|
className: "poweroff nf",
|
||||||
label: "",
|
label: "",
|
||||||
onClick: () => execAsync("systemctl poweroff")
|
onClick: () => AskPopup({
|
||||||
|
title: "Power Off",
|
||||||
|
text: "Are you sure you want to power off? Unsaved work will be lost.",
|
||||||
|
cancelText: "No! Let me go back",
|
||||||
|
acceptText: "Yes, shutdown",
|
||||||
|
onAccept: () => execAsync("systemctl poweroff")
|
||||||
|
})
|
||||||
} as Widget.ButtonProps),
|
} as Widget.ButtonProps),
|
||||||
new Widget.Button({
|
new Widget.Button({
|
||||||
className: "reboot nf",
|
className: "reboot nf",
|
||||||
label: "",
|
label: "",
|
||||||
onClick: () => execAsync("systemctl reboot")
|
onClick: () => AskPopup({
|
||||||
|
title: "Reboot",
|
||||||
|
text: "Are you sure you want to Reboot? Unsaved work will be lost.",
|
||||||
|
cancelText: "No! Let me go back",
|
||||||
|
acceptText: "Yes, reboot",
|
||||||
|
onAccept: () => execAsync("systemctl reboot")
|
||||||
|
})
|
||||||
} as Widget.ButtonProps),
|
} as Widget.ButtonProps),
|
||||||
new Widget.Button({
|
new Widget.Button({
|
||||||
className: "suspend nf",
|
className: "suspend nf",
|
||||||
label: "",
|
label: "",
|
||||||
onClick: () => execAsync("systemctl suspend")
|
onClick: () => AskPopup({
|
||||||
|
title: "Suspend",
|
||||||
|
text: "Are you sure you want to Suspend?",
|
||||||
|
cancelText: "No! Let me go back",
|
||||||
|
acceptText: "Yes, suspend",
|
||||||
|
onAccept: () => execAsync("systemctl suspend")
|
||||||
|
})
|
||||||
} as Widget.ButtonProps),
|
} as Widget.ButtonProps),
|
||||||
new Widget.Button({
|
new Widget.Button({
|
||||||
className: "logout nf",
|
className: "logout nf",
|
||||||
label: "",
|
label: "",
|
||||||
onClick: () => execAsync("astal close logout-menu && bash -c 'loginctl terminate-user $USER'")
|
onClick: () => AskPopup({
|
||||||
|
title: "Log out",
|
||||||
|
text: "Are you sure you want to log out? Your session will be ended.",
|
||||||
|
cancelText: "No! Let me go back",
|
||||||
|
acceptText: "Yes, please log out",
|
||||||
|
onAccept: () => execAsync(`sh -c "loginctl terminate-user ${GLib.getenv("USER") || "$USER"}"`)
|
||||||
|
})
|
||||||
} as Widget.ButtonProps),
|
} as Widget.ButtonProps),
|
||||||
]
|
]
|
||||||
} as Widget.BoxProps)
|
} as Widget.BoxProps)
|
||||||
|
|||||||
+174
-31
@@ -1,41 +1,184 @@
|
|||||||
import { Variable } from "astal";
|
import { Variable } from "astal";
|
||||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
import { Gdk, Gtk, Widget } from "astal/gtk3";
|
||||||
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
||||||
|
import { updateApps } from "../scripts/apps";
|
||||||
|
import { handleShell } from "../scripts/runner/shell";
|
||||||
|
import { handleWebSearch } from "../scripts/runner/websearch";
|
||||||
|
import { handleApplications } from "../scripts/runner/apps";
|
||||||
|
import { ResultWidget, ResultWidgetProps } from "../widget/runner/ResultWidget";
|
||||||
|
import Wp05 from "gi://Wp";
|
||||||
|
|
||||||
// TODO
|
export let runnerInstance: (Widget.Window|null) = null;
|
||||||
|
|
||||||
export interface RunnerProps {
|
export function closeRunner(gtkWindow?: Widget.Window) {
|
||||||
halign?: Gtk.Align;
|
const window = gtkWindow ? gtkWindow : runnerInstance;
|
||||||
valign?: Gtk.Align;
|
|
||||||
width?: number;
|
window?.destroy();
|
||||||
height?: number;
|
runnerInstance = null;
|
||||||
entryPlaceHolder?: string;
|
|
||||||
resultsPlaceholder?: Array<Gtk.Widget>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Runner(props?: RunnerProps) {
|
export function startRunnerDefault() {
|
||||||
|
return Runner.RunnerWindow({
|
||||||
|
entryPlaceHolder: "Start typing...",
|
||||||
|
resultsPlaceholder: () => [
|
||||||
|
new ResultWidget({
|
||||||
|
icon: "utilities-terminal-symbolic",
|
||||||
|
title: "Run shell commands",
|
||||||
|
description: "Start typing with '!' prefix to run shell commands"
|
||||||
|
} as ResultWidgetProps),
|
||||||
|
new ResultWidget({
|
||||||
|
icon: "application-x-executable-symbolic",
|
||||||
|
title: "Run your applications",
|
||||||
|
description: "Type the name of the application to search"
|
||||||
|
} as ResultWidgetProps),
|
||||||
|
new ResultWidget({
|
||||||
|
icon: "applications-internet-symbolic",
|
||||||
|
title: "Search the Web",
|
||||||
|
description: "Start typing with '?' prefix to search the web"
|
||||||
|
} as ResultWidgetProps)
|
||||||
|
]
|
||||||
|
} as Runner.RunnerProps);
|
||||||
|
}
|
||||||
|
|
||||||
const entryText: Variable<string> = new Variable<string>("");
|
export namespace Runner {
|
||||||
|
export type RunnerProps = {
|
||||||
|
halign?: Gtk.Align;
|
||||||
|
valign?: Gtk.Align;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
entryPlaceHolder?: string;
|
||||||
|
resultsPlaceholder?: () => Array<Gtk.Widget>;
|
||||||
|
};
|
||||||
|
|
||||||
const resultsBox: Widget.Box = new Widget.Box({
|
export const prefixes = new Map<string, (entry: string) => (ResultWidget|Array<ResultWidget>|null)>([
|
||||||
className: "results",
|
[ "!", handleShell ],
|
||||||
|
[ "?", handleWebSearch ],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function RunnerWindow(props?: RunnerProps): (Widget.Window|null) {
|
||||||
|
let subs: Array<() => void> = [];
|
||||||
|
const entryText: Variable<string> = new Variable<string>("");
|
||||||
|
let results: (Array<ResultWidget>|null) = null;
|
||||||
|
let selectedResultIndex = 0;
|
||||||
|
|
||||||
|
const searchEntry = new Widget.Entry({
|
||||||
|
className: "search",
|
||||||
|
onChanged: (entry) => entryText.set(entry.text),
|
||||||
|
placeholderText: props?.entryPlaceHolder || "",
|
||||||
|
primary_icon_name: "system-search"
|
||||||
|
} as Widget.EntryProps);
|
||||||
|
|
||||||
|
const resultsList: Gtk.ListBox = new Gtk.ListBox({
|
||||||
|
visible: true,
|
||||||
|
expand: true
|
||||||
|
} as Gtk.ListBox.ConstructorProps);
|
||||||
|
|
||||||
|
subs.push(entryText().subscribe((text: string) => {
|
||||||
|
const trimmedText = text.trim();
|
||||||
|
const pluginResult: (ResultWidget|Array<ResultWidget>|null|undefined) = handlePrefix(
|
||||||
|
trimmedText)?.(trimmedText.replace(trimmedText.charAt(0), ""));
|
||||||
|
results = Boolean(pluginResult) ?
|
||||||
|
(!Array.isArray(pluginResult) ?
|
||||||
|
[ pluginResult! ]
|
||||||
|
: pluginResult)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
[
|
||||||
|
new Widget.Box({
|
||||||
|
className: "not-found",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
visible: entryText((text: string) => text.trim().length > 0),
|
||||||
|
expand: true,
|
||||||
|
children: [
|
||||||
|
new Widget.Icon({
|
||||||
|
icon: "software-update-urgent-symbolic"
|
||||||
|
} as Widget.IconProps),
|
||||||
|
new Widget.Label({
|
||||||
|
label: "Couldn't find any results with this search. Maybe try pressing F5 and searching again?",
|
||||||
|
truncate: false,
|
||||||
|
wrap: true
|
||||||
|
} as Widget.LabelProps)
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps),
|
||||||
|
new Widget.Box({
|
||||||
|
className: "placeholder",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
expand: true,
|
||||||
|
visible: Boolean(props?.resultsPlaceholder),
|
||||||
|
children: props?.resultsPlaceholder &&
|
||||||
|
props?.resultsPlaceholder()
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
];
|
||||||
|
|
||||||
|
if(resultsList.get_children().length > 0) {
|
||||||
|
resultsList.get_children().map((listItem: Gtk.Widget) => {
|
||||||
|
resultsList.remove(listItem);
|
||||||
|
listItem.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(results && results.length > 0)
|
||||||
|
results.map((resultWidget: ResultWidget) => {
|
||||||
|
resultsList.insert(resultWidget, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedResultIndex = 0;
|
||||||
|
resultsList.select_row(resultsList.get_row_at_index(selectedResultIndex));
|
||||||
|
}));
|
||||||
|
|
||||||
|
if(!runnerInstance)
|
||||||
|
runnerInstance = PopupWindow({
|
||||||
|
namespace: "runner",
|
||||||
|
halign: props?.halign || Gtk.Align.CENTER,
|
||||||
|
valign: props?.valign || Gtk.Align.CENTER,
|
||||||
|
widthRequest: props?.width || 750,
|
||||||
|
heightRequest: props?.height || 450,
|
||||||
|
onKeyPressEvent: (_, event: Gdk.Event) => {
|
||||||
|
event.get_keyval()[1] === Gdk.KEY_F5 &&
|
||||||
|
updateApps();
|
||||||
|
|
||||||
|
if(event.get_keyval()[1] === Gdk.KEY_Down) {
|
||||||
|
resultsList.get_children().length > 0 &&
|
||||||
|
resultsList.select_row(resultsList.get_row_at_index(
|
||||||
|
(selectedResultIndex + 1) > (resultsList.get_children().length - 1) ?
|
||||||
|
0
|
||||||
|
: selectedResultIndex + 1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeAction: (_) => closeRunner(_),
|
||||||
|
onClose: () => subs.map(sub => sub()),
|
||||||
|
child: new Widget.Box({
|
||||||
|
className: "runner main",
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
children: [
|
||||||
|
searchEntry,
|
||||||
|
new Widget.Scrollable({
|
||||||
|
className: "results-scrollable",
|
||||||
|
vscroll: Gtk.PolicyType.AUTOMATIC,
|
||||||
|
hscroll: Gtk.PolicyType.NEVER,
|
||||||
|
expand: true,
|
||||||
|
child: resultsList
|
||||||
|
})
|
||||||
|
]
|
||||||
|
} as Widget.BoxProps)
|
||||||
|
} as PopupWindowProps);
|
||||||
|
|
||||||
|
return runnerInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handlePrefix(text: string): (((a: string) => (Array<ResultWidget>|ResultWidget|null)) | null) {
|
||||||
|
const prefix = text.charAt(0);
|
||||||
|
let result: (((a: string) => ResultWidget|Array<ResultWidget>|null)|null) = null;
|
||||||
|
|
||||||
|
if(/([a-z]|[A-Z]|[0-9])/.test(prefix))
|
||||||
|
result = handleApplications;
|
||||||
|
|
||||||
|
[...prefixes.keys()].map((curPrefix: string) => {
|
||||||
|
if(curPrefix === prefix)
|
||||||
|
result = prefixes.get(curPrefix)!;
|
||||||
|
});
|
||||||
|
|
||||||
} as Widget.BoxProps);
|
return result;
|
||||||
|
}
|
||||||
return PopupWindow({
|
|
||||||
namespace: "runner",
|
|
||||||
halign: props?.halign || Gtk.Align.CENTER,
|
|
||||||
valign: props?.valign || Gtk.Align.CENTER,
|
|
||||||
widthRequest: props?.width || 600,
|
|
||||||
heightRequest: props?.height || 500,
|
|
||||||
child: new Widget.Box({
|
|
||||||
className: "main",
|
|
||||||
children: [
|
|
||||||
new Widget.Entry({
|
|
||||||
className: "search",
|
|
||||||
onChanged: (entry) => entryText.set(entry.text),
|
|
||||||
} as Widget.EntryProps),
|
|
||||||
]
|
|
||||||
} as Widget.BoxProps)
|
|
||||||
} as PopupWindowProps);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,9 @@ export const Wallpaper: Widget.Window = new Widget.Window({
|
|||||||
exclusivity: Astal.Exclusivity.IGNORE,
|
exclusivity: Astal.Exclusivity.IGNORE,
|
||||||
keymode: Astal.Keymode.NONE,
|
keymode: Astal.Keymode.NONE,
|
||||||
visible: true,
|
visible: true,
|
||||||
|
style: new Gtk.Style(),
|
||||||
|
css: ".wallpaper { all: unset; }",
|
||||||
monitor: 0, //Needs rework for all monitors
|
monitor: 0, //Needs rework for all monitors
|
||||||
child: new Widget.Box({
|
|
||||||
className: "wallpaper",
|
|
||||||
} as Widget.BoxProps),
|
|
||||||
onButtonPressEvent: (_, event: Gdk.Event) => {
|
onButtonPressEvent: (_, event: Gdk.Event) => {
|
||||||
const [ , x, y ] = event.get_coords();
|
const [ , x, y ] = event.get_coords();
|
||||||
if(event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
if(event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ import { CenterWindow } from "./window/CenterWindow";
|
|||||||
import { FloatingNotifications } from "./window/FloatingNotifications";
|
import { FloatingNotifications } from "./window/FloatingNotifications";
|
||||||
import { GObject, register } from "astal";
|
import { GObject, register } from "astal";
|
||||||
import { LogoutMenu } from "./window/LogoutMenu";
|
import { LogoutMenu } from "./window/LogoutMenu";
|
||||||
import { Wallpaper } from "./window/Wallpaper";
|
import { AppsWindow } from "./window/AppsWindow";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get open windows / interact with windows(e.g.: close, open or toggle)
|
* get open windows / interact with windows(e.g.: close, open or toggle)
|
||||||
@@ -23,7 +23,7 @@ class WindowsClass extends GObject.Object {
|
|||||||
this.setWindow("center-window", CenterWindow);
|
this.setWindow("center-window", CenterWindow);
|
||||||
this.setWindow("logout-menu", LogoutMenu);
|
this.setWindow("logout-menu", LogoutMenu);
|
||||||
this.setWindow("floating-notifications", FloatingNotifications);
|
this.setWindow("floating-notifications", FloatingNotifications);
|
||||||
this.setWindow("wallpaper", Wallpaper);
|
this.setWindow("apps-window", AppsWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static setWindow(name: string, window: Gtk.Window): void {
|
public static setWindow(name: string, window: Gtk.Window): void {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
Config (
|
Config (
|
||||||
max_entries: 8,
|
max_entries: 8,
|
||||||
desktop_actions: false,
|
desktop_actions: false,
|
||||||
terminal: Some("kitty -c")
|
terminal: Some(Terminal(
|
||||||
|
command: "kitty",
|
||||||
|
args: "-c {}"
|
||||||
|
))
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
###############
|
###############
|
||||||
|
|
||||||
# Services/Daemons
|
# Services/Daemons
|
||||||
exec-once = systemctl enable --user --now hyprpolkitagent
|
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
||||||
exec-once = systemctl enable --user --now hypridle
|
exec-once = systemctl enable --user --now hypridle
|
||||||
exec-once = systemctl enable --user --now gnome-keyring-daemon
|
exec-once = systemctl enable --user --now gnome-keyring-daemon
|
||||||
exec-once = wl-paste --type text --watch cliphist store
|
exec-once = wl-paste --type text --watch cliphist store
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ $mainMod = SUPER # The master key
|
|||||||
$media = amberol # Media App
|
$media = amberol # Media App
|
||||||
$terminal = kitty # Terminal Emulator
|
$terminal = kitty # Terminal Emulator
|
||||||
$fm = nautilus # File Manager
|
$fm = nautilus # File Manager
|
||||||
$menu = anyrun # App Runner
|
$menu = astal runner || anyrun # App Runner
|
||||||
$dmenu = anyrun --plugins libstdin.so # dmenu app
|
$dmenu = anyrun --plugins libstdin.so # dmenu app
|
||||||
|
|
||||||
$lockscreen = hyprlock
|
$lockscreen = hyprlock
|
||||||
@@ -24,7 +24,7 @@ $screenshotSelect = hyprshot -m region -o $screenshotDir
|
|||||||
|
|
||||||
bind = $mainMod, K, exec, $terminal
|
bind = $mainMod, K, exec, $terminal
|
||||||
bind = $mainMod, Q, killactive
|
bind = $mainMod, Q, killactive
|
||||||
bind = $mainMod, E, exec, $fileManager
|
bind = $mainMod, E, exec, $fm
|
||||||
bind = $mainMod, F, togglefloating
|
bind = $mainMod, F, togglefloating
|
||||||
bind = $mainMod, SPACE, exec, $menu
|
bind = $mainMod, SPACE, exec, $menu
|
||||||
bind = $mainMod, P, pseudo,
|
bind = $mainMod, P, pseudo,
|
||||||
|
|||||||
+13
-10
@@ -35,19 +35,22 @@ windowrulev2 = movetoworkspace e, class:org.pulseaudio.pavucontrol
|
|||||||
windowrulev2 = animation slide right, class:org.pulseaudio.pavucontrol
|
windowrulev2 = animation slide right, class:org.pulseaudio.pavucontrol
|
||||||
windowrulev2 = animation slide right, class:blueberry.py
|
windowrulev2 = animation slide right, class:blueberry.py
|
||||||
windowrulev2 = animation slide right, class:io.github.kaii_lb.Overskride
|
windowrulev2 = animation slide right, class:io.github.kaii_lb.Overskride
|
||||||
layerrule = animation slide right, swaync-control-center
|
layerrule = animation slide, swaync-control-center
|
||||||
layerrule = animation fade, selection
|
layerrule = animation fade, selection
|
||||||
layerrule = animation fade, waybar
|
layerrule = animation fade, waybar
|
||||||
layerrule = animation fade, hyprpaper
|
layerrule = animation fade, hyprpaper
|
||||||
layerrule = animation slide right, swaync-notification-window
|
layerrule = animation slide right, swaync-notification-window
|
||||||
layerrule = animation fade, hyprpicker
|
layerrule = animation fade, hyprpicker
|
||||||
layerrule = animation fade, anyrun
|
layerrule = animation fade, anyrun
|
||||||
layerrule = animation slide right, eww-cc
|
layerrule = animation fade, ^(eww-(.*))$
|
||||||
layerrule = animation fade, eww-calendar
|
layerrule = animation slide, eww-cc
|
||||||
layerrule = animation fade, eww-volume
|
|
||||||
layerrule = animation fade, eww-powermenu
|
|
||||||
layerrule = animation fade, control-center
|
layerrule = animation fade, control-center
|
||||||
layerrule = animation fade, center-window # Bruh i need a better name for this :skull:
|
layerrule = animation fade, center-window # Bruh i need a better name for this :skull:
|
||||||
|
layerrule = animation fade, logout-menu
|
||||||
|
layerrule = animation fade, wallpaper
|
||||||
|
layerrule = animation fade, apps-window
|
||||||
|
layerrule = animation fade, runner
|
||||||
|
layerrule = animation fade, ask-popup
|
||||||
|
|
||||||
# Opacity
|
# Opacity
|
||||||
windowrulev2 = opacity .95 .95, class:kitty
|
windowrulev2 = opacity .95 .95, class:kitty
|
||||||
@@ -58,10 +61,6 @@ windowrulev2 = opacity .88 .88, class:hyprpolkitagent
|
|||||||
windowrulev2 = noblur, class:^()$, title:^()$ # Removes blur from context menus
|
windowrulev2 = noblur, class:^()$, title:^()$ # Removes blur from context menus
|
||||||
windowrulev2 = noblur, class:steam(.*)$
|
windowrulev2 = noblur, class:steam(.*)$
|
||||||
|
|
||||||
# Window Blur list
|
|
||||||
blurls = logout_dialog
|
|
||||||
blurls = kitty
|
|
||||||
|
|
||||||
# Layer Blur list
|
# Layer Blur list
|
||||||
layerrule = blur, waybar
|
layerrule = blur, waybar
|
||||||
layerrule = blur, eww-bar
|
layerrule = blur, eww-bar
|
||||||
@@ -73,13 +72,17 @@ layerrule = blur, top-bar
|
|||||||
layerrule = blur, osd
|
layerrule = blur, osd
|
||||||
layerrule = blur, control-center
|
layerrule = blur, control-center
|
||||||
layerrule = blur, center-window
|
layerrule = blur, center-window
|
||||||
#layerrule = blur, logout-menu
|
layerrule = blur, logout-menu
|
||||||
|
layerrule = blur, runner
|
||||||
|
layerrule = blur, ask-popup
|
||||||
|
layerrule = ignorealpha .7, runner
|
||||||
layerrule = ignorealpha .6, eww-volume
|
layerrule = ignorealpha .6, eww-volume
|
||||||
layerrule = ignorealpha .55, eww-bar
|
layerrule = ignorealpha .55, eww-bar
|
||||||
layerrule = ignorealpha .5, eww-calendar
|
layerrule = ignorealpha .5, eww-calendar
|
||||||
layerrule = ignorealpha .7, eww-cc
|
layerrule = ignorealpha .7, eww-cc
|
||||||
layerrule = ignorealpha .4, osd
|
layerrule = ignorealpha .4, osd
|
||||||
layerrule = ignorealpha .55, top-bar
|
layerrule = ignorealpha .55, top-bar
|
||||||
|
layerrule = ignorealpha .6, ask-popup
|
||||||
layerrule = ignorealpha .7, control-center
|
layerrule = ignorealpha .7, control-center
|
||||||
layerrule = ignorealpha .7, center-window
|
layerrule = ignorealpha .7, center-window
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# Made by retrozinndev (João Dias)
|
# Made by retrozinndev (João Dias)
|
||||||
# From https://github.com/retrozinndev/Hyprland-Dots
|
# From https://github.com/retrozinndev/Hyprland-Dots
|
||||||
|
|
||||||
style="darken" # lighten / darken
|
style="lighten" # lighten / darken
|
||||||
dmenu=$(sh "$XDG_CONFIG_HOME/hypr/scripts/get-dmenu.sh")
|
dmenu=$(sh "$XDG_CONFIG_HOME/hypr/scripts/get-dmenu.sh")
|
||||||
|
|
||||||
if [[ -z "$WALLPAPERS_DIR" ]]; then
|
if [[ -z "$WALLPAPERS_DIR" ]]; then
|
||||||
@@ -46,7 +46,7 @@ function Reload_wallpaper() {
|
|||||||
|
|
||||||
function Reload_pywal() {
|
function Reload_pywal() {
|
||||||
echo "[LOG] Reloading pywal colorscheme"
|
echo "[LOG] Reloading pywal colorscheme"
|
||||||
wal -q -t --cols16 $style -i "$wall"
|
wal -t --cols16 $style -i "$wall"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prompt wallpaper list
|
# Prompt wallpaper list
|
||||||
@@ -55,8 +55,7 @@ wall="$WALLPAPERS_DIR/$(ls $WALLPAPERS_DIR | $dmenu)"
|
|||||||
# Check if input wallpaper is empty
|
# Check if input wallpaper is empty
|
||||||
if [[ $wall == "$WALLPAPERS_DIR/" ]]; then
|
if [[ $wall == "$WALLPAPERS_DIR/" ]]; then
|
||||||
echo "No wallpaper has been selected by user!"
|
echo "No wallpaper has been selected by user!"
|
||||||
if [[ $RANDOM_WALLPAPER_WHEN_EMPTY == true ]]
|
if [[ $RANDOM_WALLPAPER_WHEN_EMPTY == true ]]; then
|
||||||
then
|
|
||||||
wall="$WALLPAPERS_DIR/$(ls $WALLPAPERS_DIR | shuf -n 1)"
|
wall="$WALLPAPERS_DIR/$(ls $WALLPAPERS_DIR | shuf -n 1)"
|
||||||
echo "Selected random from $WALLPAPERS_DIR: $wall"
|
echo "Selected random from $WALLPAPERS_DIR: $wall"
|
||||||
else
|
else
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
Reference in New Issue
Block a user