{"id":13800864,"url":"https://github.com/ahkohd/tauri-nspanel","last_synced_at":"2025-09-07T20:45:10.951Z","repository":{"id":144632797,"uuid":"616317817","full_name":"ahkohd/tauri-nspanel","owner":"ahkohd","description":"Tauri plugin to convert a window to panel","archived":false,"fork":false,"pushed_at":"2025-09-02T21:27:18.000Z","size":10288,"stargazers_count":218,"open_issues_count":6,"forks_count":24,"subscribers_count":3,"default_branch":"v2.1","last_synced_at":"2025-09-06T21:00:00.537Z","etag":null,"topics":["macos","objcective-c","rust","tauri","tauri-plugin"],"latest_commit_sha":null,"homepage":"https://docs.aremu.dev/tauri-nspanel/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahkohd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE_APACHE-2.0","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-03-20T06:32:24.000Z","updated_at":"2025-09-03T06:47:04.000Z","dependencies_parsed_at":"2023-12-05T16:25:17.957Z","dependency_job_id":"56002a60-9e2c-492b-9537-379793d35d73","html_url":"https://github.com/ahkohd/tauri-nspanel","commit_stats":{"total_commits":65,"total_committers":6,"mean_commits":"10.833333333333334","dds":0.523076923076923,"last_synced_commit":"e38d989c5e97c5a55d90277765e2508b4f296832"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ahkohd/tauri-nspanel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahkohd%2Ftauri-nspanel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahkohd%2Ftauri-nspanel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahkohd%2Ftauri-nspanel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahkohd%2Ftauri-nspanel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahkohd","download_url":"https://codeload.github.com/ahkohd/tauri-nspanel/tar.gz/refs/heads/v2.1","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahkohd%2Ftauri-nspanel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274094507,"owners_count":25221423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["macos","objcective-c","rust","tauri","tauri-plugin"],"created_at":"2024-08-04T00:01:17.078Z","updated_at":"2025-09-07T20:45:10.935Z","avatar_url":"https://github.com/ahkohd.png","language":"Rust","funding_links":[],"categories":["Development"],"sub_categories":["Plugins"],"readme":"# tauri-nspanel\n\nCreate macOS panels for your Tauri app. Convert a regular window into a panel, or configure a new window with the panel builder.\n\n\u003e **Note**: For the previous version, see the [v2 branch](https://github.com/ahkohd/tauri-nspanel/tree/v2).\n\n## What are panels?\n\nPanels are a special type of window on macOS ([`NSPanel`](https://developer.apple.com/documentation/appkit/nspanel)) that float above other windows and provide auxiliary controls or information. They're commonly used for:\n- Tool palettes\n- Inspectors \n- Floating controls\n- HUD displays\n\n## Installation\n\nAdd the plugin to your `Cargo.toml`:\n\n```toml\n[dependencies]\ntauri-nspanel = { git = \"https://github.com/ahkohd/tauri-nspanel\", branch = \"v2.1\" }\n```\n\n## Usage\n\n### 1. Register the plugin\n\nIn your `src-tauri/src/main.rs`:\n\n```rust\nfn main() {\n    tauri::Builder::default()\n        .plugin(tauri_nspanel::init())\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\n### 2. Define custom panel classes\n\nDefine custom panel classes using the `tauri_panel!` macro to control panel behavior. Use one `tauri_panel!` per file or scope:\n\n```rust\ntauri_panel! {\n    panel!(MyPanel {\n        config: {\n            canBecomeKeyWindow: true\n        }\n    })\n    \n    // You can define multiple panels and event handlers in one block\n    panel!(MyFloatingPanel {\n        config: {\n            isFloatingPanel: true,\n            canBecomeKeyWindow: false\n        }\n    })\n    \n    panel_event!(MyPanelDelegate {\n        windowDidBecomeKey(notification: \u0026NSNotification) -\u003e (),\n        windowShouldClose(window: \u0026NSWindow) -\u003e Bool\n    })\n}\n```\n\n### Understanding the macros\n\n#### `panel!` macro\nThe `panel!` macro creates a custom NSPanel subclass with specified behaviors:\n- **config**: Override NSPanel methods that return boolean values\n- **with**: Optional configurations like tracking areas\n\n#### `panel_event!` macro\nThe `panel_event!` macro creates an NSWindowDelegate to handle window events:\n- Each method must specify parameter types and return type: `methodName(param: Type) -\u003e ReturnType`\n- Method names must match NSWindowDelegate protocol methods\n- Parameters in parentheses become part of the Objective-C selector\n- The macro automatically converts snake_case to camelCase\n- All callbacks receive strongly typed parameters instead of raw pointers\n\n### 3. Create panels using PanelBuilder\n\nThe `PanelBuilder` provides a flexible way to create panels with your custom panel classes:\n\n```rust\nuse tauri::{LogicalPosition, LogicalSize};\nuse tauri_nspanel::{PanelBuilder, PanelLevel, WebviewUrl};\n\ntauri::Builder::default()\n    .setup(|app| {\n        // Use the MyPanel class defined above\n        let panel = PanelBuilder::\u003c_, MyPanel\u003e::new(app.handle(), \"my-panel\")\n            .url(WebviewUrl::App(\"panel.html\".into()))\n            .title(\"My Panel\")\n            .position(LogicalPosition::new(100.0, 100.0))\n            .size(LogicalSize::new(400.0, 300.0))\n            .level(PanelLevel::Floating)\n            .floating(true)\n            .build()?;\n        \n        panel.show();\n        Ok(())\n    })\n    // ...\n```\n\n\n#### Advanced PanelBuilder configuration\n\n```rust\n// Define a more advanced panel class\ntauri_panel! {\n    panel!(AdvancedPanel {\n        config: {\n            canBecomeKeyWindow: false,\n            isFloatingPanel: true,\n            becomesKeyOnlyIfNeeded: true\n        }\n    })\n}\n\n// Use it with advanced configuration\nlet panel = PanelBuilder::\u003c_, AdvancedPanel\u003e::new(app.handle(), \"advanced-panel\")\n    .url(WebviewUrl::App(\"panel.html\".into()))\n    .level(PanelLevel::Status)  // High window level\n    .style_mask(\n        StyleMask::empty()\n            .nonactivating_panel()  // Doesn't activate app\n            .utility_window()       // Smaller title bar\n            .titled()\n            .closable()\n    )\n    .collection_behavior(\n        CollectionBehavior::new()\n            .can_join_all_spaces()\n            .stationary()\n    )\n    .alpha_value(0.95)\n    .has_shadow(true)\n    .with_window(|window| {\n        // Access any Tauri window configuration\n        window\n            .decorations(false)\n            .min_inner_size(300.0, 200.0)\n            .max_inner_size(800.0, 600.0)\n            .resizable(false)\n    })\n    .build()?;\n```\n\n### 4. Convert existing windows to panels\n\nYou can convert existing Tauri windows to panels. First define your panel class, then use it:\n\n```rust\nuse tauri_nspanel::{tauri_panel, WebviewWindowExt};\n\n// Define your custom panel type\ntauri_panel! {\n    panel!(ConvertedPanel {\n        config: {\n            canBecomeKeyWindow: false,\n            isFloatingPanel: true\n        }\n    })\n}\n\n// Convert existing window to your custom panel type\nlet window = app.get_webview_window(\"main\").unwrap();\nlet panel = window.to_panel::\u003cConvertedPanel\u003e()?;\npanel.show();\n```\n\n### 5. Create custom panel classes\n\nFor advanced use cases, you can define custom panel classes with specific behaviors. The `panel!` and `panel_event!` macros should be used inside the `tauri_panel!` macro.\n\n#### Using `panel!` macro\n\nThe `panel!` macro creates a custom NSPanel subclass. Inside the config block, you can override any NSPanel method that returns a boolean value:\n\n```rust\nuse tauri_nspanel::{tauri_panel, PanelBuilder, TrackingAreaOptions};\n\n// Define custom panel class and event handler together\ntauri_panel! {\n    panel!(MyFloatingPanel {\n        config: {\n            // Override NSPanel methods that return boolean values\n            canBecomeKeyWindow: true,\n            canBecomeMainWindow: false,\n            becomesKeyOnlyIfNeeded: true,\n            isFloatingPanel: true\n        }\n        with: {\n            tracking_area: {\n                // Mouse tracking configuration using the builder pattern\n                options: TrackingAreaOptions::new()\n                    .active_always()\n                    .mouse_entered_and_exited()\n                    .mouse_moved(),\n                auto_resize: true\n            }\n        }\n    })\n    \n    panel_event!(MyFloatingPanelDelegate {\n        windowDidBecomeKey(notification: \u0026NSNotification) -\u003e (),\n        windowDidResignKey(notification: \u0026NSNotification) -\u003e (),\n        windowShouldClose(window: \u0026NSWindow) -\u003e Bool\n    })\n}\n\n// Use with PanelBuilder\nlet panel = PanelBuilder::\u003c_, MyFloatingPanel\u003e::new(app.handle(), \"my-panel\")\n    .url(WebviewUrl::App(\"panel.html\".into()))\n    .level(PanelLevel::Floating)\n    .build()?;\n\n// Or convert existing window to custom panel type\nlet panel = window.to_panel::\u003cMyFloatingPanel\u003e()?;\n```\n\n### 6. Handle panel events with event handlers\n\nEvent handlers are typically defined together with panel classes in the `tauri_panel!` macro.\n\n#### Using `panel_event!` macro\n\nThe `panel_event!` macro creates an NSWindowDelegate that handles window events. The macro:\n- Generates type-safe event handler methods with strongly typed parameters\n- Automatically converts method names to proper Objective-C selectors\n- Requires explicit return type declarations for all methods\n- Supports both void (`-\u003e ()`) and value-returning delegate methods\n- **Mouse tracking**: When you enable `tracking_area` in your panel configuration, mouse event callbacks become available\n\n**Selector generation rules:**\n- Single parameter: `methodName(param)` → `methodName:`\n- Multiple parameters: `methodName(first, second)` → `methodName:second:`\n- Snake_case is automatically converted to camelCase: `to_size` → `toSize`\n\nSee the [objc2-app-kit NSWindowDelegate documentation](https://docs.rs/objc2-app-kit/0.3.1/objc2_app_kit/trait.NSWindowDelegate.html) for the complete list of available delegate methods.\n\n#### Mouse tracking events\n\nWhen you enable `tracking_area` in your panel configuration, the following mouse event callbacks become available on your event handler:\n\n- `on_mouse_entered()` - Called when the mouse enters the panel\n- `on_mouse_exited()` - Called when the mouse exits the panel  \n- `on_mouse_moved()` - Called when the mouse moves within the panel\n- `on_cursor_update()` - Called when the cursor needs to be updated\n\nExample with mouse tracking:\n\n```rust\ntauri_panel! {\n    panel!(MouseTrackingPanel {\n        config: {\n            canBecomeKeyWindow: true\n        }\n        with: {\n            tracking_area: {\n                options: TrackingAreaOptions::new()\n                    .active_always()\n                    .mouse_entered_and_exited()\n                    .mouse_moved()\n                    .cursor_update(),\n                auto_resize: true\n            }\n        }\n    })\n    \n    panel_event!(MouseTrackingPanelDelegate {\n        windowDidBecomeKey(notification: \u0026NSNotification) -\u003e ()\n    })\n}\n\n// Create the event handler and set up mouse callbacks\nlet handler = MouseTrackingPanelDelegate::new();\n\n// These methods are available when tracking_area is enabled\nhandler.on_mouse_entered(|event| {\n    println!(\"Mouse entered the panel\");\n});\n\nhandler.on_mouse_exited(|event| {\n    println!(\"Mouse exited the panel\");\n});\n\nhandler.on_mouse_moved(|event| {\n    let location = unsafe { event.locationInWindow() };\n    println!(\"Mouse moved to: x={}, y={}\", location.x, location.y);\n});\n\nhandler.on_cursor_update(|event| {\n    println!(\"Cursor update requested\");\n    // You could change the cursor here based on hover state\n});\n\n// Attach the handler to your panel\npanel.set_event_handler(Some(handler.as_protocol_object()));\n```\n\nExample usage:\n\n```rust\nuse tauri_nspanel::{tauri_panel, PanelBuilder};\n\n// Define panel class and event handler together\ntauri_panel! {\n    panel!(MyInteractivePanel {\n        config: {\n            canBecomeKeyWindow: true,\n            canBecomeMainWindow: false\n        }\n    })\n    \n    panel_event!(MyInteractivePanelDelegate {\n        windowDidBecomeKey(notification: \u0026NSNotification) -\u003e (),\n        windowDidResignKey(notification: \u0026NSNotification) -\u003e (),\n        windowShouldClose(window: \u0026NSWindow) -\u003e Bool,\n        windowWillClose(notification: \u0026NSNotification) -\u003e (),\n        windowWillResize(sender: \u0026NSWindow, to_size: \u0026NSSize) -\u003e NSSize  // Multiple parameters example\n    })\n}\n\n// Create and configure the event handler\nlet handler = MyInteractivePanelDelegate::new();\n\nhandler.window_did_become_key(|notification| {\n    // notification is already typed as \u0026NSNotification\n    println!(\"Panel became key window: {:?}\", notification);\n});\n\nhandler.window_should_close(|window| {\n    println!(\"Panel should close?: {:?}\", window);\n    // Return true to allow closing\n    Bool::new(true)\n});\n\nhandler.window_will_resize(|sender, to_size| {\n    // Parameters are already typed: sender is \u0026NSWindow, to_size is \u0026NSSize\n    println!(\"Window {:?} will resize to: {:?}\", sender, to_size);\n    \n    // Enforce minimum size\n    NSSize {\n        width: to_size.width.max(400.0),\n        height: to_size.height.max(300.0),\n    }\n});\n\nlet panel = PanelBuilder::\u003c_, MyInteractivePanel\u003e::new(app.handle(), \"my-panel\")\n    .url(WebviewUrl::App(\"panel.html\".into()))\n    .build()?;\n\npanel.set_event_handler(Some(handler.as_protocol_object()));\n```\n\n**Return types in panel_event! macro:**\n\nMethods must specify their return type explicitly:\n- `-\u003e ()` - For void methods (no return value)\n- `-\u003e Bool` - For BOOL returns (use `Bool::new(true/false)`)\n- `-\u003e NSSize` - For NSSize value returns\n- `-\u003e NSRect` - For NSRect value returns  \n- `-\u003e NSPoint` - For NSPoint value returns\n- `-\u003e Option\u003c\u0026'static NSObject\u003e` - For nullable object returns\n- Other types as needed by the delegate method\n\nThe macro handles conversion between Rust types and Objective-C types automatically.\n\n### 7. Access panels from anywhere\n\n```rust\nuse tauri_nspanel::ManagerExt;\n\nlet panel = app.get_webview_panel(\"my-panel\")?;\npanel.show(); // or `panel.show_and_make_key();`\n```\n\n### 8. Panel cleanup\n\nPanels are not automatically released when closed. To ensure proper cleanup:\n\n```rust\npanel.set_released_when_closed(true);\n// release the event handler if any\npanel.set_event_handler(None);\npanel.close(\u0026app_handle);\n```\n\n## Available Panel Methods\n\nCommon panel control methods:\n- Window visibility: `show()`, `hide()`, `close()`\n- Window state: `make_key_window()`, `resign_key_window()`, `make_main_window()`\n- Window level: `set_level()` (accepts `PanelLevel` enum or `i32`)\n- Appearance: `set_alpha_value()`, `set_has_shadow()`, `set_opaque()`\n- Size: `set_content_size()`\n- Behavior: `set_floating_panel()`, `set_hides_on_deactivate()`, `set_works_when_modal()`\n- Mouse events: `set_accepts_mouse_moved_events()`, `set_ignores_mouse_events()`\n- Collection behavior: `set_collection_behavior()` (accepts `CollectionBehavior` or raw flags)\n- And many more...\n\n### Advanced: Accessing the underlying NSPanel\n\nFor functionality not directly exposed by the library, you can access the underlying `NSPanel` instance:\n\n```rust\nuse tauri_nspanel::ManagerExt;\nuse objc2_app_kit::{NSWindowOcclusionState, NSWindowTabbingMode};\n\nlet panel = app.get_webview_panel(\"my-panel\")?;\n\n// Get the underlying NSPanel reference\nlet ns_panel = panel.as_panel();\n\n// Use any NSPanel/NSWindow method from objc2-app-kit\nunsafe {\n    // Example: Get window information\n    let frame = ns_panel.frame();\n    let screen = ns_panel.screen();\n    let backing_scale_factor = ns_panel.backingScaleFactor();\n    \n    // Example: Set tinting and appearance\n    ns_panel.setTitlebarSeparatorStyle(objc2_app_kit::NSTitlebarSeparatorStyle::Shadow);\n    ns_panel.setTitleVisibility(objc2_app_kit::NSWindowTitleVisibility::Hidden);\n    \n    // Example: Window tabbing\n    ns_panel.setTabbingMode(NSWindowTabbingMode::Disallowed);\n    \n    // Example: Check occlusion state\n    let occlusion_state = ns_panel.occlusionState();\n    if occlusion_state.contains(NSWindowOcclusionState::Visible) {\n        println!(\"Window is visible\");\n    }\n}\n```\n\nThis allows you to:\n- Call any NSPanel or NSWindow method not wrapped by the library\n- Access window properties like frame, screen, backing scale factor\n- Configure advanced window features like tabbing, title visibility, etc.\n- Integrate with other macOS APIs that expect NSPanel/NSWindow references\n\nSee the [objc2-app-kit NSPanel documentation](https://docs.rs/objc2-app-kit/0.3.1/objc2_app_kit/struct.NSPanel.html) for the complete list of available methods.\n\n**Note**: The NSPanel methods are marked as `unsafe` in objc2-app-kit because they must be called on the main thread. Ensure you're on the main thread when using these methods.\n\n## Key Types\n\n### PanelLevel\nPredefined window levels for panels:\n```rust\nPanelLevel::Normal \nPanelLevel::Floating\nPanelLevel::ModalPanel\nPanelLevel::Utility\nPanelLevel::Status        \nPanelLevel::PopUpMenu\nPanelLevel::ScreenSaver\nPanelLevel::Custom(i32)    // Any custom value\n```\n\n### CollectionBehavior\nBuilder pattern for window collection behaviors:\n```rust\nCollectionBehavior::new()\n    .can_join_all_spaces()    // Show on all spaces\n    .stationary()             // Don't move between spaces\n    .ignores_cycle()          // Skip in Cmd+Tab\n    .full_screen_auxiliary()  // Allow with fullscreen apps\n```\n\n### TrackingAreaOptions\nBuilder pattern for mouse tracking areas:\n```rust\nTrackingAreaOptions::new()\n    .active_always()                // Track in any app state\n    .mouse_entered_and_exited()     // Track enter/exit events\n    .mouse_moved()                  // Track mouse movement\n    .cursor_update()                // Update cursor\n```\n\n### StyleMask\nBuilder pattern for window style masks:\n```rust\n// Default panel with title bar and controls\nStyleMask::new()  // Includes: Titled, Closable, Miniaturizable, Resizable\n\n// Borderless panel\nStyleMask::empty()\n    .borderless()\n\n// HUD-style panel\nStyleMask::empty()\n    .hud_window()\n    .titled()\n    .closable()\n\n// Non-activating utility panel\nStyleMask::empty()\n    .utility_window()\n    .nonactivating_panel()\n    .titled()\n\n// Full-featured panel with custom styling\nStyleMask::new()\n    .full_size_content_view()\n    .unified_title_and_toolbar()\n```\n\nAvailable style mask options:\n- `titled()` - Window has a title bar\n- `closable()` - Window has a close button\n- `miniaturizable()` - Window has a minimize button\n- `resizable()` - Window can be resized\n- `unified_title_and_toolbar()` - Unified title and toolbar appearance\n- `full_size_content_view()` - Content extends under title bar\n- `utility_window()` - Utility window style (smaller title bar)\n- `hud_window()` - HUD window style (dark translucent)\n- `nonactivating_panel()` - Panel doesn't activate the app\n- `borderless()` - No title bar or border (replaces all other styles)\n\n## Examples\n\nCheck out the [examples](/examples) directory for complete working examples:\n- [`basic/`](/examples/basic/) - Basic panel setup in a vanilla JavaScript Tauri app\n- [`panel_builder/`](/examples/panel_builder/) - Basic panel setup using `PanelBuilder`\n- [`panel_macro`](/examples/panel_macro.rs) - Basic panel creation with the macro\n- [`panel_builder`](/examples/panel_builder.rs) - Using the PanelBuilder API\n- [`panel_levels`](/examples/panel_levels.rs) - Demonstrating different window levels\n- [`panel_style_mask`](/examples/panel_style_mask.rs) - Different NSWindowStyleMask configurations\n- [`collection_behavior`](/examples/collection_behavior.rs) - Combining collection behaviors\n- [`builder_with_custom_panel`](/examples/builder_with_custom_panel.rs) - Using custom panel classes with PanelBuilder\n- [`panel_event_macro`](/examples/panel_event_macro.rs) - Event handling with delegates\n- [`fullscreen/`](/examples/fullscreen/) - Panel behavior with fullscreen windows (full Tauri app example)\n- [`mouse_tracking/`](/examples/mouse_tracking/) - Mouse tracking events with panels (full Tauri app example with mouse enter/exit/move callbacks)\n- [`hover_activate/`](/examples/hover_activate/) - Auto-activate panel on hover using mouse tracking (full Tauri app example)\n\n## Thread Safety\n\nThe panel types implement `Send` and `Sync` to work with Tauri's command system. However, all actual panel operations must be performed on the main thread. The plugin handles this internally for the provided methods.\n\n## Related Projects\n\n- [tauri-plugin-spotlight](https://github.com/zzzze/tauri-plugin-spotlight)\n- [tauri-macos-spotlight-example](https://github.com/ahkohd/tauri-macos-spotlight-example)\n- [tauri-macos-menubar-example](https://github.com/ahkohd/tauri-macos-menubar-app-example)\n\n## Showcase\n\nProjects using `tauri-nspanel`:\n- [Cap](https://github.com/CapSoftware/Cap)\n- [EcoPaste](https://github.com/EcoPasteHub/EcoPaste)\n- [Overlayed](https://github.com/overlayeddev/overlayed)\n- [Lume](https://github.com/lumehq/lume)\n- [Verve](https://github.com/ParthJadhav/verve)\n- [Buffer](https://buffer.md)\n\n## Contributing\n\nPRs accepted. Please make sure to read the Contributing Guide before making a pull request.\n\n## License\n\nMIT or MIT/Apache 2.0 where applicable.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahkohd%2Ftauri-nspanel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahkohd%2Ftauri-nspanel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahkohd%2Ftauri-nspanel/lists"}