ags(runner): add new functions and organize existing

This commit is contained in:
retrozinndev
2025-05-11 20:53:19 -03:00
parent ae1d29bc89
commit c574fa87f9
3 changed files with 253 additions and 225 deletions
+93 -62
View File
@@ -1,42 +1,10 @@
import { AstalIO, timeout, Variable } from "astal"; import { AstalIO, timeout } from "astal";
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3"; import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow"; import { PopupWindow, PopupWindowProps } from "../widget/PopupWindow";
import { updateApps } from "../scripts/apps"; import { updateApps } from "../scripts/apps";
import { ResultWidget, ResultWidgetProps } from "../widget/runner/ResultWidget"; import { ResultWidget, ResultWidgetProps } from "../widget/runner/ResultWidget";
import { Windows } from "../windows"; import { Windows } from "../windows";
export let runnerInstance: (Gtk.Window|null) = null;
export function startRunnerDefault(initialText?: string) {
return Runner.openRunner({
entryPlaceHolder: "Start typing...",
showResultsPlaceHolderOnStartup: false,
initialText
} as Runner.RunnerProps,
() => [
new ResultWidget({
icon: "application-x-executable-symbolic",
title: "Run your applications",
description: "Type the name of the application to search"
} as ResultWidgetProps),
new ResultWidget({
icon: "media-playback-start-symbolic",
title: "Control media",
description: "Use prefix ':' to run"
} as ResultWidgetProps),
new ResultWidget({
icon: "utilities-terminal-symbolic",
title: "Run shell commands",
description: "Start typing with '!' prefix to run shell commands"
} as ResultWidgetProps),
new ResultWidget({
icon: "applications-internet-symbolic",
title: "Search the Web",
description: "Start typing with '?' prefix to search the web"
} as ResultWidgetProps)
]);
}
export namespace Runner { export namespace Runner {
export type RunnerProps = { export type RunnerProps = {
halign?: Gtk.Align; halign?: Gtk.Align;
@@ -48,10 +16,6 @@ export namespace Runner {
showResultsPlaceHolderOnStartup?: boolean; showResultsPlaceHolderOnStartup?: boolean;
}; };
export function close() { runnerInstance?.close(); }
const plugins = new Set<Runner.Plugin>([]);
export interface Plugin { export interface Plugin {
/** prefix to call the plugin. if undefined, will be triggered like applications plugin */ /** prefix to call the plugin. if undefined, will be triggered like applications plugin */
readonly prefix?: string; readonly prefix?: string;
@@ -63,10 +27,24 @@ export namespace Runner {
readonly handle: (inputText: string) => (ResultWidget|Array<ResultWidget>|null|undefined); readonly handle: (inputText: string) => (ResultWidget|Array<ResultWidget>|null|undefined);
/** ran on runner close */ /** ran on runner close */
readonly onClose?: () => void; readonly onClose?: () => void;
/** hide other plugins when using this plugin **/ /** hide other plugins when using this plugin */
prioritize?: boolean; prioritize?: boolean;
} }
export let instance: (Widget.Window|null) = null;
let gtkEntry: (Widget.Entry|null) = null;
const plugins = new Set<Runner.Plugin>();
export function close() { instance?.close(); }
export function regExMatch(search: string, item: string): boolean {
search = search.replace(/[\\^$.*?()[\]{}|]/g, "\\$&");
return new RegExp(`${search.split('').map(c =>
`.*(${c.toLowerCase()}|${c.toUpperCase()}).*`).join('')}`
).test(item);
}
export function addPlugin(plugin: Runner.Plugin, force?: boolean) { export function addPlugin(plugin: Runner.Plugin, force?: boolean) {
if(!force && plugin.prefix && plugins.has(plugin)) if(!force && plugin.prefix && plugins.has(plugin))
throw new Error(`Runner plugin with prefix ${plugin.prefix} already exists`); throw new Error(`Runner plugin with prefix ${plugin.prefix} already exists`);
@@ -86,15 +64,76 @@ export namespace Runner {
return plugins.delete(plugin); return plugins.delete(plugin);
} }
export function openRunner(props?: RunnerProps, placeholder?: () => Array<ResultWidget>): (Gtk.Window|null) { export function setEntryText(text: string): void {
let subs: Array<() => void> = []; gtkEntry?.set_text(text);
const entryText: Variable<string> = new Variable<string>(""); gtkEntry?.set_position(gtkEntry.textLength);
gtkEntry?.grab_focus_without_selecting();
}
export function openDefault(initialText?: string) {
return Runner.openRunner({
entryPlaceHolder: "Start typing...",
showResultsPlaceHolderOnStartup: false,
initialText
} as Runner.RunnerProps,
() => [
new ResultWidget({
icon: "application-x-executable-symbolic",
title: "Use your applications",
description: "Search for any app installed in your computer",
closeOnClick: false,
onClick: () => gtkEntry?.grab_focus()
} as ResultWidgetProps),
new ResultWidget({
icon: "edit-paste-symbolic",
title: "See your clipboard history",
description: "Start your search with '>' to go through your clipboard history",
closeOnClick: false,
onClick: () => setEntryText('>')
} as ResultWidgetProps),
new ResultWidget({
icon: "image-x-generic-symbolic",
title: "Change your wallpaper",
description: "Add '#' at the start to search through the wallpapers folder!",
closeOnClick: false,
onClick: () => setEntryText('#'),
} as ResultWidgetProps),
new ResultWidget({
icon: "utilities-terminal-symbolic",
title: "Run shell commands",
description: "Add '!' before your command to run it (pro tip: add a second '!' to show command output)",
closeOnClick: false,
onClick: () => setEntryText('!!')
} as ResultWidgetProps),
new ResultWidget({
icon: "media-playback-start-symbolic",
title: "Control media",
description: "Type ':' to control playing media",
closeOnClick: false,
onClick: () => setEntryText(':')
} as ResultWidgetProps),
new ResultWidget({
icon: "applications-internet-symbolic",
title: "Search the Web",
description: "Start typing with '?' prefix to search the web",
closeOnClick: false,
onClick: () => setEntryText('?')
} as ResultWidgetProps)
]);
}
export function openRunner(props?: RunnerProps, placeholder?: () => Array<ResultWidget>): Widget.Window {
let onClickTimeout: (AstalIO.Time|undefined); let onClickTimeout: (AstalIO.Time|undefined);
const searchEntry = new Widget.Entry({ gtkEntry = new Widget.Entry({
className: "search", className: "search",
onChanged: (entry) => entryText.set(entry.text),
placeholderText: props?.entryPlaceHolder || "", placeholderText: props?.entryPlaceHolder || "",
onChanged: (self) => {
updateResultsList(self.text);
resultsList.get_row_at_index(0) &&
resultsList.select_row(resultsList.get_row_at_index(0));
},
onActivate: (entry) => { onActivate: (entry) => {
const resultWidget = resultsList.get_selected_row()?.get_child(); const resultWidget = resultsList.get_selected_row()?.get_child();
if(resultWidget instanceof ResultWidget) { if(resultWidget instanceof ResultWidget) {
@@ -120,7 +159,6 @@ export namespace Runner {
function cleanResults() { function cleanResults() {
resultsList.get_children().map((listItem) => { resultsList.get_children().map((listItem) => {
resultsList.remove(listItem); resultsList.remove(listItem);
listItem.destroy();
}); });
} }
@@ -171,13 +209,8 @@ export namespace Runner {
}); });
} }
subs.push(entryText().subscribe((text: string) => { if(!instance)
updateResultsList(text.trim()); instance = Windows.createWindowForFocusedMonitor((mon: number): (Widget.Window) => PopupWindow({
resultsList.select_row(resultsList.get_row_at_index(0));
}));
if(!runnerInstance)
runnerInstance = Windows.createWindowForFocusedMonitor((mon: number): (Widget.Window) => PopupWindow({
namespace: "runner", namespace: "runner",
monitor: mon, monitor: mon,
widthRequest: props?.width ?? 750, widthRequest: props?.width ?? 750,
@@ -187,18 +220,17 @@ export namespace Runner {
setup: () => { setup: () => {
// Init plugins // Init plugins
plugins.forEach(plugin => plugin.init && plugin.init()); plugins.forEach(plugin => plugin.init && plugin.init());
if(props?.initialText) {
searchEntry.set_text(props.initialText); if(props?.initialText)
searchEntry.set_position(searchEntry.textLength); Runner.setEntryText(props.initialText);
}
}, },
onKeyPressEvent: (_, event: Gdk.Event) => { onKeyPressEvent: (_, event: Gdk.Event) => {
const keyVal = event.get_keyval()[1]; const keyVal = event.get_keyval()[1];
if(!searchEntry.has_focus && keyVal !== Gdk.KEY_F5 if(!gtkEntry!.has_focus && keyVal !== Gdk.KEY_F5
&& keyVal !== Gdk.KEY_Down && keyVal !== Gdk.KEY_Up && keyVal !== Gdk.KEY_Down && keyVal !== Gdk.KEY_Up
&& keyVal !== Gdk.KEY_Return) { && keyVal !== Gdk.KEY_Return) {
searchEntry.grab_focus_without_selecting(); gtkEntry!.grab_focus_without_selecting();
return; return;
} }
@@ -206,11 +238,10 @@ export namespace Runner {
updateApps(); updateApps();
}, },
onDestroy: () => { onDestroy: () => {
subs.map(sub => sub()); gtkEntry = null;
[...plugins.values()].map(plugin => [...plugins.values()].map(plugin =>
plugin && plugin.onClose && plugin.onClose()); plugin && plugin.onClose && plugin.onClose());
runnerInstance = null; instance = null;
}, },
child: new Widget.Box({ child: new Widget.Box({
className: "runner main", className: "runner main",
@@ -218,7 +249,7 @@ export namespace Runner {
expand: false, expand: false,
valign: Gtk.Align.START, valign: Gtk.Align.START,
children: [ children: [
searchEntry, gtkEntry,
new Widget.Scrollable({ new Widget.Scrollable({
className: "results-scrollable", className: "results-scrollable",
vscroll: Gtk.PolicyType.AUTOMATIC, vscroll: Gtk.PolicyType.AUTOMATIC,
@@ -232,6 +263,6 @@ export namespace Runner {
} as Widget.BoxProps) } as Widget.BoxProps)
} as PopupWindowProps))(); } as PopupWindowProps))();
return runnerInstance; return instance!;
} }
} }
+1 -3
View File
@@ -26,9 +26,7 @@ export class PluginWallpapers implements Runner.Plugin {
handle(search: string) { handle(search: string) {
if(this.#files!.length > 0) if(this.#files!.length > 0)
return this.#files!.filter(file => // not the best way to search, but it works return this.#files!.filter(file => // not the best way to search, but it works
new RegExp(`${search.split('').map(c => Runner.regExMatch(search, file.split('/')[file.split('/').length-1])
`.*(${c.toLowerCase()}|${c.toUpperCase()}).*`).join('')}`
).test(file.split('/')[file.split('/').length-1])
).map(path => new ResultWidget({ ).map(path => new ResultWidget({
title: path.split('/')[path.split('/').length-1].replace(/\..*$/, ""), title: path.split('/')[path.split('/').length-1].replace(/\..*$/, ""),
onClick: () => Wallpaper.getDefault().setWallpaper(path) onClick: () => Wallpaper.getDefault().setWallpaper(path)
+7 -8
View File
@@ -2,9 +2,9 @@ import { Wireplumber } from "./volume";
import { Windows } from "../windows"; import { Windows } from "../windows";
import { restartInstance } from "./reload-handler"; import { restartInstance } from "./reload-handler";
import { runnerInstance, startRunnerDefault } from "../runner/Runner";
import { showWorkspaceNumbers } from "../widget/bar/Workspaces"; import { showWorkspaceNumbers } from "../widget/bar/Workspaces";
import { timeout } from "astal"; import { timeout } from "astal";
import { Runner } from "../runner/Runner";
export function handleArguments(request: string): any { export function handleArguments(request: string): any {
@@ -31,9 +31,9 @@ export function handleArguments(request: string): any {
`${name}: ${Windows.isVisible(name) ? "open" : "closed" }`).join('\n'); `${name}: ${Windows.isVisible(name) ? "open" : "closed" }`).join('\n');
case "runner": case "runner":
!runnerInstance ? !Runner.instance ?
startRunnerDefault(args[1] || undefined) Runner.openDefault(args[1] || undefined)
: runnerInstance.close(); : Runner.close();
return "Opening runner..." return "Opening runner..."
case "show-ws-numbers": case "show-ws-numbers":
@@ -154,8 +154,7 @@ Options:
} }
function getHelp(): string { function getHelp(): string {
return ` return `Manage Astal Windows and do more stuff. From
Manage Astal Windows and do more stuff. From
retrozinndev's Hyprland Dots, using Astal and AGS by Aylur. retrozinndev's Hyprland Dots, using Astal and AGS by Aylur.
Options: Options:
@@ -165,11 +164,11 @@ Options:
windows: shows available windows to control. windows: shows available windows to control.
reload: creates a new astal instance and removes this one. reload: creates a new astal instance and removes this one.
volume: wireplumber volume controller, see "volume help". volume: wireplumber volume controller, see "volume help".
runner: open the application runner. runner [initial_text]: open the application runner.
show-ws-numbers: show or hide workspace numbers in bar. show-ws-numbers: show or hide workspace numbers in bar.
h, help: shows this help message. h, help: shows this help message.
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License. 2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
https://github.com/retrozinndev/Hyprland-Dots https://github.com/retrozinndev/Hyprland-Dots
`.trim(); `.split('\n').map(l => l.replace(/^ {8}/, "")).join('\n');
} }