ags: update to new syntax

This commit is contained in:
end-4
2024-01-11 16:50:12 +07:00
parent c61db15a88
commit 85704218e3
74 changed files with 2155 additions and 1898 deletions
+34 -58
View File
@@ -1,44 +1,42 @@
import { App, Service, Utils, Widget } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.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;
}
const WindowTitle = async () => 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',
setup: (self) => self.hook(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',
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
}),
})
]
})
})
const OptionalWindowTitle = async () => {
try {
return await WindowTitle();
} catch {
return null;
}
if (lastDash === -1) return str;
return str.substring(0, lastDash);
}
};
const OptionalWindowTitleInstance = await OptionalWindowTitle();
export const ModuleLeftSpace = () => Widget.EventBox({
onScrollUp: () => {
@@ -65,29 +63,7 @@ export const ModuleLeftSpace = () => Widget.EventBox({
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',
setup: (self) => self.hook(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',
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client._title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client._title;
}),
})
]
})
})
OptionalWindowTitleInstance,
]
})]
}),
+11 -6
View File
@@ -1,13 +1,18 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
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 OptionalWorkspaces = async () => {
try {
return (await import('./workspaces_hyprland.js')).default();
} catch {
// return (await import('./workspaces_sway.js')).default();
return Box({});
}
};
const left = Widget.Box({
className: 'bar-sidemodule',
@@ -18,7 +23,7 @@ const left = Widget.Box({
const center = Widget.Box({
children: [
ModuleWorkspaces(),
await OptionalWorkspaces(),
],
});
+12 -12
View File
@@ -1,6 +1,6 @@
import { Service, Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
@@ -23,10 +23,10 @@ const TrackProgress = () => {
return AnimatedCircProg({
className: 'bar-music-circprog',
vpack: 'center', hpack: 'center',
connections: [ // Update on change/once every 3 seconds
[Mpris, _updateProgress],
[3000, _updateProgress]
]
extraSetup: (self) => self
.hook(Mpris, _updateProgress)
.poll(3000, _updateProgress)
,
})
}
@@ -53,17 +53,17 @@ export const ModuleMusic = () => Widget.EventBox({ // TODO: use cairo to make bu
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
connections: [[Mpris, label => {
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}]],
}),
})],
connections: [[Mpris, label => {
setup: (self) => self.hook(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(),
@@ -74,13 +74,13 @@ export const ModuleMusic = () => Widget.EventBox({ // TODO: use cairo to make bu
hexpand: true,
child: Widget.Label({
className: 'txt-smallie txt-onSurfaceVariant',
connections: [[Mpris, label => {
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
if (mpris)
label.label = `${trimTrackTitle(mpris.trackTitle)}${mpris.trackArtists.join(', ')}`;
else
label.label = 'No media';
}]],
}),
})
})
]
+15 -12
View File
@@ -1,6 +1,8 @@
import { App, Utils, Widget } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { execAsync } = Utils;
import Indicator from '../../services/indicator.js';
@@ -33,8 +35,8 @@ export const ModuleRightSpace = () => {
// 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(),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
child: Widget.Box({
homogeneous: false,
children: [
@@ -51,19 +53,20 @@ export const ModuleRightSpace = () => {
Widget.Revealer({
transition: 'slide_left',
revealChild: false,
properties: [
['count', 0],
['update', (self, diff) => {
self._count += diff;
self.revealChild = (self._count > 0);
}]],
attribute: {
'count': 0,
'update': (self, diff) => {
self.attribute.count += diff;
self.revealChild = (self.attribute.count > 0);
}
},
child: Widget.Box({
vpack: 'center',
className: 'separator-circle',
}),
setup: (self) => self
.hook(SystemTray, (self) => self._update(self, 1), 'added')
.hook(SystemTray, (self) => self._update(self, -1), 'removed')
.hook(SystemTray, (self) => self.attribute.update(self, 1), 'added')
.hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
,
}),
barStatusIcons,
+14 -12
View File
@@ -1,6 +1,7 @@
// 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';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
const { exec, execAsync } = Utils;
const { GLib } = imports.gi;
@@ -21,9 +22,9 @@ const BatBatteryProgress = () => {
return AnimatedCircProg({
className: 'bar-batt-circprog',
vpack: 'center', hpack: 'center',
connections: [
[Battery, _updateProgress],
],
extraSetup: (self) => self
.hook(Battery, _updateProgress)
,
})
}
@@ -147,13 +148,6 @@ const BarResource = (name, icon, command) => {
const resourceCircProg = AnimatedCircProg({
className: 'bar-batt-circprog',
vpack: 'center', hpack: 'center',
connections: [[5000, (progress) => execAsync(['bash', '-c', command])
.then((output) => {
progress.css = `font-size: ${Number(output)}px;`;
resourceLabel.label = `${Math.round(Number(output))}%`;
widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
}).catch(print)
]],
});
const widget = Box({
className: 'spacing-h-4 txt-onSurfaceVariant',
@@ -170,7 +164,15 @@ const BarResource = (name, icon, command) => {
}),
overlays: [resourceCircProg]
}),
]
],
setup: (self) => self
.poll(5000, () => execAsync(['bash', '-c', command])
.then((output) => {
resourceCircProg.css = `font-size: ${Number(output)}px;`;
resourceLabel.label = `${Math.round(Number(output))}%`;
widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
}).catch(print))
,
});
return widget;
}
+31 -27
View File
@@ -1,24 +1,28 @@
const { GLib, Gdk, Gtk } = imports.gi;
import { Service, Widget } from '../../imports.js';
const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.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({
const SysTrayItem = (item) => Button({
className: 'bar-systray-item',
child: Icon({
hpack: 'center',
binds: [['icon', item, 'icon']],
setup: (self) => Utils.timeout(1, () => {
const styleContext = self.get_parent().get_style_context();
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
self.size = Math.max(width, height, 1); // im too lazy to add another box lol
}),
setup: (self) => {
self.hook(item, (self) => self.icon = item.icon);
Utils.timeout(1, () => {
const styleContext = self.get_parent().get_style_context();
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
self.size = Math.max(width, height, 1); // im too lazy to add another box lol
})
},
}),
binds: [['tooltipMarkup', item, 'tooltip-markup']],
setup: (self) => self
.hook(item, (self) => self.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),
});
@@ -26,34 +30,34 @@ const SysTrayItem = item => Button({
export const Tray = (props = {}) => {
const trayContent = Box({
className: 'margin-right-5 spacing-h-15',
properties: [
['items', new Map()],
['onAdded', (box, id) => {
attribute: {
items: new Map(),
onAdded: (box, id) => {
const item = SystemTray.getItem(id);
if (!item) return;
item.menu.className = 'menu';
if (box._items.has(id) || !item)
if (box.attribute.items.has(id) || !item)
return;
const widget = SysTrayItem(item);
box._items.set(id, widget);
box.attribute.items.set(id, widget);
box.add(widget);
box.show_all();
if (box._items.size === 1)
if (box.attribute.items.size === 1)
trayRevealer.revealChild = true;
}],
['onRemoved', (box, id) => {
if (!box._items.has(id))
},
onRemoved: (box, id) => {
if (!box.attribute.items.has(id))
return;
box._items.get(id).destroy();
box._items.delete(id);
if (box._items.size === 0)
box.attribute.items.get(id).destroy();
box.attribute.items.delete(id);
if (box.attribute.items.size === 0)
trayRevealer.revealChild = false;
}],
],
},
},
setup: (self) => self
.hook(SystemTray, (box, id) => box._onAdded(box, id), 'added')
.hook(SystemTray, (box, id) => box._onRemoved(box, id), 'removed')
.hook(SystemTray, (box, id) => box.attribute.onAdded(box, id), 'added')
.hook(SystemTray, (box, id) => box.attribute.onRemoved(box, id), 'removed')
,
});
const trayRevealer = Widget.Revealer({
@@ -3,7 +3,8 @@ const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
import { App, Service, Utils, Widget } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, DrawingArea, EventBox } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
@@ -16,11 +17,11 @@ const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not sho
const WorkspaceContents = (count = 10) => {
return DrawingArea({
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
properties: [
['initialized', false],
['workspaceMask', 0],
['updateMask', (self) => {
if (self._initialized) return; // We only need this to run once
attribute: {
initialized: false,
workspaceMask: 0,
updateMask: (self) => {
if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Hyprland.workspaces;
let workspaceMask = 0;
for (let i = 0; i < workspaces.length; i++) {
@@ -31,21 +32,21 @@ const WorkspaceContents = (count = 10) => {
workspaceMask |= (1 << ws.id);
}
}
self._workspaceMask = workspaceMask;
self._initialized = true;
}],
['toggleMask', (self, occupied, name) => {
if (occupied) self._workspaceMask |= (1 << parseInt(name));
else self._workspaceMask &= ~(1 << parseInt(name));
}]
],
self.attribute.workspaceMask = workspaceMask;
self.attribute.initialized = true;
},
toggleMask: (self, occupied, name) => {
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
},
},
setup: (area) => area
.hook(Hyprland.active.workspace, (area) =>
area.setCss(`font-size: ${Hyprland.active.workspace.id}px;`)
)
.hook(Hyprland, (self) => self._updateMask(self), 'notify::workspaces')
.hook(Hyprland, (self, name) => self._toggleMask(self, true, name), 'workspace-added')
.hook(Hyprland, (self, name) => self._toggleMask(self, false, name), 'workspace-removed')
.hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
.hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
.hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
.on('draw', Lang.bind(area, (area, cr) => {
const allocation = area.get_allocation();
const { width, height } = allocation;
@@ -85,12 +86,12 @@ const WorkspaceContents = (count = 10) => {
// Draw workspace numbers
for (let i = 1; i <= count; i++) {
if (area._workspaceMask & (1 << i)) {
if (area.attribute.workspaceMask & (1 << i)) {
// Draw bg highlight
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
const wsCenterY = height / 2;
if (!(area._workspaceMask & (1 << (i - 1)))) { // Left
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
}
@@ -98,7 +99,7 @@ const WorkspaceContents = (count = 10) => {
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
if (!(area._workspaceMask & (1 << (i + 1)))) { // Right
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
@@ -141,9 +142,7 @@ export default () => EventBox({
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`),
onMiddleClickRelease: () => App.toggleWindow('overview'),
onSecondaryClickRelease: () => App.toggleWindow('osk'),
properties: [
['clicked', false],
],
attribute: { clicked: false },
child: Box({
homogeneous: true,
className: 'bar-group-margin',
@@ -158,8 +157,7 @@ export default () => EventBox({
setup: (self) => {
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
if (!self._clicked) return;
console.log('switching move');
if (!self.attribute.clicked) return;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
@@ -167,13 +165,12 @@ export default () => EventBox({
})
self.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
console.log('switching');
self._clicked = true;
self.attribute.clicked = true;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
Hyprland.sendMessage(`dispatch workspace ${wsId}`);
})
self.on('button-release-event', (self) => self._clicked = false);
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
})
@@ -0,0 +1,58 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Sway from "../../services/sway.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import options from "../../options.js";
import { range } from "../../utils.js";
const dispatch = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`);
const Workspaces = () => {
const ws = options.workspaces.value || 20;
return Widget.Box({
children: range(ws).map((i) =>
Widget.Button({
setup: (btn) => (btn.id = i),
on_clicked: () => dispatch(i),
child: Widget.Label({
label: `${i}`,
class_name: "indicator",
vpack: "center",
}),
setup: (self) => self.hook(Sway, (btn) => {
btn.toggleClassName("active", Sway.active.workspace.name == i);
btn.toggleClassName(
"occupied",
Sway.getWorkspace(`${i}`)?.nodes.length > 0,
);
}),
})
),
setup: (self) => self.hook(Sway.active.workspace,
(box) => box.children.map((btn) => {
btn.visible = Sway.workspaces.some(
(ws) => ws.name == btn.id,
);
})
),
});
};
export default () => Widget.EventBox({
class_name: "workspaces panel-button",
child: Widget.Box({
// its nested like this to keep it consistent with other PanelButton widgets
child: Widget.EventBox({
on_scroll_up: () => dispatch("next"),
on_scroll_down: () => dispatch("prev"),
class_name: "eventbox",
// binds: [["child", options.workspaces, "value", Workspaces]],
setup: (self) => self
.hook(options.workspaces, (self) => Selection.child = Workspaces(), "value")
,
}),
}),
setup: (self) => {
console.log('[LOG] Sway workspace module loaded')
}
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { keybindList } from "../../data/keybinds.js";
export const Keybinds = () => Widget.Box({
+2 -1
View File
@@ -1,5 +1,6 @@
const { Gdk, Gtk } = imports.gi;
import { Service, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Service from 'resource:///com/github/Aylur/ags/service.js';
import { Keybinds } from "./keybinds.js";
import { setupCursorHover } from "../../lib/cursorhover.js";
@@ -1,5 +1,6 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import TimeAndLaunchesWidget from './timeandlaunches.js'
@@ -1,4 +1,5 @@
import { App, Service, Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, EventBox, Label, Revealer, Overlay } = Widget;
import { AnimatedCircProg } from '../../lib/animatedcircularprogress.js'
@@ -24,7 +25,9 @@ const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props
Label({
xalign: 1,
className: 'titlefont txt-norm txt-onSecondaryContainer',
connections: [[interval, (label) => displayFunc(label)]]
setup: (self) => self
.poll(interval, (label) => displayFunc(label))
,
})
]
})
@@ -32,11 +35,13 @@ const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props
Overlay({
child: AnimatedCircProg({
className: 'bg-system-circprog',
connections: [[interval, (self) => {
execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
self.css = `font-size: ${Math.round(newValue)}px;`
}).catch(print);
}]]
extraSetup: (self) => self
.poll(interval, (self) => {
execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
self.css = `font-size: ${Math.round(newValue)}px;`
}).catch(print);
})
,
}),
overlays: [
MaterialIcon(`${icon}`, 'hugeass'),
@@ -143,7 +148,7 @@ export default () => Box({
const firstChild = child.get_children()[0];
firstChild.revealChild = !firstChild.revealChild;
}
},
})
],
@@ -1,5 +1,9 @@
const { GLib, Gio } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
const { execAsync, exec } = Utils;
const { Box, Label, Button, Revealer, EventBox } = Widget;
+46 -44
View File
@@ -1,12 +1,14 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
const { Gtk } = imports.gi;
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { EventBox } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
const { execAsync, exec } = Utils;
const { Box, EventBox, Label, Revealer, Overlay } = Widget;
import { AnimatedCircProg } from '../../lib/animatedcircularprogress.js'
import { MaterialIcon } from '../../lib/materialicon.js';
import { setupCursorHover, setupCursorHoverAim } from "../../lib/cursorhover.js";
const { Box, Revealer } = Widget;
import { setupCursorHover } from "../../lib/cursorhover.js";
const ANIMATION_TIME = 150;
const pinnedApps = [
@@ -41,9 +43,9 @@ const DockSeparator = (props = {}) => Box({
})
const AppButton = ({ icon, ...rest }) => Widget.Revealer({
properties: [
['workspace', 0],
],
attribute: {
'workspace': 0
},
revealChild: false,
transition: 'slide_right',
transitionDuration: ANIMATION_TIME,
@@ -80,12 +82,12 @@ const AppButton = ({ icon, ...rest }) => Widget.Revealer({
const Taskbar = () => Widget.Box({
className: 'dock-apps',
properties: [
['map', new Map()],
['clientSortFunc', (a, b) => {
return a._workspace > b._workspace;
}],
['update', (box) => {
attribute: {
'map': new Map(),
'clientSortFunc': (a, b) => {
return a.attribute.workspace > b.attribute.workspace;
},
'update': (box) => {
for (let i = 0; i < Hyprland.clients.length; i++) {
const client = Hyprland.clients[i];
if (client["pid"] == -1) return;
@@ -99,15 +101,15 @@ const Taskbar = () => Widget.Box({
tooltipText: `${client.title} (${appClass})`,
onClicked: () => focus(client),
});
newButton._workspace = client.workspace.id;
newButton.attribute.workspace = client.workspace.id;
newButton.revealChild = true;
box._map.set(client.address, newButton);
box.attribute.map.set(client.address, newButton);
}
box.children = Array.from(box._map.values());
}],
['add', (box, address) => {
box.children = Array.from(box.attribute.map.values());
},
'add': (box, address) => {
if (!address) { // First active emit is undefined
box._update(box);
box.attribute.update(box);
return;
}
const newClient = Hyprland.clients.find(client => {
@@ -120,29 +122,29 @@ const Taskbar = () => Widget.Box({
tooltipText: `${newClient.title} (${appClass})`,
onClicked: () => focus(newClient),
})
newButton._workspace = newClient.workspace.id;
box._map.set(address, newButton);
box.children = Array.from(box._map.values());
newButton.attribute.workspace = newClient.workspace.id;
box.attribute.map.set(address, newButton);
box.children = Array.from(box.attribute.map.values());
newButton.revealChild = true;
}],
['remove', (box, address) => {
},
'remove': (box, address) => {
if (!address) return;
const removedButton = box._map.get(address);
const removedButton = box.attribute.map.get(address);
if (!removedButton) return;
removedButton.revealChild = false;
Utils.timeout(ANIMATION_TIME, () => {
removedButton.destroy();
box._map.delete(address);
box.children = Array.from(box._map.values());
box.attribute.map.delete(address);
box.children = Array.from(box.attribute.map.values());
})
}],
],
},
},
setup: (self) => {
self.hook(Hyprland, (box, address) => box._add(box, address), 'client-added')
.hook(Hyprland, (box, address) => box._remove(box, address), 'client-removed')
Utils.timeout(100, () => self._update(self));
self.hook(Hyprland, (box, address) => box.attribute.add(box, address), 'client-added')
.hook(Hyprland, (box, address) => box.attribute.remove(box, address), 'client-removed')
Utils.timeout(100, () => self.attribute.update(self));
},
});
@@ -192,8 +194,8 @@ export default () => {
]
})
const dockRevealer = Revealer({
properties: [
['updateShow', self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
attribute: {
'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
const dockSize = [
dockContent.get_allocated_width(),
dockContent.get_allocated_height()
@@ -230,18 +232,18 @@ export default () => {
}
}
self.revealChild = true;
}]
],
}
},
revealChild: false,
transition: 'slide_up',
transitionDuration: 200,
child: dockContent,
// setup: (self) => self
// .hook(Hyprland, (self) => self._updateShow(self))
// .hook(Hyprland.active.workspace, (self) => self._updateShow(self))
// .hook(Hyprland.active.client, (self) => self._updateShow(self))
// .hook(Hyprland, (self) => self._updateShow(self), 'client-added')
// .hook(Hyprland, (self) => self._updateShow(self), 'client-removed')
// .hook(Hyprland, (self) => self.attribute.updateShow(self))
// .hook(Hyprland.active.workspace, (self) => self.attribute.updateShow(self))
// .hook(Hyprland.active.client, (self) => self.attribute.updateShow(self))
// .hook(Hyprland, (self) => self.attribute.updateShow(self), 'client-added')
// .hook(Hyprland, (self) => self.attribute.updateShow(self), 'client-removed')
// ,
})
return EventBox({
@@ -249,7 +251,7 @@ export default () => {
dockRevealer.revealChild = true;
},
onHoverLost: () => {
if (Hyprland.active.client._class.length === 0) return;
if (Hyprland.active.client.attribute.class.length === 0) return;
dockRevealer.revealChild = false;
},
child: Box({
+1 -1
View File
@@ -1,4 +1,4 @@
import { App, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Dock from './dock.js';
export default () => Widget.Window({
@@ -1,11 +1,6 @@
const { Gio, GLib, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { exec, execAsync } = Utils;
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
import { MaterialIcon } from '../../lib/materialicon.js';
import { showColorScheme } from '../../variables.js';
const ColorBox = ({
@@ -1,13 +1,12 @@
// This file is for brightness/volume indicators
const { GLib, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
const { Box, Label, ProgressBar, Revealer } = Widget;
import { MarginRevealer } from '../../lib/advancedwidgets.js';
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
const OsdValue = (name, labelConnections, progressConnections, props = {}) => Box({ // Volume
const OsdValue = (name, labelSetup, progressSetup, props = {}) => Box({ // Volume
...props,
vertical: true,
className: 'osd-bg osd-value',
@@ -23,8 +22,7 @@ const OsdValue = (name, labelConnections, progressConnections, props = {}) => Bo
}),
Label({
hexpand: false, className: 'osd-value-txt',
label: '100',
connections: labelConnections,
setup: labelSetup,
}),
]
}),
@@ -32,41 +30,49 @@ const OsdValue = (name, labelConnections, progressConnections, props = {}) => Bo
className: 'osd-progress',
hexpand: true,
vertical: false,
connections: progressConnections,
setup: progressSetup,
})
],
});
const brightnessIndicator = OsdValue('Brightness',
[[Brightness, self => {
self.label = `${Math.round(Brightness.screen_value * 100)}`;
}, 'notify::screen-value']],
[[Brightness, (progress) => {
const updateValue = Brightness.screen_value;
progress.value = updateValue;
}, 'notify::screen-value']],
(self) => self
.hook(Brightness, self => {
self.label = `${Math.round(Brightness.screen_value * 100)}`;
}, 'notify::screen-value')
,
(self) => self
.hook(Brightness, (progress) => {
const updateValue = Brightness.screen_value;
progress.value = updateValue;
}, 'notify::screen-value')
,
)
const volumeIndicator = OsdValue('Volume',
[[Audio, (label) => {
label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
}]],
[[Audio, (progress) => {
const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) progress.value = updateValue;
}]],
(self) => self
.hook(Audio, (label) => {
label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
})
,
(self) => self
.hook(Audio, (progress) => {
const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) progress.value = updateValue;
})
,
);
export default () => MarginRevealer({
transition: 'slide_down',
showClass: 'osd-show',
hideClass: 'osd-hide',
connections: [
[Indicator, (revealer, value) => {
if(value > -1) revealer._show();
else revealer._hide();
}, 'popup'],
],
extraSetup: (self) => self
.hook(Indicator, (revealer, value) => {
if (value > -1) revealer.attribute.show();
else revealer.attribute.hide();
}, 'popup')
,
child: Box({
hpack: 'center',
vertical: false,
+1 -1
View File
@@ -1,4 +1,4 @@
import { Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Indicator from '../../services/indicator.js';
import IndicatorValues from './indicatorvalues.js';
import MusicControls from './musiccontrols.js';
+40 -47
View File
@@ -1,5 +1,7 @@
const { Gio, GLib, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { Gio, GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { exec, execAsync } = Utils;
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
@@ -92,10 +94,10 @@ const TrackProgress = ({ player, ...rest }) => {
...rest,
className: 'osd-music-circprog',
vpack: 'center',
connections: [ // Update on change/once every 3 seconds
[Mpris, _updateProgress],
[3000, _updateProgress]
],
extraSetup: (self) => self
.hook(Mpris, _updateProgress)
.poll(3000, _updateProgress)
,
})
}
@@ -106,13 +108,13 @@ const TrackTitle = ({ player, ...rest }) => Label({
truncate: 'end',
// wrap: true,
className: 'osd-music-title',
connections: [[player, (self) => {
setup: (self) => self.hook(player, (self) => {
// Player name
self.label = player.trackTitle.length > 0 ? trimTrackTitle(player.trackTitle) : 'No media';
// Font based on track/artist
const fontForThisTrack = getTrackfont(player);
self.css = `font-family: ${fontForThisTrack}, ${DEFAULT_MUSIC_FONT};`;
}, 'notify::track-title']]
}, 'notify::track-title'),
});
const TrackArtists = ({ player, ...rest }) => Label({
@@ -120,9 +122,9 @@ const TrackArtists = ({ player, ...rest }) => Label({
xalign: 0,
className: 'osd-music-artists',
truncate: 'end',
connections: [[player, (self) => {
setup: (self) => self.hook(player, (self) => {
self.label = player.trackArtists.length > 0 ? player.trackArtists.join(', ') : '';
}, 'notify::track-artists']]
}, 'notify::track-artists'),
})
const CoverArt = ({ player, ...rest }) => Box({
@@ -140,8 +142,8 @@ const CoverArt = ({ player, ...rest }) => Box({
}),
overlays: [ // Real
Box({
properties: [
['updateCover', (self) => {
attribute: {
'updateCover': (self) => {
const player = Mpris.getPlayer();
// Player closed
@@ -177,11 +179,11 @@ const CoverArt = ({ player, ...rest }) => Box({
App.applyCss(`${stylePath}`);
})
.catch(print);
}],
],
},
},
className: 'osd-music-cover-art',
connections: [
[player, (self) => self._updateCover(self), 'notify::cover-path']
$: [
[player, (self) => self.attribute.updateCover(self), 'notify::cover-path']
],
})
]
@@ -218,13 +220,13 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({
}),
],
}),
connections: [[Mpris, (self) => {
setup: (self) => szelf.hook(Mpris, (self) => {
const player = Mpris.getPlayer();
if (!player)
self.revealChild = false;
else
self.revealChild = true;
}, 'notify::play-back-status']]
}, 'notify::play-back-status'),
});
const TrackSource = ({ player, ...rest }) => Widget.Revealer({
@@ -240,19 +242,19 @@ const TrackSource = ({ player, ...rest }) => Widget.Revealer({
hpack: 'fill',
justification: 'center',
className: 'icon-nerd',
connections: [[player, (self) => {
setup: (self) => self.hook(player, (self) => {
self.label = detectMediaSource(player.trackCoverUrl);
}, 'notify::cover-path']]
}, 'notify::cover-path'),
}),
],
}),
connections: [[Mpris, (self) => {
setup: (self) => self.hook(Mpris, (self) => {
const mpris = Mpris.getPlayer('');
if (!mpris)
self.revealChild = false;
else
self.revealChild = true;
}]]
}),
});
const TrackTime = ({ player, ...rest }) => {
@@ -266,28 +268,26 @@ const TrackTime = ({ player, ...rest }) => {
className: 'osd-music-pill spacing-h-5',
children: [
Label({
connections: [[1000, (self) => {
setup: (self) => self.poll(1000, (self) => {
const player = Mpris.getPlayer();
if (!player) return;
self.label = lengthStr(player.position);
}]]
}),
}),
Label({ label: '/' }),
Label({
connections: [[Mpris, (self) => {
setup: (self) => self.hook(Mpris, (self) => {
const player = Mpris.getPlayer();
if (!player) return;
self.label = lengthStr(player.length);
}]]
}),
}),
],
}),
connections: [[Mpris, (self) => {
if (!player)
self.revealChild = false;
else
self.revealChild = true;
}]]
setup: (self) => self.hook(Mpris, (self) => {
if (!player) self.revealChild = false;
else self.revealChild = true;
}),
})
}
@@ -306,15 +306,12 @@ const PlayState = ({ player }) => {
justification: 'center',
hpack: 'fill',
vpack: 'center',
connections: [[player, (label) => {
setup: (self) => self.hook(player, (label) => {
label.label = `${player.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}, 'notify::play-back-status']],
}, 'notify::play-back-status'),
}),
}),
],
// setup: self => Utils.timeout(1, () => {
// self.set_overlay_pass_through(self.get_children()[1], true);
// }),
passThrough: true,
})
});
@@ -358,19 +355,17 @@ export default () => MarginRevealer({
showClass: 'osd-show',
hideClass: 'osd-hide',
child: Box({
connections: [[Mpris, box => {
setup: (self) => self.hook(Mpris, box => {
let foundPlayer = false;
Mpris.players.forEach((player, i) => {
if (isRealPlayer(player)) {
foundPlayer = true;
box._player = player;
box.children = [MusicControlsWidget(player)];
}
});
if (!foundPlayer) {
box._player = null;
const children = box.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
@@ -378,12 +373,10 @@ export default () => MarginRevealer({
}
return;
}
}, 'notify::players']],
}, 'notify::players'),
}),
setup: (self) => self.hook(showMusicControls, (revealer) => {
if (showMusicControls.value) revealer.attribute.show();
else revealer.attribute.hide();
}),
connections: [
[showMusicControls, (revealer) => {
if (showMusicControls.value) revealer._show();
else revealer._hide();
}],
],
})
@@ -1,9 +1,7 @@
// This file is for popup notifications
const { GLib, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
const { Box } = Widget;
import Notification from '../../lib/notification.js';
const PopupNotification = (notifObject) => Widget.Box({
@@ -39,41 +37,39 @@ const naiveNotifPopupList = Widget.Box({
const notifPopupList = Box({
vertical: true,
className: 'osd-notifs spacing-v-5-revealer',
properties: [
['map', new Map()],
['dismiss', (box, id, force = false) => {
if (!id || !box._map.has(id) || box._map.get(id)._hovered && !force)
attribute: {
'map': new Map(),
'dismiss': (box, id, force = false) => {
if (!id || !box.attribute.map.has(id) || box.attribute.map.get(id).attribute.hovered && !force)
return;
const notif = box._map.get(id);
const notif = box.attribute.map.get(id);
notif.revealChild = false;
notif._destroyWithAnims();
}],
['notify', (box, id) => {
// console.log('new notiffy', id, Notifications.getNotification(id))
notif.attribute.destroyWithAnims();
box.attribute.map.delete(id);
},
'notify': (box, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
box._map.delete(id);
box.attribute.map.delete(id);
const notif = Notifications.getNotification(id);
const newNotif = Notification({
notifObject: notif,
isPopup: true,
});
box._map.set(id, newNotif);
box.pack_end(box._map.get(id), false, false, 0);
box.attribute.map.set(id, newNotif);
box.pack_end(box.attribute.map.get(id), false, false, 0);
box.show_all();
// box.children = Array.from(box._map.values()).reverse();
}],
],
// box.children = Array.from(box.attribute.map.values()).reverse();
},
},
setup: (self) => self
.hook(Notifications, (box, id) => box._notify(box, id), 'notified')
.hook(Notifications, (box, id) => box._dismiss(box, id), 'dismissed')
.hook(Notifications, (box, id) => box._dismiss(box, id, true), 'closed')
.hook(Notifications, (box, id) => box.attribute.notify(box, id), 'notified')
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id), 'dismissed')
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
,
});
@@ -1,5 +1,9 @@
const { GLib, Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { Box, EventBox, Button, Revealer } = Widget;
const { execAsync, exec } = Utils;
+1 -3
View File
@@ -1,6 +1,4 @@
const { Gio, GLib } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
function moveClientToWorkspace(address, workspace) {
+2 -2
View File
@@ -1,5 +1,5 @@
import { Widget } from '../../imports.js';
import { SearchAndWindows } from "./overview.js";
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { SearchAndWindows } from "./windowcontent.js";
export default () => Widget.Window({
name: 'overview',
@@ -1,5 +1,6 @@
const { Gio, GLib } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import Todo from "../../services/todo.js";
-552
View File
@@ -1,552 +0,0 @@
const { Gdk, Gio, Gtk } = imports.gi;
import { App, Service, Utils, Variable, Widget, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverGrab } from "../../lib/cursorhover.js";
import { DoubleRevealer } from "../../lib/advancedwidgets.js";
import { execAndClose, expandTilde, hasUnterminatedBackslash, startsWithNumber, launchCustomCommand, ls } from './miscfunctions.js';
import {
CalculationResultButton, CustomCommandButton, DirectoryButton,
DesktopEntryButton, ExecuteCommandButton, SearchButton
} from './searchbuttons.js';
import { dumpToWorkspace, swapWorkspace } from "./actions.js";
// Add math funcs
const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
const pi = Math.PI;
// trigonometric funcs for deg
const sind = x => sin(x * pi / 180);
const cosd = x => cos(x * pi / 180);
const tand = x => tan(x * pi / 180);
const cotd = x => cot(x * pi / 180);
const asind = x => asin(x) * 180 / pi;
const acosd = x => acos(x) * 180 / pi;
const atand = x => atan(x) * 180 / pi;
const acotd = x => acot(x) * 180 / pi;
const MAX_RESULTS = 10;
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
const OVERVIEW_WS_NUM_SCALE = 0.09;
const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
const searchPromptTexts = [
'Try "~/.config"',
'Try "Files"',
'Try "6*cos(pi)"',
'Try "sudo pacman -Syu"',
'Try "How to basic"',
'Drag n\' drop to move windows',
'Type to search',
]
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);
}
function iconExists(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
return iconTheme.has_icon(iconName);
}
function substitute(str) {
const subs = [
{ from: 'code-url-handler', to: 'visual-studio-code' },
{ from: 'Code', to: 'visual-studio-code' },
{ from: 'GitHub Desktop', to: 'github-desktop' },
{ from: 'wpsoffice', to: 'wps-office2019-kprometheus' },
{ from: 'gnome-tweaks', to: 'org.gnome.tweaks' },
{ from: 'Minecraft* 1.20.1', to: 'minecraft' },
{ from: '', to: 'image-missing' },
];
for (const { from, to } of subs) {
if (from === str)
return to;
}
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
return str;
}
const ContextWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
label: `${label}`,
setup: (menuItem) => {
let submenu = new Gtk.Menu();
submenu.className = 'menu';
for (let i = 1; i <= 10; i++) {
let button = new Gtk.MenuItem({
label: `Workspace ${i}`
});
button.connect("activate", () => {
// execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
actionFunc(thisWorkspace, i);
});
submenu.append(button);
}
menuItem.set_reserve_indicator(true);
menuItem.set_submenu(submenu);
}
})
const client = ({ address, size: [w, h], workspace: { id, name }, class: c, title, xwayland }) => {
const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70);
if (w <= 0 || h <= 0) return null;
title = truncateTitle(title);
return Widget.Button({
className: 'overview-tasks-window',
hpack: 'center',
vpack: 'center',
onClicked: () => {
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
App.closeWindow('overview');
},
onMiddleClickRelease: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
onSecondaryClick: (button) => {
button.toggleClassName('overview-tasks-window-selected', true);
const menu = Widget.Menu({
className: 'menu',
children: [
Widget.MenuItem({
child: Widget.Label({
xalign: 0,
label: "Close (Middle-click)",
}),
onActivate: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
}),
ContextWorkspaceArray({
label: "Dump windows to workspace",
actionFunc: dumpToWorkspace,
thisWorkspace: Number(id)
}),
ContextWorkspaceArray({
label: "Swap windows with workspace",
actionFunc: swapWorkspace,
thisWorkspace: Number(id)
}),
],
});
menu.connect("deactivate", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.connect("selection-done", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.popup_at_pointer(null); // Show the menu at the pointer's position
},
child: Widget.Box({
css: `
min-width: ${Math.max(w * OVERVIEW_SCALE - 4, 1)}px;
min-height: ${Math.max(h * OVERVIEW_SCALE - 4, 1)}px;
`,
homogeneous: true,
child: Widget.Box({
vertical: true,
vpack: 'center',
className: 'spacing-v-5',
children: [
Widget.Icon({
icon: substitute(c),
size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
}),
// TODO: Add xwayland tag instead of just having italics
DoubleRevealer({
transition1: 'slide_right',
transition2: 'slide_down',
revealChild: revealInfoCondition,
child: Widget.Scrollable({
hexpand: true,
vscroll: 'never',
hscroll: 'automatic',
child: Widget.Label({
truncate: 'end',
className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
css: `
font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px;
margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 10}px;
`,
// If the title is too short, include the class
label: (title.length <= 1 ? `${c}: ${title}` : title),
})
})
})
]
})
}),
tooltipText: `${c}: ${title}`,
setup: (button) => {
setupCursorHoverGrab(button);
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
button.drag_source_set_icon_name(substitute(c));
// button.drag_source_set_icon_gicon(icon);
button.connect('drag-begin', (button) => { // On drag start, add the dragging class
button.toggleClassName('overview-tasks-window-dragging', true);
});
button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
data.set_text(address, address.length);
button.toggleClassName('overview-tasks-window-dragging', false);
});
},
});
}
const workspace = index => {
const fixed = Gtk.Fixed.new();
const WorkspaceNumber = (index) => Widget.Label({
className: 'overview-tasks-workspace-number',
label: `${index}`,
css: `
margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px;
font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px;
`,
})
const widget = Widget.Box({
className: 'overview-tasks-workspace',
vpack: 'center',
css: `
min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
`,
children: [Widget.EventBox({
hexpand: true,
vexpand: true,
onPrimaryClick: () => {
Hyprland.sendMessage(`dispatch workspace ${index}`)
App.closeWindow('overview');
},
setup: (eventbox) => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
});
},
child: fixed,
})],
});
widget.update = (clients) => {
clients = clients.filter(({ workspace: { id } }) => id === index);
// this is for my monitor layout
// shifts clients back by SCREEN_WIDTHpx if necessary
clients = clients.map(client => {
const [x, y] = client.at;
if (x > SCREEN_WIDTH)
client.at = [x - SCREEN_WIDTH, y];
return client;
});
const children = fixed.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
fixed.put(WorkspaceNumber(index), 0, 0);
for (let i = 0; i < clients.length; i++) {
const c = clients[i];
if (c.mapped) {
fixed.put(client(c), c.at[0] * OVERVIEW_SCALE, c.at[1] * OVERVIEW_SCALE);
}
}
fixed.show_all();
};
return widget;
};
const arr = (s, n) => {
const array = [];
for (let i = 0; i < n; i++)
array.push(s + i);
return array;
};
const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
children: arr(startWorkspace, workspaces).map(workspace),
properties: [['update', box => {
if (!App.getWindow(windowName).visible) return;
execAsync('hyprctl -j clients').then(clients => {
const json = JSON.parse(clients);
const children = box.get_children();
for (let i = 0; i < children.length; i++) {
const ch = children[i];
ch.update(json)
}
}).catch(print);
}]],
setup: (box) => {
box
// .hook(Hyprland, (box, name, data) => { // idk, does this make it lag occasionally?
// if (["changefloatingmode", "movewindow"].includes(name))
// box._update(box);
// }, 'event')
.hook(Hyprland, (box) => box._update(box), 'client-added')
.hook(Hyprland, (box) => box._update(box), 'client-removed')
.hook(App, (box, name, visible) => { // Update on open
if (name == 'overview' && visible) box._update(box);
})
},
});
export const SearchAndWindows = () => {
var _appSearchResults = [];
const ClickToClose = ({ ...props }) => Widget.EventBox({
...props,
onPrimaryClick: () => App.closeWindow('overview'),
onSecondaryClick: () => App.closeWindow('overview'),
onMiddleClick: () => App.closeWindow('overview'),
});
const resultsBox = Widget.Box({
className: 'overview-search-results',
vertical: true,
vexpand: true,
});
const resultsRevealer = Widget.Revealer({
transitionDuration: 200,
revealChild: false,
transition: 'slide_down',
// duration: 200,
hpack: 'center',
child: resultsBox,
});
const overviewRevealer = Widget.Revealer({
revealChild: true,
transition: 'slide_down',
transitionDuration: 200,
child: Widget.Box({
vertical: true,
className: 'overview-tasks',
children: [
OverviewRow({ startWorkspace: 1, workspaces: 5 }),
OverviewRow({ startWorkspace: 6, workspaces: 5 }),
]
}),
});
const entryPromptRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: true,
hpack: 'center',
child: Widget.Label({
className: 'overview-search-prompt txt-small txt',
label: searchPromptTexts[Math.floor(Math.random() * searchPromptTexts.length)],
})
});
const entryIconRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: false,
hpack: 'end',
child: Widget.Label({
className: 'txt txt-large icon-material overview-search-icon',
label: 'search',
}),
});
const entryIcon = Widget.Box({
className: 'overview-search-prompt-box',
setup: box => box.pack_start(entryIconRevealer, true, true, 0),
});
const entry = Widget.Entry({
className: 'overview-search-box txt-small txt',
hpack: 'center',
onAccept: (self) => { // This is when you hit Enter
const text = self.text;
if (text.length == 0) return;
const isAction = text.startsWith('>');
const isDir = (['/', '~'].includes(entry.text[0]));
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a workaround
try {
const fullResult = eval(text);
// copy
execAsync(['wl-copy', `${fullResult}`]).catch(print);
App.closeWindow('overview');
return;
} catch (e) {
// console.log(e);
}
}
if (isDir) {
App.closeWindow('overview');
execAsync(['bash', '-c', `xdg-open "${expandTilde(text)}"`, `&`]).catch(print);
return;
}
if (_appSearchResults.length > 0) {
App.closeWindow('overview');
_appSearchResults[0].launch();
return;
}
else if (text[0] == '>') { // Custom commands
App.closeWindow('overview');
launchCustomCommand(text);
return;
}
// Fallback: Execute command
if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
if (text.startsWith('sudo'))
execAndClose(text, true);
else
execAndClose(text, false);
}
else {
App.closeWindow('overview');
execAsync(['bash', '-c', `xdg-open 'https://www.google.com/search?q=${text} -site:quora.com' &`]).catch(print); // quora is useless
}
},
onChange: (entry) => {
const isAction = entry.text[0] == '>';
const isDir = (['/', '~'].includes(entry.text[0]));
const children = resultsBox.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
// check empty if so then dont do stuff
if (entry.text == '') {
resultsRevealer.set_reveal_child(false);
overviewRevealer.set_reveal_child(true);
entryPromptRevealer.set_reveal_child(true);
entryIconRevealer.set_reveal_child(false);
entry.toggleClassName('overview-search-box-extended', false);
return;
}
const text = entry.text;
resultsRevealer.set_reveal_child(true);
overviewRevealer.set_reveal_child(false);
entryPromptRevealer.set_reveal_child(false);
entryIconRevealer.set_reveal_child(true);
entry.toggleClassName('overview-search-box-extended', true);
_appSearchResults = Applications.query(text);
// Calculate
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a small workaround.
try {
const fullResult = eval(text);
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
} catch (e) {
// console.log(e);
}
}
if (isDir) {
var contents = [];
contents = ls({ path: text, silent: true });
contents.forEach((item) => {
resultsBox.add(DirectoryButton(item));
})
}
if (isAction) { // Eval on typing is dangerous, this is a workaround.
resultsBox.add(CustomCommandButton({ text: entry.text }));
}
// Add application entries
let appsToAdd = MAX_RESULTS;
_appSearchResults.forEach(app => {
if (appsToAdd == 0) return;
resultsBox.add(DesktopEntryButton(app));
appsToAdd--;
});
// Fallbacks
// if the first word is an actual command
if (!isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
}
// Add fallback: search
resultsBox.add(SearchButton({ text: entry.text }));
resultsBox.show_all();
},
});
return Widget.Box({
vertical: true,
children: [
ClickToClose({ // Top margin. Also works as a click-outside-to-close thing
child: Widget.Box({
className: 'bar-height',
})
}),
Widget.Box({
hpack: 'center',
children: [
entry,
Widget.Box({
className: 'overview-search-icon-box',
setup: box => box.pack_start(entryPromptRevealer, true, true, 0),
}),
entryIcon,
]
}),
overviewRevealer,
resultsRevealer,
],
setup: (self) => self
.hook(App, (_b, name, visible) => {
if (name == 'overview' && !visible) {
entryPromptRevealer.child.label = searchPromptTexts[Math.floor(Math.random() * searchPromptTexts.length)];
resultsBox.children = [];
entry.set_text('');
}
})
.on('key-press-event', (widget, event) => { // Typing
if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && widget != entry) {
Utils.timeout(1, () => entry.grab_focus());
entry.set_text(entry.text + String.fromCharCode(event.get_keyval()[1]));
entry.set_position(-1);
}
})
,
// connections: [
// [App, (_b, name, visible) => {
// if (name == 'overview' && !visible) {
// entryPromptRevealer.child.label = searchPromptTexts[Math.floor(Math.random() * searchPromptTexts.length)];
// resultsBox.children = [];
// entry.set_text('');
// }
// }],
// ['key-press-event', (widget, event) => { // Typing
// if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && widget != entry) {
// Utils.timeout(1, () => entry.grab_focus());
// entry.set_text(entry.text + String.fromCharCode(event.get_keyval()[1]));
// entry.set_position(-1);
// }
// }],
// ],
});
};
@@ -0,0 +1,308 @@
const { Gdk, Gtk } = imports.gi;
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { setupCursorHoverGrab } from "../../lib/cursorhover.js";
import { dumpToWorkspace, swapWorkspace } from "./actions.js";
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
const OVERVIEW_WS_NUM_SCALE = 0.09;
const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
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);
}
function iconExists(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
return iconTheme.has_icon(iconName);
}
function substitute(str) {
const subs = [
{ from: 'code-url-handler', to: 'visual-studio-code' },
{ from: 'Code', to: 'visual-studio-code' },
{ from: 'GitHub Desktop', to: 'github-desktop' },
{ from: 'wpsoffice', to: 'wps-office2019-kprometheus' },
{ from: 'gnome-tweaks', to: 'org.gnome.tweaks' },
{ from: 'Minecraft* 1.20.1', to: 'minecraft' },
{ from: '', to: 'image-missing' },
];
for (const { from, to } of subs) {
if (from === str)
return to;
}
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
return str;
}
const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
label: `${label}`,
setup: (menuItem) => {
let submenu = new Gtk.Menu();
submenu.className = 'menu';
for (let i = 1; i <= 10; i++) {
let button = new Gtk.MenuItem({
label: `Workspace ${i}`
});
button.connect("activate", () => {
// execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
actionFunc(thisWorkspace, i);
});
submenu.append(button);
}
menuItem.set_reserve_indicator(true);
menuItem.set_submenu(submenu);
}
})
const client = ({ address, size: [w, h], workspace: { id, name }, class: c, title, xwayland }) => {
const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70);
if (w <= 0 || h <= 0) return null;
// title = truncateTitle(title);
return Widget.Button({
className: 'overview-tasks-window',
hpack: 'center',
vpack: 'center',
onClicked: () => {
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
App.closeWindow('overview');
},
onMiddleClickRelease: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
onSecondaryClick: (button) => {
button.toggleClassName('overview-tasks-window-selected', true);
const menu = Widget.Menu({
className: 'menu',
children: [
Widget.MenuItem({
child: Widget.Label({
xalign: 0,
label: "Close (Middle-click)",
}),
onActivate: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
}),
ContextMenuWorkspaceArray({
label: "Dump windows to workspace",
actionFunc: dumpToWorkspace,
thisWorkspace: Number(id)
}),
ContextMenuWorkspaceArray({
label: "Swap windows with workspace",
actionFunc: swapWorkspace,
thisWorkspace: Number(id)
}),
],
});
menu.connect("deactivate", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.connect("selection-done", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.popup_at_pointer(null); // Show the menu at the pointer's position
},
child: Widget.Box({
css: `
min-width: ${Math.max(w * OVERVIEW_SCALE - 4, 1)}px;
min-height: ${Math.max(h * OVERVIEW_SCALE - 4, 1)}px;
`,
homogeneous: true,
child: Widget.Box({
vertical: true,
vpack: 'center',
className: 'spacing-v-5',
children: [
Widget.Icon({
icon: substitute(c),
size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
}),
// TODO: Add xwayland tag instead of just having italics
Widget.Revealer({
transition: 'slide_down',
revealChild: revealInfoCondition,
child: Widget.Label({
truncate: 'end',
className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
css: `
font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px;
margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 10}px;
`,
// If the title is too short, include the class
label: (title.length <= 1 ? `${c}: ${title}` : title),
})
})
]
})
}),
tooltipText: `${c}: ${title}`,
setup: (button) => {
setupCursorHoverGrab(button);
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
button.drag_source_set_icon_name(substitute(c));
// button.drag_source_set_icon_gicon(icon);
button.connect('drag-begin', (button) => { // On drag start, add the dragging class
button.toggleClassName('overview-tasks-window-dragging', true);
});
button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
data.set_text(address, address.length);
button.toggleClassName('overview-tasks-window-dragging', false);
});
},
});
}
const workspace = index => {
const fixed = Gtk.Fixed.new();
const WorkspaceNumber = (index) => Widget.Label({
className: 'overview-tasks-workspace-number',
label: `${index}`,
css: `
margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px;
font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px;
`,
})
const widget = Widget.Box({
className: 'overview-tasks-workspace',
vpack: 'center',
css: `
min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
`,
children: [Widget.EventBox({
hexpand: true,
vexpand: true,
onPrimaryClick: () => {
Hyprland.sendMessage(`dispatch workspace ${index}`)
App.closeWindow('overview');
},
setup: (eventbox) => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
});
},
child: fixed,
})],
});
widget.update = (clients) => {
clients = clients.filter(({ workspace: { id } }) => id === index);
// this is for my monitor layout
// shifts clients back by SCREEN_WIDTHpx if necessary
clients = clients.map(client => {
const [x, y] = client.at;
if (x > SCREEN_WIDTH)
client.at = [x - SCREEN_WIDTH, y];
return client;
});
const children = fixed.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
fixed.put(WorkspaceNumber(index), 0, 0);
for (let i = 0; i < clients.length; i++) {
const c = clients[i];
if (c.mapped) {
fixed.put(client(c), c.at[0] * OVERVIEW_SCALE, c.at[1] * OVERVIEW_SCALE);
}
}
fixed.show_all();
};
return widget;
};
const arr = (s, n) => {
const array = [];
for (let i = 0; i < n; i++)
array.push(s + i);
return array;
};
const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
children: arr(startWorkspace, workspaces).map(workspace),
attribute: {
'update': box => {
if (!App.getWindow(windowName).visible) return;
execAsync('hyprctl -j clients').then(clients => {
const json = JSON.parse(clients);
const children = box.get_children();
for (let i = 0; i < children.length; i++) {
const ch = children[i];
ch.update(json)
}
}).catch(print);
}
},
setup: (box) => {
box
// .hook(Hyprland, (box, name, data) => { // idk, does this make it lag occasionally?
// if (["changefloatingmode", "movewindow"].includes(name))
// box.attribute.update(box);
// }, 'event')
.hook(Hyprland, (box) => box.attribute.update(box), 'client-added')
.hook(Hyprland, (box) => box.attribute.update(box), 'client-removed')
.hook(App, (box, name, visible) => { // Update on open
if (name == 'overview' && visible) box.attribute.update(box);
})
},
});
export default () => {
const overviewRevealer = Widget.Revealer({
revealChild: true,
transition: 'slide_down',
transitionDuration: 200,
child: Widget.Box({
vertical: true,
className: 'overview-tasks',
children: [
OverviewRow({ startWorkspace: 1, workspaces: 5 }),
OverviewRow({ startWorkspace: 6, workspaces: 5 }),
]
}),
});
return overviewRevealer;
};
+16 -14
View File
@@ -1,5 +1,7 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import { searchItem } from './searchitem.js';
import { execAndClose, startsWithNumber, launchCustomCommand } from './miscfunctions.js';
@@ -54,16 +56,16 @@ export const DirectoryButton = ({ parentPath, name, type, icon }) => {
})
]
}),
connections: [
['focus-in-event', (button) => {
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
}],
['focus-out-event', (button) => {
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
}],
]
})
,
})
}
@@ -128,16 +130,16 @@ export const DesktopEntryButton = (app) => {
})
]
}),
connections: [
['focus-in-event', (button) => {
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
}],
['focus-out-event', (button) => {
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
}],
]
})
,
})
}
+7 -11
View File
@@ -1,8 +1,4 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverAim } from "../../lib/cursorhover.js";
import { MaterialIcon } from '../../lib/materialicon.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
export const searchItem = ({ materialIconName, name, actionName, content, onActivate }) => {
const actionText = Widget.Revealer({
@@ -55,15 +51,15 @@ export const searchItem = ({ materialIconName, name, actionName, content, onActi
})
]
}),
connections: [
['focus-in-event', (button) => {
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
}],
['focus-out-event', (button) => {
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
}],
]
})
,
});
}
@@ -0,0 +1,244 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
const { execAsync, exec } = Utils;
import { execAndClose, expandTilde, hasUnterminatedBackslash, startsWithNumber, launchCustomCommand, ls } from './miscfunctions.js';
import {
CalculationResultButton, CustomCommandButton, DirectoryButton,
DesktopEntryButton, ExecuteCommandButton, SearchButton
} from './searchbuttons.js';
// Add math funcs
const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
const pi = Math.PI;
// trigonometric funcs for deg
const sind = x => sin(x * pi / 180);
const cosd = x => cos(x * pi / 180);
const tand = x => tan(x * pi / 180);
const cotd = x => cot(x * pi / 180);
const asind = x => asin(x) * 180 / pi;
const acosd = x => acos(x) * 180 / pi;
const atand = x => atan(x) * 180 / pi;
const acotd = x => acot(x) * 180 / pi;
const MAX_RESULTS = 10;
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
const OVERVIEW_WS_NUM_SCALE = 0.09;
const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
function iconExists(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
return iconTheme.has_icon(iconName);
}
const OptionalOverview = async () => {
try {
return (await import('./overview_hyprland.js')).default();
} catch {
return null;
// return (await import('./overview_hyprland.js')).default();
}
};
const overviewContent = await OptionalOverview();
export const SearchAndWindows = () => {
var _appSearchResults = [];
const ClickToClose = ({ ...props }) => Widget.EventBox({
...props,
onPrimaryClick: () => App.closeWindow('overview'),
onSecondaryClick: () => App.closeWindow('overview'),
onMiddleClick: () => App.closeWindow('overview'),
});
const resultsBox = Widget.Box({
className: 'overview-search-results',
vertical: true,
vexpand: true,
});
const resultsRevealer = Widget.Revealer({
transitionDuration: 200,
revealChild: false,
transition: 'slide_down',
// duration: 200,
hpack: 'center',
child: resultsBox,
});
const entryPromptRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: true,
hpack: 'center',
child: Widget.Label({
className: 'overview-search-prompt txt-small txt',
label: 'Type to search'
})
});
const entryIconRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: false,
hpack: 'end',
child: Widget.Label({
className: 'txt txt-large icon-material overview-search-icon',
label: 'search',
}),
});
const entryIcon = Widget.Box({
className: 'overview-search-prompt-box',
setup: box => box.pack_start(entryIconRevealer, true, true, 0),
});
const entry = Widget.Entry({
className: 'overview-search-box txt-small txt',
hpack: 'center',
onAccept: (self) => { // This is when you hit Enter
const text = self.text;
if (text.length == 0) return;
const isAction = text.startsWith('>');
const isDir = (['/', '~'].includes(entry.text[0]));
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a workaround
try {
const fullResult = eval(text);
// copy
execAsync(['wl-copy', `${fullResult}`]).catch(print);
App.closeWindow('overview');
return;
} catch (e) {
// console.log(e);
}
}
if (isDir) {
App.closeWindow('overview');
execAsync(['bash', '-c', `xdg-open "${expandTilde(text)}"`, `&`]).catch(print);
return;
}
if (_appSearchResults.length > 0) {
App.closeWindow('overview');
_appSearchResults[0].launch();
return;
}
else if (text[0] == '>') { // Custom commands
App.closeWindow('overview');
launchCustomCommand(text);
return;
}
// Fallback: Execute command
if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
if (text.startsWith('sudo'))
execAndClose(text, true);
else
execAndClose(text, false);
}
else {
App.closeWindow('overview');
execAsync(['bash', '-c', `xdg-open 'https://www.google.com/search?q=${text} -site:quora.com' &`]).catch(print); // quora is useless
}
},
onChange: (entry) => { // this is when you type
const isAction = entry.text[0] == '>';
const isDir = (['/', '~'].includes(entry.text[0]));
resultsBox.get_children().forEach(ch => ch.destroy());
// check empty if so then dont do stuff
if (entry.text == '') {
resultsRevealer.revealChild = false;
overviewContent.revealChild = true;
entryPromptRevealer.revealChild = true;
entryIconRevealer.revealChild = false;
entry.toggleClassName('overview-search-box-extended', false);
return;
}
const text = entry.text;
resultsRevealer.revealChild = true;
overviewContent.revealChild = false;
entryPromptRevealer.revealChild = false;
entryIconRevealer.revealChild = true;
entry.toggleClassName('overview-search-box-extended', true);
_appSearchResults = Applications.query(text);
// Calculate
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a small workaround.
try {
const fullResult = eval(text);
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
} catch (e) {
// console.log(e);
}
}
if (isDir) {
var contents = [];
contents = ls({ path: text, silent: true });
contents.forEach((item) => {
resultsBox.add(DirectoryButton(item));
})
}
if (isAction) { // Eval on typing is dangerous, this is a workaround.
resultsBox.add(CustomCommandButton({ text: entry.text }));
}
// Add application entries
let appsToAdd = MAX_RESULTS;
_appSearchResults.forEach(app => {
if (appsToAdd == 0) return;
resultsBox.add(DesktopEntryButton(app));
appsToAdd--;
});
// Fallbacks
// if the first word is an actual command
if (!isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
}
// Add fallback: search
resultsBox.add(SearchButton({ text: entry.text }));
resultsBox.show_all();
},
});
return Widget.Box({
vertical: true,
children: [
ClickToClose({ // Top margin. Also works as a click-outside-to-close thing
child: Widget.Box({
className: 'bar-height',
})
}),
Widget.Box({
hpack: 'center',
children: [
entry,
Widget.Box({
className: 'overview-search-icon-box',
setup: box => box.pack_start(entryPromptRevealer, true, true, 0),
}),
entryIcon,
]
}),
overviewContent,
resultsRevealer,
],
setup: (self) => self
.hook(App, (_b, name, visible) => {
if (name == 'overview' && !visible) {
resultsBox.children = [];
entry.set_text('');
}
})
.on('key-press-event', (widget, event) => { // Typing
if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && widget != entry) {
Utils.timeout(1, () => entry.grab_focus());
entry.set_text(entry.text + String.fromCharCode(event.get_keyval()[1]));
entry.set_position(-1);
}
})
,
});
};
+1 -1
View File
@@ -1,5 +1,5 @@
import Cairo from 'gi://cairo?version=1.0';
import { Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { RoundedCorner } from "../../lib/roundedcorner.js";
const dummyRegion = new Cairo.Region();
+1 -2
View File
@@ -1,5 +1,4 @@
const { Gdk, Gtk } = imports.gi;
import { Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import SessionScreen from "./sessionscreen.js";
export default () => Widget.Window({ // On-screen keyboard
+15 -11
View File
@@ -1,7 +1,11 @@
// This is for the cool memory indicator on the sidebar
// For the right pill of the bar, see system.js
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { exec, execAsync } = Utils;
const SessionButton = (name, icon, command, props = {}) => {
@@ -41,16 +45,16 @@ const SessionButton = (name, icon, command, props = {}) => {
button.get_window().set_cursor(cursor);
buttonDescription.revealChild = false;
},
connections: [
['focus-in-event', (self) => {
setup: (self) => self
.on('focus-in-event', (self) => {
buttonDescription.revealChild = true;
self.toggleClassName('session-button-focused', true);
}],
['focus-out-event', (self) => {
})
.on('focus-out-event', (self) => {
buttonDescription.revealChild = false;
self.toggleClassName('session-button-focused', false);
}],
],
})
,
...props,
});
}
@@ -134,10 +138,10 @@ export default () => {
]
})
],
connections: [
[App, (_b, name, visible) => {
setup: (self) => self
.hook(App, (_b, name, visible) => {
if (visible) lockButton.grab_focus(); // Lock is the default option
}],
],
})
,
});
}
+23 -18
View File
@@ -1,5 +1,8 @@
const { Gdk, GLib, Gtk, Pango } = imports.gi;
import { App, Utils, Widget } from '../../../imports.js';
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import ChatGPT from '../../../services/chatgpt.js';
@@ -64,14 +67,14 @@ const chatGPTInfo = Box({
export const chatGPTSettings = MarginRevealer({
transition: 'slide_down',
revealChild: true,
connections: [
[ChatGPT, (self) => Utils.timeout(200, () => {
self._hide();
}), 'newMsg'],
[ChatGPT, (self) => Utils.timeout(200, () => {
self._show();
}), 'clear'],
],
extraSetup: (self) => self
.hook(ChatGPT, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
.hook(ChatGPT, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
child: Box({
vertical: true,
className: 'sidebar-chat-settings',
@@ -126,9 +129,11 @@ export const openaiApiKeyInstructions = Box({
children: [Revealer({
transition: 'slide_down',
transitionDuration: 150,
connections: [[ChatGPT, (self, hasKey) => {
self.revealChild = (ChatGPT.key.length == 0);
}, 'hasKey']],
setup: (self) => self
.hook(ChatGPT, (self, hasKey) => {
self.revealChild = (ChatGPT.key.length == 0);
}, 'hasKey')
,
child: Button({
child: Label({
useMarkup: true,
@@ -163,13 +168,13 @@ export const chatGPTWelcome = Box({
export const chatContent = Box({
className: 'spacing-v-15',
vertical: true,
connections: [
[ChatGPT, (box, id) => {
setup: (self) => self
.hook(ChatGPT, (box, id) => {
const message = ChatGPT.messages[id];
if (!message) return;
box.add(ChatMessage(message, chatGPTView))
}, 'newMsg'],
]
}, 'newMsg')
,
});
const clearChat = () => {
@@ -197,7 +202,7 @@ export const chatGPTView = Scrollable({
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
@@ -1,6 +1,8 @@
const { Gdk, Gio, GLib, Gtk, Pango } = imports.gi;
import { App, Utils, Widget } from '../../../imports.js';
const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
const { Gdk, Gio, GLib, Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Scrollable } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from "../../../lib/materialicon.js";
import md2pango from "../../../lib/md2pango.js";
@@ -112,11 +114,11 @@ const CodeBlock = (content = '', lang = 'txt') => {
const sourceView = HighlightedCode(content, lang);
const codeBlock = Box({
properties: [
['updateText', (text) => {
attribute: {
'updateText': (text) => {
sourceView.get_buffer().set_text(text, -1);
}]
],
}
},
className: 'sidebar-chat-codeblock',
vertical: true,
children: [
@@ -149,8 +151,8 @@ const Divider = () => Box({
const MessageContent = (content) => {
const contentBox = Box({
vertical: true,
properties: [
['fullUpdate', (self, content, useCursor = false) => {
attribute: {
'fullUpdate': (self, content, useCursor = false) => {
// Clear and add first text widget
const children = contentBox.get_children();
for (let i = 0; i < children.length; i++) {
@@ -175,7 +177,7 @@ const MessageContent = (content) => {
contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
}
else {
lastLabel._updateText(blockContent);
lastLabel.attribute.updateText(blockContent);
contentBox.add(TextBlock());
}
@@ -201,7 +203,7 @@ const MessageContent = (content) => {
if (!inCode)
lastLabel.label = `${md2pango(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`;
else
lastLabel._updateText(blockContent);
lastLabel.attribute.updateText(blockContent);
}
// Debug: plain text
// contentBox.add(Label({
@@ -214,10 +216,10 @@ const MessageContent = (content) => {
// label: '------------------------------\n' + md2pango(content),
// }))
contentBox.show_all();
}]
]
}
}
});
contentBox._fullUpdate(contentBox, content, false);
contentBox.attribute.fullUpdate(contentBox, content, false);
return contentBox;
}
@@ -243,17 +245,17 @@ export const ChatMessage = (message, scrolledWindow) => {
}),
messageContentBox,
],
connections: [
[message, (self, isThinking) => {
setup: (self) => self
.hook(message, (self, isThinking) => {
messageContentBox.toggleClassName('thinking', message.thinking);
}, 'notify::thinking'],
[message, (self) => { // Message update
messageContentBox._fullUpdate(messageContentBox, message.content, message.role != 'user');
}, 'notify::content'],
[message, (label, isDone) => { // Remove the cursor
messageContentBox._fullUpdate(messageContentBox, message.content, false);
}, 'notify::done'],
]
}, 'notify::thinking')
.hook(message, (self) => { // Message update
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
}, 'notify::content')
.hook(message, (label, isDone) => { // Remove the cursor
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, false);
}, 'notify::done')
,
})
]
});
+49 -42
View File
@@ -1,17 +1,28 @@
const { Gdk, GdkPixbuf, Gio, GLib, Gtk, Pango } = imports.gi;
import { App, Utils, Widget } from '../../../imports.js';
const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from "../../../lib/materialicon.js";
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
import { setupCursorHover } from "../../../lib/cursorhover.js";
import WaifuService from '../../../services/waifus.js';
async function getImageViewerApp(preferredApp) {
Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
.then((output) => {
if (output != '') return preferredApp;
else return 'xdg-open';
});
}
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
const IMAGE_VIEWER_APP = getImageViewerApp('loupe'); // Gnome's image viewer cuz very comfortable zooming
const USER_CACHE_DIR = GLib.get_user_cache_dir();
// Create cache folder and clear pics from previous session
Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/media/waifus'`);
Utils.exec(`bash -c 'rm ${GLib.get_user_cache_dir()}/ags/media/waifus/*'`);
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
export function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath);
@@ -96,17 +107,17 @@ const WaifuImage = (taglist) => {
ImageAction({
name: 'Go to source',
icon: 'link',
action: () => execAsync(['xdg-open', `${thisBlock._imageData.source}`]).catch(print),
action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
}),
ImageAction({
name: 'Hoard',
icon: 'save',
action: () => execAsync(['bash', '-c', `mkdir -p ~/Pictures/waifus && cp ${thisBlock._imagePath} ~/Pictures/waifus`]).catch(print),
action: () => execAsync(['bash', '-c', `mkdir -p ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`]).catch(print),
}),
ImageAction({
name: 'Open externally',
icon: 'open_in_new',
action: () => execAsync(['xdg-open', `${thisBlock._imagePath}`]).catch(print),
action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
}),
]
})
@@ -116,13 +127,6 @@ const WaifuImage = (taglist) => {
const blockImage = Widget.DrawingArea({
className: 'sidebar-waifu-image',
});
// const blockImage = Box({});
// const blockImage = Image({
// hpack: 'start',
// vertical: true,
// className: 'sidebar-waifu-image',
// // homogeneous: true,
// })
const blockImageRevealer = Revealer({
transition: 'slide_down',
transitionDuration: 150,
@@ -138,17 +142,19 @@ const WaifuImage = (taglist) => {
});
const thisBlock = Box({
className: 'sidebar-chat-message',
properties: [
['imagePath', ''],
['imageData', ''],
['update', (imageData, force = false) => {
thisBlock._imageData = imageData;
const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock._imageData;
attribute: {
'imagePath': '',
'isNsfw': false,
'imageData': '',
'update': (imageData, force = false) => {
thisBlock.attribute.imageData = imageData;
const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
thisBlock.attribute.isNsfw = is_nsfw;
if (status != 200) {
downloadState.shown = 'error';
return;
}
thisBlock._imagePath = `${GLib.get_user_cache_dir()}/ags/media/waifus/${signature}${extension}`;
thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
downloadState.shown = 'download';
// Width/height
const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
@@ -156,7 +162,7 @@ const WaifuImage = (taglist) => {
blockImage.set_size_request(widgetWidth, widgetHeight);
const showImage = () => {
downloadState.shown = 'done';
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock._imagePath, widgetWidth, widgetHeight, false);
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
blockImage.set_size_request(widgetWidth, widgetHeight);
blockImage.connect("draw", (widget, cr) => {
@@ -182,19 +188,19 @@ const WaifuImage = (taglist) => {
Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
() => blockImageActions.revealChild = true
);
downloadIndicator._hide();
downloadIndicator.attribute.hide();
}
// Show
if (!force && fileExists(thisBlock._imagePath)) showImage();
else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock._imagePath}' '${url}'`])
if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
.then(showImage)
.catch(print);
blockHeading.get_children().forEach((child) => {
child.setCss(`border-color: ${dominant_color};`);
})
colorIndicator.css = `background-color: ${dominant_color};`;
}],
],
},
},
children: [
colorIndicator,
Box({
@@ -218,25 +224,25 @@ const waifuContent = Box({
className: 'spacing-v-15',
vertical: true,
vexpand: true,
properties: [
['map', new Map()],
],
connections: [
[WaifuService, (box, id) => {
attribute: {
'map': new Map(),
},
setup: (self) => self
.hook(WaifuService, (box, id) => {
if (id === undefined) return;
const newImageBlock = WaifuImage(WaifuService.queries[id]);
box.add(newImageBlock);
box.show_all();
box._map.set(id, newImageBlock);
}, 'newResponse'],
[WaifuService, (box, id) => {
box.attribute.map.set(id, newImageBlock);
}, 'newResponse')
.hook(WaifuService, (box, id) => {
if (id === undefined) return;
const data = WaifuService.responses[id];
if (!data) return;
const imageBlock = box._map.get(id);
imageBlock._update(data);
}, 'updateResponse'],
]
const imageBlock = box.attribute.map.get(id);
imageBlock.attribute.update(data);
}, 'updateResponse')
,
});
export const waifuView = Scrollable({
@@ -313,6 +319,7 @@ export const waifuCommands = Box({
});
const clearChat = () => {
waifuContent.attribute.map.clear();
const kids = waifuContent.get_children();
for (let i = 0; i < kids.length; i++) {
const child = kids[i];
@@ -328,7 +335,7 @@ export const sendMessage = (text) => {
else if (text.startsWith('/test')) {
const newImage = WaifuImage(['/test']);
waifuContent.add(newImage);
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage._update({ // Needs timeout or inits won't make it
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update({ // Needs timeout or inits won't make it
// This is an image uploaded to my github repo
status: 200,
url: 'https://picsum.photos/400/600',
+12 -11
View File
@@ -1,5 +1,6 @@
const { Gtk, Gdk } = imports.gi;
import { App, Utils, Widget } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js";
@@ -32,12 +33,12 @@ APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enable
export const chatEntry = Entry({
className: 'sidebar-chat-entry',
hexpand: true,
connections: [
[ChatGPT, (self) => {
setup: (self) => self
.hook(ChatGPT, (self) => {
if (APIS[currentApiId].name != 'ChatGPT') return;
self.placeholderText = (ChatGPT.key.length > 0 ? 'Ask a question...' : 'Enter OpenAI API Key...');
}, 'hasKey']
],
}, 'hasKey')
,
onChange: (entry) => {
chatSendButton.toggleClassName('sidebar-chat-send-available', entry.text.length > 0);
},
@@ -83,7 +84,7 @@ function switchToTab(id) {
apiContentStack.shown = APIS[id].name;
apiCommandStack.shown = APIS[id].name;
chatEntry.placeholderText = APIS[id].placeholderText,
currentApiId = id;
currentApiId = id;
}
const apiSwitcher = Box({
homogeneous: true,
@@ -104,10 +105,10 @@ const apiSwitcher = Box({
})
export default Widget.Box({
properties: [
['nextTab', () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1))],
['prevTab', () => switchToTab(Math.max(0, currentApiId-1))],
],
attribute: {
'nextTab': () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1)),
'prevTab': () => switchToTab(Math.max(0, currentApiId - 1)),
},
vertical: true,
className: 'spacing-v-10',
homogeneous: false,
+2 -4
View File
@@ -1,7 +1,5 @@
const { Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
const { Box, Button, EventBox, Label, Scrollable } = Widget;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Button, Label } = Widget;
export const SidebarModule = ({
name,
+2 -2
View File
@@ -1,5 +1,5 @@
const { Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, Button, EventBox, Label, Scrollable } = Widget;
import { SidebarModule } from './module.js';
+35 -31
View File
@@ -1,5 +1,7 @@
const { Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
const { Gdk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from "../../lib/materialicon.js";
@@ -93,20 +95,20 @@ const navBar = Box({
});
const pinButton = Button({
properties: [
['enabled', false],
['toggle', (self) => {
self._enabled = !self._enabled;
self.toggleClassName('sidebar-pin-enabled', self._enabled);
attribute: {
'enabled': false,
'toggle': (self) => {
self.attribute.enabled = !self.attribute.enabled;
self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled);
const sideleftWindow = App.getWindow('sideleft');
const barWindow = App.getWindow('bar');
const cornerTopLeftWindow = App.getWindow('cornertl');
const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
sideleftContent.toggleClassName('sidebar-pinned', self._enabled);
sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
if (self._enabled) {
if (self.attribute.enabled) {
sideleftWindow.layer = 'bottom';
barWindow.layer = 'bottom';
cornerTopLeftWindow.layer = 'bottom';
@@ -118,19 +120,20 @@ const pinButton = Button({
cornerTopLeftWindow.layer = 'top';
sideleftWindow.exclusivity = 'normal';
}
}],
],
},
},
vpack: 'start',
className: 'sidebar-pin',
child: MaterialIcon('push_pin', 'larger'),
tooltipText: 'Pin sidebar',
onClicked: (self) => self._toggle(self),
onClicked: (self) => self.attribute.toggle(self),
// QoL: Focus Pin button on open. Hit keybind -> space/enter = toggle pin state
connections: [[App, (self, currentName, visible) => {
if (currentName === 'sideleft' && visible) {
self.grab_focus();
}
}]]
setup: (self) => self
.hook(App, (self, currentName, visible) => {
if (currentName === 'sideleft' && visible)
self.grab_focus();
})
,
})
export default () => Box({
@@ -158,19 +161,20 @@ export default () => Box({
}),
contentStack,
],
connections: [[App, (self, currentName, visible) => {
if (currentName === 'sideleft') {
self.toggleClassName('sidebar-pinned', pinButton._enabled && visible);
}
}]]
setup: (self) => self
.hook(App, (self, currentName, visible) => {
if (currentName === 'sideleft')
self.toggleClassName('sidebar-pinned', pinButton.attribute.enabled && visible);
})
,
}),
],
connections: [
['key-press-event', (widget, event) => { // Handle keybinds
setup: (self) => self
.on('key-press-event', (widget, event) => { // Handle keybinds
if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) {
// Pin sidebar
if (event.get_keyval()[1] == Gdk.KEY_p)
pinButton._toggle(pinButton);
pinButton.attribute.toggle(pinButton);
// Switch sidebar tab
else if (event.get_keyval()[1] === Gdk.KEY_Tab)
switchToTab((currentTabId + 1) % contents.length);
@@ -186,8 +190,8 @@ export default () => Box({
event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
||
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] === Gdk.KEY_v)
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] === Gdk.KEY_v)
) {
chatEntry.grab_focus();
chatEntry.set_text(chatEntry.text + String.fromCharCode(event.get_keyval()[1]));
@@ -197,15 +201,15 @@ export default () => Box({
else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] === Gdk.KEY_Page_Down) {
const toSwitchTab = contentStack.get_visible_child();
toSwitchTab._nextTab();
toSwitchTab.attribute.nextTab();
}
else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] === Gdk.KEY_Page_Up) {
const toSwitchTab = contentStack.get_visible_child();
toSwitchTab._prevTab();
toSwitchTab.attribute.prevTab();
}
}
}],
],
})
,
});
+2 -2
View File
@@ -1,5 +1,5 @@
const { Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { QuickScripts } from './quickscripts.js';
+4 -3
View File
@@ -1,6 +1,7 @@
const { Gio, Gdk, GLib, Gtk } = imports.gi;
import { App, Widget, Utils } from '../../imports.js';
const { Box, Button, CenterBox, Label, Revealer } = Widget;
const { Gio } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label } = Widget;
import { MaterialIcon } from "../../lib/materialicon.js";
import { getCalendarLayout } from "../../lib/calendarlayout.js";
import { setupCursorHover } from "../../lib/cursorhover.js";
@@ -1,15 +1,11 @@
// This file is for the notification widget on the sidebar
// This file is for the notification list on the sidebar
// For the popup notifications, see onscreendisplay.js
// The actual widget for each single notification is in lib/notification.js
const { GLib, Gtk } = imports.gi;
import { Service, Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { lookUpIcon, timeout } = Utils;
const { Box, Button, Icon, Label, Revealer, Scrollable, Stack } = Widget;
const { Box, Button, Label, Scrollable, Stack } = Widget;
import { MaterialIcon } from "../../lib/materialicon.js";
import { setupCursorHover } from "../../lib/cursorhover.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../lib/configwidgets.js';
import Notification from "../../lib/notification.js";
export default (props) => {
@@ -35,8 +31,8 @@ export default (props) => {
vertical: true,
vpack: 'start',
className: 'spacing-v-5-revealer',
connections: [
[Notifications, (box, id) => {
setup: (self) => self
.hook(Notifications, (box, id) => {
if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
Notifications.notifications
.forEach(n => {
@@ -58,17 +54,16 @@ export default (props) => {
box.pack_end(NewNotif, false, false, 0);
box.show_all();
}
}, 'notified'],
[Notifications, (box, id) => {
}, 'notified')
.hook(Notifications, (box, id) => {
if (!id) return;
for (const ch of box.children) {
if (ch._id === id) {
ch._destroyWithAnims();
ch.attribute.destroyWithAnims();
}
}
}, 'closed'],
],
}, 'closed')
,
});
const ListActionButton = (icon, name, action) => Button({
className: 'notif-listaction-btn',
@@ -100,7 +95,7 @@ export default (props) => {
Label({
hexpand: true,
xalign: 0,
className: 'txt-title-small margin-left-10',
className: 'txt-title-small margin-left-10',
// ^ (extra margin on the left so that it looks similarly spaced
// when compared to borderless "Clear" button on the right)
label: 'Notifications',
+33 -33
View File
@@ -1,5 +1,7 @@
const { GLib } = imports.gi;
import { Widget, Utils, Service } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
@@ -20,21 +22,19 @@ function expandTilde(path) {
export const ToggleIconWifi = (props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Wifi | Right-click to configure',
onClicked: Network.toggleWifi,
onClicked: () => Network.toggleWifi(),
onSecondaryClickRelease: () => {
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center wifi', '&']);
App.closeWindow('sideright');
},
child: NetworkIndicator(),
connections: [
[Network, button => {
setup: (self) => {
setupCursorHover(self);
self.hook(Network, button => {
button.toggleClassName('sidebar-button-active', [Network.wifi?.internet, Network.wired?.internet].includes('connected'))
}],
[Network, button => {
button.tooltipText = (`${Network.wifi?.ssid} | Right-click to configure` || 'Unknown');
}],
],
setup: setupCursorHover,
});
},
...props,
});
@@ -53,12 +53,12 @@ export const ToggleIconBluetooth = (props = {}) => Widget.Button({
App.closeWindow('sideright');
},
child: BluetoothIndicator(),
connections: [
[Bluetooth, button => {
setup: (self) => {
setupCursorHover(self);
self.hook(Bluetooth, button => {
button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
}],
],
setup: setupCursorHover,
});
},
...props,
});
@@ -82,24 +82,24 @@ export const HyprToggleIcon = (icon, name, hyprlandConfigValue, props = {}) => W
})
export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work
properties: [
['enabled', false],
['yellowlight', undefined],
],
attribute: {
enabled: false,
yellowlight: undefined,
},
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Night Light',
onClicked: (self) => {
self._enabled = !self._enabled;
self.toggleClassName('sidebar-button-active', self._enabled);
// if (self._enabled) Utils.execAsync(['bash', '-c', 'wlsunset & disown'])
if (self._enabled) Utils.execAsync('wlsunset')
self.attribute.enabled = !self.attribute.enabled;
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
// if (self.attribute.enabled) Utils.execAsync(['bash', '-c', 'wlsunset & disown'])
if (self.attribute.enabled) Utils.execAsync('wlsunset')
else Utils.execAsync('pkill wlsunset');
},
child: MaterialIcon('nightlight', 'norm'),
setup: (self) => {
setupCursorHover(self);
self._enabled = !!exec('pidof wlsunset');
self.toggleClassName('sidebar-button-active', self._enabled);
self.attribute.enabled = !!exec('pidof wlsunset');
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
},
...props,
});
@@ -131,17 +131,17 @@ export const ModuleInvertColors = (props = {}) => Widget.Button({
})
export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
properties: [
['enabled', false],
['inhibitor', undefined],
],
attribute: {
enabled: false,
inhibitor: undefined,
},
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Keep system awake',
onClicked: (self) => {
self._enabled = !self._enabled;
self.toggleClassName('sidebar-button-active', self._enabled);
if (self._enabled) {
self._inhibitor = Utils.subprocess(
self.attribute.enabled = !self.attribute.enabled;
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
if (self.attribute.enabled) {
self.attribute.inhibitor = Utils.subprocess(
['wayland-idle-inhibitor.py'],
(output) => print(output),
(err) => logError(err),
@@ -149,7 +149,7 @@ export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make
);
}
else {
self._inhibitor.force_exit();
self.attribute.inhibitor.force_exit();
}
},
child: MaterialIcon('coffee', 'norm'),
+9 -34
View File
@@ -1,5 +1,5 @@
const { GLib, Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, EventBox } = Widget;
import {
@@ -17,44 +17,19 @@ import {
import ModuleNotificationList from "./notificationlist.js";
import { ModuleCalendar } from "./calendar.js";
// const NUM_OF_TOGGLES_PER_LINE = 5;
// const togglesFlowBox = Widget.FlowBox({
// className: 'sidebar-group spacing-h-10',
// setup: (self) => {
// self.set_max_children_per_line(NUM_OF_TOGGLES_PER_LINE);
// self.add(ToggleIconWifi({ hexpand: true }));
// self.add(ToggleIconBluetooth({ hexpand: true }));
// self.add(HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', { hexpand: true }));
// self.add(HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', { hexpand: true }));
// self.add(ModuleNightLight({ hexpand: true }));
// // Setup flowbox rearrange
// self.connect('child-activated', (self, child) => {
// if (child.get_index() === 0) {
// self.reorder_child(child, self.get_children().length - 1);
// } else {
// self.reorder_child(child, 0);
// }
// });
// }
// })
const timeRow = Box({
className: 'spacing-h-5 sidebar-group-invisible-morehorizpad',
children: [
// Widget.Label({
// className: 'txt-title txt',
// connections: [[5000, label => {
// label.label = GLib.DateTime.new_now_local().format("%H:%M");
// }]],
// }),
Widget.Label({
hpack: 'center',
className: 'txt-small txt',
connections: [[5000, label => {
execAsync(['bash', '-c', `uptime -p | sed -e 's/up //;s/ hours,/h/;s/ minutes/m/'`]).then(upTimeString => {
label.label = `System uptime: ${upTimeString}`;
}).catch(print);
}]],
setup: (self) => self
.poll(5000, label => {
execAsync(['bash', '-c', `uptime -p | sed -e 's/up //;s/ hours,/h/;s/ minutes/m/'`]).then(upTimeString => {
label.label = `Uptime: ${upTimeString}`;
}).catch(print);
})
,
}),
Widget.Box({ hexpand: true }),
// ModuleEditIcon({ hpack: 'end' }), // TODO: Make this work
+29 -26
View File
@@ -1,6 +1,6 @@
const { Gio, Gdk, GLib, Gtk } = imports.gi;
import { App, Widget, Utils } from '../../imports.js';
const { Box, Button, CenterBox, Label, Revealer } = Widget;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Revealer } = Widget;
import { MaterialIcon } from "../../lib/materialicon.js";
import Todo from "../../services/todo.js";
import { setupCursorHover } from "../../lib/cursorhover.js";
@@ -73,33 +73,36 @@ const todoListItem = (task, id, isDone, isEven = false) => {
}
const todoItems = (isDone) => Widget.Scrollable({
hscroll: 'never',
vscroll: 'automatic',
child: Widget.Box({
vertical: true,
connections: [[Todo, (self) => {
self.children = Todo.todo_json.map((task, i) => {
if (task.done != isDone) return null;
return todoListItem(task, i, isDone);
})
if (self.children.length == 0) {
self.homogeneous = true;
self.children = [
Widget.Box({
hexpand: true,
vertical: true,
vpack: 'center',
className: 'txt',
children: [
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'badonkers'),
Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
]
})
]
}
else self.homogeneous = false;
}, 'updated']]
setup: (self) => self
.hook(Todo, (self) => {
self.children = Todo.todo_json.map((task, i) => {
if (task.done != isDone) return null;
return todoListItem(task, i, isDone);
})
if (self.children.length == 0) {
self.homogeneous = true;
self.children = [
Widget.Box({
hexpand: true,
vertical: true,
vpack: 'center',
className: 'txt',
children: [
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'badonkers'),
Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
]
})
]
}
else self.homogeneous = false;
}, 'updated')
,
}),
setup: (listContents) => {
listContents.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = listContents.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
}