✨ ags(runner): add new functions and organize existing
This commit is contained in:
+105
-74
@@ -1,44 +1,12 @@
|
|||||||
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;
|
||||||
valign?: Gtk.Align;
|
valign?: Gtk.Align;
|
||||||
width?: number;
|
width?: number;
|
||||||
@@ -46,13 +14,9 @@ export namespace Runner {
|
|||||||
entryPlaceHolder?: string;
|
entryPlaceHolder?: string;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
showResultsPlaceHolderOnStartup?: boolean;
|
showResultsPlaceHolderOnStartup?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function close() { runnerInstance?.close(); }
|
export interface Plugin {
|
||||||
|
|
||||||
const plugins = new Set<Runner.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;
|
||||||
/** name of the plugin. e.g.: websearch, shell */
|
/** name of the plugin. e.g.: websearch, shell */
|
||||||
@@ -63,38 +27,113 @@ 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 function addPlugin(plugin: Runner.Plugin, force?: 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) {
|
||||||
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`);
|
||||||
|
|
||||||
plugins.delete(plugin);
|
plugins.delete(plugin);
|
||||||
plugins.add(plugin);
|
plugins.add(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlugins(): Array<Runner.Plugin> {
|
export function getPlugins(): Array<Runner.Plugin> {
|
||||||
return [...plugins.values()];
|
return [...plugins.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Removes a plugin from the runner plugin list
|
/** Removes a plugin from the runner plugin list
|
||||||
* @returns true if plugin was removed or false if plugin wasn't found
|
* @returns true if plugin was removed or false if plugin wasn't found
|
||||||
*/
|
*/
|
||||||
export function removePlugin(plugin: Plugin): boolean {
|
export function removePlugin(plugin: Plugin): boolean {
|
||||||
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!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+11
-12
@@ -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,22 +154,21 @@ 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:
|
||||||
open [window_name]: sets specified window's visibility to true.
|
open [window_name]: sets specified window's visibility to true.
|
||||||
close [window_name]: sets specified window's visibility to false.
|
close [window_name]: sets specified window's visibility to false.
|
||||||
toggle [window_name]: toggles visibility of specified window.
|
toggle [window_name]: toggles visibility of specified window.
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user