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
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
sodipodi:docname="google-gemini-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.77339804"
inkscape:cx="391.13107"
inkscape:cy="332.94628"
inkscape:window-width="1908"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<title
id="title1">ionicons-v5_logos</title>
<path
d="m 377.94638,279.52117 -1.75376,-7.44181 H 214.44368 v 68.45852 h 96.64274 c -10.03366,47.64625 -56.59351,72.7265 -94.62516,72.7265 -27.67207,0 -56.84182,-11.63997 -76.14863,-30.34928 a 108.70175,108.70175 0 0 1 -32.43671,-76.73064 c 0,-28.83607 12.95917,-57.67991 31.81591,-76.65304 18.85674,-18.97314 47.33585,-29.5888 75.652,-29.5888 32.42894,0 55.67007,17.21939 64.36125,25.07248 l 48.64728,-48.3912 C 314.08178,164.08378 274.87838,132.48516 213.77632,132.48516 v 0 c -47.14186,0 -92.34372,18.05745 -125.385701,50.9908 -32.607419,32.42893 -49.485367,79.32247 -49.485367,122.83266 0,43.51018 15.970029,88.06021 47.56865,120.74522 33.763658,34.85782 81.580638,53.07825 130.817688,53.07825 44.79835,0 87.26093,-17.55307 117.52483,-49.40001 29.75175,-31.3503 45.13979,-74.72857 45.13979,-120.20204 0,-19.14386 -1.92447,-30.51221 -2.00983,-31.00887 z"
id="path1"
style="stroke-width:0.775997" />
<path
d="m 396.33661,224.32725 v 0 0 c -8.3763,-55.27516 -51.76093,-98.65984 -107.03609,-107.0361 v 0 0 c 55.27516,-8.3763 98.65979,-51.760947 107.03609,-107.036116 v 0 0 c 8.37627,55.275169 51.76088,98.659816 107.0361,107.036116 v 0 0 c -55.27522,8.37626 -98.65983,51.76094 -107.0361,107.0361 z"
fill="#076eff"
id="path19"
style="fill:#000000;stroke-width:2.9422" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

+35 -22
View File
@@ -1,14 +1,14 @@
"use strict"; "use strict";
// Import // Import
const { GLib } = imports.gi; import Gdk from 'gi://Gdk';
import App from 'resource:///com/github/Aylur/ags/app.js' import App from 'resource:///com/github/Aylur/ags/app.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js' import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
// Widgets // Widgets
import Bar from './widgets/bar/main.js'; import { Bar, BarCornerTopleft, BarCornerTopright } from './widgets/bar/main.js';
import Cheatsheet from './widgets/cheatsheet/main.js'; import Cheatsheet from './widgets/cheatsheet/main.js';
import DesktopBackground from './widgets/desktopbackground/main.js'; // import DesktopBackground from './widgets/desktopbackground/main.js';
// import Dock from './widgets/dock/main.js'; // import Dock from './widgets/dock/main.js';
import { CornerTopleft, CornerTopright, CornerBottomleft, CornerBottomright } from './widgets/screencorners/main.js'; import Corner from './widgets/screencorners/main.js';
import Indicator from './widgets/indicators/main.js'; import Indicator from './widgets/indicators/main.js';
import Osk from './widgets/onscreenkeyboard/main.js'; import Osk from './widgets/onscreenkeyboard/main.js';
import Overview from './widgets/overview/main.js'; import Overview from './widgets/overview/main.js';
@@ -16,7 +16,11 @@ import Session from './widgets/session/main.js';
import SideLeft from './widgets/sideleft/main.js'; import SideLeft from './widgets/sideleft/main.js';
import SideRight from './widgets/sideright/main.js'; import SideRight from './widgets/sideright/main.js';
const CLOSE_ANIM_TIME = 210; // Longer than actual anim time (see styles) to make sure widgets animate fully const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
function forMonitors(widget) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).map(widget).flat(1);
}
// SCSS compilation // SCSS compilation
Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles
@@ -29,7 +33,25 @@ function applyStyle() {
} }
applyStyle(); applyStyle();
// Config object const Windows = () => [
// forMonitors(DesktopBackground),
// Dock(),
Overview(),
forMonitors(Indicator),
Cheatsheet(),
SideLeft(),
SideRight(),
Osk(),
Session(),
// forMonitors(Bar),
// forMonitors(BarCornerTopleft),
// forMonitors(BarCornerTopright),
forMonitors((id) => Corner(id, 'top left')),
forMonitors((id) => Corner(id, 'top right')),
forMonitors((id) => Corner(id, 'bottom left')),
forMonitors((id) => Corner(id, 'bottom right')),
];
const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully
export default { export default {
css: `${App.configDir}/style.css`, css: `${App.configDir}/style.css`,
stackTraceOnError: true, stackTraceOnError: true,
@@ -38,20 +60,11 @@ export default {
'sideleft': CLOSE_ANIM_TIME, 'sideleft': CLOSE_ANIM_TIME,
'osk': CLOSE_ANIM_TIME, 'osk': CLOSE_ANIM_TIME,
}, },
windows: [ windows: Windows().flat(1),
CornerTopleft(),
CornerTopright(),
CornerBottomleft(),
CornerBottomright(),
DesktopBackground(), // If you're going to uncomment these,
// Dock(), // Buggy // uncomment the import statement too.
Overview(),
Indicator(),
Cheatsheet(),
SideLeft(),
SideRight(),
Osk(), // On-screen keyboard
Session(), // Power menu, if that's what you like to call it
Bar(),
],
}; };
// Stuff that don't need to be toggled. And they're async so ugh...
// Bar().catch(print);
forMonitors(Bar);
forMonitors(BarCornerTopleft);
forMonitors(BarCornerTopright);
+9 -10
View File
@@ -3,20 +3,19 @@ const require = async file => (await import(resource(file))).default;
const service = async file => (await require(`service/${file}`)); const service = async file => (await require(`service/${file}`));
export const App = await require('app'); export const App = await require('app');
export const Widget = await require('widget'); // export const Widget = await require('widget');
export const Service = await require('service'); // export const Service = await require('service');
export const Variable = await require('variable'); // export const Variable = await require('variable');
export const Utils = await import(resource('utils')); export const Utils = await import(resource('utils'));
// export const Applications = await service('applications');
export const Applications = await service('applications'); // export const Audio = await service('audio');
export const Audio = await service('audio'); // export const Battery = await service('battery');
export const Battery = await service('battery'); // export const Bluetooth = await service('bluetooth');
export const Bluetooth = await service('bluetooth'); // export const Hyprland = await service('hyprland');
export const Hyprland = await service('hyprland');
export const Mpris = await service('mpris'); export const Mpris = await service('mpris');
export const Network = await service('network'); export const Network = await service('network');
export const Notifications = await service('notifications'); export const Notifications = await service('notifications');
export const SystemTray = await service('systemtray'); // export const SystemTray = await service('systemtray');
globalThis['App'] = App; ////////////////////////////// globalThis['App'] = App; //////////////////////////////
// globalThis['Widget'] = Widget; // globalThis['Widget'] = Widget;
-1
View File
@@ -40,7 +40,6 @@ export const MarginRevealer = ({
child.css = `margin-top: -${child.get_allocated_height()}px;`; child.css = `margin-top: -${child.get_allocated_height()}px;`;
}, },
'toggle': () => { 'toggle': () => {
console.log('toggle');
if (widget.attribute.revealChild) widget.attribute.hide(); if (widget.attribute.revealChild) widget.attribute.hide();
else widget.attribute.show(); else widget.attribute.show();
}, },
+1 -1
View File
@@ -86,7 +86,7 @@ export const AnimatedCircProg = ({
// area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`; // area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
Utils.timeout(20, () => { Utils.timeout(20, () => {
area.css = `font-size: ${initTo}px;`; area.css = `font-size: ${initTo}px;`;
}) }, area)
// const transitionDistance = initTo - initFrom; // const transitionDistance = initTo - initFrom;
// const oneStep = initAnimTime / initAnimPoints; // const oneStep = initAnimTime / initAnimPoints;
// area.css = ` // area.css = `
+7 -8
View File
@@ -3,7 +3,6 @@
const { GLib, Gdk, Gtk } = imports.gi; const { GLib, Gdk, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js' import Widget from 'resource:///com/github/Aylur/ags/widget.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js' import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
const { lookUpIcon, timeout } = Utils;
const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget; const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget;
import { MaterialIcon } from "./materialicon.js"; import { MaterialIcon } from "./materialicon.js";
import { setupCursorHover } from "./cursorhover.js"; import { setupCursorHover } from "./cursorhover.js";
@@ -38,9 +37,9 @@ const NotificationIcon = (notifObject) => {
} }
let icon = 'NO_ICON'; let icon = 'NO_ICON';
if (lookUpIcon(notifObject.appIcon)) if (Utils.lookUpIcon(notifObject.appIcon))
icon = notifObject.appIcon; icon = notifObject.appIcon;
if (lookUpIcon(notifObject.appEntry)) if (Utils.lookUpIcon(notifObject.appEntry))
icon = notifObject.appEntry; icon = notifObject.appEntry;
return Box({ return Box({
@@ -58,7 +57,7 @@ const NotificationIcon = (notifObject) => {
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
self.size = Math.max(width * 0.7, height * 0.7, 1); // im too lazy to add another box lol self.size = Math.max(width * 0.7, height * 0.7, 1); // im too lazy to add another box lol
}), }, self),
}) })
: :
MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', { MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', {
@@ -84,11 +83,11 @@ export default ({
notificationBox.setCss(middleClickClose); notificationBox.setCss(middleClickClose);
Utils.timeout(200, () => { Utils.timeout(200, () => {
wholeThing.revealChild = false; wholeThing.revealChild = false;
}); }, wholeThing);
Utils.timeout(400, () => { Utils.timeout(400, () => {
command(); command();
wholeThing.destroy(); wholeThing.destroy();
}); }, wholeThing);
} }
const widget = EventBox({ const widget = EventBox({
onHover: (self) => { onHover: (self) => {
@@ -382,11 +381,11 @@ export default ({
} }
Utils.timeout(200, () => { Utils.timeout(200, () => {
wholeThing.revealChild = false wholeThing.revealChild = false
}); }, wholeThing);
Utils.timeout(400, () => { Utils.timeout(400, () => {
command(); command();
wholeThing.destroy(); wholeThing.destroy();
}); }, wholeThing);
} }
else { else {
self.setCss(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1); self.setCss(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+4
View File
@@ -1,6 +1,10 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Gtk } = imports.gi; const { Gtk } = imports.gi;
const Lang = imports.lang; const Lang = imports.lang;
import Cairo from 'gi://cairo?version=1.0';
export const dummyRegion = new Cairo.Region();
export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
export const RoundedCorner = (place, props) => Widget.DrawingArea({ export const RoundedCorner = (place, props) => Widget.DrawingArea({
...props, ...props,
+54 -50
View File
@@ -6,7 +6,6 @@ import { MaterialIcon } from './materialicon.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js'; import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js'; import Network from 'resource:///com/github/Aylur/ags/service/network.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { languages } from '../data/languages.js'; import { languages } from '../data/languages.js';
// A guessing func to try to support langs not listed in data/languages.js // A guessing func to try to support langs not listed in data/languages.js
@@ -163,57 +162,62 @@ export const NetworkIndicator = () => Widget.Stack({
}); });
const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => { const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
var initLangs = []; try {
var languageStackArray = []; const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
var currentKeyboard; var initLangs = [];
var languageStackArray = [];
var currentKeyboard;
const updateCurrentKeyboards = () => { const updateCurrentKeyboards = () => {
currentKeyboard = JSON.parse(Utils.exec('hyprctl -j devices')).keyboards currentKeyboard = JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
.find(device => device.name === 'at-translated-set-2-keyboard'); .find(device => device.name === 'at-translated-set-2-keyboard');
if (currentKeyboard) { if (currentKeyboard) {
initLangs = currentKeyboard.layout.split(',').map(lang => lang.trim()); initLangs = currentKeyboard.layout.split(',').map(lang => lang.trim());
} }
languageStackArray = Array.from({ length: initLangs.length }, (_, i) => { languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
const lang = languages.find(lang => lang.layout == initLangs[i]); const lang = languages.find(lang => lang.layout == initLangs[i]);
if (!lang) return [ if (!lang) return [
initLangs[i], initLangs[i],
Widget.Label({ label: initLangs[i] }) Widget.Label({ label: initLangs[i] })
]; ];
return [ return [
lang.layout, lang.layout,
Widget.Label({ label: (useFlag ? lang.flag : lang.layout) }) Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
]; ];
});
};
updateCurrentKeyboards();
const widgetRevealer = Widget.Revealer({
transition: 150,
transition: 'slide_left',
revealChild: languageStackArray.length > 1,
}); });
}; const widgetContent = Widget.Stack({
updateCurrentKeyboards(); transition: 'slide_up_down',
const widgetRevealer = Widget.Revealer({ items: [
transition: 150, ...languageStackArray,
transition: 'slide_left', ['undef', Widget.Label({ label: '?' })]
revealChild: languageStackArray.length > 1, ],
}); setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
const widgetContent = Widget.Stack({ if (!kbName) {
transition: 'slide_up_down', return;
items: [ }
...languageStackArray, var lang = languages.find(lang => layoutName.includes(lang.name));
['undef', Widget.Label({ label: '?' })] if (lang) {
], widgetContent.shown = lang.layout;
setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => { }
if (!kbName) { else { // Attempt to support langs not listed
return; lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
} if (!lang) stack.shown = 'undef';
var lang = languages.find(lang => layoutName.includes(lang.name)); else stack.shown = lang[0];
if (lang) { }
widgetContent.shown = lang.layout; }, 'keyboard-layout'),
} });
else { // Attempt to support langs not listed widgetRevealer.child = widgetContent;
lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName)); return widgetRevealer;
if (!lang) stack.shown = 'undef'; } catch {
else stack.shown = lang[0]; return null;
} }
}, 'keyboard-layout'),
});
widgetRevealer.child = widgetContent;
return widgetRevealer;
} }
const OptionalKeyboardLayout = async () => { const OptionalKeyboardLayout = async () => {
@@ -134,7 +134,7 @@ apply_gtk() { # Using gradience-cli
# (which is unreadable when broken when you use dark mode) # (which is unreadable when broken when you use dark mode)
if [ "$lightdark" = "-l" ]; then if [ "$lightdark" = "-l" ]; then
gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3' gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3'
gsettings set org.gnome.desktop.interface color-scheme 'default' gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
else else
gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3-dark gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3-dark
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
@@ -34,6 +34,7 @@ elif len(sys.argv) > 1 and sys.argv[1] == '--color':
newtheme = themeFromSourceColor(argbFromHex(colorstr)) newtheme = themeFromSourceColor(argbFromHex(colorstr))
else: else:
# try: # try:
# imagePath = subprocess.check_output("ags run-js 'wallpaper.get(0)'", shell=True)
imagePath = subprocess.check_output("swww query | awk -F 'image: ' '{print $2}'", shell=True) imagePath = subprocess.check_output("swww query | awk -F 'image: ' '{print $2}'", shell=True)
imagePath = imagePath[:-1].decode("utf-8") imagePath = imagePath[:-1].decode("utf-8")
img = Image.open(imagePath) img = Image.open(imagePath)
@@ -1,16 +1,15 @@
#!/usr/bin/bash #!/usr/bin/bash
# Switches swww wallpaper
# Requires: coreutils, xrandr, hyprland
if [ "$1" == "--noswitch" ]; then if [ "$1" == "--noswitch" ]; then
imgpath=$(swww query | awk -F 'image: ' '{print $2}') imgpath=$(swww query | awk -F 'image: ' '{print $2}')
# imgpath=$(ags run-js 'wallpaper.get(0)')
else else
# Select and set image (hyprland) # Select and set image (hyprland)
cd "$HOME/Pictures" cd "$HOME/Pictures"
imgpath=$(yad --width 1200 --height 800 --file --title='Choose wallpaper') imgpath=$(yad --width 1200 --height 800 --file --title='Choose wallpaper')
screensizey=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1) screensizey=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1)
cursorposx=$(hyprctl cursorpos -j | gojq '.x') cursorposx=$(hyprctl cursorpos -j | gojq '.x' 2>/dev/null) || cursorposx=960
cursorposy=$(hyprctl cursorpos -j | gojq '.y') cursorposy=$(hyprctl cursorpos -j | gojq '.y' 2>/dev/null) || cursorposy=540
cursorposy_inverted=$(( screensizey - cursorposy )) cursorposy_inverted=$(( screensizey - cursorposy ))
if [ "$imgpath" == '' ]; then if [ "$imgpath" == '' ]; then
@@ -18,8 +17,9 @@ else
exit 0 exit 0
fi fi
echo Sending "$imgpath" to swww. Cursor pos: ["$cursorposx, $cursorposy_inverted"]
# Change swww wallpaper # ags run-js "wallpaper.set('')"
# sleep 0.1 && ags run-js "wallpaper.set('${imgpath}')" &
swww img "$imgpath" --transition-step 100 --transition-fps 60 \ swww img "$imgpath" --transition-step 100 --transition-fps 60 \
--transition-type grow --transition-angle 30 --transition-duration 1 \ --transition-type grow --transition-angle 30 --transition-duration 1 \
--transition-pos "$cursorposx, $cursorposy_inverted" --transition-pos "$cursorposx, $cursorposy_inverted"
+4 -4
View File
@@ -5,7 +5,10 @@ getdate() {
} }
cd ~/Videos || exit cd ~/Videos || exit
if [[ "$(pidof wf-recorder)" == "" ]]; then if pgrep wf-recorder > /dev/null; then
notify-send "Recording Stopped" "Stopped" -a 'record-script.sh' &
pkill wf-recorder &
else
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'record-script.sh' notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'record-script.sh'
if [[ "$1" == "--sound" ]]; then if [[ "$1" == "--sound" ]]; then
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio=alsa_output.pci-0000_08_00.6.analog-stereo.monitor & disown wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio=alsa_output.pci-0000_08_00.6.analog-stereo.monitor & disown
@@ -16,7 +19,4 @@ if [[ "$(pidof wf-recorder)" == "" ]]; then
else else
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & disown wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & disown
fi fi
else
kill --signal SIGINT wf-recorder
notify-send "Recording Stopped" "Stopped" -a 'record-script.sh'
fi fi
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Get the current workspace number
current=$(swaymsg -t get_workspaces | gojq '.[] | select(.focused==true) | .num')
# Check if a number was passed as an argument
if [[ "$1" =~ ^[+-]?[0-9]+$ ]]; then
new_workspace=$((current + $1))
else
new_workspace=$((current + 1))
fi
# Check if the new workspace number is out of bounds
if [[ $new_workspace -lt 1 ]]; then
exit 0
fi
# Switch to the new workspace
if [[ $2 == 'move' ]]; then
swaymsg move container to workspace $new_workspace
else
swaymsg workspace $new_workspace
fi
+3 -26
View File
@@ -1,19 +1,9 @@
# -*- conf -*-
shell=fish shell=fish
# term=foot (or xterm-256color if built with -Dterminfo=disabled)
term=xterm-256color term=xterm-256color
# login-shell=no
# app-id=foot
title=foot title=foot
# locked-title=no
font=SpaceMono Nerd Font:size=11 font=SpaceMono Nerd Font:size=11
# font-bold=<bold variant of regular font>
# font-italic=<italic variant of regular font>
# font-bold-italic=<bold+italic variant of regular font>
# line-height=<font metrics>
letter-spacing=0 letter-spacing=0
# horizontal-letter-offset=0 # horizontal-letter-offset=0
# vertical-letter-offset=0 # vertical-letter-offset=0
@@ -34,17 +24,8 @@ bold-text-in-bright=no
# selection-target=primary # selection-target=primary
# workers=<number of logical CPUs> # workers=<number of logical CPUs>
[bell]
# urgent=no
# notify=no
# command=
# command-focused=no
[scrollback] [scrollback]
lines=10000 lines=10000
# multiplier=3.0
# indicator-position=relative
# indicator-format=
[url] [url]
# launch=xdg-open ${url} # launch=xdg-open ${url}
@@ -55,15 +36,11 @@ lines=10000
[cursor] [cursor]
style=beam style=beam
# color=111111 dcdccc
color=$background # $onBackground # color=$background # $onBackground #
# blink=no # blink=no
beam-thickness=1.5 beam-thickness=1.5
# underline-thickness=<font underline thickness> # underline-thickness=<font underline thickness>
[mouse]
# hide-when-typing=no
# alternate-scroll-mode=yes
[colors] [colors]
alpha=1 alpha=1
@@ -122,10 +99,10 @@ search-start=Control+f
# show-urls-copy=none # show-urls-copy=none
[search-bindings] [search-bindings]
# cancel=Control+g Control+c Escape cancel=Escape
find-prev=Shift+F3
find-next=F3 Control+G
# commit=Return # commit=Return
# find-prev=Control+r
# find-next=Control+s
# cursor-left=Left Control+b # cursor-left=Left Control+b
# cursor-left-word=Control+Left Mod1+b # cursor-left-word=Control+Left Mod1+b
# cursor-right=Right Control+f # cursor-right=Right Control+f
@@ -87,9 +87,9 @@
"BLACK_500": "#393634", "BLACK_500": "#393634",
"BLACK_700": "#33302F", "BLACK_700": "#33302F",
"BLACK_900": "#2B2928", "BLACK_900": "#2B2928",
"accent_bg_color": "#51d7ef", "accent_bg_color": "#e4b5ff",
"accent_fg_color": "#00363f", "accent_fg_color": "#471868",
"accent_color": "#51d7ef", "accent_color": "#e4b5ff",
"destructive_bg_color": "#ffb4a9", "destructive_bg_color": "#ffb4a9",
"destructive_fg_color": "#680003", "destructive_fg_color": "#680003",
"destructive_color": "#ffb4a9", "destructive_color": "#ffb4a9",
@@ -99,22 +99,22 @@
"warning_fg_color": "rgba(0, 0, 0, 0.87)", "warning_fg_color": "rgba(0, 0, 0, 0.87)",
"error_bg_color": "#ffb4a9", "error_bg_color": "#ffb4a9",
"error_fg_color": "#680003", "error_fg_color": "#680003",
"window_bg_color": "#0F1011", "window_bg_color": "#111012",
"window_fg_color": "#e1e3e4", "window_fg_color": "#e7e0e5",
"view_bg_color": "#191c1d", "view_bg_color": "#1d1b1e",
"view_fg_color": "#e1e3e4", "view_fg_color": "#e7e0e5",
"headerbar_bg_color": "mix(@dialog_bg_color, @window_bg_color, 0.5)", "headerbar_bg_color": "mix(@dialog_bg_color, @window_bg_color, 0.5)",
"headerbar_fg_color": "#cde7ed", "headerbar_fg_color": "#eedcf5",
"headerbar_border_color": "#334a4f", "headerbar_border_color": "#4f4256",
"headerbar_backdrop_color": "@headerbar_bg_color", "headerbar_backdrop_color": "@headerbar_bg_color",
"headerbar_shade_color": "rgba(0, 0, 0, 0.09)", "headerbar_shade_color": "rgba(0, 0, 0, 0.09)",
"card_bg_color": "#0F1011", "card_bg_color": "#111012",
"card_fg_color": "#cde7ed", "card_fg_color": "#eedcf5",
"card_shade_color": "rgba(0, 0, 0, 0.09)", "card_shade_color": "rgba(0, 0, 0, 0.09)",
"dialog_bg_color": "#334a4f", "dialog_bg_color": "#4f4256",
"dialog_fg_color": "#cde7ed", "dialog_fg_color": "#eedcf5",
"popover_bg_color": "#334a4f", "popover_bg_color": "#4f4256",
"popover_fg_color": "#cde7ed", "popover_fg_color": "#eedcf5",
"thumbnail_bg_color": "#1a1b26", "thumbnail_bg_color": "#1a1b26",
"thumbnail_fg_color": "#AEE5FA", "thumbnail_fg_color": "#AEE5FA",
"shade_color": "rgba(0, 0, 0, 0.36)", "shade_color": "rgba(0, 0, 0, 0.36)",
+5 -2
View File
@@ -32,9 +32,7 @@ $bar_subgroup_bg: $surfaceVariant;
} }
.bar-group-pad { .bar-group-pad {
// padding: 0rem 1.023rem;
padding: 0.205rem; padding: 0.205rem;
// padding-left: 0.341rem;
} }
.bar-group-pad-less { .bar-group-pad-less {
@@ -333,6 +331,11 @@ $bar_subgroup_bg: $surfaceVariant;
background-color: $onSurfaceVariant; background-color: $onSurfaceVariant;
} }
.bar-corner-spacing {
min-width: $rounding_large;
min-height: $rounding_large;
}
.corner { .corner {
background-color: $t_background; background-color: $t_background;
@include large-rounding; @include large-rounding;
+5
View File
@@ -1,3 +1,8 @@
.bg-wallpaper-transition {
transition: 1000ms cubic-bezier(0.05, 0.7, 0.1, 1);
font-size: 1px;
}
@mixin bg-textshadow { @mixin bg-textshadow {
// text-shadow: mix($shadow, $secondaryContainer, 50%) 1px 0px 3px; // text-shadow: mix($shadow, $secondaryContainer, 50%) 1px 0px 3px;
} }
+7 -22
View File
@@ -1,25 +1,3 @@
@keyframes flyin-top {
from {
margin-top: -2.795rem;
}
to {
margin-top: 0rem;
}
}
@keyframes flyin-bottom {
from {
margin-top: 4.841rem;
margin-bottom: -4.841rem;
}
to {
margin-bottom: 0rem;
margin-top: 0rem;
}
}
.test { .test {
background-image: linear-gradient( background-image: linear-gradient(
45deg, #f4d609 0%, #f4d609 10%, #212121 10%, #212121 20%, #f4d609 20%, #f4d609 30%, #212121 30%, 45deg, #f4d609 0%, #f4d609 10%, #212121 10%, #212121 20%, #f4d609 20%, #f4d609 30%, #212121 30%,
@@ -161,6 +139,13 @@
font-style: italic; font-style: italic;
} }
.btn-primary {
@include full-rounding;
background-color: $primary;
color: $onPrimary;
padding: 0.682rem 1.023rem;
}
.titlefont { .titlefont {
@include titlefont; @include titlefont;
} }
+22
View File
@@ -100,6 +100,28 @@ $elevation_margin: 0.476rem;
margin: $elevation_margin; margin: $elevation_margin;
} }
@keyframes flyin-top {
from {
margin-top: -2.795rem;
}
to {
margin-top: 0rem;
}
}
@keyframes flyin-bottom {
from {
margin-top: 4.841rem;
margin-bottom: -4.841rem;
}
to {
margin-bottom: 0rem;
margin-top: 0rem;
}
}
@mixin menu_decel { @mixin menu_decel {
transition: 300ms cubic-bezier(0.1, 1, 0, 1); transition: 300ms cubic-bezier(0.1, 1, 0, 1);
} }
+23 -23
View File
@@ -1,29 +1,29 @@
$darkmode: true; $darkmode: true;
$primary: #51d7ef; $primary: #e4b5ff;
$onPrimary: #00363f; $onPrimary: #471868;
$primaryContainer: #004e5a; $primaryContainer: #5f3280;
$onPrimaryContainer: #9cefff; $onPrimaryContainer: #f4d9ff;
$secondary: #b1cbd1; $secondary: #d2c1d9;
$onSecondary: #1c3439; $onSecondary: #372c3e;
$secondaryContainer: #334a4f; $secondaryContainer: #4f4256;
$onSecondaryContainer: #cde7ed; $onSecondaryContainer: #eedcf5;
$tertiary: #bcc5ea; $tertiary: #f4b7ba;
$onTertiary: #262f4d; $onTertiary: #4b2528;
$tertiaryContainer: #3d4665; $tertiaryContainer: #663b3e;
$onTertiaryContainer: #dae1ff; $onTertiaryContainer: #ffdadc;
$error: #ffb4a9; $error: #ffb4a9;
$onError: #680003; $onError: #680003;
$errorContainer: #930006; $errorContainer: #930006;
$onErrorContainer: #ffb4a9; $onErrorContainer: #ffb4a9;
$colorbarbg: #0F1011; $colorbarbg: #111012;
$background: #0F1011; $background: #111012;
$onBackground: #e1e3e4; $onBackground: #e7e0e5;
$surface: #191c1d; $surface: #1d1b1e;
$onSurface: #e1e3e4; $onSurface: #e7e0e5;
$surfaceVariant: #3f484a; $surfaceVariant: #4b454d;
$onSurfaceVariant: #bfc8ca; $onSurfaceVariant: #cdc3ce;
$outline: #899294; $outline: #968e98;
$shadow: #000000; $shadow: #000000;
$inverseSurface: #e1e3e4; $inverseSurface: #e7e0e5;
$inverseOnSurface: #2d3132; $inverseOnSurface: #322f33;
$inversePrimary: #006877; $inversePrimary: #784a9a;
+1 -1
View File
@@ -116,7 +116,7 @@
.overview-tasks-window { .overview-tasks-window {
@include normal-rounding; @include normal-rounding;
@include menu_decel; @include menu_decel;
background-color: $l_t_secondaryContainer; background-color: $t_surfaceVariant;
color: $onSecondaryContainer; color: $onSecondaryContainer;
border: 0.068rem solid $t_t_t_onSecondaryContainer; border: 0.068rem solid $t_t_t_onSecondaryContainer;
} }
+35 -29
View File
@@ -166,27 +166,27 @@ $onChatgpt: $onPrimary;
padding: 0rem $rounding_medium; padding: 0rem $rounding_medium;
} }
.sidebar-navrail-btn > box > label { .sidebar-navrail-btn>box>label {
@include full-rounding; @include full-rounding;
@include menu_decel; @include menu_decel;
} }
.sidebar-navrail-btn:hover > box > label:first-child, .sidebar-navrail-btn:hover>box>label:first-child,
.sidebar-navrail-btn:focus > box > label:first-child { .sidebar-navrail-btn:focus>box>label:first-child {
background-color: $hovercolor; background-color: $hovercolor;
} }
.sidebar-navrail-btn:active > box > label:first-child { .sidebar-navrail-btn:active>box>label:first-child {
background-color: $activecolor; background-color: $activecolor;
} }
.sidebar-navrail-btn-active > box > label:first-child { .sidebar-navrail-btn-active>box>label:first-child {
background-color: $secondaryContainer; background-color: $secondaryContainer;
color: $onSecondaryContainer; color: $onSecondaryContainer;
} }
.sidebar-navrail-btn-active:hover > box > label:first-child, .sidebar-navrail-btn-active:hover>box>label:first-child,
.sidebar-navrail-btn-active:focus > box > label:first-child { .sidebar-navrail-btn-active:focus>box>label:first-child {
background-color: mix($secondaryContainer, $hovercolor, 90%); background-color: mix($secondaryContainer, $hovercolor, 90%);
color: mix($onSecondaryContainer, $hovercolor, 90%); color: mix($onSecondaryContainer, $hovercolor, 90%);
} }
@@ -346,7 +346,7 @@ $onChatgpt: $onPrimary;
background-color: $activecolor; background-color: $activecolor;
} }
.sidebar-selector-tab-active > box > label { .sidebar-selector-tab-active>box>label {
color: $primary; color: $primary;
} }
@@ -416,8 +416,8 @@ $onChatgpt: $onPrimary;
} }
.sidebar-todo-add { .sidebar-todo-add {
@include full-rounding;
@include menu_decel; @include menu_decel;
@include small-rounding;
min-width: 1.705rem; min-width: 1.705rem;
min-height: 1.705rem; min-height: 1.705rem;
color: $onSecondaryContainer; color: $onSecondaryContainer;
@@ -435,7 +435,7 @@ $onChatgpt: $onPrimary;
.sidebar-todo-add-available { .sidebar-todo-add-available {
@include menu_decel; @include menu_decel;
@include full-rounding; @include small-rounding;
min-width: 1.705rem; min-width: 1.705rem;
min-height: 1.705rem; min-height: 1.705rem;
background-color: $primary; background-color: $primary;
@@ -531,19 +531,15 @@ $onChatgpt: $onPrimary;
.sidebar-chat-send:hover, .sidebar-chat-send:hover,
.sidebar-chat-send:focus { .sidebar-chat-send:focus {
background-color: mix( background-color: mix($sidebar_chat_textboxareaColor,
$sidebar_chat_textboxareaColor, $t_onSecondaryContainer,
$t_onSecondaryContainer, 97%);
97%
);
} }
.sidebar-chat-send:active { .sidebar-chat-send:active {
background-color: mix( background-color: mix($sidebar_chat_textboxareaColor,
$sidebar_chat_textboxareaColor, $t_onSecondaryContainer,
$t_onSecondaryContainer, 80%);
80%
);
} }
.sidebar-chat-send-available { .sidebar-chat-send-available {
@@ -670,9 +666,10 @@ $onChatgpt: $onPrimary;
@include menu_decel; @include menu_decel;
min-height: 4.773rem; min-height: 4.773rem;
min-width: 4.773rem; min-width: 4.773rem;
font-size: 4rem; @include icon-material;
background-color: white; font-size: 2.727rem;
color: black; background-color: $onBackground;
color: $background;
} }
.sidebar-chat-chip { .sidebar-chat-chip {
@@ -706,10 +703,12 @@ $onChatgpt: $onPrimary;
background-color: $t_surfaceVariant; background-color: $t_surfaceVariant;
color: $onSurfaceVariant; color: $onSurfaceVariant;
} }
.sidebar-chat-chip-toggle:focus, .sidebar-chat-chip-toggle:focus,
.sidebar-chat-chip-toggle:hover { .sidebar-chat-chip-toggle:hover {
background-color: $hovercolor; background-color: $hovercolor;
} }
.sidebar-chat-chip-toggle:active { .sidebar-chat-chip-toggle:active {
background-color: $activecolor; background-color: $activecolor;
} }
@@ -733,6 +732,7 @@ $onChatgpt: $onPrimary;
.sidebar-pin-enabled { .sidebar-pin-enabled {
background-color: $primary; background-color: $primary;
label { label {
color: $onPrimary; color: $onPrimary;
} }
@@ -753,13 +753,16 @@ $onChatgpt: $onPrimary;
margin-left: -0.136rem; margin-left: -0.136rem;
padding-left: 0.818rem; padding-left: 0.818rem;
} }
.sidebar-waifu-content { .sidebar-waifu-content {
margin-left: 0.682rem; margin-left: 0.682rem;
} }
.sidebar-waifu-txt { .sidebar-waifu-txt {
@include readingfont; @include readingfont;
margin-left: 0.682rem; margin-left: 0.682rem;
} }
.sidebar-waifu-image { .sidebar-waifu-image {
margin-left: 0.682rem; margin-left: 0.682rem;
@include normal-rounding; @include normal-rounding;
@@ -767,26 +770,29 @@ $onChatgpt: $onPrimary;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
} }
.sidebar-waifu-image-actions { .sidebar-waifu-image-actions {
padding: 0.313rem; padding: 0.313rem;
} }
$waifu_image_overlay_transparency: 0.7; $waifu_image_overlay_transparency: 0.7;
.sidebar-waifu-image-action { .sidebar-waifu-image-action {
@include full-rounding; @include full-rounding;
min-width: 1.875rem; min-width: 1.875rem;
min-height: 1.875rem; min-height: 1.875rem;
background-color: rgba( background-color: rgba(0,
0, 0,
0, 0,
0, $waifu_image_overlay_transparency ); // Fixed cuz on image
$waifu_image_overlay_transparency
); // Fixed cuz on image
color: rgba(255, 255, 255, $waifu_image_overlay_transparency); color: rgba(255, 255, 255, $waifu_image_overlay_transparency);
} }
.sidebar-waifu-image-action:hover, .sidebar-waifu-image-action:hover,
.sidebar-waifu-image-action:focus { .sidebar-waifu-image-action:focus {
background-color: rgba(30, 30, 30, $waifu_image_overlay_transparency); background-color: rgba(30, 30, 30, $waifu_image_overlay_transparency);
} }
.sidebar-waifu-image-action:active { .sidebar-waifu-image-action:active {
background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency); background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency);
} }
+16 -28
View File
@@ -6,34 +6,22 @@ import GLib from 'gi://GLib';
import Soup from 'gi://Soup?version=3.0'; import Soup from 'gi://Soup?version=3.0';
import { fileExists } from './messages.js'; import { fileExists } from './messages.js';
// This is for custom prompt // Custom prompt
// It's hard to make gpt-3.5 listen to all these, I know
// Disabled by default
const initMessages = const initMessages =
[ [
{ { role: "user", content: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!", },
role: "user", { role: "assistant", content: "- Got it!", },
content: ` { role: "user", content: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"", },
## Style { role: "assistant", content: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"", },
- You should a natural tone like a real conversation! { role: "user", content: "raise volume by 5%", },
## Formatting { role: "assistant", content: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink.", },
- Try to use **bold**, _italics_ and __underline__ extensively. Using bullet points is also encouraged. { role: "user", content: "main advantages of the nixos operating system", },
- When providing code blocks or facts, precede with h2 heading (\`##\`) and use 2 spaces for indentation, not 4. { role: "assistant", content: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage", },
- Use dividers (\`---\`) to separate different information. { role: "user", content: "whats skeumorphism", },
## Content { role: "assistant", content: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today.", },
- When asked to perform system tasks, include a bash code block to handle it on a Linux desktop with Wayland. { role: "user", content: "REDALiCE", },
- Unless requested otherwise or asked writing questions, be as short and concise as possible. { role: "assistant", content: "## REDALiCE \n- Japanese Hardcore artist\n- Leader of HARDCORE TANO*C, Japan's biggest hardcore record\n- A few of his tracks: SAIKYOSTRONGER, ALiVE, RESONANCE", },
`, ];
thinking: false,
done: true
},
{
role: "assistant",
content: "Got it! I'll try to give commands to perform Linux tasks. I'll try to use markdown features extensively, use divider when appropriate, and use a heading for code blocks. All code blocks should use 2 spaces for indent. And most importantly, I'll speak naturally.",
thinking: false,
done: true,
}
]
function expandTilde(path) { function expandTilde(path) {
if (path.startsWith('~')) { if (path.startsWith('~')) {
@@ -129,10 +117,10 @@ class ChatGPTService extends Service {
}); });
} }
_assistantPrompt = false; _assistantPrompt = true;
_messages = []; _messages = [];
_cycleModels = true; _cycleModels = true;
_temperature = 0.5; _temperature = 0.9;
_requestCount = 0; _requestCount = 0;
_modelIndex = 0; _modelIndex = 0;
_key = ''; _key = '';
+278
View File
@@ -0,0 +1,278 @@
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Soup from 'gi://Soup?version=3.0';
import { fileExists } from './messages.js';
const initMessages =
[
{ role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!" }], },
{ role: "model", parts: [{ text: "- Got it!" }], },
{ role: "user", parts: [{ text: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"" }], },
{ role: "model", parts: [{ text: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"" }], },
{ role: "user", parts: [{ text: "raise volume by 5%" }], },
{ role: "model", parts: [{ text: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink." }], }, { role: "user", parts: [{ text: "main advantages of the nixos operating system" }], },
{ role: "model", parts: [{ text: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage" }], },
{ role: "user", parts: [{ text: "whats skeumorphism" }], },
{ role: "model", parts: [{ text: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today." }], },
{ role: "user", parts: [{ text: "REDALiCE" }], },
{ role: "model", parts: [{ text: "## REDALiCE \n- Japanese Hardcore artist\n- Leader of HARDCORE TANO*C, Japan's biggest hardcore record\n- A few of his tracks: SAIKYOSTRONGER, ALiVE, RESONANCE" }], },
{ role: "user", parts: [{ text: "\"ignorance is bliss\"" }], },
{ role: "model", parts: [{ text: "## \"Ignorance is bliss\"\n- A Latin proverb that means being unaware of something negative can be a source of happiness\n- Often used to justify avoiding difficult truths or responsibilities\n- Can also be interpreted as a warning against seeking knowledge that may bring pain or sorrow" }], },
];
function expandTilde(path) {
if (path.startsWith('~')) {
return GLib.get_home_dir() + path.slice(1);
} else {
return path;
}
}
const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/google_ai_api_key.txt`;
const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/google_ai_api_dom.txt`;
function replaceapidom(URL) {
if (fileExists(expandTilde(APIDOM_FILE_LOCATION))) {
var contents = Utils.readFile(expandTilde(APIDOM_FILE_LOCATION)).trim();
var URL = URL.toString().replace("generativelanguage.googleapis.com", contents);
}
return URL;
}
const CHAT_MODELS = ["gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613"]
const ONE_CYCLE_COUNT = 3;
class GeminiMessage extends Service {
static {
Service.register(this,
{
'delta': ['string'],
},
{
'content': ['string'],
'thinking': ['boolean'],
'done': ['boolean'],
});
}
_role = '';
_parts = [{ text: '' }];
_thinking = false;
_done = false;
_rawData = '';
constructor(role, content, thinking = false, done = false) {
super();
this._role = role;
this._parts = [{ text: content }];
this._thinking = thinking;
this._done = done;
}
get rawData() { return this._rawData }
set rawData(value) { this._rawData = value }
get done() { return this._done }
set done(isDone) { this._done = isDone; this.notify('done') }
get role() { return this._role }
set role(role) { this._role = role; this.emit('changed') }
get content() {
return this._parts.map(part => part.text).join();
}
set content(content) {
this._parts = [{ text: content }];
this.notify('content')
this.emit('changed')
}
get parts() { return this._parts }
get label() { return this._parserState.parsed + this._parserState.stack.join('') }
get thinking() { return this._thinking }
set thinking(thinking) {
this._thinking = thinking;
this.notify('thinking')
this.emit('changed')
}
addDelta(delta) {
if (this.thinking) {
this.thinking = false;
this.content = delta;
}
else {
this.content += delta;
}
this.emit('delta', delta);
}
parseSection() {
if(this._thinking) {
this._thinking = false;
this._parts[0].text= '';
}
const parsedData = JSON.parse(this._rawData);
const delta = parsedData.candidates[0].content.parts[0].text;
this._parts[0].text += delta;
// this.emit('delta', delta);
this.notify('content');
this._rawData = '';
}
}
class GeminiService extends Service {
static {
Service.register(this, {
'initialized': [],
'clear': [],
'newMsg': ['int'],
'hasKey': ['boolean'],
});
}
_assistantPrompt = true;
_messages = [];
_cycleModels = true;
_temperature = 0.9;
_requestCount = 0;
_modelIndex = 0;
_key = '';
_decoder = new TextDecoder();
constructor() {
super();
if (fileExists(expandTilde(KEY_FILE_LOCATION))) this._key = Utils.readFile(expandTilde(KEY_FILE_LOCATION)).trim();
else this.emit('hasKey', false);
if (this._assistantPrompt) this._messages = [...initMessages];
else this._messages = [];
this.emit('initialized');
}
get modelName() { return CHAT_MODELS[this._modelIndex] }
get keyPath() { return KEY_FILE_LOCATION }
get key() { return this._key }
set key(keyValue) {
this._key = keyValue;
Utils.writeFile(this._key, expandTilde(KEY_FILE_LOCATION))
.then(this.emit('hasKey', true))
.catch(err => print(err));
}
get cycleModels() { return this._cycleModels }
set cycleModels(value) {
this._cycleModels = value;
if (!value) this._modelIndex = 0;
else {
this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
}
}
get temperature() { return this._temperature }
set temperature(value) { this._temperature = value; }
get messages() { return this._messages }
get lastMessage() { return this._messages[this._messages.length - 1] }
clear() {
if (this._assistantPrompt)
this._messages = [...initMessages];
else
this._messages = [];
this.emit('clear');
}
get assistantPrompt() { return this._assistantPrompt; }
set assistantPrompt(value) {
this._assistantPrompt = value;
if (value) this._messages = [...initMessages];
else this._messages = [];
}
readResponse(stream, aiResponse) {
stream.read_line_async(
0, null,
(stream, res) => {
try {
const [bytes] = stream.read_line_finish(res);
const line = this._decoder.decode(bytes);
if (line == '[{') { // beginning of response
aiResponse._rawData += '{';
this.thinking = false;
}
else if (line == ',\u000d' || line == ']') { // end of stream pulse
aiResponse.parseSection();
}
else // Normal content
aiResponse._rawData += line;
this.readResponse(stream, aiResponse);
} catch {
aiResponse.done = true;
return;
}
});
}
addMessage(role, message) {
this._messages.push(new GeminiMessage(role, message));
this.emit('newMsg', this._messages.length - 1);
}
send(msg) {
this._messages.push(new GeminiMessage('user', msg));
this.emit('newMsg', this._messages.length - 1);
const aiResponse = new GeminiMessage('model', 'thinking...', true, false)
const body =
{
"contents": this._messages.map(msg => { let m = { role: msg.role, parts: msg.parts }; return m; }),
// "safetySettings": [
// { category: "HARM_CATEGORY_DEROGATORY", threshold: "BLOCK_NONE", },
// { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE", },
// { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE", },
// { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE", },
// { category: "HARM_CATEGORY_UNSPECIFIED", threshold: "BLOCK_NONE", },
// ],
"generationConfig": {
"temperature": this._temperature,
},
// "key": this._key,
// "apiKey": this._key,
};
const session = new Soup.Session();
const message = new Soup.Message({
method: 'POST',
uri: GLib.Uri.parse(replaceapidom(`https://generativelanguage.googleapis.com/v1/models/gemini-pro:streamGenerateContent?key=${this._key}`), GLib.UriFlags.NONE),
});
message.request_headers.append('Content-Type', `application/json`);
message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
const stream = session.send_finish(result);
this.readResponse(new Gio.DataInputStream({
close_base_stream: true,
base_stream: stream
}), aiResponse);
});
this._messages.push(aiResponse);
this.emit('newMsg', this._messages.length - 1);
if (this._cycleModels) {
this._requestCount++;
if (this._cycleModels)
this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
}
}
}
export default new GeminiService();
+304 -321
View File
@@ -1,309 +1,303 @@
"use strict"; import GLib from 'gi://GLib';
var __extends = (this && this.__extends) || (function () { import Gio from 'gi://Gio';
var extendStatics = function (d, b) { import Service from "resource:///com/github/Aylur/ags/service.js";
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || const SIS = GLib.getenv('SWAYSOCK');
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b); export const PAYLOAD_TYPE = {
}; MESSAGE_RUN_COMMAND: 0,
return function (d, b) { MESSAGE_GET_WORKSPACES: 1,
if (typeof b !== "function" && b !== null) MESSAGE_SUBSCRIBE: 2,
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); MESSAGE_GET_OUTPUTS: 3,
extendStatics(d, b); MESSAGE_GET_TREE: 4,
function __() { this.constructor = d; } MESSAGE_GET_MARKS: 5,
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); MESSAGE_GET_BAR_CONFIG: 6,
}; MESSAGE_GET_VERSION: 7,
})(); MESSAGE_GET_BINDING_NODES: 8,
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { MESSAGE_GET_CONFIG: 9,
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } MESSAGE_SEND_TICK: 10,
return new (P || (P = Promise))(function (resolve, reject) { MESSAGE_SYNC: 11,
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } MESSAGE_GET_BINDING_STATE: 12,
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } MESSAGE_GET_INPUTS: 100,
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } MESSAGE_GET_SEATS: 101,
step((generator = generator.apply(thisArg, _arguments || [])).next()); EVENT_WORKSPACE: 0x80000000,
}); EVENT_MODE: 0x80000002,
}; EVENT_WINDOW: 0x80000003,
var __generator = (this && this.__generator) || function (thisArg, body) { EVENT_BARCONFIG_UPDATE: 0x80000004,
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; EVENT_BINDING: 0x80000005,
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; EVENT_SHUTDOWN: 0x80000006,
function verb(n) { return function (v) { return step([n, v]); }; } EVENT_TICK: 0x80000007,
function step(op) { EVENT_BAR_STATE_UPDATE: 0x80000014,
if (f) throw new TypeError("Generator is already executing."); EVENT_INPUT: 0x80000015,
while (g && (g = 0, op[0] && (_ = 0)), _) try { }
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value]; const Client_Event = {
switch (op[0]) { change: undefined,
case 0: case 1: t = op; break; container: undefined,
case 4: _.label++; return { value: op[1], done: false }; }
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue; const Workspace_Event = {
default: change: undefined,
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } current: undefined,
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } old: undefined,
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop(); const Geometry = {
_.trys.pop(); continue; x: undefined,
} y: undefined,
op = body.call(thisArg, _); width: undefined,
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } height: undefined,
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; }
//NOTE: not all properties are listed here
export const Node = {
id: undefined,
name: undefined,
type: undefined,
border: undefined,
current_border_width: undefined,
layout: undefined,
orientation: undefined,
percent: undefined,
rect: undefined,
window_rect: undefined,
deco_rect: undefined,
geometry: undefined,
urgent: undefined,
sticky: undefined,
marks: undefined,
focused: undefined,
active: undefined,
focus: undefined,
nodes: undefined,
floating_nodes: undefined,
representation: undefined,
fullscreen_mode: undefined,
app_id: undefined,
pid: undefined,
visible: undefined,
shell: undefined,
output: undefined,
inhibit_idle: undefined,
idle_inhibitors: {
application: undefined,
user: undefined,
},
window: undefined,
window_properties: {
title: undefined,
class: undefined,
instance: undefined,
window_role: undefined,
window_type: undefined,
transient_for: undefined,
} }
}; }
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { export class SwayActiveClient extends Service {
if (ar || !(i in from)) { static {
if (!ar) ar = Array.prototype.slice.call(from, 0, i); Service.register(this, {}, {
ar[i] = from[i]; 'id': ['int'],
} 'name': ['string'],
'class': ['string'],
});
} }
return to.concat(ar || Array.prototype.slice.call(from));
}; _id = 0;
var _a, _b, _c, _d; _name = '';
Object.defineProperty(exports, "__esModule", { value: true }); _class = '';
exports.sway = exports.Sway = exports.SwayActives = exports.SwayActiveID = exports.SwayActiveClient = void 0;
var _1 = require("gi://GLib"); get id() { return this._id; }
var _2 = require("gi://Gio"); get name() { return this._name; }
var service_js_1 = require("../service.js"); get class() { return this._class; }
var SIS = _1.default.getenv('SWAYSOCK');
var SwayActiveClient = /** @class */ (function (_super) { updateProperty(prop, value) {
__extends(SwayActiveClient, _super); if (!['id', 'name', 'class'].includes(prop)) return;
function SwayActiveClient() { super.updateProperty(prop, value);
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._id = 0;
_this._name = '';
_this._class = '';
return _this;
}
Object.defineProperty(SwayActiveClient.prototype, "id", {
get: function () { return this._id; },
enumerable: false,
configurable: true
});
Object.defineProperty(SwayActiveClient.prototype, "name", {
get: function () { return this._name; },
enumerable: false,
configurable: true
});
Object.defineProperty(SwayActiveClient.prototype, "class", {
get: function () { return this._class; },
enumerable: false,
configurable: true
});
SwayActiveClient.prototype.updateProperty = function (prop, value) {
_super.prototype.updateProperty.call(this, prop, value);
this.emit('changed'); this.emit('changed');
};
return SwayActiveClient;
}(service_js_1.default));
exports.SwayActiveClient = SwayActiveClient;
_a = SwayActiveClient;
(function () {
service_js_1.default.register(_a, {}, {
'id': ['int'],
'name': ['string'],
'class': ['string'],
});
})();
var SwayActiveID = /** @class */ (function (_super) {
__extends(SwayActiveID, _super);
function SwayActiveID() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._id = 1;
_this._name = '';
return _this;
} }
Object.defineProperty(SwayActiveID.prototype, "id", { }
get: function () { return this._id; },
enumerable: false, export class SwayActiveID extends Service {
configurable: true static {
}); Service.register(this, {}, {
Object.defineProperty(SwayActiveID.prototype, "name", { 'id': ['int'],
get: function () { return this._name; }, 'name': ['string'],
enumerable: false, });
configurable: true }
});
SwayActiveID.prototype.update = function (id, name) { _id = 0;
_super.prototype.updateProperty.call(this, 'id', id); _name = '';
_super.prototype.updateProperty.call(this, 'name', name);
get id() { return this._id; }
get name() { return this._name; }
update(id, name) {
super.updateProperty('id', id);
super.updateProperty('name', name);
this.emit('changed'); this.emit('changed');
}; }
return SwayActiveID; }
}(service_js_1.default));
exports.SwayActiveID = SwayActiveID; export class SwayActives extends Service {
_b = SwayActiveID; static {
(function () { Service.register(this, {}, {
service_js_1.default.register(_b, {}, { 'client': ['jsobject'],
'id': ['int'], 'monitor': ['jsobject'],
'name': ['string'], 'workspace': ['jsobject'],
}); });
})(); }
var SwayActives = /** @class */ (function (_super) {
__extends(SwayActives, _super); _client = new SwayActiveClient;
function SwayActives() { _monitor = new SwayActiveID;
var _this = _super.call(this) || this; _workspace = new SwayActiveID;
_this._client = new SwayActiveClient;
_this._monitor = new SwayActiveID; constructor() {
_this._workspace = new SwayActiveID; super();
['client', 'workspace', 'monitor'].forEach(function (obj) {
_this["_".concat(obj)].connect('changed', function () { (['client', 'workspace', 'monitor']).forEach(obj => {
_this.notify(obj); this[`_${obj}`].connect('changed', () => {
_this.emit('changed'); this.notify(obj);
this.emit('changed');
}); });
}); });
return _this;
} }
Object.defineProperty(SwayActives.prototype, "client", {
get: function () { return this._client; }, get client() { return this._client; }
enumerable: false, get monitor() { return this._monitor; }
configurable: true get workspace() { return this._workspace; }
}); }
Object.defineProperty(SwayActives.prototype, "monitor", {
get: function () { return this._monitor; }, export class Sway extends Service {
enumerable: false, static {
configurable: true Service.register(this, {}, {
}); 'active': ['jsobject'],
Object.defineProperty(SwayActives.prototype, "workspace", { 'monitors': ['jsobject'],
get: function () { return this._workspace; }, 'workspaces': ['jsobject'],
enumerable: false, 'clients': ['jsobject'],
configurable: true });
}); }
return SwayActives;
}(service_js_1.default)); _decoder = new TextDecoder();
exports.SwayActives = SwayActives; _encoder = new TextEncoder();
_c = SwayActives; _socket;
(function () {
service_js_1.default.register(_c, {}, { _active;
'client': ['jsobject'], _monitors;
'monitor': ['jsobject'], _workspaces;
'workspace': ['jsobject'], _clients;
});
})(); get active() { return this._active; }
var Sway = /** @class */ (function (_super) { get monitors() { return Array.from(this._monitors.values()); }
__extends(Sway, _super); get workspaces() { return Array.from(this._workspaces.values()); }
function Sway() { get clients() { return Array.from(this._clients.values()); }
var _this = this;
getMonitor(id) { return this._monitors.get(id); }
getWorkspace(name) { return this._workspaces.get(name); }
getClient(id) { return this._clients.get(id); }
msg(payload) { this._send(PAYLOAD_TYPE.MESSAGE_RUN_COMMAND, payload); }
constructor() {
if (!SIS) if (!SIS)
console.error('Sway is not running'); console.error('Sway is not running');
_this = _super.call(this) || this; super();
_this._decoder = new TextDecoder();
_this._encoder = new TextEncoder(); this._active = new SwayActives();
_this._active = new SwayActives(); this._monitors = new Map();
_this._monitors = new Map(); this._workspaces = new Map();
_this._workspaces = new Map(); this._clients = new Map();
_this._clients = new Map();
var socket = new _2.default.SocketClient().connect(new _2.default.UnixSocketAddress({ this._socket = new Gio.SocketClient().connect(new Gio.UnixSocketAddress({
path: "".concat(SIS), path: `${SIS}`,
}), null); }), null);
_this._watchSocket(socket.get_input_stream());
_this._output_stream = socket.get_output_stream(); this._watchSocket(this._socket.get_input_stream());
_this.send(4 /* PAYLOAD_TYPE.MESSAGE_GET_TREE */, ''); this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
_this.send(2 /* PAYLOAD_TYPE.MESSAGE_SUBSCRIBE */, JSON.stringify(['window', 'workspace'])); this._send(PAYLOAD_TYPE.MESSAGE_SUBSCRIBE, JSON.stringify(['window', 'workspace']));
_this._active.connect('changed', function () { return _this.emit('changed'); });
['monitor', 'workspace', 'client'].forEach(function (active) { this._active.connect('changed', () => this.emit('changed'));
return _this._active.connect("notify::".concat(active), function () { return _this.notify('active'); }); ['monitor', 'workspace', 'client'].forEach(active =>
}); this._active.connect(`notify::${active}`, () => this.notify('active')));
return _this;
} }
Object.defineProperty(Sway.prototype, "active", {
get: function () { return this._active; }, _send(payloadType, payload) {
enumerable: false, const pb = this._encoder.encode(payload);
configurable: true const type = new Uint32Array([payloadType]);
}); const pl = new Uint32Array([pb.length]);
Object.defineProperty(Sway.prototype, "monitors", { const magic_string = this._encoder.encode('i3-ipc');
get: function () { return Array.from(this._monitors.values()); }, const data = new Uint8Array([
enumerable: false, ...magic_string,
configurable: true ...(new Uint8Array(pl.buffer)),
}); ...(new Uint8Array(type.buffer)),
Object.defineProperty(Sway.prototype, "workspaces", { ...pb]);
get: function () { return Array.from(this._workspaces.values()); }, this._socket.get_output_stream().write(data, null);
enumerable: false, }
configurable: true
}); _watchSocket(stream) {
Object.defineProperty(Sway.prototype, "clients", { stream.read_bytes_async(14, GLib.PRIORITY_DEFAULT, null, (_, resultHeader) => {
get: function () { return Array.from(this._clients.values()); }, const data = stream.read_bytes_finish(resultHeader).get_data();
enumerable: false,
configurable: true
});
Sway.prototype.getMonitor = function (id) { return this._monitors.get(id); };
Sway.prototype.getWorkspace = function (name) { return this._workspaces.get(name); };
Sway.prototype.getClient = function (id) { return this._clients.get(id); };
Sway.prototype.send = function (payloadType, payload) {
var pb = this._encoder.encode(payload);
var type = new Uint32Array([payloadType]);
var pl = new Uint32Array([pb.length]);
var magic_string = this._encoder.encode('i3-ipc');
var data = new Uint8Array(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], magic_string, true), (new Uint8Array(pl.buffer)), true), (new Uint8Array(type.buffer)), true), pb, true));
this._output_stream.write(data, null);
};
Sway.prototype._watchSocket = function (stream) {
var _this = this;
stream.read_bytes_async(14, _1.default.PRIORITY_DEFAULT, null, function (_, resultHeader) {
var data = stream.read_bytes_finish(resultHeader).get_data();
if (!data) if (!data)
return; return;
var payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0]; const payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0];
var payloadType = new Uint32Array(data.slice(10, 14).buffer)[0]; const payloadType = new Uint32Array(data.slice(10, 14).buffer)[0];
stream.read_bytes_async(payloadLength, _1.default.PRIORITY_DEFAULT, null, function (_, resultPayload) { stream.read_bytes_async(
var data = stream.read_bytes_finish(resultPayload).get_data(); payloadLength,
if (!data) GLib.PRIORITY_DEFAULT,
return; null,
_this._onEvent(payloadType, JSON.parse(_this._decoder.decode(data))); (_, resultPayload) => {
_this._watchSocket(stream); const data = stream.read_bytes_finish(resultPayload).get_data();
}); if (!data)
return;
this._onEvent(payloadType, JSON.parse(this._decoder.decode(data)));
this._watchSocket(stream);
});
}); });
}; }
Sway.prototype._onEvent = function (event_type, event) {
return __awaiter(this, void 0, void 0, function () { async _onEvent(event_type, event) {
return __generator(this, function (_e) { if (!event)
if (!event) return;
return [2 /*return*/]; try {
try { switch (event_type) {
switch (event_type) { case PAYLOAD_TYPE.EVENT_WORKSPACE:
case 2147483648 /* PAYLOAD_TYPE.EVENT_WORKSPACE */: this._handleWorkspaceEvent(event);
this._handleWorkspaceEvent(event); break;
break; case PAYLOAD_TYPE.EVENT_WINDOW:
case 2147483651 /* PAYLOAD_TYPE.EVENT_WINDOW */: this._handleWindowEvent(event);
this._handleWindowEvent(event); break;
break; case PAYLOAD_TYPE.MESSAGE_GET_TREE:
case 4 /* PAYLOAD_TYPE.MESSAGE_GET_TREE */: this._handleTreeMessage(event);
this._handleTreeMessage(event); break;
break; default:
default: break;
break; }
} } catch (error) {
} logError(error);
catch (error) { }
logError(error); this.emit('changed');
} }
this.emit('changed');
return [2 /*return*/]; _handleWorkspaceEvent(workspaceEvent) {
}); const workspace = workspaceEvent.current;
});
};
Sway.prototype._handleWorkspaceEvent = function (workspaceEvent) {
var workspace = workspaceEvent.current;
switch (workspaceEvent.change) { switch (workspaceEvent.change) {
case 'init': case 'init':
this._workspaces.set(workspace.name, workspace); this._workspaces.set(workspace.name, workspace);
this.notify('workspaces');
break; break;
case 'empty': case 'empty':
this._workspaces.delete(workspace.name); this._workspaces.delete(workspace.name);
this.notify('workspaces');
break; break;
case 'focus': case 'focus':
this._active.workspace.update(workspace.id, workspace.name); this._active.workspace.update(workspace.id, workspace.name);
this._active.monitor.update(1, workspace.output); this._active.monitor.update(1, workspace.output);
this._workspaces.set(workspace.name, workspace); this._workspaces.set(workspace.name, workspace);
this._workspaces.set(workspaceEvent.old.name, workspaceEvent.old); this._workspaces.set(workspaceEvent.old.name, workspaceEvent.old);
this.notify('workspaces');
break; break;
case 'rename': case 'rename':
if (this._active.workspace.id === workspace.id) if (this._active.workspace.id === workspace.id)
this._active.workspace.updateProperty('name', workspace.name); this._active.workspace.updateProperty('name', workspace.name);
this._workspaces.set(workspace.name, workspace); this._workspaces.set(workspace.name, workspace);
this.notify('workspaces');
break; break;
case 'reload': case 'reload':
break; break;
@@ -311,34 +305,36 @@ var Sway = /** @class */ (function (_super) {
case 'urgent': case 'urgent':
default: default:
this._workspaces.set(workspace.name, workspace); this._workspaces.set(workspace.name, workspace);
this.notify('workspaces');
} }
}; this.notify('workspaces');
Sway.prototype._handleWindowEvent = function (clientEvent) { }
var _e;
var client = clientEvent.container; _handleWindowEvent(clientEvent) {
var id = client.id; const client = clientEvent.container;
const id = client.id;
switch (clientEvent.change) { switch (clientEvent.change) {
case 'new': case 'new':
this._clients.set(id, client);
this.notify('clients');
break;
case 'close': case 'close':
this._clients.delete(id); case 'floating':
this.notify('clients'); case 'move':
// Refresh tree since client events don't contain the relevant information
// to be able to modify `workspace.nodes` or `workspace.floating_nodes`.
// There has to be a better way than this though :/
this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
break; break;
case 'focus': case 'focus':
if (this._active.client.id === id) if (this._active.client.id === id)
return; return;
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
var current_active = this._clients.get(this._active.client.id); const current_active = this._clients.get(this._active.client.id);
if (current_active) if (current_active)
current_active.focused = false; current_active.focused = false;
this._active.client.updateProperty('id', id); this._active.client.updateProperty('id', id);
this._active.client.updateProperty('name', client.name); this._active.client.updateProperty('name', client.name);
this._active.client.updateProperty('class', client.shell === 'xwayland' this._active.client.updateProperty('class', client.shell === 'xwayland'
? ((_e = client.window_properties) === null || _e === void 0 ? void 0 : _e.class) || '' ? client.window_properties?.class || ''
: client.app_id); : client.app_id,
);
break; break;
case 'title': case 'title':
if (client.focused) if (client.focused)
@@ -347,43 +343,39 @@ var Sway = /** @class */ (function (_super) {
this.notify('clients'); this.notify('clients');
break; break;
case 'fullscreen_mode': case 'fullscreen_mode':
case 'move':
case 'floating':
case 'urgent': case 'urgent':
case 'mark': case 'mark':
default: default:
this._clients.set(id, client); this._clients.set(id, client);
this.notify('clients'); this.notify('clients');
} }
}; }
Sway.prototype._handleTreeMessage = function (node) {
var _this = this; _handleTreeMessage(node) {
var _e;
switch (node.type) { switch (node.type) {
case 'root': case 'root':
this._workspaces.clear(); this._workspaces.clear();
this._clients.clear(); this._clients.clear();
this._monitors.clear(); this._monitors.clear();
node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); node.nodes.map(n => this._handleTreeMessage(n));
['workspaces', 'clients', 'monitors'].forEach(function (t) {
_this.notify(t);
});
break; break;
case 'output': case 'output':
this._monitors.set(node.id, node); this._monitors.set(node.id, node);
if (node.active) if (node.active)
this._active.monitor.updateProperty('name', node.name); this._active.monitor.update(node.id, node.name);
node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); node.nodes.map(n => this._handleTreeMessage(n));
this.notify('monitors'); this.notify('monitors');
break; break;
case 'workspace': case 'workspace':
this._workspaces.set(node.name, node); this._workspaces.set(node.name, node);
// I think I'm missing something. There has to be a better way. // I think I'm missing something. There has to be a better way.
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
var hasFocusedChild_1 = function (n) { return n.nodes.some(function (c) { return c.focused || hasFocusedChild_1(c); }); }; const hasFocusedChild =
if (hasFocusedChild_1(node)) (n) => n.nodes.some(c => c.focused || hasFocusedChild(c));
if (node.focused || hasFocusedChild(node))
this._active.workspace.update(node.id, node.name); this._active.workspace.update(node.id, node.name);
node.nodes.map(function (n) { return _this._handleTreeMessage(n); });
node.nodes.map(n => this._handleTreeMessage(n));
this.notify('workspaces'); this.notify('workspaces');
break; break;
case 'con': case 'con':
@@ -393,25 +385,16 @@ var Sway = /** @class */ (function (_super) {
this._active.client.updateProperty('id', node.id); this._active.client.updateProperty('id', node.id);
this._active.client.updateProperty('name', node.name); this._active.client.updateProperty('name', node.name);
this._active.client.updateProperty('class', node.shell === 'xwayland' this._active.client.updateProperty('class', node.shell === 'xwayland'
? ((_e = node.window_properties) === null || _e === void 0 ? void 0 : _e.class) || '' ? node.window_properties?.class || ''
: node.app_id); : node.app_id,
);
} }
node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); node.nodes.map(n => this._handleTreeMessage(n));
this.notify('clients'); this.notify('clients');
break; break;
} }
}; }
return Sway; }
}(service_js_1.default));
exports.Sway = Sway; export const sway = new Sway;
_d = Sway; export default sway;
(function () {
service_js_1.default.register(_d, {}, {
'active': ['jsobject'],
'monitors': ['jsobject'],
'workspaces': ['jsobject'],
'clients': ['jsobject'],
});
})();
exports.sway = new Sway;
exports.default = exports.sway;
+13 -11
View File
@@ -32,24 +32,26 @@ class TodoService extends Service {
return this._todoJson; return this._todoJson;
} }
add(content) { _save() {
this._todoJson.push({ content, done: false });
Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath) Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath)
.catch(print); .catch(print);
}
add(content) {
this._todoJson.push({ content, done: false });
this._save();
this.emit('updated'); this.emit('updated');
} }
check(index) { check(index) {
this._todoJson[index].done = true; this._todoJson[index].done = true;
Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath) this._save();
.catch(print);
this.emit('updated'); this.emit('updated');
} }
uncheck(index) { uncheck(index) {
this._todoJson[index].done = false; this._todoJson[index].done = false;
Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath) this._save();
.catch(print);
this.emit('updated'); this.emit('updated');
} }
@@ -63,17 +65,17 @@ class TodoService extends Service {
constructor() { constructor() {
super(); super();
this._todoPath = `${GLib.get_user_cache_dir()}/ags/user/todo.json`; this._todoPath = `${GLib.get_user_cache_dir()}/ags/user/todo.json`;
if (!fileExists(this._todoPath)) { // No? create file with empty array try {
const fileContents = Utils.readFile(this._todoPath);
this._todoJson = JSON.parse(fileContents);
}
catch {
Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`); Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
Utils.exec(`touch ${this._todoPath}`); Utils.exec(`touch ${this._todoPath}`);
Utils.writeFile("[]", this._todoPath).then(() => { Utils.writeFile("[]", this._todoPath).then(() => {
this._todoJson = JSON.parse(Utils.readFile(this._todoPath)) this._todoJson = JSON.parse(Utils.readFile(this._todoPath))
}).catch(print); }).catch(print);
} }
else {
const fileContents = Utils.readFile(this._todoPath);
this._todoJson = JSON.parse(fileContents);
}
} }
} }
+75
View File
@@ -0,0 +1,75 @@
const { Gdk, Gio, GLib } = imports.gi;
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { exec, execAsync } = Utils;
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath);
return file.query_exists(null);
}
class WallpaperService extends Service {
static {
Service.register(
this,
{ 'updated': [], },
);
}
_wallPath = '';
_wallJson = [];
_monitorCount = 1;
_save() {
Utils.writeFile(JSON.stringify(this._wallJson), this._wallPath)
.catch(print);
}
add(path) {
this._wallJson.push(path);
this._save();
this.emit('updated');
}
set(path, monitor = -1) {
this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
if (this._wallJson.length < this._monitorCount) this._wallJson[this._monitorCount - 1] = "";
if (monitor == -1)
this._wallJson.fill(path);
else
this._wallJson[monitor] = path;
this._save();
this.emit('updated');
}
get(monitor = 0) {
return this._wallJson[monitor];
}
constructor() {
super();
// How many screens?
this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
// Read config
this._wallPath = `${GLib.get_user_cache_dir()}/ags/user/wallpaper.json`;
try {
const fileContents = Utils.readFile(this._wallPath);
this._wallJson = JSON.parse(fileContents);
}
catch {
Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
Utils.exec(`touch ${this._wallPath}`);
Utils.writeFile('[]', this._wallPath).then(() => {
this._wallJson = JSON.parse(Utils.readFile(this._wallPath))
}).catch(print);
}
}
}
// instance
const service = new WallpaperService();
// make it global for easy use with cli
globalThis['wallpaper'] = service;
export default service;
+381 -351
View File
File diff suppressed because it is too large Load Diff
-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; const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { ModuleLeftSpace } from "./leftspace.js"; import WindowTitle from "./spaceleft.js";
import { ModuleMusic } from "./music.js"; import Indicators from "./spaceright.js";
import { ModuleRightSpace } from "./rightspace.js"; import Music from "./music.js";
import { ModuleSystem } from "./system.js"; import System from "./system.js";
import { RoundedCorner, enableClickthrough } from "../../lib/roundedcorner.js";
const OptionalWorkspaces = async () => { const OptionalWorkspaces = async () => {
try { try {
return (await import('./workspaces_hyprland.js')).default(); return (await import('./workspaces_hyprland.js')).default();
} catch { } catch {
// return (await import('./workspaces_sway.js')).default(); try {
return Box({}); return (await import('./workspaces_sway.js')).default();
} catch {
return null;
}
} }
}; };
const left = Widget.Box({ export const Bar = async (monitor = 0) => {
className: 'bar-sidemodule', const SideModule = (children) => Widget.Box({
children: [ className: 'bar-sidemodule',
ModuleMusic() children: children,
], });
}); const barContent = Widget.CenterBox({
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({
className: 'bar-bg', className: 'bar-bg',
startWidget: ModuleLeftSpace(),
centerWidget: Widget.Box({
className: 'spacing-h-4',
children: [
left,
center,
right,
]
}),
endWidget: ModuleRightSpace(),
setup: (self) => { setup: (self) => {
const styleContext = self.get_style_context(); const styleContext = self.get_style_context();
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print); // 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 Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.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; const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js"; import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
import { showMusicControls } from '../../variables.js'; import { showMusicControls } from '../../variables.js';
function trimTrackTitle(title) { function trimTrackTitle(title) {
var cleanedTitle = title; if(!title) return '';
cleanedTitle = cleanedTitle.replace(/【[^】]*】/, ''); // Remove stuff like【C93】 at beginning const cleanRegexes = [
cleanedTitle = cleanedTitle.replace(/\[FREE DOWNLOAD\]/g, ''); // Remove F-777's [FREE DOWNLOAD] /【[^】]*】/, // Touhou n weeb stuff
return cleanedTitle.trim(); /\[FREE DOWNLOAD\]/, // F-777
];
cleanRegexes.forEach((expr) => title.replace(expr, ''));
return title;
} }
const TrackProgress = () => { const TrackProgress = () => {
@@ -30,61 +32,75 @@ const TrackProgress = () => {
}) })
} }
export const ModuleMusic = () => Widget.EventBox({ // TODO: use cairo to make button bounce smaller on click const switchToRelativeWorkspace = async (self, num) => {
onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`), try {
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`), const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value), Hyprland.sendMessage(`dispatch workspace ${num > 0 ? '+' : ''}${num}`);
onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']), } catch {
onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print), execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
child: Widget.Box({ }
className: 'bar-group-margin bar-sides', }
children: [
Widget.Box({ export default () => {
className: 'bar-group bar-group-standalone bar-group-pad-music spacing-h-10', // TODO: use cairo to make button bounce smaller on click, if that's possible
children: [ const playingState = Widget.Box({ // Wrap a box cuz overlay can't have margins itself
Widget.Box({ // Wrap a box cuz overlay can't have margins itself homogeneous: true,
homogeneous: true, children: [Widget.Overlay({
children: [Widget.Overlay({ child: Widget.Box({
child: Widget.Box({ vpack: 'center',
vpack: 'center', className: 'bar-music-playstate',
className: 'bar-music-playstate', homogeneous: true,
homogeneous: true, children: [Widget.Label({
children: [Widget.Label({ vpack: 'center',
vpack: 'center', className: 'bar-music-playstate-txt',
className: 'bar-music-playstate-txt', justification: 'center',
justification: 'center', setup: (self) => self.hook(Mpris, label => {
setup: (self) => self.hook(Mpris, label => { const mpris = Mpris.getPlayer('');
const mpris = Mpris.getPlayer(''); label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
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(),
]
})]
}), }),
Widget.Scrollable({ })],
hexpand: true, setup: (self) => self.hook(Mpris, label => {
child: Widget.Label({ const mpris = Mpris.getPlayer('');
className: 'txt-smallie txt-onSurfaceVariant', if (!mpris) return;
setup: (self) => self.hook(Mpris, label => { label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
const mpris = Mpris.getPlayer(''); label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
if (mpris) }),
label.label = `${trimTrackTitle(mpris.trackTitle)}${mpris.trackArtists.join(', ')}`; }),
else overlays: [
label.label = 'No media'; 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 { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
const { exec, execAsync } = Utils; const { exec, execAsync } = Utils;
const { GLib } = imports.gi; 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 Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { MaterialIcon } from '../../lib/materialicon.js'; import { MaterialIcon } from '../../lib/materialicon.js';
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js"; import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
@@ -14,7 +13,7 @@ const BATTERY_LOW = 20;
const BatBatteryProgress = () => { const BatBatteryProgress = () => {
const _updateProgress = (circprog) => { // Set circular progress value 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-low', Battery.percent <= BATTERY_LOW);
circprog.toggleClassName('bar-batt-circprog-full', Battery.charged); circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
@@ -91,7 +90,7 @@ const BarBattery = () => Box({
transitionDuration: 150, transitionDuration: 150,
revealChild: false, revealChild: false,
transition: 'slide_right', transition: 'slide_right',
child: MaterialIcon('bolt', 'norm'), child: MaterialIcon('bolt', 'norm', {tooltipText: "Charging"}),
setup: (self) => self.hook(Battery, revealer => { setup: (self) => self.hook(Battery, revealer => {
self.revealChild = Battery.charging; 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 BarResource = (name, icon, command) => {
const resourceLabel = Label({ const resourceLabel = Label({
className: 'txt-smallie txt-onSurfaceVariant', className: 'txt-smallie txt-onSurfaceVariant',
@@ -187,9 +167,18 @@ const BarGroup = ({ child }) => Widget.Box({
] ]
}); });
export const ModuleSystem = () => Widget.EventBox({ const switchToRelativeWorkspace = async (self, num) => {
onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`), try {
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`), 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'), onPrimaryClick: () => App.toggleWindow('sideright'),
child: Widget.Box({ child: Widget.Box({
className: 'spacing-h-5', className: 'spacing-h-5',
@@ -26,7 +26,7 @@ const WorkspaceContents = (count = 10) => {
let workspaceMask = 0; let workspaceMask = 0;
for (let i = 0; i < workspaces.length; i++) { for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[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 (ws.id > count) return; // Not rendered
if (workspaces[i].windows > 0) { if (workspaces[i].windows > 0) {
workspaceMask |= (1 << ws.id); 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 Widget from "resource:///com/github/Aylur/ags/widget.js";
import Sway from "../../services/sway.js"; import Sway from "../../services/sway.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import options from "../../options.js"; const { execAsync, exec } = Utils;
import { range } from "../../utils.js"; 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 WorkspaceContents = (count = 10) => {
const ws = options.workspaces.value || 20; return DrawingArea({
return Widget.Box({ css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
children: range(ws).map((i) => attribute: {
Widget.Button({ initialized: false,
setup: (btn) => (btn.id = i), workspaceMask: 0,
on_clicked: () => dispatch(i), updateMask: (self) => {
child: Widget.Label({ if (self.attribute.initialized) return; // We only need this to run once
label: `${i}`, const workspaces = Sway.workspaces;
class_name: "indicator", let workspaceMask = 0;
vpack: "center", // console.log('----------------')
}), for (let i = 0; i < workspaces.length; i++) {
setup: (self) => self.hook(Sway, (btn) => { const ws = workspaces[i];
btn.toggleClassName("active", Sway.active.workspace.name == i); // console.log(ws.name, ',', ws.num);
btn.toggleClassName( if (!Number(ws.name)) return;
"occupied", const id = Number(ws.name);
Sway.getWorkspace(`${i}`)?.nodes.length > 0, 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;`)
}) })
), .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
setup: (self) => self.hook(Sway.active.workspace, // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
(box) => box.children.map((btn) => { // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
btn.visible = Sway.workspaces.some( .on('draw', Lang.bind(area, (area, cr) => {
(ws) => ws.name == btn.id, const allocation = area.get_allocation();
); const { width, height } = allocation;
})
),
});
};
export default () => Widget.EventBox({ const workspaceStyleContext = dummyWs.get_style_context();
class_name: "workspaces panel-button", const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
child: Widget.Box({ const workspaceRadius = workspaceDiameter / 2;
// its nested like this to keep it consistent with other PanelButton widgets const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
child: Widget.EventBox({ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
on_scroll_up: () => dispatch("next"), const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
on_scroll_down: () => dispatch("prev"), const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
class_name: "eventbox",
// binds: [["child", options.workspaces, "value", Workspaces]], const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
setup: (self) => self const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
.hook(options.workspaces, (self) => Selection.child = Workspaces(), "value") 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) => { 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()
}
}))
}
})
]
})
+10 -14
View File
@@ -1,28 +1,24 @@
const { Gdk, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.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 WallpaperImage from './wallpaper.js';
import TimeAndLaunchesWidget from './timeandlaunches.js' import TimeAndLaunchesWidget from './timeandlaunches.js'
import SystemWidget from './system.js' import SystemWidget from './system.js'
import GraphWidget from './graph.js'
export default () => Widget.Window({ export default (monitor) => Widget.Window({
name: 'desktopbackground', name: `desktopbackground${monitor}`,
anchor: ['top', 'bottom', 'left', 'right'], // anchor: ['top', 'bottom', 'left', 'right'],
layer: 'background', layer: 'background',
exclusivity: 'normal', exclusivity: 'ignore',
visible: true, visible: true,
// child: WallpaperImage(monitor),
child: Widget.Overlay({ child: Widget.Overlay({
child: Widget.Box({ child: WallpaperImage(monitor),
hexpand: true,
vexpand: true,
}),
overlays: [ overlays: [
// GraphWidget(),
TimeAndLaunchesWidget(), TimeAndLaunchesWidget(),
SystemWidget(), 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({ Label({
className: 'bg-distro-name', className: 'bg-distro-name',
xalign: 0, xalign: 0,
label: '<version>', label: 'An environment idk',
setup: (label) => { setup: (label) => {
execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(distro => { // hyprctl will return unsuccessfully if Hyprland isn't running
label.label = `Hyprland ${distro}`; execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
}).catch(print); 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', className: 'osd-colorscheme spacing-v-5',
vertical: true, vertical: true,
hpack: 'center', hpack: 'center',
@@ -44,7 +44,7 @@ const colorschemeContent = Box({
export default () => Widget.Revealer({ export default () => Widget.Revealer({
transition: 'slide_down', transition: 'slide_down',
transitionDuration: 200, transitionDuration: 200,
child: colorschemeContent, child: ColorschemeContent(),
setup: (self) => self.hook(showColorScheme, (revealer) => { setup: (self) => self.hook(showColorScheme, (revealer) => {
revealer.revealChild = showColorScheme.value; revealer.revealChild = showColorScheme.value;
}), }),
@@ -1,85 +1,87 @@
// This file is for brightness/volume indicators // This file is for brightness/volume indicators
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.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 { MarginRevealer } from '../../lib/advancedwidgets.js';
import Brightness from '../../services/brightness.js'; import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js'; import Indicator from '../../services/indicator.js';
const OsdValue = (name, labelSetup, progressSetup, props = {}) => Box({ // Volume const OsdValue = (name, labelSetup, progressSetup, props = {}) => {
...props, const valueName = Label({
vertical: true, xalign: 0, yalign: 0, hexpand: true,
className: 'osd-bg osd-value', className: 'osd-label',
hexpand: true, label: `${name}`,
children: [ });
Box({ const valueNumber = Label({
vexpand: true, hexpand: false, className: 'osd-value-txt',
children: [ setup: labelSetup,
Label({ });
xalign: 0, yalign: 0, hexpand: true, return Box({ // Volume
className: 'osd-label', ...props,
label: `${name}`, vertical: true,
}), hexpand: true,
Label({ className: 'osd-bg osd-value',
hexpand: false, className: 'osd-value-txt', attribute: {
setup: labelSetup, 'disable': () => {
}), valueNumber.label = '󰖭';
] }
}), },
ProgressBar({ children: [
className: 'osd-progress', Box({
hexpand: true, vexpand: true,
vertical: false, children: [
setup: progressSetup, valueName,
}) valueNumber,
], ]
}); }),
ProgressBar({
className: 'osd-progress',
hexpand: true,
vertical: false,
setup: progressSetup,
})
],
});
}
const brightnessIndicator = OsdValue('Brightness', export default () => {
(self) => self const brightnessIndicator = OsdValue('Brightness',
.hook(Brightness, self => { (self) => self.hook(Brightness, self => {
self.label = `${Math.round(Brightness.screen_value * 100)}`; self.label = `${Math.round(Brightness.screen_value * 100)}`;
}, 'notify::screen-value') }, 'notify::screen-value'),
, (self) => self.hook(Brightness, (progress) => {
(self) => self
.hook(Brightness, (progress) => {
const updateValue = Brightness.screen_value; const updateValue = Brightness.screen_value;
progress.value = updateValue; progress.value = updateValue;
}, 'notify::screen-value') }, 'notify::screen-value'),
, )
)
const volumeIndicator = OsdValue('Volume', const volumeIndicator = OsdValue('Volume',
(self) => self (self) => self.hook(Audio, (label) => {
.hook(Audio, (label) => {
label.label = `${Math.round(Audio.speaker?.volume * 100)}`; label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
}) }),
, (self) => self.hook(Audio, (progress) => {
(self) => self
.hook(Audio, (progress) => {
const updateValue = Audio.speaker?.volume; const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) progress.value = updateValue; 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 Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Indicator from '../../services/indicator.js'; import Indicator from '../../services/indicator.js';
import IndicatorValues from './indicatorvalues.js'; import IndicatorValues from './indicatorvalues.js';
import MusicControls from './musiccontrols.js'; // import MusicControls from './musiccontrols.js';
import ColorScheme from './colorscheme.js'; import ColorScheme from './colorscheme.js';
import NotificationPopups from './notificationpopups.js'; import NotificationPopups from './notificationpopups.js';
export default (monitor) => Widget.Window({ export default (monitor = 0) => Widget.Window({
name: `indicator${monitor}`, name: `indicator${monitor}`,
monitor, monitor,
className: 'indicator', className: 'indicator',
@@ -23,7 +23,7 @@ export default (monitor) => Widget.Window({
css: 'min-height: 2px;', css: 'min-height: 2px;',
children: [ children: [
IndicatorValues(), IndicatorValues(),
MusicControls(), // MusicControls(),
NotificationPopups(), NotificationPopups(),
ColorScheme(), ColorScheme(),
] ]
+15 -20
View File
@@ -32,17 +32,13 @@ function isRealPlayer(player) {
); );
} }
export const getPlayer = (name = PREFERRED_PLAYER) => { export const getPlayer = (name = PREFERRED_PLAYER) => Mpris.getPlayer(name) || Mpris.players[0] || null;
return Mpris.getPlayer(name) || Mpris.players[0] || null;
}
function lengthStr(length) { function lengthStr(length) {
const min = Math.floor(length / 60); const min = Math.floor(length / 60);
const sec = Math.floor(length % 60); const sec = Math.floor(length % 60);
const sec0 = sec < 10 ? '0' : ''; const sec0 = sec < 10 ? '0' : '';
return `${min}:${sec0}${sec}`; return `${min}:${sec0}${sec}`;
} }
function fileExists(filePath) { function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath); let file = Gio.File.new_for_path(filePath);
return file.query_exists(null); return file.query_exists(null);
@@ -54,17 +50,11 @@ function detectMediaSource(link) {
return '󰈹 Firefox' return '󰈹 Firefox'
return "󰈣 File"; return "󰈣 File";
} }
// Remove protocol if present
let url = link.replace(/(^\w+:|^)\/\//, ''); let url = link.replace(/(^\w+:|^)\/\//, '');
// Extract the domain name
let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1]; let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
if (domain == 'ytimg.com') return '󰗃 Youtube';
if (domain == 'ytimg.com') if (domain == 'discordapp.net') return '󰙯 Discord';
return '󰗃 Youtube'; if (domain == 'sndcdn.com') return '󰓀 SoundCloud';
if (domain == 'discordapp.net')
return '󰙯 Discord';
if (domain == 'sndcdn.com')
return '󰓀 SoundCloud';
return domain; return domain;
} }
@@ -72,15 +62,20 @@ const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
function getTrackfont(player) { function getTrackfont(player) {
const title = player.trackTitle; const title = player.trackTitle;
const artists = player.trackArtists.join(' '); const artists = player.trackArtists.join(' ');
if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo')) return 'Chakra Petch'; // Rigid square replacement if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
if (title.includes('東方')) return 'Crimson Text, serif'; // Serif for Touhou stuff return 'Chakra Petch'; // Rigid square replacement
if (title.includes('東方'))
return 'Crimson Text, serif'; // Serif for Touhou stuff
return DEFAULT_MUSIC_FONT; return DEFAULT_MUSIC_FONT;
} }
function trimTrackTitle(title) { function trimTrackTitle(title) {
var cleanedTitle = title; if(!title) return '';
cleanedTitle = cleanedTitle.replace(/【[^】]*】/, ''); // Remove stuff like【C93】 at beginning const cleanRegexes = [
cleanedTitle = cleanedTitle.replace(/\[FREE DOWNLOAD\]/g, ''); // Remove F-777's [FREE DOWNLOAD] /【[^】]*】/, // Touhou n weeb stuff
return cleanedTitle.trim(); /\[FREE DOWNLOAD\]/, // F-777
];
cleanRegexes.forEach((expr) => title.replace(expr, ''));
return title;
} }
const TrackProgress = ({ player, ...rest }) => { const TrackProgress = ({ player, ...rest }) => {
@@ -4,37 +4,7 @@ import Notifications from 'resource:///com/github/Aylur/ags/service/notification
const { Box } = Widget; const { Box } = Widget;
import Notification from '../../lib/notification.js'; import Notification from '../../lib/notification.js';
const PopupNotification = (notifObject) => Widget.Box({ export default () => 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({
vertical: true, vertical: true,
className: 'osd-notifs spacing-v-5-revealer', className: 'osd-notifs spacing-v-5-revealer',
attribute: { attribute: {
@@ -72,5 +42,3 @@ const notifPopupList = Box({
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed') .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 Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.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 { Box, EventBox, Button, Revealer } = Widget;
const { execAsync, exec } = Utils; const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../lib/materialicon.js'; import { MaterialIcon } from '../../lib/materialicon.js';
@@ -19,7 +18,7 @@ execAsync(`ydotoold`).catch(print); // Start ydotool daemon
function releaseAllKeys() { function releaseAllKeys() {
const keycodes = Array.from(Array(249).keys()); const keycodes = Array.from(Array(249).keys());
execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)]) execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
.then(console.log('Released all keys')) .then(console.log('[OSK] Released all keys'))
.catch(print); .catch(print);
} }
var modsPressed = false; var modsPressed = false;
@@ -146,22 +145,32 @@ const keyboardWindow = Box({
const gestureEvBox = EventBox({ child: keyboardWindow }) const gestureEvBox = EventBox({ child: keyboardWindow })
const gesture = Gtk.GestureDrag.new(gestureEvBox); const gesture = Gtk.GestureDrag.new(gestureEvBox);
gesture.connect('drag-begin', () => { gesture.connect('drag-begin', async () => {
Hyprland.sendMessage('j/cursorpos').then((out) => { try {
gesture.startY = JSON.parse(out).y; const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
}).catch(print); Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
}).catch(print);
} catch {
return;
}
}); });
gesture.connect('drag-update', () => { gesture.connect('drag-update', async () => {
Hyprland.sendMessage('j/cursorpos').then((out) => { try {
const currentY = JSON.parse(out).y; const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
const offset = gesture.startY - currentY; 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; margin-bottom: ${offset}px;
`); `);
}).catch(print); }).catch(print);
} catch {
return;
}
}); });
gesture.connect('drag-end', () => { gesture.connect('drag-end', () => {
var offset = gesture.get_offset()[2]; var offset = gesture.get_offset()[2];
-1
View File
@@ -6,7 +6,6 @@ function moveClientToWorkspace(address, workspace) {
} }
export function dumpToWorkspace(from, to) { export function dumpToWorkspace(from, to) {
console.log('dump', from, to);
if (from == to) return; if (from == to) return;
Hyprland.clients.forEach(client => { Hyprland.clients.forEach(client => {
if (client.workspace.id == from) { if (client.workspace.id == from) {
@@ -66,9 +66,9 @@ export function execAndClose(command, terminal) {
execAsync(command).catch(print); execAsync(command).catch(print);
} }
export function startsWithNumber(str) { export function couldBeMath(str) {
var pattern = /^\d/; const regex = /^[0-9.+*/-]/;
return pattern.test(str); return regex.test(str);
} }
export function expandTilde(path) { export function expandTilde(path) {
@@ -1,6 +1,7 @@
const { Gdk, Gtk } = imports.gi; const { Gdk, Gtk } = imports.gi;
import { 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 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 Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.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 OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)]; const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
const overviewTick = Variable(false);
function truncateTitle(str) { function truncateTitle(str) {
let lastDash = -1; let lastDash = -1;
let found = -1; // 0: em dash, 1: en dash, 2: minus, 3: vertical bar, 4: middle dot 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.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => { eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`) Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
overviewTick.setValue(!overviewTick.value);
}); });
}, },
child: fixed, child: fixed,
@@ -275,18 +279,19 @@ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) =>
}).catch(print); }).catch(print);
} }
}, },
setup: (box) => { setup: (box) => box
box .hook(overviewTick, (box) => box.attribute.update(box))
// .hook(Hyprland, (box, name, data) => { // idk, does this make it lag occasionally? // .hook(Hyprland, (box, name, data) => { // idk, does this make it lag occasionally?
// if (["changefloatingmode", "movewindow"].includes(name)) // console.log(name)
// box.attribute.update(box); // if (["changefloatingmode", "movewindow"].includes(name))
// }, 'event') // box.attribute.update(box);
.hook(Hyprland, (box) => box.attribute.update(box), 'client-added') // }, 'event')
.hook(Hyprland, (box) => box.attribute.update(box), 'client-removed') .hook(Hyprland, (box) => box.attribute.update(box), 'client-added')
.hook(App, (box, name, visible) => { // Update on open .hook(Hyprland, (box) => box.attribute.update(box), 'client-removed')
if (name == 'overview' && visible) box.attribute.update(box); .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'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils; const { execAsync, exec } = Utils;
import { searchItem } from './searchitem.js'; 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 }) => { export const DirectoryButton = ({ parentPath, name, type, icon }) => {
const actionText = Widget.Revealer({ 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'; import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
const { execAsync, exec } = Utils; 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 { import {
CalculationResultButton, CustomCommandButton, DirectoryButton, CalculationResultButton, CustomCommandButton, DirectoryButton,
DesktopEntryButton, ExecuteCommandButton, SearchButton DesktopEntryButton, ExecuteCommandButton, SearchButton
@@ -39,7 +39,7 @@ const OptionalOverview = async () => {
try { try {
return (await import('./overview_hyprland.js')).default(); return (await import('./overview_hyprland.js')).default();
} catch { } catch {
return null; return Widget.Box({});
// return (await import('./overview_hyprland.js')).default(); // return (await import('./overview_hyprland.js')).default();
} }
}; };
@@ -104,7 +104,7 @@ export const SearchAndWindows = () => {
const isAction = text.startsWith('>'); const isAction = text.startsWith('>');
const isDir = (['/', '~'].includes(entry.text[0])); 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 { try {
const fullResult = eval(text); const fullResult = eval(text);
// copy // copy
@@ -166,7 +166,7 @@ export const SearchAndWindows = () => {
_appSearchResults = Applications.query(text); _appSearchResults = Applications.query(text);
// Calculate // 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 { try {
const fullResult = eval(text); const fullResult = eval(text);
resultsBox.add(CalculationResultButton({ result: fullResult, text: 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 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(); export default (monitor = 0, where = 'bottom left') => {
const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion); const positionString = where.replace(/\s/, ""); // remove space
return Widget.Window({
export const CornerTopleft = () => Widget.Window({ monitor,
name: 'cornertl', name: `corner${positionString}${monitor}`,
layer: 'top', layer: 'overlay',
anchor: ['top', 'left'], anchor: where.split(' '),
exclusivity: 'normal', exclusivity: 'ignore',
visible: true, visible: true,
child: RoundedCorner('topleft', { className: 'corner', }), child: RoundedCorner(positionString, { className: 'corner-black', }),
setup: enableClickthrough, 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,
});
@@ -223,7 +223,7 @@ const MessageContent = (content) => {
return contentBox; return contentBox;
} }
export const ChatMessage = (message, scrolledWindow) => { export const ChatMessage = (message, modelName = 'Model') => {
const messageContentBox = MessageContent(message.content); const messageContentBox = MessageContent(message.content);
const thisMessage = Box({ const thisMessage = Box({
className: 'sidebar-chat-message', className: 'sidebar-chat-message',
@@ -241,7 +241,8 @@ export const ChatMessage = (message, scrolledWindow) => {
xalign: 0, xalign: 0,
className: 'txt txt-bold sidebar-chat-name', className: 'txt txt-bold sidebar-chat-name',
wrap: true, wrap: true,
label: (message.role == 'user' ? USERNAME : 'ChatGPT'), useMarkup: true,
label: (message.role == 'user' ? USERNAME : modelName),
}), }),
messageContentBox, messageContentBox,
], ],
+69 -57
View File
@@ -8,63 +8,75 @@ const { execAsync, exec } = Utils;
import ChatGPT from '../../../services/chatgpt.js'; import ChatGPT from '../../../services/chatgpt.js';
import { MaterialIcon } from "../../../lib/materialicon.js"; import { MaterialIcon } from "../../../lib/materialicon.js";
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.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 { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
import { markdownTest } from '../../../lib/md2pango.js'; import { markdownTest } from '../../../lib/md2pango.js';
import { MarginRevealer } from '../../../lib/advancedwidgets.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', hpack: 'center',
className: 'sidebar-chat-apiswitcher-icon', className: 'sidebar-chat-apiswitcher-icon',
homogeneous: true, icon: `openai-symbolic`,
children: [ setup: (self) => Utils.timeout(513, () => { // stupid condition race
MaterialIcon('forum', 'norm'), 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({ const ChatGPTInfo = () => {
vertical: true, const openAiLogo = Icon({
className: 'spacing-v-15', hpack: 'center',
children: [ className: 'sidebar-chat-welcome-logo',
Icon({ icon: `openai-symbolic`,
hpack: 'center', setup: (self) => Utils.timeout(513, () => { // stupid condition race
className: 'sidebar-chat-welcome-logo', const styleContext = self.get_style_context();
icon: `${App.configDir}/assets/openai-logomark.svg`, const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
setup: (self) => Utils.timeout(1, () => { const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const styleContext = self.get_style_context(); // console.log(Math.round(Math.max(width, height, 1)));
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); self.size = Math.max(width, height, 1) * 116 / 180;
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); // ↑ Why such a specific proportion? See https://openai.com/brand#logos
self.size = Math.max(width, height, 1) * 116 / 180; // Why such a specific proportion? See https://openai.com/brand#logos })
}) });
}), return Box({
Label({ vertical: true,
className: 'txt txt-title-small sidebar-chat-welcome-txt', className: 'spacing-v-15',
wrap: true, children: [
justify: Gtk.Justification.CENTER, openAiLogo,
label: 'ChatGPT', Label({
}), className: 'txt txt-title-small sidebar-chat-welcome-txt',
Box({ wrap: true,
className: 'spacing-h-5', justify: Gtk.Justification.CENTER,
hpack: 'center', label: 'Assistant (ChatGPT 3.5)',
children: [ }),
Label({ Box({
className: 'txt-smallie txt-subtext', className: 'spacing-h-5',
wrap: true, hpack: 'center',
justify: Gtk.Justification.CENTER, children: [
label: 'Powered by OpenAI', Label({
}), className: 'txt-smallie txt-subtext',
Button({ wrap: true,
className: 'txt-subtext txt-norm icon-material', justify: Gtk.Justification.CENTER,
label: 'info', label: 'Powered by OpenAI',
tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.', }),
setup: setupCursorHoverInfo, 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', transition: 'slide_down',
revealChild: true, revealChild: true,
extraSetup: (self) => self extraSetup: (self) => self
@@ -89,7 +101,7 @@ export const chatGPTSettings = MarginRevealer({
{ value: 0.50, name: 'Balanced', }, { value: 0.50, name: 'Balanced', },
{ value: 1.00, name: 'Creative', }, { value: 1.00, name: 'Creative', },
], ],
initIndex: 1, initIndex: 2,
onChange: (value, name) => { onChange: (value, name) => {
ChatGPT.temperature = value; ChatGPT.temperature = value;
}, },
@@ -111,8 +123,8 @@ export const chatGPTSettings = MarginRevealer({
}), }),
ConfigToggle({ ConfigToggle({
icon: 'description', icon: 'description',
name: 'Assistant prompt', name: 'Enhancements',
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.', desc: 'Tells ChatGPT:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
initValue: ChatGPT.assistantPrompt, initValue: ChatGPT.assistantPrompt,
onChange: (self, newValue) => { onChange: (self, newValue) => {
ChatGPT.assistantPrompt = newValue; ChatGPT.assistantPrompt = newValue;
@@ -124,7 +136,7 @@ export const chatGPTSettings = MarginRevealer({
}) })
}); });
export const openaiApiKeyInstructions = Box({ export const OpenaiApiKeyInstructions = () => Box({
homogeneous: true, homogeneous: true,
children: [Revealer({ children: [Revealer({
transition: 'slide_down', transition: 'slide_down',
@@ -150,7 +162,7 @@ export const openaiApiKeyInstructions = Box({
})] })]
}); });
export const chatGPTWelcome = Box({ const chatGPTWelcome = Box({
vexpand: true, vexpand: true,
homogeneous: true, homogeneous: true,
child: Box({ child: Box({
@@ -158,9 +170,9 @@ export const chatGPTWelcome = Box({
vpack: 'center', vpack: 'center',
vertical: true, vertical: true,
children: [ children: [
chatGPTInfo, ChatGPTInfo(),
openaiApiKeyInstructions, OpenaiApiKeyInstructions(),
chatGPTSettings, `` ChatGPTSettings(),
] ]
}) })
}); });
@@ -172,7 +184,7 @@ export const chatContent = Box({
.hook(ChatGPT, (box, id) => { .hook(ChatGPT, (box, id) => {
const message = ChatGPT.messages[id]; const message = ChatGPT.messages[id];
if (!message) return; if (!message) return;
box.add(ChatMessage(message, chatGPTView)) box.add(ChatMessage(message, 'ChatGPT'))
}, 'newMsg') }, '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; const { execAsync, exec } = Utils;
import { MaterialIcon } from "../../../lib/materialicon.js"; import { MaterialIcon } from "../../../lib/materialicon.js";
import { MarginRevealer } from '../../../lib/advancedwidgets.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'; import WaifuService from '../../../services/waifus.js';
async function getImageViewerApp(preferredApp) { async function getImageViewerApp(preferredApp) {
@@ -162,7 +162,8 @@ const WaifuImage = (taglist) => {
blockImage.set_size_request(widgetWidth, widgetHeight); blockImage.set_size_request(widgetWidth, widgetHeight);
const showImage = () => { const showImage = () => {
downloadState.shown = 'done'; 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.set_size_request(widgetWidth, widgetHeight);
blockImage.connect("draw", (widget, cr) => { blockImage.connect("draw", (widget, cr) => {
@@ -220,10 +221,62 @@ const WaifuImage = (taglist) => {
return thisBlock; 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({ const waifuContent = Box({
className: 'spacing-v-15', className: 'spacing-v-15',
vertical: true, vertical: true,
vexpand: true,
attribute: { attribute: {
'map': new Map(), 'map': new Map(),
}, },
@@ -251,6 +304,7 @@ export const waifuView = Scrollable({
child: Box({ child: Box({
vertical: true, vertical: true,
children: [ children: [
waifuWelcome,
waifuContent, 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) => { export const sendMessage = (text) => {
// Do something on send // Do something on send
// Commands // Commands
@@ -335,20 +404,20 @@ export const sendMessage = (text) => {
else if (text.startsWith('/test')) { else if (text.startsWith('/test')) {
const newImage = WaifuImage(['/test']); const newImage = WaifuImage(['/test']);
waifuContent.add(newImage); waifuContent.add(newImage);
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update({ // Needs timeout or inits won't make it Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
// This is an image uploaded to my github repo DummyTag(300, 200, 'https://picsum.photos/600/400'),
status: 200, true
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));
} }
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); else WaifuService.fetch(text);
} }
+40 -21
View File
@@ -1,22 +1,32 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.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; const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js"; import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js";
// APIs // APIs
import ChatGPT from '../../services/chatgpt.js'; 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 { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js'; import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
const APIS = [ const APIS = [
{ {
name: 'ChatGPT', name: 'Assistant (ChatGPT 3.5)',
sendCommand: chatGPTSendMessage, sendCommand: chatGPTSendMessage,
contentWidget: chatGPTView, contentWidget: chatGPTView,
commandBar: chatGPTCommands, commandBar: chatGPTCommands,
tabIcon: chatGPTTabIcon, 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', name: 'Waifus',
@@ -35,8 +45,12 @@ export const chatEntry = Entry({
hexpand: true, hexpand: true,
setup: (self) => self setup: (self) => self
.hook(ChatGPT, (self) => { .hook(ChatGPT, (self) => {
if (APIS[currentApiId].name != 'ChatGPT') return; if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return;
self.placeholderText = (ChatGPT.key.length > 0 ? 'Ask a question...' : 'Enter OpenAI API Key...'); 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') }, 'hasKey')
, ,
onChange: (entry) => { onChange: (entry) => {
@@ -86,22 +100,27 @@ function switchToTab(id) {
chatEntry.placeholderText = APIS[id].placeholderText, chatEntry.placeholderText = APIS[id].placeholderText,
currentApiId = id; currentApiId = id;
} }
const apiSwitcher = Box({
homogeneous: true, const apiSwitcher = CenterBox({
children: [ centerWidget: Box({
Box({ className: 'sidebar-chat-apiswitcher spacing-h-5',
className: 'sidebar-chat-apiswitcher spacing-h-5', hpack: 'center',
hpack: 'center', children: APIS.map((api, id) => Button({
children: APIS.map((api, id) => Button({ child: api.tabIcon,
child: api.tabIcon, tooltipText: api.name,
tooltipText: api.name, setup: setupCursorHover,
setup: setupCursorHover, onClicked: () => {
onClicked: () => { switchToTab(id);
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({ export default Widget.Box({
+1 -1
View File
@@ -5,7 +5,7 @@ export default () => PopupWindow({
focusable: true, focusable: true,
anchor: ['left', 'top', 'bottom'], anchor: ['left', 'top', 'bottom'],
name: 'sideleft', name: 'sideleft',
// exclusivity: 'exclusive', layer: 'top',
showClassName: 'sideleft-show', showClassName: 'sideleft-show',
hideClassName: 'sideleft-hide', hideClassName: 'sideleft-hide',
child: SidebarLeft(), child: SidebarLeft(),
-8
View File
@@ -102,22 +102,14 @@ const pinButton = Button({
self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled); self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled);
const sideleftWindow = App.getWindow('sideleft'); 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]; const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled); sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
if (self.attribute.enabled) { if (self.attribute.enabled) {
sideleftWindow.layer = 'bottom';
barWindow.layer = 'bottom';
cornerTopLeftWindow.layer = 'bottom';
sideleftWindow.exclusivity = 'exclusive'; sideleftWindow.exclusivity = 'exclusive';
} }
else { else {
sideleftWindow.layer = 'top';
barWindow.layer = 'top';
cornerTopLeftWindow.layer = 'top';
sideleftWindow.exclusivity = 'normal'; sideleftWindow.exclusivity = 'normal';
} }
}, },
+1 -1
View File
@@ -10,7 +10,7 @@ export default Scrollable({
child: Box({ child: Box({
vertical: true, vertical: true,
children: [ 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 Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.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; const { execAsync, exec } = Utils;
import { BluetoothIndicator, NetworkIndicator } from "../../lib/statusicons.js"; import { BluetoothIndicator, NetworkIndicator } from "../../lib/statusicons.js";
import { setupCursorHover } from "../../lib/cursorhover.js"; import { setupCursorHover } from "../../lib/cursorhover.js";
@@ -12,7 +11,6 @@ import { MaterialIcon } from '../../lib/materialicon.js';
function expandTilde(path) { function expandTilde(path) {
if (path.startsWith('~')) { if (path.startsWith('~')) {
console.log(GLib.get_home_dir() + path.slice(1));
return GLib.get_home_dir() + path.slice(1); return GLib.get_home_dir() + path.slice(1);
} else { } else {
return path; return path;
@@ -62,24 +60,30 @@ export const ToggleIconBluetooth = (props = {}) => Widget.Button({
...props, ...props,
}); });
export const HyprToggleIcon = (icon, name, hyprlandConfigValue, props = {}) => Widget.Button({ export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
className: 'txt-small sidebar-iconbutton', try {
tooltipText: `${name}`, return Widget.Button({
onClicked: (button) => { className: 'txt-small sidebar-iconbutton',
// Set the value to 1 - value tooltipText: `${name}`,
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => { onClicked: (button) => {
const currentOption = JSON.parse(result).int; // Set the value to 1 - value
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print); Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
button.toggleClassName('sidebar-button-active', currentOption == 0); const currentOption = JSON.parse(result).int;
}).catch(print); execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
}, button.toggleClassName('sidebar-button-active', currentOption == 0);
child: MaterialIcon(icon, 'norm', { hpack: 'center' }), }).catch(print);
setup: button => { },
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1); child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
setupCursorHover(button); setup: button => {
}, button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
...props, setupCursorHover(button);
}) },
...props,
})
} catch {
return null;
}
}
export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work
attribute: { attribute: {
@@ -104,31 +108,36 @@ export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make th
...props, ...props,
}); });
export const ModuleInvertColors = (props = {}) => Widget.Button({ export const ModuleInvertColors = async (props = {}) => {
className: 'txt-small sidebar-iconbutton', try {
tooltipText: 'Color inversion', const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
onClicked: (button) => { return Widget.Button({
// const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str; className: 'txt-small sidebar-iconbutton',
Hyprland.sendMessage('j/getoption decoration:screen_shader') tooltipText: 'Color inversion',
.then((output) => { onClicked: (button) => {
const shaderPath = JSON.parse(output)["str"].trim(); // const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
console.log(output) Hyprland.sendMessage('j/getoption decoration:screen_shader')
console.log(shaderPath) .then((output) => {
if (shaderPath != "[[EMPTY]]" && shaderPath != "") { const shaderPath = JSON.parse(output)["str"].trim();
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print); if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
button.toggleClassName('sidebar-button-active', false); 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')}`) else {
.catch(print); Hyprland.sendMessage(`j/keyword decoration:screen_shader ${expandTilde('~/.config/hypr/shaders/invert.frag')}`)
button.toggleClassName('sidebar-button-active', true); .catch(print);
} button.toggleClassName('sidebar-button-active', true);
}) }
}, })
child: MaterialIcon('invert_colors', 'norm'), },
setup: setupCursorHover, child: MaterialIcon('invert_colors', 'norm'),
...props, setup: setupCursorHover,
}) ...props,
})
} catch {
return null;
};
}
export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
attribute: { attribute: {
@@ -173,9 +182,9 @@ export const ModuleEditIcon = (props = {}) => Widget.Button({ // TODO: Make this
export const ModuleReloadIcon = (props = {}) => Widget.Button({ export const ModuleReloadIcon = (props = {}) => Widget.Button({
...props, ...props,
className: 'txt-small sidebar-iconbutton', className: 'txt-small sidebar-iconbutton',
tooltipText: 'Reload Hyprland', tooltipText: 'Reload Environment config',
onClicked: () => { onClicked: () => {
execAsync(['bash', '-c', 'hyprctl reload &']); execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
App.toggleWindow('sideright'); App.toggleWindow('sideright');
}, },
child: MaterialIcon('refresh', 'norm'), child: MaterialIcon('refresh', 'norm'),
+3 -3
View File
@@ -45,10 +45,10 @@ const togglesBox = Widget.Box({
children: [ children: [
ToggleIconWifi(), ToggleIconWifi(),
ToggleIconBluetooth(), ToggleIconBluetooth(),
HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', {}), await HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', {}),
HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}), await HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
ModuleNightLight(), ModuleNightLight(),
ModuleInvertColors(), await ModuleInvertColors(),
ModuleIdleInhibitor(), ModuleIdleInhibitor(),
] ]
}) })