From 1a563e64d5e8f9bfa0fe649b9f8ca323823d5fe8 Mon Sep 17 00:00:00 2001 From: HorizonCode Date: Sat, 12 Jul 2025 22:56:15 +0200 Subject: [PATCH] feat: add linux support with osu-winello --- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands.rs | 31 ++++++++++--------- src-tauri/src/lib.rs | 6 ++-- src-tauri/src/utils.rs | 65 +++++++++++++++++++++++++++++++++++++-- src/lib/global.ts | 2 ++ src/lib/osuUtil.ts | 4 +++ src/pages/Launch.svelte | 25 +++++++++++++-- src/routes/+layout.svelte | 4 ++- 9 files changed, 117 insertions(+), 24 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 123f945..6d4b259 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1094,7 +1094,7 @@ dependencies = [ [[package]] name = "ezpplauncher" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" dependencies = [ "discord-rich-presence", "hardware-id", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4e4499c..2b29aba 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ezpplauncher" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" description = "EZPPLauncher redefined." authors = ["HorizonCode"] edition = "2024" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 3ae752c..90f6c72 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -14,7 +14,7 @@ use tokio::time::{Duration, sleep}; use crate::presence; use crate::utils::{ check_folder_completeness, get_osu_config, get_osu_user_config, get_window_title_by_pid, - set_osu_config_vals, set_osu_user_config_vals, + set_osu_config_vals, set_osu_user_config_vals, is_wmctrl_available, is_osuwinello_available }; #[tauri::command] @@ -282,8 +282,6 @@ pub fn set_osu_config_values( #[tauri::command] pub async fn run_osu_updater(folder: String) -> Result<(), String> { - let osu_exe_path = PathBuf::from(&folder).join("osu!.exe"); - #[cfg(windows)] const DETACHED_PROCESS: u32 = 0x00000008; #[cfg(windows)] @@ -292,6 +290,7 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> { let mut updater_process = { #[cfg(windows)] { + let osu_exe_path = PathBuf::from(&folder).join("osu!.exe"); Command::new(&osu_exe_path) .arg("-repair") .creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP) @@ -301,7 +300,7 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> { #[cfg(not(windows))] { - Command::new(&osu_exe_path) + Command::new("osu-wine") .arg("-repair") .spawn() .map_err(|e| format!("Failed to spawn updater: {}", e))? @@ -372,8 +371,6 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> { /* #[cfg(windows)] use std::os::windows::process::CommandExt; */ - let osu_exe_path = PathBuf::from(&folder).join("osu!.exe"); - #[cfg(windows)] const DETACHED_PROCESS: u32 = 0x00000008; #[cfg(windows)] @@ -382,6 +379,7 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> { let mut game_process = { #[cfg(windows)] { + let osu_exe_path = PathBuf::from(&folder).join("osu!.exe"); Command::new(&osu_exe_path) .arg("-devserver") .arg("ez-pp.farm") @@ -392,8 +390,8 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> { #[cfg(not(windows))] { - Command::new(&osu_exe_path) - .arg("-devserver") + Command::new("osu-wine") + .arg("--devserver") .arg("ez-pp.farm") .spawn() .map_err(|e| format!("Failed to spawn updater: {}", e))? @@ -416,13 +414,6 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> { .spawn() .map_err(|e| format!("Failed to run patcher: {e}"))?; } - - #[cfg(not(windows))] - { - let _ = Command::new(&patcher_exe_path) - .spawn() - .map_err(|e| format!("Failed to run patcher: {e}"))?; - } } } @@ -713,3 +704,13 @@ pub async fn presence_update_user(user: PresenceUser) { pub async fn presence_is_connected() -> bool { presence::has_presence().await } + +#[tauri::command] +pub fn has_wmctrl() -> bool { + is_wmctrl_available() +} + +#[tauri::command] +pub fn has_osuwinello() -> bool { + is_osuwinello_available() +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index eeb56b4..28a733a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,7 +10,7 @@ use crate::commands::{ get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_skins_count, is_osu_running, open_url_in_browser, presence_connect, presence_disconnect, presence_is_connected, presence_update_status, presence_update_user, replace_ui_files, run_osu, - run_osu_updater, set_osu_config_values, set_osu_user_config_values, valid_osu_folder, + run_osu_updater, set_osu_config_values, set_osu_user_config_values, valid_osu_folder, has_osuwinello, has_wmctrl }; #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -60,7 +60,9 @@ pub fn run() { presence_disconnect, presence_update_status, presence_update_user, - presence_is_connected + presence_is_connected, + has_osuwinello, + has_wmctrl ]) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_dialog::init()) diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 8053eed..b16566c 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,5 +1,6 @@ use std::fs; use std::path::Path; +use std::process::Command; use sysinfo::Pid; pub fn check_folder_completeness>(folder_path: P, required_files: &[&str]) -> f32 { @@ -177,8 +178,68 @@ pub fn get_osu_config>( } #[cfg(not(windows))] -pub fn get_window_title_by_pid(_pid: Pid) -> String { - "".to_string() +pub fn is_osuwinello_available() -> bool { + Command::new("osu-wine") + .arg("--info") // A lightweight operation like getting the version is ideal. + .stdout(std::process::Stdio::null()) // Suppress stdout + .stderr(std::process::Stdio::null()) // Suppress stderr + .status() // Execute the command and get its status + .is_ok() // is_ok() will be true if the command was found and ran +} + +#[cfg(windows)] +pub fn is_osuwinello_available() -> bool { + false +} + +#[cfg(not(windows))] +pub fn is_wmctrl_available() -> bool { + Command::new("wmctrl") + .arg("-V") // A lightweight operation like getting the version is ideal. + .stdout(std::process::Stdio::null()) // Suppress stdout + .stderr(std::process::Stdio::null()) // Suppress stderr + .status() // Execute the command and get its status + .is_ok() // is_ok() will be true if the command was found and ran +} + +#[cfg(windows)] +pub fn is_wmctrl_available() -> bool { + false +} + +#[cfg(not(windows))] +pub fn get_window_title_by_pid(target_pid: Pid) -> String { + let find_title = || -> Option { + // 1. Execute `wmctrl -lp` + let output = Command::new("wmctrl").arg("-lp").output().ok()?; + + if !output.status.success() { + return None; // wmctrl command failed (e.g., not on X11) + } + + let output_str = String::from_utf8(output.stdout).ok()?; + + // 2. Parse the output line by line + for line in output_str.lines() { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 4 { + continue; + } + + // The PID is typically the 3rd column (index 2) + if let Ok(pid) = parts[2].parse::() { + if pid == target_pid.as_u32() { + // 3. Extract the title and return it as Some(title) + let title = parts[4..].join(" "); + return Some(title); + } + } + } + + None + }; + + find_title().unwrap_or_default() } #[cfg(windows)] diff --git a/src/lib/global.ts b/src/lib/global.ts index 382b02e..09f5875 100644 --- a/src/lib/global.ts +++ b/src/lib/global.ts @@ -6,6 +6,8 @@ import type { Release } from './types'; export const currentView = writable(Loading); +export const platform = writable(""); + export const launcherVersion = writable(''); export const newVersion = writable(undefined); diff --git a/src/lib/osuUtil.ts b/src/lib/osuUtil.ts index c9a85f3..26224cc 100644 --- a/src/lib/osuUtil.ts +++ b/src/lib/osuUtil.ts @@ -116,3 +116,7 @@ export const exit = async () => await invoke('exit'); export const getPlatform = async () => await invoke('get_platform'); export const isOsuCorrupted = async (folder: string) => await invoke('check_for_corruption', { folder }); +export const hasWMCTRL = async () => + await invoke('has_wmctrl'); +export const hasOsuWinello = async () => + await invoke('has_osuwinello'); \ No newline at end of file diff --git a/src/pages/Launch.svelte b/src/pages/Launch.svelte index af4f2dd..1f251b6 100644 --- a/src/pages/Launch.svelte +++ b/src/pages/Launch.svelte @@ -14,6 +14,7 @@ newVersion, osuBuild, osuStream, + platform, presenceLoading, serverConnectionFails, serverPing, @@ -87,6 +88,8 @@ getSkin, getSkinsCount, getVersion, + hasOsuWinello, + hasWMCTRL, isOsuCorrupted, isOsuRunning, isValidOsuFolder, @@ -184,6 +187,23 @@ return; } + if($platform === "linux"){ + if(!(await hasWMCTRL())){ + toast.error('Hmmm...', { + description: 'wmctrl seems to be missing, please install via AUR.', + }); + launching.set(false); + return; + } + if(!(await hasOsuWinello())){ + toast.error('Hmmm...', { + description: 'osu-winello seems to be missing, please install it.', + }); + launching.set(false); + return; + } + } + try { launchInfo = 'Looking for EZPPLauncher File updates...'; const updateResult = await getEZPPLauncherUpdateFiles(osuPath); @@ -1028,11 +1048,12 @@ >
-
Shows misses in Relax and Autopilot
+
Shows misses in Relax and Autopilot {#if $platform !== "windows"}currently only on windows! {/if}
{ patch.set(e); $userSettings.save(); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index acffac9..31e3bd1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -9,6 +9,7 @@ discordPresence, firstStartup, launcherVersion, + platform, presenceLoading, setupValues, } from '@/global'; @@ -90,7 +91,8 @@ disableReload(); setupValues(); launcherVersion.set(await getLauncherVersion()); - if ((await getPlatform()) !== 'windows') unsupported_platform = true; + platform.set(await getPlatform()); + if ($platform !== "windows" && $platform !== "linux") unsupported_platform = true; const isFirstStartup = await $userSettings.init(); $userAuth.init();