diff --git a/res/icon-chunithm.ico b/res/icon-chunithm.ico new file mode 100644 index 0000000..0de4dc3 Binary files /dev/null and b/res/icon-chunithm.ico differ diff --git a/res/icon-ongeki.ico b/res/icon-ongeki.ico new file mode 100644 index 0000000..11c028d Binary files /dev/null and b/res/icon-ongeki.ico differ diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 686f42f..ef5d262 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -803,7 +803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b6f366b..ea57554 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -53,5 +53,5 @@ tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } tauri-plugin-updater = "2" [target.'cfg(target_os = "windows")'.dependencies] -winsafe = { version = "0.0.23", features = ["user"] } +winsafe = { version = "0.0.23", features = ["user", "ole", "shell"] } displayz = "^0.2.0" diff --git a/rust/src/cmd.rs b/rust/src/cmd.rs index 707495e..3f835f3 100644 --- a/rust/src/cmd.rs +++ b/rust/src/cmd.rs @@ -397,6 +397,13 @@ pub async fn load_segatools_ini(state: State<'_, Mutex>, path: PathBuf) Ok(()) } +#[tauri::command] +pub async fn create_shortcut(app: AppHandle, profile_meta: ProfileMeta) -> Result<(), String> { + log::debug!("invoke: create_shortcut({:?})", profile_meta); + + util::create_shortcut(app, &profile_meta).map_err(|e| e.to_string()) +} + #[tauri::command] pub async fn list_platform_capabilities() -> Result, ()> { log::debug!("invoke: list_platform_capabilities"); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0f16002..1153ae3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -199,6 +199,7 @@ pub async fn run(_args: Vec) { cmd::sync_current_profile, cmd::save_current_profile, cmd::load_segatools_ini, + cmd::create_shortcut, cmd::get_global_config, cmd::set_global_config, diff --git a/rust/src/model/misc.rs b/rust/src/model/misc.rs index 0f727c1..36cea60 100644 --- a/rust/src/model/misc.rs +++ b/rust/src/model/misc.rs @@ -20,6 +20,13 @@ impl Game { } } + pub fn print(&self) -> &'static str { + match self { + Game::Ongeki => "O.N.G.E.K.I.", + Game::Chunithm => "CHUNITHM" + } + } + pub fn hook_exe(&self) -> &'static str { match self { Game::Ongeki => "mu3hook.dll", diff --git a/rust/src/util.rs b/rust/src/util.rs index c459fb7..17fbbf0 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -173,4 +173,44 @@ pub async fn remove_dir_all(path: impl AsRef) -> Result<()> { } else { Err(anyhow!("invalid remove_dir_all target: not in a data directory")) } +} + +#[cfg(target_os = "windows")] +pub fn create_shortcut( + apph: AppHandle, + meta: &crate::profiles::ProfileMeta +) -> Result<()> { + use winsafe::{co, prelude::{ole_IPersistFile, ole_IUnknown, shell_IShellLink}, CoCreateInstance, CoInitializeEx, IPersistFile}; + let _com_guard = CoInitializeEx( + co::COINIT::APARTMENTTHREADED + | co::COINIT::DISABLE_OLE1DDE, + )?; + let obj = CoCreateInstance::( + &co::CLSID::ShellLink, + None, + co::CLSCTX::INPROC_SERVER, + )?; + + let target_dir = apph.path().cache_dir()?.join(NAME); + let target_path = target_dir.join("startliner.exe"); + let lnk_path = apph.path().desktop_dir()?.join(format!("{}-{}.lnk", &meta.game, &meta.name)); + + obj.SetPath(target_path.to_str().ok_or_else(|| anyhow!("Illegal target path"))?)?; + obj.SetDescription(&format!("{} – {}", &meta.game.print(), &meta.name))?; + obj.SetArguments(&format!("--start --game {} --profile {}", &meta.game, &meta.name))?; + obj.SetIconLocation( + target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?, + 0 + )?; + + match meta.game { + Game::Ongeki => std::fs::write(target_dir.join("icon-ongeki.ico"), include_bytes!("../../res/icon-ongeki.ico")), + Game::Chunithm => std::fs::write(target_dir.join("icon-chunithm.ico"), include_bytes!("../../res/icon-chunithm.ico")) + }?; + + let file = obj.QueryInterface::()?; + + file.Save(Some(lnk_path.to_str().ok_or_else(|| anyhow!("Illegal shortcut path"))?), true)?; + + Ok(()) } \ No newline at end of file diff --git a/src/components/ProfileListEntry.vue b/src/components/ProfileListEntry.vue index a8c5da4..57b09cb 100644 --- a/src/components/ProfileListEntry.vue +++ b/src/components/ProfileListEntry.vue @@ -2,6 +2,7 @@ import { ref } from 'vue'; import Button from 'primevue/button'; import InputText from 'primevue/inputtext'; +import { useConfirm } from 'primevue/useconfirm'; import * as path from '@tauri-apps/api/path'; import { invoke } from '../invoke'; import { useGeneralStore, usePrfStore } from '../stores'; @@ -9,6 +10,8 @@ import { ProfileMeta } from '../types'; const general = useGeneralStore(); const prf = usePrfStore(); +const confirmDialog = useConfirm(); + const isEditing = ref(false); const props = defineProps({ @@ -54,6 +57,14 @@ const deleteProfile = async () => { await prf.reloadList(); await prf.reload(); }; + +const promptDeleteProfile = async () => { + confirmDialog.require({ + message: `Are you sure you want to delete ${props.p?.game}-${props.p?.name}?`, + header: 'Delete profile', + accept: deleteProfile, + }); +};