This commit is contained in:
HorizonCode 2025-07-14 19:39:41 +02:00
commit 307e0c9747
11 changed files with 132 additions and 26 deletions

View File

@ -7,6 +7,7 @@
"fill-labs.dependi", "fill-labs.dependi",
"mylesmurphy.prettify-ts", "mylesmurphy.prettify-ts",
"edwinhuish.better-comments-next", "edwinhuish.better-comments-next",
"dbaeumer.vscode-eslint" "dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss"
] ]
} }

2
src-tauri/Cargo.lock generated
View File

@ -1094,7 +1094,7 @@ dependencies = [
[[package]] [[package]]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.0-beta.3" version = "3.0.0-beta.4"
dependencies = [ dependencies = [
"discord-rich-presence", "discord-rich-presence",
"hardware-id", "hardware-id",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.0-beta.3" version = "3.0.0-beta.4"
description = "EZPPLauncher redefined." description = "EZPPLauncher redefined."
authors = ["HorizonCode"] authors = ["HorizonCode"]
edition = "2024" edition = "2024"

View File

@ -14,7 +14,7 @@ use tokio::time::{Duration, sleep};
use crate::presence; use crate::presence;
use crate::utils::{ use crate::utils::{
check_folder_completeness, get_osu_config, get_osu_user_config, get_window_title_by_pid, 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] #[tauri::command]
@ -282,8 +282,6 @@ pub fn set_osu_config_values(
#[tauri::command] #[tauri::command]
pub async fn run_osu_updater(folder: String) -> Result<(), String> { pub async fn run_osu_updater(folder: String) -> Result<(), String> {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
#[cfg(windows)] #[cfg(windows)]
const DETACHED_PROCESS: u32 = 0x00000008; const DETACHED_PROCESS: u32 = 0x00000008;
#[cfg(windows)] #[cfg(windows)]
@ -292,6 +290,7 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> {
let mut updater_process = { let mut updater_process = {
#[cfg(windows)] #[cfg(windows)]
{ {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
Command::new(&osu_exe_path) Command::new(&osu_exe_path)
.arg("-repair") .arg("-repair")
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP) .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))] #[cfg(not(windows))]
{ {
Command::new(&osu_exe_path) Command::new("osu-wine")
.arg("-repair") .arg("-repair")
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn updater: {}", e))? .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)] /* #[cfg(windows)]
use std::os::windows::process::CommandExt; */ use std::os::windows::process::CommandExt; */
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
#[cfg(windows)] #[cfg(windows)]
const DETACHED_PROCESS: u32 = 0x00000008; const DETACHED_PROCESS: u32 = 0x00000008;
#[cfg(windows)] #[cfg(windows)]
@ -382,6 +379,7 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
let mut game_process = { let mut game_process = {
#[cfg(windows)] #[cfg(windows)]
{ {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
Command::new(&osu_exe_path) Command::new(&osu_exe_path)
.arg("-devserver") .arg("-devserver")
.arg("ez-pp.farm") .arg("ez-pp.farm")
@ -392,8 +390,8 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
Command::new(&osu_exe_path) Command::new("osu-wine")
.arg("-devserver") .arg("--devserver")
.arg("ez-pp.farm") .arg("ez-pp.farm")
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn updater: {}", e))? .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() .spawn()
.map_err(|e| format!("Failed to run patcher: {e}"))?; .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}"))?;
}
} }
} }
@ -715,3 +706,13 @@ pub async fn presence_update_user(user: PresenceUser) {
pub async fn presence_is_connected() -> bool { pub async fn presence_is_connected() -> bool {
presence::has_presence().await presence::has_presence().await
} }
#[tauri::command]
pub fn has_wmctrl() -> bool {
is_wmctrl_available()
}
#[tauri::command]
pub fn has_osuwinello() -> bool {
is_osuwinello_available()
}

View File

@ -10,11 +10,17 @@ use crate::commands::{
get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_skins_count, 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, 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, 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)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
#[cfg(target_os = "linux")]
unsafe {
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
let mut builder = tauri::Builder::default(); let mut builder = tauri::Builder::default();
#[cfg(desktop)] #[cfg(desktop)]
{ {
@ -54,7 +60,9 @@ pub fn run() {
presence_disconnect, presence_disconnect,
presence_update_status, presence_update_status,
presence_update_user, presence_update_user,
presence_is_connected presence_is_connected,
has_osuwinello,
has_wmctrl
]) ])
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())

View File

@ -1,5 +1,6 @@
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::process::Command;
use sysinfo::Pid; use sysinfo::Pid;
pub fn check_folder_completeness<P: AsRef<Path>>(folder_path: P, required_files: &[&str]) -> f32 { pub fn check_folder_completeness<P: AsRef<Path>>(folder_path: P, required_files: &[&str]) -> f32 {
@ -177,8 +178,68 @@ pub fn get_osu_config<P: AsRef<Path>>(
} }
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn get_window_title_by_pid(_pid: Pid) -> String { pub fn is_osuwinello_available() -> bool {
"".to_string() 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<String> {
// 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::<u32>() {
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)] #[cfg(windows)]

View File

@ -150,6 +150,12 @@
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;

View File

@ -6,6 +6,8 @@ import type { Release } from './types';
export const currentView = writable<Component>(Loading); export const currentView = writable<Component>(Loading);
export const platform = writable<string>("");
export const launcherVersion = writable<string>(''); export const launcherVersion = writable<string>('');
export const newVersion = writable<Release | undefined>(undefined); export const newVersion = writable<Release | undefined>(undefined);

View File

@ -133,3 +133,7 @@ export const exit = async () => await invoke('exit');
export const getPlatform = async () => await invoke<string>('get_platform'); export const getPlatform = async () => await invoke<string>('get_platform');
export const isOsuCorrupted = async (folder: string) => export const isOsuCorrupted = async (folder: string) =>
await invoke<boolean>('check_for_corruption', { folder }); await invoke<boolean>('check_for_corruption', { folder });
export const hasWMCTRL = async () =>
await invoke<boolean>('has_wmctrl');
export const hasOsuWinello = async () =>
await invoke<boolean>('has_osuwinello');

View File

@ -16,6 +16,7 @@
newVersion, newVersion,
osuBuild, osuBuild,
osuStream, osuStream,
platform,
presenceLoading, presenceLoading,
serverConnectionFails, serverConnectionFails,
serverPing, serverPing,
@ -89,6 +90,8 @@
getSkin, getSkin,
getSkinsCount, getSkinsCount,
getVersion, getVersion,
hasOsuWinello,
hasWMCTRL,
isOsuCorrupted, isOsuCorrupted,
isOsuRunning, isOsuRunning,
isValidOsuFolder, isValidOsuFolder,
@ -186,6 +189,23 @@
return; 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 { try {
launchInfo = 'Looking for EZPPLauncher File updates...'; launchInfo = 'Looking for EZPPLauncher File updates...';
const updateResult = await getEZPPLauncherUpdateFiles(osuPath, $launcherStream); const updateResult = await getEZPPLauncherUpdateFiles(osuPath, $launcherStream);
@ -804,7 +824,7 @@
</div> </div>
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6"> <div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
<div <div
class="flex flex-row flex-nowrap h-11 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]" class="flex flex-row flex-nowrap h-11 gap-1 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]"
> >
<button <button
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab === class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
@ -1030,11 +1050,12 @@
> >
<div class="flex flex-col"> <div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Patching</Label> <Label class="text-sm" for="setting-custom-cursor">Patching</Label>
<div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot</div> <div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot {#if $platform !== "windows"}<span class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg">currently only on windows!</span> {/if}</div>
</div> </div>
<Checkbox <Checkbox
id="setting-custom-cursor" id="setting-custom-cursor"
checked={$patch} checked={$platform === "windows" ? $patch : false}
disabled={$platform !== "windows"}
onCheckedChange={async (e) => { onCheckedChange={async (e) => {
patch.set(e); patch.set(e);
$userSettings.save(); $userSettings.save();

View File

@ -9,6 +9,7 @@
discordPresence, discordPresence,
firstStartup, firstStartup,
launcherVersion, launcherVersion,
platform,
presenceLoading, presenceLoading,
setupValues, setupValues,
} from '@/global'; } from '@/global';
@ -90,7 +91,8 @@
disableReload(); disableReload();
setupValues(); setupValues();
launcherVersion.set(await getLauncherVersion()); 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(); const isFirstStartup = await $userSettings.init();
$userAuth.init(); $userAuth.init();