Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fe16d78
refactor(ui): add buttons for adding and removing applications
imoize May 23, 2025
27911af
refactor: update SchemaType and Application interfaces for consistency
imoize May 23, 2025
89260a0
chore: add generateId for random IDs
imoize May 23, 2025
8972c49
refactor: simplify validation logic and align with updated schema
imoize May 23, 2025
7aada67
refactor: update application settings schema and enhance application …
imoize May 23, 2025
373aa47
refactor: update use schemaKey for settings retrieval
imoize May 23, 2025
3ebc1ae
refactor: update settings migration logic and adjust schema version h…
imoize May 23, 2025
6aaa043
feat: now can select application to show in context menu
imoize May 24, 2025
0982a35
chore: add script for nautilus-extension
imoize May 24, 2025
09adc73
feat: add logging functionality with custom formatter (nautilus exten…
imoize May 24, 2025
4fd53eb
refactor: support for new app chooser feature and lot of improvement
imoize May 24, 2025
126fe92
chore(lang): update translation template
imoize May 24, 2025
6937743
feat(ui): add custom menu for preferences dialog
imoize May 24, 2025
3857394
chore(lang): update translation template
imoize May 24, 2025
70d573b
chore(menu): rename addMenu method to add for consistency
imoize May 24, 2025
9edd04c
feat(ui): add custom icon
imoize May 25, 2025
0302a22
feat: app entry can be pinned in main menu if submenu is enabled
imoize May 25, 2025
4d140d5
fix: fallback launcher retrieval and use gio for first launcher option
imoize May 25, 2025
a72ca98
refactor: organize nautilus-extension codebase into modules for bette…
imoize May 26, 2025
03e078b
chore(lang): update translation template
imoize May 26, 2025
acc4660
feat: add package type detection
imoize May 26, 2025
0d07c71
refactor: remove hardcoded application entries from schema
imoize May 26, 2025
7302279
misc: forgot to comment out
imoize May 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions @types/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
export interface SchemaType {
'settings-version': number;
'submenu': boolean;
'editors': string[];
settingsVersion: number;
submenu: boolean;
applications: string[];
}

export interface Application {
id: number;
id: string;
appId: string;
name: string;
enable?: boolean;
native?: string[];
flatpak?: string[];
arguments?: string[];
supports_files?: boolean;
icon: string;
pinned: boolean;
multipleFiles: boolean;
multipleFolders: boolean;
packageType: 'Flatpak' | 'AppImage' | 'Native';
mimeTypes?: string[];
enable: boolean;
}

export interface ValidationResult {
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ UI_FILES := $(patsubst resources/ui/%.blp,src/ui/%.ui,$(BLP_FILES))
UI_SRC := $(shell find src/ui -name '*.ui')
UI_DST := $(patsubst src/ui/%,dist/ui/%,$(UI_SRC))

.PHONY: all build build-ui pot pot-merge mo pack install test test-shell remove clean
.PHONY: all build build-ui pot pot-merge mo pack install test test-py test-shell remove clean

all: pack

Expand Down Expand Up @@ -73,6 +73,7 @@ pack: build schemas/gschemas.compiled copy-ui mo
@cp metadata.json dist/
@cp -r schemas dist/
@cp -r nautilus-extension/* dist/
@cp -r resources/ui/icons dist/ui/
@(cd dist && zip ../$(UUID).shell-extension.zip -9r .)

install: pack
Expand All @@ -83,6 +84,11 @@ test: pack
@cp -r dist $(HOME)/.local/share/gnome-shell/extensions/$(UUID)
gnome-extensions prefs $(UUID)

test-py:
@rm -rf $(HOME)/.local/share/gnome-shell/extensions/$(UUID)/Flickernaut
@rm -rf $(HOME)/.local/share/gnome-shell/extensions/$(UUID)/nautilus-flickernaut.py
@cp -r nautilus-extension/* $(HOME)/.local/share/gnome-shell/extensions/$(UUID)

test-shell:
@env GNOME_SHELL_SLOWDOWN_FACTOR=2 \
MUTTER_DEBUG_DUMMY_MODE_SPECS=1500x1000 \
Expand Down
140 changes: 140 additions & 0 deletions nautilus-extension/Flickernaut/launcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import os
import shlex
from gi.repository import GLib, Gio # type: ignore
from .logger import get_logger

logger = get_logger(__name__)


class Launcher:
"""Handles launching a desktop application."""

def __init__(self, app_info: Gio.DesktopAppInfo, app_id: str, name: str) -> None:
self.app_id = app_id
self.name = name
self._app_info = app_info
self._launch_method = "none"
self._run_command = ()
self._commandline = self._get_commandline(app_info)
self._set_launch_command()

logger.debug(f"launcher method: {self._launch_method}")
logger.debug(f"commandline: {self._commandline}")

def _get_commandline(self, app_info: Gio.DesktopAppInfo) -> list[str]:
"""Get the commandline from the app_info, handling special cases."""
executable = os.path.basename(app_info.get_executable()) or ""

bin_path = GLib.find_program_in_path(executable)
if not bin_path:
return []

cmd = app_info.get_commandline() or ""

# Split commandline into tokens while respecting quotes
tokens = shlex.split(cmd)

# Placeholder tokens
placeholders = {
"%f",
"%F",
"%u",
"%U",
"%d",
"%D",
"%n",
"%N",
"%k",
"%v",
"%m",
"%i",
"%c",
"%r",
"@@u",
"@@",
"@",
}
filtered = [
t for t in tokens if t not in placeholders and not t.startswith("%")
]

if bin_path and filtered:
filtered[0] = bin_path

return filtered

def _set_launch_command(self) -> None:
"""Determine the best launch command for the application."""
# 1. Try Gio.AppInfo.launch_uris first
if self._app_info:
self._launch_method = "gio-launch"
self._run_command = ()
return

# 2. Fallback to gtk-launch if gio-launch is not available
bin_path = GLib.find_program_in_path("gtk-launch")
if bin_path and os.path.isfile(bin_path):
desktop_id = (
self._app_info.get_id()[:-8]
if self._app_info.get_id().endswith(".desktop")
else self._app_info.get_id()
)
self._launch_method = "gtk-launch"
self._run_command = (bin_path, desktop_id)
return

# 3. Fallback to commandline if other methods are not available
if self._commandline:
self._launch_method = "commandline"
self._run_command = tuple(self._commandline)
return

self._run_command = ()
self._launch_method = "none"
self._init_failed = True

def launch(self, paths: list[str]) -> bool:
"""Launch the application based _launch_method."""
if self._launch_method == "gio-launch" and self._app_info:
try:
logger.debug(f"Launching {self.name} with gio-launch: {paths}")
ctx = None
self._app_info.launch_uris_async(paths, ctx)
return True
except Exception as e:
logger.error(
f"Failed to launch {self.name} with Gio.AppInfo.launch_uris: {e}"
)
return False

elif self._launch_method == "gtk-launch":
try:
command = list(self._run_command) + list(paths)
logger.debug(f"Launching {self.name}: {command}")
pid, *_ = GLib.spawn_async(command)
GLib.spawn_close_pid(pid)
return True
except Exception as e:
logger.error(f"Failed to launch {self.name} with gtk-launch: {e}")
return False

elif self._launch_method == "commandline":
try:
command = list(self._run_command) + list(paths)
logger.debug(f"Launching {self.name} with commandline: {command}")
pid, *_ = GLib.spawn_async(command)
GLib.spawn_close_pid(pid)
return True
except Exception as e:
logger.error(f"Failed to launch {self.name} with commandline: {e}")
return False

logger.error(f"No valid launch method for {self.app_id}")
return False

@property
def run_command(self) -> tuple[str, ...]:
return self._run_command

def __str__(self) -> str:
return f"Launcher({self.name}, method={self._launch_method}, cmd={self._run_command})"
20 changes: 20 additions & 0 deletions nautilus-extension/Flickernaut/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging

# Set to True for development only
FLICKERNAUT_DEBUG: bool = False


class FlickernautFormatter(logging.Formatter):
def format(self, record):
record.msg = f"[Flickernaut] [{record.levelname}] : {record.msg}"
return super().format(record)


def get_logger(name: str) -> logging.Logger:
logger = logging.getLogger(name)
if not logger.hasHandlers():
handler = logging.StreamHandler()
handler.setFormatter(FlickernautFormatter())
logger.addHandler(handler)
logger.setLevel(logging.DEBUG if FLICKERNAUT_DEBUG else logging.WARNING)
return logger
Loading