From b970c2fcf3368cd4406053b23dbbfa051798f8e8 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Fri, 18 Oct 2024 00:44:45 -0400 Subject: [PATCH] feat(agsV2): add applauncher --- nixosModules/ags/default.nix | 18 +-- nixosModules/ags/v2/app.ts | 2 + nixosModules/ags/v2/default.nix | 2 +- nixosModules/ags/v2/lib.ts | 32 ++++ nixosModules/ags/v2/package-lock.json | Bin 74534 -> 74875 bytes nixosModules/ags/v2/package.json | 1 + nixosModules/ags/v2/style.scss | 3 +- .../ags/v2/widgets/applauncher/app-item.tsx | 72 +++++++++ .../ags/v2/widgets/applauncher/launch.ts | 27 ++++ .../ags/v2/widgets/applauncher/main.tsx | 146 ++++++++++++++++++ .../ags/v2/widgets/applauncher/style.scss | 32 ++++ .../ags/v2/widgets/misc/popup-window.tsx | 17 +- nixosModules/ags/v2/widgets/notifs/style.scss | 4 - 13 files changed, 329 insertions(+), 27 deletions(-) create mode 100644 nixosModules/ags/v2/widgets/applauncher/app-item.tsx create mode 100644 nixosModules/ags/v2/widgets/applauncher/launch.ts create mode 100644 nixosModules/ags/v2/widgets/applauncher/main.tsx create mode 100644 nixosModules/ags/v2/widgets/applauncher/style.scss diff --git a/nixosModules/ags/default.nix b/nixosModules/ags/default.nix index e56cc935..927c70a4 100644 --- a/nixosModules/ags/default.nix +++ b/nixosModules/ags/default.nix @@ -140,7 +140,7 @@ in { }; wayland.windowManager.hyprland = let - runAgs = "pgrep ags -a | grep '/bin/gjs' && ags"; + runAgs = cmd: "pgrep ags -a | grep '/bin/gjs' && ags ${cmd} || agsV2 ${cmd}"; in { settings = { animations = { @@ -169,18 +169,18 @@ in { ]; bind = [ - "$mainMod SHIFT, E , exec, ${runAgs} -t win-powermenu" - "$mainMod , D , exec, ${runAgs} -t win-applauncher" - "$mainMod , V , exec, ${runAgs} -t win-clipboard" - " , Print, exec, ${runAgs} -t win-screenshot" + "$mainMod SHIFT, E , exec, ${runAgs "-t win-powermenu"}" + "$mainMod , D , exec, ${runAgs "-t win-applauncher"}" + "$mainMod , V , exec, ${runAgs "-t win-clipboard"}" + " , Print, exec, ${runAgs "-t win-screenshot"}" ]; binde = [ ## Brightness control - ", XF86MonBrightnessUp , exec, ${runAgs} -r 'Brightness.screen += 0.05'" - ", XF86MonBrightnessDown, exec, ${runAgs} -r 'Brightness.screen -= 0.05'" + ", XF86MonBrightnessUp , exec, ${runAgs "-r 'Brightness.screen += 0.05'"}" + ", XF86MonBrightnessDown, exec, ${runAgs "-r 'Brightness.screen -= 0.05'"}" ]; - bindn = [" , Escape , exec, ${runAgs} -r 'closeAll()'"]; - bindr = ["CAPS, Caps_Lock, exec, ${runAgs} -r 'Brightness.fetchCapsState()'"]; + bindn = [" , Escape , exec, ${runAgs "-r 'closeAll()'"}"]; + bindr = ["CAPS, Caps_Lock, exec, ${runAgs "-r 'Brightness.fetchCapsState()'"}"]; }; }; }) diff --git a/nixosModules/ags/v2/app.ts b/nixosModules/ags/v2/app.ts index f99ce9a8..9ca7c0b1 100644 --- a/nixosModules/ags/v2/app.ts +++ b/nixosModules/ags/v2/app.ts @@ -2,6 +2,7 @@ import { App } from 'astal/gtk3'; import style from './style.scss'; +import AppLauncher from './widgets/applauncher/main'; import Bar from './widgets/bar/wim'; import BgFade from './widgets/bg-fade/main'; import Corners from './widgets/corners/main'; @@ -14,6 +15,7 @@ App.start({ css: style, main: () => { + AppLauncher(); Bar(); BgFade(); Corners(); diff --git a/nixosModules/ags/v2/default.nix b/nixosModules/ags/v2/default.nix index b5902532..77ce6b13 100644 --- a/nixosModules/ags/v2/default.nix +++ b/nixosModules/ags/v2/default.nix @@ -37,7 +37,7 @@ self: { }) // { "${configDir}/node_modules".source = - buildNodeModules ./. "sha256-u2LDbIKA32urN/NqqJrdAl46pUloPaoa5HoYDRJDh1k="; + buildNodeModules ./. "sha256-aWVnn4G5ypOacju+0wwPeie7JEZoXYDY2qPOTmCdXsg="; "${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" { "$schema" = "https://json.schemastore.org/tsconfig"; diff --git a/nixosModules/ags/v2/lib.ts b/nixosModules/ags/v2/lib.ts index 1aac07eb..145c22d0 100644 --- a/nixosModules/ags/v2/lib.ts +++ b/nixosModules/ags/v2/lib.ts @@ -77,3 +77,35 @@ export const hyprMessage = (message: string) => new Promise(( rejection(e); } }); + +export const centerCursor = async(): Promise => { + let x: number; + let y: number; + const monitor = (JSON.parse(await hyprMessage('j/monitors')) as AstalHyprland.Monitor[]) + .find((m) => m.focused) as AstalHyprland.Monitor; + + // @ts-expect-error this should be good + switch (monitor.transform) { + case 1: + x = monitor.x - (monitor.height / 2); + y = monitor.y - (monitor.width / 2); + break; + + case 2: + x = monitor.x - (monitor.width / 2); + y = monitor.y - (monitor.height / 2); + break; + + case 3: + x = monitor.x + (monitor.height / 2); + y = monitor.y + (monitor.width / 2); + break; + + default: + x = monitor.x + (monitor.width / 2); + y = monitor.y + (monitor.height / 2); + break; + } + + await hyprMessage(`dispatch movecursor ${x} ${y}`); +}; diff --git a/nixosModules/ags/v2/package-lock.json b/nixosModules/ags/v2/package-lock.json index a5f2636355c2502d563434bd10adb6c1bc9cd659..32f3b59649bc39059df49c4dd22e73aafdf6371a 100644 GIT binary patch delta 165 zcmZ2>jOF(UmJQz-xzeiAl&ln#4D?JlKV;8>u$*~Td;$@*13AcwmTt-3B|fELi7xIX?yma5A>q#2S;mte7RXJm=;WRJ Ms7Pt^n~he50HD1%B>(^b delta 27 lcmV+$0ObGs$ONXw1hDr3lc0wPvmXNOuakVDD6?s^5^=*@4Ltw= diff --git a/nixosModules/ags/v2/package.json b/nixosModules/ags/v2/package.json index e8997f45..2a8a6c82 100644 --- a/nixosModules/ags/v2/package.json +++ b/nixosModules/ags/v2/package.json @@ -9,6 +9,7 @@ "@types/node": "22.7.5", "eslint": "9.12.0", "eslint-plugin-jsdoc": "50.3.2", + "fzf": "0.5.2", "jiti": "2.3.3", "typescript": "5.6.3", "typescript-eslint": "8.8.1" diff --git a/nixosModules/ags/v2/style.scss b/nixosModules/ags/v2/style.scss index 429f802f..1809da04 100644 --- a/nixosModules/ags/v2/style.scss +++ b/nixosModules/ags/v2/style.scss @@ -32,7 +32,7 @@ $popover_fg_color: #f8f8f2; $shade_color: #383838; $scrollbar_outline_color: rgba(0, 0, 0, 0.5); -window { +window, viewport { all: unset; } @@ -44,5 +44,6 @@ window { box-shadow: 8px 8px rgba($accent_color, 1); } +@import 'widgets/applauncher/style.scss'; @import 'widgets/bar/style.scss'; @import 'widgets/notifs/style.scss'; diff --git a/nixosModules/ags/v2/widgets/applauncher/app-item.tsx b/nixosModules/ags/v2/widgets/applauncher/app-item.tsx new file mode 100644 index 00000000..8f5fb6dc --- /dev/null +++ b/nixosModules/ags/v2/widgets/applauncher/app-item.tsx @@ -0,0 +1,72 @@ +import { App, Gtk, Widget } from 'astal/gtk3'; +import { register } from 'astal/gobject'; + +/* Types */ +import AstalApps from 'gi://AstalApps?version=0.1'; +type AppItemProps = Widget.BoxProps & { + app: AstalApps.Application +}; + + +@register() +export class AppItem extends Widget.Box { + readonly app: AstalApps.Application; + + constructor({ + app, + hexpand = true, + className = '', + ...rest + }: AppItemProps) { + super({ + ...rest, + className: `app ${className}`, + hexpand, + }); + this.app = app; + + const icon = ( + + ) as Widget.Icon; + + if (app.get_icon_name() && Widget.Icon.lookup_icon(app.get_icon_name())) { + icon.icon = this.app.get_icon_name(); + } + else { + const iconString = this.app.get_key('Icon'); + + App.add_icons(iconString); + icon.icon = iconString; + } + + const textBox = ( + + + ); + + this.add(icon); + this.add(textBox); + } +} + +export default (props: AppItemProps) => new AppItem(props); diff --git a/nixosModules/ags/v2/widgets/applauncher/launch.ts b/nixosModules/ags/v2/widgets/applauncher/launch.ts new file mode 100644 index 00000000..956d9d08 --- /dev/null +++ b/nixosModules/ags/v2/widgets/applauncher/launch.ts @@ -0,0 +1,27 @@ +import { execAsync } from 'astal'; + +import AstalApps from 'gi://AstalApps?version=0.1'; + + +const bash = async(strings: TemplateStringsArray | string, ...values: unknown[]) => { + const cmd = typeof strings === 'string' ? + strings : + strings.flatMap((str, i) => `${str}${values[i] ?? ''}`) + .join(''); + + return execAsync(['bash', '-c', cmd]).catch((err) => { + console.error(cmd, err); + + return ''; + }); +}; + +export const launchApp = (app: AstalApps.Application) => { + const exe = app.executable + .split(/\s+/) + .filter((str) => !str.startsWith('%') && !str.startsWith('@')) + .join(' '); + + bash(`${exe} &`); + app.frequency += 1; +}; diff --git a/nixosModules/ags/v2/widgets/applauncher/main.tsx b/nixosModules/ags/v2/widgets/applauncher/main.tsx new file mode 100644 index 00000000..2ebbdf6b --- /dev/null +++ b/nixosModules/ags/v2/widgets/applauncher/main.tsx @@ -0,0 +1,146 @@ +import { App, Astal, Gtk, Widget } from 'astal/gtk3'; + +import AstalApps from 'gi://AstalApps?version=0.1'; + +import { Fzf, FzfResultItem } from 'fzf'; + +import PopupWindow from '../misc/popup-window'; +import { centerCursor } from '../../lib'; + +import AppItemWidget, { AppItem } from './app-item'; +import { launchApp } from './launch'; + + +export default () => { + let Applications: AstalApps.Application[] = []; + let fzfResults = [] as FzfResultItem[]; + + const list = new Gtk.ListBox({ + selectionMode: Gtk.SelectionMode.SINGLE, + }); + + list.connect('row-activated', (_, row) => { + const app = (row.get_children()[0] as AppItem).app; + + launchApp(app); + App.get_window('win-applauncher')?.set_visible(false); + }); + + const placeholder = ( + + + ) as Widget.Revealer; + + const on_text_change = (text: string) => { + const fzf = new Fzf(Applications, { + selector: (app) => app.name + app.executable, + + tiebreakers: [ + (a, b) => b.item.frequency - a.item.frequency, + ], + }); + + fzfResults = fzf.find(text); + list.invalidate_sort(); + + const visibleApplications = list.get_children().filter((row) => row.visible).length; + + placeholder.reveal_child = visibleApplications <= 0; + }; + + const entry = ( + on_text_change(self.text)} + hexpand + /> + ) as Widget.Entry; + + list.set_sort_func((a, b) => { + const row1 = (a.get_children()[0] as AppItem).app; + const row2 = (b.get_children()[0] as AppItem).app; + + if (entry.text === '' || entry.text === '-') { + a.set_visible(true); + b.set_visible(true); + + return row2.frequency - row1.frequency; + } + else { + const s1 = fzfResults.find((r) => r.item.name === row1.name)?.score ?? 0; + const s2 = fzfResults.find((r) => r.item.name === row2.name)?.score ?? 0; + + a.set_visible(s1 !== 0); + b.set_visible(s2 !== 0); + + return s2 - s1; + } + }); + + const refreshApplications = () => { + (list.get_children() as Gtk.ListBoxRow[]) + .forEach((child) => { + child.destroy(); + }); + + Applications = AstalApps.Apps.new().get_list(); + + Applications + .flatMap((app) => AppItemWidget({ app })) + .forEach((child) => { + list.add(child); + }); + + list.show_all(); + on_text_change(''); + }; + + refreshApplications(); + + return ( + { + entry.text = ''; + centerCursor(); + }} + > + + + + + + {entry} + + + + + + + + {list} + {placeholder} + + + + + ); +}; diff --git a/nixosModules/ags/v2/widgets/applauncher/style.scss b/nixosModules/ags/v2/widgets/applauncher/style.scss new file mode 100644 index 00000000..2ee89180 --- /dev/null +++ b/nixosModules/ags/v2/widgets/applauncher/style.scss @@ -0,0 +1,32 @@ +.applauncher { + .app-search { + icon { + font-size: 20px; + min-width: 40px; + min-height: 40px + } + + entry {} + } + + .app-list { + row { + border-radius: 10px; + + &:hover, &:selected { + icon { + -gtk-icon-shadow: 2px 2px $accent_color; + } + } + + .app { + margin: 20px; + font-size: 16px; + } + } + + .placeholder { + font-size: 20px; + } + } +} diff --git a/nixosModules/ags/v2/widgets/misc/popup-window.tsx b/nixosModules/ags/v2/widgets/misc/popup-window.tsx index d9eecb80..5e060deb 100644 --- a/nixosModules/ags/v2/widgets/misc/popup-window.tsx +++ b/nixosModules/ags/v2/widgets/misc/popup-window.tsx @@ -1,5 +1,5 @@ import { App, Astal, Gtk, Widget } from 'astal/gtk3'; -import { register, property } from 'astal/gobject'; +import { register } from 'astal/gobject'; import { Binding, idle } from 'astal'; import { get_hyprland_monitor, hyprMessage } from '../../lib'; @@ -20,17 +20,10 @@ type PopupWindowProps = Widget.WindowProps & { @register() export class PopupWindow extends Widget.Window { - @property(String) - declare transition: HyprTransition | Binding; - - @property(String) - declare close_on_unfocus: CloseType | Binding; - - @property(Object) - declare on_open: PopupCallback; - - @property(Object) - declare on_close: PopupCallback; + transition: HyprTransition | Binding; + close_on_unfocus: CloseType | Binding; + on_open: PopupCallback; + on_close: PopupCallback; constructor({ transition = 'slide top', diff --git a/nixosModules/ags/v2/widgets/notifs/style.scss b/nixosModules/ags/v2/widgets/notifs/style.scss index 5de17bc0..16efc182 100644 --- a/nixosModules/ags/v2/widgets/notifs/style.scss +++ b/nixosModules/ags/v2/widgets/notifs/style.scss @@ -74,10 +74,6 @@ box-shadow: none; } - viewport { - all: unset; - } - scrollbar { all: unset; border-radius: 8px;