✨ ags(bar, notifications, control-center): add status icons to bar, notification history fixed, notification history below control-center
This commit is contained in:
@@ -9,7 +9,10 @@ export default {
|
||||
},
|
||||
control_center: {
|
||||
tiles: {
|
||||
enabled: "Enabled",
|
||||
disabled: "Disabled",
|
||||
more: "More",
|
||||
|
||||
network: {
|
||||
network: "Network",
|
||||
connected: "Connected",
|
||||
@@ -21,8 +24,11 @@ export default {
|
||||
},
|
||||
recording: {
|
||||
title: "Screen Recording",
|
||||
disabled_description: "Start recording",
|
||||
enabled_description: "Stop recording",
|
||||
disabled_desc: "Start recording",
|
||||
enabled_desc: "Stop recording",
|
||||
},
|
||||
dnd: {
|
||||
title: "Do Not Disturb"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
},
|
||||
control_center: {
|
||||
tiles: {
|
||||
enabled: "Ligado",
|
||||
disabled: "Desligado",
|
||||
more: "Mais",
|
||||
|
||||
network: {
|
||||
@@ -24,6 +26,9 @@ export default {
|
||||
title: "Gravação de Tela",
|
||||
disabled_description: "Iniciar gravação",
|
||||
enabled_description: "Parar gravação",
|
||||
},
|
||||
dnd: {
|
||||
title: "Não Perturbe"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+31
-24
@@ -7,17 +7,11 @@ import { ResultWidget, ResultWidgetProps } from "../widget/runner/ResultWidget";
|
||||
export let runnerInstance: (Widget.Window|null) = null;
|
||||
let onClickTimeout: (AstalIO.Time|undefined);
|
||||
|
||||
export function closeRunner(gtkWindow?: Widget.Window) {
|
||||
const window = gtkWindow ? gtkWindow : runnerInstance;
|
||||
|
||||
window?.destroy();
|
||||
runnerInstance = null;
|
||||
}
|
||||
|
||||
export function startRunnerDefault() {
|
||||
return Runner.RunnerWindow({
|
||||
entryPlaceHolder: "Start typing...",
|
||||
resultsPlaceholder: () => [
|
||||
return Runner.openRunner({
|
||||
entryPlaceHolder: "Start typing..."
|
||||
} as Runner.RunnerProps,
|
||||
() => [
|
||||
new ResultWidget({
|
||||
icon: "utilities-terminal-symbolic",
|
||||
title: "Run shell commands",
|
||||
@@ -33,8 +27,7 @@ export function startRunnerDefault() {
|
||||
title: "Search the Web",
|
||||
description: "Start typing with '?' prefix to search the web"
|
||||
} as ResultWidgetProps)
|
||||
]
|
||||
} as Runner.RunnerProps);
|
||||
]);
|
||||
}
|
||||
|
||||
export namespace Runner {
|
||||
@@ -44,9 +37,18 @@ export namespace Runner {
|
||||
width?: number;
|
||||
height?: number;
|
||||
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>([]);
|
||||
|
||||
export interface Plugin {
|
||||
@@ -54,14 +56,19 @@ export namespace Runner {
|
||||
readonly prefix?: string;
|
||||
/** name of the plugin. e.g.: websearch, shell */
|
||||
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);
|
||||
/** ran on runner close */
|
||||
readonly onClose?: () => void;
|
||||
}
|
||||
|
||||
export function addPlugin(plugin: Runner.Plugin, force?: boolean) {
|
||||
if(!force && plugin.prefix && plugins.has(plugin))
|
||||
throw new Error(`Runner plugin with prefix ${plugin.prefix} already exists`);
|
||||
|
||||
plugins.delete(plugin);
|
||||
plugins.add(plugin);
|
||||
}
|
||||
|
||||
@@ -76,7 +83,7 @@ export namespace Runner {
|
||||
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> = [];
|
||||
const entryText: Variable<string> = new Variable<string>("");
|
||||
|
||||
@@ -87,8 +94,8 @@ export namespace Runner {
|
||||
onActivate: (entry) => {
|
||||
const resultWidget = resultsList.get_selected_row()?.get_child();
|
||||
if(resultWidget instanceof ResultWidget) {
|
||||
resultWidget.onClick();
|
||||
entry.isFocus = false;
|
||||
resultWidget.onClick();
|
||||
}
|
||||
},
|
||||
primary_icon_name: "system-search"
|
||||
@@ -114,8 +121,8 @@ export namespace Runner {
|
||||
});
|
||||
|
||||
// Insert placeholder if somehow no results are found
|
||||
if((!entryText || !widgets || widgets.length === 0) && props?.resultsPlaceholder)
|
||||
widgets.push(...props.resultsPlaceholder());
|
||||
if(placeholder && (!entryText || !widgets || widgets.length === 0))
|
||||
widgets.push(...placeholder());
|
||||
|
||||
// Insert results inside GtkListBox
|
||||
widgets.map((resultWidget: ResultWidget) => {
|
||||
@@ -142,25 +149,25 @@ export namespace Runner {
|
||||
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) => {
|
||||
const keyVal = event.get_keyval()[1];
|
||||
if(!searchEntry.has_focus && keyVal !== Gdk.KEY_F5
|
||||
&& 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();
|
||||
}
|
||||
|
||||
|
||||
event.get_keyval()[1] === Gdk.KEY_F5 &&
|
||||
updateApps();
|
||||
|
||||
},
|
||||
closeAction: (_) => closeRunner(_),
|
||||
onClose: () => subs.map(sub => sub()),
|
||||
closeAction: (_) => {
|
||||
close(_);
|
||||
subs.map(sub => sub());
|
||||
},
|
||||
child: new Widget.Box({
|
||||
className: "runner main",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
|
||||
+21
-2
@@ -1,4 +1,6 @@
|
||||
import { Astal } from "astal/gtk3";
|
||||
import AstalApps from "gi://AstalApps";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
const astalApps: AstalApps.Apps = new AstalApps.Apps();
|
||||
let appsList: Array<AstalApps.Application> = astalApps.get_list();
|
||||
@@ -16,6 +18,10 @@ export function getAstalApps(): AstalApps.Apps {
|
||||
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) {
|
||||
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) {
|
||||
const found: (Array<AstalApps.Application>|undefined) = getAppsByName(appName);
|
||||
return found ? found[0]?.iconName : undefined;
|
||||
if(Astal.Icon.lookup_icon(appName))
|
||||
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";
|
||||
}
|
||||
|
||||
@@ -6,13 +6,24 @@ export const
|
||||
NOTIFICATION_TIMEOUT_NORMAL: number = 4000,
|
||||
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" })
|
||||
class Notifications extends GObject.Object {
|
||||
private static instance: (Notifications|null) = null;
|
||||
|
||||
#notifications: Array<AstalNotifd.Notification> = [];
|
||||
#history: Array<AstalNotifd.Notification> = [];
|
||||
#history: Array<HistoryNotification> = [];
|
||||
#connections: Array<number>;
|
||||
#historyLimit: number = 10;
|
||||
|
||||
|
||||
@property()
|
||||
@@ -21,6 +32,14 @@ class Notifications extends GObject.Object {
|
||||
@property()
|
||||
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)
|
||||
declare notificationAdded: (notification: AstalNotifd.Notification) => void;
|
||||
@@ -28,7 +47,7 @@ class Notifications extends GObject.Object {
|
||||
@signal(Number)
|
||||
declare notificationRemoved: (id: number) => void;
|
||||
|
||||
@signal(AstalNotifd.Notification)
|
||||
@signal(Object)
|
||||
declare historyAdded: (notification: AstalNotifd.Notification) => void;
|
||||
|
||||
@signal(Number)
|
||||
@@ -42,7 +61,7 @@ class Notifications extends GObject.Object {
|
||||
super();
|
||||
|
||||
this.#connections = [
|
||||
AstalNotifd.get_default().connect("notified", (notifd, id, _replaced) => {
|
||||
AstalNotifd.get_default().connect("notified", (notifd, id) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
const notifTimeout = notification.urgency === AstalNotifd.Urgency.LOW ?
|
||||
NOTIFICATION_TIMEOUT_LOW
|
||||
@@ -50,29 +69,39 @@ class Notifications extends GObject.Object {
|
||||
NOTIFICATION_TIMEOUT_URGENT
|
||||
: NOTIFICATION_TIMEOUT_NORMAL);
|
||||
|
||||
if(this.getNotifd().dontDisturb) {
|
||||
this.addHistory(notification, () => notification.dismiss());
|
||||
return;
|
||||
}
|
||||
|
||||
this.addNotification(notification, () => {
|
||||
if(notification.urgency !== AstalNotifd.Urgency.CRITICAL ||
|
||||
(notification.urgency === AstalNotifd.Urgency.CRITICAL &&
|
||||
NOTIFICATION_TIMEOUT_URGENT > 0)) {
|
||||
|
||||
let notifTimer: AstalIO.Time;
|
||||
let notifTimer: (AstalIO.Time|undefined) = undefined;
|
||||
let replacedConnectionId: number;
|
||||
|
||||
const removeFun = () => { // Funny name haha lmao remove fun :skull:
|
||||
this.removeNotification(id);
|
||||
notifTimer = undefined;
|
||||
this.addHistory(notification, () => {
|
||||
replacedConnectionId && this.disconnect(replacedConnectionId);
|
||||
this.removeNotification(id);
|
||||
});
|
||||
}
|
||||
|
||||
notifTimer = timeout(notifTimeout, removeFun);
|
||||
|
||||
replacedConnectionId = this.connect("notification-replaced", (_, id: number) => {
|
||||
if(notification.id === id) {
|
||||
notifTimer.cancel();
|
||||
notifTimer?.cancel();
|
||||
notifTimer = timeout(notifTimeout, removeFun);
|
||||
}
|
||||
});
|
||||
|
||||
notifTimer = timeout(notifTimeout, removeFun);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
AstalNotifd.get_default().connect("resolved", (notifd, id, _reason) => {
|
||||
this.removeNotification(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 {
|
||||
const newArray = this.#history.reverse().filter((item) => item.id !== notif.id);
|
||||
newArray.push(notif);
|
||||
if(!notif) return;
|
||||
|
||||
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.notify("history");
|
||||
this.emit("history-added", notif);
|
||||
this.emit("history-added", this.#history[0]);
|
||||
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) =>
|
||||
public clearHistory(): void {
|
||||
for(let i = 0; i < this.history.length; i++) {
|
||||
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);
|
||||
|
||||
this.notify("history");
|
||||
@@ -125,20 +178,26 @@ class Notifications extends GObject.Object {
|
||||
}
|
||||
|
||||
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) =>
|
||||
item.id !== notification.id);
|
||||
item.id !== notificationId);
|
||||
|
||||
notification.dismiss();
|
||||
AstalNotifd.get_default().get_notification(notificationId)?.dismiss();
|
||||
this.notify("notifications");
|
||||
this.emit("notification-removed", notification.id);
|
||||
this.emit("notification-removed", notificationId);
|
||||
}
|
||||
|
||||
public toggleDoNotDisturb(): boolean {
|
||||
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 {
|
||||
return super.connect(signal, callback);
|
||||
|
||||
+21
-21
@@ -71,42 +71,42 @@ class WireplumberClass extends GObject.Object {
|
||||
);
|
||||
}
|
||||
|
||||
public increaseSinkVolume(volumeIncrease: number): void {
|
||||
if((this.getSinkVolume() + volumeIncrease) > this.maxSinkVolume) {
|
||||
this.setSinkVolume(this.maxSinkVolume);
|
||||
public increaseEndpointVolume(endpoint: AstalWp.Endpoint, volumeIncrease: number): void {
|
||||
volumeIncrease = Math.abs(volumeIncrease) / 100;
|
||||
|
||||
if((endpoint.get_volume() + volumeIncrease) > this.maxSinkVolume) {
|
||||
endpoint.set_volume(1.0);
|
||||
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 {
|
||||
if((this.getSourceVolume() + volumeIncrease) > this.maxSourceVolume) {
|
||||
this.setSourceVolume(this.maxSourceVolume);
|
||||
this.increaseEndpointVolume(this.getDefaultSource(), volumeIncrease);
|
||||
}
|
||||
|
||||
public decreaseEndpointVolume(endpoint: AstalWp.Endpoint, volumeDecrease: number): void {
|
||||
volumeDecrease = Math.abs(volumeDecrease) / 100;
|
||||
|
||||
if((endpoint.get_volume() - volumeDecrease) < 0) {
|
||||
endpoint.set_volume(0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSourceVolume(this.getSourceVolume() + volumeIncrease);
|
||||
endpoint.set_volume(endpoint.get_volume() - volumeDecrease);
|
||||
}
|
||||
|
||||
public decreaseSinkVolume(volumeDecrease: number): void {
|
||||
const absDecrease = Math.abs(volumeDecrease);
|
||||
|
||||
if((this.getSinkVolume() - absDecrease) < 0) {
|
||||
this.setSinkVolume(0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSinkVolume(this.getSinkVolume() - absDecrease);
|
||||
this.decreaseEndpointVolume(this.getDefaultSink(), volumeDecrease);
|
||||
}
|
||||
|
||||
public decreaseSourceVolume(volumeDecrease: number): void {
|
||||
const absDecrease = Math.abs(volumeDecrease);
|
||||
|
||||
if((this.getSourceVolume() - absDecrease) < 0)
|
||||
return this.setSourceVolume(0);
|
||||
|
||||
this.setSourceVolume(this.getSourceVolume() - absDecrease);
|
||||
this.decreaseEndpointVolume(this.getDefaultSource(), volumeDecrease);
|
||||
}
|
||||
|
||||
public muteSink(): void {
|
||||
|
||||
+11
-2
@@ -72,6 +72,14 @@ window.ask-popup {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
& label.time {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: colors.$fg-disabled;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
& button.close {
|
||||
padding: 2px;
|
||||
border-radius: 8px;
|
||||
@@ -177,13 +185,14 @@ menu {
|
||||
|
||||
scrollbar trough {
|
||||
@include mixins.reset-props;
|
||||
|
||||
background: colors.$bg-translucent;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-radius: 8px;
|
||||
padding: 2px;
|
||||
|
||||
& slider {
|
||||
@include mixins.reset-props;
|
||||
|
||||
min-width: .85em;
|
||||
background: colors.$bg-tertiary;
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
.apps-window.container {
|
||||
& > entry {
|
||||
@use "mixins";
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -171,7 +171,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.audio {
|
||||
.status {
|
||||
@include mixins.reset-props;
|
||||
|
||||
&:hover > box,
|
||||
@@ -211,13 +211,17 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& .bell {
|
||||
& .status-icons {
|
||||
padding: 0 4px;
|
||||
|
||||
& > * {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
.apps {
|
||||
& > box {
|
||||
border-radius: 12px;
|
||||
transition: 100ms linear;
|
||||
@@ -225,7 +229,6 @@
|
||||
|
||||
& > label {
|
||||
font-size: 14px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
|
||||
@@ -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 {
|
||||
@include mixins.reset-props;
|
||||
|
||||
@@ -150,15 +187,16 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.bluetooth {
|
||||
.connections button {
|
||||
& button {
|
||||
@include mixins.hover-shadow;
|
||||
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&.connected {
|
||||
&.bluetooth {
|
||||
button.connected {
|
||||
background: colors.$bg-tertiary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -1,26 +1,26 @@
|
||||
// SCSS Variables
|
||||
// Generated by 'wal'
|
||||
$wallpaper: "/home/joaov/wallpapers/Miku Guitar.jpg";
|
||||
$wallpaper: "/home/joaov/wallpapers/Garden Kita.png";
|
||||
|
||||
// Special
|
||||
$background: #171418;
|
||||
$foreground: #c5c4c5;
|
||||
$cursor: #c5c4c5;
|
||||
$background: #101212;
|
||||
$foreground: #c3c3c3;
|
||||
$cursor: #c3c3c3;
|
||||
|
||||
// Colors
|
||||
$color0: #171418;
|
||||
$color1: #607985;
|
||||
$color2: #208FB6;
|
||||
$color3: #4C9CB4;
|
||||
$color4: #63ADC9;
|
||||
$color5: #C3B49C;
|
||||
$color6: #89BBCF;
|
||||
$color7: #96909b;
|
||||
$color8: #715c71;
|
||||
$color9: #6aa4bf;
|
||||
$color10: #62a5bc;
|
||||
$color11: #78b3c5;
|
||||
$color12: #90becf;
|
||||
$color13: #dac7ab;
|
||||
$color14: #a7cbd9;
|
||||
$color15: #c5c4c5;
|
||||
$color0: #101212;
|
||||
$color1: #778839;
|
||||
$color2: #6C965F;
|
||||
$color3: #B4B350;
|
||||
$color4: #96A460;
|
||||
$color5: #658388;
|
||||
$color6: #B0B493;
|
||||
$color7: #8e9898;
|
||||
$color8: #596d6d;
|
||||
$color9: #a3b757;
|
||||
$color10: #85c373;
|
||||
$color11: #c6c67a;
|
||||
$color12: #b7c67a;
|
||||
$color13: #6eb7c1;
|
||||
$color14: #ced59e;
|
||||
$color15: #c3c3c3;
|
||||
|
||||
+36
-17
@@ -2,6 +2,8 @@ import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { Separator } from "./Separator";
|
||||
import Pango from "gi://Pango";
|
||||
import { HistoryNotification } from "../scripts/notifications";
|
||||
import { GLib } from "astal";
|
||||
|
||||
export function getUrgencyString(notif: AstalNotifd.Notification) {
|
||||
switch(notif.urgency) {
|
||||
@@ -14,24 +16,29 @@ export function getUrgencyString(notif: AstalNotifd.Notification) {
|
||||
return "normal";
|
||||
}
|
||||
|
||||
export function NotificationWidget(notification: AstalNotifd.Notification|number,
|
||||
onClose?: (notif: AstalNotifd.Notification) => void): Gtk.Widget {
|
||||
export function NotificationWidget(notification: AstalNotifd.Notification|number|HistoryNotification,
|
||||
onClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void,
|
||||
showTime?: boolean /* It's showTime :speaking_head: :boom: :bangbang: */): Gtk.Widget {
|
||||
|
||||
notification = (notification instanceof AstalNotifd.Notification) ?
|
||||
notification
|
||||
: AstalNotifd.get_default().get_notification(notification);
|
||||
notification = (typeof notification === "number") ?
|
||||
AstalNotifd.get_default().get_notification(notification)
|
||||
: notification;
|
||||
|
||||
return new Widget.EventBox({
|
||||
onClick: () => {
|
||||
if(notification.actions.length >= 1 && notification.actions[0].label.toLowerCase() === "view") {
|
||||
notification.invoke(notification.actions[0]!.id);
|
||||
onClose && onClose(notification);
|
||||
if(notification instanceof AstalNotifd.Notification) {
|
||||
const viewAction = notification.actions.filter(action => action.label.toLowerCase() === "view")?.[0];
|
||||
if(viewAction) notification.invoke(viewAction.id);
|
||||
}
|
||||
|
||||
onClose && onClose(notification);
|
||||
},
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
child: new Widget.Box({
|
||||
className: `notification ${getUrgencyString(notification)}`,
|
||||
className: `notification ${ (notification instanceof AstalNotifd.Notification) ? getUrgencyString(notification) : "" }`,
|
||||
homogeneous: false,
|
||||
expand: false,
|
||||
expand: true,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
@@ -42,7 +49,7 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
|
||||
children: [
|
||||
new Widget.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
|
||||
: (Astal.Icon.lookup_icon(notification.appName.toLowerCase()) ?
|
||||
notification.appName.toLowerCase()
|
||||
@@ -59,9 +66,17 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
|
||||
hexpand: true,
|
||||
label: notification.appName || "Unknown Application"
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
halign: Gtk.Align.END,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
xalign: 1,
|
||||
visible: !showTime ? false : true,
|
||||
className: "time",
|
||||
label: GLib.DateTime.new_from_unix_utc(notification.time).format("%H:%M"),
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Button({
|
||||
className: "close nf",
|
||||
halign: Gtk.Align.END,
|
||||
onClick: () => onClose && onClose(notification),
|
||||
image: new Widget.Icon({
|
||||
className: "close icon",
|
||||
@@ -69,6 +84,8 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
|
||||
} as Widget.IconProps)
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
Separator({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
@@ -112,13 +129,14 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
|
||||
new Widget.Box({
|
||||
className: "actions button-row",
|
||||
hexpand: true,
|
||||
visible: (notification.actions.length === 1 &&
|
||||
notification.actions[0].label.toLowerCase() === "view")
|
||||
|| notification.actions.length === 0 ? false : true,
|
||||
children: notification.actions.map((action: AstalNotifd.Action, i: number) =>
|
||||
visible: (notification instanceof AstalNotifd.Notification) ?
|
||||
(notification.actions.filter(action => action.label.toLowerCase() !== "view").length > 0)
|
||||
: false,
|
||||
children: (notification instanceof AstalNotifd.Notification) ?
|
||||
notification.actions.filter(action => action.label.toLowerCase() !== "view")
|
||||
.map((action: AstalNotifd.Action) =>
|
||||
new Widget.Button({
|
||||
className: "action",
|
||||
visible: i === 0 ? (action.label.toLowerCase() !== "view") : true,
|
||||
label: action.label,
|
||||
hexpand: true,
|
||||
onClicked: () => {
|
||||
@@ -127,6 +145,7 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
|
||||
}
|
||||
} as Widget.ButtonProps)
|
||||
)
|
||||
: []
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
|
||||
@@ -18,6 +18,7 @@ export type PopupWindowProps = Pick<Widget.WindowProps,
|
||||
| "heightRequest"
|
||||
| "child"
|
||||
| "monitor"
|
||||
| "setup"
|
||||
| "exclusivity"> & {
|
||||
marginTop?: number;
|
||||
marginLeft?: number;
|
||||
@@ -42,8 +43,8 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
layer: props?.layer || Astal.Layer.OVERLAY,
|
||||
focusOnMap: true,
|
||||
visible: props?.visible,
|
||||
acceptFocus: true,
|
||||
monitor: props?.monitor || 0,
|
||||
setup: props.setup,
|
||||
onButtonPressEvent: (_, event: Gdk.Event) => {
|
||||
const [, posX, posY] = event.get_coords();
|
||||
const childAllocation = _.get_child()!.get_allocation();
|
||||
@@ -70,20 +71,19 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
: `popup ${props?.className || ""}`,
|
||||
halign: props?.halign || 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 {
|
||||
margin-top: ${props.marginTop || 0}px;
|
||||
margin-bottom: ${props.marginBottom || 0}px;
|
||||
margin-left: ${props.marginLeft || 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,
|
||||
child: props.child
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.WindowProps);;
|
||||
} as Widget.WindowProps);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { trGet } from "../../i18n/intl";
|
||||
import { tr } from "../../i18n/intl";
|
||||
import { Windows } from "../../windows";
|
||||
|
||||
export function Logo(): Gtk.Widget {
|
||||
export function Apps(): Gtk.Widget {
|
||||
return new Widget.EventBox({
|
||||
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
|
||||
className: "logo",
|
||||
onClickRelease: () => Windows.getWindow("apps-window")?.show(),
|
||||
className: "apps",
|
||||
child: new Widget.Box({
|
||||
child: new Widget.Label({
|
||||
className: "nf",
|
||||
tooltipText: trGet()["bar"]["apps"]["tooltip"],
|
||||
tooltipText: tr("bar.apps.tooltip"),
|
||||
label: ""
|
||||
} as Widget.LabelProps)
|
||||
} as Widget.BoxProps)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -15,11 +15,7 @@ export function FocusedClient(): Gtk.Widget {
|
||||
vexpand: true,
|
||||
css: ".icon { font-size: 18px; }",
|
||||
icon: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
|
||||
client ?
|
||||
(getAppIcon(client.initialClass) || client.initialClass)
|
||||
:
|
||||
"image-missing"
|
||||
)
|
||||
client ? getAppIcon(client.initialClass) : "image-missing")
|
||||
}),
|
||||
new Widget.Box({
|
||||
className: "text-content",
|
||||
|
||||
@@ -105,7 +105,7 @@ export function Media(): Gtk.Widget {
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
size: 2,
|
||||
cssColor: `rgb(180, 180, 180)`,
|
||||
alpha: 1
|
||||
alpha: 0.3
|
||||
} as SeparatorProps),
|
||||
new Widget.Label({
|
||||
className: "artist",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,19 +1,55 @@
|
||||
import { bind } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { Notifications } from "../../scripts/notifications";
|
||||
import { HistoryNotification, Notifications } from "../../scripts/notifications";
|
||||
import { NotificationWidget } from "../Notification";
|
||||
|
||||
export const NotifHistory: Gtk.Widget = new Widget.Scrollable({
|
||||
|
||||
export const NotifHistory: Gtk.Widget = new Widget.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
className: "history",
|
||||
expand: true,
|
||||
visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0),
|
||||
children: [
|
||||
new Widget.Scrollable({
|
||||
className: "history",
|
||||
hscroll: Gtk.PolicyType.NEVER,
|
||||
vscroll: Gtk.PolicyType.AUTOMATIC,
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
expand: true,
|
||||
visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0),
|
||||
child: new Widget.Box({
|
||||
className: "notifications",
|
||||
children: bind(Notifications.getDefault(), "history").as((history: Array<AstalNotifd.Notification>) =>
|
||||
history.map((notification: AstalNotifd.Notification) => NotificationWidget(notification,
|
||||
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)
|
||||
} 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);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { TileNetwork } from "./tiles/Network";
|
||||
import { TileBluetooth } from "./tiles/Bluetooth";
|
||||
import { TileRecording } from "./tiles/Recording";
|
||||
import { TileDND } from "./tiles/DoNotDisturb";
|
||||
|
||||
export const tileList: Array<any> = [
|
||||
TileNetwork,
|
||||
TileBluetooth,
|
||||
TileRecording
|
||||
TileDND
|
||||
];
|
||||
|
||||
export function TilesWidget(): Gtk.Widget {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { bind, timeout } from "astal";
|
||||
import { AstalIO, bind, timeout } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
import { Page } from "./Page";
|
||||
import { Separator, SeparatorProps } from "../../Separator";
|
||||
|
||||
let watchingDevices: boolean = false;
|
||||
let watchTimeout: (AstalIO.Time|undefined);
|
||||
|
||||
export const BluetoothPage: Page = new Page({
|
||||
title: "Bluetooth Devices",
|
||||
@@ -48,7 +49,7 @@ export const BluetoothPage: Page = new Page({
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
})
|
||||
});
|
||||
|
||||
function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
|
||||
return new Widget.Button({
|
||||
@@ -85,11 +86,13 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
|
||||
}
|
||||
|
||||
function watchNewDevices(): void {
|
||||
if(watchingDevices) {
|
||||
timeout(8000, () => {
|
||||
reloadBluetoothDevicesList();
|
||||
if(!watchTimeout) {
|
||||
watchTimeout = timeout(5000, () => {
|
||||
reloadBluetoothDevicesList(2500);
|
||||
watchNewDevices();
|
||||
watchTimeout = undefined;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,11 +101,15 @@ function watchNewDevices(): void {
|
||||
|
||||
export function stopBluetoothDevicesWatch(): void {
|
||||
watchingDevices = false;
|
||||
watchTimeout?.cancel();
|
||||
watchTimeout = undefined;
|
||||
|
||||
AstalBluetooth.get_default().adapter.discovering &&
|
||||
AstalBluetooth.get_default().adapter.stop_discovery();
|
||||
}
|
||||
|
||||
export function reloadBluetoothDevicesList(): void {
|
||||
export function reloadBluetoothDevicesList(discoveryTimeout?: number): void {
|
||||
AstalBluetooth.get_default().adapter.start_discovery();
|
||||
timeout(4000, () => AstalBluetooth.get_default().adapter.stop_discovery());
|
||||
timeout(discoveryTimeout || 2500, () =>
|
||||
AstalBluetooth.get_default().adapter.stop_discovery());
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import { BluetoothPage } from "../pages/Bluetooth";
|
||||
|
||||
export const TileBluetooth = Tile({
|
||||
title: "Bluetooth",
|
||||
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||
devices.filter((dev: AstalBluetooth.Device) => dev.connected)[0]?.get_alias()),
|
||||
description: bind(AstalBluetooth.get_default(), "isConnected").as((connected) => {
|
||||
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),
|
||||
onToggledOff: () => AstalBluetooth.get_default().adapter.set_powered(false),
|
||||
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);
|
||||
@@ -56,7 +56,7 @@ export function Tile(props: TileProps): Widget.EventBox {
|
||||
new Widget.Label({
|
||||
className: "icon nf",
|
||||
label: props.icon || "icon",
|
||||
css: `label { font-size: ${props.iconSize || "12"}px; }`
|
||||
css: `label { font-size: ${props.iconSize || 12}px; }`
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
className: "text",
|
||||
@@ -74,23 +74,15 @@ export function Tile(props: TileProps): Widget.EventBox {
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "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();
|
||||
});
|
||||
}
|
||||
},
|
||||
visible: (props.description instanceof Binding) ?
|
||||
props.description.as(Boolean)
|
||||
: Boolean(props.description),
|
||||
halign: Gtk.Align.START,
|
||||
truncate: true,
|
||||
xalign: 0,
|
||||
label: props.description
|
||||
label: (props.description instanceof Binding) ?
|
||||
props.description.as((desc) => desc ? desc : "")
|
||||
: (props.description || "")
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { register } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { closeRunner } from "../../runner/Runner";
|
||||
import { Runner } from "../../runner/Runner";
|
||||
|
||||
export { ResultWidget, ResultWidgetProps };
|
||||
|
||||
@@ -32,7 +32,7 @@ class ResultWidget extends Widget.Box {
|
||||
|
||||
this.onClick = () => {
|
||||
props.onClick && props.onClick();
|
||||
this.closeOnClick && closeRunner();
|
||||
this.closeOnClick && Runner.close();
|
||||
};
|
||||
|
||||
this.set_class_name("result");
|
||||
|
||||
+76
-44
@@ -1,12 +1,10 @@
|
||||
import { Variable } from "astal";
|
||||
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 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({
|
||||
@@ -16,64 +14,98 @@ export const AppsWindow = new Widget.Window({
|
||||
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({
|
||||
onDestroy: () => searchSubscription(),
|
||||
setup: (window) => {
|
||||
const flowbox = new Gtk.FlowBox({
|
||||
rowSpacing: 6,
|
||||
homogeneous: true,
|
||||
columnSpacing: 6,
|
||||
expand: false,
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
visible: true
|
||||
} as Gtk.FlowBox.ConstructorProps);
|
||||
|
||||
const entry = new Widget.Entry({
|
||||
className: "entry",
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
onDraw: (_) => _.grab_focus(),
|
||||
halign: Gtk.Align.CENTER,
|
||||
primary_icon_name: "system-search",
|
||||
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());
|
||||
} 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",
|
||||
iconName: app.get_icon_name()
|
||||
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)
|
||||
)
|
||||
)
|
||||
} 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);
|
||||
|
||||
function hideAppsWindow(window: Widget.Window) {
|
||||
searchString.set("");
|
||||
window.hide();
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,12 +1,12 @@
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
import { Clock } from "../widget/bar/Clock";
|
||||
import { Logo } from "../widget/bar/Logo";
|
||||
import { Tray } from "../widget/bar/Tray";
|
||||
import { Workspaces } from "../widget/bar/Workspaces";
|
||||
import { Audio } from "../widget/bar/Audio";
|
||||
import { FocusedClient } from "../widget/bar/FocusedClient";
|
||||
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({
|
||||
monitor: 0,
|
||||
@@ -14,6 +14,7 @@ export const Bar: Widget.Window = new Widget.Window({
|
||||
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT,
|
||||
layer: Astal.Layer.TOP,
|
||||
exclusivity: Astal.Exclusivity.EXCLUSIVE,
|
||||
heightRequest: 46,
|
||||
canFocus: false,
|
||||
visible: true,
|
||||
child: new Widget.Box({
|
||||
@@ -27,7 +28,7 @@ export const Bar: Widget.Window = new Widget.Window({
|
||||
homogeneous: false,
|
||||
halign: Gtk.Align.START,
|
||||
children: [
|
||||
Logo(),
|
||||
Apps(),
|
||||
Workspaces(),
|
||||
FocusedClient()
|
||||
]
|
||||
@@ -47,7 +48,7 @@ export const Bar: Widget.Window = new Widget.Window({
|
||||
halign: Gtk.Align.END,
|
||||
children: [
|
||||
Tray(),
|
||||
Audio()
|
||||
Status()
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.CenterBoxProps)
|
||||
|
||||
+38
-15
@@ -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 { Tiles } from "../widget/control-center/Tiles";
|
||||
import { Sliders } from "../widget/control-center/Sliders";
|
||||
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
|
||||
import { hidePages, PagesWidget } from "../widget/control-center/Pages";
|
||||
import { NotifHistory } from "../widget/control-center/NotifHistory";
|
||||
|
||||
const connections: Array<number> = [];
|
||||
const { TOP, LEFT, BOTTOM, RIGHT } = Astal.WindowAnchor;
|
||||
|
||||
export const ControlCenter: Widget.Window = PopupWindow({
|
||||
className: "control-center",
|
||||
export const ControlCenter = new Widget.Window({
|
||||
namespace: "control-center",
|
||||
marginTop: 10,
|
||||
marginRight: 10,
|
||||
monitor: 0,
|
||||
onClose: () => hidePages(),
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.START,
|
||||
className: "control-center",
|
||||
anchor: TOP | BOTTOM | LEFT | RIGHT,
|
||||
exclusivity: Astal.Exclusivity.NORMAL,
|
||||
keymode: Astal.Keymode.EXCLUSIVE,
|
||||
layer: Astal.Layer.OVERLAY,
|
||||
focusOnMap: true,
|
||||
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({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
className: "popup",
|
||||
halign: Gtk.Align.END,
|
||||
css: `.popup {
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}`,
|
||||
vexpand: true,
|
||||
widthRequest: 400,
|
||||
onButtonPressEvent: () => true,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "control-center-container",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
widthRequest: 400,
|
||||
vexpand: false,
|
||||
hexpand: true,
|
||||
children: [
|
||||
QuickActions,
|
||||
Sliders,
|
||||
@@ -40,7 +63,7 @@ export const ControlCenter: Widget.Window = PopupWindow({
|
||||
NotifHistory
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as PopupWindowProps);
|
||||
} as Widget.WindowProps);
|
||||
|
||||
connections.push(ControlCenter.connect("hide", (_) => {
|
||||
hidePages();
|
||||
|
||||
Reference in New Issue
Block a user