diff --git a/Cargo.lock b/Cargo.lock index ab1bece904..8eb8194f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15382,6 +15382,7 @@ dependencies = [ "specta-typescript", "tauri", "tauri-plugin", + "tauri-plugin-cli2", "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", "tauri-plugin-local-stt", diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index f301ca3f21..32757120b9 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -56,8 +56,14 @@ "description": "Hyprnote", "args": [], "subcommands": { - "hello": { + "bug": { + "description": "Open GitHub issues page to report a bug" + }, + "web": { "description": "Open https://hyprnote.com" + }, + "changelog": { + "description": "Open the changelog page" } } }, diff --git a/plugins/cli2/src/commands.rs b/plugins/cli2/src/commands.rs index 8cd5eb105f..13a0bc6085 100644 --- a/plugins/cli2/src/commands.rs +++ b/plugins/cli2/src/commands.rs @@ -1,8 +1,11 @@ +use crate::CliPluginExt; + #[tauri::command] #[specta::specta] pub(crate) async fn install_cli(app: tauri::AppHandle) -> Result<(), String> { - use crate::CliPluginExt; - app.install_cli_to_path().map_err(|e| e.to_string()) + app.plugin_cli() + .install_cli_to_path() + .map_err(|e| e.to_string()) } #[tauri::command] @@ -10,8 +13,9 @@ pub(crate) async fn install_cli(app: tauri::AppHandle) -> pub(crate) async fn uninstall_cli( app: tauri::AppHandle, ) -> Result<(), String> { - use crate::CliPluginExt; - app.uninstall_cli_from_path().map_err(|e| e.to_string()) + app.plugin_cli() + .uninstall_cli_from_path() + .map_err(|e| e.to_string()) } #[tauri::command] @@ -19,6 +23,7 @@ pub(crate) async fn uninstall_cli( pub(crate) async fn check_cli_status( app: tauri::AppHandle, ) -> Result { - use crate::CliPluginExt; - app.check_cli_status().map_err(|e| e.to_string()) + app.plugin_cli() + .check_cli_status() + .map_err(|e| e.to_string()) } diff --git a/plugins/cli2/src/ext.rs b/plugins/cli2/src/ext.rs index bbda9c4a6e..c62744f8af 100644 --- a/plugins/cli2/src/ext.rs +++ b/plugins/cli2/src/ext.rs @@ -1,19 +1,14 @@ use std::path::PathBuf; -pub trait CliPluginExt { - fn handle_cli_matches(&self) -> Result<(), crate::Error>; - fn get_cli_symlink_path(&self) -> PathBuf; - fn get_cli_executable_path(&self) -> Result; - fn install_cli_to_path(&self) -> Result<(), crate::Error>; - fn uninstall_cli_from_path(&self) -> Result<(), crate::Error>; - fn check_cli_status(&self) -> Result; +pub struct PluginCli { + app_handle: tauri::AppHandle, } -impl> crate::CliPluginExt for T { - fn handle_cli_matches(&self) -> Result<(), crate::Error> { +impl PluginCli { + pub fn handle_cli_matches(&self) -> Result<(), crate::Error> { use tauri_plugin_cli::CliExt; - match self.cli().matches() { + match self.app_handle.cli().matches() { Ok(matches) => { if matches.args.contains_key("help") || matches.args.contains_key("version") { std::process::exit(0); @@ -28,7 +23,7 @@ impl> crate::CliPluginExt for T { Ok(()) } - fn get_cli_symlink_path(&self) -> PathBuf { + pub fn get_cli_symlink_path(&self) -> PathBuf { #[cfg(unix)] { if let Some(home) = std::env::var_os("HOME") { @@ -51,11 +46,11 @@ impl> crate::CliPluginExt for T { } } - fn get_cli_executable_path(&self) -> Result { + pub fn get_cli_executable_path(&self) -> Result { std::env::current_exe().map_err(|e| e.into()) } - fn install_cli_to_path(&self) -> Result<(), crate::Error> { + pub fn install_cli_to_path(&self) -> Result<(), crate::Error> { let exe_path = self.get_cli_executable_path()?; let symlink_path = self.get_cli_symlink_path(); @@ -91,7 +86,7 @@ impl> crate::CliPluginExt for T { Ok(()) } - fn uninstall_cli_from_path(&self) -> Result<(), crate::Error> { + pub fn uninstall_cli_from_path(&self) -> Result<(), crate::Error> { #[cfg(not(any(unix, windows)))] { return Err(crate::Error::UnsupportedPlatform); @@ -116,7 +111,7 @@ impl> crate::CliPluginExt for T { Ok(()) } - fn check_cli_status(&self) -> Result { + pub fn check_cli_status(&self) -> Result { let symlink_path = self.get_cli_symlink_path(); if !symlink_path.exists() { @@ -137,6 +132,18 @@ impl> crate::CliPluginExt for T { } } +pub trait CliPluginExt { + fn plugin_cli(&self) -> PluginCli; +} + +impl> CliPluginExt for T { + fn plugin_cli(&self) -> PluginCli { + PluginCli { + app_handle: self.app_handle().clone(), + } + } +} + #[derive(serde::Serialize, serde::Deserialize, specta::Type)] #[serde(rename_all = "camelCase")] pub struct CliStatus { diff --git a/plugins/cli2/src/handler.rs b/plugins/cli2/src/handler.rs index 211fb88b5f..b246d1306e 100644 --- a/plugins/cli2/src/handler.rs +++ b/plugins/cli2/src/handler.rs @@ -2,17 +2,15 @@ use tauri::AppHandle; use tauri_plugin_cli::Matches; pub fn entrypoint(app: &AppHandle, matches: Matches) { - if matches.args.contains_key("help") { - std::process::exit(0); - } - - if matches.args.contains_key("version") { - std::process::exit(0); - } - + let version = app.package_info().version.to_string(); if let Some(subcommand_matches) = matches.subcommand { match subcommand_matches.name.as_str() { - "hello" => hello(app), + "bug" => url( + app, + format!("https://github.com/fastrepl/hyprnote/issues/new?labels=bug,v{version}"), + ), + "web" => url(app, "https://hyprnote.com"), + "changelog" => url(app, "https://hyprnote.com/changelog"), _ => { tracing::warn!("unknown_subcommand: {}", subcommand_matches.name); std::process::exit(1); @@ -21,8 +19,8 @@ pub fn entrypoint(app: &AppHandle, matches: Matches) { } } -fn hello(_app: &AppHandle) { - match open::that("https://hyprnote.com") { +fn url(_app: &AppHandle, url: impl Into) { + match open::that(url.into()) { Ok(_) => std::process::exit(0), Err(e) => { tracing::error!("open_url_error: {e}"); diff --git a/plugins/cli2/src/lib.rs b/plugins/cli2/src/lib.rs index 3163f0ad0a..831a07fcd0 100644 --- a/plugins/cli2/src/lib.rs +++ b/plugins/cli2/src/lib.rs @@ -6,6 +6,8 @@ mod handler; pub use error::{Error, Result}; pub use ext::*; +pub use tauri_plugin_cli::CliExt; + const PLUGIN_NAME: &str = "cli2"; fn make_specta_builder() -> tauri_specta::Builder { diff --git a/plugins/tray/Cargo.toml b/plugins/tray/Cargo.toml index 79a4d4d0cf..54a08c1c80 100644 --- a/plugins/tray/Cargo.toml +++ b/plugins/tray/Cargo.toml @@ -19,6 +19,7 @@ hypr-host = { workspace = true } tauri = { workspace = true, features = ["tray-icon", "image-png"] } tauri-specta = { workspace = true, features = ["derive", "typescript"] } +tauri-plugin-cli2 = { workspace = true } tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-local-stt = { workspace = true } diff --git a/plugins/tray/src/ext.rs b/plugins/tray/src/ext.rs index 34164c05fc..444c2d34f3 100644 --- a/plugins/tray/src/ext.rs +++ b/plugins/tray/src/ext.rs @@ -17,6 +17,8 @@ pub enum HyprMenuItem { TrayStart, TrayQuit, AppInfo, + AppCliInstall, + AppCliUninstall, AppNew, } @@ -27,6 +29,8 @@ impl From for MenuId { HyprMenuItem::TrayStart => "hypr_tray_start", HyprMenuItem::TrayQuit => "hypr_tray_quit", HyprMenuItem::AppInfo => "hypr_app_info", + HyprMenuItem::AppCliInstall => "hypr_app_cli_install", + HyprMenuItem::AppCliUninstall => "hypr_app_cli_uninstall", HyprMenuItem::AppNew => "hypr_app_new", } .into() @@ -41,6 +45,8 @@ impl From for HyprMenuItem { "hypr_tray_start" => HyprMenuItem::TrayStart, "hypr_tray_quit" => HyprMenuItem::TrayQuit, "hypr_app_info" => HyprMenuItem::AppInfo, + "hypr_app_cli_install" => HyprMenuItem::AppCliInstall, + "hypr_app_cli_uninstall" => HyprMenuItem::AppCliUninstall, "hypr_app_new" => HyprMenuItem::AppNew, _ => unreachable!(), } @@ -58,6 +64,7 @@ impl> TrayPluginExt for T { let app = self.app_handle(); let info_item = app_info_menu(app)?; + let cli_item = app_cli_menu(app)?; let new_item = app_new_menu(app)?; if cfg!(target_os = "macos") { @@ -67,15 +74,15 @@ impl> TrayPluginExt for T { if items.len() > 0 { if let MenuItemKind::Submenu(submenu) = &items[0] { submenu.remove_at(0)?; + submenu.remove_at(0)?; + submenu.prepend(&cli_item)?; submenu.prepend(&info_item)?; - return Ok(()); } } if items.len() > 1 { if let MenuItemKind::Submenu(submenu) = &items[1] { submenu.prepend(&new_item)?; - return Ok(()); } } } @@ -163,6 +170,18 @@ impl> TrayPluginExt for T { } }); } + HyprMenuItem::AppCliInstall => { + use tauri_plugin_cli2::CliPluginExt; + if let Ok(_) = app.plugin_cli().install_cli_to_path() { + let _ = app.create_app_menu(); + } + } + HyprMenuItem::AppCliUninstall => { + use tauri_plugin_cli2::CliPluginExt; + if let Ok(_) = app.plugin_cli().uninstall_cli_from_path() { + let _ = app.create_app_menu(); + } + } HyprMenuItem::AppNew => { use tauri_plugin_windows::{AppWindow, Navigate, WindowsPluginExt}; if let Ok(_) = app.window_show(AppWindow::Main) { @@ -213,6 +232,34 @@ fn app_info_menu(app: &AppHandle) -> Result> { ) } +fn app_cli_menu(app: &AppHandle) -> Result> { + use tauri_plugin_cli2::CliPluginExt; + + let is_installed = app + .plugin_cli() + .check_cli_status() + .map(|status| status.is_installed) + .unwrap_or(false); + + if is_installed { + MenuItem::with_id( + app, + HyprMenuItem::AppCliUninstall, + "Uninstall CLI", + true, + None::<&str>, + ) + } else { + MenuItem::with_id( + app, + HyprMenuItem::AppCliInstall, + "Install CLI", + true, + None::<&str>, + ) + } +} + fn app_new_menu(app: &AppHandle) -> Result> { MenuItem::with_id( app,