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
|
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 {
|
in {
|
||||||
settings = {
|
settings = {
|
||||||
animations = {
|
animations = {
|
||||||
|
@ -169,18 +169,18 @@ in {
|
||||||
];
|
];
|
||||||
|
|
||||||
bind = [
|
bind = [
|
||||||
"$mainMod SHIFT, E , exec, ${runAgs} -t win-powermenu"
|
"$mainMod SHIFT, E , exec, ${runAgs "-t win-powermenu"}"
|
||||||
"$mainMod , D , exec, ${runAgs} -t win-applauncher"
|
"$mainMod , D , exec, ${runAgs "-t win-applauncher"}"
|
||||||
"$mainMod , V , exec, ${runAgs} -t win-clipboard"
|
"$mainMod , V , exec, ${runAgs "-t win-clipboard"}"
|
||||||
" , Print, exec, ${runAgs} -t win-screenshot"
|
" , Print, exec, ${runAgs "-t win-screenshot"}"
|
||||||
];
|
];
|
||||||
binde = [
|
binde = [
|
||||||
## Brightness control
|
## Brightness control
|
||||||
", XF86MonBrightnessUp , exec, ${runAgs} -r 'Brightness.screen += 0.05'"
|
", XF86MonBrightnessUp , exec, ${runAgs "-r 'Brightness.screen += 0.05'"}"
|
||||||
", XF86MonBrightnessDown, exec, ${runAgs} -r 'Brightness.screen -= 0.05'"
|
", XF86MonBrightnessDown, exec, ${runAgs "-r 'Brightness.screen -= 0.05'"}"
|
||||||
];
|
];
|
||||||
bindn = [" , Escape , exec, ${runAgs} -r 'closeAll()'"];
|
bindn = [" , Escape , exec, ${runAgs "-r 'closeAll()'"}"];
|
||||||
bindr = ["CAPS, Caps_Lock, exec, ${runAgs} -r 'Brightness.fetchCapsState()'"];
|
bindr = ["CAPS, Caps_Lock, exec, ${runAgs "-r 'Brightness.fetchCapsState()'"}"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { App } from 'astal/gtk3';
|
||||||
|
|
||||||
import style from './style.scss';
|
import style from './style.scss';
|
||||||
|
|
||||||
|
import AppLauncher from './widgets/applauncher/main';
|
||||||
import Bar from './widgets/bar/wim';
|
import Bar from './widgets/bar/wim';
|
||||||
import BgFade from './widgets/bg-fade/main';
|
import BgFade from './widgets/bg-fade/main';
|
||||||
import Corners from './widgets/corners/main';
|
import Corners from './widgets/corners/main';
|
||||||
|
@ -14,6 +15,7 @@ App.start({
|
||||||
css: style,
|
css: style,
|
||||||
|
|
||||||
main: () => {
|
main: () => {
|
||||||
|
AppLauncher();
|
||||||
Bar();
|
Bar();
|
||||||
BgFade();
|
BgFade();
|
||||||
Corners();
|
Corners();
|
||||||
|
|
|
@ -37,7 +37,7 @@ self: {
|
||||||
})
|
})
|
||||||
// {
|
// {
|
||||||
"${configDir}/node_modules".source =
|
"${configDir}/node_modules".source =
|
||||||
buildNodeModules ./. "sha256-u2LDbIKA32urN/NqqJrdAl46pUloPaoa5HoYDRJDh1k=";
|
buildNodeModules ./. "sha256-aWVnn4G5ypOacju+0wwPeie7JEZoXYDY2qPOTmCdXsg=";
|
||||||
|
|
||||||
"${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" {
|
"${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" {
|
||||||
"$schema" = "https://json.schemastore.org/tsconfig";
|
"$schema" = "https://json.schemastore.org/tsconfig";
|
||||||
|
|
|
@ -77,3 +77,35 @@ export const hyprMessage = (message: string) => new Promise<string>((
|
||||||
rejection(e);
|
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",
|
"@types/node": "22.7.5",
|
||||||
"eslint": "9.12.0",
|
"eslint": "9.12.0",
|
||||||
"eslint-plugin-jsdoc": "50.3.2",
|
"eslint-plugin-jsdoc": "50.3.2",
|
||||||
|
"fzf": "0.5.2",
|
||||||
"jiti": "2.3.3",
|
"jiti": "2.3.3",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"typescript-eslint": "8.8.1"
|
"typescript-eslint": "8.8.1"
|
||||||
|
|
|
@ -32,7 +32,7 @@ $popover_fg_color: #f8f8f2;
|
||||||
$shade_color: #383838;
|
$shade_color: #383838;
|
||||||
$scrollbar_outline_color: rgba(0, 0, 0, 0.5);
|
$scrollbar_outline_color: rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
window {
|
window, viewport {
|
||||||
all: unset;
|
all: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,5 +44,6 @@ window {
|
||||||
box-shadow: 8px 8px rgba($accent_color, 1);
|
box-shadow: 8px 8px rgba($accent_color, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import 'widgets/applauncher/style.scss';
|
||||||
@import 'widgets/bar/style.scss';
|
@import 'widgets/bar/style.scss';
|
||||||
@import 'widgets/notifs/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 { App, Astal, Gtk, Widget } from 'astal/gtk3';
|
||||||
import { register, property } from 'astal/gobject';
|
import { register } from 'astal/gobject';
|
||||||
import { Binding, idle } from 'astal';
|
import { Binding, idle } from 'astal';
|
||||||
|
|
||||||
import { get_hyprland_monitor, hyprMessage } from '../../lib';
|
import { get_hyprland_monitor, hyprMessage } from '../../lib';
|
||||||
|
@ -20,17 +20,10 @@ type PopupWindowProps = Widget.WindowProps & {
|
||||||
|
|
||||||
@register()
|
@register()
|
||||||
export class PopupWindow extends Widget.Window {
|
export class PopupWindow extends Widget.Window {
|
||||||
@property(String)
|
transition: HyprTransition | Binding<HyprTransition>;
|
||||||
declare transition: HyprTransition | Binding<HyprTransition>;
|
close_on_unfocus: CloseType | Binding<CloseType>;
|
||||||
|
on_open: PopupCallback;
|
||||||
@property(String)
|
on_close: PopupCallback;
|
||||||
declare close_on_unfocus: CloseType | Binding<CloseType>;
|
|
||||||
|
|
||||||
@property(Object)
|
|
||||||
declare on_open: PopupCallback;
|
|
||||||
|
|
||||||
@property(Object)
|
|
||||||
declare on_close: PopupCallback;
|
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
transition = 'slide top',
|
transition = 'slide top',
|
||||||
|
|
|
@ -74,10 +74,6 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport {
|
|
||||||
all: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollbar {
|
scrollbar {
|
||||||
all: unset;
|
all: unset;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
Loading…
Reference in a new issue