from __future__ import annotations import os from pathlib import Path from typing import Any, cast import gi gi.require_version("Gdk", "4.0") gi.require_version("Gtk", "4.0") gi.require_version("Gst", "1.0") from gi.repository import Gdk, Gst, Gtk # NOQA: E402 class MainWindow(Gtk.ApplicationWindow): file_info_label: Gtk.Label stack: Gtk.Stack list_view: Gtk.ListView list_store: Gtk.StringList def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) # Make window fullscreen and borderless self.set_decorated(False) self.fullscreen() # Setup key event controller key_controller = Gtk.EventControllerKey() key_controller.connect("key-pressed", self._on_key_pressed) self.add_controller(key_controller) # Main horizontal box to split the screen main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) # Create stack to hold our widgets self.stack = Gtk.Stack() # Create black overlay overlay_box = Gtk.Box() overlay_box.set_name("black-overlay") overlay_box.set_vexpand(True) overlay_box.set_hexpand(True) # Add both main menu and overlay to stack self.stack.add_named(main_box, "menu") self.stack.add_named(overlay_box, "overlay") self.stack.set_visible_child_name("menu") # Create a grid to handle the 1:2 ratio grid = Gtk.Grid() grid.set_column_homogeneous(True) grid.set_hexpand(True) # Left third (1/3 width) left_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) left_box.set_valign(Gtk.Align.CENTER) left_box.set_halign(Gtk.Align.FILL) self.file_info_label = Gtk.Label(label="") left_box.append(self.file_info_label) # Right two-thirds (2/3 width) right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) right_box.set_hexpand(True) # Attach boxes to grid with specific column spans grid.attach(left_box, 0, 0, 1, 1) grid.attach(right_box, 1, 0, 2, 1) main_box.append(grid) # Create list store and view self.list_store = Gtk.StringList() self.list_view = Gtk.ListView() selection_model = Gtk.SingleSelection.new(self.list_store) selection_model.connect("selection-changed", self._on_selection_changed) self.list_view.set_model(selection_model) self.list_view.set_vexpand(True) def on_activate(widget: Gtk.ListView, index: int): selected_item = selection_model.get_item(index) if selected_item: string_obj = cast(Gtk.StringObject, selected_item) string = string_obj.get_string() if string.endswith("/"): os.chdir(string) self._populate_file_list() return print("activated", string) self.stack.set_visible_child_name("overlay") self.list_view.connect("activate", on_activate) # Factory for list items factory = Gtk.SignalListItemFactory() factory.connect("setup", self._setup_list_item) factory.connect("bind", self._bind_list_item) self.list_view.set_factory(factory) # Populate the list store self._populate_file_list() # Add list view to a scrolled window scrolled = Gtk.ScrolledWindow() scrolled.set_child(self.list_view) right_box.append(scrolled) self.set_child(self.stack) def _setup_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem): label = Gtk.Label() label.set_halign(Gtk.Align.START) list_item.set_child(label) def _bind_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem): label = cast(Gtk.Label, list_item.get_child()) item = cast(Gtk.StringObject, list_item.get_item()) label.set_text(item.get_string()) def _on_selection_changed( self, selection_model: Gtk.SingleSelection, position: int, n_items: int, ): if selection_model.get_selected() == Gtk.INVALID_LIST_POSITION: self.file_info_label.set_text("") else: selected_item = selection_model.get_selected_item() if selected_item: string_obj = cast(Gtk.StringObject, selected_item) self.file_info_label.set_text(string_obj.get_string()) def _on_key_pressed( self, controller: Gtk.EventControllerKey, keyval: int, keycode: int, state: Gdk.ModifierType, ) -> bool: if keyval == Gdk.keyval_from_name("Escape"): self.stack.set_visible_child_name("menu") self.list_view.grab_focus() return True return False def _populate_file_list(self) -> None: # TODO: Implement proper version sort (strverscmp equivalent) directories: list[str] = ["../"] files: list[str] = [] with os.scandir(".") as it: for entry in it: if entry.name == ".." or not entry.name.startswith("."): if entry.is_dir(): directories.append(entry.name + "/") else: files.append(entry.name) directories.sort(key=lambda x: x.lower()) files.sort(key=lambda x: x.lower()) while self.list_store.get_n_items(): self.list_store.remove(0) for name in directories + files: self.list_store.append(name) all = directories + files self.file_info_label.set_text(all[0] if all else "") class App(Gtk.Application): def __init__(self): super().__init__() # Initialize GStreamer Gst.init(None) # Load CSS css_provider = Gtk.CssProvider() css_file = Path(__file__).parent / "style.css" css_provider.load_from_path(str(css_file)) display = Gdk.Display.get_default() if display is None: raise RuntimeError("No display available") Gtk.StyleContext.add_provider_for_display( display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, ) def do_activate(self): win = MainWindow(application=self) win.present() def main(): app = App() app.run(None)