mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 01:26:23 +00:00
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
126 lines
3.4 KiB
Rust
126 lines
3.4 KiB
Rust
use crate::api::Result;
|
|
use std::{
|
|
os::windows::ffi::OsStrExt,
|
|
path::{Path, PathBuf},
|
|
};
|
|
use url::Url;
|
|
use windows::{
|
|
Win32::{
|
|
System::Com::{
|
|
CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED,
|
|
COINIT_DISABLE_OLE1DDE, CoCreateInstance, CoInitializeEx,
|
|
CoUninitialize, IPersistFile,
|
|
},
|
|
UI::Shell::{IShellLinkW, ShellLink},
|
|
},
|
|
core::{Interface, PCWSTR},
|
|
};
|
|
|
|
pub(super) const SHORTCUT_EXTENSION: &str = "lnk";
|
|
|
|
pub(super) async fn create_shortcut(
|
|
_profile_name: &str,
|
|
launch_url: &Url,
|
|
output_path: &Path,
|
|
) -> Result<()> {
|
|
let target_path = std::env::current_exe()?;
|
|
let working_dir = target_path
|
|
.parent()
|
|
.map(Path::to_path_buf)
|
|
.unwrap_or_default();
|
|
let output_path = output_path.to_path_buf();
|
|
let launch_url = launch_url.to_string();
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
create_windows_shortcut(
|
|
output_path,
|
|
target_path,
|
|
working_dir,
|
|
launch_url,
|
|
)
|
|
})
|
|
.await
|
|
.map_err(|error| {
|
|
std::io::Error::other(format!(
|
|
"failed to join shortcut creation task: {error}"
|
|
))
|
|
})??;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn create_windows_shortcut(
|
|
output_path: PathBuf,
|
|
target_path: PathBuf,
|
|
working_dir: PathBuf,
|
|
launch_url: String,
|
|
) -> std::io::Result<()> {
|
|
let output_path = windows_wide_path(&output_path);
|
|
let target_path = windows_wide_path(&target_path);
|
|
let working_dir = windows_wide_path(&working_dir);
|
|
let launch_url = windows_wide_string(&launch_url);
|
|
|
|
// SAFETY:
|
|
// - COM is initialized for this blocking thread before any COM object is created.
|
|
// - `_com` is declared before the COM interface values, so it is dropped
|
|
// after them and calls `CoUninitialize` only once they are released.
|
|
// - Every PCWSTR points to a NUL-terminated UTF-16 buffer that lives until
|
|
// each call using it has returned.
|
|
unsafe {
|
|
let init_result = CoInitializeEx(
|
|
None,
|
|
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
|
|
);
|
|
windows_result(init_result.ok())?;
|
|
let _com = WindowsComGuard;
|
|
|
|
let shortcut: IShellLinkW = windows_result(CoCreateInstance(
|
|
&ShellLink,
|
|
None,
|
|
CLSCTX_INPROC_SERVER,
|
|
))?;
|
|
windows_result(shortcut.SetPath(windows_pcwstr(&target_path)))?;
|
|
windows_result(shortcut.SetArguments(windows_pcwstr(&launch_url)))?;
|
|
windows_result(
|
|
shortcut.SetWorkingDirectory(windows_pcwstr(&working_dir)),
|
|
)?;
|
|
windows_result(
|
|
shortcut.SetIconLocation(windows_pcwstr(&target_path), 0),
|
|
)?;
|
|
|
|
let persist_file: IPersistFile = windows_result(shortcut.cast())?;
|
|
windows_result(persist_file.Save(windows_pcwstr(&output_path), true))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn windows_result<T>(result: windows::core::Result<T>) -> std::io::Result<T> {
|
|
result.map_err(std::io::Error::other)
|
|
}
|
|
|
|
struct WindowsComGuard;
|
|
|
|
impl Drop for WindowsComGuard {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
CoUninitialize();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn windows_wide_path(path: &Path) -> Vec<u16> {
|
|
path.as_os_str()
|
|
.encode_wide()
|
|
.chain(std::iter::once(0))
|
|
.collect()
|
|
}
|
|
|
|
fn windows_wide_string(value: &str) -> Vec<u16> {
|
|
value.encode_utf16().chain(std::iter::once(0)).collect()
|
|
}
|
|
|
|
fn windows_pcwstr(value: &[u16]) -> PCWSTR {
|
|
PCWSTR::from_raw(value.as_ptr())
|
|
}
|