chore: add custom cursor, add config system

This commit is contained in:
2025-06-29 22:50:31 +02:00
parent 60e3102257
commit 807ad60d62
13 changed files with 315 additions and 37 deletions

View File

@@ -4,6 +4,9 @@
import { animate } from 'animejs';
import { onMount } from 'svelte';
let { smoothCursor = true }: { smoothCursor?: boolean } =
$props();
let mouseX = $state(0);
let mouseY = $state(0);
let rotation = $state(0);
@@ -75,7 +78,7 @@
}
animate(cursor, {
duration: 180,
duration: smoothCursor ? 180 : 0,
translateX: mouseX,
translateY: mouseY - 50,
ease: (t: number) => (t - 1) ** 3 + 1,
@@ -128,19 +131,12 @@
let cursorInner: HTMLDivElement;
let cursorAdditive: HTMLImageElement;
/* onMount(() => {
const processMouseMove = (e: MouseEvent) =>
handleMouseMove(e.clientX, e.clientY, e.pageX, e.pageY);
document.addEventListener('pointermove', processMouseMove);
document.addEventListener('pointerdown', handleMouseDown);
document.addEventListener('pointerup', handleMouseUp);
onMount(() => {
document.documentElement.classList.add('hiddenCursor');
return () => {
document.removeEventListener('pointermove', processMouseMove);
document.removeEventListener('pointerdown', handleMouseDown);
document.removeEventListener('pointerup', handleMouseUp);
document.documentElement.classList.remove('hiddenCursor');
};
}); */
});
</script>
<svelte:window
@@ -148,8 +144,10 @@
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
/>
<div class="h-7 w-7 fixed pointer-events-none z-[99999]" bind:this={cursor}>
<div
class="h-7 w-7 fixed pointer-events-none z-[99999]"
bind:this={cursor}
>
<div class="relative">
<img class="absolute top-0 left-0" src={cursor_default} bind:this={cursorInner} alt="cursor" />
<img
@@ -162,15 +160,15 @@
</div>
<style>
:global(html),
:global(body),
:global(*),
:global(*:hover),
:global(button),
:global(a),
:global(input),
:global(select),
:global(textarea) {
:global(html.hiddenCursor),
:global(html.hiddenCursor body),
:global(html.hiddenCursor *),
:global(html.hiddenCursor *:hover),
:global(html.hiddenCursor button),
:global(html.hiddenCursor a),
:global(html.hiddenCursor input),
:global(html.hiddenCursor select),
:global(html.hiddenCursor textarea) {
cursor: none !important;
}
</style>

77
src/lib/config.ts Normal file
View File

@@ -0,0 +1,77 @@
import {
BaseDirectory,
exists,
mkdir,
readFile,
readTextFile,
writeFile,
} from '@tauri-apps/plugin-fs';
import * as path from '@tauri-apps/api/path';
import { invoke } from '@tauri-apps/api/core';
import { Crypto } from './crypto';
import { enc } from 'crypto-js';
export class Config {
private config: Record<string, unknown> = {};
private crypto: Crypto | undefined;
private configFilePath: string | undefined;
async init() {
const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge';
this.crypto = new Crypto(hwid);
const homeDir = await path.homeDir();
const folderPath = await path.join(homeDir, '.ezpplauncher');
this.configFilePath = await path.join(folderPath, 'user_settings');
const createFolder = !(await exists(folderPath));
if (createFolder) await mkdir(folderPath);
const createConfig = !(await exists(this.configFilePath));
if (createConfig) await this.save();
else await this.load();
}
private async load() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (!this.crypto) throw Error('crypto not initialized');
const fileStream = await readTextFile(this.configFilePath);
try {
const decryptedJSON = JSON.parse(this.crypto.decrypt(fileStream)) as Record<string, unknown>;
this.config = decryptedJSON;
console.log('config file loaded');
console.log(JSON.stringify(this.config));
} catch (err) {
console.log('failed to read file');
this.config = {};
await this.save();
}
}
async save() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (!this.crypto) throw Error('crypto not initialized');
const encryptedJSON = this.crypto.encrypt(JSON.stringify(this.config));
console.log(this.config);
console.log('saving file...');
console.log(encryptedJSON);
await writeFile(this.configFilePath, Buffer.from(encryptedJSON), {
append: false,
});
}
value(key: string) {
console.log(this.config);
return {
set: <T>(val: T) => {
this.config[key] = val;
},
get: <T>(fallback: T): T => {
return (this.config[key] as T) ?? fallback;
},
};
}
}

37
src/lib/crypto.ts Normal file
View File

@@ -0,0 +1,37 @@
import cryptojs from 'crypto-js';
export class Crypto {
private key: cryptojs.lib.WordArray;
private ivLength: number;
constructor(key: string, opts?: { ivLength?: number }) {
this.key = cryptojs.SHA256(key);
this.ivLength = opts?.ivLength ?? 16;
}
encrypt(str: string): string {
const iv = cryptojs.lib.WordArray.random(this.ivLength);
const encrypted = cryptojs.AES.encrypt(str, this.key, { iv });
const ivBase64 = iv.toString(cryptojs.enc.Base64);
const ctBase64 = encrypted.ciphertext.toString(cryptojs.enc.Base64);
return `${ivBase64}:${ctBase64}`;
}
decrypt(data: string): string {
const [ivBase64, ctBase64] = data.split(':');
if (!ivBase64 || !ctBase64) throw new Error('Invalid input format');
const iv = cryptojs.enc.Base64.parse(ivBase64);
const ciphertext = cryptojs.enc.Base64.parse(ctBase64);
const cipherParams = cryptojs.lib.CipherParams.create({
ciphertext,
});
const decrypted = cryptojs.AES.decrypt(cipherParams, this.key, { iv });
return decrypted.toString(cryptojs.enc.Utf8);
}
}

7
src/lib/userSettings.ts Normal file
View File

@@ -0,0 +1,7 @@
import { writable } from 'svelte/store';
import { Config } from './config';
export const userSettings = writable<Config>(new Config());
export const customCursor = writable<boolean>(true);
export const cursorSmoothening = writable<boolean>(true);

View File

@@ -6,7 +6,17 @@
import * as Select from '@/components/ui/select';
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff, Settings2 } from 'lucide-svelte';
import {
LoaderCircle,
Logs,
Music2,
Play,
Users,
Wifi,
Gamepad2,
WifiOff,
Settings2,
} from 'lucide-svelte';
import { Circle } from 'radix-icons-svelte';
import NumberFlow from '@number-flow/svelte';
import * as AlertDialog from '@/components/ui/alert-dialog';
@@ -15,6 +25,7 @@
import { fade, fly, scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox';
import Label from '@/components/ui/label/label.svelte';
import { cursorSmoothening, customCursor, userSettings } from '@/userSettings';
let selectedTab = $state('settings');
let launching = $state(false);
@@ -273,24 +284,48 @@
class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg"
in:scale={{ duration: 400, start: 0.98 }}
>
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3"><Settings2 /> EZPPLauncher Settings</div>
<div class="grid grid-cols-[1fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6">
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3">
<Settings2 /> EZPPLauncher Settings
</div>
<div
class="grid grid-cols-[1fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6"
>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
<div class="text-muted-foreground text-xs">
Enable a custom cursor in the Launcher like in the lazer build of osu!
</div>
</div>
<Checkbox id="setting-custom-cursor" class="flex items-center justify-center w-5 h-5"
<Checkbox
id="setting-custom-cursor"
checked={$customCursor}
onCheckedChange={async (e) => {
if (!e) {
cursorSmoothening.set(false);
}
customCursor.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Cursor Smoothening</Label>
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
<div class="text-muted-foreground text-xs">
Makes the custom cursor movement smoother.
</div>
</div>
<Checkbox id="setting-custom-cursor" class="flex items-center justify-center w-5 h-5"
<Checkbox
id="setting-cursor-smoothening"
checked={$cursorSmoothening}
onCheckedChange={async (e) => {
if (!$customCursor) return;
cursorSmoothening.set(e);
$userSettings.save();
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
</div>
</div>

View File

@@ -4,12 +4,31 @@
import { setupValues } from '@/global';
import { onMount } from 'svelte';
import OsuCursor from '@/components/ui/osu-cursor/OsuCursor.svelte';
import { cursorSmoothening, customCursor, userSettings } from '@/userSettings';
import { Buffer } from 'buffer';
let { children } = $props();
onMount(setupValues);
onMount(async () => {
window.Buffer = Buffer;
setupValues();
await $userSettings.init();
const config_custom_cursor = $userSettings.value('custom_cursor');
const config_cursor_smoothening = $userSettings.value('cursor_smoothening');
console.log("yes", config_cursor_smoothening.get(true))
customCursor.set(config_custom_cursor.get(true));
cursorSmoothening.set(config_cursor_smoothening.get(true));
customCursor.subscribe((val) => config_custom_cursor.set(val));
cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val));
});
</script>
<OsuCursor />
{#if $customCursor}
<OsuCursor smoothCursor={$cursorSmoothening} />
{/if}
<Titlebar />
<main>

View File

@@ -1,6 +1,6 @@
// Tauri doesn't have a Node.js server to do proper SSR
// so we will use adapter-static to prerender the app (SSG)
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
export const prerender = true;
export const ssr = false;
export const ssr = false;