import os import subprocess from typing import List from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.entry import Entry from fabric.widgets.label import Label from fabric.widgets.scrolledwindow import ScrolledWindow import config.data as data BINS = f"{data.CACHE_DIR}/binaries" class Sh(Box): def __init__(self, **kwargs): super().__init__( all_visible=False, visible=False, **kwargs, ) self.launcher = kwargs["launcher"] self._arranger_handler: int = 0 self.shell_command_manager = ShellCommandManager(self) self.viewport = None self.search_entry = Entry( name="search-entry", placeholder="Search Shell Commands...", h_expand=True, notify_text=lambda entry, *_: self.handle_search_input(entry.get_text()), on_activate=lambda entry, *_: self.handle_search_input(entry.get_text()), ) self.search_entry.props.xalign = 0.5 self.header_box = Box( spacing=10, orientation="h", children=[self.search_entry], ) self.launcher_box = Box( spacing=10, orientation="v", h_expand=True, children=[self.header_box], ) self.add(self.launcher_box) def open_sh(self): if not self.viewport: self.viewport = Box(name="viewport", spacing=10, orientation="v") self.scrolled_window = ScrolledWindow( name="scrolled-window", spacing=10, h_scrollbar_policy="never", v_scrollbar_policy="never", # min_content_size=(-1, -1), child=self.viewport, ) self.launcher_box.add(self.scrolled_window) self.viewport.children = [] self.shell_command_manager.show_shell_commands(self.viewport, "") self.viewport.show() self.search_entry.grab_focus() def handle_search_input(self, text: str): self.shell_command_manager.show_shell_commands(self.viewport, text) class ShellCommandManager: def __init__(self, launcher): self.launcher = launcher self.binaries = set() self.reload_binaries() def reload_binaries(self): paths = os.getenv("PATH", "").split(":") self.binaries = set() for path in paths: self.binaries.update(self.list_binaries_in_path(path)) with open(BINS, "w") as f: f.write("\n".join(self.binaries)) def list_binaries_in_path(self, path): if not os.path.exists(path): return [] try: return [ entry for entry in os.listdir(path) if os.access(os.path.join(path, entry), os.X_OK) ] except OSError: return [] def show_shell_commands(self, viewport, search_query: str): if not search_query: return viewport.children = [] results = self.query_binaries(search_query) self.display_results(viewport, results) def query_binaries(self, filter_str: str) -> List[str]: filter_str = filter_str.lower() filtered_bins = (cmd for cmd in self.binaries if filter_str in cmd) return list(dict.fromkeys(filtered_bins))[:8] def display_results(self, viewport, results: List[str]): for command in results: button = Button( child=Label( label=command, v_align="center", h_align="start", ), on_clicked=lambda _, cmd=command: self.execute_command(cmd), name="app-slot-button", ) viewport.add(button) def close_menu(self): self.launcher.close() def execute_command(self, cmd: str): try: output = subprocess.check_output(cmd, shell=True, text=True) print(output) except subprocess.CalledProcessError as e: print(e.output) finally: self.close_menu()