import { bind, Gio, timeout, Variable } from "astal"; import { Astal, Gtk, Widget } from "astal/gtk3"; import type AstalApps from "gi://AstalApps"; import AstalHyprland from "gi://AstalHyprland"; import { Apps } from "../services/apps"; import { getAppCategoryIcon } from "../utils/icons"; import { launch } from "../utils/system"; import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets"; const maxSearchResults = 15; const browser = [ "firefox", "waterfox", "google-chrome", "chromium", "brave-browser", "vivaldi-stable", "vivaldi-snapshot", ]; const terminal = ["foot", "alacritty", "kitty", "wezterm"]; const files = ["thunar", "nemo", "nautilus"]; const ide = ["codium", "code", "clion", "intellij-idea-ultimate-edition"]; const music = ["spotify-adblock", "spotify", "audacious", "elisa"]; const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => { const toplevel = self.get_toplevel(); if (toplevel instanceof Widget.Window) toplevel.hide(); launch(astalApp); }; const PinnedApp = ({ names }: { names: string[] }) => { let app: Gio.DesktopAppInfo | null = null; let astalApp: AstalApps.Application | undefined; for (const name of names) { app = Gio.DesktopAppInfo.new(`${name}.desktop`); if (app) { astalApp = Apps.get_list().find(a => a.entry === `${name}.desktop`); if (app.get_icon() && astalApp) break; else app = null; // Set app to null if no icon or matching AstalApps#Application } } if (!app) console.error(`Launcher - Unable to find app for "${names.join(", ")}"`); return app ? ( ) : null; }; const PinnedApps = () => ( ); const SearchEntry = ({ entry }: { entry: Widget.Entry }) => ( self.hook(entry, "notify::text-length", () => // Timeout to avoid flickering when replacing entire text (cause it'll set len to 0 then back to > 0) timeout(1, () => (self.shown = entry.textLength > 0 ? "entry" : "placeholder")) ) } > ); const Result = ({ app }: { app: AstalApps.Application }) => ( ); const Results = ({ entry }: { entry: Widget.Entry }) => { const empty = Variable(true); return ( (t ? "empty" : "list"))} > { let apps: AstalApps.Application[] = []; self.hook(entry, "activate", () => { if (entry.text && apps[0]) launchAndClose(self, apps[0]); }); 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(); }); }} /> ); }; const Launcher = ({ entry }: { entry: Widget.Entry }) => ( `margin-top: ${m.height / 4}px;`)} > t === 0)} transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} transitionDuration={150} > t > 0)} transitionType={Gtk.RevealerTransitionType.SLIDE_UP} transitionDuration={150} > ); export default () => { const entry = () as Widget.Entry; return ( { const keyval = event.get_keyval()[1]; // Focus entry on typing if (!entry.isFocus && keyval >= 32 && keyval <= 126) { entry.text += String.fromCharCode(keyval); entry.grab_focus(); entry.set_position(-1); // 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} > ); };