301 lines
11 KiB
JavaScript
Executable File
301 lines
11 KiB
JavaScript
Executable File
const { Gtk, GLib } = imports.gi;
|
|
import App from 'resource:///com/github/Aylur/ags/app.js';
|
|
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
|
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
|
const { EventBox, Button } = Widget;
|
|
|
|
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
|
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
|
|
const { execAsync, exec } = Utils;
|
|
const { Box, Revealer } = Widget;
|
|
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
|
import { getAllFiles, searchIcons } from './icons.js'
|
|
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
|
import { substitute } from '../.miscutils/icons.js';
|
|
|
|
const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1)
|
|
|
|
let isPinned = false
|
|
let cachePath = new Map()
|
|
|
|
let timers = []
|
|
|
|
function clearTimes() {
|
|
timers.forEach(e => GLib.source_remove(e))
|
|
timers = []
|
|
}
|
|
|
|
function ExclusiveWindow(client) {
|
|
const fn = [
|
|
(client) => !(client !== null && client !== undefined),
|
|
// Jetbrains
|
|
(client) => client.title.includes("win"),
|
|
// Vscode
|
|
(client) => client.title === '' && client.class === ''
|
|
]
|
|
|
|
for (const item of fn) { if (item(client)) { return true } }
|
|
return false
|
|
}
|
|
|
|
const focus = ({ address }) => Utils.execAsync(`hyprctl dispatch focuswindow address:${address}`).catch(print);
|
|
|
|
const DockSeparator = (props = {}) => Box({
|
|
...props,
|
|
className: 'dock-separator',
|
|
})
|
|
|
|
const PinButton = () => Widget.Button({
|
|
className: 'dock-app-btn dock-app-btn-animate',
|
|
tooltipText: 'Pin Dock',
|
|
child: Widget.Box({
|
|
homogeneous: true,
|
|
className: 'dock-app-icon txt',
|
|
child: MaterialIcon('push_pin', 'hugeass')
|
|
}),
|
|
onClicked: (self) => {
|
|
isPinned = !isPinned
|
|
self.className = `${isPinned ? "pinned-dock-app-btn" : "dock-app-btn animate"} dock-app-btn-animate`
|
|
},
|
|
setup: setupCursorHover,
|
|
})
|
|
|
|
const LauncherButton = () => Widget.Button({
|
|
className: 'dock-app-btn dock-app-btn-animate',
|
|
tooltipText: 'Open launcher',
|
|
child: Widget.Box({
|
|
homogeneous: true,
|
|
className: 'dock-app-icon txt',
|
|
child: MaterialIcon('apps', 'hugerass')
|
|
}),
|
|
onClicked: (self) => {
|
|
App.toggleWindow('overview');
|
|
},
|
|
setup: setupCursorHover,
|
|
})
|
|
|
|
const AppButton = ({ icon, ...rest }) => Widget.Revealer({
|
|
attribute: {
|
|
'workspace': 0
|
|
},
|
|
revealChild: false,
|
|
transition: 'slide_right',
|
|
transitionDuration: userOptions.animations.durationLarge,
|
|
child: Widget.Button({
|
|
...rest,
|
|
className: 'dock-app-btn dock-app-btn-animate',
|
|
child: Widget.Box({
|
|
child: Widget.Overlay({
|
|
child: Widget.Box({
|
|
homogeneous: true,
|
|
className: 'dock-app-icon',
|
|
child: Widget.Icon({
|
|
icon: icon,
|
|
}),
|
|
}),
|
|
overlays: [Widget.Box({
|
|
class_name: 'indicator',
|
|
vpack: 'end',
|
|
hpack: 'center',
|
|
})],
|
|
}),
|
|
}),
|
|
setup: (button) => {
|
|
setupCursorHover(button);
|
|
}
|
|
})
|
|
});
|
|
|
|
const Taskbar = (monitor) => Widget.Box({
|
|
className: 'dock-apps',
|
|
attribute: {
|
|
monitor: monitor,
|
|
'map': new Map(),
|
|
'clientSortFunc': (a, b) => {
|
|
return a.attribute.workspace > b.attribute.workspace;
|
|
},
|
|
'update': (box, monitor) => {
|
|
for (let i = 0; i < Hyprland.clients.length; i++) {
|
|
const client = Hyprland.clients[i];
|
|
if (client["pid"] == -1) return;
|
|
const appClass = substitute(client.class);
|
|
// for (const appName of userOptions.dock.pinnedApps) {
|
|
// if (appClass.includes(appName.toLowerCase()))
|
|
// return null;
|
|
// }
|
|
let appClassLower = appClass.toLowerCase()
|
|
let path = ''
|
|
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
|
|
else {
|
|
path = searchIcons(appClass.toLowerCase(), icon_files)
|
|
cachePath[appClassLower] = path
|
|
}
|
|
if (path === '') { path = substitute(appClass) }
|
|
const newButton = AppButton({
|
|
icon: path,
|
|
tooltipText: `${client.title} (${appClass})`,
|
|
onClicked: () => focus(client),
|
|
});
|
|
newButton.attribute.workspace = client.workspace.id;
|
|
newButton.revealChild = true;
|
|
box.attribute.map.set(client.address, newButton);
|
|
}
|
|
box.children = Array.from(box.attribute.map.values());
|
|
},
|
|
'add': (box, address, monitor) => {
|
|
if (!address) { // First active emit is undefined
|
|
box.attribute.update(box);
|
|
return;
|
|
}
|
|
const newClient = Hyprland.clients.find(client => {
|
|
return client.address == address;
|
|
});
|
|
if (ExclusiveWindow(newClient)) { return }
|
|
let appClass = newClient.class
|
|
let appClassLower = appClass.toLowerCase()
|
|
let path = ''
|
|
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
|
|
else {
|
|
path = searchIcons(appClassLower, icon_files)
|
|
cachePath[appClassLower] = path
|
|
}
|
|
if (path === '') { path = substitute(appClass) }
|
|
const newButton = AppButton({
|
|
icon: path,
|
|
tooltipText: `${newClient.title} (${appClass})`,
|
|
onClicked: () => focus(newClient),
|
|
})
|
|
newButton.attribute.workspace = newClient.workspace.id;
|
|
box.attribute.map.set(address, newButton);
|
|
box.children = Array.from(box.attribute.map.values());
|
|
newButton.revealChild = true;
|
|
},
|
|
'remove': (box, address) => {
|
|
if (!address) return;
|
|
|
|
const removedButton = box.attribute.map.get(address);
|
|
if (!removedButton) return;
|
|
removedButton.revealChild = false;
|
|
|
|
Utils.timeout(userOptions.animations.durationLarge, () => {
|
|
removedButton.destroy();
|
|
box.attribute.map.delete(address);
|
|
box.children = Array.from(box.attribute.map.values());
|
|
})
|
|
},
|
|
},
|
|
setup: (self) => {
|
|
self.hook(Hyprland, (box, address) => box.attribute.add(box, address, self.monitor), 'client-added')
|
|
.hook(Hyprland, (box, address) => box.attribute.remove(box, address, self.monitor), 'client-removed')
|
|
Utils.timeout(100, () => self.attribute.update(self));
|
|
},
|
|
});
|
|
|
|
const PinnedApps = () => Widget.Box({
|
|
class_name: 'dock-apps',
|
|
homogeneous: true,
|
|
children: userOptions.dock.pinnedApps
|
|
.map(term => ({ app: Applications.query(term)?.[0], term }))
|
|
.filter(({ app }) => app)
|
|
.map(({ app, term = true }) => {
|
|
const newButton = AppButton({
|
|
// different icon, emm...
|
|
icon: userOptions.dock.searchPinnedAppIcons ?
|
|
searchIcons(app.name, icon_files) :
|
|
app.icon_name,
|
|
onClicked: () => {
|
|
for (const client of Hyprland.clients) {
|
|
if (client.class.toLowerCase().includes(term))
|
|
return focus(client);
|
|
}
|
|
|
|
app.launch();
|
|
},
|
|
onMiddleClick: () => app.launch(),
|
|
tooltipText: app.name,
|
|
setup: (self) => {
|
|
self.revealChild = true;
|
|
self.hook(Hyprland, button => {
|
|
const running = Hyprland.clients
|
|
.find(client => client.class.toLowerCase().includes(term)) || false;
|
|
|
|
button.toggleClassName('notrunning', !running);
|
|
button.toggleClassName('focused', Hyprland.active.client.address == running.address);
|
|
button.set_tooltip_text(running ? running.title : app.name);
|
|
}, 'notify::clients')
|
|
},
|
|
})
|
|
newButton.revealChild = true;
|
|
return newButton;
|
|
}),
|
|
});
|
|
|
|
export default (monitor = 0) => {
|
|
const dockContent = Box({
|
|
className: 'dock-bg spacing-h-5',
|
|
children: [
|
|
PinButton(),
|
|
PinnedApps(),
|
|
DockSeparator(),
|
|
Taskbar(),
|
|
LauncherButton(),
|
|
]
|
|
})
|
|
const dockRevealer = Revealer({
|
|
attribute: {
|
|
'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
|
|
if (userOptions.dock.monitorExclusivity)
|
|
self.revealChild = Hyprland.active.monitor.id === monitor;
|
|
else
|
|
self.revealChild = true;
|
|
|
|
return self.revealChild
|
|
}
|
|
},
|
|
revealChild: false,
|
|
transition: 'slide_up',
|
|
transitionDuration: userOptions.animations.durationLarge,
|
|
child: dockContent,
|
|
setup: (self) => {
|
|
const callback = (self, trigger) => {
|
|
if (!userOptions.dock.trigger.includes(trigger)) return
|
|
const flag = self.attribute.updateShow(self)
|
|
|
|
if (flag) clearTimes();
|
|
|
|
const hidden = userOptions.dock.autoHide.find(e => e["trigger"] === trigger)
|
|
|
|
if (hidden) {
|
|
let id = Utils.timeout(hidden.interval, () => {
|
|
if (!isPinned) { self.revealChild = false }
|
|
timers = timers.filter(e => e !== id)
|
|
})
|
|
timers.push(id)
|
|
}
|
|
}
|
|
|
|
self
|
|
// .hook(Hyprland, (self) => self.attribute.updateShow(self))
|
|
.hook(Hyprland.active.workspace, self => callback(self, "workspace-active"))
|
|
.hook(Hyprland.active.client, self => callback(self, "client-active"))
|
|
.hook(Hyprland, self => callback(self, "client-added"), "client-added")
|
|
.hook(Hyprland, self => callback(self, "client-removed"), "client-removed")
|
|
},
|
|
})
|
|
return EventBox({
|
|
onHover: () => {
|
|
dockRevealer.revealChild = true;
|
|
clearTimes()
|
|
},
|
|
child: Box({
|
|
homogeneous: true,
|
|
css: `min-height: ${userOptions.dock.hiddenThickness}px;`,
|
|
children: [dockRevealer],
|
|
}),
|
|
setup: self => self.on("leave-notify-event", () => {
|
|
if (!isPinned) dockRevealer.revealChild = false;
|
|
clearTimes()
|
|
})
|
|
})
|
|
}
|