lazy-player/lazy_player/__init__.py

211 lines
6.6 KiB
Python

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="")
self.file_info_label.set_wrap(True)
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("."):
parts = entry.name.split(".")
suffix = parts[-1] if len(parts) >= 2 else ""
if entry.is_dir():
directories.append(entry.name + "/")
elif suffix in ("mkv", "mp4", "avi"):
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)