From a313624734dde8b2f562eb0815c52e93b00f7986 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:41:28 +1100 Subject: launcher modes + player controls IPC --- modules/launcher.tsx | 230 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 189 insertions(+), 41 deletions(-) (limited to 'modules') diff --git a/modules/launcher.tsx b/modules/launcher.tsx index 4cde7c4..8f01596 100644 --- a/modules/launcher.tsx +++ b/modules/launcher.tsx @@ -1,12 +1,23 @@ -import { bind, Gio, timeout, Variable } from "astal"; +import { bind, execAsync, Gio, register, timeout, Variable } from "astal"; import { Astal, Gtk, Widget } from "astal/gtk3"; +import fuzzysort from "fuzzysort"; import type AstalApps from "gi://AstalApps"; import AstalHyprland from "gi://AstalHyprland"; +import Mexp from "math-expression-evaluator"; import { Apps } from "../services/apps"; import { getAppCategoryIcon } from "../utils/icons"; import { launch } from "../utils/system"; import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets"; +type Mode = "apps" | "files" | "math"; + +interface Subcommand { + icon: string; + name: string; + description: string; + command: (...args: string[]) => void; +} + const maxSearchResults = 15; const browser = [ @@ -23,6 +34,17 @@ const files = ["thunar", "nemo", "nautilus"]; const ide = ["codium", "code", "clion", "intellij-idea-ultimate-edition"]; const music = ["spotify-adblock", "spotify", "audacious", "elisa"]; +const getIconFromMode = (mode: Mode) => { + switch (mode) { + case "apps": + return "apps"; + case "files": + return "folder"; + case "math": + return "calculate"; + } +}; + const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => { const toplevel = self.get_toplevel(); if (toplevel instanceof Widget.Window) toplevel.hide(); @@ -45,7 +67,7 @@ const PinnedApp = ({ names }: { names: string[] }) => { return app ? ( ); -const Results = ({ entry }: { entry: Widget.Entry }) => { +const SubcommandResult = ({ + entry, + subcommand, + args, +}: { + entry: Widget.Entry; + subcommand: Subcommand; + args: string[]; +}) => ( + { + subcommand.command(...args); + entry.set_text(""); + }} + /> +); + +const AppResult = ({ app }: { app: AstalApps.Application }) => ( + launchAndClose(self, app)} + /> +); + +const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable }) => { const empty = Variable(true); + return ( { vertical name="list" setup={self => { - let apps: AstalApps.Application[] = []; - self.hook(entry, "activate", () => { - if (entry.text && apps[0]) launchAndClose(self, apps[0]); - }); + const subcommands: Record = { + apps: { + icon: "apps", + name: "Apps", + description: "Search for apps", + command: () => mode.set("apps"), + }, + files: { + icon: "folder", + name: "Files", + description: "Search for files", + command: () => mode.set("files"), + }, + calc: { + icon: "calculate", + name: "Calculator", + description: "A calculator...", + command: () => mode.set("math"), + }, + todo: { + icon: "checklist", + name: "Todo", + description: "Create a todo in ", + command: (...args) => { + // TODO: todo service or maybe use external app + }, + }, + }; + const subcommandList = Object.keys(subcommands); + const mexp = new Mexp(); + + const appSearch = () => { + const apps = Apps.fuzzy_query(entry.text); + empty.set(apps.length === 0); + if (apps.length > maxSearchResults) apps.length = maxSearchResults; + for (const app of apps) self.add(); + }; + + const calculate = () => { + // TODO: allow defs, history + let math = null; + try { + math = mexp.eval(entry.text); + } catch (e) { + // Ignore + } + if (math !== null) + self.add( + execAsync(`wl-copy -- ${math}`).catch(console.error)} + /> + ); + }; + + self.hook(entry, "activate", () => entry.text && self.get_children()[0].activate()); self.hook(entry, "changed", () => { if (!entry.text) return; self.foreach(ch => ch.destroy()); - apps = Apps.fuzzy_query(entry.text); - empty.set(apps.length === 0); - if (apps.length > maxSearchResults) apps.length = maxSearchResults; - for (const app of apps) self.add(); + + if (entry.text.startsWith(">")) { + const args = entry.text.split(" "); + for (const { target } of fuzzysort.go(args[0].slice(1), subcommandList, { all: true })) + self.add( + + ); + } else if (mode.get() === "apps") appSearch(); + else if (mode.get() === "math") calculate(); + + empty.set(self.get_children().length === 0); }); }} /> @@ -130,16 +268,16 @@ const Results = ({ entry }: { entry: Widget.Entry }) => { ); }; -const Launcher = ({ entry }: { entry: Widget.Entry }) => ( +const LauncherContent = ({ mode, entry }: { mode: Variable; entry: Widget.Entry }) => ( `launcher ${m}`)} css={bind(AstalHyprland.get_default(), "focusedMonitor").as(m => `margin-top: ${m.height / 4}px;`)} > t === 0)} @@ -153,20 +291,24 @@ const Launcher = ({ entry }: { entry: Widget.Entry }) => ( transitionType={Gtk.RevealerTransitionType.SLIDE_UP} transitionDuration={150} > - + ); -export default () => { - const entry = () as Widget.Entry; +@register() +export default class Launcher extends PopupWindow { + readonly mode: Variable; - return ( - { + constructor() { + const entry = () as Widget.Entry; + const mode = Variable("apps"); + + super({ + name: "launcher", + keymode: Astal.Keymode.EXCLUSIVE, + exclusivity: Astal.Exclusivity.IGNORE, + onKeyPressEvent(_, event) { const keyval = event.get_keyval()[1]; // Focus entry on typing if (!entry.isFocus && keyval >= 32 && keyval <= 126) { @@ -177,14 +319,20 @@ export default () => { // Consume event, if not consumed it will duplicate character in entry return true; } - }} - // Clear entry text on hide - setup={self => self.connect("hide", () => entry.set_text(""))} - transitionType={TransitionType.SLIDE_DOWN} - halign={Gtk.Align.CENTER} - valign={Gtk.Align.START} - > - - - ); -}; + }, + transitionType: TransitionType.SLIDE_DOWN, + halign: Gtk.Align.CENTER, + valign: Gtk.Align.START, + child: , + }); + + this.mode = mode; + + this.connect("hide", () => entry.set_text("")); + } + + open(mode: Mode) { + this.mode.set(mode); + this.show(); + } +} -- cgit v1.2.3-freya