diff --git a/Cargo.lock b/Cargo.lock index 5a61121f..dd904a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2173,6 +2173,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "global-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.3", + "objc2-app-kit", + "once_cell", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -3744,6 +3762,7 @@ dependencies = [ "tauri-plugin-deep-link", "tauri-plugin-dialog", "tauri-plugin-fs", + "tauri-plugin-global-shortcut", "tauri-plugin-os", "tauri-plugin-single-instance", "tauri-plugin-updater", @@ -6508,6 +6527,21 @@ dependencies = [ "uuid 1.16.0", ] +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31919f3c07bcb585afef217c0c33cde80da9ebccf5b8e2c90e0e0a535b14ab47" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + [[package]] name = "tauri-plugin-opener" version = "2.3.0" @@ -8448,6 +8482,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 05859d53..e4bebe86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ tauri-plugin-deep-link = { version = "=2.2.1" } tauri-plugin-dialog = { version = "=2.2.1" } tauri-plugin-fs = { version = "=2.2.1", features = ["watch"] } tauri-plugin-os = { version = "=2.2.1" } +tauri-plugin-global-shortcut = { version = "=2.2.1" } tauri-plugin-single-instance = { version = "=2.2.3" } tauri-plugin-updater = { version = "=2.7.1" } tauri-plugin-window-state = { version = "=2.2.2" } diff --git a/apps/oneclient/desktop/Cargo.toml b/apps/oneclient/desktop/Cargo.toml index 079bfb4e..cfcff190 100644 --- a/apps/oneclient/desktop/Cargo.toml +++ b/apps/oneclient/desktop/Cargo.toml @@ -38,6 +38,7 @@ tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-fs = { workspace = true } tauri-plugin-os = { workspace = true } +tauri-plugin-global-shortcut = { workspace = true } tauri-plugin-deep-link = { workspace = true } # code gen diff --git a/apps/oneclient/desktop/capabilities/default.json b/apps/oneclient/desktop/capabilities/default.json index b72d4d5c..a6abfba1 100644 --- a/apps/oneclient/desktop/capabilities/default.json +++ b/apps/oneclient/desktop/capabilities/default.json @@ -37,7 +37,8 @@ "fs:allow-download-write", "fs:allow-data-read-recursive", "fs:allow-data-write-recursive", - "fs:allow-watch", + "fs:allow-watch", + "os:deny-hostname", "os:allow-locale", "os:allow-os-type", @@ -45,6 +46,10 @@ "os:allow-version", "os:allow-arch", "os:deny-exe-extension", - "os:allow-family" + "os:allow-family", + + "global-shortcut:allow-is-registered", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister" ] } diff --git a/apps/oneclient/desktop/src/lib.rs b/apps/oneclient/desktop/src/lib.rs index 5881c0cf..fbfd9245 100644 --- a/apps/oneclient/desktop/src/lib.rs +++ b/apps/oneclient/desktop/src/lib.rs @@ -1,5 +1,5 @@ use onelauncher_core::api::proxy::ProxyTauri; -use onelauncher_core::api::tauri::TauRPCLauncherExt; +use onelauncher_core::api::tauri::{TauRPCLauncherExt, TauriLauncherDebugApi}; use onelauncher_core::error::LauncherResult; use onelauncher_core::store::proxy::ProxyState; use onelauncher_core::store::semaphore::SemaphoreStore; @@ -48,6 +48,12 @@ async fn initialize_core() -> LauncherResult<()> { SemaphoreStore::get().await; tracing::info!("initialized core modules"); + let debug_info = onelauncher_core::api::tauri::TauriLauncherDebugApiImpl + .get_full_debug_info_parsed_string() + .await; + + tracing::info!("\n{}", debug_info); + Ok(()) } @@ -75,11 +81,17 @@ async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_deep_link::init()) + .plugin(tauri_plugin_os::init()) .menu(tauri::menu::Menu::default) .invoke_handler(router.into_handler()) .setup(move |app| { app.manage(ext::updater::UpdaterState::default()); setup_window(app.handle()).expect("failed to setup main window"); + + #[cfg(desktop)] + app.handle() + .plugin(tauri_plugin_global_shortcut::Builder::new().build())?; + Ok(()) }); diff --git a/apps/oneclient/frontend/package.json b/apps/oneclient/frontend/package.json index 6bd7972f..f1237b57 100644 --- a/apps/oneclient/frontend/package.json +++ b/apps/oneclient/frontend/package.json @@ -29,6 +29,7 @@ "@tauri-apps/plugin-clipboard-manager": "catalog:", "@tauri-apps/plugin-dialog": "catalog:", "@tauri-apps/plugin-fs": "catalog:", + "@tauri-apps/plugin-global-shortcut": "catalog:", "@untitled-theme/icons-react": "catalog:", "fuse.js": "^7.1.0", "motion": "catalog:", diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index cafcd331..18b0d442 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -29,6 +29,10 @@ export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExis export type DbVec = T[] +export type DebugInfoData = { inDev: boolean; platform: string; arch: string; family: string; locale: string; osType: string; osVersion: string; osDistro: string; commitHash: string; buildTimestamp: string; buildVersion: string } + +export type DebugInfoParsedLine = { title: string; value: string } + export type DirectoryError = { type: "BaseDir"; data: string } export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string } @@ -244,9 +248,9 @@ gallery: string[] } export type SettingProfileModel = { name: string; java_id: number | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } -export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; show_tanstack_dev_tools: boolean } +export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; log_debug_info: boolean; show_tanstack_dev_tools: boolean } -export type SettingsOsExtra = Record +export type SettingsOsExtra = { enable_gamemode: boolean | null } export type SkinVariant = "classic" | "slim" @@ -313,18 +317,27 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"extractBundleOverrides":["bundle_path","cluster_id"],"getVersions":[],"isBundleSyncing":[],"cacheArt":["path"],"checkForUpdate":[],"updateBundlePackages":["cluster_id"],"refreshArt":["path"],"getClustersGroupedByMajor":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"installUpdate":[]}', 'core':'{"getLogs":["id"],"getProfileOrDefault":["name"],"getClusters":[],"installModpack":["modpack","cluster_id"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"refreshAccounts":[],"updateClusterById":["id","request"],"launchCluster":["id","uuid","search_for_java"],"getGlobalProfile":[],"createCluster":["options"],"searchPackages":["provider","query"],"removeUser":["uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getDefaultUser":["fallback"],"removeCape":["access_token"],"removePackage":["cluster_id","package_hash"],"removeCluster":["id"],"getMultiplePackages":["provider","slugs"],"syncCluster":["cluster_id"],"getLinkedPackages":["cluster_id"],"fetchMinecraftProfile":["uuid"],"refreshAccount":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"isClusterRunning":["cluster_id"],"setClusterStage":["id","stage"],"killProcess":["pid"],"getLogByName":["id","name"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"getScreenshots":["id"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"openMsaLogin":[],"convertUsernameUUID":["username_uuid"],"getClusterById":["id"],"getRunningProcesses":[],"getLoadersForVersion":["mc_version"],"getUsers":[],"createSettingsProfile":["name"],"getUser":["uuid"],"readSettings":[],"setDefaultUser":["uuid"],"getPackageBody":["provider","body"],"updateClusterProfile":["name","profile"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getWorlds":["id"],"getProcessLogTail":["id","max_lines"],"changeCape":["access_token","cape_uuid"],"getGameVersions":[],"setDiscordRPCMessage":["message"],"open":["input"],"getPackage":["provider","slug"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'debug':'{"getGitCommitHash":[],"getPackageVersion":[],"getFamily":[],"getOsVersion":[],"openDevTools":[],"getArch":[],"getBuildTimestamp":[],"getType":[],"isInDev":[],"getLocale":[],"getPlatform":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'debug': { openDevTools: () => Promise, +const ARGS_MAP = { 'debug':'{"getFullDebugInfoParsedString":[],"getBuildTimestamp":[],"getPackageVersion":[],"openDevTools":[],"getFamily":[],"getArch":[],"isInDev":[],"getFullDebugInfo":[],"getType":[],"getLocale":[],"getPlatform":[],"getOsDistro":[],"getGitCommitHash":[],"getOsVersion":[],"getFullDebugInfoParsed":[]}', 'oneclient':'{"updateBundlePackages":["cluster_id"],"installUpdate":[],"checkForUpdate":[],"cacheArt":["path"],"extractBundleOverrides":["bundle_path","cluster_id"],"isBundleSyncing":[],"getBundlesFor":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getVersions":[],"getClustersGroupedByMajor":[],"refreshArt":["path"]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"removeCape":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"getLinkedPackages":["cluster_id"],"fetchMinecraftProfile":["uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"updateClusterById":["id","request"],"getLogs":["id"],"openMsaLogin":[],"getGameVersions":[],"setDefaultUser":["uuid"],"syncCluster":["cluster_id"],"refreshAccount":["uuid"],"removeCluster":["id"],"removePackage":["cluster_id","package_hash"],"convertUsernameUUID":["username_uuid"],"getClusterById":["id"],"fetchLoggedInProfile":["access_token"],"removeUser":["uuid"],"getLogByName":["id","name"],"launchCluster":["id","uuid","search_for_java"],"readSettings":[],"createCluster":["options"],"getGlobalProfile":[],"isClusterRunning":["cluster_id"],"searchPackages":["provider","query"],"getRunningProcessesByClusterId":["cluster_id"],"refreshAccounts":[],"getDefaultUser":["fallback"],"getUsers":[],"getPackage":["provider","slug"],"getPackageBody":["provider","body"],"getClusters":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"setDiscordRPCMessage":["message"],"updateClusterProfile":["name","profile"],"setClusterStage":["id","stage"],"getUser":["uuid"],"killProcess":["pid"],"getWorlds":["id"],"installModpack":["modpack","cluster_id"],"open":["input"],"createSettingsProfile":["name"],"getProcessLogTail":["id","max_lines"],"getMultiplePackages":["provider","slugs"],"getRunningProcesses":[],"getLoadersForVersion":["mc_version"],"getProfileOrDefault":["name"],"writeSettings":["setting"],"getScreenshots":["id"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"changeCape":["access_token","cape_uuid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'debug': { openDevTools: () => Promise, isInDev: () => Promise, getArch: () => Promise, getFamily: () => Promise, -getLocale: () => Promise, +getLocale: () => Promise, getType: () => Promise, getPlatform: () => Promise, getOsVersion: () => Promise, +getOsDistro: () => Promise, getGitCommitHash: () => Promise, getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise }, +getPackageVersion: () => Promise, +getFullDebugInfo: () => Promise, +getFullDebugInfoParsed: () => Promise, +getFullDebugInfoParsedString: () => Promise }, 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, @@ -389,12 +402,7 @@ downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleN updateBundlePackages: (clusterId: number) => Promise, isBundleSyncing: () => Promise, cacheArt: (path: string) => Promise, -refreshArt: (path: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise } }; +refreshArt: (path: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/MadeBy.tsx b/apps/oneclient/frontend/src/components/MadeBy.tsx new file mode 100644 index 00000000..6802ee8a --- /dev/null +++ b/apps/oneclient/frontend/src/components/MadeBy.tsx @@ -0,0 +1,21 @@ +import { bindings } from '@/main'; +import { copyDebugInfo } from '@/utils/debugInfo'; +import { useCommand } from '@onelauncher/common'; +import { Button } from '@onelauncher/common/components'; + +export function MadeBy() { + const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion()); + const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev()); + + return ( + + ); +} diff --git a/apps/oneclient/frontend/src/components/index.ts b/apps/oneclient/frontend/src/components/index.ts index fa60c6e2..ef71b48f 100644 --- a/apps/oneclient/frontend/src/components/index.ts +++ b/apps/oneclient/frontend/src/components/index.ts @@ -8,6 +8,7 @@ export * from './GameBackground'; export * from './LaunchButton'; export * from './Loader'; export * from './LogViewer'; +export * from './MadeBy'; export * from './ManageSkinButton'; export * from './Markdown'; export * from './Navbar'; diff --git a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx index e28f30cd..c19b16ee 100644 --- a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx +++ b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx @@ -1,106 +1,7 @@ +import type { DebugInfoArray } from '@/utils/debugInfo'; import { Overlay } from '@/components'; -import { bindings } from '@/main'; +import { copyDebugInfo, useDebugInfo } from '@/utils/debugInfo'; import { Button } from '@onelauncher/common/components'; -import { writeText } from '@tauri-apps/plugin-clipboard-manager'; -import { useEffect, useState } from 'react'; - -export type DebugInfoArray = Array<{ title: string; value: string }>; -export interface DebugInfoData { - inDev: boolean; - platform: string; - arch: string; - family: string; - locale: string; - type: string; - osVersion: string; - commitHash: string; - buildTimestamp: string; - buildVersion: string; -} - -export function useDebugInfo(): DebugInfoArray { - const [devInfo, setDevInfo] = useState({ - inDev: false, - platform: 'UNKNOWN', - arch: 'UNKNOWN', - family: 'UNKNOWN', - locale: 'UNKNOWN', - type: 'UNKNOWN', - osVersion: 'UNKNOWN', - commitHash: 'UNKNOWN', - buildTimestamp: 'UNKNOWN', - buildVersion: 'UNKNOWN', - }); - - useEffect(() => { - const fetchDevInfo = async () => { - const [ - inDev, - platform, - arch, - family, - locale, - type, - osVersion, - commitHash, - buildTimestamp, - buildVersion, - ] = await Promise.all([ - bindings.debug.isInDev(), - bindings.debug.getPlatform(), - bindings.debug.getArch(), - bindings.debug.getFamily(), - bindings.debug.getLocale(), - bindings.debug.getType(), - bindings.debug.getOsVersion(), - bindings.debug.getGitCommitHash(), - bindings.debug.getBuildTimestamp(), - bindings.debug.getPackageVersion(), - ]); - - setDevInfo({ - inDev, - platform, - arch, - family, - locale: locale ?? 'UNKNOWN', - type, - osVersion, - commitHash, - buildTimestamp, - buildVersion, - }); - }; - - void fetchDevInfo(); - }, []); - - return [ - { title: 'inDev', value: devInfo.inDev ? 'yes' : 'no' }, - { title: 'Platform', value: devInfo.platform }, - { title: 'Arch', value: devInfo.arch }, - { title: 'Family', value: devInfo.family }, - { title: 'Locale', value: devInfo.locale }, - { title: 'Type', value: devInfo.type }, - { title: 'Os Version', value: devInfo.osVersion }, - { title: 'Commit Hash', value: devInfo.commitHash }, - { title: 'Build Timestamp', value: devInfo.buildTimestamp }, - { title: 'Version', value: devInfo.buildVersion }, - ]; -} - -export function copyDebugInfo(debugInfo: DebugInfoArray) { - const timestamp = Math.floor(new Date().getTime() / 1000); - const lines = [ - `**Data exported at:** (\`${timestamp}\`)`, - ...debugInfo.map((lineData) => { - if (lineData.title === 'Build Timestamp') - return `**${lineData.title}:** (\`${lineData.value}\`)`; - return `**${lineData.title}:** \`${lineData.value}\``; - }), - ]; - writeText(lines.join('\n')); -} export function DebugInfo() { const debugInfo = useDebugInfo(); @@ -113,7 +14,6 @@ export function DebugInfo() { } export function RawDebugInfo({ debugInfo }: { debugInfo: DebugInfoArray }) { - const copy = () => copyDebugInfo(debugInfo); return ( <>
@@ -126,7 +26,7 @@ export function RawDebugInfo({ debugInfo }: { debugInfo: DebugInfoArray }) { return

{line}

; })}
- + ); } diff --git a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx index b99e2b92..bebf92c4 100644 --- a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx +++ b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx @@ -1,6 +1,7 @@ -import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingsRow, SettingsSwitch, useDebugInfo } from '@/components'; +import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingsRow, SettingsSwitch } from '@/components'; import { useSettings } from '@/hooks/useSettings'; import { bindings } from '@/main'; +import { useDebugInfo } from '@/utils/debugInfo'; import { Button } from '@onelauncher/common/components'; import { Link } from '@tanstack/react-router'; import { dataDir, join } from '@tauri-apps/api/path'; @@ -34,6 +35,10 @@ export function SuperSecretDevOptions() { Super Secret Dev Options
+ } title="Log Debug Info"> + + + } title="Tanstack Dev Tools"> diff --git a/apps/oneclient/frontend/src/routes/__root.tsx b/apps/oneclient/frontend/src/routes/__root.tsx index 2b01b2ef..d10333a2 100644 --- a/apps/oneclient/frontend/src/routes/__root.tsx +++ b/apps/oneclient/frontend/src/routes/__root.tsx @@ -2,14 +2,13 @@ import type { QueryClient } from '@tanstack/react-query'; import type { NavigateOptions, ToOptions } from '@tanstack/react-router'; import { Toasts } from '@/components'; import { useSettings } from '@/hooks/useSettings'; -import { bindings } from '@/main'; -import { checkForUpdate, installUpdate } from '@/utils/updater'; -import { useCommand } from '@onelauncher/common'; +import { useDebugKeybind } from '@/utils/debugInfo'; +import { useDiscordRPC } from '@/utils/discordRPC'; +import { useAutoUpdater } from '@/utils/updater'; import { TanStackDevtools } from '@tanstack/react-devtools'; import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'; -import { createRootRouteWithContext, Outlet, useLocation, useRouter } from '@tanstack/react-router'; +import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router'; import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; -import { useEffect } from 'react'; import { RouterProvider } from 'react-aria-components'; interface AppRouterContext { @@ -27,99 +26,12 @@ export const Route = createRootRouteWithContext()({ component: RootRoute, }); -type URLPath = Exclude; -const ResolvedPathNames: Record = { - '.': 'UNKNOWN', - '..': 'UNKNOWN', - '/': 'Viewing Home', - '/app': 'Viewing Homepage', - '/app/account': 'Viewing Account', - '/app/account/skins': 'Viewing Skin Manager', - '/app/cluster': 'Viewing Versions', - '/app/cluster/browser': 'Viewing {clusterName}\'s mods', - '/app/cluster/browser/package': 'Browsing {packageName}', - '/app/cluster/logs': 'Viewing {clusterName}\'s logs', - '/app/cluster/mods': 'Viewing {clusterName}\'s mods', - '/app/cluster/resource-packs': 'Viewing {clusterName}\'s resource packs', - '/app/cluster/shaders': 'Viewing {clusterName}\'s shaders', - '/app/cluster/datapacks': 'Viewing {clusterName}\'s data packs', - '/app/cluster/process': 'Viewing {clusterName}', - '/app/cluster/settings': 'Viewing {clusterName}\'s settings', - '/app/settings': 'Viewing Settings', - '/app/settings/appearance': 'Viewing Settings', - '/app/settings/developer': 'Viewing Settings', - '/app/settings/minecraft': 'Viewing Settings', - '/app/settings/changelog': 'Viewing Settings', - '/app/accounts': 'Viewing Accounts', - '/app/clusters': 'Viewing Versions', - '/onboarding': 'Preparing OneClient', - '/onboarding/account': 'Preparing OneClient', - '/onboarding/finished': 'Preparing OneClient', - '/onboarding/language': 'Preparing OneClient', - '/onboarding/preferences/version': 'Preparing OneClient', - '/onboarding/preferences/versionCategory': 'Preparing OneClient', - '/onboarding/preferences': 'Preparing OneClient', -}; - -// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225 -function ReplaceVariables(template: string, variables: Record) { - return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match); -} - -function useDiscordRPC() { - const location = useLocation(); - const clusterId = location.search.clusterId ?? 0; - const provider = location.search.provider ?? null; - const packageId = location.search.packageId ?? null; - const { data: cluster } = useCommand(['getClusterById', clusterId], () => bindings.core.getClusterById(clusterId)); - - const { data: managedPackage } = useCommand( - ['getPackage', provider, packageId], - () => { - if (provider == null || packageId == null) - return Promise.reject(new Error('Missing parameters')); - return bindings.core.getPackage(provider, packageId); - }, - { enabled: provider != null && packageId != null }, - ); - - useEffect(() => { - const template = ResolvedPathNames[location.pathname as URLPath]; - if (template) - bindings.core.setDiscordRPCMessage(ReplaceVariables(template, { clusterName: cluster?.name ?? 'UNKNOWN', packageName: managedPackage?.name ?? 'UNKNOWN' })); - }, [location.pathname, location.search.clusterId, cluster?.name, managedPackage?.name]); -} - -function useAutoUpdate() { - useEffect(() => { - void (async () => { - try { - const update = await checkForUpdate(); - if (!update) - return; - - // eslint-disable-next-line no-console -- Used for debugging - aka important - console.log('Update found on initial check:', update.version); - - try { - await installUpdate(); - } - catch (e) { - console.error('Failed to install update:', e); - } - } - catch (e) { - console.error('Failed to check for update:', e); - } - })(); - }, []); -} - function RootRoute() { const router = useRouter(); const { setting } = useSettings(); useDiscordRPC(); - useAutoUpdate(); + useAutoUpdater(); + useDebugKeybind(); return ( router.navigate({ to, ...options })} diff --git a/apps/oneclient/frontend/src/routes/app/settings/developer.tsx b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx index 196cb999..6c5ec3de 100644 --- a/apps/oneclient/frontend/src/routes/app/settings/developer.tsx +++ b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx @@ -31,6 +31,9 @@ function RouteComponent() { Dev Tools + } title="Log Debug Info"> + + - {/* */} +
diff --git a/apps/oneclient/frontend/src/routes/onboarding/route.tsx b/apps/oneclient/frontend/src/routes/onboarding/route.tsx index adb1772f..25244aea 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/route.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/route.tsx @@ -1,9 +1,9 @@ import type { DownloadModsRef } from '@/components'; import type { PropsWithChildren } from 'react'; import LauncherLogo from '@/assets/logos/oneclient.svg?react'; -import { GameBackground, LoaderSuspense, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components'; +import { GameBackground, LoaderSuspense, MadeBy, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components'; import { bindings } from '@/main'; -import { useCommand, useCommandSuspense } from '@onelauncher/common'; +import { useCommandSuspense } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { useQueryClient } from '@tanstack/react-query'; import { createFileRoute, Link, Outlet, useLocation, useNavigate } from '@tanstack/react-router'; @@ -154,8 +154,6 @@ function AppShell({ children, }: PropsWithChildren) { const { isFirstStep, currentStepIndex } = Route.useLoaderData(); - const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion()); - const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev()); return (
@@ -180,12 +178,7 @@ function AppShell({ - -
-

OneClient by Polyfrost

-

Version {version}

-

{isInDev ? 'Development' : 'Release'} Build

-
+
diff --git a/apps/oneclient/frontend/src/utils/debugInfo.ts b/apps/oneclient/frontend/src/utils/debugInfo.ts new file mode 100644 index 00000000..b6073053 --- /dev/null +++ b/apps/oneclient/frontend/src/utils/debugInfo.ts @@ -0,0 +1,48 @@ +import type { DebugInfoParsedLine } from '@/bindings.gen'; +import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut'; +import { bindings } from '@/main'; +import { toast } from '@/utils/toast'; +import { writeText } from '@tauri-apps/plugin-clipboard-manager'; +import { register } from '@tauri-apps/plugin-global-shortcut'; +import { useEffect, useState } from 'react'; + +export type DebugInfoArray = Array; +export function useDebugInfo(): DebugInfoArray { + const [debugInfo, setDebugInfo] = useState([]); + + useEffect(() => { + const fetchDevInfo = async () => { + const info = await bindings.debug.getFullDebugInfoParsed(); + setDebugInfo(info); + }; + + void fetchDevInfo(); + }, []); + + return debugInfo; +} + +export async function copyDebugInfo() { + const info = await bindings.debug.getFullDebugInfoParsedString(); + writeText(info); + toast({ + type: 'info', + title: 'Debug Info', + message: 'Debug info has been copied to clipboard', + }); +} + +export function useDebugKeybind() { + const handleKeybind = (event: ShortcutEvent) => { + if (event.state !== 'Pressed') + return; + copyDebugInfo(); + }; + + useEffect(() => { + void (async () => { + await register('CommandOrControl+Shift+D', handleKeybind); + await register('Alt+F12', handleKeybind); + })(); + }, []); +} diff --git a/apps/oneclient/frontend/src/utils/discordRPC.ts b/apps/oneclient/frontend/src/utils/discordRPC.ts new file mode 100644 index 00000000..007989af --- /dev/null +++ b/apps/oneclient/frontend/src/utils/discordRPC.ts @@ -0,0 +1,68 @@ +import type { ToOptions } from '@tanstack/react-router'; +import { bindings } from '@/main'; +import { useCommand } from '@onelauncher/common'; +import { useLocation } from '@tanstack/react-router'; +import { useEffect } from 'react'; + +type URLPath = Exclude; +export const ResolvedPathNames: Record = { + '.': 'UNKNOWN', + '..': 'UNKNOWN', + '/': 'Viewing Home', + '/app': 'Viewing Homepage', + '/app/account': 'Viewing Account', + '/app/account/skins': 'Viewing Skin Manager', + '/app/cluster': 'Viewing Versions', + '/app/cluster/browser': 'Viewing {clusterName}\'s mods', + '/app/cluster/browser/package': 'Browsing {packageName}', + '/app/cluster/logs': 'Viewing {clusterName}\'s logs', + '/app/cluster/mods': 'Viewing {clusterName}\'s mods', + '/app/cluster/resource-packs': 'Viewing {clusterName}\'s resource packs', + '/app/cluster/shaders': 'Viewing {clusterName}\'s shaders', + '/app/cluster/datapacks': 'Viewing {clusterName}\'s data packs', + '/app/cluster/process': 'Viewing {clusterName}', + '/app/cluster/settings': 'Viewing {clusterName}\'s settings', + '/app/settings': 'Viewing Settings', + '/app/settings/appearance': 'Viewing Settings', + '/app/settings/developer': 'Viewing Settings', + '/app/settings/minecraft': 'Viewing Settings', + '/app/settings/changelog': 'Viewing Settings', + '/app/accounts': 'Viewing Accounts', + '/app/clusters': 'Viewing Versions', + '/onboarding': 'Preparing OneClient', + '/onboarding/account': 'Preparing OneClient', + '/onboarding/finished': 'Preparing OneClient', + '/onboarding/language': 'Preparing OneClient', + '/onboarding/preferences/version': 'Preparing OneClient', + '/onboarding/preferences/versionCategory': 'Preparing OneClient', + '/onboarding/preferences': 'Preparing OneClient', +}; + +// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225 +function ReplaceVariables(template: string, variables: Record) { + return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match); +} + +export function useDiscordRPC() { + const location = useLocation(); + const clusterId = location.search.clusterId ?? 0; + const provider = location.search.provider ?? null; + const packageId = location.search.packageId ?? null; + const { data: cluster } = useCommand(['getClusterById', clusterId], () => bindings.core.getClusterById(clusterId)); + + const { data: managedPackage } = useCommand( + ['getPackage', provider, packageId], + () => { + if (provider == null || packageId == null) + return Promise.reject(new Error('Missing parameters')); + return bindings.core.getPackage(provider, packageId); + }, + { enabled: provider != null && packageId != null }, + ); + + useEffect(() => { + const template = ResolvedPathNames[location.pathname as URLPath]; + if (template) + bindings.core.setDiscordRPCMessage(ReplaceVariables(template, { clusterName: cluster?.name ?? 'UNKNOWN', packageName: managedPackage?.name ?? 'UNKNOWN' })); + }, [location.pathname, location.search.clusterId, cluster?.name, managedPackage?.name]); +} diff --git a/apps/oneclient/frontend/src/utils/updater.ts b/apps/oneclient/frontend/src/utils/updater.ts index 0a0b0f26..0f88aa1f 100644 --- a/apps/oneclient/frontend/src/utils/updater.ts +++ b/apps/oneclient/frontend/src/utils/updater.ts @@ -1,6 +1,7 @@ import type { UnlistenFn } from '@tauri-apps/api/event'; +import { bindings } from '@/main'; import { listen } from '@tauri-apps/api/event'; -import { bindings } from '../main'; +import { useEffect } from 'react'; export interface Update { version: string; @@ -28,3 +29,28 @@ export async function listenForUpdateEvents( callback(event.payload); }); } + +export function useAutoUpdater() { + useEffect(() => { + void (async () => { + try { + const update = await checkForUpdate(); + if (!update) + return; + + // eslint-disable-next-line no-console -- Used for debugging - aka important + console.log('Update found on initial check:', update.version); + + try { + await installUpdate(); + } + catch (e) { + console.error('Failed to install update:', e); + } + } + catch (e) { + console.error('Failed to check for update:', e); + } + })(); + }, []); +} diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index 2b0ef86f..4173e356 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -1,5 +1,40 @@ +use std::time::{SystemTime, UNIX_EPOCH}; use tauri::Runtime; use tauri_plugin_os::{arch, family, locale, platform, type_, version}; +use tokio::fs; + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DebugInfoData { + pub in_dev: bool, + pub platform: String, + pub arch: String, + pub family: String, + pub locale: String, + pub os_type: String, + pub os_version: String, + pub os_distro: String, + pub commit_hash: String, + pub build_timestamp: String, + pub build_version: String, +} + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct DebugInfoParsedLine { + pub title: String, + pub value: String, +} + +impl DebugInfoParsedLine { + pub fn new, U: Into>(title: T, value: U) -> Self { + Self { + title: title.into(), + value: value.into(), + } + } +} #[taurpc::procedures(path = "debug")] pub trait TauriLauncherDebugApi { @@ -16,7 +51,7 @@ pub trait TauriLauncherDebugApi { async fn get_family() -> String; #[taurpc(alias = "getLocale")] - async fn get_locale() -> Option; + async fn get_locale() -> String; #[taurpc(alias = "getType")] async fn get_type() -> String; @@ -27,6 +62,9 @@ pub trait TauriLauncherDebugApi { #[taurpc(alias = "getOsVersion")] async fn get_version() -> String; + #[taurpc(alias = "getOsDistro")] + async fn get_distro() -> String; + #[taurpc(alias = "getGitCommitHash")] async fn get_git_commit_hash() -> String; @@ -35,6 +73,15 @@ pub trait TauriLauncherDebugApi { #[taurpc(alias = "getPackageVersion")] async fn get_package_version() -> String; + + #[taurpc(alias = "getFullDebugInfo")] + async fn get_full_debug_info() -> DebugInfoData; + + #[taurpc(alias = "getFullDebugInfoParsed")] + async fn get_full_debug_info_parsed() -> Vec; + + #[taurpc(alias = "getFullDebugInfoParsedString")] + async fn get_full_debug_info_parsed_string() -> String; } #[taurpc::ipc_type] @@ -58,8 +105,9 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { family().to_string() } - async fn get_locale(self) -> Option { - locale() + /// Returns the user's locale (like "en-AU"), or "UNKNOWN" if it couldn't be found. + async fn get_locale(self) -> String { + locale().unwrap_or_else(|| "UNKNOWN".to_string()) } async fn get_type(self) -> String { @@ -74,6 +122,30 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { version().to_string() } + async fn get_distro(self) -> String { + let platform = self.clone().get_platform().await; + + if platform == "linux" { + if let Ok(contents) = fs::read_to_string("/etc/os-release").await { + for line in contents.lines() { + if let Some(value) = line.strip_prefix("PRETTY_NAME=") { + return value.trim_matches('"').to_string(); + } + } + + for line in contents.lines() { + if let Some(value) = line.strip_prefix("NAME=") { + return value.trim_matches('"').to_string(); + } + } + } + + "UNKNOWN".to_string() + } else { + platform + } + } + async fn get_git_commit_hash(self) -> String { crate::build::COMMIT_HASH.to_string() } @@ -85,4 +157,67 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { async fn get_package_version(self) -> String { crate::build::PKG_VERSION.to_string() } + + async fn get_full_debug_info(self) -> DebugInfoData { + DebugInfoData { + in_dev: self.clone().is_in_dev().await, + platform: self.clone().get_platform().await, + arch: self.clone().get_arch().await, + family: self.clone().get_family().await, + locale: self.clone().get_locale().await, + os_type: self.clone().get_type().await, + os_version: self.clone().get_version().await, + os_distro: self.clone().get_distro().await, + commit_hash: self.clone().get_git_commit_hash().await, + build_timestamp: self.clone().get_build_timestamp().await, + build_version: self.clone().get_package_version().await, + } + } + + async fn get_full_debug_info_parsed(self) -> Vec { + let info = self.clone().get_full_debug_info().await; + + vec![ + DebugInfoParsedLine::new("In Dev", if info.in_dev { "yes" } else { "no" }), + DebugInfoParsedLine::new("Platform", info.platform), + DebugInfoParsedLine::new("Arch", info.arch), + DebugInfoParsedLine::new("Family", info.family), + DebugInfoParsedLine::new("Locale", info.locale), + DebugInfoParsedLine::new("Os Type", info.os_type), + DebugInfoParsedLine::new("Os Version", info.os_version), + DebugInfoParsedLine::new("Os Distro", info.os_distro), + DebugInfoParsedLine::new("Commit Hash", info.commit_hash), + DebugInfoParsedLine::new("Build Timestamp", info.build_timestamp), + DebugInfoParsedLine::new("Version", info.build_version), + ] + } + + async fn get_full_debug_info_parsed_string(self) -> String { + let debug_info = self.clone().get_full_debug_info_parsed().await; + + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + + let mut lines = Vec::new(); + + lines.push("## OneClient Debug Information".to_string()); + lines.push(format!( + "**Data exported at:** (`{timestamp}`)" + )); + + for line_data in debug_info { + if line_data.title == "Build Timestamp" { + lines.push(format!( + "**{}:** (`{}`)", + line_data.title, line_data.value, line_data.value + )); + } else { + lines.push(format!("**{}:** `{}`", line_data.title, line_data.value)); + } + } + + lines.join("\n") + } } diff --git a/packages/core/src/logger.rs b/packages/core/src/logger.rs index b372f3eb..18e83351 100644 --- a/packages/core/src/logger.rs +++ b/packages/core/src/logger.rs @@ -11,15 +11,21 @@ pub async fn start_logger() { use std::fs::OpenOptions; use tracing_subscriber::fmt::time::ChronoLocal; - use crate::store::Dirs; + use crate::store::{Dirs, State}; + + let state = State::get().await.expect("failed to get state"); + let enable_debug_logs = state.settings.read().await.log_debug_info; + + let level = if enable_debug_logs { + tracing::Level::DEBUG + } else { + tracing::Level::INFO + }; let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { - tracing_subscriber::EnvFilter::new( - Core::get() - .logger_filter - .clone() - .unwrap_or_else(|| format!("{}=debug", env!("CARGO_PKG_NAME"))), - ) + tracing_subscriber::EnvFilter::new(format!( + "oneclient_gui={level},onelauncher_core={level}" + )) }); let mut console_fmt_layer = tracing_subscriber::fmt::layer() diff --git a/packages/core/src/store/settings.rs b/packages/core/src/store/settings.rs index e94aefa0..608a2f39 100644 --- a/packages/core/src/store/settings.rs +++ b/packages/core/src/store/settings.rs @@ -21,6 +21,7 @@ pub struct Settings { pub native_window_frame: bool, #[cfg(feature = "tauri")] + pub log_debug_info: bool, pub show_tanstack_dev_tools: bool, } @@ -38,6 +39,7 @@ impl Default for Settings { native_window_frame: false, #[cfg(feature = "tauri")] + log_debug_info: tauri::is_dev(), show_tanstack_dev_tools: tauri::is_dev(), } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 645679cd..c02a651b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ catalogs: '@tauri-apps/plugin-fs': specifier: ^2.2.1 version: 2.4.2 + '@tauri-apps/plugin-global-shortcut': + specifier: ^2.2.1 + version: 2.3.1 '@tauri-apps/plugin-opener': specifier: 2.3.0 version: 2.3.0 @@ -283,6 +286,9 @@ importers: '@tauri-apps/plugin-fs': specifier: 'catalog:' version: 2.4.2 + '@tauri-apps/plugin-global-shortcut': + specifier: 'catalog:' + version: 2.3.1 '@untitled-theme/icons-react': specifier: 'catalog:' version: 0.14.1(@types/react@19.2.7)(react@19.2.3) @@ -1606,35 +1612,30 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-glibc@2.4.1': resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.4.1': resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.4.1': resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.4.1': resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.4.1': resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} @@ -2357,133 +2358,111 @@ packages: resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.41.1': resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.1': resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.41.1': resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.1': resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.41.1': resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.1': resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-musl@4.41.1': resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.1': resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loongarch64-gnu@4.41.1': resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.1': resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.41.1': resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.1': resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.41.1': resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.1': resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.41.1': resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.1': resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.41.1': resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.1': resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.41.1': resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.1': resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-linux-x64-musl@4.41.1': resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.1': resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==} @@ -2685,28 +2664,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.10': resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.10': resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.10': resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.10': resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} @@ -2932,35 +2907,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tauri-apps/cli-linux-riscv64-gnu@2.5.0': resolution: {integrity: sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-gnu@2.5.0': resolution: {integrity: sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.5.0': resolution: {integrity: sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.5.0': resolution: {integrity: sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==} @@ -2994,6 +2964,9 @@ packages: '@tauri-apps/plugin-fs@2.4.2': resolution: {integrity: sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==} + '@tauri-apps/plugin-global-shortcut@2.3.1': + resolution: {integrity: sha512-vr40W2N6G63dmBPaha1TsBQLLURXG538RQbH5vAm0G/ovVZyXJrmZR1HF1W+WneNloQvwn4dm8xzwpEXRW560g==} + '@tauri-apps/plugin-opener@2.3.0': resolution: {integrity: sha512-yAbauwp8BCHIhhA48NN8rEf6OtfZBPCgTOCa10gmtoVCpmic5Bq+1Ba7C+NZOjogedkSiV7hAotjYnnbUVmYrw==} @@ -3156,49 +3129,41 @@ packages: resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.7.2': resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.7.2': resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.7.2': resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.7.2': resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} @@ -4641,28 +4606,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -8957,6 +8918,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.8.0 + '@tauri-apps/plugin-global-shortcut@2.3.1': + dependencies: + '@tauri-apps/api': 2.8.0 + '@tauri-apps/plugin-opener@2.3.0': dependencies: '@tauri-apps/api': 2.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1a9e8c7f..00e6b01c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -47,6 +47,7 @@ catalog: '@tauri-apps/plugin-clipboard-manager': ^2.2.2 '@tauri-apps/plugin-dialog': ^2.2.1 '@tauri-apps/plugin-fs': ^2.2.1 + '@tauri-apps/plugin-global-shortcut': ^2.2.1 '@tauri-apps/plugin-shell': ^2.2.1 '@tauri-apps/plugin-opener': 2.3.0 overlayscrollbars: ^2.11.2