feat(agsV2): add applauncher
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
bb17277123
commit
b970c2fcf3
13 changed files with 329 additions and 27 deletions
|
@ -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()'"}"];
|
||||
};
|
||||
};
|
||||
})
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -77,3 +77,35 @@ export const hyprMessage = (message: string) => new Promise<string>((
|
|||
rejection(e);
|
||||
}
|
||||
});
|
||||
|
||||
export const centerCursor = async(): Promise<void> => {
|
||||
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}`);
|
||||
};
|
||||
|
|
BIN
nixosModules/ags/v2/package-lock.json
generated
BIN
nixosModules/ags/v2/package-lock.json
generated
Binary file not shown.
|
@ -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"
|
||||
|
|
|
@ -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';
|
||||
|
|
72
nixosModules/ags/v2/widgets/applauncher/app-item.tsx
Normal file
72
nixosModules/ags/v2/widgets/applauncher/app-item.tsx
Normal file
|
@ -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 = (
|
||||
<icon css="font-size: 42px; margin-right: 25px;" />
|
||||
) 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 = (
|
||||
<box
|
||||
vertical
|
||||
>
|
||||
<label
|
||||
className="title"
|
||||
label={app.name}
|
||||
xalign={0}
|
||||
truncate
|
||||
valign={Gtk.Align.CENTER}
|
||||
/>
|
||||
|
||||
{app.description !== '' && (
|
||||
<label
|
||||
className="description"
|
||||
label={app.description}
|
||||
wrap
|
||||
xalign={0}
|
||||
justify={Gtk.Justification.LEFT}
|
||||
valign={Gtk.Align.CENTER}
|
||||
/>
|
||||
)}
|
||||
</box>
|
||||
);
|
||||
|
||||
this.add(icon);
|
||||
this.add(textBox);
|
||||
}
|
||||
}
|
||||
|
||||
export default (props: AppItemProps) => new AppItem(props);
|
27
nixosModules/ags/v2/widgets/applauncher/launch.ts
Normal file
27
nixosModules/ags/v2/widgets/applauncher/launch.ts
Normal file
|
@ -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;
|
||||
};
|
146
nixosModules/ags/v2/widgets/applauncher/main.tsx
Normal file
146
nixosModules/ags/v2/widgets/applauncher/main.tsx
Normal file
|
@ -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<AstalApps.Application>[];
|
||||
|
||||
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 = (
|
||||
<revealer>
|
||||
<label
|
||||
label=" Couldn't find a match"
|
||||
className="placeholder"
|
||||
/>
|
||||
</revealer>
|
||||
) 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 = (
|
||||
<entry
|
||||
onChanged={(self) => 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 (
|
||||
<PopupWindow
|
||||
name="applauncher"
|
||||
keymode={Astal.Keymode.ON_DEMAND}
|
||||
on_open={() => {
|
||||
entry.text = '';
|
||||
centerCursor();
|
||||
}}
|
||||
>
|
||||
<box
|
||||
vertical
|
||||
className="applauncher"
|
||||
>
|
||||
<box className="widget app-search">
|
||||
|
||||
<icon icon="preferences-system-search-symbolic" />
|
||||
|
||||
{entry}
|
||||
|
||||
<button
|
||||
css="margin-left: 5px;"
|
||||
onButtonReleaseEvent={refreshApplications}
|
||||
>
|
||||
<icon icon="view-refresh-symbolic" css="font-size: 26px;" />
|
||||
</button>
|
||||
|
||||
</box>
|
||||
|
||||
<scrollable
|
||||
className="widget app-list"
|
||||
|
||||
css="min-height: 600px; min-width: 600px;"
|
||||
hscroll={Gtk.PolicyType.NEVER}
|
||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
>
|
||||
<box vertical>
|
||||
{list}
|
||||
{placeholder}
|
||||
</box>
|
||||
</scrollable>
|
||||
</box>
|
||||
</PopupWindow>
|
||||
);
|
||||
};
|
32
nixosModules/ags/v2/widgets/applauncher/style.scss
Normal file
32
nixosModules/ags/v2/widgets/applauncher/style.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<HyprTransition>;
|
||||
|
||||
@property(String)
|
||||
declare close_on_unfocus: CloseType | Binding<CloseType>;
|
||||
|
||||
@property(Object)
|
||||
declare on_open: PopupCallback;
|
||||
|
||||
@property(Object)
|
||||
declare on_close: PopupCallback;
|
||||
transition: HyprTransition | Binding<HyprTransition>;
|
||||
close_on_unfocus: CloseType | Binding<CloseType>;
|
||||
on_open: PopupCallback;
|
||||
on_close: PopupCallback;
|
||||
|
||||
constructor({
|
||||
transition = 'slide top',
|
||||
|
|
|
@ -74,10 +74,6 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
viewport {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
scrollbar {
|
||||
all: unset;
|
||||
border-radius: 8px;
|
||||
|
|
Loading…
Reference in a new issue