Browse Source
Updated search iscon for overview search result suggested action Removed excluded site from overview web search Updated calculator icon for overview Updated overview search action icon spacing Added initial pywal integration to overview search and updated seach iconpull/231/head
23 changed files with 1781 additions and 1 deletions
@ -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), |
||||||
|
}); |
||||||
|
|
||||||
@ -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; |
||||||
@ -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; |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
|
||||||
|
export function clamp(x, min, max) { |
||||||
|
return Math.min(Math.max(x, min), max); |
||||||
|
} |
||||||
@ -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'; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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, |
||||||
|
}), |
||||||
|
}); |
||||||
|
} |
||||||
@ -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); |
||||||
@ -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); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
@ -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; |
||||||
|
} |
||||||
@ -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)); |
||||||
|
} |
||||||
@ -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(), |
||||||
|
] |
||||||
|
}), |
||||||
|
}) |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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, |
||||||
|
}) |
||||||
|
) |
||||||
|
}), |
||||||
|
}); |
||||||
|
} |
||||||
@ -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; |
||||||
|
}) |
||||||
|
, |
||||||
|
}); |
||||||
|
} |
||||||
@ -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 */ |
||||||
@ -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; |
||||||
@ -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'; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue