Merge pull request #167 from rubiidev18alt/main

Better accuracy to steam big picture mode
This commit is contained in:
gardenGnostic 2026-03-12 00:10:16 +01:00 committed by GitHub
commit 6c59ad20d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 204 additions and 21 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -76,6 +76,7 @@ The launcher supports several compatibility options for Linux:
- **discord-rpc**: Discord Rich Presence integration - **discord-rpc**: Discord Rich Presence integration
- **extract-zip**: ZIP archive extraction - **extract-zip**: ZIP archive extraction
- **Tailwind CSS**: UI styling (via CDN) - **Tailwind CSS**: UI styling (via CDN)
- **UI Sounds**: Using the free version of [JDSherbert's Ultimate UI SFX Pack on itch.io](https://jdsherbert.itch.io/ultimate-ui-sfx-pack)
## Development ## Development

View file

@ -38,6 +38,12 @@ function createWindow() {
} }
}); });
ipcMain.on('window-close', () => win.close()); ipcMain.on('window-close', () => win.close());
ipcMain.on('window-fullscreen', () => {
win.setFullScreen(!win.isFullScreen());
});
ipcMain.on('window-set-fullscreen', (event, enabled) => {
win.setFullScreen(Boolean(enabled));
});
ipcMain.handle('store-get', (event, key) => store.get(key)); ipcMain.handle('store-get', (event, key) => store.get(key));
ipcMain.handle('store-set', (event, key, value) => store.set(key, value)); ipcMain.handle('store-set', (event, key, value) => store.set(key, value));

View file

@ -300,6 +300,73 @@ const GamepadManager = {
} }
}; };
const UiSoundManager = {
files: {
cursor: 'JDSherbert - Ultimate UI SFX Pack - Cursor - 1.mp3',
select: 'JDSherbert - Ultimate UI SFX Pack - Select - 1.mp3',
cancel: 'JDSherbert - Ultimate UI SFX Pack - Cancel - 1.mp3',
popupOpen: 'JDSherbert - Ultimate UI SFX Pack - Popup Open - 1.mp3',
popupClose: 'JDSherbert - Ultimate UI SFX Pack - Popup Close - 1.mp3',
error: 'JDSherbert - Ultimate UI SFX Pack - Error - 1.mp3'
},
cache: {},
lastPlayedAt: {},
cooldownMs: 70,
lastHoverItem: null,
init() {
Object.entries(this.files).forEach(([key, file]) => {
this.cache[key] = new Audio(file);
this.cache[key].preload = 'auto';
this.cache[key].volume = key === 'cursor' ? 0.45 : 0.6;
});
document.addEventListener('focusin', (e) => {
if (e.target?.classList?.contains('nav-item')) this.play('cursor');
});
document.addEventListener('pointerover', (e) => {
const navItem = e.target?.closest?.('.nav-item');
if (!navItem || navItem === this.lastHoverItem) return;
this.lastHoverItem = navItem;
this.play('cursor');
});
document.addEventListener('pointerleave', () => {
this.lastHoverItem = null;
});
document.addEventListener('click', (e) => {
const navItem = e.target?.closest?.('.nav-item');
if (!navItem) return;
const label = (navItem.textContent || '').trim().toLowerCase();
if (label.includes('cancel') || label.includes('close') || label.includes('back') || label.includes('later')) {
this.play('cancel');
return;
}
this.play('select');
});
},
play(name) {
const now = Date.now();
if (this.lastPlayedAt[name] && now - this.lastPlayedAt[name] < this.cooldownMs) return;
this.lastPlayedAt[name] = now;
const audio = this.cache[name];
if (!audio) return;
audio.currentTime = 0;
audio.play().catch(() => {});
},
playToast(message) {
const normalized = String(message || '').toLowerCase();
if (normalized.includes('error') || normalized.includes('failed') || normalized.includes('missing') || normalized.includes('required')) {
this.play('error');
}
}
};
const MusicManager = { const MusicManager = {
audio: new Audio(), audio: new Audio(),
playlist: [], playlist: [],
@ -442,6 +509,31 @@ async function migrateLegacyConfig() {
currentInstance = instances.find(i => i.id === currentInstanceId) || instances[0]; currentInstance = instances.find(i => i.id === currentInstanceId) || instances[0];
} }
function isSteamDeckEnvironment() {
if (process.platform !== 'linux') return false;
const env = process.env || {};
if (env.STEAMDECK === '1' || env.SteamDeck === '1') return true;
try {
const osRelease = fs.readFileSync('/etc/os-release', 'utf8').toLowerCase();
if (osRelease.includes('steamos') || osRelease.includes('steam deck')) return true;
} catch (_) {}
return false;
}
function focusPrimaryPlayButton() {
const classicPlayBtn = document.getElementById('classic-btn-play');
const mainPlayBtn = document.getElementById('btn-play-main');
const target = (classicPlayBtn && classicPlayBtn.offsetParent !== null) ? classicPlayBtn : mainPlayBtn;
if (!target) return;
target.focus();
target.classList.add('controller-active');
setTimeout(() => target.classList.remove('controller-active'), 180);
}
window.onload = async () => { window.onload = async () => {
try { try {
await migrateLegacyConfig(); await migrateLegacyConfig();
@ -485,11 +577,21 @@ window.onload = async () => {
loadSplashText(); loadSplashText();
MusicManager.init(); MusicManager.init();
GamepadManager.init(); GamepadManager.init();
UiSoundManager.init();
if (isSteamDeckEnvironment()) {
ipcRenderer.send('window-set-fullscreen', true);
setTimeout(() => focusPrimaryPlayButton(), 150);
}
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
if (e.key === 'F9') { if (e.key === 'F9') {
checkForLauncherUpdates(true); checkForLauncherUpdates(true);
} }
if (e.key === 'F11') {
e.preventDefault();
ipcRenderer.send('window-fullscreen');
}
}); });
window.addEventListener('online', () => { window.addEventListener('online', () => {
@ -528,8 +630,10 @@ async function toggleInstances(show) {
document.activeElement?.blur(); document.activeElement?.blur();
modal.style.display = 'flex'; modal.style.display = 'flex';
modal.style.opacity = '1'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
} else { } else {
modal.style.opacity = '0'; modal.style.opacity = '0';
UiSoundManager.play('popupClose');
setTimeout(() => modal.style.display = 'none', 300); setTimeout(() => modal.style.display = 'none', 300);
} }
} }
@ -574,8 +678,10 @@ function toggleAddInstance(show) {
document.getElementById('new-instance-repo').value = DEFAULT_REPO; document.getElementById('new-instance-repo').value = DEFAULT_REPO;
modal.style.display = 'flex'; modal.style.display = 'flex';
modal.style.opacity = '1'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
} else { } else {
modal.style.opacity = '0'; modal.style.opacity = '0';
UiSoundManager.play('popupClose');
setTimeout(() => modal.style.display = 'none', 300); setTimeout(() => modal.style.display = 'none', 300);
} }
} }
@ -950,8 +1056,10 @@ async function promptUpdate(newTag) {
document.activeElement?.blur(); document.activeElement?.blur();
modal.style.display = 'flex'; modal.style.display = 'flex';
modal.style.opacity = '1'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
const cleanup = (result) => { const cleanup = (result) => {
modal.style.opacity = '0'; modal.style.opacity = '0';
UiSoundManager.play('popupClose');
setTimeout(() => { setTimeout(() => {
modal.style.display = 'none'; modal.style.display = 'none';
if (modalText) modalText.style.display = 'none'; if (modalText) modalText.style.display = 'none';
@ -1143,22 +1251,23 @@ function toggleOptions(show) {
const cb = document.getElementById('classic-theme-checkbox'); const cb = document.getElementById('classic-theme-checkbox');
if (cb) cb.checked = document.body.classList.contains('classic-theme'); if (cb) cb.checked = document.body.classList.contains('classic-theme');
document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
} }
else { modal.style.opacity = '0'; setTimeout(() => modal.style.display = 'none', 300); } else { modal.style.opacity = '0'; UiSoundManager.play('popupClose'); setTimeout(() => modal.style.display = 'none', 300); }
} }
async function toggleProfile(show) { async function toggleProfile(show) {
if (isProcessing) return; if (isProcessing) return;
const modal = document.getElementById('profile-modal'); const modal = document.getElementById('profile-modal');
if (show) { await updatePlaytimeDisplay(); document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; } if (show) { await updatePlaytimeDisplay(); document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; UiSoundManager.play('popupOpen'); }
else { modal.style.opacity = '0'; setTimeout(() => modal.style.display = 'none', 300); } else { modal.style.opacity = '0'; UiSoundManager.play('popupClose'); setTimeout(() => modal.style.display = 'none', 300); }
} }
async function toggleServers(show) { async function toggleServers(show) {
if (isProcessing) return; if (isProcessing) return;
const modal = document.getElementById('servers-modal'); const modal = document.getElementById('servers-modal');
if (show) { await loadServers(); document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; } if (show) { await loadServers(); document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; UiSoundManager.play('popupOpen'); }
else { modal.style.opacity = '0'; setTimeout(() => modal.style.display = 'none', 300); } else { modal.style.opacity = '0'; UiSoundManager.play('popupClose'); setTimeout(() => modal.style.display = 'none', 300); }
} }
async function getServersFilePath() { return path.join(currentInstance.installPath, 'servers.txt'); } async function getServersFilePath() { return path.join(currentInstance.installPath, 'servers.txt'); }
@ -1272,6 +1381,7 @@ async function saveProfile() {
function showToast(msg) { function showToast(msg) {
const t = document.getElementById('toast'); if (!t) return; const t = document.getElementById('toast'); if (!t) return;
UiSoundManager.playToast(msg);
t.textContent = msg; t.style.display = 'block'; t.style.animation = 'none'; t.offsetHeight; t.style.animation = 'slideUp 0.3s ease-out'; t.textContent = msg; t.style.display = 'block'; t.style.animation = 'none'; t.offsetHeight; t.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => { t.style.display = 'none'; }, 3000); setTimeout(() => { t.style.display = 'none'; }, 3000);
} }
@ -1282,20 +1392,81 @@ function scanCompatibilityLayers() {
const select = document.getElementById('compat-select'); if (!select) return; const select = document.getElementById('compat-select'); if (!select) return;
const savedValue = currentInstance.compatLayer; const savedValue = currentInstance.compatLayer;
const layers = [{ name: 'Default (Direct)', cmd: 'direct' }, { name: 'Wine64', cmd: 'wine64' }, { name: 'Wine', cmd: 'wine' }]; const layers = [{ name: 'Default (Direct)', cmd: 'direct' }, { name: 'Wine64', cmd: 'wine64' }, { name: 'Wine', cmd: 'wine' }];
// Add custom option const seen = new Set(layers.map(l => l.cmd));
layers.push({ name: 'Custom (Linux)', cmd: 'custom' }); const foundProtonLayers = [];
const addLayer = (name, cmd) => {
const homeDir = require('os').homedir(); let steamPaths = []; if (!name || !cmd || seen.has(cmd)) return;
if (process.platform === 'linux') steamPaths = [path.join(homeDir, '.steam', 'steam', 'steamapps', 'common'), path.join(homeDir, '.local', 'share', 'Steam', 'steamapps', 'common'), path.join(homeDir, '.var', 'app', 'com.valvesoftware.Steam', 'data', 'Steam', 'steamapps', 'common')]; seen.add(cmd);
else if (process.platform === 'darwin') steamPaths = [path.join(homeDir, 'Library', 'Application Support', 'Steam', 'steamapps', 'common')]; foundProtonLayers.push({ name, cmd });
for (const steamPath of steamPaths) { };
if (fs.existsSync(steamPath)) { try { const dirs = fs.readdirSync(steamPath); dirs.filter(d => d.startsWith('Proton') || d.includes('Wine') || d.includes('CrossOver')).forEach(d => { const protonPath = path.join(steamPath, d, 'proton'); if (fs.existsSync(protonPath)) layers.push({ name: d, cmd: protonPath }); }); } catch (e) {} }
const homeDir = require('os').homedir();
const protonCandidates = [];
if (process.platform === 'linux') {
const steamRoots = [
path.join(homeDir, '.steam', 'steam'),
path.join(homeDir, '.local', 'share', 'Steam'),
path.join(homeDir, '.var', 'app', 'com.valvesoftware.Steam', 'data', 'Steam')
];
steamRoots.forEach((root) => {
protonCandidates.push(path.join(root, 'steamapps', 'common'));
protonCandidates.push(path.join(root, 'compatibilitytools.d'));
});
} else if (process.platform === 'darwin') {
protonCandidates.push(path.join(homeDir, 'Library', 'Application Support', 'Steam', 'steamapps', 'common'));
protonCandidates.push(path.join(homeDir, 'Library', 'Application Support', 'Steam', 'compatibilitytools.d'));
} }
const toolNameMatches = (dir) => {
const n = dir.toLowerCase();
return n.startsWith('proton') || n.startsWith('ge-proton') || n.includes('proton-ge') || n.includes('wine') || n.includes('crossover') || n.includes('umu-proton');
};
for (const basePath of protonCandidates) {
if (!fs.existsSync(basePath)) continue;
try {
const dirs = fs.readdirSync(basePath);
dirs.forEach((dirName) => {
if (!toolNameMatches(dirName)) return;
const protonPath = path.join(basePath, dirName, 'proton');
if (fs.existsSync(protonPath)) addLayer(dirName, protonPath);
});
} catch (e) {
console.error('Compatibility scan error:', e.message);
}
}
foundProtonLayers.sort((a, b) => {
const aGe = /(^|\b)(ge-proton|proton-ge|umu-proton)/i.test(a.name) ? 1 : 0;
const bGe = /(^|\b)(ge-proton|proton-ge|umu-proton)/i.test(b.name) ? 1 : 0;
if (aGe !== bGe) return bGe - aGe;
return b.name.localeCompare(a.name, undefined, { numeric: true, sensitivity: 'base' });
});
layers.push(...foundProtonLayers);
// Add custom option at end so discovered runtimes are easier to browse first.
layers.push({ name: 'Custom (Linux)', cmd: 'custom' });
select.innerHTML = ''; select.innerHTML = '';
layers.forEach(l => { const opt = document.createElement('option'); opt.value = l.cmd; opt.textContent = l.name; select.appendChild(opt); if (l.cmd === savedValue) opt.selected = true; }); layers.forEach(l => {
const opt = document.createElement('option');
opt.value = l.cmd;
opt.textContent = l.name;
select.appendChild(opt);
if (l.cmd === savedValue) opt.selected = true;
});
// If no saved compat layer or still on direct, prefer the newest detected GE/Proton on Linux.
if (process.platform === 'linux' && (savedValue === 'direct' || !savedValue) && foundProtonLayers.length > 0) {
select.value = foundProtonLayers[0].cmd;
}
updateCompatDisplay(); updateCompatDisplay();
const customPathInput = document.getElementById('custom-proton-path'); const customPathInput = document.getElementById('custom-proton-path');
if (customPathInput) customPathInput.value = currentInstance.customCompatPath || ""; if (customPathInput) customPathInput.value = currentInstance.customCompatPath || "";
} }
@ -1363,8 +1534,9 @@ async function promptLauncherUpdate(version, changelog) {
modalText.style.display = 'block'; modalText.style.display = 'block';
} }
document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
const cleanup = (result) => { const cleanup = (result) => {
modal.style.opacity = '0'; setTimeout(() => { modal.style.display = 'none'; if (modalText) modalText.style.display = 'none'; }, 300); modal.style.opacity = '0'; UiSoundManager.play('popupClose'); setTimeout(() => { modal.style.display = 'none'; if (modalText) modalText.style.display = 'none'; }, 300);
confirmBtn.onclick = null; skipBtn.onclick = null; closeBtn.onclick = null; resolve(result); confirmBtn.onclick = null; skipBtn.onclick = null; closeBtn.onclick = null; resolve(result);
}; };
confirmBtn.onclick = () => cleanup(true); skipBtn.onclick = () => cleanup(false); closeBtn.onclick = () => cleanup(false); confirmBtn.onclick = () => cleanup(true); skipBtn.onclick = () => cleanup(false); closeBtn.onclick = () => cleanup(false);
@ -1475,8 +1647,10 @@ async function toggleSnapshots(show, id = null) {
document.activeElement?.blur(); document.activeElement?.blur();
modal.style.display = 'flex'; modal.style.display = 'flex';
modal.style.opacity = '1'; modal.style.opacity = '1';
UiSoundManager.play('popupOpen');
} else { } else {
modal.style.opacity = '0'; modal.style.opacity = '0';
UiSoundManager.play('popupClose');
setTimeout(() => modal.style.display = 'none', 300); setTimeout(() => modal.style.display = 'none', 300);
} }
} }

View file

@ -348,7 +348,7 @@ body {
box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa; box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa;
margin-bottom: 16px; margin-bottom: 16px;
position: relative; position: relative;
transition: transform(0.05s); transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
} }
.nav-item { .nav-item {
@ -358,9 +358,10 @@ body {
} }
.nav-item:focus { .nav-item:focus {
transform: scale(1.05); transform: scale(1.04);
z-index: 1000; z-index: 1000;
box-shadow: 0 0 0 3px #fff, 0 0 20px rgba(255, 255, 255, 0.4) !important; box-shadow: 0 0 0 1px rgba(255,255,255,0.92), 0 0 10px rgba(255, 255, 255, 0.62), 0 0 20px rgba(90, 170, 255, 0.28) !important;
filter: brightness(1.08);
} }
.nav-item.active-bump { .nav-item.active-bump {
@ -371,7 +372,8 @@ body {
.btn-mc:hover:not(.disabled) { .btn-mc:hover:not(.disabled) {
background-color: var(--mc-button-hover); background-color: var(--mc-button-hover);
color: #fff; color: #fff;
outline: 2px solid #fff !important; outline: 1px solid rgba(255,255,255,0.95) !important;
box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa, 0 0 12px rgba(255,255,255,0.25);
} }
.btn-mc.controller-active { .btn-mc.controller-active {