feat: instances v2 (#6431)

* 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>
This commit is contained in:
Calum H.
2026-06-25 21:19:29 +00:00
committed by GitHub
co-authored by DJCheesusReal Truman Gao
parent ef4044534f
commit 734720e11e
353 changed files with 24745 additions and 9771 deletions
+125
View File
@@ -0,0 +1,125 @@
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())
}