Initial commit

This commit is contained in:
end-4
2023-12-25 18:16:14 +07:00
commit 16b0d77075
174 changed files with 18893 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
import { App, Service, Utils, Widget } from '../../imports.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { CONFIG_DIR, exec, execAsync } = Utils;
import { setupCursorHover } from "../../lib/cursorhover.js";
import { RoundedCorner } from "../../lib/roundedcorner.js";
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
// Removes everything after the last
// em dash, en dash, minus, vertical bar, or middle dot (note: maybe add open parenthesis?)
// For example:
// • Discord | #ricing-theming | r/unixporn — Mozilla Firefox --> • Discord | #ricing-theming
// GJS Error · Issue #112 · Aylur/ags — Mozilla Firefox --> GJS Error · Issue #112
function truncateTitle(str) {
let lastDash = -1;
let found = -1; // 0: em dash, 1: en dash, 2: minus, 3: vertical bar, 4: middle dot
for (let i = str.length - 1; i >= 0; i--) {
if (str[i] === '—') {
found = 0;
lastDash = i;
}
else if (str[i] === '' && found < 1) {
found = 1;
lastDash = i;
}
else if (str[i] === '-' && found < 2) {
found = 2;
lastDash = i;
}
else if (str[i] === '|' && found < 3) {
found = 3;
lastDash = i;
}
else if (str[i] === '·' && found < 4) {
found = 4;
lastDash = i;
}
}
if (lastDash === -1) return str;
return str.substring(0, lastDash);
}
export const ModuleLeftSpace = () => Widget.EventBox({
onScrollUp: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value += 0.05;
},
onScrollDown: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value -= 0.05;
},
onPrimaryClick: () => {
App.toggleWindow('sideleft');
},
child: Widget.Box({
homogeneous: false,
children: [
RoundedCorner('topleft', { className: 'corner-black' }),
Widget.Overlay({
overlays: [
Widget.Box({ hexpand: true }),
Widget.Box({
className: 'bar-sidemodule', hexpand: true,
children: [Widget.Box({
vertical: true,
className: 'bar-space-button',
children: [
Widget.Scrollable({
hexpand: true, vexpand: true,
hscroll: 'automatic', vscroll: 'never',
child: Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
className: 'txt-smaller bar-topdesc txt',
connections: [[Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client._class.length === 0 ? 'Desktop' : Hyprland.active.client._class;
}]],
}),
Widget.Label({
xalign: 0,
className: 'txt txt-smallie',
connections: [
[Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client._title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : truncateTitle(Hyprland.active.client._title);
}]
],
})
]
})
})
]
})]
}),
]
})
]
})
});
+53
View File
@@ -0,0 +1,53 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
import { ModuleLeftSpace } from "./leftspace.js";
import { ModuleMusic } from "./music.js";
import { ModuleRightSpace } from "./rightspace.js";
import { ModuleSystem } from "./system.js";
import { ModuleWorkspaces } from "./workspaces.js";
import { RoundedCorner } from "../../lib/roundedcorner.js";
const left = Widget.Box({
className: 'bar-sidemodule',
children: [ModuleMusic()],
});
const center = Widget.Box({
children: [
RoundedCorner('topright', { className: 'corner-bar-group' }),
ModuleWorkspaces(),
RoundedCorner('topleft', { className: 'corner-bar-group' }),
],
});
const right = Widget.Box({
className: 'bar-sidemodule',
children: [ModuleSystem()],
});
export default () => Widget.Window({
name: 'bar',
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
visible: true,
child: Widget.CenterBox({
className: 'bar-bg',
startWidget: ModuleLeftSpace(),
centerWidget: Widget.Box({
className: 'spacing-h--20',
children: [
left,
center,
right,
]
}),
endWidget: ModuleRightSpace(),
setup: (self) => {
const styleContext = self.get_style_context();
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
}
}),
});
+82
View File
@@ -0,0 +1,82 @@
import { Service, Utils, Widget } from '../../imports.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
import { showMusicControls } from '../../variables.js';
const TrackProgress = () => {
const _updateProgress = (circprog) => {
const mpris = Mpris.getPlayer('');
if (!mpris) return;
// Set circular progress (font size cuz that's how this hacky circprog works)
circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
}
return AnimatedCircProg({
className: 'bar-music-circprog',
vpack: 'center',
connections: [ // Update on change/once every 3 seconds
[Mpris, _updateProgress],
[3000, _updateProgress]
]
})
}
export const ModuleMusic = () => Widget.EventBox({
onScrollUp: () => execAsync('hyprctl dispatch workspace -1'),
onScrollDown: () => execAsync('hyprctl dispatch workspace +1'),
onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']),
onMiddleClickRelease: () => Mpris.getPlayer('')?.playPause(),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-music spacing-h-10',
children: [
Widget.Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
children: [Widget.Overlay({
child: Widget.Box({
vpack: 'center',
className: 'bar-music-playstate',
homogeneous: true,
children: [Widget.Label({
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}]],
})],
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
if (!mpris) return;
label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
}]],
}),
overlays: [
TrackProgress(),
]
})]
}),
Widget.Scrollable({
hexpand: true,
child: Widget.Label({
className: 'txt txt-smallie',
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
if (mpris)
label.label = `${mpris.trackTitle}${mpris.trackArtists.join(', ')}`;
else
label.label = 'No media';
}]],
})
})
]
})
]
})
});
+59
View File
@@ -0,0 +1,59 @@
import { App, Utils, Widget } from '../../imports.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
const { execAsync } = Utils;
import Indicator from '../../services/indicator.js';
import { StatusIcons } from "../../lib/statusicons.js";
import { RoundedCorner } from "../../lib/roundedcorner.js";
import { Tray } from "./tray.js";
export const ModuleRightSpace = () => {
const barTray = Tray();
const barStatusIcons = StatusIcons({
className: 'bar-statusicons',
connections: [[App, (self, currentName, visible) => {
if (currentName === 'sideright') {
self.toggleClassName('bar-statusicons-active', visible);
}
}]],
});
return Widget.EventBox({
onScrollUp: () => {
if (!Audio.speaker) return;
Audio.speaker.volume += 0.03;
Indicator.popup(1);
},
onScrollDown: () => {
if (!Audio.speaker) return;
Audio.speaker.volume -= 0.03;
Indicator.popup(1);
},
// onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
// onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
onPrimaryClick: () => App.toggleWindow('sideright'),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']),
onMiddleClickRelease: () => Mpris.getPlayer('')?.playPause(),
child: Widget.Box({
homogeneous: false,
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-15 txt',
children: [
Widget.Box({ hexpand: true, }),
barTray,
barStatusIcons,
],
}),
]
}),
RoundedCorner('topright', { className: 'corner-black' })
]
})
});
}
+149
View File
@@ -0,0 +1,149 @@
// This is for the right pill of the bar.
// For the cool memory indicator on the sidebar, see sysinfo.js
import { Service, Utils, Widget } from '../../imports.js';
const { exec, execAsync } = Utils;
const { GLib } = imports.gi;
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { MaterialIcon } from '../../lib/materialicon.js';
const BATTERY_LOW = 20;
const BarClock = () => Widget.Box({
vpack: 'center',
className: 'spacing-h-5',
children: [
Widget.Label({
className: 'bar-clock',
connections: [[5000, label => {
label.label = GLib.DateTime.new_now_local().format("%H:%M");
}]],
}),
Widget.Label({
className: 'txt-norm txt',
label: '•',
}),
Widget.Label({
className: 'txt-smallie txt',
connections: [[5000, label => {
label.label = GLib.DateTime.new_now_local().format("%A, %d/%m");
}]],
}),
],
});
const BarBattery = () => {
const BarResourceValue = (name, icon, command) => Widget.Box({
vpack: 'center',
className: 'bar-batt spacing-h-5',
children: [
MaterialIcon(icon, 'small'),
Widget.ProgressBar({ // Progress
vpack: 'center', hexpand: true,
className: 'bar-prog-batt',
connections: [[5000, (progress) => execAsync(['bash', '-c', command])
.then((output) => {
progress.value = Number(output) / 100;
progress.tooltipText = `${name}: ${Number(output)}%`
})
.catch(print)
]],
}),
]
});
const batteryWidget = Widget.Box({
vpack: 'center',
hexpand: true,
className: 'spacing-h-5 bar-batt',
connections: [[Battery, box => {
box.toggleClassName('bar-batt-low', Battery.percent <= BATTERY_LOW);
box.toggleClassName('bar-batt-full', Battery.charged);
}]],
children: [
MaterialIcon('settings_heart', 'small'),
Widget.Label({ // Percentage
className: 'bar-batt-percentage',
connections: [[Battery, label => {
label.label = `${Battery.percent}`;
}]],
}),
Widget.ProgressBar({ // Progress
vpack: 'center',
hexpand: true,
className: 'bar-prog-batt',
connections: [[Battery, progress => {
progress.value = Math.abs(Battery.percent / 100); // battery could be initially negative wtf
progress.toggleClassName('bar-prog-batt-low', Battery.percent <= BATTERY_LOW);
progress.toggleClassName('bar-prog-batt-full', Battery.charged);
batteryWidget.tooltipText = `Battery: ${Battery.percent}%`
}]],
}),
Widget.Revealer({ // A dot for charging state
transitionDuration: 150,
revealChild: false,
transition: 'slide_left',
child: Widget.Box({
className: 'spacing-h-3',
children: [
Widget.Box({
vpack: 'center',
className: 'bar-batt-chargestate-charging-smaller',
connections: [[Battery, box => {
box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
}]],
}),
Widget.Box({
vpack: 'center',
className: 'bar-batt-chargestate-charging',
connections: [[Battery, box => {
box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
}]],
}),
]
}),
connections: [[Battery, revealer => {
revealer.revealChild = Battery.charging;
}]],
}),
],
});
const memUsage = Widget.Box({
className: 'spacing-h-5',
children: [
BarResourceValue('RAM usage', 'memory', `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
BarResourceValue('Swap usage', 'swap_horiz', `free | awk '/^Swap/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
]
})
const widgetStack = Widget.Stack({
transition: 'slide_up_down',
vpack: 'center',
hexpand: true,
items: [
['fallback', memUsage],
['battery', batteryWidget],
],
setup: (stack) => Utils.timeout(1, () => {
if (Battery.available) stack.shown = 'battery';
else stack.shown = 'fallback';
})
})
return widgetStack;
}
export const ModuleSystem = () => Widget.EventBox({
onScrollUp: () => execAsync('hyprctl dispatch workspace -1'),
onScrollDown: () => execAsync('hyprctl dispatch workspace +1'),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-system spacing-h-15',
children: [
BarClock(),
BarBattery(),
],
}),
]
})
});
+66
View File
@@ -0,0 +1,66 @@
const { GLib, Gdk, Gtk } = imports.gi;
import { Service, Widget } from '../../imports.js';
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { Box, Icon, Button, Revealer } = Widget;
const { Gravity } = imports.gi.Gdk;
const revealerDuration = 200;
const SysTrayItem = item => Button({
className: 'bar-systray-item',
child: Icon({
hpack: 'center',
binds: [['icon', item, 'icon']]
}),
binds: [['tooltipMarkup', item, 'tooltip-markup']],
onClicked: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
onSecondaryClick: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
});
export const Tray = (props = {}) => {
const trayContent = Box({
vpack: 'center',
className: 'bar-systray bar-group',
properties: [
['items', new Map()],
['onAdded', (box, id) => {
const item = SystemTray.getItem(id);
if (!item) return;
item.menu.className = 'menu';
if (box._items.has(id) || !item)
return;
const widget = SysTrayItem(item);
box._items.set(id, widget);
box.pack_start(widget, false, false, 0);
box.show_all();
if (box._items.size === 1)
trayRevealer.revealChild = true;
}],
['onRemoved', (box, id) => {
if (!box._items.has(id))
return;
box._items.get(id).destroy();
box._items.delete(id);
if (box._items.size === 0)
trayRevealer.revealChild = false;
}],
],
connections: [
[SystemTray, (box, id) => box._onAdded(box, id), 'added'],
[SystemTray, (box, id) => box._onRemoved(box, id), 'removed'],
],
});
const trayRevealer = Widget.Revealer({
revealChild: false,
transition: 'slide_left',
transitionDuration: revealerDuration,
child: trayContent,
});
return Box({
...props,
children: [
trayRevealer,
]
});
}
+100
View File
@@ -0,0 +1,100 @@
const { GLib, Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const WORKSPACE_SIDE_PAD = 0.546; // rem
const NUM_OF_WORKSPACES = 10;
let lastWorkspace = 0;
const activeWorkspaceIndicator = Widget.Box({
css: `
padding: 0rem ${WORKSPACE_SIDE_PAD}rem;
`,
children: [
Widget.Box({
vpack: 'center',
hpack: 'start',
className: 'bar-ws-active-box',
connections: [
[Hyprland.active.workspace, (box) => {
const ws = Hyprland.active.workspace.id;
box.setCss(`
margin-left: ${1.774 * (ws - 1) + 0.068}rem;
`);
lastWorkspace = ws;
}],
],
children: [
Widget.Label({
vpack: 'center',
className: 'bar-ws-active',
label: ``,
})
]
})
]
});
export const ModuleWorkspaces = () => Widget.EventBox({
onScrollUp: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace -1 &']),
onScrollDown: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace +1 &']),
onMiddleClickRelease: () => App.toggleWindow('overview'),
onSecondaryClickRelease: () => App.toggleWindow('osk'),
child: Widget.Box({
homogeneous: true,
className: 'bar-ws-width',
children: [
Widget.Overlay({
passThrough: true,
child: Widget.Box({
homogeneous: true,
className: 'bar-group-center',
children: [Widget.Box({
className: 'bar-group-standalone bar-group-pad',
})]
}),
overlays: [
Widget.Overlay({
setup: (self) => self.set_overlay_pass_through(self.get_children()[1], true),
child: Widget.Box({
hpack: 'center',
css: `
padding: 0rem ${WORKSPACE_SIDE_PAD}rem;
`,
// homogeneous: true,
children: Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => i + 1).map(i => Widget.Button({
onPrimaryClick: () => Utils.execAsync(['bash', '-c', `hyprctl dispatch workspace ${i} &`]).catch(print),
child: Widget.Label({
vpack: 'center',
label: `${i}`,
className: 'bar-ws txt',
}),
})),
connections: [
[Hyprland, (box) => {
// console.log('update');
const kids = box.children;
kids.forEach((child, i) => {
child.child.toggleClassName('bar-ws-occupied', false);
child.child.toggleClassName('bar-ws-occupied-left', false);
child.child.toggleClassName('bar-ws-occupied-right', false);
child.child.toggleClassName('bar-ws-occupied-left-right', false);
});
const occupied = Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => Hyprland.getWorkspace(i + 1)?.windows > 0);
for (let i = 0; i < occupied.length; i++) {
if (!occupied[i]) continue;
const child = kids[i];
child.child.toggleClassName(`bar-ws-occupied${!occupied[i - 1] ? '-left' : ''}${!occupied[i + 1] ? '-right' : ''}`, true);
}
}, 'notify::workspaces'],
],
}),
overlays: [
activeWorkspaceIndicator,
]
})
],
})
]
})
});