Merge pull request #265 from afonsojramos/gate-flatpak-snap-macos
Reviewed: clean platform detection refactor, no audio code touched, all gates correct. Lockfiles will be regenerated post-merge.
This commit is contained in:
10 files changed
+64
-31
No files matched your search
@@ -31,6 +31,7 @@
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2",
|
||||
"@tauri-apps/plugin-dialog": "^2.5.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-os": "^2",
|
||||
"lucide-svelte": "^1.0.1",
|
||||
"svelte-i18n": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@ qconnect-transport-ws = { path = "../crates/qconnect-transport-ws" }
|
||||
# Tauri
|
||||
tauri = { version = "2", features = ["protocol-asset", "tray-icon", "macos-private-api"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||
tauri-plugin-deep-link = "2"
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"core:webview:allow-webview-close",
|
||||
"core:webview:allow-set-webview-zoom",
|
||||
"opener:default",
|
||||
"os:default",
|
||||
"dialog:default",
|
||||
"clipboard-manager:default",
|
||||
"clipboard-manager:allow-write-text"
|
||||
|
||||
@@ -745,6 +745,7 @@ pub fn run() {
|
||||
}))
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.manage(AppState::with_device_and_settings(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { getName, getVersion } from '@tauri-apps/api/app';
|
||||
import { onMount } from 'svelte';
|
||||
import QobuzLegalNotice from '$lib/components/QobuzLegalNotice.svelte';
|
||||
import { platform } from '$lib/utils/platform';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
@@ -37,9 +38,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Detect platform for Easter eggs
|
||||
const isMac = typeof navigator !== 'undefined' && /Mac/.test(navigator.platform);
|
||||
const platformLabel = isMac ? 'macOS (Tauri 2.0)' : 'Linux (Tauri 2.0)';
|
||||
const platformLabel = platform === 'macos' ? 'macOS (Tauri 2.0)' : platform === 'windows' ? 'Windows (Tauri 2.0)' : 'Linux (Tauri 2.0)';
|
||||
|
||||
function handleOpenUrl(url: string) {
|
||||
openUrl(url).catch(err => console.error('Failed to open URL:', err));
|
||||
@@ -193,7 +192,7 @@
|
||||
<img src="/mexico-flag.svg" alt="México" class="inline-icon flag" />
|
||||
</p>
|
||||
<p class="signature-detail">
|
||||
{$t('about.signatureDetail')} <img src="/Tux.svg" alt="Tux" class="inline-icon tux" class:mac-tux={isMac} />
|
||||
{$t('about.signatureDetail')} <img src="/Tux.svg" alt="Tux" class="inline-icon tux" class:mac-tux={platform === 'macos'} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import RemoteControlSetupGuide from '../RemoteControlSetupGuide.svelte';
|
||||
import LogsModal from '../LogsModal.svelte';
|
||||
import DiagnosticsPanel from '../DiagnosticsPanel.svelte';
|
||||
import { platform } from '$lib/utils/platform';
|
||||
import VolumeSlider from '../VolumeSlider.svelte';
|
||||
import UpdateCheckResultModal from '../updates/UpdateCheckResultModal.svelte';
|
||||
import WhatsNewModal from '../updates/WhatsNewModal.svelte';
|
||||
@@ -1470,9 +1471,11 @@
|
||||
updatesCurrentVersion = getUpdatesCurrentVersion();
|
||||
});
|
||||
|
||||
// Detect sandbox environments
|
||||
loadFlatpakStatus();
|
||||
loadSnapStatus();
|
||||
// Detect sandbox environments (Linux-only)
|
||||
if (platform === 'linux') {
|
||||
loadFlatpakStatus();
|
||||
loadSnapStatus();
|
||||
}
|
||||
|
||||
// Check for legacy cached files
|
||||
checkLegacyCachedFiles();
|
||||
@@ -3882,6 +3885,7 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if platform === 'linux'}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.audio.audioBackend')}</span>
|
||||
@@ -3915,6 +3919,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.audio.outputDevice')}</span>
|
||||
@@ -3983,6 +3988,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if platform === 'linux'}
|
||||
{#if showAlsaPluginSelector}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
@@ -4008,6 +4014,7 @@
|
||||
<Toggle enabled={alsaHardwareVolume} onchange={handleAlsaHardwareVolumeChange} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.audio.exclusiveMode')} <span class="help-tip" title={$t('settings.audio.exclusiveModeHelp')}>(?)</span></span>
|
||||
@@ -4025,6 +4032,7 @@
|
||||
{#if dacPassthrough}
|
||||
<small class="setting-note">{$t('settings.audio.dacPassthroughNote')}</small>
|
||||
{/if}
|
||||
{#if platform === 'linux'}
|
||||
{#if isFlatpak && selectedBackend === 'PipeWire' && dacPassthrough}
|
||||
<div class="flatpak-warning">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
@@ -4047,6 +4055,7 @@
|
||||
<small class="setting-note">{$t('settings.audio.pwForceBitperfectNote')}</small>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.audio.allowQualityFallback')} <span class="help-tip" title={$t('settings.audio.allowQualityFallbackHelp')}>(?)</span></span>
|
||||
@@ -4369,7 +4378,7 @@
|
||||
<Toggle enabled={systemNotificationsEnabled} onchange={(v) => { systemNotificationsEnabled = v; setSystemNotificationsEnabled(v); }} />
|
||||
</div>
|
||||
<!-- Title bar toggles: hidden on macOS (always uses native overlay title bar) -->
|
||||
{#if !document.documentElement.classList.contains('macos')}
|
||||
{#if platform !== 'macos'}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.appearance.useSystemTitleBar')}</span>
|
||||
@@ -4386,7 +4395,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Title bar customization: hidden on macOS (uses native overlay title bar) -->
|
||||
{#if !document.documentElement.classList.contains('macos')}
|
||||
{#if platform !== 'macos'}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.appearance.searchInTitleBar')}</span>
|
||||
@@ -4573,15 +4582,15 @@
|
||||
</div>
|
||||
|
||||
<!-- System Tray / Menu Bar subsection -->
|
||||
<h4 class="subsection-title">{$t(document.documentElement.classList.contains('macos') ? 'settings.appearance.tray.titleMacos' : 'settings.appearance.tray.title')}</h4>
|
||||
<h4 class="subsection-title">{$t(platform === 'macos' ? 'settings.appearance.tray.titleMacos' : 'settings.appearance.tray.title')}</h4>
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t(document.documentElement.classList.contains('macos') ? 'settings.appearance.tray.enableTrayMacos' : 'settings.appearance.tray.enableTray')}</span>
|
||||
<span class="setting-desc">{$t(document.documentElement.classList.contains('macos') ? 'settings.appearance.tray.enableTrayDescMacos' : 'settings.appearance.tray.enableTrayDesc')}</span>
|
||||
<span class="setting-label">{$t(platform === 'macos' ? 'settings.appearance.tray.enableTrayMacos' : 'settings.appearance.tray.enableTray')}</span>
|
||||
<span class="setting-desc">{$t(platform === 'macos' ? 'settings.appearance.tray.enableTrayDescMacos' : 'settings.appearance.tray.enableTrayDesc')}</span>
|
||||
</div>
|
||||
<Toggle enabled={enableTray} onchange={(v) => handleEnableTrayChange(v)} />
|
||||
</div>
|
||||
{#if !document.documentElement.classList.contains('macos')}
|
||||
{#if platform !== 'macos'}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.appearance.tray.minimizeToTray')}</span>
|
||||
@@ -4609,7 +4618,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Composition subsection (collapsible) -->
|
||||
<!-- Composition subsection (collapsible, Linux-only: GDK/GSK/X11/Wayland/DMA-BUF) -->
|
||||
{#if platform === 'linux'}
|
||||
<div class="collapsible-section composition-subsection">
|
||||
<button class="section-title-btn" onclick={() => compositionCollapsed = !compositionCollapsed}>
|
||||
<div class="section-title-row">
|
||||
@@ -4760,6 +4770,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
@@ -4979,8 +4990,8 @@
|
||||
<section class="section">
|
||||
<h3 class="section-title">{$t('settings.integrations.title')}</h3>
|
||||
|
||||
<!-- Qobuz Link Handler (Linux only — macOS registers via Info.plist at build time) -->
|
||||
{#if !document.documentElement.classList.contains('macos')}
|
||||
<!-- Qobuz Link Handler (Linux only — macOS registers via Info.plist, Windows via registry) -->
|
||||
{#if platform === 'linux'}
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.integrations.qobuzLinkHandler')}</span>
|
||||
|
||||
@@ -9,14 +9,12 @@
|
||||
*/
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { platform } from '$lib/utils/platform';
|
||||
|
||||
const STORAGE_KEY_HIDE = 'qbz-hide-titlebar';
|
||||
const STORAGE_KEY_SYSTEM = 'qbz-use-system-titlebar';
|
||||
const STORAGE_KEY_WINDOW_CONTROLS = 'qbz-show-window-controls';
|
||||
|
||||
// Platform detection (cached once — never changes at runtime)
|
||||
const isMacOS = typeof document !== 'undefined' && document.documentElement.classList.contains('macos');
|
||||
|
||||
// State
|
||||
let hideTitleBar = false;
|
||||
let useSystemTitleBar = false;
|
||||
@@ -91,7 +89,7 @@ export function getUseSystemTitleBar(): boolean {
|
||||
*/
|
||||
export function shouldShowTitleBar(): boolean {
|
||||
// macOS always uses native decorations — no custom title bar
|
||||
if (isMacOS) return false;
|
||||
if (platform === 'macos') return false;
|
||||
return !hideTitleBar && !useSystemTitleBar;
|
||||
}
|
||||
|
||||
@@ -100,7 +98,7 @@ export function shouldShowTitleBar(): boolean {
|
||||
* Returns 0 if title bar is hidden or system title bar is active, 40 otherwise
|
||||
*/
|
||||
export function getTitleBarHeight(): number {
|
||||
if (isMacOS) return 0;
|
||||
if (platform === 'macos') return 0;
|
||||
return (hideTitleBar || useSystemTitleBar) ? 0 : 40;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Keyboard utilities for keybindings system
|
||||
*/
|
||||
|
||||
import { platform } from '$lib/utils/platform';
|
||||
|
||||
/** Display mappings for special keys */
|
||||
const KEY_DISPLAY_MAP: Record<string, string> = {
|
||||
ArrowLeft: '←',
|
||||
@@ -78,21 +80,17 @@ export function parseShortcut(shortcut: string): {
|
||||
* @example formatShortcutDisplay('Ctrl+ArrowRight') // '⌘ →' on macOS, 'Ctrl + →' on others
|
||||
*/
|
||||
export function formatShortcutDisplay(shortcut: string): string {
|
||||
const isMac =
|
||||
typeof navigator !== 'undefined' &&
|
||||
navigator.platform.toLowerCase().includes('mac');
|
||||
|
||||
const { ctrl, alt, shift, key } = parseShortcut(shortcut);
|
||||
const parts: string[] = [];
|
||||
|
||||
if (ctrl) parts.push(isMac ? '⌘' : 'Ctrl');
|
||||
if (alt) parts.push(isMac ? '⌥' : 'Alt');
|
||||
if (shift) parts.push(isMac ? '⇧' : 'Shift');
|
||||
if (ctrl) parts.push(platform === 'macos' ? '⌘' : 'Ctrl');
|
||||
if (alt) parts.push(platform === 'macos' ? '⌥' : 'Alt');
|
||||
if (shift) parts.push(platform === 'macos' ? '⇧' : 'Shift');
|
||||
|
||||
const displayKey = KEY_DISPLAY_MAP[key] || key.toUpperCase();
|
||||
parts.push(displayKey);
|
||||
|
||||
return parts.join(isMac ? ' ' : ' + ');
|
||||
return parts.join(platform === 'macos' ? ' ' : ' + ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Platform detection via @tauri-apps/plugin-os.
|
||||
*
|
||||
* `platform()` is synchronous (compile-time constant baked into the binary).
|
||||
* Falls back to 'linux' in non-Tauri environments (vitest, SSR, svelte-check).
|
||||
*
|
||||
* NOTE: The `macos` CSS class on `<html>` is set separately by an inline
|
||||
* script in app.html for FOUC prevention — that is intentionally kept.
|
||||
*/
|
||||
|
||||
import { platform as tauriPlatform } from '@tauri-apps/plugin-os';
|
||||
|
||||
type Platform = 'macos' | 'linux' | 'windows';
|
||||
|
||||
let detectedPlatform: Platform;
|
||||
try {
|
||||
detectedPlatform = tauriPlatform() as Platform;
|
||||
} catch {
|
||||
// Fallback for non-Tauri environments (vitest, SSR, svelte-check)
|
||||
detectedPlatform = 'linux';
|
||||
}
|
||||
|
||||
export const platform: Platform = detectedPlatform;
|
||||
@@ -154,6 +154,7 @@
|
||||
import { loadAlbumFavorites } from '$lib/stores/albumFavoritesStore';
|
||||
import { loadArtistFavorites } from '$lib/stores/artistFavoritesStore';
|
||||
import { getDefaultFavoritesTab } from '$lib/utils/favorites';
|
||||
import { platform } from '$lib/utils/platform';
|
||||
import type { FavoritesPreferences, ResolvedMusician } from '$lib/types';
|
||||
|
||||
// Navigation state management
|
||||
@@ -479,7 +480,6 @@
|
||||
// Title Bar State (from titleBarStore subscription)
|
||||
let showTitleBar = $state(shouldShowTitleBar());
|
||||
let showWindowControls = $state(getShowWindowControls());
|
||||
const isMacOS = typeof document !== 'undefined' && document.documentElement.classList.contains('macos');
|
||||
|
||||
// Search Bar Location State
|
||||
let searchBarLocationPref = $state(getSearchBarLocation());
|
||||
@@ -4883,7 +4883,7 @@
|
||||
{:else}
|
||||
<div class="app" class:no-titlebar={!showTitleBar} class:floating={isWindowFloating}>
|
||||
<!-- macOS: drag region for window movement (overlay title bar has no native drag area) -->
|
||||
{#if !showTitleBar && isMacOS}
|
||||
{#if !showTitleBar && platform === 'macos'}
|
||||
<div class="macos-drag-region" data-tauri-drag-region></div>
|
||||
{/if}
|
||||
<!-- Custom Title Bar (CSD) -->
|
||||
|
||||
Reference in new issue
Block a user