ags(bar, notifications, control-center): add status icons to bar, notification history fixed, notification history below control-center

This commit is contained in:
retrozinndev
2025-03-23 10:17:22 -03:00
parent 30d1fded84
commit 3db477598f
29 changed files with 746 additions and 363 deletions
+8 -2
View File
@@ -9,7 +9,10 @@ export default {
}, },
control_center: { control_center: {
tiles: { tiles: {
enabled: "Enabled",
disabled: "Disabled",
more: "More", more: "More",
network: { network: {
network: "Network", network: "Network",
connected: "Connected", connected: "Connected",
@@ -21,8 +24,11 @@ export default {
}, },
recording: { recording: {
title: "Screen Recording", title: "Screen Recording",
disabled_description: "Start recording", disabled_desc: "Start recording",
enabled_description: "Stop recording", enabled_desc: "Stop recording",
},
dnd: {
title: "Do Not Disturb"
} }
} }
}, },
+5
View File
@@ -9,6 +9,8 @@ export default {
}, },
control_center: { control_center: {
tiles: { tiles: {
enabled: "Ligado",
disabled: "Desligado",
more: "Mais", more: "Mais",
network: { network: {
@@ -24,6 +26,9 @@ export default {
title: "Gravação de Tela", title: "Gravação de Tela",
disabled_description: "Iniciar gravação", disabled_description: "Iniciar gravação",
enabled_description: "Parar gravação", enabled_description: "Parar gravação",
},
dnd: {
title: "Não Perturbe"
} }
} }
}, },
+47 -40
View File
@@ -7,34 +7,27 @@ import { ResultWidget, ResultWidgetProps } from "../widget/runner/ResultWidget";
export let runnerInstance: (Widget.Window|null) = null; export let runnerInstance: (Widget.Window|null) = null;
let onClickTimeout: (AstalIO.Time|undefined); let onClickTimeout: (AstalIO.Time|undefined);
export function closeRunner(gtkWindow?: Widget.Window) {
const window = gtkWindow ? gtkWindow : runnerInstance;
window?.destroy();
runnerInstance = null;
}
export function startRunnerDefault() { export function startRunnerDefault() {
return Runner.RunnerWindow({ return Runner.openRunner({
entryPlaceHolder: "Start typing...", entryPlaceHolder: "Start typing..."
resultsPlaceholder: () => [ } as Runner.RunnerProps,
new ResultWidget({ () => [
icon: "utilities-terminal-symbolic", new ResultWidget({
title: "Run shell commands", icon: "utilities-terminal-symbolic",
description: "Start typing with '!' prefix to run shell commands" title: "Run shell commands",
} as ResultWidgetProps), description: "Start typing with '!' prefix to run shell commands"
new ResultWidget({ } as ResultWidgetProps),
icon: "application-x-executable-symbolic", new ResultWidget({
title: "Run your applications", icon: "application-x-executable-symbolic",
description: "Type the name of the application to search" title: "Run your applications",
} as ResultWidgetProps), description: "Type the name of the application to search"
new ResultWidget({ } as ResultWidgetProps),
icon: "applications-internet-symbolic", new ResultWidget({
title: "Search the Web", icon: "applications-internet-symbolic",
description: "Start typing with '?' prefix to search the web" title: "Search the Web",
} as ResultWidgetProps) description: "Start typing with '?' prefix to search the web"
] } as ResultWidgetProps)
} as Runner.RunnerProps); ]);
} }
export namespace Runner { export namespace Runner {
@@ -44,9 +37,18 @@ export namespace Runner {
width?: number; width?: number;
height?: number; height?: number;
entryPlaceHolder?: string; entryPlaceHolder?: string;
resultsPlaceholder?: () => Array<ResultWidget>;
}; };
export function close(gtkWindow?: Widget.Window) {
const window = gtkWindow ? gtkWindow : runnerInstance;
[...plugins.values()].map(plugin =>
plugin && plugin.onClose && plugin.onClose());
window?.close();
runnerInstance = null;
}
const plugins = new Set<Runner.Plugin>([]); const plugins = new Set<Runner.Plugin>([]);
export interface Plugin { export interface Plugin {
@@ -54,14 +56,19 @@ export namespace Runner {
readonly prefix?: string; readonly prefix?: string;
/** name of the plugin. e.g.: websearch, shell */ /** name of the plugin. e.g.: websearch, shell */
readonly name?: string; readonly name?: string;
/** handle the user input to return results (does not contain prefix) */ /** ran on plugin load */
readonly init?: () => void;
/** handle the user input to return results (does not include plugin's prefix) */
readonly handle: (inputText: string) => (ResultWidget|Array<ResultWidget>|null|undefined); readonly handle: (inputText: string) => (ResultWidget|Array<ResultWidget>|null|undefined);
/** ran on runner close */
readonly onClose?: () => void;
} }
export function addPlugin(plugin: Runner.Plugin, force?: boolean) { export function addPlugin(plugin: Runner.Plugin, force?: boolean) {
if(!force && plugin.prefix && plugins.has(plugin)) if(!force && plugin.prefix && plugins.has(plugin))
throw new Error(`Runner plugin with prefix ${plugin.prefix} already exists`); throw new Error(`Runner plugin with prefix ${plugin.prefix} already exists`);
plugins.delete(plugin);
plugins.add(plugin); plugins.add(plugin);
} }
@@ -76,7 +83,7 @@ export namespace Runner {
return plugins.delete(plugin); return plugins.delete(plugin);
} }
export function RunnerWindow(props?: RunnerProps): (Widget.Window|null) { export function openRunner(props?: RunnerProps, placeholder?: () => Array<ResultWidget>): (Widget.Window|null) {
let subs: Array<() => void> = []; let subs: Array<() => void> = [];
const entryText: Variable<string> = new Variable<string>(""); const entryText: Variable<string> = new Variable<string>("");
@@ -87,8 +94,8 @@ export namespace Runner {
onActivate: (entry) => { onActivate: (entry) => {
const resultWidget = resultsList.get_selected_row()?.get_child(); const resultWidget = resultsList.get_selected_row()?.get_child();
if(resultWidget instanceof ResultWidget) { if(resultWidget instanceof ResultWidget) {
resultWidget.onClick();
entry.isFocus = false; entry.isFocus = false;
resultWidget.onClick();
} }
}, },
primary_icon_name: "system-search" primary_icon_name: "system-search"
@@ -114,8 +121,8 @@ export namespace Runner {
}); });
// Insert placeholder if somehow no results are found // Insert placeholder if somehow no results are found
if((!entryText || !widgets || widgets.length === 0) && props?.resultsPlaceholder) if(placeholder && (!entryText || !widgets || widgets.length === 0))
widgets.push(...props.resultsPlaceholder()); widgets.push(...placeholder());
// Insert results inside GtkListBox // Insert results inside GtkListBox
widgets.map((resultWidget: ResultWidget) => { widgets.map((resultWidget: ResultWidget) => {
@@ -142,25 +149,25 @@ export namespace Runner {
if(!runnerInstance) if(!runnerInstance)
runnerInstance = PopupWindow({ runnerInstance = PopupWindow({
namespace: "runner", namespace: "runner",
halign: props?.halign || Gtk.Align.CENTER,
valign: props?.valign || Gtk.Align.CENTER,
widthRequest: props?.width || 750, widthRequest: props?.width || 750,
heightRequest: props?.height || 450, heightRequest: props?.height || 450,
onKeyPressEvent: (_, event: Gdk.Event) => { onKeyPressEvent: (_, event: Gdk.Event) => {
const keyVal = event.get_keyval()[1]; const keyVal = event.get_keyval()[1];
if(!searchEntry.has_focus && keyVal !== Gdk.KEY_F5 if(!searchEntry.has_focus && keyVal !== Gdk.KEY_F5
&& keyVal !== Gdk.KEY_Down && keyVal !== Gdk.KEY_Up && keyVal !== Gdk.KEY_Down && keyVal !== Gdk.KEY_Up
&& keyVal !== Gdk.KEY_KP_Enter && keyVal !== Gdk.KEY_ISO_Enter) { && keyVal !== Gdk.KEY_KP_Enter && keyVal !== Gdk.KEY_ISO_Enter
&& keyVal !== Gdk.KEY_Escape) {
searchEntry.grab_focus_without_selecting(); searchEntry.grab_focus_without_selecting();
} }
event.get_keyval()[1] === Gdk.KEY_F5 && event.get_keyval()[1] === Gdk.KEY_F5 &&
updateApps(); updateApps();
},
closeAction: (_) => {
close(_);
subs.map(sub => sub());
}, },
closeAction: (_) => closeRunner(_),
onClose: () => subs.map(sub => sub()),
child: new Widget.Box({ child: new Widget.Box({
className: "runner main", className: "runner main",
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
+21 -2
View File
@@ -1,4 +1,6 @@
import { Astal } from "astal/gtk3";
import AstalApps from "gi://AstalApps"; import AstalApps from "gi://AstalApps";
import AstalHyprland from "gi://AstalHyprland";
const astalApps: AstalApps.Apps = new AstalApps.Apps(); const astalApps: AstalApps.Apps = new AstalApps.Apps();
let appsList: Array<AstalApps.Application> = astalApps.get_list(); let appsList: Array<AstalApps.Application> = astalApps.get_list();
@@ -16,6 +18,10 @@ export function getAstalApps(): AstalApps.Apps {
return astalApps; return astalApps;
} }
export function cleanExec(app: AstalApps.Application): void {
AstalHyprland.get_default().dispatch("exec", app.executable.replace(/(%f|%F|%u|%U|%i|%c|%k)/g, ""));
}
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> = [];
@@ -29,6 +35,19 @@ export function getAppsByName(appName: string): (Array<AstalApps.Application>|un
} }
export function getAppIcon(appName: string): (string|undefined) { export function getAppIcon(appName: string): (string|undefined) {
const found: (Array<AstalApps.Application>|undefined) = getAppsByName(appName); if(Astal.Icon.lookup_icon(appName))
return found ? found[0]?.iconName : undefined; return appName;
if(Astal.Icon.lookup_icon(appName.toLowerCase()))
return appName.toLowerCase();
const nameReverseDNS = appName.split('.');
if(Astal.Icon.lookup_icon(nameReverseDNS[nameReverseDNS.length - 1]))
return nameReverseDNS[nameReverseDNS.length - 1];
const found: (AstalApps.Application|undefined) = getAppsByName(appName)?.[0];
if(Boolean(found))
return found?.iconName;
return "application-x-executable-symbolic";
} }
+79 -20
View File
@@ -6,13 +6,24 @@ export const
NOTIFICATION_TIMEOUT_NORMAL: number = 4000, NOTIFICATION_TIMEOUT_NORMAL: number = 4000,
NOTIFICATION_TIMEOUT_LOW: number = 2000; NOTIFICATION_TIMEOUT_LOW: number = 2000;
export interface HistoryNotification {
id: number;
appName: string;
body: string;
summary: string;
urgency: AstalNotifd.Urgency;
time: number;
image?: string;
}
@register({ GTypeName: "Notifications" }) @register({ GTypeName: "Notifications" })
class Notifications extends GObject.Object { class Notifications extends GObject.Object {
private static instance: (Notifications|null) = null; private static instance: (Notifications|null) = null;
#notifications: Array<AstalNotifd.Notification> = []; #notifications: Array<AstalNotifd.Notification> = [];
#history: Array<AstalNotifd.Notification> = []; #history: Array<HistoryNotification> = [];
#connections: Array<number>; #connections: Array<number>;
#historyLimit: number = 10;
@property() @property()
@@ -21,6 +32,14 @@ class Notifications extends GObject.Object {
@property() @property()
public get history() { return this.#history }; public get history() { return this.#history };
@property()
public get historyLimit() { return this.#historyLimit };
public set historyLimit(newValue: number) {
this.#historyLimit = newValue;
this.notify("historyLimit");
}
@signal(AstalNotifd.Notification) @signal(AstalNotifd.Notification)
declare notificationAdded: (notification: AstalNotifd.Notification) => void; declare notificationAdded: (notification: AstalNotifd.Notification) => void;
@@ -28,7 +47,7 @@ class Notifications extends GObject.Object {
@signal(Number) @signal(Number)
declare notificationRemoved: (id: number) => void; declare notificationRemoved: (id: number) => void;
@signal(AstalNotifd.Notification) @signal(Object)
declare historyAdded: (notification: AstalNotifd.Notification) => void; declare historyAdded: (notification: AstalNotifd.Notification) => void;
@signal(Number) @signal(Number)
@@ -42,7 +61,7 @@ class Notifications extends GObject.Object {
super(); super();
this.#connections = [ this.#connections = [
AstalNotifd.get_default().connect("notified", (notifd, id, _replaced) => { AstalNotifd.get_default().connect("notified", (notifd, id) => {
const notification = notifd.get_notification(id); const notification = notifd.get_notification(id);
const notifTimeout = notification.urgency === AstalNotifd.Urgency.LOW ? const notifTimeout = notification.urgency === AstalNotifd.Urgency.LOW ?
NOTIFICATION_TIMEOUT_LOW NOTIFICATION_TIMEOUT_LOW
@@ -50,29 +69,39 @@ class Notifications extends GObject.Object {
NOTIFICATION_TIMEOUT_URGENT NOTIFICATION_TIMEOUT_URGENT
: NOTIFICATION_TIMEOUT_NORMAL); : NOTIFICATION_TIMEOUT_NORMAL);
if(this.getNotifd().dontDisturb) {
this.addHistory(notification, () => notification.dismiss());
return;
}
this.addNotification(notification, () => { this.addNotification(notification, () => {
if(notification.urgency !== AstalNotifd.Urgency.CRITICAL || if(notification.urgency !== AstalNotifd.Urgency.CRITICAL ||
(notification.urgency === AstalNotifd.Urgency.CRITICAL && (notification.urgency === AstalNotifd.Urgency.CRITICAL &&
NOTIFICATION_TIMEOUT_URGENT > 0)) { NOTIFICATION_TIMEOUT_URGENT > 0)) {
let notifTimer: AstalIO.Time; let notifTimer: (AstalIO.Time|undefined) = undefined;
let replacedConnectionId: number; let replacedConnectionId: number;
const removeFun = () => { // Funny name haha lmao remove fun :skull: const removeFun = () => { // Funny name haha lmao remove fun :skull:
this.removeNotification(id); notifTimer = undefined;
replacedConnectionId && this.disconnect(replacedConnectionId); this.addHistory(notification, () => {
replacedConnectionId && this.disconnect(replacedConnectionId);
this.removeNotification(id);
});
} }
notifTimer = timeout(notifTimeout, removeFun);
replacedConnectionId = this.connect("notification-replaced", (_, id: number) => { replacedConnectionId = this.connect("notification-replaced", (_, id: number) => {
if(notification.id === id) { if(notification.id === id) {
notifTimer.cancel(); notifTimer?.cancel();
notifTimer = timeout(notifTimeout, removeFun); notifTimer = timeout(notifTimeout, removeFun);
} }
}); });
notifTimer = timeout(notifTimeout, removeFun);
} }
}); });
}), }),
AstalNotifd.get_default().connect("resolved", (notifd, id, _reason) => { AstalNotifd.get_default().connect("resolved", (notifd, id, _reason) => {
this.removeNotification(id); this.removeNotification(id);
this.addHistory(notifd.get_notification(id)); this.addHistory(notifd.get_notification(id));
@@ -94,17 +123,41 @@ class Notifications extends GObject.Object {
} }
private addHistory(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void { private addHistory(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void {
const newArray = this.#history.reverse().filter((item) => item.id !== notif.id); if(!notif) return;
newArray.push(notif);
this.#history.length === this.#historyLimit &&
this.removeHistory(this.#history[this.#history.length - 1]);
const newArray = this.#history.length > 0 ? this.#history.reverse().filter((item) => item.id !== notif.id) : [];
newArray.push({
id: notif.id,
appName: notif.appName,
body: notif.body,
summary: notif.summary,
urgency: notif.urgency,
time: notif.time,
image: notif.image ? notif.image : undefined
} as HistoryNotification);
this.#history = newArray.reverse(); this.#history = newArray.reverse();
this.notify("history"); this.notify("history");
this.emit("history-added", notif); this.emit("history-added", this.#history[0]);
onAdded && onAdded(notif); onAdded && onAdded(notif);
} }
public removeHistory(notif: (AstalNotifd.Notification|number)): void { public clearHistory(): void {
const notifId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif; for(let i = 0; i < this.history.length; i++) {
this.#history = this.#history.filter((item: AstalNotifd.Notification) => const notif = this.history[this.history.length-1];
if(this.#history.pop()) {
this.emit("history-removed", notif.id);
this.notify("history");
}
}
}
public removeHistory(notif: (HistoryNotification|number)): void {
const notifId = (typeof notif === "number") ? notif : notif.id;
this.#history = this.#history.filter((item: HistoryNotification) =>
item.id !== notifId); item.id !== notifId);
this.notify("history"); this.notify("history");
@@ -125,21 +178,27 @@ class Notifications extends GObject.Object {
} }
public removeNotification(notif: (AstalNotifd.Notification|number)): void { public removeNotification(notif: (AstalNotifd.Notification|number)): void {
const notification = (notif instanceof AstalNotifd.Notification) ? notif : AstalNotifd.get_default().get_notification(notif); const notificationId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif;
this.#notifications = this.#notifications.filter((item: AstalNotifd.Notification) => this.#notifications = this.#notifications.filter((item: AstalNotifd.Notification) =>
item.id !== notification.id); item.id !== notificationId);
notification.dismiss(); AstalNotifd.get_default().get_notification(notificationId)?.dismiss();
this.notify("notifications"); this.notify("notifications");
this.emit("notification-removed", notification.id); this.emit("notification-removed", notificationId);
} }
public toggleDoNotDisturb(): boolean { public toggleDoNotDisturb(): boolean {
if(AstalNotifd.get_default().dontDisturb) { if(AstalNotifd.get_default().dontDisturb) {
AstalNotifd.get_default().dontDisturb = false;
return false;
} }
AstalNotifd.get_default().dontDisturb = true;
return true;
} }
public getNotifd(): AstalNotifd.Notifd { return AstalNotifd.get_default(); }
connect(signal: string, callback: (...args: any[]) => void): number { connect(signal: string, callback: (...args: any[]) => void): number {
return super.connect(signal, callback); return super.connect(signal, callback);
} }
+21 -21
View File
@@ -71,42 +71,42 @@ class WireplumberClass extends GObject.Object {
); );
} }
public increaseSinkVolume(volumeIncrease: number): void { public increaseEndpointVolume(endpoint: AstalWp.Endpoint, volumeIncrease: number): void {
if((this.getSinkVolume() + volumeIncrease) > this.maxSinkVolume) { volumeIncrease = Math.abs(volumeIncrease) / 100;
this.setSinkVolume(this.maxSinkVolume);
if((endpoint.get_volume() + volumeIncrease) > this.maxSinkVolume) {
endpoint.set_volume(1.0);
return; return;
} }
this.setSinkVolume(this.getSinkVolume() + volumeIncrease); endpoint.set_volume(endpoint.get_volume() + volumeIncrease);
}
public increaseSinkVolume(volumeIncrease: number): void {
this.increaseEndpointVolume(this.getDefaultSink(), volumeIncrease);
} }
public increaseSourceVolume(volumeIncrease: number): void { public increaseSourceVolume(volumeIncrease: number): void {
if((this.getSourceVolume() + volumeIncrease) > this.maxSourceVolume) { this.increaseEndpointVolume(this.getDefaultSource(), volumeIncrease);
this.setSourceVolume(this.maxSourceVolume); }
public decreaseEndpointVolume(endpoint: AstalWp.Endpoint, volumeDecrease: number): void {
volumeDecrease = Math.abs(volumeDecrease) / 100;
if((endpoint.get_volume() - volumeDecrease) < 0) {
endpoint.set_volume(0);
return; return;
} }
this.setSourceVolume(this.getSourceVolume() + volumeIncrease); endpoint.set_volume(endpoint.get_volume() - volumeDecrease);
} }
public decreaseSinkVolume(volumeDecrease: number): void { public decreaseSinkVolume(volumeDecrease: number): void {
const absDecrease = Math.abs(volumeDecrease); this.decreaseEndpointVolume(this.getDefaultSink(), volumeDecrease);
if((this.getSinkVolume() - absDecrease) < 0) {
this.setSinkVolume(0);
return;
}
this.setSinkVolume(this.getSinkVolume() - absDecrease);
} }
public decreaseSourceVolume(volumeDecrease: number): void { public decreaseSourceVolume(volumeDecrease: number): void {
const absDecrease = Math.abs(volumeDecrease); this.decreaseEndpointVolume(this.getDefaultSource(), volumeDecrease);
if((this.getSourceVolume() - absDecrease) < 0)
return this.setSourceVolume(0);
this.setSourceVolume(this.getSourceVolume() - absDecrease);
} }
public muteSink(): void { public muteSink(): void {
+11 -2
View File
@@ -72,6 +72,14 @@ window.ask-popup {
font-size: 12px; font-size: 12px;
} }
& label.time {
font-size: 11px;
font-weight: 500;
color: colors.$fg-disabled;
margin-right: 6px;
}
& button.close { & button.close {
padding: 2px; padding: 2px;
border-radius: 8px; border-radius: 8px;
@@ -177,13 +185,14 @@ menu {
scrollbar trough { scrollbar trough {
@include mixins.reset-props; @include mixins.reset-props;
background: colors.$bg-translucent; background: colors.$bg-translucent;
border-top-left-radius: 8px; border-radius: 8px;
border-bottom-left-radius: 8px;
padding: 2px; padding: 2px;
& slider { & slider {
@include mixins.reset-props; @include mixins.reset-props;
min-width: .85em; min-width: .85em;
background: colors.$bg-tertiary; background: colors.$bg-tertiary;
border-radius: 12px; border-radius: 12px;
+40 -2
View File
@@ -1,5 +1,43 @@
.apps-window.container { @use "mixins";
& > entry { @use "colors";
.apps-window-container {
padding: 24px;
& > entry {
background: rgba(colors.$bg-primary, .4);
padding: 10px 9px;
margin-bottom: 32px;
border-radius: 12px;
min-width: 350px;
&:focus {
box-shadow: inset 0 0 0 2px colors.$bg-secondary;
}
& image.left {
margin-right: 6px;
}
}
& flowbox {
padding: 16px 24px;
& > flowboxchild > button {
padding: 8px;
border-radius: 24px;
&:hover {
background: colors.$bg-translucent;
}
& icon {
font-size: 64px;
}
& label {
margin-top: 6px;
}
}
} }
} }
+8 -5
View File
@@ -171,7 +171,7 @@
} }
} }
.audio { .status {
@include mixins.reset-props; @include mixins.reset-props;
&:hover > box, &:hover > box,
@@ -211,13 +211,17 @@
font-size: 12px; font-size: 12px;
} }
& .bell { & .status-icons {
margin: 0 4px; padding: 0 4px;
& > * {
margin: 0 4px;
}
} }
} }
} }
.logo { .apps {
& > box { & > box {
border-radius: 12px; border-radius: 12px;
transition: 100ms linear; transition: 100ms linear;
@@ -225,7 +229,6 @@
& > label { & > label {
font-size: 14px; font-size: 14px;
margin-right: 1px;
} }
} }
&:hover { &:hover {
+46 -8
View File
@@ -66,6 +66,43 @@
} }
} }
box.history {
margin-top: 10px;
background: colors.$bg-translucent;
border-radius: 24px;
padding: 20px 14px;
& scrollable viewport .notifications > eventbox {
& > box {
margin: 4px 0;
}
&:first-child {
& > box {
margin-top: 0;
}
}
&:last-child {
& > box {
margin-bottom: 0;
}
}
}
& > .button-row {
& button {
& label.nf {
font-size: 16px;
}
& label:not(.nf) {
font-size: 12px;
font-weight: 600;
}
}
}
}
.tiles-container { .tiles-container {
@include mixins.reset-props; @include mixins.reset-props;
@@ -150,15 +187,16 @@
font-weight: 500; font-weight: 500;
} }
&.bluetooth { & button {
.connections button { @include mixins.hover-shadow;
@include mixins.hover-shadow;
padding: 6px;
border-radius: 12px;
&.connected { padding: 6px;
background: colors.$bg-tertiary; border-radius: 12px;
} }
&.bluetooth {
button.connected {
background: colors.$bg-tertiary;
} }
} }
} }
+20 -20
View File
@@ -1,26 +1,26 @@
// SCSS Variables // SCSS Variables
// Generated by 'wal' // Generated by 'wal'
$wallpaper: "/home/joaov/wallpapers/Miku Guitar.jpg"; $wallpaper: "/home/joaov/wallpapers/Garden Kita.png";
// Special // Special
$background: #171418; $background: #101212;
$foreground: #c5c4c5; $foreground: #c3c3c3;
$cursor: #c5c4c5; $cursor: #c3c3c3;
// Colors // Colors
$color0: #171418; $color0: #101212;
$color1: #607985; $color1: #778839;
$color2: #208FB6; $color2: #6C965F;
$color3: #4C9CB4; $color3: #B4B350;
$color4: #63ADC9; $color4: #96A460;
$color5: #C3B49C; $color5: #658388;
$color6: #89BBCF; $color6: #B0B493;
$color7: #96909b; $color7: #8e9898;
$color8: #715c71; $color8: #596d6d;
$color9: #6aa4bf; $color9: #a3b757;
$color10: #62a5bc; $color10: #85c373;
$color11: #78b3c5; $color11: #c6c67a;
$color12: #90becf; $color12: #b7c67a;
$color13: #dac7ab; $color13: #6eb7c1;
$color14: #a7cbd9; $color14: #ced59e;
$color15: #c5c4c5; $color15: #c3c3c3;
+53 -34
View File
@@ -2,6 +2,8 @@ import { Astal, Gtk, Widget } from "astal/gtk3";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
import { Separator } from "./Separator"; import { Separator } from "./Separator";
import Pango from "gi://Pango"; import Pango from "gi://Pango";
import { HistoryNotification } from "../scripts/notifications";
import { GLib } from "astal";
export function getUrgencyString(notif: AstalNotifd.Notification) { export function getUrgencyString(notif: AstalNotifd.Notification) {
switch(notif.urgency) { switch(notif.urgency) {
@@ -14,24 +16,29 @@ export function getUrgencyString(notif: AstalNotifd.Notification) {
return "normal"; return "normal";
} }
export function NotificationWidget(notification: AstalNotifd.Notification|number, export function NotificationWidget(notification: AstalNotifd.Notification|number|HistoryNotification,
onClose?: (notif: AstalNotifd.Notification) => void): Gtk.Widget { onClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void,
showTime?: boolean /* It's showTime :speaking_head: :boom: :bangbang: */): Gtk.Widget {
notification = (notification instanceof AstalNotifd.Notification) ? notification = (typeof notification === "number") ?
notification AstalNotifd.get_default().get_notification(notification)
: AstalNotifd.get_default().get_notification(notification); : notification;
return new Widget.EventBox({ return new Widget.EventBox({
onClick: () => { onClick: () => {
if(notification.actions.length >= 1 && notification.actions[0].label.toLowerCase() === "view") { if(notification instanceof AstalNotifd.Notification) {
notification.invoke(notification.actions[0]!.id); const viewAction = notification.actions.filter(action => action.label.toLowerCase() === "view")?.[0];
onClose && onClose(notification); if(viewAction) notification.invoke(viewAction.id);
} }
onClose && onClose(notification);
}, },
hexpand: true,
vexpand: false,
child: new Widget.Box({ child: new Widget.Box({
className: `notification ${getUrgencyString(notification)}`, className: `notification ${ (notification instanceof AstalNotifd.Notification) ? getUrgencyString(notification) : "" }`,
homogeneous: false, homogeneous: false,
expand: false, expand: true,
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
children: [ children: [
new Widget.Box({ new Widget.Box({
@@ -42,7 +49,7 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
children: [ children: [
new Widget.Icon({ new Widget.Icon({
className: "icon app-icon", className: "icon app-icon",
icon: Astal.Icon.lookup_icon(notification.appIcon) ? icon: (notification instanceof AstalNotifd.Notification) && Astal.Icon.lookup_icon(notification.appIcon) ?
notification.appIcon notification.appIcon
: (Astal.Icon.lookup_icon(notification.appName.toLowerCase()) ? : (Astal.Icon.lookup_icon(notification.appName.toLowerCase()) ?
notification.appName.toLowerCase() notification.appName.toLowerCase()
@@ -59,15 +66,25 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
hexpand: true, hexpand: true,
label: notification.appName || "Unknown Application" label: notification.appName || "Unknown Application"
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Button({ new Widget.Box({
className: "close nf",
halign: Gtk.Align.END, halign: Gtk.Align.END,
onClick: () => onClose && onClose(notification), children: [
image: new Widget.Icon({ new Widget.Label({
className: "close icon", xalign: 1,
icon: "window-close-symbolic" visible: !showTime ? false : true,
} as Widget.IconProps) className: "time",
} as Widget.ButtonProps) label: GLib.DateTime.new_from_unix_utc(notification.time).format("%H:%M"),
} as Widget.LabelProps),
new Widget.Button({
className: "close nf",
onClick: () => onClose && onClose(notification),
image: new Widget.Icon({
className: "close icon",
icon: "window-close-symbolic"
} as Widget.IconProps)
} as Widget.ButtonProps)
]
} as Widget.BoxProps)
] ]
} as Widget.BoxProps), } as Widget.BoxProps),
Separator({ Separator({
@@ -112,21 +129,23 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
new Widget.Box({ new Widget.Box({
className: "actions button-row", className: "actions button-row",
hexpand: true, hexpand: true,
visible: (notification.actions.length === 1 && visible: (notification instanceof AstalNotifd.Notification) ?
notification.actions[0].label.toLowerCase() === "view") (notification.actions.filter(action => action.label.toLowerCase() !== "view").length > 0)
|| notification.actions.length === 0 ? false : true, : false,
children: notification.actions.map((action: AstalNotifd.Action, i: number) => children: (notification instanceof AstalNotifd.Notification) ?
new Widget.Button({ notification.actions.filter(action => action.label.toLowerCase() !== "view")
className: "action", .map((action: AstalNotifd.Action) =>
visible: i === 0 ? (action.label.toLowerCase() !== "view") : true, new Widget.Button({
label: action.label, className: "action",
hexpand: true, label: action.label,
onClicked: () => { hexpand: true,
notification.invoke(action.id); onClicked: () => {
onClose && onClose(notification); notification.invoke(action.id);
} onClose && onClose(notification);
} as Widget.ButtonProps) }
) } as Widget.ButtonProps)
)
: []
} as Widget.BoxProps) } as Widget.BoxProps)
] ]
} as Widget.BoxProps), } as Widget.BoxProps),
+8 -8
View File
@@ -18,6 +18,7 @@ export type PopupWindowProps = Pick<Widget.WindowProps,
| "heightRequest" | "heightRequest"
| "child" | "child"
| "monitor" | "monitor"
| "setup"
| "exclusivity"> & { | "exclusivity"> & {
marginTop?: number; marginTop?: number;
marginLeft?: number; marginLeft?: number;
@@ -42,8 +43,8 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
layer: props?.layer || Astal.Layer.OVERLAY, layer: props?.layer || Astal.Layer.OVERLAY,
focusOnMap: true, focusOnMap: true,
visible: props?.visible, visible: props?.visible,
acceptFocus: true,
monitor: props?.monitor || 0, monitor: props?.monitor || 0,
setup: props.setup,
onButtonPressEvent: (_, event: Gdk.Event) => { onButtonPressEvent: (_, event: Gdk.Event) => {
const [, posX, posY] = event.get_coords(); const [, posX, posY] = event.get_coords();
const childAllocation = _.get_child()!.get_allocation(); const childAllocation = _.get_child()!.get_allocation();
@@ -70,20 +71,19 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
: `popup ${props?.className || ""}`, : `popup ${props?.className || ""}`,
halign: props?.halign || Gtk.Align.CENTER, halign: props?.halign || Gtk.Align.CENTER,
valign: props?.valign || Gtk.Align.CENTER, valign: props?.valign || Gtk.Align.CENTER,
expand: props?.expand || false,
widthRequest: props?.widthRequest,
heightRequest: props?.heightRequest,
hexpand: props?.hexpand || false,
vexpand: props?.vexpand || false,
visible: true,
css: `.popup { css: `.popup {
margin-top: ${props.marginTop || 0}px; margin-top: ${props.marginTop || 0}px;
margin-bottom: ${props.marginBottom || 0}px; margin-bottom: ${props.marginBottom || 0}px;
margin-left: ${props.marginLeft || 0}px; margin-left: ${props.marginLeft || 0}px;
margin-right: ${props.marginRight || 0}px; margin-right: ${props.marginRight || 0}px;
}`, }`,
expand: props.expand,
vexpand: props.vexpand,
hexpand: props.hexpand,
widthRequest: props.widthRequest,
heightRequest: props.heightRequest,
onButtonPressEvent: () => true, onButtonPressEvent: () => true,
child: props.child child: props.child
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.WindowProps);; } as Widget.WindowProps);
} }
@@ -1,15 +1,15 @@
import { Gtk, Widget } from "astal/gtk3"; import { Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland"; import { tr } from "../../i18n/intl";
import { trGet } from "../../i18n/intl"; import { Windows } from "../../windows";
export function Logo(): Gtk.Widget { export function Apps(): Gtk.Widget {
return new Widget.EventBox({ return new Widget.EventBox({
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"), onClickRelease: () => Windows.getWindow("apps-window")?.show(),
className: "logo", className: "apps",
child: new Widget.Box({ child: new Widget.Box({
child: new Widget.Label({ child: new Widget.Label({
className: "nf", className: "nf",
tooltipText: trGet()["bar"]["apps"]["tooltip"], tooltipText: tr("bar.apps.tooltip"),
label: "" label: ""
} as Widget.LabelProps) } as Widget.LabelProps)
} as Widget.BoxProps) } as Widget.BoxProps)
-62
View File
@@ -1,62 +0,0 @@
import { bind, Process } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import { Wireplumber } from "../../scripts/volume";
import { ControlCenter } from "../../window/ControlCenter";
export function Audio(): Gtk.Widget {
return new Widget.EventBox({
className: bind(ControlCenter, "visible").as((visible: boolean) =>
visible ? "audio open" : "audio"),
onClick: () => Process.exec_async("astal toggle control-center", () => {}),
child: new Widget.Box({
children: [
new Widget.EventBox({
className: "sink",
onScroll: (_, event) =>
event.delta_y > 0 ?
Wireplumber.getDefault().decreaseSinkVolume(5)
:
Wireplumber.getDefault().increaseSinkVolume(5),
child: new Widget.Box({
children: [
new Widget.Label({
className: "nf",
label: "󰕾"
} as Widget.LabelProps),
new Widget.Label({
className: "volume",
label: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) =>
Math.floor(volume * 100) + "%")
} as Widget.LabelProps)
]
})
} as Widget.EventBoxProps),
new Widget.EventBox({
className: "source",
onScroll: (_, event) =>
event.delta_y > 0 ?
Wireplumber.getDefault().decreaseSourceVolume(5)
:
Wireplumber.getDefault().increaseSourceVolume(5),
child: new Widget.Box({
children: [
new Widget.Label({
className: "nf",
label: "󰍬"
} as Widget.LabelProps),
new Widget.Label({
className: "volume",
label: bind(Wireplumber.getDefault().getDefaultSource(), "volume").as((volume: number) =>
Math.floor(volume * 100) + "%")
} as Widget.LabelProps)
]
})
} as Widget.EventBoxProps),
new Widget.Label({
className: "bell nf",
label: "󰂚"
} as Widget.LabelProps)
]
} as Widget.BoxProps)
} as Widget.EventBoxProps);
}
+1 -5
View File
@@ -15,11 +15,7 @@ export function FocusedClient(): Gtk.Widget {
vexpand: true, vexpand: true,
css: ".icon { font-size: 18px; }", css: ".icon { font-size: 18px; }",
icon: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) => icon: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
client ? client ? getAppIcon(client.initialClass) : "image-missing")
(getAppIcon(client.initialClass) || client.initialClass)
:
"image-missing"
)
}), }),
new Widget.Box({ new Widget.Box({
className: "text-content", className: "text-content",
+1 -1
View File
@@ -105,7 +105,7 @@ export function Media(): Gtk.Widget {
orientation: Gtk.Orientation.HORIZONTAL, orientation: Gtk.Orientation.HORIZONTAL,
size: 2, size: 2,
cssColor: `rgb(180, 180, 180)`, cssColor: `rgb(180, 180, 180)`,
alpha: 1 alpha: 0.3
} as SeparatorProps), } as SeparatorProps),
new Widget.Label({ new Widget.Label({
className: "artist", className: "artist",
+128
View File
@@ -0,0 +1,128 @@
import AstalBluetooth from "gi://AstalBluetooth";
import AstalNetwork from "gi://AstalNetwork";
import AstalWp from "gi://AstalWp";
import { bind, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import { Wireplumber } from "../../scripts/volume";
import { ControlCenter } from "../../window/ControlCenter";
import { Notifications } from "../../scripts/notifications";
import { Windows } from "../../windows";
export function Status(): Gtk.Widget {
return new Widget.EventBox({
className: bind(ControlCenter, "visible").as((visible: boolean) =>
visible ? "status open" : "status"),
onClick: () => Windows.toggle(ControlCenter!),
child: new Widget.Box({
children: [
volumeStatusSlider({
className: "sink",
endpoint: Wireplumber.getDefault().getDefaultSink(),
icon: "󰕾"
}),
volumeStatusSlider({
className: "source",
endpoint: Wireplumber.getDefault().getDefaultSource(),
icon: "󰍬"
}),
StatusIcons()
]
} as Widget.BoxProps)
} as Widget.EventBoxProps);
}
function volumeStatusSlider(props: { className?: string, endpoint: AstalWp.Endpoint, icon: string }): Gtk.Widget {
return new Widget.EventBox({
className: props.className,
onScroll: (_, event) =>
event.delta_y > 0 ?
Wireplumber.getDefault().decreaseEndpointVolume(props.endpoint, 5)
:
Wireplumber.getDefault().increaseEndpointVolume(props.endpoint, 5),
setup: (eventbox) => {
const connections: Array<number> = [];
connections.push(eventbox.connect("destroy-event", () =>
connections.map(id => eventbox.disconnect(id))));
eventbox.add(new Widget.Box({
children: [
new Widget.Label({
className: "nf",
label: props.icon,
} as Widget.LabelProps),
new Widget.Revealer({
revealChild: false,
transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT,
transitionDuration: 350,
setup: (revealer) => {
connections.push(
eventbox.connect("hover", () => revealer.revealChild = true),
eventbox.connect("hover-lost", () => revealer.revealChild = false));
revealer.add(new Widget.Slider({
className: "slider",
onDragged: (slider) => props.endpoint.set_volume(slider.value / 100),
value: bind(props.endpoint, "volume").as((volume) =>
Math.floor(volume * 100)),
max: 100
} as Widget.SliderProps));
}
} as Widget.RevealerProps),
new Widget.Label({
className: "volume",
label: bind(props.endpoint, "volume").as((volume: number) =>
Math.floor(volume * 100) + "%")
} as Widget.LabelProps),
]
} as Widget.BoxProps))
}
} as Widget.EventBoxProps)
}
function StatusIcons(): Gtk.Widget {
return new Widget.Box({
className: "status-icons",
children: [
new Widget.Label({
className: "bluetooth nf state",
label: Variable.derive([
bind(AstalBluetooth.get_default(), "isPowered"),
bind(AstalBluetooth.get_default(), "isConnected")
], (powered, connected) => {
return powered ? (
connected ? "󰂱"
: "󰂯"
) : "󰂲"
})()
} as Widget.LabelProps),
new Widget.Label({
className: "network nf state",
label: Variable.derive([
bind(AstalNetwork.get_default(), "primary"),
bind(AstalNetwork.get_default(), "wired"),
bind(AstalNetwork.get_default(), "wifi")
],
(primary, wired, wifi) => {
switch(primary) {
case AstalNetwork.Primary.WIRED: return wired ?
"󰛳"
: "󰛵";
case AstalNetwork.Primary.WIFI: return wifi ?
"󰤨"
: "󰤭";
}
return "󰲊";
})()
} as Widget.LabelProps),
new Widget.Label({
className: "bell nf state",
label: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd: boolean) =>
dnd ? "󰂠" : "󰂚")
} as Widget.LabelProps),
]
} as Widget.BoxProps);
}
+51 -15
View File
@@ -1,19 +1,55 @@
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 { HistoryNotification, Notifications } from "../../scripts/notifications";
import { Notifications } from "../../scripts/notifications";
import { NotificationWidget } from "../Notification"; import { NotificationWidget } from "../Notification";
export const NotifHistory: Gtk.Widget = new Widget.Scrollable({
hscroll: Gtk.PolicyType.NEVER, export const NotifHistory: Gtk.Widget = new Widget.Box({
vscroll: Gtk.PolicyType.AUTOMATIC, orientation: Gtk.Orientation.VERTICAL,
vexpand: true, className: "history",
hexpand: true, expand: true,
child: new Widget.Box({ visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0),
className: "notifications", children: [
children: bind(Notifications.getDefault(), "history").as((history: Array<AstalNotifd.Notification>) => new Widget.Scrollable({
history.map((notification: AstalNotifd.Notification) => NotificationWidget(notification, className: "history",
() => Notifications.getDefault().removeHistory(notification.id)) hscroll: Gtk.PolicyType.NEVER,
)) vscroll: Gtk.PolicyType.AUTOMATIC,
} as Widget.BoxProps) expand: true,
} as Widget.ScrollableProps) visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0),
child: new Widget.Box({
className: "notifications",
hexpand: true,
orientation: Gtk.Orientation.VERTICAL,
homogeneous: false,
children: bind(Notifications.getDefault(), "history").as((history: Array<HistoryNotification>) =>
history.map((notification: HistoryNotification) => NotificationWidget(notification,
() => Notifications.getDefault().removeHistory(notification.id))
))
} as Widget.BoxProps)
} as Widget.ScrollableProps),
new Widget.Box({
vexpand: false,
hexpand: true,
halign: Gtk.Align.END,
className: "button-row",
children: [
new Widget.Button({
className: "clear-all",
child: new Widget.Box({
children: [
new Widget.Label({
className: "nf",
css: "margin-right: 6px",
label: "󰎟"
} as Widget.LabelProps),
new Widget.Label({
label: "Clear"
} as Widget.LabelProps)
]
} as Widget.BoxProps),
onClick: () => Notifications.getDefault().clearHistory(),
} as Widget.ButtonProps)
]
})
]
} as Widget.BoxProps);
+2 -2
View File
@@ -1,12 +1,12 @@
import { Gtk, Widget } from "astal/gtk3"; import { Gtk, Widget } from "astal/gtk3";
import { TileNetwork } from "./tiles/Network"; import { TileNetwork } from "./tiles/Network";
import { TileBluetooth } from "./tiles/Bluetooth"; import { TileBluetooth } from "./tiles/Bluetooth";
import { TileRecording } from "./tiles/Recording"; import { TileDND } from "./tiles/DoNotDisturb";
export const tileList: Array<any> = [ export const tileList: Array<any> = [
TileNetwork, TileNetwork,
TileBluetooth, TileBluetooth,
TileRecording TileDND
]; ];
export function TilesWidget(): Gtk.Widget { export function TilesWidget(): Gtk.Widget {
+14 -7
View File
@@ -1,10 +1,11 @@
import { bind, timeout } from "astal"; import { AstalIO, bind, timeout } from "astal";
import { Gtk, Widget } from "astal/gtk3"; import { Gtk, Widget } from "astal/gtk3";
import AstalBluetooth from "gi://AstalBluetooth"; import AstalBluetooth from "gi://AstalBluetooth";
import { Page } from "./Page"; import { Page } from "./Page";
import { Separator, SeparatorProps } from "../../Separator"; import { Separator, SeparatorProps } from "../../Separator";
let watchingDevices: boolean = false; let watchingDevices: boolean = false;
let watchTimeout: (AstalIO.Time|undefined);
export const BluetoothPage: Page = new Page({ export const BluetoothPage: Page = new Page({
title: "Bluetooth Devices", title: "Bluetooth Devices",
@@ -48,7 +49,7 @@ export const BluetoothPage: Page = new Page({
} as Widget.BoxProps) } as Widget.BoxProps)
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
}) });
function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
return new Widget.Button({ return new Widget.Button({
@@ -85,11 +86,13 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
} }
function watchNewDevices(): void { function watchNewDevices(): void {
if(watchingDevices) { if(!watchTimeout) {
timeout(8000, () => { watchTimeout = timeout(5000, () => {
reloadBluetoothDevicesList(); reloadBluetoothDevicesList(2500);
watchNewDevices(); watchNewDevices();
watchTimeout = undefined;
}); });
return; return;
} }
@@ -98,11 +101,15 @@ function watchNewDevices(): void {
export function stopBluetoothDevicesWatch(): void { export function stopBluetoothDevicesWatch(): void {
watchingDevices = false; watchingDevices = false;
watchTimeout?.cancel();
watchTimeout = undefined;
AstalBluetooth.get_default().adapter.discovering && AstalBluetooth.get_default().adapter.discovering &&
AstalBluetooth.get_default().adapter.stop_discovery(); AstalBluetooth.get_default().adapter.stop_discovery();
} }
export function reloadBluetoothDevicesList(): void { export function reloadBluetoothDevicesList(discoveryTimeout?: number): void {
AstalBluetooth.get_default().adapter.start_discovery(); AstalBluetooth.get_default().adapter.start_discovery();
timeout(4000, () => AstalBluetooth.get_default().adapter.stop_discovery()); timeout(discoveryTimeout || 2500, () =>
AstalBluetooth.get_default().adapter.stop_discovery());
} }
+4 -2
View File
@@ -6,8 +6,10 @@ 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(), "isConnected").as((connected) => {
devices.filter((dev: AstalBluetooth.Device) => dev.connected)[0]?.get_alias()), const connectedDev = AstalBluetooth.get_default().devices.filter(dev => dev.connected)?.[0];
return connected && connectedDev ? connectedDev.get_alias() : ""
}),
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),
@@ -0,0 +1,15 @@
import { bind } from "astal";
import { Notifications } from "../../../scripts/notifications";
import { Tile } from "./Tile";
import { tr } from "../../../i18n/intl";
export const TileDND = Tile({
title: tr("control_center.tiles.dnd.title"),
description: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as(
(dnd: boolean) => dnd ? tr("control_center.tiles.enabled") : tr("control_center.tiles.disabled")),
onToggledOff: () => Notifications.getDefault().getNotifd().dontDisturb = false,
onToggledOn: () => Notifications.getDefault().getNotifd().dontDisturb = true,
icon: "󰍶",
iconSize: 16,
toggleState: Notifications.getDefault().getNotifd().dontDisturb
});
@@ -0,0 +1,10 @@
import { Tile, TileProps } from "./Tile";
export const TileNightLight = Tile({
title: "Luz Noturna",
icon: "󰖔",
iconSize: 16,
onToggledOff: () => false,
onToggledOn: () => true,
toggleState: false
} as TileProps);
+7 -15
View File
@@ -56,7 +56,7 @@ 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: `label { font-size: ${props.iconSize || "12"}px; }` css: `label { font-size: ${props.iconSize || 12}px; }`
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Box({ new Widget.Box({
className: "text", className: "text",
@@ -74,23 +74,15 @@ export function Tile(props: TileProps): Widget.EventBox {
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Label({ new Widget.Label({
className: "description", className: "description",
visible: Boolean(props.description), visible: (props.description instanceof Binding) ?
setup: (label: Widget.Label) => { props.description.as(Boolean)
if(props.description instanceof Binding) { : Boolean(props.description),
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, halign: Gtk.Align.START,
truncate: true, truncate: true,
xalign: 0, xalign: 0,
label: props.description label: (props.description instanceof Binding) ?
props.description.as((desc) => desc ? desc : "")
: (props.description || "")
} as Widget.LabelProps) } as Widget.LabelProps)
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
+2 -2
View File
@@ -1,6 +1,6 @@
import { register } from "astal"; import { register } from "astal";
import { Gtk, Widget } from "astal/gtk3"; import { Gtk, Widget } from "astal/gtk3";
import { closeRunner } from "../../runner/Runner"; import { Runner } from "../../runner/Runner";
export { ResultWidget, ResultWidgetProps }; export { ResultWidget, ResultWidgetProps };
@@ -32,7 +32,7 @@ class ResultWidget extends Widget.Box {
this.onClick = () => { this.onClick = () => {
props.onClick && props.onClick(); props.onClick && props.onClick();
this.closeOnClick && closeRunner(); this.closeOnClick && Runner.close();
}; };
this.set_class_name("result"); this.set_class_name("result");
+95 -63
View File
@@ -1,12 +1,10 @@
import { Variable } from "astal"; import { Variable } from "astal";
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3"; import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { getAstalApps } from "../scripts/apps"; import { cleanExec, getAstalApps } from "../scripts/apps";
import AstalApps from "gi://AstalApps"; import AstalApps from "gi://AstalApps";
import AstalHyprland from "gi://AstalHyprland";
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor; const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
const searchString = new Variable<string>(""); const searchString = new Variable<string>("");
const appsArray = new Variable<Array<AstalApps.Application>>([]);
let searchSubscription: () => void; let searchSubscription: () => void;
export const AppsWindow = new Widget.Window({ export const AppsWindow = new Widget.Window({
@@ -16,64 +14,98 @@ export const AppsWindow = new Widget.Window({
anchor: TOP | LEFT | RIGHT | BOTTOM, anchor: TOP | LEFT | RIGHT | BOTTOM,
visible: false, visible: false,
keymode: Astal.Keymode.EXCLUSIVE, keymode: Astal.Keymode.EXCLUSIVE,
onKeyPressEvent: (_, event: Gdk.Event) => { onDestroy: () => searchSubscription(),
event.get_keyval()[1] === Gdk.KEY_Escape && setup: (window) => {
hideAppsWindow(_); const flowbox = new Gtk.FlowBox({
}, rowSpacing: 6,
setup: () => { homogeneous: true,
searchSubscription = searchString.subscribe((str: string) => { columnSpacing: 6,
appsArray.set(getAstalApps().fuzzy_query(str)); expand: false,
}); orientation: Gtk.Orientation.HORIZONTAL,
}, visible: true
child: new Widget.Box({ } as Gtk.FlowBox.ConstructorProps);
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) { const entry = new Widget.Entry({
searchString.set(""); className: "entry",
window.hide(); halign: Gtk.Align.CENTER,
} primary_icon_name: "system-search",
onChanged: (entry) => {
searchString.set(entry.text);
}
} as Widget.EntryProps);
searchSubscription = searchString.subscribe((str: string) => {
const results: Array<AstalApps.Application> = getAstalApps().fuzzy_query(str);
// Destroy is handled by GnomeJS
flowbox.get_children().map(flowboxChild => flowbox.remove(flowboxChild));
results.map(app => {
flowbox.insert(new Widget.Button({
onClick: (_button, event: Astal.ClickEvent) => {
if(event.button === Astal.MouseButton.PRIMARY) {
searchString.set("");
entry.text = "";
window.hide();
cleanExec(app);
return;
}
// select app launch options TODO
},
child: new Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
children: [
new Widget.Icon({
className: "icon",
expand: true,
icon: app.get_icon_name()
} as Widget.IconProps),
new Widget.Label({
className: "name",
truncate: true,
label: app.get_name()
} as Widget.LabelProps)
]
} as Widget.BoxProps)
} as Widget.ButtonProps), -1);
const flowboxchild = flowbox.get_children()[flowbox.get_children().length-1];
flowboxchild.set_valign(Gtk.Align.START);
});
const firstChild = flowbox.get_child_at_index(0);
firstChild && flowbox.select_child(firstChild);
});
window.add(new Widget.EventBox({
onClick: () => {
searchString.set("");
entry.text = "";
window.hide();
},
onKeyPressEvent: (_, event: Gdk.Event) => {
if(event.get_keyval()[1] === Gdk.KEY_Escape) {
searchString.set("");
entry.text = "";
window.hide();
}
},
child: new Widget.Box({
className: "apps-window-container",
expand: true,
orientation: Gtk.Orientation.VERTICAL,
children: [
entry,
new Widget.Scrollable({
vscroll: Gtk.PolicyType.AUTOMATIC,
hscroll: Gtk.PolicyType.NEVER,
expand: true,
child: flowbox
} as Widget.ScrollableProps)
]
} as Widget.BoxProps)
} as Widget.EventBoxProps));
}
} as Widget.WindowProps);
+5 -4
View File
@@ -1,12 +1,12 @@
import { Astal, Gtk, Widget } from "astal/gtk3"; import { Astal, Gtk, Widget } from "astal/gtk3";
import { Clock } from "../widget/bar/Clock"; import { Clock } from "../widget/bar/Clock";
import { Logo } from "../widget/bar/Logo";
import { Tray } from "../widget/bar/Tray"; import { Tray } from "../widget/bar/Tray";
import { Workspaces } from "../widget/bar/Workspaces"; import { Workspaces } from "../widget/bar/Workspaces";
import { Audio } from "../widget/bar/Audio";
import { FocusedClient } from "../widget/bar/FocusedClient"; import { FocusedClient } from "../widget/bar/FocusedClient";
import { Media } from "../widget/bar/Media"; import { Media } from "../widget/bar/Media";
import { Status } from "../widget/bar/Status";
import { Apps } from "../widget/bar/Apps";
export const Bar: Widget.Window = new Widget.Window({ export const Bar: Widget.Window = new Widget.Window({
monitor: 0, monitor: 0,
@@ -14,6 +14,7 @@ export const Bar: Widget.Window = new Widget.Window({
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT, anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT,
layer: Astal.Layer.TOP, layer: Astal.Layer.TOP,
exclusivity: Astal.Exclusivity.EXCLUSIVE, exclusivity: Astal.Exclusivity.EXCLUSIVE,
heightRequest: 46,
canFocus: false, canFocus: false,
visible: true, visible: true,
child: new Widget.Box({ child: new Widget.Box({
@@ -27,7 +28,7 @@ export const Bar: Widget.Window = new Widget.Window({
homogeneous: false, homogeneous: false,
halign: Gtk.Align.START, halign: Gtk.Align.START,
children: [ children: [
Logo(), Apps(),
Workspaces(), Workspaces(),
FocusedClient() FocusedClient()
] ]
@@ -47,7 +48,7 @@ export const Bar: Widget.Window = new Widget.Window({
halign: Gtk.Align.END, halign: Gtk.Align.END,
children: [ children: [
Tray(), Tray(),
Audio() Status()
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.CenterBoxProps) } as Widget.CenterBoxProps)
+38 -15
View File
@@ -1,35 +1,58 @@
import { Gtk, Widget } from "astal/gtk3"; import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { QuickActions } from "../widget/control-center/QuickActions"; import { QuickActions } from "../widget/control-center/QuickActions";
import { Tiles } from "../widget/control-center/Tiles"; 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 { hidePages, PagesWidget } from "../widget/control-center/Pages"; import { hidePages, PagesWidget } from "../widget/control-center/Pages";
import { NotifHistory } from "../widget/control-center/NotifHistory"; import { NotifHistory } from "../widget/control-center/NotifHistory";
const connections: Array<number> = []; const connections: Array<number> = [];
const { TOP, LEFT, BOTTOM, RIGHT } = Astal.WindowAnchor;
export const ControlCenter: Widget.Window = PopupWindow({ export const ControlCenter = new Widget.Window({
className: "control-center",
namespace: "control-center", namespace: "control-center",
marginTop: 10, className: "control-center",
marginRight: 10, anchor: TOP | BOTTOM | LEFT | RIGHT,
monitor: 0, exclusivity: Astal.Exclusivity.NORMAL,
onClose: () => hidePages(), keymode: Astal.Keymode.EXCLUSIVE,
halign: Gtk.Align.END, layer: Astal.Layer.OVERLAY,
valign: Gtk.Align.START, focusOnMap: true,
visible: false, visible: false,
vexpand: true, monitor: 0,
onDestroy: (_) => connections.map(id => _.disconnect(id)),
onButtonPressEvent: (_, event: Gdk.Event) => {
const [, posX, posY] = event.get_coords();
const childAllocation = _.get_child()!.get_allocation();
if((posX < childAllocation.x || posX > (childAllocation.x + childAllocation.width)) ||
(posY < childAllocation.y || posY > (childAllocation.y + childAllocation.height))) {
_.hide();
hidePages();
}
},
onKeyPressEvent: (_, event: Gdk.Event) => {
if(event.get_keyval()[1] === Gdk.KEY_Escape) {
_.hide();
hidePages();
}
},
child: new Widget.Box({ child: new Widget.Box({
orientation: Gtk.Orientation.VERTICAL, className: "popup",
expand: true, halign: Gtk.Align.END,
css: `.popup {
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
}`,
vexpand: true, vexpand: true,
widthRequest: 400,
onButtonPressEvent: () => true,
orientation: Gtk.Orientation.VERTICAL,
children: [ children: [
new Widget.Box({ new Widget.Box({
className: "control-center-container", className: "control-center-container",
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
widthRequest: 400, widthRequest: 400,
vexpand: false, vexpand: false,
hexpand: true,
children: [ children: [
QuickActions, QuickActions,
Sliders, Sliders,
@@ -40,7 +63,7 @@ export const ControlCenter: Widget.Window = PopupWindow({
NotifHistory NotifHistory
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
} as PopupWindowProps); } as Widget.WindowProps);
connections.push(ControlCenter.connect("hide", (_) => { connections.push(ControlCenter.connect("hide", (_) => {
hidePages(); hidePages();