aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/ags/config.js31
-rw-r--r--config/ags/modules/.configuration/user_options.js121
-rw-r--r--config/ags/modules/.miscutils/icons.js13
-rw-r--r--config/ags/modules/.miscutils/mathfuncs.js4
-rw-r--r--config/ags/modules/.miscutils/system.js54
-rw-r--r--config/ags/modules/.widgethacks/advancedrevealers.js86
-rw-r--r--config/ags/modules/.widgethacks/popupwindow.js32
-rw-r--r--config/ags/modules/.widgetutils/clickthrough.js4
-rw-r--r--config/ags/modules/.widgetutils/cursorhover.js57
-rw-r--r--config/ags/modules/.widgetutils/keybind.js25
-rw-r--r--config/ags/modules/overview/actions.js28
-rw-r--r--config/ags/modules/overview/main.js18
-rw-r--r--config/ags/modules/overview/miscfunctions.js155
-rw-r--r--config/ags/modules/overview/overview_hyprland.js423
-rw-r--r--config/ags/modules/overview/searchbuttons.js163
-rw-r--r--config/ags/modules/overview/searchitem.js65
-rw-r--r--config/ags/modules/overview/windowcontent.js262
-rw-r--r--config/ags/user/style.css190
-rw-r--r--config/ags/user_options.js21
-rw-r--r--config/ags/variables.js21
-rw-r--r--config/hypr/UserConfigs/Startup_Apps.conf1
-rw-r--r--config/hypr/configs/Keybinds.conf1
-rwxr-xr-xconfig/hypr/scripts/Refresh.sh7
23 files changed, 1781 insertions, 1 deletions
diff --git a/config/ags/config.js b/config/ags/config.js
new file mode 100644
index 00000000..278bf351
--- /dev/null
+++ b/config/ags/config.js
@@ -0,0 +1,31 @@
+"use strict";
+import GLib from 'gi://GLib';
+import App from 'resource:///com/github/Aylur/ags/app.js'
+import userOptions from './modules/.configuration/user_options.js';
+import Overview from './modules/overview/main.js';
+
+const COMPILED_STYLE_DIR = `${GLib.get_user_config_dir()}/ags/user/`
+
+async function applyStyle() {
+
+ App.resetCss();
+ App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
+ console.log('[LOG] Styles loaded')
+}
+applyStyle().catch(print);
+
+const Windows = () => [
+ Overview()
+];
+const CLOSE_ANIM_TIME = 210;
+App.config({
+ css: `${COMPILED_STYLE_DIR}/style.css`,
+ stackTraceOnError: true,
+ closeWindowDelay: {
+ 'sideright': CLOSE_ANIM_TIME,
+ 'sideleft': CLOSE_ANIM_TIME,
+ 'osk': CLOSE_ANIM_TIME,
+ },
+ windows: Windows().flat(1),
+});
+
diff --git a/config/ags/modules/.configuration/user_options.js b/config/ags/modules/.configuration/user_options.js
new file mode 100644
index 00000000..0c76a549
--- /dev/null
+++ b/config/ags/modules/.configuration/user_options.js
@@ -0,0 +1,121 @@
+
+import userOverrides from '../../user_options.js';
+
+// Defaults
+let configOptions = {
+ // General stuff
+ 'ai': {
+ 'defaultGPTProvider': "openai",
+ 'defaultTemperature': 0.9,
+ 'enhancements': true,
+ 'useHistory': true,
+ 'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering
+ },
+ 'animations': {
+ 'choreographyDelay': 35,
+ 'durationSmall': 110,
+ 'durationLarge': 180,
+ },
+ 'appearance': {
+ 'keyboardUseFlag': false, // Use flag emoji instead of abbreviation letters
+ },
+ 'apps': {
+ 'imageViewer': "loupe",
+ 'terminal': "foot", // This is only for shell actions
+ },
+ 'battery': {
+ 'low': 20,
+ 'critical': 10,
+ },
+ 'music': {
+ 'preferredPlayer': "plasma-browser-integration",
+ },
+ 'onScreenKeyboard': {
+ 'layout': "qwerty_full", // See modules/onscreenkeyboard/onscreenkeyboard.js for available layouts
+ },
+ 'overview': {
+ 'scale': 0.18, // Relative to screen size
+ 'numOfRows': 2,
+ 'numOfCols': 5,
+ 'wsNumScale': 0.09,
+ 'wsNumMarginScale': 0.07,
+ },
+ 'sidebar': {
+ 'imageColumns': 2,
+ 'imageBooruCount': 20,
+ 'imageAllowNsfw': false,
+ },
+ 'search': {
+ 'engineBaseUrl': "https://www.google.com/search?q=",
+ 'excludedSites': [], //add site to exclude from result. eg: "quora.com"
+ },
+ 'time': {
+ // See https://docs.gtk.org/glib/method.DateTime.format.html
+ // Here's the 12h format: "%I:%M%P"
+ // For seconds, add "%S" and set interval to 1000
+ 'format': "%H:%M",
+ 'interval': 5000,
+ 'dateFormatLong': "%A, %d/%m", // On bar
+ 'dateInterval': 5000,
+ 'dateFormat': "%d/%m", // On notif time
+ },
+ 'weather': {
+ 'city': "",
+ },
+ 'workspaces': {
+ 'shown': 10,
+ },
+ // Longer stuff
+ 'icons': {
+ substitutions: {
+ 'code-url-handler': "visual-studio-code",
+ 'Code': "visual-studio-code",
+ 'GitHub Desktop': "github-desktop",
+ 'Minecraft* 1.20.1': "minecraft",
+ 'gnome-tweaks': "org.gnome.tweaks",
+ 'pavucontrol-qt': "pavucontrol",
+ 'wps': "wps-office2019-kprometheus",
+ 'wpsoffice': "wps-office2019-kprometheus",
+ '': "image-missing",
+ }
+ },
+ 'keybinds': {
+ // Format: Mod1+Mod2+key. CaSe SeNsItIvE!
+ // Modifiers: Shift Ctrl Alt Hyper Meta
+ // See https://docs.gtk.org/gdk3/index.html#constants for the other keys (they are listed as KEY_key)
+ 'overview': {
+ 'altMoveLeft': "Ctrl+b",
+ 'altMoveRight': "Ctrl+f",
+ 'deleteToEnd': "Ctrl+k",
+ },
+ 'sidebar': {
+ 'apis': {
+ 'nextTab': "Page_Down",
+ 'prevTab': "Page_Up",
+ },
+ 'options': { // Right sidebar
+ 'nextTab': "Page_Down",
+ 'prevTab': "Page_Up",
+ },
+ 'pin': "Ctrl+p",
+ 'cycleTab': "Ctrl+Tab",
+ 'nextTab': "Ctrl+Page_Down",
+ 'prevTab': "Ctrl+Page_Up",
+ },
+ },
+}
+
+// Override defaults with user's options
+function overrideConfigRecursive(userOverrides, configOptions = {}) {
+ for (const [key, value] of Object.entries(userOverrides)) {
+ if (typeof value === 'object') {
+ overrideConfigRecursive(value, configOptions[key]);
+ } else {
+ configOptions[key] = value;
+ }
+ }
+}
+overrideConfigRecursive(userOverrides, configOptions);
+
+globalThis['userOptions'] = configOptions;
+export default configOptions; \ No newline at end of file
diff --git a/config/ags/modules/.miscutils/icons.js b/config/ags/modules/.miscutils/icons.js
new file mode 100644
index 00000000..fb1e20da
--- /dev/null
+++ b/config/ags/modules/.miscutils/icons.js
@@ -0,0 +1,13 @@
+const { Gtk } = imports.gi;
+
+export function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+export function substitute(str) {
+ if(userOptions.icons.substitutions[str]) return userOptions.icons.substitutions[str];
+
+ if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
+ return str;
+} \ No newline at end of file
diff --git a/config/ags/modules/.miscutils/mathfuncs.js b/config/ags/modules/.miscutils/mathfuncs.js
new file mode 100644
index 00000000..ba1c0b59
--- /dev/null
+++ b/config/ags/modules/.miscutils/mathfuncs.js
@@ -0,0 +1,4 @@
+
+export function clamp(x, min, max) {
+ return Math.min(Math.max(x, min), max);
+} \ No newline at end of file
diff --git a/config/ags/modules/.miscutils/system.js b/config/ags/modules/.miscutils/system.js
new file mode 100644
index 00000000..ef7d15c3
--- /dev/null
+++ b/config/ags/modules/.miscutils/system.js
@@ -0,0 +1,54 @@
+const { GLib } = imports.gi;
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+
+export const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2 | sed "s/\\"//g"'`).trim();
+export const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'popos' || distroID == 'raspbian' || distroID == 'kali');
+export const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros' || distroID == 'cachyos');
+export const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
+
+const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/colormode.txt`;
+const colorMode = Utils.exec('bash -c "sed -n \'1p\' $HOME/.cache/ags/user/colormode.txt"');
+export let darkMode = Variable(!(Utils.readFile(LIGHTDARK_FILE_LOCATION).split('\n')[0].trim() == 'light'));
+export const hasPlasmaIntegration = !!Utils.exec('bash -c "command -v plasma-browser-integration-host"');
+
+export const getDistroIcon = () => {
+ // Arches
+ if(distroID == 'arch') return 'arch-symbolic';
+ if(distroID == 'endeavouros') return 'endeavouros-symbolic';
+ if(distroID == 'cachyos') return 'cachyos-symbolic';
+ // Funny flake
+ if(distroID == 'nixos') return 'nixos-symbolic';
+ // Cool thing
+ if(distroID == 'fedora') return 'fedora-symbolic';
+ // Debians
+ if(distroID == 'linuxmint') return 'ubuntu-symbolic';
+ if(distroID == 'ubuntu') return 'ubuntu-symbolic';
+ if(distroID == 'debian') return 'debian-symbolic';
+ if(distroID == 'zorin') return 'ubuntu-symbolic';
+ if(distroID == 'popos') return 'ubuntu-symbolic';
+ if(distroID == 'raspbian') return 'debian-symbolic';
+ if(distroID == 'kali') return 'debian-symbolic';
+ return 'linux-symbolic';
+}
+
+export const getDistroName = () => {
+ // Arches
+ if(distroID == 'arch') return 'Arch Linux';
+ if(distroID == 'endeavouros') return 'EndeavourOS';
+ if(distroID == 'cachyos') return 'CachyOS';
+ // Funny flake
+ if(distroID == 'nixos') return 'NixOS';
+ // Cool thing
+ if(distroID == 'fedora') return 'Fedora';
+ // Debians
+ if(distroID == 'linuxmint') return 'Linux Mint';
+ if(distroID == 'ubuntu') return 'Ubuntu';
+ if(distroID == 'debian') return 'Debian';
+ if(distroID == 'zorin') return 'Zorin';
+ if(distroID == 'popos') return 'Pop!_OS';
+ if(distroID == 'raspbian') return 'Raspbian';
+ if(distroID == 'kali') return 'Kali Linux';
+ return 'Linux';
+}
diff --git a/config/ags/modules/.widgethacks/advancedrevealers.js b/config/ags/modules/.widgethacks/advancedrevealers.js
new file mode 100644
index 00000000..3f127931
--- /dev/null
+++ b/config/ags/modules/.widgethacks/advancedrevealers.js
@@ -0,0 +1,86 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+const { Revealer, Scrollable } = Widget;
+
+export const MarginRevealer = ({
+ transition = 'slide_down',
+ child,
+ revealChild,
+ showClass = 'element-show', // These are for animation curve, they don't really hide
+ hideClass = 'element-hide', // Don't put margins in these classes!
+ extraSetup = () => { },
+ ...rest
+}) => {
+ const widget = Scrollable({
+ ...rest,
+ attribute: {
+ 'revealChild': true, // It'll be set to false after init if it's supposed to hide
+ 'transition': transition,
+ 'show': () => {
+ if (widget.attribute.revealChild) return;
+ widget.hscroll = 'never';
+ widget.vscroll = 'never';
+ child.toggleClassName(hideClass, false);
+ child.toggleClassName(showClass, true);
+ widget.attribute.revealChild = true;
+ child.css = 'margin: 0px;';
+ },
+ 'hide': () => {
+ if (!widget.attribute.revealChild) return;
+ child.toggleClassName(hideClass, true);
+ child.toggleClassName(showClass, false);
+ widget.attribute.revealChild = false;
+ if (widget.attribute.transition == 'slide_left')
+ child.css = `margin-right: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_right')
+ child.css = `margin-left: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_up')
+ child.css = `margin-bottom: -${child.get_allocated_height()}px;`;
+ else if (widget.attribute.transition == 'slide_down')
+ child.css = `margin-top: -${child.get_allocated_height()}px;`;
+ },
+ 'toggle': () => {
+ if (widget.attribute.revealChild) widget.attribute.hide();
+ else widget.attribute.show();
+ },
+ },
+ child: child,
+ hscroll: `${revealChild ? 'never' : 'always'}`,
+ vscroll: `${revealChild ? 'never' : 'always'}`,
+ setup: (self) => {
+ extraSetup(self);
+ }
+ });
+ child.toggleClassName(`${revealChild ? showClass : hideClass}`, true);
+ return widget;
+}
+
+// TODO: Allow reveal update. Currently this just helps at declaration
+export const DoubleRevealer = ({
+ transition1 = 'slide_right',
+ transition2 = 'slide_left',
+ duration1 = 150,
+ duration2 = 150,
+ child,
+ revealChild,
+ ...rest
+}) => {
+ const r2 = Revealer({
+ transition: transition2,
+ transitionDuration: duration2,
+ revealChild: revealChild,
+ child: child,
+ });
+ const r1 = Revealer({
+ transition: transition1,
+ transitionDuration: duration1,
+ revealChild: revealChild,
+ child: r2,
+ ...rest,
+ })
+ r1.toggleRevealChild = (value) => {
+ r1.revealChild = value;
+ r2.revealChild = value;
+ }
+ return r1;
+}
diff --git a/config/ags/modules/.widgethacks/popupwindow.js b/config/ags/modules/.widgethacks/popupwindow.js
new file mode 100644
index 00000000..26dad59c
--- /dev/null
+++ b/config/ags/modules/.widgethacks/popupwindow.js
@@ -0,0 +1,32 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Window } = Widget;
+
+
+export default ({
+ name,
+ child,
+ showClassName = "",
+ hideClassName = "",
+ ...props
+}) => {
+ return Window({
+ name,
+ visible: false,
+ layer: 'overlay',
+ ...props,
+
+ child: Box({
+ setup: (self) => {
+ self.hook(App, (self, currentName, visible) => {
+ if (currentName === name) {
+ self.toggleClassName(hideClassName, !visible);
+ }
+ }).keybind("Escape", () => App.closeWindow(name))
+ if (showClassName !== "" && hideClassName !== "")
+ self.className = `${showClassName} ${hideClassName}`;
+ },
+ child: child,
+ }),
+ });
+} \ No newline at end of file
diff --git a/config/ags/modules/.widgetutils/clickthrough.js b/config/ags/modules/.widgetutils/clickthrough.js
new file mode 100644
index 00000000..505f1412
--- /dev/null
+++ b/config/ags/modules/.widgetutils/clickthrough.js
@@ -0,0 +1,4 @@
+import Cairo from 'gi://cairo?version=1.0';
+
+export const dummyRegion = new Cairo.Region();
+export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion); \ No newline at end of file
diff --git a/config/ags/modules/.widgetutils/cursorhover.js b/config/ags/modules/.widgetutils/cursorhover.js
new file mode 100644
index 00000000..89be913b
--- /dev/null
+++ b/config/ags/modules/.widgetutils/cursorhover.js
@@ -0,0 +1,57 @@
+const { Gdk } = imports.gi;
+
+export function setupCursorHover(button) { // Hand pointing cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+
+}
+
+export function setupCursorHoverAim(button) { // Crosshair cursor on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverGrab(button) { // Hand ready to grab on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'grab');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverInfo(button) { // "?" mark cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'help');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
diff --git a/config/ags/modules/.widgetutils/keybind.js b/config/ags/modules/.widgetutils/keybind.js
new file mode 100644
index 00000000..eda7877b
--- /dev/null
+++ b/config/ags/modules/.widgetutils/keybind.js
@@ -0,0 +1,25 @@
+const { Gdk } = imports.gi;
+
+const MODS = {
+ 'Shift': Gdk.ModifierType.SHIFT_MASK,
+ 'Ctrl': Gdk.ModifierType.CONTROL_MASK,
+ 'Alt': Gdk.ModifierType.ALT_MASK,
+ 'Hyper': Gdk.ModifierType.HYPER_MASK,
+ 'Meta': Gdk.ModifierType.META_MASK
+}
+
+export const checkKeybind = (event, keybind) => {
+ const pressedModMask = event.get_state()[1];
+ const pressedKey = event.get_keyval()[1];
+ const keys = keybind.split('+');
+ for (let i = 0; i < keys.length; i++) {
+ if (keys[i] in MODS) {
+ if (!(pressedModMask & MODS[keys[i]])) {
+ return false;
+ }
+ } else if (pressedKey !== Gdk[`KEY_${keys[i]}`]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/config/ags/modules/overview/actions.js b/config/ags/modules/overview/actions.js
new file mode 100644
index 00000000..766cf454
--- /dev/null
+++ b/config/ags/modules/overview/actions.js
@@ -0,0 +1,28 @@
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+function moveClientToWorkspace(address, workspace) {
+ Utils.execAsync(['bash', '-c', `hyprctl dispatch movetoworkspacesilent ${workspace},address:${address} &`]);
+}
+
+export function dumpToWorkspace(from, to) {
+ if (from == to) return;
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == from) {
+ moveClientToWorkspace(client.address, to);
+ }
+ });
+}
+
+export function swapWorkspace(workspaceA, workspaceB) {
+ if (workspaceA == workspaceB) return;
+ const clientsA = [];
+ const clientsB = [];
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == workspaceA) clientsA.push(client.address);
+ if (client.workspace.id == workspaceB) clientsB.push(client.address);
+ });
+
+ clientsA.forEach((address) => moveClientToWorkspace(address, workspaceB));
+ clientsB.forEach((address) => moveClientToWorkspace(address, workspaceA));
+} \ No newline at end of file
diff --git a/config/ags/modules/overview/main.js b/config/ags/modules/overview/main.js
new file mode 100644
index 00000000..1f5348d9
--- /dev/null
+++ b/config/ags/modules/overview/main.js
@@ -0,0 +1,18 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { SearchAndWindows } from "./windowcontent.js";
+import PopupWindow from '../.widgethacks/popupwindow.js';
+
+export default (id = '') => PopupWindow({
+ name: `overview${id}`,
+ exclusivity: 'ignore',
+ keymode: 'exclusive',
+ visible: false,
+ // anchor: ['middle'],
+ layer: 'overlay',
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ SearchAndWindows(),
+ ]
+ }),
+})
diff --git a/config/ags/modules/overview/miscfunctions.js b/config/ags/modules/overview/miscfunctions.js
new file mode 100644
index 00000000..187ee6ec
--- /dev/null
+++ b/config/ags/modules/overview/miscfunctions.js
@@ -0,0 +1,155 @@
+const { Gio, GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+// import Todo from "../../services/todo.js";
+import { darkMode } from '../.miscutils/system.js';
+
+export function hasUnterminatedBackslash(inputString) {
+ // Use a regular expression to match a trailing odd number of backslashes
+ const regex = /\\+$/;
+ return regex.test(inputString);
+}
+
+export function launchCustomCommand(command) {
+ const args = command.toLowerCase().split(' ');
+ if (args[0] == '>raw') { // Mouse raw input
+ Utils.execAsync('hyprctl -j getoption input:accel_profile')
+ .then((output) => {
+ const value = JSON.parse(output)["str"].trim();
+ if (value != "[[EMPTY]]" && value != "") {
+ execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
+ }
+ else {
+ execAsync(['bash', '-c', `hyprctl keyword input:accel_profile flat`]).catch(print);
+ }
+ })
+ }
+ else if (args[0] == '>img') { // Change wallpaper
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
+ }
+ else if (args[0] == '>color') { // Generate colorscheme from color picker
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh --pick`, `&`]).catch(print);
+ }
+ else if (args[0] == '>light') { // Light mode
+ darkMode.value = false;
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && sed -i "1s/.*/light/" ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ }
+ else if (args[0] == '>dark') { // Dark mode
+ darkMode.value = true;
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && sed -i "1s/.*/dark/" ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ }
+ else if (args[0] == '>badapple') { // Black and white
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && sed -i "3s/.*/monochrome/" ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ }
+ else if (args[0] == '>material') { // Use material colors
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "material" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>pywal') { // Use Pywal (ik it looks shit but I'm not removing)
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "pywal" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>todo') { // Todo
+ Todo.add(args.slice(1).join(' '));
+ }
+ else if (args[0] == '>shutdown') { // Shut down
+ execAsync([`bash`, `-c`, `systemctl poweroff || loginctl poweroff`]).catch(print);
+ }
+ else if (args[0] == '>reboot') { // Reboot
+ execAsync([`bash`, `-c`, `systemctl reboot || loginctl reboot`]).catch(print);
+ }
+ else if (args[0] == '>sleep') { // Sleep
+ execAsync([`bash`, `-c`, `systemctl suspend || loginctl suspend`]).catch(print);
+ }
+ else if (args[0] == '>logout') { // Log out
+ execAsync([`bash`, `-c`, `pkill Hyprland || pkill sway`]).catch(print);
+ }
+}
+
+export function execAndClose(command, terminal) {
+ App.closeWindow('overview');
+ if (terminal) {
+ execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${command}"`, `&`]).catch(print);
+ }
+ else
+ execAsync(command).catch(print);
+}
+
+export function couldBeMath(str) {
+ const regex = /^[0-9.+*/-]/;
+ return regex.test(str);
+}
+
+export function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+function getFileIcon(fileInfo) {
+ let icon = fileInfo.get_icon();
+ if (icon) {
+ // Get the icon's name
+ return icon.get_names()[0];
+ } else {
+ // Default icon for files
+ return 'text-x-generic';
+ }
+}
+
+export function ls({ path = '~', silent = false }) {
+ let contents = [];
+ try {
+ let expandedPath = expandTilde(path);
+ if (expandedPath.endsWith('/'))
+ expandedPath = expandedPath.slice(0, -1);
+ let folder = Gio.File.new_for_path(expandedPath);
+
+ let enumerator = folder.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let fileInfo;
+ while ((fileInfo = enumerator.next_file(null)) !== null) {
+ let fileName = fileInfo.get_display_name();
+ let fileType = fileInfo.get_file_type();
+
+ let item = {
+ parentPath: expandedPath,
+ name: fileName,
+ type: fileType === Gio.FileType.DIRECTORY ? 'folder' : 'file',
+ icon: getFileIcon(fileInfo),
+ };
+
+ // Add file extension for files
+ if (fileType === Gio.FileType.REGULAR) {
+ let fileExtension = fileName.split('.').pop();
+ item.type = `${fileExtension}`;
+ }
+
+ contents.push(item);
+ contents.sort((a, b) => {
+ const aIsFolder = a.type.startsWith('folder');
+ const bIsFolder = b.type.startsWith('folder');
+ if (aIsFolder && !bIsFolder) {
+ return -1;
+ } else if (!aIsFolder && bIsFolder) {
+ return 1;
+ } else {
+ return a.name.localeCompare(b.name); // Sort alphabetically within folders and files
+ }
+ });
+ }
+ } catch (e) {
+ if (!silent) console.log(e);
+ }
+ return contents;
+}
diff --git a/config/ags/modules/overview/overview_hyprland.js b/config/ags/modules/overview/overview_hyprland.js
new file mode 100644
index 00000000..7a5b55c7
--- /dev/null
+++ b/config/ags/modules/overview/overview_hyprland.js
@@ -0,0 +1,423 @@
+// TODO
+// - Make client destroy/create not destroy and recreate the whole thing
+// - Active ws hook optimization: only update when moving to next group
+//
+const { Gdk, Gtk } = imports.gi;
+const { Gravity } = imports.gi.Gdk;
+import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../variables.js';
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+const { execAsync, exec } = Utils;
+import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
+import { dumpToWorkspace, swapWorkspace } from "./actions.js";
+import { substitute } from "../.miscutils/icons.js";
+
+const NUM_OF_WORKSPACES_SHOWN = userOptions.overview.numOfCols * userOptions.overview.numOfRows;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+const POPUP_CLOSE_TIME = 100; // ms
+
+const overviewTick = Variable(false);
+
+export default () => {
+ const clientMap = new Map();
+ let workspaceGroup = 0;
+ const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
+ label: `${label}`,
+ setup: (menuItem) => {
+ let submenu = new Gtk.Menu();
+ submenu.className = 'menu';
+
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const startWorkspace = offset + 1;
+ const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1;
+ for (let i = startWorkspace; i <= endWorkspace; i++) {
+ let button = new Gtk.MenuItem({
+ label: `Workspace ${i}`
+ });
+ button.connect("activate", () => {
+ // execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
+ actionFunc(thisWorkspace, i);
+ overviewTick.setValue(!overviewTick.value);
+ });
+ submenu.append(button);
+ }
+ menuItem.set_reserve_indicator(true);
+ menuItem.set_submenu(submenu);
+ }
+ })
+
+ const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, title, xwayland }, screenCoords) => {
+ const revealInfoCondition = (Math.min(w, h) * userOptions.overview.scale > 70);
+ if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
+ // Non-primary monitors
+ if (screenCoords.x != 0) x -= screenCoords.x;
+ if (screenCoords.y != 0) y -= screenCoords.y;
+ // Other offscreen adjustments
+ if (x + w <= 0) x += (Math.floor(x / SCREEN_WIDTH) * SCREEN_WIDTH);
+ else if (x < 0) { w = x + w; x = 0; }
+ if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT);
+ else if (y < 0) { h = y + h; y = 0; }
+ // Truncate if offscreen
+ if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x;
+ if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y;
+
+ const appIcon = Widget.Icon({
+ icon: substitute(c),
+ size: Math.min(w, h) * userOptions.overview.scale / 2.5,
+ });
+ return Widget.Button({
+ attribute: {
+ address, x, y, w, h, ws: id,
+ updateIconSize: (self) => {
+ appIcon.size = Math.min(self.attribute.w, self.attribute.h) * userOptions.overview.scale / 2.5;
+ },
+ },
+ className: 'overview-tasks-window',
+ hpack: 'start',
+ vpack: 'start',
+ css: `
+ margin-left: ${Math.round(x * userOptions.overview.scale)}px;
+ margin-top: ${Math.round(y * userOptions.overview.scale)}px;
+ margin-right: -${Math.round((x + w) * userOptions.overview.scale)}px;
+ margin-bottom: -${Math.round((y + h) * userOptions.overview.scale)}px;
+ `,
+ onClicked: (self) => {
+ App.closeWindow('overview');
+ Utils.timeout(POPUP_CLOSE_TIME, () => Hyprland.messageAsync(`dispatch focuswindow address:${address}`));
+ },
+ onMiddleClickRelease: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
+ onSecondaryClick: (button) => {
+ button.toggleClassName('overview-tasks-window-selected', true);
+ const menu = Widget.Menu({
+ className: 'menu',
+ children: [
+ Widget.MenuItem({
+ child: Widget.Label({
+ xalign: 0,
+ label: "Close (Middle-click)",
+ }),
+ onActivate: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Dump windows to workspace",
+ actionFunc: dumpToWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Swap windows with workspace",
+ actionFunc: swapWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ],
+ });
+ menu.connect("deactivate", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.connect("selection-done", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button
+ button.connect("destroy", () => menu.destroy());
+ },
+ child: Widget.Box({
+ homogeneous: true,
+ child: Widget.Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'spacing-v-5',
+ children: [
+ appIcon,
+ // TODO: Add xwayland tag instead of just having italics
+ Widget.Revealer({
+ transition: 'slide_down',
+ revealChild: revealInfoCondition,
+ child: Widget.Label({
+ maxWidthChars: 10, // Doesn't matter what number
+ truncate: 'end',
+ className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
+ css: `
+ font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * userOptions.overview.scale / 14.6}px;
+ margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * userOptions.overview.scale / 10}px;
+ `,
+ // If the title is too short, include the class
+ label: (title.length <= 1 ? `${c}: ${title}` : title),
+ })
+ })
+ ]
+ })
+ }),
+ tooltipText: `${c}: ${title}`,
+ setup: (button) => {
+ setupCursorHoverGrab(button);
+
+ button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
+ button.drag_source_set_icon_name(substitute(c));
+ // button.drag_source_set_icon_gicon(icon);
+
+ button.connect('drag-begin', (button) => { // On drag start, add the dragging class
+ button.toggleClassName('overview-tasks-window-dragging', true);
+ });
+ button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
+ data.set_text(address, address.length);
+ button.toggleClassName('overview-tasks-window-dragging', false);
+ });
+ },
+ });
+ }
+
+ const Workspace = (index) => {
+ // const fixed = Widget.Fixed({
+ // attribute: {
+ // put: (widget, x, y) => {
+ // fixed.put(widget, x, y);
+ // },
+ // move: (widget, x, y) => {
+ // fixed.move(widget, x, y);
+ // },
+ // }
+ // });
+ const fixed = Widget.Box({
+ attribute: {
+ put: (widget, x, y) => {
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by userOptions.overview.scale
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
+ `;
+ widget.css = newCss;
+ fixed.pack_start(widget, false, false, 0);
+ },
+ move: (widget, x, y) => {
+ if (!widget) return;
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by userOptions.overview.scale
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
+ `;
+ widget.css = newCss;
+ },
+ }
+ })
+ const WorkspaceNumber = ({ index, ...rest }) => Widget.Label({
+ className: 'overview-tasks-workspace-number',
+ label: `${index}`,
+ css: `
+ margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * userOptions.overview.scale * userOptions.overview.wsNumMarginScale}px;
+ font-size: ${SCREEN_HEIGHT * userOptions.overview.scale * userOptions.overview.wsNumScale}px;
+ `,
+ setup: (self) => self.hook(Hyprland.active.workspace, (self) => {
+ // Update when going to new ws group
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
+ self.label = `${currentGroup * NUM_OF_WORKSPACES_SHOWN + index}`;
+ }),
+ ...rest,
+ })
+ const widget = Widget.Box({
+ className: 'overview-tasks-workspace',
+ vpack: 'center',
+ css: `
+ min-width: ${SCREEN_WIDTH * userOptions.overview.scale}px;
+ min-height: ${SCREEN_HEIGHT * userOptions.overview.scale}px;
+ `,
+ children: [Widget.EventBox({
+ hexpand: true,
+ vexpand: true,
+ onPrimaryClick: () => {
+ App.closeWindow('overview');
+ Utils.timeout(POPUP_CLOSE_TIME, () => Hyprland.messageAsync(`dispatch workspace ${index}`));
+ },
+ setup: (eventbox) => {
+ eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
+ eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ Hyprland.messageAsync(`dispatch movetoworkspacesilent ${index + offset},address:${data.get_text()}`)
+ overviewTick.setValue(!overviewTick.value);
+ });
+ },
+ child: Widget.Overlay({
+ child: Widget.Box({}),
+ overlays: [
+ WorkspaceNumber({ index: index, hpack: 'start', vpack: 'start' }),
+ fixed
+ ]
+ }),
+ })],
+ });
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0);
+ widget.clear = () => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ clientMap.forEach((client, address) => {
+ if (!client) return;
+ if ((client.attribute.ws <= offset || client.attribute.ws > offset + NUM_OF_WORKSPACES_SHOWN) ||
+ (client.attribute.ws == offset + index)) {
+ client.destroy();
+ client = null;
+ clientMap.delete(address);
+ }
+ });
+ }
+ widget.set = (clientJson, screenCoords) => {
+ let c = clientMap.get(clientJson.address);
+ if (c) {
+ if (c.attribute?.ws !== clientJson.workspace.id) {
+ c.destroy();
+ c = null;
+ clientMap.delete(clientJson.address);
+ }
+ else if (c) {
+ c.attribute.w = clientJson.size[0];
+ c.attribute.h = clientJson.size[1];
+ c.attribute.updateIconSize(c);
+ fixed.attribute.move(c,
+ Math.max(0, clientJson.at[0] * userOptions.overview.scale),
+ Math.max(0, clientJson.at[1] * userOptions.overview.scale)
+ );
+ return;
+ }
+ }
+ const newWindow = Window(clientJson, screenCoords);
+ if (newWindow === null) return;
+ // clientMap.set(clientJson.address, newWindow);
+ fixed.attribute.put(newWindow,
+ Math.max(0, newWindow.attribute.x * userOptions.overview.scale),
+ Math.max(0, newWindow.attribute.y * userOptions.overview.scale)
+ );
+ clientMap.set(clientJson.address, newWindow);
+ };
+ widget.unset = (clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ let c = clientMap.get(clientAddress);
+ if (!c) return;
+ c.destroy();
+ c = null;
+ clientMap.delete(clientAddress);
+ };
+ widget.show = () => {
+ fixed.show_all();
+ }
+ return widget;
+ };
+
+ const arr = (s, n) => {
+ const array = [];
+ for (let i = 0; i < n; i++)
+ array.push(s + i);
+
+ return array;
+ };
+
+ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
+ children: arr(startWorkspace, workspaces).map(Workspace),
+ attribute: {
+ monitorMap: [],
+ getMonitorMap: (box) => {
+ execAsync('hyprctl -j monitors').then(monitors => {
+ box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => {
+ acc[item.id] = { x: item.x, y: item.y };
+ return acc;
+ }, {});
+ });
+ },
+ update: (box) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ if (!App.getWindow(windowName).visible) return;
+ Hyprland.messageAsync('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ kids.forEach(kid => kid.clear());
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ const childID = client.workspace.id - (offset + startWorkspace);
+ if (offset + startWorkspace <= client.workspace.id &&
+ client.workspace.id <= offset + startWorkspace + workspaces) {
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ if (kids[childID]) {
+ kids[childID].set(client, screenCoords);
+ }
+ continue;
+ }
+ }
+ kids.forEach(kid => kid.show());
+ }).catch(print);
+ },
+ updateWorkspace: (box, id) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ if (!( // Not in range, ignore
+ offset + startWorkspace <= id &&
+ id <= offset + startWorkspace + workspaces
+ )) return;
+ // if (!App.getWindow(windowName).visible) return;
+ Hyprland.messageAsync('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ if (client.workspace.id != id) continue;
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ kids[id - (offset + startWorkspace)]?.set(client, screenCoords);
+ }
+ kids[id - (offset + startWorkspace)]?.show();
+ }).catch(print);
+ },
+ },
+ setup: (box) => {
+ box.attribute.getMonitorMap(box);
+ box
+ .hook(overviewTick, (box) => box.attribute.update(box))
+ .hook(Hyprland, (box, clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const kids = box.get_children();
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ const id = client.workspace.id;
+
+ box.attribute.updateWorkspace(box, id);
+ kids[id - (offset + startWorkspace)]?.unset(clientAddress);
+ }, 'client-removed')
+ .hook(Hyprland, (box, clientAddress) => {
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ box.attribute.updateWorkspace(box, client.workspace.id);
+ }, 'client-added')
+ .hook(Hyprland.active.workspace, (box) => {
+ // Full update when going to new ws group
+ const previousGroup = box.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
+ if (currentGroup !== previousGroup) {
+ box.attribute.update(box);
+ box.attribute.workspaceGroup = currentGroup;
+ }
+ })
+ .hook(App, (box, name, visible) => { // Update on open
+ if (name == 'overview' && visible) box.attribute.update(box);
+ })
+ },
+ });
+
+ return Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Box({
+ vertical: true,
+ className: 'overview-tasks',
+ children: Array.from({ length: userOptions.overview.numOfRows }, (_, index) =>
+ OverviewRow({
+ startWorkspace: 1 + index * userOptions.overview.numOfCols,
+ workspaces: userOptions.overview.numOfCols,
+ })
+ )
+ }),
+ });
+} \ No newline at end of file
diff --git a/config/ags/modules/overview/searchbuttons.js b/config/ags/modules/overview/searchbuttons.js
new file mode 100644
index 00000000..f5892f31
--- /dev/null
+++ b/config/ags/modules/overview/searchbuttons.js
@@ -0,0 +1,163 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+import { searchItem } from './searchitem.js';
+import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
+
+export const DirectoryButton = ({ parentPath, name, type, icon }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Open',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open '${parentPath}/${name}'`, `&`]).catch(print);
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: icon,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const CalculationResultButton = ({ result, text }) => searchItem({
+ materialIconName: '󱖦 ',
+ name: `Math result`,
+ actionName: "Copy",
+ content: `${result}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ execAsync(['wl-copy', `${result}`]).catch(print);
+ },
+});
+
+export const DesktopEntryButton = (app) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Launch',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ app.launch();
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: app.iconName,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: app.name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
+ materialIconName: `${terminal ? 'terminal' : ' '}`,
+ name: `Run command`,
+ actionName: `Execute ${terminal ? 'in terminal' : ''}`,
+ content: `${command}`,
+ onActivate: () => execAndClose(command, terminal),
+ extraClassName: 'techfont',
+})
+
+export const CustomCommandButton = ({ text = '' }) => searchItem({
+ materialIconName: ' ',
+ name: 'Action',
+ actionName: 'Run',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ launchCustomCommand(text);
+ },
+});
+
+export const SearchButton = ({ text = '' }) => searchItem({
+ materialIconName: '󰜏 ',
+ name: 'Search the web',
+ actionName: 'Go',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open '${userOptions.search.engineBaseUrl}${text} ${['', ...userOptions.search.excludedSites].join(' -site:')}' &`]).catch(print);
+ },
+}); \ No newline at end of file
diff --git a/config/ags/modules/overview/searchitem.js b/config/ags/modules/overview/searchitem.js
new file mode 100644
index 00000000..2a3303a4
--- /dev/null
+++ b/config/ags/modules/overview/searchitem.js
@@ -0,0 +1,65 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const searchItem = ({ materialIconName, name, actionName, content, onActivate, extraClassName = '', ...rest }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: `${actionName}`,
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ })
+ return Widget.Button({
+ className: `overview-search-result-btn txt ${extraClassName}`,
+ onClicked: onActivate,
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Label({
+ className: `icon-material overview-search-results-icon`,
+ label: `${materialIconName}`,
+ }),
+ Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-smallie txt-subtext',
+ label: `${name}`,
+ truncate: "end",
+ }),
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-norm',
+ label: `${content}`,
+ truncate: "end",
+ }),
+ ]
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ],
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ });
+}
diff --git a/config/ags/modules/overview/windowcontent.js b/config/ags/modules/overview/windowcontent.js
new file mode 100644
index 00000000..7a19dd3c
--- /dev/null
+++ b/config/ags/modules/overview/windowcontent.js
@@ -0,0 +1,262 @@
+const { Gdk, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
+const { execAsync, exec } = Utils;
+import { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
+import {
+ CalculationResultButton, CustomCommandButton, DirectoryButton,
+ DesktopEntryButton, ExecuteCommandButton, SearchButton
+} from './searchbuttons.js';
+import { checkKeybind } from '../.widgetutils/keybind.js';
+
+// Add math funcs
+const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
+const pi = Math.PI;
+// trigonometric funcs for deg
+const sind = x => sin(x * pi / 180);
+const cosd = x => cos(x * pi / 180);
+const tand = x => tan(x * pi / 180);
+const cotd = x => cot(x * pi / 180);
+const asind = x => asin(x) * 180 / pi;
+const acosd = x => acos(x) * 180 / pi;
+const atand = x => atan(x) * 180 / pi;
+const acotd = x => acot(x) * 180 / pi;
+
+const MAX_RESULTS = 10;
+const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
+const OVERVIEW_WS_NUM_SCALE = 0.0;
+const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+
+function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+const OptionalOverview = async () => {
+ try {
+ return (await import('./overview_hyprland.js')).default();
+ } catch {
+ return Widget.Box({});
+ // return (await import('./overview_hyprland.js')).default();
+ }
+};
+
+const overviewContent = await OptionalOverview();
+
+export const SearchAndWindows = () => {
+ var _appSearchResults = [];
+
+ const ClickToClose = ({ ...props }) => Widget.EventBox({
+ ...props,
+ onPrimaryClick: () => App.closeWindow('overview'),
+ onSecondaryClick: () => App.closeWindow('overview'),
+ onMiddleClick: () => App.closeWindow('overview'),
+ });
+ const resultsBox = Widget.Box({
+ className: 'overview-search-results',
+ vertical: true,
+ vexpand: true,
+ });
+ const resultsRevealer = Widget.Revealer({
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ transition: 'slide_down',
+ // duration: 200,
+ hpack: 'center',
+ child: resultsBox,
+ });
+ const entryPromptRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: true,
+ hpack: 'center',
+ child: Widget.Label({
+ className: 'overview-search-prompt txt-small txt',
+ label: 'Type to search'
+ }),
+ });
+
+ const entryIconRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ hpack: 'end',
+ child: Widget.Label({
+ className: 'txt txt-large icon-material overview-search-icon',
+ label: ' ',
+ }),
+ });
+
+ const entryIcon = Widget.Box({
+ className: 'overview-search-prompt-box',
+ setup: box => box.pack_start(entryIconRevealer, true, true, 0),
+ });
+
+ const entry = Widget.Entry({
+ className: 'overview-search-box txt-small txt',
+ hpack: 'center',
+ onAccept: (self) => { // This is when you hit Enter
+ const text = self.text;
+ if (text.length == 0) return;
+ const isAction = text.startsWith('>');
+ const isDir = (['/', '~'].includes(entry.text[0]));
+
+ if (couldBeMath(text)) { // Eval on typing is dangerous, this is a workaround
+ try {
+ const fullResult = eval(text.replace(/\^/g, "**"));
+ // copy
+ execAsync(['wl-copy', `${fullResult}`]).catch(print);
+ App.closeWindow('overview');
+ return;
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ if (isDir) {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open "${expandTilde(text)}"`, `&`]).catch(print);
+ return;
+ }
+ if (_appSearchResults.length > 0) {
+ App.closeWindow('overview');
+ _appSearchResults[0].launch();
+ return;
+ }
+ else if (text[0] == '>') { // Custom commands
+ App.closeWindow('overview');
+ launchCustomCommand(text);
+ return;
+ }
+ // Fallback: Execute command
+ if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
+ if (text.startsWith('sudo'))
+ execAndClose(text, true);
+ else
+ execAndClose(text, false);
+ }
+
+ else {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open '${userOptions.search.engineBaseUrl}${text} ${['', ...userOptions.search.excludedSites].join(' -site:')}' &`]).catch(print);
+ }
+ },
+ onChange: (entry) => { // this is when you type
+ const isAction = entry.text[0] == '>';
+ const isDir = (['/', '~'].includes(entry.text[0]));
+ resultsBox.get_children().forEach(ch => ch.destroy());
+
+ // check empty if so then dont do stuff
+ if (entry.text == '') {
+ resultsRevealer.revealChild = false;
+ overviewContent.revealChild = true;
+ entryPromptRevealer.revealChild = true;
+ entryIconRevealer.revealChild = false;
+ entry.toggleClassName('overview-search-box-extended', false);
+ return;
+ }
+ const text = entry.text;
+ resultsRevealer.revealChild = true;
+ overviewContent.revealChild = false;
+ entryPromptRevealer.revealChild = false;
+ entryIconRevealer.revealChild = true;
+ entry.toggleClassName('overview-search-box-extended', true);
+ _appSearchResults = Applications.query(text);
+
+ // Calculate
+ if (couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
+ try {
+ const fullResult = eval(text.replace(/\^/g, "**"));
+ resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ if (isDir) {
+ var contents = [];
+ contents = ls({ path: text, silent: true });
+ contents.forEach((item) => {
+ resultsBox.add(DirectoryButton(item));
+ })
+ }
+ if (isAction) { // Eval on typing is dangerous, this is a workaround.
+ resultsBox.add(CustomCommandButton({ text: entry.text }));
+ }
+ // Add application entries
+ let appsToAdd = MAX_RESULTS;
+ _appSearchResults.forEach(app => {
+ if (appsToAdd == 0) return;
+ resultsBox.add(DesktopEntryButton(app));
+ appsToAdd--;
+ });
+
+ // Fallbacks
+ // if the first word is an actual command
+ if (!isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
+ resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
+ }
+
+ // Add fallback: search
+ resultsBox.add(SearchButton({ text: entry.text }));
+ resultsBox.show_all();
+ },
+ });
+ return Widget.Box({
+ vertical: true,
+ children: [
+ ClickToClose({ // Top margin. Also works as a click-outside-to-close thing
+ child: Widget.Box({
+ className: 'bar-height',
+ })
+ }),
+ Widget.Box({
+ hpack: 'center',
+ children: [
+ entry,
+ Widget.Box({
+ className: 'overview-search-icon-box',
+ setup: (box) => {
+ box.pack_start(entryPromptRevealer, true, true, 0)
+ },
+ }),
+ entryIcon,
+ ]
+ }),
+ overviewContent,
+ resultsRevealer,
+ ],
+ setup: (self) => self
+ .hook(App, (_b, name, visible) => {
+ if (name == 'overview' && !visible) {
+ resultsBox.children = [];
+ entry.set_text('');
+ }
+ })
+ .on('key-press-event', (widget, event) => { // Typing
+ const keyval = event.get_keyval()[1];
+ const modstate = event.get_state()[1];
+ if (checkKeybind(event, userOptions.keybinds.overview.altMoveLeft))
+ entry.set_position(Math.max(entry.get_position() - 1, 0));
+ else if (checkKeybind(event, userOptions.keybinds.overview.altMoveRight))
+ entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
+ else if (checkKeybind(event, userOptions.keybinds.overview.deleteToEnd)) {
+ const text = entry.get_text();
+ const pos = entry.get_position();
+ const newText = text.slice(0, pos);
+ entry.set_text(newText);
+ entry.set_position(newText.length);
+ }
+ else if (!(modstate & Gdk.ModifierType.CONTROL_MASK)) { // Ctrl not held
+ if (keyval >= 32 && keyval <= 126 && widget != entry) {
+ Utils.timeout(1, () => entry.grab_focus());
+ entry.set_text(entry.text + String.fromCharCode(keyval));
+ entry.set_position(-1);
+ }
+ }
+ })
+ ,
+ });
+};
diff --git a/config/ags/user/style.css b/config/ags/user/style.css
new file mode 100644
index 00000000..7941573d
--- /dev/null
+++ b/config/ags/user/style.css
@@ -0,0 +1,190 @@
+*:not(popover) {
+ all: unset;
+}
+
+@import '../../../.cache/wal/colors-waybar.css';
+/* @import '../../../.cache/wal/colors-waybar-rgba.css'; */
+
+
+widget {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+}
+
+.overview-window {
+ margin-top: 2.727rem;
+}
+
+.overview-search-box {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid @color7;
+ border-left: 1px solid @color7;
+ border-right: 1px solid @color7;
+ border-bottom: 1px solid @color7;
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ min-width: 13.636rem;
+ min-height: 3.409rem;
+ padding: 0rem 1.364rem;
+ padding-right: 2.864rem;
+ background-color: alpha(@color0, 0.5);
+ /* color: #EBDFED; */
+ color: alpha(@color7, 0.9);
+ caret-color: transparent;
+}
+.overview-search-box selection {
+ background-color: #DEBCDF;
+ color: #402843;
+}
+
+.overview-search-box-extended {
+ min-width: 25.909rem;
+ caret-color: #FDD9FD;
+}
+
+.overview-search-prompt {
+ /* color: #988D9D; */
+ color: alpha(@color7, 0.9);
+}
+
+.overview-search-icon {
+ margin: 0rem 1.023rem;
+}
+
+.overview-search-prompt-box {
+ margin-left: -18.545rem;
+ margin-right: 0.544rem;
+}
+
+.overview-search-icon-box {
+ margin-left: -18.545rem;
+ margin-right: 0.544rem;
+}
+
+.overview-search-results {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid @color7;
+ border-left: 1px solid @color7;
+ border-right: 1px solid @color7;
+ border-bottom: 1px solid @color7;
+ box-shadow: 0px 2px 3px @color8;
+ margin: 0.476rem;
+ min-width: 28.773rem;
+ padding: 0.682rem;
+ background-color: @color2;
+ color: #EBDFED;
+}
+
+.overview-search-results-icon {
+ margin: 0rem 0.682rem;
+ font-size: 2.386rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+}
+
+.overview-search-results-txt {
+ margin-right: 0.682rem;
+}
+
+.overview-search-results-txt-cmd {
+ margin-right: 0.682rem;
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font", "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+ font-size: 1.227rem;
+}
+
+.overview-search-result-btn {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.341rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+ caret-color: transparent;
+}
+
+.overview-search-result-btn:hover,
+.overview-search-result-btn:focus {
+ background-color: #2E2832;
+}
+
+.overview-search-result-btn:active {
+ background-color: #39323D;
+}
+
+.overview-tasks {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid @color7;
+ border-left: 1px solid @color7;
+ border-right: 1px solid @color7;
+ border-bottom: 1px solid @color7;
+ box-shadow: 0px 2px 3px @color5;
+ margin: 0.476rem;
+ padding: 0.341rem;
+ /* background-color: rgba(49, 50, 68, 0.8); */
+ background-color: alpha(@color9, 0.4);
+ color: #EBDFED;
+}
+
+.overview-tasks-workspace {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ margin: 0.341rem;
+ /* background-color: #26233A; */
+ background-image: url('../../rofi/.current_wallpaper');
+ background-size: cover;
+ background-position: center;
+ border: 0.068rem solid alpha(@color4, 0.5);
+}
+
+.overview-tasks-workspace-number {
+ font-family: "Open Sans", "Noto Sans", sans-serif;
+ color: #CFC2D3;
+}
+
+.overview-tasks-window {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ background-color: alpha(@color3, .7);
+ /* background-color: @color_a3; */
+ /* background-color: rgba(46, 40, 50, 0.8); */
+ color: #EBDFED;
+ border: 0.068rem solid @color7;
+}
+
+.overview-tasks-window:hover,
+.overview-tasks-window:focus {
+ background-color: @color9;
+}
+
+.overview-tasks-window:active {
+ background-color: @color9;
+}
+
+.overview-tasks-window-selected {
+ background-color: @color9;
+}
+
+.overview-tasks-window-dragging {
+ opacity: 0.2;
+}
+
+.growingRadial {
+ transition: 300ms cubic-bezier(0.2, 0, 0, 1);
+}
+
+.fadingRadial {
+ transition: 50ms cubic-bezier(0.2, 0, 0, 1);
+}
+
+.sidebar-pinned {
+ margin: 0rem;
+ border-radius: 0rem;
+ border-bottom-right-radius: 1.705rem;
+ border: 0rem solid;
+}
+
+/*# sourceMappingURL=style.css.map */
diff --git a/config/ags/user_options.js b/config/ags/user_options.js
new file mode 100644
index 00000000..9105decb
--- /dev/null
+++ b/config/ags/user_options.js
@@ -0,0 +1,21 @@
+
+const userConfigOptions = {
+ // For every option, see ~/.config/ags/modules/.configuration/user_options.js
+ // (vscode users ctrl+click this: file://./modules/.configuration/user_options.js)
+ // (vim users: `:vsp` to split window, move cursor to this path, press `gf`. `Ctrl-w` twice to switch between)
+ // options listed in this file will override the default ones in the above file
+ // Here's an example
+ 'overview':{
+ 'scale': 0.15,
+ 'numOfRows': 2
+ },
+ 'keybinds': {
+ 'sidebar': {
+ 'pin': "Ctrl+p",
+ 'nextTab': "Ctrl+Page_Down",
+ 'prevTab': "Ctrl+Page_Up",
+ },
+ },
+}
+
+export default userConfigOptions;
diff --git a/config/ags/variables.js b/config/ags/variables.js
new file mode 100644
index 00000000..ec4aedec
--- /dev/null
+++ b/config/ags/variables.js
@@ -0,0 +1,21 @@
+const { Gtk } = imports.gi;
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets/icons`);
+
+// Screen size
+export const SCREEN_WIDTH = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1 | head -1" | awk '{print $1}'`));
+export const SCREEN_HEIGHT = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1" | awk '{print $1}'`));
+
+// Mode switching
+export const currentShellMode = Variable('normal', {}) // normal, focus
+globalThis['currentMode'] = currentShellMode;
+globalThis['cycleMode'] = () => {
+ if (currentShellMode.value === 'normal') {
+ currentShellMode.value = 'focus';
+ } else {
+ currentShellMode.value = 'normal';
+ }
+}
diff --git a/config/hypr/UserConfigs/Startup_Apps.conf b/config/hypr/UserConfigs/Startup_Apps.conf
index ee387d8f..c6b96ee5 100644
--- a/config/hypr/UserConfigs/Startup_Apps.conf
+++ b/config/hypr/UserConfigs/Startup_Apps.conf
@@ -25,6 +25,7 @@ exec-once = nm-applet --indicator &
exec-once = swaync &
#exec-once = blueman-applet &
#exec-once = rog-control-center &
+exec-once = ags &
#clipboard manager
diff --git a/config/hypr/configs/Keybinds.conf b/config/hypr/configs/Keybinds.conf
index 1946cc27..1cc8e2af 100644
--- a/config/hypr/configs/Keybinds.conf
+++ b/config/hypr/configs/Keybinds.conf
@@ -30,6 +30,7 @@ bind = $mainMod ALT, L, exec, $scriptsDir/ChangeLayout.sh # Toggle Master or Dwi
bind = $mainMod ALT, V, exec, $scriptsDir/ClipManager.sh # Clipboard Manager
bind = $mainMod SHIFT, N, exec, swaync-client -t -sw # swayNC panel
bind = SHIFT, ALT_L, exec, $scriptsDir/SwitchKeyboardLayout.sh # Switch Keyboard Layout
+bind = $mainMod, A, exec, ags -t 'overview'
# FEATURES / EXTRAS (UserScripts)
bind = $mainMod, E, exec, $UserScripts/QuickEdit.sh # Quick Edit Hyprland Settings
diff --git a/config/hypr/scripts/Refresh.sh b/config/hypr/scripts/Refresh.sh
index 15516dff..bf806e01 100755
--- a/config/hypr/scripts/Refresh.sh
+++ b/config/hypr/scripts/Refresh.sh
@@ -15,13 +15,15 @@ file_exists() {
}
# Kill already running processes
-_ps=(waybar rofi swaync)
+_ps=(waybar rofi swaync ags)
for _prs in "${_ps[@]}"; do
if pidof "${_prs}" >/dev/null; then
pkill "${_prs}"
fi
done
+ags -q
+
sleep 0.3
# Relaunch waybar
waybar &
@@ -30,6 +32,9 @@ waybar &
sleep 0.5
swaync > /dev/null 2>&1 &
+# relaunch ags
+ags &
+
# Relaunching rainbow borders if the script exists
sleep 1
if file_exists "${UserScripts}/RainbowBorders.sh"; then
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage