Compare commits
2 Commits
main
...
app-launch
| Author | SHA1 | Date | |
|---|---|---|---|
| 6366f57d6e | |||
| 5c5fce2581 |
@ -11,6 +11,7 @@ from fabric.utils import (
|
|||||||
)
|
)
|
||||||
from .modules.bar import StatusBar
|
from .modules.bar import StatusBar
|
||||||
from .modules.window_fuzzy import FuzzyWindowFinder
|
from .modules.window_fuzzy import FuzzyWindowFinder
|
||||||
|
from .modules.app_launcher import AppLauncher
|
||||||
|
|
||||||
|
|
||||||
tray = SystemTray(name="system-tray", spacing=4)
|
tray = SystemTray(name="system-tray", spacing=4)
|
||||||
@ -18,10 +19,11 @@ river = get_river_connection()
|
|||||||
|
|
||||||
dummy = Window(visible=False)
|
dummy = Window(visible=False)
|
||||||
finder = FuzzyWindowFinder()
|
finder = FuzzyWindowFinder()
|
||||||
|
launcher = AppLauncher()
|
||||||
|
|
||||||
bar_windows = []
|
bar_windows = []
|
||||||
|
|
||||||
app = Application("bar", dummy, finder)
|
app = Application("bar", dummy, finder, launcher)
|
||||||
app.set_stylesheet_from_file(get_relative_path("styles/main.css"))
|
app.set_stylesheet_from_file(get_relative_path("styles/main.css"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
192
bar/modules/app_launcher.py
Normal file
192
bar/modules/app_launcher.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
"""
|
||||||
|
example configuration shows how to make a simple
|
||||||
|
desktop applications launcher, this example doesn't involve
|
||||||
|
any styling (except a couple of basic style properties)
|
||||||
|
|
||||||
|
|
||||||
|
the purpose of this configuration is to show to to use
|
||||||
|
the given utils and mainly how using lazy executors might
|
||||||
|
make the configuration way more faster than it's supposed to be
|
||||||
|
"""
|
||||||
|
|
||||||
|
import operator
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.button import Button
|
||||||
|
from fabric.widgets.image import Image
|
||||||
|
from fabric.widgets.entry import Entry
|
||||||
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.utils import DesktopApp, get_desktop_applications, idle_add, remove_handler
|
||||||
|
import subprocess
|
||||||
|
from time import sleep
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class CustomApp:
|
||||||
|
name: str
|
||||||
|
generic_name: str | None
|
||||||
|
display_name: str | None
|
||||||
|
description: str | None
|
||||||
|
executable: str | None
|
||||||
|
command_line: str | None
|
||||||
|
hidden: bool
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
display_name=None,
|
||||||
|
executable=None,
|
||||||
|
generic_name=None,
|
||||||
|
description=None,
|
||||||
|
command_line=None,
|
||||||
|
hidden=False,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.generic_name = generic_name
|
||||||
|
self.display_name = display_name
|
||||||
|
self.description = description
|
||||||
|
self.executable = executable
|
||||||
|
self.command_line = command_line
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
|
def launch(self):
|
||||||
|
def background():
|
||||||
|
subprocess.run([self.command_line])
|
||||||
|
|
||||||
|
threading.Thread(target=background, daemon=True).start()
|
||||||
|
|
||||||
|
def get_icon_pixbuf(
|
||||||
|
self,
|
||||||
|
size: int = 48,
|
||||||
|
default_icon: str | None = "image-missing",
|
||||||
|
) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AppLauncher(Window):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
layer="top",
|
||||||
|
anchor="center",
|
||||||
|
exclusivity="none",
|
||||||
|
keyboard_mode="on-demand",
|
||||||
|
visible=False,
|
||||||
|
all_visible=False,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self._arranger_handler: int = 0
|
||||||
|
self._all_apps = get_desktop_applications()
|
||||||
|
self._custom_apps = [
|
||||||
|
CustomApp("Screenshot Clipboard", command_line="grim2clip")
|
||||||
|
]
|
||||||
|
|
||||||
|
self.viewport = Box(spacing=2, orientation="v")
|
||||||
|
self.search_entry = Entry(
|
||||||
|
placeholder="Search Applications...",
|
||||||
|
h_expand=True,
|
||||||
|
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
|
||||||
|
)
|
||||||
|
self.scrolled_window = ScrolledWindow(
|
||||||
|
min_content_size=(280, 320),
|
||||||
|
max_content_size=(280 * 2, 320),
|
||||||
|
child=self.viewport,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add(
|
||||||
|
Box(
|
||||||
|
spacing=2,
|
||||||
|
orientation="v",
|
||||||
|
style="margin: 2px",
|
||||||
|
children=[
|
||||||
|
# the header with the search entry
|
||||||
|
Box(
|
||||||
|
spacing=2,
|
||||||
|
orientation="h",
|
||||||
|
children=[
|
||||||
|
self.search_entry,
|
||||||
|
Button(
|
||||||
|
image=Image(icon_name="window-close"),
|
||||||
|
tooltip_text="Exit",
|
||||||
|
on_clicked=lambda *_: self.application.quit(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# the actual slots holder
|
||||||
|
self.scrolled_window,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def arrange_viewport(self, query: str = ""):
|
||||||
|
# reset everything so we can filter current viewport's slots...
|
||||||
|
# remove the old handler so we can avoid race conditions
|
||||||
|
remove_handler(self._arranger_handler) if self._arranger_handler else None
|
||||||
|
|
||||||
|
# remove all children from the viewport
|
||||||
|
self.viewport.children = []
|
||||||
|
|
||||||
|
combined_apps = self._all_apps + self._custom_apps
|
||||||
|
# make a new iterator containing the filtered apps
|
||||||
|
filtered_apps_iter = iter(
|
||||||
|
[
|
||||||
|
app
|
||||||
|
for app in combined_apps
|
||||||
|
if query.casefold()
|
||||||
|
in (
|
||||||
|
(app.display_name or "")
|
||||||
|
+ (" " + app.name + " ")
|
||||||
|
+ (app.generic_name or "")
|
||||||
|
).casefold()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
should_resize = operator.length_hint(filtered_apps_iter) == len(self._all_apps)
|
||||||
|
|
||||||
|
# all aboard...
|
||||||
|
# start the process of adding slots with a lazy executor
|
||||||
|
# using this method makes the process of adding slots way more less
|
||||||
|
# resource expensive without blocking the main thread and resulting in a lock
|
||||||
|
self._arranger_handler = idle_add(
|
||||||
|
lambda *args: self.add_next_application(*args)
|
||||||
|
or (self.resize_viewport() if should_resize else False),
|
||||||
|
filtered_apps_iter,
|
||||||
|
pin=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_next_application(self, apps_iter: Iterator[DesktopApp]):
|
||||||
|
if not (app := next(apps_iter, None)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.viewport.add(self.bake_application_slot(app))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def resize_viewport(self):
|
||||||
|
self.scrolled_window.set_min_content_width(
|
||||||
|
self.viewport.get_allocation().width # type: ignore
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def bake_application_slot(self, app: DesktopApp, **kwargs) -> Button:
|
||||||
|
return Button(
|
||||||
|
child=Box(
|
||||||
|
orientation="h",
|
||||||
|
spacing=12,
|
||||||
|
children=[
|
||||||
|
Image(pixbuf=app.get_icon_pixbuf(), h_align="start", size=32),
|
||||||
|
Label(
|
||||||
|
label=app.display_name or "Unknown",
|
||||||
|
v_align="center",
|
||||||
|
h_align="center",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
tooltip_text=app.description,
|
||||||
|
on_clicked=lambda *_: (self.hide(), app.launch()),
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user