ags: sync

This commit is contained in:
end-4
2024-01-25 22:25:27 +07:00
parent ed24fe4ae3
commit 7e73e24dd8
64 changed files with 2674 additions and 1723 deletions
-74
View File
@@ -1,74 +0,0 @@
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';
import { RoundedCorner } from "../../lib/roundedcorner.js";
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
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;
}
};
const OptionalWindowTitleInstance = await OptionalWindowTitle();
export const ModuleLeftSpace = () => Widget.EventBox({
onScrollUp: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value += 0.05;
},
onScrollDown: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value -= 0.05;
},
onPrimaryClick: () => {
App.toggleWindow('sideleft');
},
child: Widget.Box({
homogeneous: false,
children: [
RoundedCorner('topleft', { className: 'corner-black' }),
Widget.Overlay({
overlays: [
Widget.Box({ hexpand: true }),
Widget.Box({
className: 'bar-sidemodule', hexpand: true,
children: [Widget.Box({
vertical: true,
className: 'bar-space-button',
children: [
OptionalWindowTitleInstance,
]
})]
}),
]
})
]
})
});
+59 -42
View File
@@ -1,58 +1,75 @@
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 WindowTitle from "./spaceleft.js";
import Indicators from "./spaceright.js";
import Music from "./music.js";
import System from "./system.js";
import { RoundedCorner, enableClickthrough } 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({});
try {
return (await import('./workspaces_sway.js')).default();
} catch {
return null;
}
}
};
const left = Widget.Box({
className: 'bar-sidemodule',
children: [
ModuleMusic()
],
});
const center = Widget.Box({
children: [
await OptionalWorkspaces(),
],
});
const right = Widget.Box({
className: 'bar-sidemodule',
children: [ModuleSystem()],
});
export default () => Widget.Window({
name: 'bar',
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
visible: true,
child: Widget.CenterBox({
export const Bar = async (monitor = 0) => {
const SideModule = (children) => Widget.Box({
className: 'bar-sidemodule',
children: children,
});
const barContent = Widget.CenterBox({
className: 'bar-bg',
startWidget: ModuleLeftSpace(),
centerWidget: Widget.Box({
className: 'spacing-h-4',
children: [
left,
center,
right,
]
}),
endWidget: ModuleRightSpace(),
setup: (self) => {
const styleContext = self.get_style_context();
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
}
}),
},
startWidget: WindowTitle(),
centerWidget: Widget.Box({
className: 'spacing-h-4',
children: [
SideModule([Music()]),
Widget.Box({
homogeneous: true,
children: [await OptionalWorkspaces()],
}),
SideModule([System()]),
]
}),
endWidget: Indicators(),
});
return Widget.Window({
monitor,
name: `bar${monitor}`,
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
visible: true,
child: barContent,
});
}
export const BarCornerTopleft = (id = '') => Widget.Window({
name: `barcornertl${id}`,
layer: 'top',
anchor: ['top', 'left'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topleft', { className: 'corner', }),
setup: enableClickthrough,
});
export const BarCornerTopright = (id = '') => Widget.Window({
name: `barcornertr${id}`,
layer: 'top',
anchor: ['top', 'right'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topright', { className: 'corner', }),
setup: enableClickthrough,
});
+77 -61
View File
@@ -1,16 +1,18 @@
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 Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
import { showMusicControls } from '../../variables.js';
function trimTrackTitle(title) {
var cleanedTitle = title;
cleanedTitle = cleanedTitle.replace(/【[^】]*】/, ''); // Remove stuff like【C93】 at beginning
cleanedTitle = cleanedTitle.replace(/\[FREE DOWNLOAD\]/g, ''); // Remove F-777's [FREE DOWNLOAD]
return cleanedTitle.trim();
if(!title) return '';
const cleanRegexes = [
/【[^】]*】/, // Touhou n weeb stuff
/\[FREE DOWNLOAD\]/, // F-777
];
cleanRegexes.forEach((expr) => title.replace(expr, ''));
return title;
}
const TrackProgress = () => {
@@ -30,61 +32,75 @@ const TrackProgress = () => {
})
}
export const ModuleMusic = () => Widget.EventBox({ // TODO: use cairo to make button bounce smaller on click
onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`),
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`),
onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']),
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-music spacing-h-10',
children: [
Widget.Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
children: [Widget.Overlay({
child: Widget.Box({
vpack: 'center',
className: 'bar-music-playstate',
homogeneous: true,
children: [Widget.Label({
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}),
})],
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(),
]
})]
const switchToRelativeWorkspace = async (self, num) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.sendMessage(`dispatch workspace ${num > 0 ? '+' : ''}${num}`);
} catch {
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
}
}
export default () => {
// TODO: use cairo to make button bounce smaller on click, if that's possible
const playingState = Widget.Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
children: [Widget.Overlay({
child: Widget.Box({
vpack: 'center',
className: 'bar-music-playstate',
homogeneous: true,
children: [Widget.Label({
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}),
Widget.Scrollable({
hexpand: true,
child: Widget.Label({
className: 'txt-smallie txt-onSurfaceVariant',
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';
}),
})
})
]
})
]
})],
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(),
]
})]
});
const trackTitle = Widget.Scrollable({
hexpand: true,
child: Widget.Label({
className: 'txt-smallie txt-onSurfaceVariant',
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';
}),
})
})
});
return Widget.EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']),
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-music spacing-h-10',
children: [
playingState,
trackTitle,
]
})
]
})
});
}
-81
View File
@@ -1,81 +0,0 @@
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 SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { execAsync } = Utils;
import Indicator from '../../services/indicator.js';
import { StatusIcons } from "../../lib/statusicons.js";
import { RoundedCorner } from "../../lib/roundedcorner.js";
import { Tray } from "./tray.js";
export const ModuleRightSpace = () => {
const barTray = Tray();
const barStatusIcons = StatusIcons({
className: 'bar-statusicons',
setup: (self) => self.hook(App, (self, currentName, visible) => {
if (currentName === 'sideright') {
self.toggleClassName('bar-statusicons-active', visible);
}
}),
});
return Widget.EventBox({
onScrollUp: () => {
if (!Audio.speaker) return;
Audio.speaker.volume += 0.03;
Indicator.popup(1);
},
onScrollDown: () => {
if (!Audio.speaker) return;
Audio.speaker.volume -= 0.03;
Indicator.popup(1);
},
// onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
// onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
onPrimaryClick: () => App.toggleWindow('sideright'),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
child: Widget.Box({
homogeneous: false,
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({ hexpand: true, }),
barTray,
Widget.Revealer({
transition: 'slide_left',
revealChild: false,
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.attribute.update(self, 1), 'added')
.hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
,
}),
barStatusIcons,
],
}),
]
}),
RoundedCorner('topright', { className: 'corner-black' })
]
})
});
}
+72
View File
@@ -0,0 +1,72 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
const WindowTitle = async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
return 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;
}),
})
]
})
});
} catch {
return null;
}
}
const OptionalWindowTitleInstance = await WindowTitle();
export default () => Widget.EventBox({
onScrollUp: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value += 0.05;
},
onScrollDown: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value -= 0.05;
},
onPrimaryClick: () => {
App.toggleWindow('sideleft');
},
child: Widget.Box({
homogeneous: false,
children: [
Widget.Box({ className: 'bar-corner-spacing' }),
Widget.Overlay({
overlays: [
Widget.Box({ hexpand: true }),
Widget.Box({
className: 'bar-sidemodule', hexpand: true,
children: [Widget.Box({
vertical: true,
className: 'bar-space-button',
children: [
OptionalWindowTitleInstance,
]
})]
}),
]
})
]
})
});
+82
View File
@@ -0,0 +1,82 @@
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 SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { execAsync } = Utils;
import Indicator from '../../services/indicator.js';
import { StatusIcons } from "../../lib/statusicons.js";
import { Tray } from "./tray.js";
export default () => {
const barTray = Tray();
const notifCounter = Widget.Revealer({
transition: 'slide_left',
revealChild: false,
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.attribute.update(self, 1), 'added')
.hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
,
});
const barStatusIcons = StatusIcons({
className: 'bar-statusicons',
setup: (self) => self.hook(App, (self, currentName, visible) => {
if (currentName === 'sideright') {
self.toggleClassName('bar-statusicons-active', visible);
}
}),
});
const actualContent = Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({ hexpand: true, }),
barTray,
notifCounter,
barStatusIcons,
],
}),
]
});
return Widget.EventBox({
onScrollUp: () => {
if (!Audio.speaker) return;
Audio.speaker.volume += 0.03;
Indicator.popup(1);
},
onScrollDown: () => {
if (!Audio.speaker) return;
Audio.speaker.volume -= 0.03;
Indicator.popup(1);
},
onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
onPrimaryClick: () => App.toggleWindow('sideright'),
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
child: Widget.Box({
homogeneous: false,
children: [
actualContent,
Widget.Box({ className: 'bar-corner-spacing' }),
]
})
});
}
+14 -25
View File
@@ -5,7 +5,6 @@ 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;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { MaterialIcon } from '../../lib/materialicon.js';
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
@@ -14,7 +13,7 @@ const BATTERY_LOW = 20;
const BatBatteryProgress = () => {
const _updateProgress = (circprog) => { // Set circular progress value
circprog.css = `font-size: ${Battery.percent}px;`
circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= BATTERY_LOW);
circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
@@ -91,7 +90,7 @@ const BarBattery = () => Box({
transitionDuration: 150,
revealChild: false,
transition: 'slide_right',
child: MaterialIcon('bolt', 'norm'),
child: MaterialIcon('bolt', 'norm', {tooltipText: "Charging"}),
setup: (self) => self.hook(Battery, revealer => {
self.revealChild = Battery.charging;
}),
@@ -122,25 +121,6 @@ const BarBattery = () => Box({
]
});
const BarResourceValue = (name, icon, command) => Widget.Box({
vpack: 'center',
className: 'bar-batt spacing-h-5',
children: [
MaterialIcon(icon, 'small'),
Widget.ProgressBar({ // Progress
vpack: 'center', hexpand: true,
className: 'bar-prog-batt',
setup: (self) => self.poll(5000, (progress) => execAsync(['bash', '-c', command])
.then((output) => {
progress.value = Number(output) / 100;
progress.tooltipText = `${name}: ${Number(output)}%`
})
.catch(print)
),
}),
]
});
const BarResource = (name, icon, command) => {
const resourceLabel = Label({
className: 'txt-smallie txt-onSurfaceVariant',
@@ -187,9 +167,18 @@ const BarGroup = ({ child }) => Widget.Box({
]
});
export const ModuleSystem = () => Widget.EventBox({
onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`),
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`),
const switchToRelativeWorkspace = async (self, num) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.sendMessage(`dispatch workspace ${num > 0 ? '+' : ''}${num}`);
} catch {
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
}
}
export default () => Widget.EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onPrimaryClick: () => App.toggleWindow('sideright'),
child: Widget.Box({
className: 'spacing-h-5',
@@ -26,7 +26,7 @@ const WorkspaceContents = (count = 10) => {
let workspaceMask = 0;
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
if (ws.id < 0) continue; // Ignore scratchpads
if (ws.id <= 0) continue; // Ignore scratchpads
if (ws.id > count) return; // Not rendered
if (workspaces[i].windows > 0) {
workspaceMask |= (1 << ws.id);
+172 -46
View File
@@ -1,58 +1,184 @@
const { GLib, Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
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 { execAsync, exec } = Utils;
const { Box, DrawingArea, EventBox } = Widget;
const NUM_OF_WORKSPACES = 10;
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
const dispatch = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`);
const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
const switchToRelativeWorkspace = (self, num) =>
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
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,
);
}),
const WorkspaceContents = (count = 10) => {
return DrawingArea({
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
attribute: {
initialized: false,
workspaceMask: 0,
updateMask: (self) => {
if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Sway.workspaces;
let workspaceMask = 0;
// console.log('----------------')
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
// console.log(ws.name, ',', ws.num);
if (!Number(ws.name)) return;
const id = Number(ws.name);
if (id <= 0) continue; // Ignore scratchpads
if (id > count) return; // Not rendered
if (workspaces[i].windows > 0) {
workspaceMask |= (1 << id);
}
}
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(Sway.active.workspace, (area) => {
area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
})
),
setup: (self) => self.hook(Sway.active.workspace,
(box) => box.children.map((btn) => {
btn.visible = Sway.workspaces.some(
(ws) => ws.name == btn.id,
);
})
),
});
};
.hook(Sway, (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;
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")
,
}),
const workspaceStyleContext = dummyWs.get_style_context();
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const workspaceRadius = workspaceDiameter / 2;
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
area.set_size_request(workspaceDiameter * count, -1);
const widgetStyleContext = area.get_style_context();
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
const activeWsCenterY = height / 2;
// Font
const layout = PangoCairo.create_layout(cr);
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
layout.set_font_description(fontDesc);
cr.setAntialias(Cairo.Antialias.BEST);
// Get kinda min radius for number indicators
layout.set_text("0".repeat(count.toString().length), -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
const indicatorGap = workspaceRadius - indicatorRadius;
// Draw workspace numbers
for (let i = 1; i <= count; 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.attribute.workspaceMask & (1 << (i - 1)))) { // Left
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
// Set color for text
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
}
else
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
layout.set_text(`${i}`, -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
const y = (height - layoutHeight) / 2;
cr.moveTo(x, y);
// cr.showText(text);
PangoCairo.show_layout(cr, layout);
cr.stroke();
}
// Draw active ws
// base
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
cr.fill();
// inner decor
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
cr.fill();
}))
,
})
}
export default () => EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onMiddleClickRelease: () => App.toggleWindow('overview'),
onSecondaryClickRelease: () => App.toggleWindow('osk'),
attribute: { clicked: false },
child: Box({
homogeneous: true,
className: 'bar-group-margin',
children: [Box({
className: 'bar-group bar-group-standalone bar-group-pad',
css: 'min-width: 2px;',
children: [
WorkspaceContents(10),
]
})]
}),
setup: (self) => {
console.log('[LOG] Sway workspace module loaded')
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
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);
switchToWorkspace(wsId);
})
self.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
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);
switchToWorkspace(wsId);
})
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
});
@@ -1,83 +0,0 @@
const { Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
import { App, Service, Utils, Widget, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
const { execAsync, exec } = Utils;
const { Box, Label } = Widget;
const NUM_OF_VERTICES = 30;
const NUM_OF_EDGES = 29;
// Vertices
var vertices = [];
for (var i = 0; i < NUM_OF_VERTICES; i++) {
vertices.push([
Math.floor(Math.random() * SCREEN_WIDTH),
Math.floor(Math.random() * SCREEN_HEIGHT)
]);
}
// Edges
function generateRandomEdges(numVertices, numEdges) { // TODO: make sure whole graph is connected
var edges = new Set();
var vertices = [];
// Generate vertices
for (var i = 0; i < numVertices; i++) {
vertices.push(i);
}
// Generate random distinct edges
while (edges.size < numEdges) {
var randomVertex1 = vertices[Math.floor(Math.random() * numVertices)];
var randomVertex2 = vertices[Math.floor(Math.random() * numVertices)];
// Ensure the two vertices are distinct and the edge doesn't already exist
if (randomVertex1 !== randomVertex2) {
var edge = [randomVertex1, randomVertex2].sort();
edges.add(edge.join(','));
}
}
return Array.from(edges).map(edge => edge.split(',').map(Number));
}
var edges = generateRandomEdges(NUM_OF_VERTICES, NUM_OF_EDGES);
export default () => Box({
hpack: 'fill',
vpack: 'fill',
homogeneous: true,
children: [
Widget.DrawingArea({
className: 'bg-graph',
setup: (area) => {
area.connect('draw', Lang.bind(area, (area, cr) => {
// area.set_size_request(SCREEN_WIDTH, SCREEN_HEIGHT);
// console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
const styleContext = area.get_style_context();
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
const backgroundColor = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const radius = area.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
const borderWidth = area.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
cr.setSourceRGBA(backgroundColor.red, backgroundColor.green, backgroundColor.blue, backgroundColor.alpha);
cr.rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
cr.fill();
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
// Draw edges
cr.setLineWidth(borderWidth);
console.log("line width:", borderWidth);
for (var i = 0; i < NUM_OF_EDGES; i++) {
console.log(vertices[edges[i][0]][0], vertices[edges[i][0]][1], '->', vertices[edges[i][1]][0], vertices[edges[i][1]][1])
cr.moveTo(vertices[edges[i][0]][0], vertices[edges[i][0]][1]);
cr.lineTo(vertices[edges[i][1]][0], vertices[edges[i][1]][1]);
cr.stroke();
}
// Draw vertices
for (var i = 0; i < NUM_OF_VERTICES; i++) {
cr.arc(vertices[i][0], vertices[i][1], radius, 0, 2 * Math.PI)
cr.fill()
}
}))
}
})
]
})
+11 -15
View File
@@ -1,28 +1,24 @@
const { Gdk, 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 { execAsync, exec } = Utils;
import WallpaperImage from './wallpaper.js';
import TimeAndLaunchesWidget from './timeandlaunches.js'
import SystemWidget from './system.js'
import GraphWidget from './graph.js'
export default () => Widget.Window({
name: 'desktopbackground',
anchor: ['top', 'bottom', 'left', 'right'],
export default (monitor) => Widget.Window({
name: `desktopbackground${monitor}`,
// anchor: ['top', 'bottom', 'left', 'right'],
layer: 'background',
exclusivity: 'normal',
exclusivity: 'ignore',
visible: true,
// child: WallpaperImage(monitor),
child: Widget.Overlay({
child: Widget.Box({
hexpand: true,
vexpand: true,
}),
child: WallpaperImage(monitor),
overlays: [
// GraphWidget(),
TimeAndLaunchesWidget(),
SystemWidget(),
],
setup: (self) => self.set_overlay_pass_through(self.get_children()[1], true),
setup: (self) => {
self.set_overlay_pass_through(self.get_children()[1], true);
},
}),
});
});
@@ -114,11 +114,14 @@ const distroAndVersion = Box({
Label({
className: 'bg-distro-name',
xalign: 0,
label: '<version>',
label: 'An environment idk',
setup: (label) => {
execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(distro => {
label.label = `Hyprland ${distro}`;
}).catch(print);
// hyprctl will return unsuccessfully if Hyprland isn't running
execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
label.label = `Hyprland ${version}`;
}).catch(() => execAsync([`bash`, `-c`, `sway -v | cut -d'-' -f1 | sed 's/sway version /v/'`]).then(version => {
label.label = `Sway ${version}`;
}).catch(print));
},
}),
]
@@ -0,0 +1,115 @@
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';
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
const { exec, execAsync } = Utils;
const { Box, Button, Label, Stack } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Wallpaper from '../../services/wallpaper.js';
import { setupCursorHover } from '../../lib/cursorhover.js';
const SWITCHWALL_SCRIPT_PATH = `${App.configDir}/scripts/color_generation/switchwall.sh`;
const WALLPAPER_ZOOM_SCALE = 1.25; // For scrolling when we switch workspace
const MAX_WORKSPACES = 10;
const WALLPAPER_OFFSCREEN_X = (WALLPAPER_ZOOM_SCALE - 1) * SCREEN_WIDTH;
const WALLPAPER_OFFSCREEN_Y = (WALLPAPER_ZOOM_SCALE - 1) * SCREEN_HEIGHT;
function clamp(x, min, max) {
return Math.min(Math.max(x, min), max);
}
export default (monitor = 0) => {
const wallpaperImage = Widget.DrawingArea({
attribute: {
pixbuf: undefined,
workspace: 1,
sideleft: 0,
sideright: 0,
updatePos: (self) => {
self.setCss(`font-size: ${self.attribute.workspace - self.attribute.sideleft + self.attribute.sideright}px;`)
},
},
className: 'bg-wallpaper-transition',
setup: (self) => {
self.set_size_request(SCREEN_WIDTH, SCREEN_HEIGHT);
self
// TODO: reduced updates using timeouts to reduce lag
// .hook(Hyprland.active.workspace, (self) => {
// self.attribute.workspace = Hyprland.active.workspace.id
// self.attribute.updatePos(self);
// })
// .hook(App, (box, name, visible) => { // Update on open
// if (self.attribute[name] === undefined) return;
// self.attribute[name] = (visible ? 1 : 0);
// self.attribute.updatePos(self);
// })
.on('draw', (self, cr) => {
if (!self.attribute.pixbuf) return;
const styleContext = self.get_style_context();
const workspace = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
// Draw
Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
-(WALLPAPER_OFFSCREEN_X / (MAX_WORKSPACES + 1) * (clamp(workspace, 0, MAX_WORKSPACES + 1))),
-WALLPAPER_OFFSCREEN_Y / 2);
cr.paint();
})
.hook(Wallpaper, (self) => {
const wallPath = Wallpaper.get(monitor);
if (!wallPath || wallPath === "") return;
self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file(wallPath);
const scale_x = SCREEN_WIDTH * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_width();
const scale_y = SCREEN_HEIGHT * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_height();
const scale_factor = Math.max(scale_x, scale_y);
self.attribute.pixbuf = self.attribute.pixbuf.scale_simple(
Math.round(self.attribute.pixbuf.get_width() * scale_factor),
Math.round(self.attribute.pixbuf.get_height() * scale_factor),
GdkPixbuf.InterpType.BILINEAR
);
self.queue_draw();
}, 'updated');
;
}
,
});
const wallpaperPrompt = Box({
hpack: 'center',
vpack: 'center',
vertical: true,
className: 'spacing-v-10',
children: [
Label({
hpack: 'center',
justification: 'center',
className: 'txt-large',
label: `No wallpaper loaded.\nAn image ≥ ${SCREEN_WIDTH * WALLPAPER_ZOOM_SCALE} × ${SCREEN_HEIGHT * WALLPAPER_ZOOM_SCALE} is recommended.`,
}),
Button({
hpack: 'center',
className: 'btn-primary',
label: `Select one`,
setup: setupCursorHover,
onClicked: (self) => Utils.execAsync([SWITCHWALL_SCRIPT_PATH]),
}),
]
});
const stack = Stack({
transition: 'crossfade',
transitionDuration: 180,
items: [
['image', wallpaperImage],
['prompt', wallpaperPrompt],
],
setup: (self) => self
.hook(Wallpaper, (self) => {
const wallPath = Wallpaper.get(monitor);
self.shown = ((wallPath && wallPath != "") ? 'image' : 'prompt');
}, 'updated')
,
})
return stack;
// return wallpaperImage;
}
@@ -16,7 +16,7 @@ const ColorBox = ({
]
})
const colorschemeContent = Box({
const ColorschemeContent = () => Box({
className: 'osd-colorscheme spacing-v-5',
vertical: true,
hpack: 'center',
@@ -44,7 +44,7 @@ const colorschemeContent = Box({
export default () => Widget.Revealer({
transition: 'slide_down',
transitionDuration: 200,
child: colorschemeContent,
child: ColorschemeContent(),
setup: (self) => self.hook(showColorScheme, (revealer) => {
revealer.revealChild = showColorScheme.value;
}),
@@ -1,85 +1,87 @@
// This file is for brightness/volume indicators
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;
const { Box, Label, ProgressBar } = Widget;
import { MarginRevealer } from '../../lib/advancedwidgets.js';
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
const OsdValue = (name, labelSetup, progressSetup, props = {}) => Box({ // Volume
...props,
vertical: true,
className: 'osd-bg osd-value',
hexpand: true,
children: [
Box({
vexpand: true,
children: [
Label({
xalign: 0, yalign: 0, hexpand: true,
className: 'osd-label',
label: `${name}`,
}),
Label({
hexpand: false, className: 'osd-value-txt',
setup: labelSetup,
}),
]
}),
ProgressBar({
className: 'osd-progress',
hexpand: true,
vertical: false,
setup: progressSetup,
})
],
});
const OsdValue = (name, labelSetup, progressSetup, props = {}) => {
const valueName = Label({
xalign: 0, yalign: 0, hexpand: true,
className: 'osd-label',
label: `${name}`,
});
const valueNumber = Label({
hexpand: false, className: 'osd-value-txt',
setup: labelSetup,
});
return Box({ // Volume
...props,
vertical: true,
hexpand: true,
className: 'osd-bg osd-value',
attribute: {
'disable': () => {
valueNumber.label = '󰖭';
}
},
children: [
Box({
vexpand: true,
children: [
valueName,
valueNumber,
]
}),
ProgressBar({
className: 'osd-progress',
hexpand: true,
vertical: false,
setup: progressSetup,
})
],
});
}
const brightnessIndicator = OsdValue('Brightness',
(self) => self
.hook(Brightness, self => {
export default () => {
const brightnessIndicator = OsdValue('Brightness',
(self) => self.hook(Brightness, self => {
self.label = `${Math.round(Brightness.screen_value * 100)}`;
}, 'notify::screen-value')
,
(self) => self
.hook(Brightness, (progress) => {
}, 'notify::screen-value'),
(self) => self.hook(Brightness, (progress) => {
const updateValue = Brightness.screen_value;
progress.value = updateValue;
}, 'notify::screen-value')
,
)
}, 'notify::screen-value'),
)
const volumeIndicator = OsdValue('Volume',
(self) => self
.hook(Audio, (label) => {
const volumeIndicator = OsdValue('Volume',
(self) => self.hook(Audio, (label) => {
label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
})
,
(self) => self
.hook(Audio, (progress) => {
}),
(self) => self.hook(Audio, (progress) => {
const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) progress.value = updateValue;
}),
);
return MarginRevealer({
transition: 'slide_down',
showClass: 'osd-show',
hideClass: 'osd-hide',
extraSetup: (self) => self
.hook(Indicator, (revealer, value) => {
if (value > -1) revealer.attribute.show();
else revealer.attribute.hide();
}, 'popup')
,
child: Box({
hpack: 'center',
vertical: false,
className: 'spacing-h--10',
children: [
brightnessIndicator,
volumeIndicator,
]
})
,
);
export default () => MarginRevealer({
transition: 'slide_down',
showClass: 'osd-show',
hideClass: 'osd-hide',
extraSetup: (self) => self
.hook(Indicator, (revealer, value) => {
if (value > -1) revealer.attribute.show();
else revealer.attribute.hide();
}, 'popup')
,
child: Box({
hpack: 'center',
vertical: false,
className: 'spacing-h--10',
children: [
brightnessIndicator,
volumeIndicator,
]
})
});
});
}
+3 -3
View File
@@ -1,11 +1,11 @@
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';
// import MusicControls from './musiccontrols.js';
import ColorScheme from './colorscheme.js';
import NotificationPopups from './notificationpopups.js';
export default (monitor) => Widget.Window({
export default (monitor = 0) => Widget.Window({
name: `indicator${monitor}`,
monitor,
className: 'indicator',
@@ -23,7 +23,7 @@ export default (monitor) => Widget.Window({
css: 'min-height: 2px;',
children: [
IndicatorValues(),
MusicControls(),
// MusicControls(),
NotificationPopups(),
ColorScheme(),
]
+15 -20
View File
@@ -32,17 +32,13 @@ function isRealPlayer(player) {
);
}
export const getPlayer = (name = PREFERRED_PLAYER) => {
return Mpris.getPlayer(name) || Mpris.players[0] || null;
}
export const getPlayer = (name = PREFERRED_PLAYER) => Mpris.getPlayer(name) || Mpris.players[0] || null;
function lengthStr(length) {
const min = Math.floor(length / 60);
const sec = Math.floor(length % 60);
const sec0 = sec < 10 ? '0' : '';
return `${min}:${sec0}${sec}`;
}
function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath);
return file.query_exists(null);
@@ -54,17 +50,11 @@ function detectMediaSource(link) {
return '󰈹 Firefox'
return "󰈣 File";
}
// Remove protocol if present
let url = link.replace(/(^\w+:|^)\/\//, '');
// Extract the domain name
let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
if (domain == 'ytimg.com')
return '󰗃 Youtube';
if (domain == 'discordapp.net')
return '󰙯 Discord';
if (domain == 'sndcdn.com')
return '󰓀 SoundCloud';
if (domain == 'ytimg.com') return '󰗃 Youtube';
if (domain == 'discordapp.net') return '󰙯 Discord';
if (domain == 'sndcdn.com') return '󰓀 SoundCloud';
return domain;
}
@@ -72,15 +62,20 @@ const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
function getTrackfont(player) {
const title = player.trackTitle;
const artists = player.trackArtists.join(' ');
if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo')) return 'Chakra Petch'; // Rigid square replacement
if (title.includes('東方')) return 'Crimson Text, serif'; // Serif for Touhou stuff
if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
return 'Chakra Petch'; // Rigid square replacement
if (title.includes('東方'))
return 'Crimson Text, serif'; // Serif for Touhou stuff
return DEFAULT_MUSIC_FONT;
}
function trimTrackTitle(title) {
var cleanedTitle = title;
cleanedTitle = cleanedTitle.replace(/【[^】]*】/, ''); // Remove stuff like【C93】 at beginning
cleanedTitle = cleanedTitle.replace(/\[FREE DOWNLOAD\]/g, ''); // Remove F-777's [FREE DOWNLOAD]
return cleanedTitle.trim();
if(!title) return '';
const cleanRegexes = [
/【[^】]*】/, // Touhou n weeb stuff
/\[FREE DOWNLOAD\]/, // F-777
];
cleanRegexes.forEach((expr) => title.replace(expr, ''));
return title;
}
const TrackProgress = ({ player, ...rest }) => {
@@ -4,37 +4,7 @@ import Notifications from 'resource:///com/github/Aylur/ags/service/notification
const { Box } = Widget;
import Notification from '../../lib/notification.js';
const PopupNotification = (notifObject) => Widget.Box({
homogeneous: true,
children: [
Widget.EventBox({
onHoverLost: () => {
notifObject.dismiss();
},
child: Widget.Revealer({
revealChild: true,
child: Widget.Box({
children: [Notification({
notifObject: notifObject,
isPopup: true,
props: { hpack: 'fill' },
})],
}),
})
})
]
})
const naiveNotifPopupList = Widget.Box({
vertical: true,
className: 'spacing-v-5',
setup: (self) => self.hook(Notifications, (box) => {
box.children = Notifications.popups.reverse()
.map(notifItem => PopupNotification(notifItem));
}),
})
const notifPopupList = Box({
export default () => Box({
vertical: true,
className: 'osd-notifs spacing-v-5-revealer',
attribute: {
@@ -72,5 +42,3 @@ const notifPopupList = Box({
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
,
});
export default () => notifPopupList;
@@ -4,7 +4,6 @@ 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;
import { MaterialIcon } from '../../lib/materialicon.js';
@@ -19,7 +18,7 @@ execAsync(`ydotoold`).catch(print); // Start ydotool daemon
function releaseAllKeys() {
const keycodes = Array.from(Array(249).keys());
execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
.then(console.log('Released all keys'))
.then(console.log('[OSK] Released all keys'))
.catch(print);
}
var modsPressed = false;
@@ -146,22 +145,32 @@ const keyboardWindow = Box({
const gestureEvBox = EventBox({ child: keyboardWindow })
const gesture = Gtk.GestureDrag.new(gestureEvBox);
gesture.connect('drag-begin', () => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
}).catch(print);
gesture.connect('drag-begin', async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
}).catch(print);
} catch {
return;
}
});
gesture.connect('drag-update', () => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY;
gesture.connect('drag-update', async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY;
if (offset > 0) return;
if (offset > 0) return;
keyboardWindow.setCss(`
keyboardWindow.setCss(`
margin-bottom: ${offset}px;
`);
}).catch(print);
}).catch(print);
} catch {
return;
}
});
gesture.connect('drag-end', () => {
var offset = gesture.get_offset()[2];
-1
View File
@@ -6,7 +6,6 @@ function moveClientToWorkspace(address, workspace) {
}
export function dumpToWorkspace(from, to) {
console.log('dump', from, to);
if (from == to) return;
Hyprland.clients.forEach(client => {
if (client.workspace.id == from) {
@@ -66,9 +66,9 @@ export function execAndClose(command, terminal) {
execAsync(command).catch(print);
}
export function startsWithNumber(str) {
var pattern = /^\d/;
return pattern.test(str);
export function couldBeMath(str) {
const regex = /^[0-9.+*/-]/;
return regex.test(str);
}
export function expandTilde(path) {
@@ -1,6 +1,7 @@
const { Gdk, Gtk } = imports.gi;
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
import App from 'resource:///com/github/Aylur/ags/app.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
@@ -14,6 +15,8 @@ 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 overviewTick = Variable(false);
function truncateTitle(str) {
let lastDash = -1;
let found = -1; // 0: em dash, 1: en dash, 2: minus, 3: vertical bar, 4: middle dot
@@ -213,6 +216,7 @@ const workspace = index => {
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()}`)
overviewTick.setValue(!overviewTick.value);
});
},
child: fixed,
@@ -275,18 +279,19 @@ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) =>
}).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);
})
},
setup: (box) => box
.hook(overviewTick, (box) => box.attribute.update(box))
// .hook(Hyprland, (box, name, data) => { // idk, does this make it lag occasionally?
// console.log(name)
// 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);
})
,
});
@@ -4,7 +4,7 @@ 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';
import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
export const DirectoryButton = ({ parentPath, name, type, icon }) => {
const actionText = Widget.Revealer({
@@ -5,7 +5,7 @@ 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 { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
import {
CalculationResultButton, CustomCommandButton, DirectoryButton,
DesktopEntryButton, ExecuteCommandButton, SearchButton
@@ -39,7 +39,7 @@ const OptionalOverview = async () => {
try {
return (await import('./overview_hyprland.js')).default();
} catch {
return null;
return Widget.Box({});
// return (await import('./overview_hyprland.js')).default();
}
};
@@ -104,7 +104,7 @@ export const SearchAndWindows = () => {
const isAction = text.startsWith('>');
const isDir = (['/', '~'].includes(entry.text[0]));
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a workaround
if (couldBeMath(text)) { // Eval on typing is dangerous, this is a workaround
try {
const fullResult = eval(text);
// copy
@@ -166,7 +166,7 @@ export const SearchAndWindows = () => {
_appSearchResults = Applications.query(text);
// Calculate
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a small workaround.
if (couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
try {
const fullResult = eval(text);
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
+14 -41
View File
@@ -1,43 +1,16 @@
import Cairo from 'gi://cairo?version=1.0';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { RoundedCorner } from "../../lib/roundedcorner.js";
import { RoundedCorner, dummyRegion, enableClickthrough } from "../../lib/roundedcorner.js";
const dummyRegion = new Cairo.Region();
const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
export const CornerTopleft = () => Widget.Window({
name: 'cornertl',
layer: 'top',
anchor: ['top', 'left'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topleft', { className: 'corner', }),
setup: enableClickthrough,
});
export const CornerTopright = () => Widget.Window({
name: 'cornertr',
layer: 'top',
anchor: ['top', 'right'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topright', { className: 'corner', }),
setup: enableClickthrough,
});
export const CornerBottomleft = () => Widget.Window({
name: 'cornerbl',
layer: 'top',
anchor: ['bottom', 'left'],
exclusivity: 'ignore',
visible: true,
child: RoundedCorner('bottomleft', { className: 'corner-black', }),
setup: enableClickthrough,
});
export const CornerBottomright = () => Widget.Window({
name: 'cornerbr',
layer: 'top',
anchor: ['bottom', 'right'],
exclusivity: 'ignore',
visible: true,
child: RoundedCorner('bottomright', { className: 'corner-black', }),
setup: enableClickthrough,
});
export default (monitor = 0, where = 'bottom left') => {
const positionString = where.replace(/\s/, ""); // remove space
return Widget.Window({
monitor,
name: `corner${positionString}${monitor}`,
layer: 'overlay',
anchor: where.split(' '),
exclusivity: 'ignore',
visible: true,
child: RoundedCorner(positionString, { className: 'corner-black', }),
setup: enableClickthrough,
});
}
@@ -223,7 +223,7 @@ const MessageContent = (content) => {
return contentBox;
}
export const ChatMessage = (message, scrolledWindow) => {
export const ChatMessage = (message, modelName = 'Model') => {
const messageContentBox = MessageContent(message.content);
const thisMessage = Box({
className: 'sidebar-chat-message',
@@ -241,7 +241,8 @@ export const ChatMessage = (message, scrolledWindow) => {
xalign: 0,
className: 'txt txt-bold sidebar-chat-name',
wrap: true,
label: (message.role == 'user' ? USERNAME : 'ChatGPT'),
useMarkup: true,
label: (message.role == 'user' ? USERNAME : modelName),
}),
messageContentBox,
],
+69 -57
View File
@@ -8,63 +8,75 @@ const { execAsync, exec } = Utils;
import ChatGPT from '../../../services/chatgpt.js';
import { MaterialIcon } from "../../../lib/materialicon.js";
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
import { SystemMessage, ChatMessage } from "./chatgpt_chatmessage.js";
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
import { markdownTest } from '../../../lib/md2pango.js';
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
export const chatGPTTabIcon = Box({
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
export const chatGPTTabIcon = Icon({
hpack: 'center',
className: 'sidebar-chat-apiswitcher-icon',
homogeneous: true,
children: [
MaterialIcon('forum', 'norm'),
],
icon: `openai-symbolic`,
setup: (self) => Utils.timeout(513, () => { // stupid condition race
const styleContext = self.get_style_context();
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// console.log(Math.round(Math.max(width, height, 1)));
self.size = Math.max(width, height, 1) * 116 / 180;
// ↑ Why such a specific proportion? See https://openai.com/brand#logos
})
});
const chatGPTInfo = Box({
vertical: true,
className: 'spacing-v-15',
children: [
Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
icon: `${App.configDir}/assets/openai-logomark.svg`,
setup: (self) => Utils.timeout(1, () => {
const styleContext = self.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) * 116 / 180; // Why such a specific proportion? See https://openai.com/brand#logos
})
}),
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'ChatGPT',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Powered by OpenAI',
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.',
setup: setupCursorHoverInfo,
}),
]
}),
]
})
const ChatGPTInfo = () => {
const openAiLogo = Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
icon: `openai-symbolic`,
setup: (self) => Utils.timeout(513, () => { // stupid condition race
const styleContext = self.get_style_context();
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// console.log(Math.round(Math.max(width, height, 1)));
self.size = Math.max(width, height, 1) * 116 / 180;
// ↑ Why such a specific proportion? See https://openai.com/brand#logos
})
});
return Box({
vertical: true,
className: 'spacing-v-15',
children: [
openAiLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Assistant (ChatGPT 3.5)',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Powered by OpenAI',
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.',
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
export const chatGPTSettings = MarginRevealer({
export const ChatGPTSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
@@ -89,7 +101,7 @@ export const chatGPTSettings = MarginRevealer({
{ value: 0.50, name: 'Balanced', },
{ value: 1.00, name: 'Creative', },
],
initIndex: 1,
initIndex: 2,
onChange: (value, name) => {
ChatGPT.temperature = value;
},
@@ -111,8 +123,8 @@ export const chatGPTSettings = MarginRevealer({
}),
ConfigToggle({
icon: 'description',
name: 'Assistant prompt',
desc: 'Tells ChatGPT\n 1. It\'s a sidebar assistant on Linux\n 2. Be short and concise\n 3. Use markdown features extensively\nLeave this off for a vanilla ChatGPT experience.',
name: 'Enhancements',
desc: 'Tells ChatGPT:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
initValue: ChatGPT.assistantPrompt,
onChange: (self, newValue) => {
ChatGPT.assistantPrompt = newValue;
@@ -124,7 +136,7 @@ export const chatGPTSettings = MarginRevealer({
})
});
export const openaiApiKeyInstructions = Box({
export const OpenaiApiKeyInstructions = () => Box({
homogeneous: true,
children: [Revealer({
transition: 'slide_down',
@@ -150,7 +162,7 @@ export const openaiApiKeyInstructions = Box({
})]
});
export const chatGPTWelcome = Box({
const chatGPTWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
@@ -158,9 +170,9 @@ export const chatGPTWelcome = Box({
vpack: 'center',
vertical: true,
children: [
chatGPTInfo,
openaiApiKeyInstructions,
chatGPTSettings, ``
ChatGPTInfo(),
OpenaiApiKeyInstructions(),
ChatGPTSettings(),
]
})
});
@@ -172,7 +184,7 @@ export const chatContent = Box({
.hook(ChatGPT, (box, id) => {
const message = ChatGPT.messages[id];
if (!message) return;
box.add(ChatMessage(message, chatGPTView))
box.add(ChatMessage(message, 'ChatGPT'))
}, 'newMsg')
,
});
+276
View File
@@ -0,0 +1,276 @@
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 Gemini from '../../../services/gemini.js';
import { MaterialIcon } from "../../../lib/materialicon.js";
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
import { markdownTest } from '../../../lib/md2pango.js';
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
const MODEL_NAME = `Gemini`;
export const geminiTabIcon = Icon({
hpack: 'center',
className: 'sidebar-chat-apiswitcher-icon',
icon: `google-gemini-symbolic`,
setup: (self) => Utils.timeout(513, () => { // stupid condition race
const styleContext = self.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) * 116 / 180;
})
})
const GeminiInfo = () => {
const geminiLogo = Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
icon: `google-gemini-symbolic`,
setup: (self) => Utils.timeout(513, () => { // stupid condition race
const styleContext = self.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) * 116 / 180;
})
});
return Box({
vertical: true,
className: 'spacing-v-15',
children: [
geminiLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Assistant (Gemini Pro)',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Powered by Google',
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.',
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
export const GeminiSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
.hook(Gemini, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
.hook(Gemini, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
child: Box({
vertical: true,
className: 'sidebar-chat-settings',
children: [
ConfigSegmentedSelection({
hpack: 'center',
icon: 'casino',
name: 'Randomness',
desc: 'Gemini\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
options: [
{ value: 0.00, name: 'Precise', },
{ value: 0.50, name: 'Balanced', },
{ value: 1.00, name: 'Creative', },
],
initIndex: 2,
onChange: (value, name) => {
Gemini.temperature = value;
},
}),
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
Box({
vertical: true,
hpack: 'fill',
className: 'sidebar-chat-settings-toggles',
children: [
ConfigToggle({
icon: 'description',
name: 'Enhancements',
desc: 'Tells Gemini:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
initValue: Gemini.assistantPrompt,
onChange: (self, newValue) => {
Gemini.assistantPrompt = newValue;
},
}),
]
})
]
})
});
export const GoogleAiInstructions = () => Box({
homogeneous: true,
children: [Revealer({
transition: 'slide_down',
transitionDuration: 150,
setup: (self) => self
.hook(Gemini, (self, hasKey) => {
self.revealChild = (Gemini.key.length == 0);
}, 'hasKey')
,
child: Button({
child: Label({
useMarkup: true,
wrap: true,
className: 'txt sidebar-chat-welcome-txt',
justify: Gtk.Justification.CENTER,
label: 'A Google AI API key is required\nYou can grab one <u>here</u>, then enter it below'
}),
setup: setupCursorHover,
onClicked: () => {
Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
}
})
})]
});
const geminiWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15',
vpack: 'center',
vertical: true,
children: [
GeminiInfo(),
GoogleAiInstructions(),
GeminiSettings(),
]
})
});
export const chatContent = Box({
className: 'spacing-v-15',
vertical: true,
setup: (self) => self
.hook(Gemini, (box, id) => {
const message = Gemini.messages[id];
if (!message) return;
box.add(ChatMessage(message, MODEL_NAME))
}, 'newMsg')
,
});
const clearChat = () => {
Gemini.clear();
const children = chatContent.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
}
export const geminiView = Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
geminiWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
});
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
setup: setupCursorHover,
label: command,
});
export const geminiCommands = Box({
className: 'spacing-h-5',
children: [
Box({ hexpand: true }),
CommandButton('/key'),
CommandButton('/model'),
CommandButton('/clear'),
]
});
export const sendMessage = (text) => {
// Check if text or API key is empty
if (text.length == 0) return;
if (Gemini.key.length == 0) {
Gemini.key = text;
chatContent.add(SystemMessage(`Key saved to\n\`${Gemini.keyPath}\``, 'API Key', geminiView));
text = '';
return;
}
// Commands
if (text.startsWith('/')) {
if (text.startsWith('/clear')) clearChat();
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${Gemini.modelName}\``, '/model', geminiView))
else if (text.startsWith('/prompt')) {
const firstSpaceIndex = text.indexOf(' ');
const prompt = text.slice(firstSpaceIndex + 1);
if (firstSpaceIndex == -1 || prompt.length < 1) {
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
}
else {
Gemini.addMessage('user', prompt)
}
}
else if (text.startsWith('/key')) {
const parts = text.split(' ');
if (parts.length == 1) chatContent.add(SystemMessage(
`Key stored in:\n\`${Gemini.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
'/key',
geminiView));
else {
Gemini.key = parts[1];
chatContent.add(SystemMessage(`Updated API Key at\n\`${Gemini.keyPath}\``, '/key', geminiView));
}
}
else if (text.startsWith('/test'))
chatContent.add(SystemMessage(markdownTest, `Markdown test`, geminiView));
else
chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView))
}
else {
Gemini.send(text);
}
}
+85 -16
View File
@@ -5,7 +5,7 @@ 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 } from "../../../lib/cursorhover.js";
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
import WaifuService from '../../../services/waifus.js';
async function getImageViewerApp(preferredApp) {
@@ -162,7 +162,8 @@ const WaifuImage = (taglist) => {
blockImage.set_size_request(widgetWidth, widgetHeight);
const showImage = () => {
downloadState.shown = 'done';
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
// 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) => {
@@ -220,10 +221,62 @@ const WaifuImage = (taglist) => {
return thisBlock;
}
const WaifuInfo = () => {
const waifuLogo = Label({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
label: 'photo_library',
})
return Box({
vertical: true,
vexpand: true,
className: 'spacing-v-15',
children: [
waifuLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Waifus',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Powered by waifu.im',
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: 'A free Waifu API. An alternative to waifu.pics.',
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
const waifuWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15',
vpack: 'center',
vertical: true,
children: [
WaifuInfo(),
]
})
});
const waifuContent = Box({
className: 'spacing-v-15',
vertical: true,
vexpand: true,
attribute: {
'map': new Map(),
},
@@ -251,6 +304,7 @@ export const waifuView = Scrollable({
child: Box({
vertical: true,
children: [
waifuWelcome,
waifuContent,
]
}),
@@ -327,6 +381,21 @@ const clearChat = () => {
}
}
const DummyTag = (width, height, url, color = '#9392A6') => {
return { // Needs timeout or inits won't make it
status: 200,
url: url,
extension: '',
signature: 0,
source: url,
dominant_color: color,
is_nsfw: false,
width: width,
height: height,
tags: ['/test'],
};
}
export const sendMessage = (text) => {
// Do something on send
// Commands
@@ -335,20 +404,20 @@ export const sendMessage = (text) => {
else if (text.startsWith('/test')) {
const newImage = WaifuImage(['/test']);
waifuContent.add(newImage);
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',
extension: '',
signature: 0,
source: 'https://picsum.photos/400/600',
dominant_color: '#9392A6',
is_nsfw: false,
width: 300,
height: 200,
tags: ['/test'],
}, true));
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
DummyTag(300, 200, 'https://picsum.photos/600/400'),
true
));
}
else if (text.startsWith('/chino')) {
const newImage = WaifuImage(['/chino']);
waifuContent.add(newImage);
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
DummyTag(300, 400, 'https://chino.pages.dev/chino', '#B2AEF3'),
true
));
}
}
else WaifuService.fetch(text);
}
+40 -21
View File
@@ -1,22 +1,32 @@
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 { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js";
// APIs
import ChatGPT from '../../services/chatgpt.js';
import Gemini from '../../services/gemini.js';
import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
const APIS = [
{
name: 'ChatGPT',
name: 'Assistant (ChatGPT 3.5)',
sendCommand: chatGPTSendMessage,
contentWidget: chatGPTView,
commandBar: chatGPTCommands,
tabIcon: chatGPTTabIcon,
placeholderText: 'Message ChatGPT',
placeholderText: 'Message ChatGPT...',
},
{
name: 'Assistant (Gemini Pro)',
sendCommand: geminiSendMessage,
contentWidget: geminiView,
commandBar: geminiCommands,
tabIcon: geminiTabIcon,
placeholderText: 'Message Gemini...',
},
{
name: 'Waifus',
@@ -35,8 +45,12 @@ export const chatEntry = Entry({
hexpand: true,
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...');
if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return;
self.placeholderText = (ChatGPT.key.length > 0 ? 'Message ChatGPT...' : 'Enter OpenAI API Key...');
}, 'hasKey')
.hook(Gemini, (self) => {
if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
self.placeholderText = (Gemini.key.length > 0 ? 'Message Gemini...' : 'Enter Google AI API Key...');
}, 'hasKey')
,
onChange: (entry) => {
@@ -86,22 +100,27 @@ function switchToTab(id) {
chatEntry.placeholderText = APIS[id].placeholderText,
currentApiId = id;
}
const apiSwitcher = Box({
homogeneous: true,
children: [
Box({
className: 'sidebar-chat-apiswitcher spacing-h-5',
hpack: 'center',
children: APIS.map((api, id) => Button({
child: api.tabIcon,
tooltipText: api.name,
setup: setupCursorHover,
onClicked: () => {
switchToTab(id);
}
})),
}),
]
const apiSwitcher = CenterBox({
centerWidget: Box({
className: 'sidebar-chat-apiswitcher spacing-h-5',
hpack: 'center',
children: APIS.map((api, id) => Button({
child: api.tabIcon,
tooltipText: api.name,
setup: setupCursorHover,
onClicked: () => {
switchToTab(id);
}
})),
}),
endWidget: Button({
hpack: 'end',
className: 'txt-subtext txt-norm icon-material',
label: 'lightbulb',
tooltipText: 'Use PageUp/PageDown to switch between API pages',
setup: setupCursorHoverInfo,
}),
})
export default Widget.Box({
+1 -1
View File
@@ -5,7 +5,7 @@ export default () => PopupWindow({
focusable: true,
anchor: ['left', 'top', 'bottom'],
name: 'sideleft',
// exclusivity: 'exclusive',
layer: 'top',
showClassName: 'sideleft-show',
hideClassName: 'sideleft-hide',
child: SidebarLeft(),
-8
View File
@@ -102,22 +102,14 @@ const pinButton = Button({
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.attribute.enabled);
if (self.attribute.enabled) {
sideleftWindow.layer = 'bottom';
barWindow.layer = 'bottom';
cornerTopLeftWindow.layer = 'bottom';
sideleftWindow.exclusivity = 'exclusive';
}
else {
sideleftWindow.layer = 'top';
barWindow.layer = 'top';
cornerTopLeftWindow.layer = 'top';
sideleftWindow.exclusivity = 'normal';
}
},
+1 -1
View File
@@ -10,7 +10,7 @@ export default Scrollable({
child: Box({
vertical: true,
children: [
QuickScripts(),
// QuickScripts(),
]
})
});
+56 -47
View File
@@ -4,7 +4,6 @@ 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';
const { execAsync, exec } = Utils;
import { BluetoothIndicator, NetworkIndicator } from "../../lib/statusicons.js";
import { setupCursorHover } from "../../lib/cursorhover.js";
@@ -12,7 +11,6 @@ import { MaterialIcon } from '../../lib/materialicon.js';
function expandTilde(path) {
if (path.startsWith('~')) {
console.log(GLib.get_home_dir() + path.slice(1));
return GLib.get_home_dir() + path.slice(1);
} else {
return path;
@@ -62,24 +60,30 @@ export const ToggleIconBluetooth = (props = {}) => Widget.Button({
...props,
});
export const HyprToggleIcon = (icon, name, hyprlandConfigValue, props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: `${name}`,
onClicked: (button) => {
// Set the value to 1 - value
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
const currentOption = JSON.parse(result).int;
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
button.toggleClassName('sidebar-button-active', currentOption == 0);
}).catch(print);
},
child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
setup: button => {
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
setupCursorHover(button);
},
...props,
})
export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
try {
return Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: `${name}`,
onClicked: (button) => {
// Set the value to 1 - value
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
const currentOption = JSON.parse(result).int;
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
button.toggleClassName('sidebar-button-active', currentOption == 0);
}).catch(print);
},
child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
setup: button => {
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
setupCursorHover(button);
},
...props,
})
} catch {
return null;
}
}
export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work
attribute: {
@@ -104,31 +108,36 @@ export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make th
...props,
});
export const ModuleInvertColors = (props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Color inversion',
onClicked: (button) => {
// const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
Hyprland.sendMessage('j/getoption decoration:screen_shader')
.then((output) => {
const shaderPath = JSON.parse(output)["str"].trim();
console.log(output)
console.log(shaderPath)
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
button.toggleClassName('sidebar-button-active', false);
}
else {
Hyprland.sendMessage(`j/keyword decoration:screen_shader ${expandTilde('~/.config/hypr/shaders/invert.frag')}`)
.catch(print);
button.toggleClassName('sidebar-button-active', true);
}
})
},
child: MaterialIcon('invert_colors', 'norm'),
setup: setupCursorHover,
...props,
})
export const ModuleInvertColors = async (props = {}) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
return Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Color inversion',
onClicked: (button) => {
// const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
Hyprland.sendMessage('j/getoption decoration:screen_shader')
.then((output) => {
const shaderPath = JSON.parse(output)["str"].trim();
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
button.toggleClassName('sidebar-button-active', false);
}
else {
Hyprland.sendMessage(`j/keyword decoration:screen_shader ${expandTilde('~/.config/hypr/shaders/invert.frag')}`)
.catch(print);
button.toggleClassName('sidebar-button-active', true);
}
})
},
child: MaterialIcon('invert_colors', 'norm'),
setup: setupCursorHover,
...props,
})
} catch {
return null;
};
}
export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
attribute: {
@@ -173,9 +182,9 @@ export const ModuleEditIcon = (props = {}) => Widget.Button({ // TODO: Make this
export const ModuleReloadIcon = (props = {}) => Widget.Button({
...props,
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Reload Hyprland',
tooltipText: 'Reload Environment config',
onClicked: () => {
execAsync(['bash', '-c', 'hyprctl reload &']);
execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
App.toggleWindow('sideright');
},
child: MaterialIcon('refresh', 'norm'),
+3 -3
View File
@@ -45,10 +45,10 @@ const togglesBox = Widget.Box({
children: [
ToggleIconWifi(),
ToggleIconBluetooth(),
HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', {}),
HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
await HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', {}),
await HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
ModuleNightLight(),
ModuleInvertColors(),
await ModuleInvertColors(),
ModuleIdleInhibitor(),
]
})