🧩 Simple File Explorer Plugin Guide

Read this through and you will learn how to create your own plugin! Basically a step-by-step.

πŸ“ File Structure

Each plugin is a .py file stored in the plugins/ folder:

Simple File Explorer/
β”œβ”€β”€ plugins/
β”‚   β”œβ”€β”€ my_plugin.py

πŸš€ Plugin Structure

Each plugin must define an on_load(app, tools) function:

def on_load(app, tools):
    def on_start():
        tools.show_noti("Plugin loaded!")
    tools.on_event("app_start", on_start)

After that, a plugin has virtually any possibilitiesβ€”you can even build full apps inside it!

πŸ› οΈ PluginAPI Methods

MethodDescription
on_event(event, handler)Register a function for a named event
trigger_event(...)Manually fire an event
show_noti(message)Display a notification near the mouse
run_error(message)Show a modal error box
refreshlist()Refresh the UI file list
get_selected_filenames()Get list of selected files
delete_path(path)Delete a file or folder
events()List all registered events
GetCurrentPath()Return current path open in the file explorer
OnKeyPress(key, handler)Detects when a key is pressed. Special characters include:
Return, Up, Control-Left, Alt-$, etc.
AddContextMenuCommand(label,handler)Adds a command into the Context Menu
NewFilePrompt()Creates a prompt for the user to create a new file.
RenameFilePrompt()Creates a prompt for the user to rename the selected file.
DeleteFilePrompt()Creates a prompt for the user to delete selected file(s).
register_undo_protoc()Register an undo event for use in the undo keybind (CTRL+Z).
add_undo()Adds and undo dictionary to the undo_stack.(EXAMPLE: {"action": "move", "extradata1": data, "extradata2": data}
run_in_thread(func, *args, **kwargs)Runs a function in a thread so it doesn't freeze the interface/UI
get_module(name)Gets a module from the Python Library that's not preloaded.
get_file_properties(filepath)Returns a dictionary containing metadata about the given file or folder. Returned dictionary keys:

        - name: The base name of the file or folder.
        - path: The absolute path.
        - is_file: True if the path is a regular file.
        - is_dir: True if the path is a directory.
        - size: File size in bytes (None for directories).
        - modified_time: Last modification time (string).
        - created_time: Creation time (string).
        - permissions: Unix-style permissions string (e.g., '644').
        - extension: File extension (if any, for files only).
    
Returns {'error': ...} if the path does not exist or an error occurs.

πŸ“¦ Plugin Metadata (Optional)

Add a plugin_info block to declare plugin details:

plugin_info = {
    "name": "Reverse Theme",
    "author": "jr",
    "version": "1.0.0",
    "trusted": True
}

This appears in the Plugin Manager if present.

🧩 Built-In Plugin Manager

Press Ctrl+P to open a window listing all installed plugins, their authors, and version numbers.

πŸ”” Event Names

(Some events are experimental or reserved.)

Event NameDescription
"app_start"Fired when the app starts
"app_exit"Before the app quits
"plugins_loaded"After all plugins are loaded
"directory_changed"When the current directory changes
"file_selected"When user selects files
"file_deleted"After a file is deleted
"file_renamed"After a file is renamed
"file_created"After a new file or folder is created
"file_moved"After a file is moved
"before_file_delete"Before deleting a file (can cancel)
"before_directory_change"Before changing folders (can cancel)
"key_pressed"When a key is pressed (e.g. <Control-p>)
"opened_context_menu"When the Context Menu is opened.

⚑ Example: React to Image Selection

def on_load(app, tools):
    def handle_file_select(files):
        for f in files:
            if f.lower().endswith((".jpg", ".png")):
                tools.show_noti("πŸ“Έ You selected an image!")

    tools.on_event("file_selected", handle_file_select)

βš™οΈ Extra Module Access

Access pre-imported modules via tools.modules["name"].
Available: os, shutil, tkinter, vlc, PIL, random

πŸͺŸ Example: Pop-up That Says β€œhey”

def on_load(app, tools):
    tk = tools.modules["tkinter"]

    def show_window():
        win = tk.Toplevel(app)
        win.title("Plugin Says Hi")
        win.geometry("200x100")
        label = tk.Label(win, text="hey", font=("Segoe UI", 12))
        label.pack(expand=True, padx=20, pady=20)
        win.transient(app)
        win.grab_set()
        win.focus_force()

    tools.on_event("app_start", lambda: show_window())

⌨️ Button Press

Detect Button Presses via tools.OnKeyPress("key", command).
This will either be a single key: "a","b" or a combination like : "<Control+a>","<Control+Alt+r>"

🎭 Example: Reversing Colors

def on_load(app, tools):
    tk = tools.modules["tkinter"]

    def reverse_colors():
        fg = tools.context.get("fg", "#fff")
        bg = tools.context.get("bg", "#000")
        inverted_fg = bg
        inverted_bg = fg

        def invert_widget_colors(widget):
            try:
                widget.configure(bg=inverted_bg, fg=inverted_fg)
            except tk.TclError:
                pass
            for child in widget.winfo_children():
                invert_widget_colors(child)
                invert_widget_colors(app)
        tools.show_noti("🎭 Reverse theme activated!")

    tools.OnKeyPress("", reverse_colors)

🎭 Example: Create File in Context Menu

def on_load(app, tools):
    def on_start():
        tools.AddContextMenuCommand("Create New File", lambda e: tools.NewFilePrompt())
        
    tools.on_event("app_start", on_start)

🎭 Example: Zip File Context Menu

def on_load(app, tools):
    print("🧩 [ZIP Compressor] Plugin loaded")

    os = tools.modules["os"]
    zipfile = tools.modules["zipfile"]

    api = tools

    def compress_selected2():
        selected = api.get_selected_filenames()
        if not selected:
            api.run_error("No files selected to compress.")
            return

        current_dir = api.GetCurrentPath()
        archive_path = os.path.join(current_dir, "compressed.zip")

        try:
            with zipfile.ZipFile(archive_path, 'w') as zipf:
                for fname in selected:
                    full_path = os.path.join(current_dir, fname)
                    if os.path.isfile(full_path):
                        zipf.write(full_path, arcname=fname)
                    else:
                        api.run_error(f"Skipping '{fname}': Not a file.")

            api.add_undo({
                "action": "compress_file",
                "info": {"archive_path": archive_path}
            })

            api.refreshlist()
            api.show_noti("βœ… Created compressed.zip")

        except Exception as e:
            api.run_error(f"Compression failed: {e}")
            
    def compress_selected():
        
        tools.run_in_thread(compress_selected2)

    def undo_zip_compress(info):
        archive_path = info.get("archive_path")
        if archive_path and os.path.exists(archive_path):
            try:
                os.remove(archive_path)
                api.refreshlist()
                print(f"[UNDO] Deleted: {archive_path}")
            except Exception as e:
                api.run_error(f"Undo failed: {e}")
        else:
            api.run_error("Undo failed: ZIP file not found.")

    api.register_undo_protoc("compress_file", undo_zip_compress)
    api.AddContextMenuCommand("πŸ“¦ Compress to ZIP (undoable)", compress_selected)

πŸ§ͺ Debugging Tips