feat(agsV2): add applauncher
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-18 00:44:45 -04:00
parent bb17277123
commit b970c2fcf3
13 changed files with 329 additions and 27 deletions

View file

@ -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()'"}"];
};
};
})

View file

@ -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();

View file

@ -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";

View file

@ -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}`);
};

Binary file not shown.

View file

@ -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"

View file

@ -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';

View 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);

View 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;
};

View 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>
);
};

View 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;
}
}
}

View file

@ -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',

View file

@ -74,10 +74,6 @@
box-shadow: none;
}
viewport {
all: unset;
}
scrollbar {
all: unset;
border-radius: 8px;