Add ags
This commit is contained in:
parent
3648827637
commit
c8d7b4c090
8 changed files with 415 additions and 0 deletions
33
misc/.config/ags/config.js
Normal file
33
misc/.config/ags/config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { execAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import { forMonitors } from "./utils.js";
|
||||
import { Bar } from "./modules/bar/bar.js";
|
||||
import { Popups } from './modules/popups/popups.js';
|
||||
|
||||
timeout(100, () => execAsync([
|
||||
'notify-send',
|
||||
'Notification Popup Example',
|
||||
'Lorem ipsum dolor sit amet, qui minim labore adipisicing ' +
|
||||
'minim sint cillum sint consectetur cupidatat.',
|
||||
'-A', 'Cool!',
|
||||
'-i', 'info-symbolic',
|
||||
]));
|
||||
|
||||
// main scss file
|
||||
const scss = `${App.configDir}/style.scss`
|
||||
|
||||
// target css file
|
||||
const css = `${App.configDir}/style.css`
|
||||
|
||||
// make sure sassc is installed on your system
|
||||
Utils.exec(`rm ${css} >/dev/null 2>&1`)
|
||||
Utils.exec(`sassc ${scss} ${css}`)
|
||||
|
||||
// Main config
|
||||
export default {
|
||||
style: `${App.configDir}/style.css`,
|
||||
windows: [
|
||||
...forMonitors(Bar),
|
||||
Popups(0),
|
||||
],
|
||||
};
|
||||
|
||||
50
misc/.config/ags/modules/bar/bar.js
Normal file
50
misc/.config/ags/modules/bar/bar.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const Network = await Service.import("network");
|
||||
const Bluetooth = await Service.import("bluetooth");
|
||||
|
||||
// Widgets
|
||||
import { Workspaces, ClientTitle } from "./workspaces.js";
|
||||
import { Battery } from "./battery.js";
|
||||
import { Date, Time } from "./clock.js";
|
||||
|
||||
const Left = (monitor) => Widget.Box({
|
||||
children: [
|
||||
Workspaces(monitor),
|
||||
ClientTitle(),
|
||||
],
|
||||
});
|
||||
|
||||
const Center = () => Widget.Box({
|
||||
children: [ ],
|
||||
});
|
||||
|
||||
const Right = () => Widget.Box({
|
||||
hpack: "end",
|
||||
children: [
|
||||
Battery(),
|
||||
Date(),
|
||||
Time(),
|
||||
],
|
||||
});
|
||||
|
||||
const hyprlandMonitors = Utils.exec(`/bin/sh -c "hyprctl monitors | grep -o '(ID [0-9]*' | awk '{print $2}' | sort"`).split('\n').map(Number);
|
||||
|
||||
const getHyprlandMonitor = monitor => {
|
||||
if (monitor >= hyprlandMonitors.length) {
|
||||
return 0;
|
||||
}
|
||||
return hyprlandMonitors[monitor];
|
||||
};
|
||||
|
||||
export const Bar = (monitor = 0) => Widget.Window({
|
||||
name: `bar-${monitor}`, // name has to be unique
|
||||
class_name: 'bar',
|
||||
monitor,
|
||||
anchor: ['top', 'left', 'right'],
|
||||
exclusivity: 'exclusive',
|
||||
child: Widget.CenterBox({
|
||||
start_widget: Left(getHyprlandMonitor(monitor)),
|
||||
center_widget: Center(),
|
||||
end_widget: Right(),
|
||||
}),
|
||||
});
|
||||
|
||||
24
misc/.config/ags/modules/bar/battery.js
Normal file
24
misc/.config/ags/modules/bar/battery.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const { exec, execAsync } = Utils;
|
||||
|
||||
export const Battery = () => Widget.Button({
|
||||
class_name: "battery",
|
||||
child: Widget.Label({
|
||||
setup: (self) => {self.poll(1000, (self) =>
|
||||
execAsync("block_battery")
|
||||
.then((out) => {
|
||||
self.label = out;
|
||||
let num = Number(out.substring(2, out.length - 1));
|
||||
|
||||
// Turn red at low battery
|
||||
if (num < 20) {
|
||||
self.class_name = "module, critical";
|
||||
} else {
|
||||
self.class_name = "module";
|
||||
};
|
||||
})
|
||||
.catch(console.error),
|
||||
)},
|
||||
}),
|
||||
on_clicked: () => {},
|
||||
});
|
||||
|
||||
32
misc/.config/ags/modules/bar/clock.js
Normal file
32
misc/.config/ags/modules/bar/clock.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const { exec, execAsync } = Utils;
|
||||
|
||||
export const Date = () => Widget.Button({
|
||||
child: Widget.Label({
|
||||
class_name: "module",
|
||||
setup: (self) => {
|
||||
self.poll(1000, (self) =>
|
||||
execAsync(["date", "+ %a %b %e"])
|
||||
.then((time) => (self.label = time))
|
||||
.catch(console.error),
|
||||
);
|
||||
},
|
||||
}),
|
||||
on_clicked: () => {
|
||||
},
|
||||
});
|
||||
|
||||
export const Time = () => Widget.Button({
|
||||
child: Widget.Label({
|
||||
class_name: "module",
|
||||
setup: (self) => {
|
||||
self.poll(1000, (self) =>
|
||||
execAsync(["date", "+ %R"])
|
||||
.then((time) => (self.label = time))
|
||||
.catch(console.error),
|
||||
);
|
||||
},
|
||||
}),
|
||||
on_clicked: () => {
|
||||
},
|
||||
});
|
||||
|
||||
53
misc/.config/ags/modules/bar/workspaces.js
Normal file
53
misc/.config/ags/modules/bar/workspaces.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
const Hyprland = await Service.import("hyprland");
|
||||
|
||||
var monitorsActiveWorkspaceCache = new Array(10).fill(-1);
|
||||
|
||||
const is_active = (workspaceID) => {
|
||||
let monitorID = Hyprland.getWorkspace(workspaceID).monitorID;
|
||||
var monitor = Hyprland.getMonitor(monitorID);
|
||||
|
||||
// Somewhat of a hack going on here. For some reason, the
|
||||
// Hyprland.monitors array only contains the monitorIDs up to the
|
||||
// currently focused one. That means if I am currently focused on
|
||||
// monitor 0, then I cannot check what the focused workspace is on
|
||||
// monitor 3. Thus, I had to create a global cache on top to reference
|
||||
// for when the result of Hyprland.getMonitor(monitorID) is undefined.
|
||||
let activeID = -1;
|
||||
if (monitor == undefined) {
|
||||
activeID = monitorsActiveWorkspaceCache[monitorID];
|
||||
} else {
|
||||
monitorsActiveWorkspaceCache[monitorID] = monitor.activeWorkspace.id;
|
||||
activeID = monitor.activeWorkspace.id;
|
||||
}
|
||||
return workspaceID == activeID;
|
||||
}
|
||||
|
||||
export const Workspaces = (monitorID) => Widget.Box({
|
||||
class_name: 'workspaces',
|
||||
children: Hyprland.bind('workspaces').transform(ws => {
|
||||
return ws
|
||||
.filter(({ id, name }) => {
|
||||
if (name == "special") {
|
||||
// Hyprland sometimes uses a special workspace, which
|
||||
// wouldn't appear on the bar.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only show the workspaces belonging to the monitor
|
||||
return Hyprland.getWorkspace(id).monitorID == monitorID;
|
||||
})
|
||||
.sort((a, b) => a.id - b.id) // Show in order of ID
|
||||
.map(({ id, name }) => Widget.Button({
|
||||
on_clicked: () => Hyprland.sendMessage(`dispatch workspace ${id}`),
|
||||
child: Widget.Label(`${name}`),
|
||||
class_name: Hyprland.active.workspace.bind('id')
|
||||
.transform(i => `${is_active(id) ? 'focused' : ''}`),
|
||||
}));
|
||||
}),
|
||||
});
|
||||
|
||||
export const ClientTitle = () => Widget.Label({
|
||||
class_name: 'module',
|
||||
label: Hyprland.active.client.bind('title'),
|
||||
});
|
||||
|
||||
105
misc/.config/ags/modules/popups/popups.js
Normal file
105
misc/.config/ags/modules/popups/popups.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */
|
||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
||||
if (image) {
|
||||
return Widget.Box({
|
||||
css: `
|
||||
background-image: url("${image}");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
let icon = 'add';
|
||||
if (lookUpIcon(app_icon))
|
||||
icon = app_icon;
|
||||
|
||||
if (app_entry && lookUpIcon(app_entry))
|
||||
icon = app_entry;
|
||||
|
||||
return Widget.Icon(icon);
|
||||
};
|
||||
|
||||
/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */
|
||||
export const Notification = n => {
|
||||
const icon = Widget.Box({
|
||||
vpack: 'start',
|
||||
class_name: 'icon',
|
||||
child: NotificationIcon(n),
|
||||
});
|
||||
|
||||
const title = Widget.Label({
|
||||
class_name: 'title',
|
||||
xalign: 0,
|
||||
justification: 'left',
|
||||
hexpand: true,
|
||||
max_width_chars: 24,
|
||||
truncate: 'end',
|
||||
wrap: true,
|
||||
label: n.summary,
|
||||
use_markup: true,
|
||||
});
|
||||
|
||||
const body = Widget.Label({
|
||||
class_name: 'body',
|
||||
hexpand: true,
|
||||
use_markup: true,
|
||||
xalign: 0,
|
||||
justification: 'left',
|
||||
label: n.body,
|
||||
wrap: true,
|
||||
});
|
||||
|
||||
const actions = Widget.Box({
|
||||
class_name: 'actions',
|
||||
children: n.actions.map(({ id, label }) => Widget.Button({
|
||||
class_name: 'action-button',
|
||||
on_clicked: () => n.invoke(id),
|
||||
hexpand: true,
|
||||
child: Widget.Label(label),
|
||||
})),
|
||||
});
|
||||
|
||||
return Widget.EventBox({
|
||||
on_primary_click: () => n.dismiss(),
|
||||
child: Widget.Box({
|
||||
class_name: `notification ${n.urgency}`,
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
children: [
|
||||
icon,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
title,
|
||||
body,
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
actions,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const Popups = monitor => Widget.Window({
|
||||
name: `notifications`,
|
||||
monitor,
|
||||
layer: 'overlay',
|
||||
anchor: ['top', 'right'],
|
||||
child: Widget.Box({
|
||||
class_name: 'notifications',
|
||||
vertical: true,
|
||||
children: Notifications.bind('popups').transform(popups => {
|
||||
return popups.map(Notification);
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
107
misc/.config/ags/style.scss
Normal file
107
misc/.config/ags/style.scss
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
$fg: #FFFFFF;
|
||||
$bg: #000000;
|
||||
|
||||
* {
|
||||
font-family: Fira Code, Symbols Nerd Font Mono;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
window.bar {
|
||||
background-color: $bg;
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
// .module {
|
||||
// margin-left: 5px;
|
||||
// margin-right: 5px;
|
||||
// }
|
||||
|
||||
.bar button {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
background: $bg;
|
||||
border-radius: 0px;
|
||||
border-color: $bg;
|
||||
}
|
||||
|
||||
.bar button:hover{
|
||||
background: #666666;
|
||||
}
|
||||
|
||||
.bar button.focused{
|
||||
// When the workspace is being shown, and the monitor is focused.
|
||||
box-shadow: inset 0 -3px $fg;
|
||||
}
|
||||
|
||||
.bar button.active{
|
||||
// When the workspace is being shown, and the monitor is focused.
|
||||
box-shadow: inset 0 -3px $fg;
|
||||
}
|
||||
|
||||
.battery .critical {
|
||||
color: #FF0000;
|
||||
}
|
||||
|
||||
|
||||
window#notifications {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
window#notifications box.notifications {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.icon image {
|
||||
font-size: 58px;
|
||||
/* to center the icon */
|
||||
margin: 5px;
|
||||
color: $fg
|
||||
}
|
||||
|
||||
.icon box {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
min-width: 350px;
|
||||
border-radius: 11px;
|
||||
padding: 1em;
|
||||
margin: .5em;
|
||||
border: 1px solid $fg;
|
||||
background-color: $bg;
|
||||
}
|
||||
|
||||
.notification.critical {
|
||||
border: 1px solid lightcoral;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $fg;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.body {
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
.actions .action-button {
|
||||
margin: 0 .4em;
|
||||
margin-top: .8em;
|
||||
}
|
||||
|
||||
.actions .action-button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.actions .action-button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
11
misc/.config/ags/utils.js
Normal file
11
misc/.config/ags/utils.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import Gdk from "gi://Gdk"
|
||||
|
||||
export function forMonitors(widget) {
|
||||
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
|
||||
return range(n, 0).map(widget).flat(1);
|
||||
}
|
||||
|
||||
export function range(length, start = 1) {
|
||||
return Array.from({ length }, (_, i) => i + start)
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue