From 2e66cdcbfa3233ce5ee35352192908b0813ff5e8 Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Sat, 18 Apr 2026 23:02:20 -0400 Subject: [PATCH] fix wayland bugs --- src/main/index.ts | 156 ++++++++--- src/preload/index.ts | 8 + src/renderer/App.tsx | 18 +- .../components/NowPlayingConfigWindow.tsx | 22 +- src/renderer/components/ScopePopoutBridge.tsx | 11 +- src/renderer/components/Strip.tsx | 9 +- src/renderer/components/Toolbar.tsx | 67 +++-- src/renderer/env.d.ts | 2 + src/renderer/popouts/ScopePopoutWindow.tsx | 38 ++- src/renderer/stores/settingsStore.ts | 117 ++++++-- src/renderer/styles/globals.css | 47 +++- src/renderer/windowCapabilities.ts | 14 + src/shared/windowCapabilities.ts | 92 +++++++ src/types/windowCapabilities.ts | 8 + test/renderer-helpers.test.ts | 256 +++++++++++++++++- 15 files changed, 750 insertions(+), 115 deletions(-) create mode 100644 src/renderer/windowCapabilities.ts create mode 100644 src/shared/windowCapabilities.ts create mode 100644 src/types/windowCapabilities.ts diff --git a/src/main/index.ts b/src/main/index.ts index c94300a..117a923 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -20,6 +20,7 @@ import { RESIZE_DIRECTIONS, type ResizeDirection } from '../types/windowResize' import type { DialogOptions, DialogResult } from '../types/dialog' import { normalizeProfile } from '../shared/profileState' import { resolveNativeThemeSource } from '../shared/themeState' +import { resolveWindowCapabilities } from '../shared/windowCapabilities' import { clampDraggedMainWindowBounds, raiseWindowAboveNormalPopouts, @@ -89,6 +90,11 @@ const NOW_PLAYING_CONFIG_DEFAULTS = { const MAIN_WINDOW_SYNC_SUPPRESSION_MS = 180 const MAIN_WINDOW_VISIBLE_GRAB_MARGIN = 64 +const runtimeWindowCapabilities = resolveWindowCapabilities({ + platform: process.platform, + argv: process.argv, + env: process.env, +}) function getProfileLibrary(): FileBackedProfileLibrary { if (!profileLibrary) { @@ -234,7 +240,7 @@ async function syncNativeThemeAppearance(): Promise { } function scheduleMainWindowBoundsSave(window: BrowserWindow): void { - if (!isMainRendererWindow(window) || !mainRendererReady) return + if (!isMainRendererWindow(window) || !mainRendererReady || !supportsGeometryPersistence()) return if (mainWindowBoundsTimer) { clearTimeout(mainWindowBoundsTimer) @@ -309,6 +315,14 @@ function getDisplayWorkAreas(): WindowBounds[] { })) } +function supportsProgrammaticReposition(): boolean { + return runtimeWindowCapabilities.supportsProgrammaticReposition +} + +function supportsGeometryPersistence(): boolean { + return runtimeWindowCapabilities.supportsGeometryPersistence +} + function suppressMainWindowSync(durationMs = MAIN_WINDOW_SYNC_SUPPRESSION_MS): void { suppressMainWindowSyncUntil = Math.max(suppressMainWindowSyncUntil, Date.now() + durationMs) } @@ -349,9 +363,16 @@ function syncMainWindowLogicalBounds(window: BrowserWindow, bounds = window.getB return } + const x = supportsGeometryPersistence() + ? bounds.x + : mainWindowLogicalBounds?.x ?? 0 + const y = supportsGeometryPersistence() + ? bounds.y + : mainWindowLogicalBounds?.y ?? 0 + mainWindowLogicalBounds = normalizeMainWindowBounds({ - x: bounds.x, - y: bounds.y, + x, + y, width: bounds.width, height: Math.max(WINDOW_DEFAULTS.minHeight, bounds.height - getSettingsHeight(window)), }) @@ -361,7 +382,12 @@ function applyMainWindowLogicalBounds(window: BrowserWindow, bounds: WindowBound const logicalBounds = normalizeMainWindowBounds(bounds) mainWindowLogicalBounds = logicalBounds suppressMainWindowSync() - window.setBounds(resolveExpandedMainWindowBounds(logicalBounds, getSettingsHeight(window), getDisplayWorkAreas())) + const expandedBounds = resolveExpandedMainWindowBounds(logicalBounds, getSettingsHeight(window), getDisplayWorkAreas()) + if (!supportsGeometryPersistence()) { + window.setSize(expandedBounds.width, expandedBounds.height) + return + } + window.setBounds(expandedBounds) } function applyLogicalBounds(window: BrowserWindow, bounds: WindowBounds): void { @@ -370,13 +396,24 @@ function applyLogicalBounds(window: BrowserWindow, bounds: WindowBounds): void { return } + const nextHeight = bounds.height + getSettingsHeight(window) + if (!supportsGeometryPersistence()) { + window.setSize(bounds.width, nextHeight) + return + } + window.setBounds({ ...bounds, - height: bounds.height + getSettingsHeight(window), + height: nextHeight, }) } function setWindowHeight(window: BrowserWindow, bounds: WindowBounds, height: number, y = bounds.y): void { + if (!supportsGeometryPersistence()) { + window.setSize(bounds.width, height) + return + } + window.setBounds({ x: bounds.x, y, @@ -452,6 +489,10 @@ function applySettingsHeight(window: BrowserWindow, rawNextHeight: number): void } function raiseMainWindowAboveNormalPopouts(): void { + if (!supportsProgrammaticReposition()) { + return + } + raiseWindowAboveNormalPopouts(mainWindow, scopePopoutWindows.values()) } @@ -792,7 +833,13 @@ function createMainWindow(): void { } function emitPopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void { - if (!mainWindow || mainWindow.isDestroyed() || window.isDestroyed() || !mainRendererReady) return + if ( + !mainWindow + || mainWindow.isDestroyed() + || window.isDestroyed() + || !mainRendererReady + || !supportsGeometryPersistence() + ) return const existingTimer = popoutBoundsTimers.get(kind) if (existingTimer) { @@ -844,19 +891,27 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro return existing } - const mainBounds = mainWindow.getBounds() - const fallbackBounds: WindowBounds = { - x: mainBounds.x + 40, - y: mainBounds.y + 40, - width: POPOUT_DEFAULTS.width, - height: POPOUT_DEFAULTS.height, - } - const bounds = normalizeBounds(rawBounds, fallbackBounds) + const shouldRestoreGeometry = supportsGeometryPersistence() + const mainBounds = shouldRestoreGeometry ? mainWindow.getBounds() : null + const fallbackBounds: WindowBounds = mainBounds + ? { + x: mainBounds.x + 40, + y: mainBounds.y + 40, + width: POPOUT_DEFAULTS.width, + height: POPOUT_DEFAULTS.height, + } + : { + x: Math.round(screen.getPrimaryDisplay().workArea.x + 48), + y: Math.round(screen.getPrimaryDisplay().workArea.y + 48), + width: POPOUT_DEFAULTS.width, + height: POPOUT_DEFAULTS.height, + } + const bounds = shouldRestoreGeometry + ? normalizeBounds(rawBounds, fallbackBounds) + : fallbackBounds suppressNextPopoutBoundsEvents.add(kind) const options: BrowserWindowConstructorOptions = { - x: bounds.x, - y: bounds.y, width: bounds.width, height: bounds.height, minWidth: POPOUT_DEFAULTS.minWidth, @@ -880,6 +935,11 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro }, } + if (shouldRestoreGeometry) { + options.x = bounds.x + options.y = bounds.y + } + const popoutWindow = new BrowserWindow(options) setSettingsHeightForWindow(popoutWindow, 0) scopePopoutWindows.set(kind, popoutWindow) @@ -934,17 +994,19 @@ function syncScopePopouts(nextState: ScopePopoutSyncStateMap): void { const popoutWindow = createScopePopoutWindow(kind, desired.bounds) if (!popoutWindow || popoutWindow.isDestroyed()) continue - const currentBounds = popoutWindow.getBounds() - const nextBounds = normalizeBounds(desired.bounds, currentBounds) - const hasBoundsDelta = - currentBounds.x !== nextBounds.x - || currentBounds.y !== nextBounds.y - || currentBounds.width !== nextBounds.width - || currentBounds.height !== nextBounds.height + if (supportsGeometryPersistence() && desired.bounds) { + const currentBounds = popoutWindow.getBounds() + const nextBounds = normalizeBounds(desired.bounds, currentBounds) + const hasBoundsDelta = + currentBounds.x !== nextBounds.x + || currentBounds.y !== nextBounds.y + || currentBounds.width !== nextBounds.width + || currentBounds.height !== nextBounds.height - if (hasBoundsDelta) { - suppressNextPopoutBoundsEvents.add(kind) - applyLogicalBounds(popoutWindow, nextBounds) + if (hasBoundsDelta) { + suppressNextPopoutBoundsEvents.add(kind) + applyLogicalBounds(popoutWindow, nextBounds) + } } } } @@ -973,6 +1035,10 @@ function normalizeNowPlayingConfigBounds(raw: unknown, fallback: WindowBounds): } function scheduleNowPlayingConfigBoundsSave(window: BrowserWindow): void { + if (!supportsGeometryPersistence()) { + return + } + if (nowPlayingConfigBoundsTimer) { clearTimeout(nowPlayingConfigBoundsTimer) } @@ -991,7 +1057,8 @@ function createNowPlayingConfigWindow(): BrowserWindow { return nowPlayingConfigWindow } - const anchorBounds = mainWindow?.getBounds() + const shouldRestoreGeometry = supportsGeometryPersistence() + const anchorBounds = shouldRestoreGeometry ? mainWindow?.getBounds() ?? null : null const fallbackBounds: WindowBounds = anchorBounds ? { x: anchorBounds.x + 40, @@ -1005,14 +1072,14 @@ function createNowPlayingConfigWindow(): BrowserWindow { width: NOW_PLAYING_CONFIG_DEFAULTS.width, height: NOW_PLAYING_CONFIG_DEFAULTS.height, } - const bounds = normalizeNowPlayingConfigBounds( - getWindowStateStore().getNowPlayingConfigWindowBounds(), - fallbackBounds, - ) + const bounds = shouldRestoreGeometry + ? normalizeNowPlayingConfigBounds( + getWindowStateStore().getNowPlayingConfigWindowBounds(), + fallbackBounds, + ) + : fallbackBounds - nowPlayingConfigWindow = new BrowserWindow({ - x: bounds.x, - y: bounds.y, + const options: BrowserWindowConstructorOptions = { width: bounds.width, height: bounds.height, minWidth: NOW_PLAYING_CONFIG_DEFAULTS.minWidth, @@ -1032,7 +1099,14 @@ function createNowPlayingConfigWindow(): BrowserWindow { nodeIntegration: false, backgroundThrottling: false, }, - }) + } + + if (shouldRestoreGeometry) { + options.x = bounds.x + options.y = bounds.y + } + + nowPlayingConfigWindow = new BrowserWindow(options) const configWindow = nowPlayingConfigWindow @@ -1091,6 +1165,10 @@ function setupIPC(): void { }) ipcMain.on('window:start-move', (event) => { + if (!supportsProgrammaticReposition()) { + return + } + const targetWindow = getWindowFromSender(event.sender) if (!targetWindow) return @@ -1415,6 +1493,10 @@ function setupIPC(): void { }) ipcMain.handle('window:get-bounds', (event) => { + if (!supportsGeometryPersistence()) { + return null + } + const targetWindow = getWindowFromSender(event.sender) if (!targetWindow) return null @@ -1422,6 +1504,10 @@ function setupIPC(): void { }) ipcMain.on('window:reposition', (event, position: 'top' | 'bottom') => { + if (!supportsProgrammaticReposition()) { + return + } + const targetWindow = getWindowFromSender(event.sender) if (!targetWindow) return diff --git a/src/preload/index.ts b/src/preload/index.ts index 6358cc1..ba8f337 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -28,15 +28,23 @@ import type { ThemeLibrarySnapshot, } from '../types/theme' import type { DialogOptions, DialogResult } from '../types/dialog' +import type { WindowCapabilities } from '../types/windowCapabilities' import type { ResizeDirection } from '../types/windowResize' import type { VisualizerDSP } from '../renderer/audio/native/visualizer-dsp' +import { resolveWindowCapabilities } from '../shared/windowCapabilities' import { getCaptureBackendSupport } from './captureSupport' type NativeAddonModule = VisualizerDSP & NativeCaptureAPI +const windowCapabilities: WindowCapabilities = resolveWindowCapabilities({ + platform: process.platform, + argv: process.argv, + env: process.env, +}) // Expose Electron API to renderer contextBridge.exposeInMainWorld('electronAPI', { platform: process.platform, + windowCapabilities, minimize: () => ipcRenderer.send('window:minimize'), close: () => ipcRenderer.send('window:close'), startWindowMove: () => ipcRenderer.send('window:start-move'), diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index c05c59d..683ab0c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -12,6 +12,7 @@ import { useAudioStore } from './stores/audioStore' import { useNowPlayingStore } from './stores/nowPlayingStore' import { useThemeStore } from './stores/themeStore' import { useUiStore } from './stores/uiStore' +import { getRendererWindowCapabilities } from './windowCapabilities' export default function App(): JSX.Element { const [toolbarVisible, setToolbarVisible] = useState(false) @@ -36,6 +37,7 @@ export default function App(): JSX.Element { const toggleSettings = useUiStore((s) => s.toggleSettings) const setSettingsOpen = useUiStore((s) => s.setSettingsOpen) const showBanner = useUiStore((s) => s.showBanner) + const useNativeDragRegions = getRendererWindowCapabilities().useNativeDragRegions const isNowPlayingVisible = !hiddenScopes.has('nowPlaying') && (scopeOrder.includes('nowPlaying') || scopePopouts.nowPlaying?.poppedOut === true) @@ -180,15 +182,23 @@ export default function App(): JSX.Element { }, [setSettingsOpen]) const handleAltDragStart = useCallback((event: React.MouseEvent) => { + if (useNativeDragRegions) { + return + } + if (event.altKey && event.button === 0) { event.preventDefault() window.electronAPI.startWindowMove() } - }, []) + }, [useNativeDragRegions]) const handleAltDragEnd = useCallback(() => { + if (useNativeDragRegions) { + return + } + window.electronAPI.stopWindowMove() - }, []) + }, [useNativeDragRegions]) useEffect(() => { return () => { @@ -204,8 +214,8 @@ export default function App(): JSX.Element { className="prism-app" onMouseEnter={showToolbar} onMouseLeave={scheduleHide} - onMouseDown={handleAltDragStart} - onMouseUp={handleAltDragEnd} + onMouseDown={useNativeDragRegions ? undefined : handleAltDragStart} + onMouseUp={useNativeDragRegions ? undefined : handleAltDragEnd} >
('astra') const [draggedProviderId, setDraggedProviderId] = useState(null) const [dropTargetProviderId, setDropTargetProviderId] = useState(null) + const useNativeDragRegions = getRendererWindowCapabilities().useNativeDragRegions useEffect(() => { let disposed = false @@ -260,18 +262,22 @@ export default function NowPlayingConfigWindow(): JSX.Element { }, [nowPlayingState.configs.astra.baseUrl, nowPlayingState.configs.astra.hasToken]) const handleToolbarDragStart = useCallback((event: ReactPointerEvent): void => { - if (isToolbarInteractiveTarget(event.target) || event.button !== 0) return + if (useNativeDragRegions || isToolbarInteractiveTarget(event.target) || event.button !== 0) return event.preventDefault() event.currentTarget.setPointerCapture(event.pointerId) window.electronAPI.startWindowMove() - }, []) + }, [useNativeDragRegions]) const handleToolbarDragEnd = useCallback((event: ReactPointerEvent): void => { + if (useNativeDragRegions) { + return + } + if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId) } window.electronAPI.stopWindowMove() - }, []) + }, [useNativeDragRegions]) const handleSaveAstraConfig = useCallback(async (): Promise => { try { @@ -352,11 +358,11 @@ export default function NowPlayingConfigWindow(): JSX.Element {
Now Playing
diff --git a/src/renderer/components/ScopePopoutBridge.tsx b/src/renderer/components/ScopePopoutBridge.tsx index a7db3aa..a1fdb8e 100644 --- a/src/renderer/components/ScopePopoutBridge.tsx +++ b/src/renderer/components/ScopePopoutBridge.tsx @@ -3,6 +3,7 @@ import { audioRouter } from '../audio/AudioRouter' import { usePerformanceStore } from '../stores/performanceStore' import { useSettingsStore } from '../stores/settingsStore' import { useThemeStore } from '../stores/themeStore' +import { getRendererWindowCapabilities } from '../windowCapabilities' import { FrameScheduler } from '../visualizers/frameScheduler' import type { ScopePopoutAudioBatch, @@ -81,6 +82,7 @@ export default function ScopePopoutBridge(): null { const flushScheduler = useMemo(() => new FrameScheduler({ frameTarget }), []) const activePopoutKindsRef = useRef(activePopoutKinds) const sessionStateRef = useRef(audioRouter.getSessionState()) + const supportsGeometryPersistence = getRendererWindowCapabilities().supportsGeometryPersistence useEffect(() => { flushScheduler.setFrameTarget(frameTarget) @@ -94,13 +96,15 @@ export default function ScopePopoutBridge(): null { const syncState = SCOPE_KINDS.reduce((acc, kind) => { acc[kind] = { shouldBeOpen: scopePopouts[kind]?.poppedOut === true && !hiddenScopes.has(kind), - bounds: scopePopouts[kind]?.windowBounds, + bounds: supportsGeometryPersistence + ? scopePopouts[kind]?.windowBounds + : undefined, } return acc }, {} as ScopePopoutSyncStateMap) window.electronAPI.syncScopePopouts(syncState) - }, [hiddenScopes, scopePopouts]) + }, [hiddenScopes, scopePopouts, supportsGeometryPersistence]) useEffect(() => { for (const kind of activePopoutKinds) { @@ -128,6 +132,7 @@ export default function ScopePopoutBridge(): null { popInScope(kind) }) const unsubscribeBoundsChanged = window.electronAPI.onScopePopoutBoundsChanged((kind, bounds) => { + if (!supportsGeometryPersistence) return updatePopoutBounds(kind, bounds) }) const unsubscribeSettingsUpdate = window.electronAPI.onScopePopoutSettingsUpdate((kind, partial) => { @@ -157,7 +162,7 @@ export default function ScopePopoutBridge(): null { unsubscribeSettingsUpdate() unsubscribeReady() } - }, [popInScope, updatePopoutBounds, updateScopeSettings]) + }, [popInScope, supportsGeometryPersistence, updatePopoutBounds, updateScopeSettings]) useEffect(() => { for (const kind of AUDIO_SCOPE_KINDS) { diff --git a/src/renderer/components/Strip.tsx b/src/renderer/components/Strip.tsx index 8e06f9d..0c00b45 100644 --- a/src/renderer/components/Strip.tsx +++ b/src/renderer/components/Strip.tsx @@ -7,6 +7,7 @@ import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout' import { audioRouter } from '../audio/AudioRouter' import { usePerformanceStore } from '../stores/performanceStore' import { FrameScheduler } from '../visualizers/frameScheduler' +import { getRendererWindowCapabilities } from '../windowCapabilities' export default function Strip(): JSX.Element { const scopeOrder = useSettingsStore((s) => s.scopeOrder) @@ -23,6 +24,7 @@ export default function Strip(): JSX.Element { const gridRef = useRef(null) const scopeRefs = useRef>>({}) const [handleOffsets, setHandleOffsets] = useState([]) + const supportsGeometryPersistence = getRendererWindowCapabilities().supportsGeometryPersistence const dockedScopes = useMemo( () => scopeOrder.filter((k) => !hiddenScopes.has(k) && !scopePopouts[k]?.poppedOut), [hiddenScopes, scopeOrder, scopePopouts], @@ -189,6 +191,11 @@ export default function Strip(): JSX.Element { }, [dockedScopes, setScopeWidthWeight]) const handlePopoutScope = useCallback(async (kind: ScopeKind): Promise => { + if (!supportsGeometryPersistence) { + popOutScope(kind) + return + } + const element = scopeRefs.current[kind] const rect = element?.getBoundingClientRect() const windowBounds = await window.electronAPI.getWindowBounds() @@ -204,7 +211,7 @@ export default function Strip(): JSX.Element { } popOutScope(kind, nextBounds) - }, [popOutScope]) + }, [popOutScope, supportsGeometryPersistence]) return (
diff --git a/src/renderer/components/Toolbar.tsx b/src/renderer/components/Toolbar.tsx index 3da7d2a..6358f3a 100644 --- a/src/renderer/components/Toolbar.tsx +++ b/src/renderer/components/Toolbar.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef, type JSX, type PointerEvent as ReactPointerEvent } from 'react' import { useSettingsStore } from '../stores/settingsStore' import { useUiStore } from '../stores/uiStore' +import { getRendererWindowCapabilities } from '../windowCapabilities' function SettingsIcon(): JSX.Element { return ( @@ -78,6 +79,7 @@ interface ToolbarProps { } const DEFAULT_PROFILE_ID = 'profile_default' +const WAYLAND_REPOSITION_UNAVAILABLE_MESSAGE = 'Top/bottom repositioning is unavailable on native Wayland.' function isToolbarInteractiveTarget(target: EventTarget | null): boolean { return target instanceof Element @@ -107,6 +109,7 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps): const [showReposition, setShowReposition] = useState(false) const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false) const profileButtonRef = useRef(null) + const { useNativeDragRegions, supportsProgrammaticReposition } = getRendererWindowCapabilities() const showProfileErrorBanner = useCallback((error: unknown, fallback: string, includeOpenFolder = false) => { const actions = includeOpenFolder @@ -139,6 +142,12 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps): return unsubscribe }, []) + useEffect(() => { + if (!supportsProgrammaticReposition && showReposition) { + setShowReposition(false) + } + }, [showReposition, supportsProgrammaticReposition]) + const handleSaveNew = useCallback(async () => { setIsProfileMenuOpen(false) const result = await window.electronAPI.showDialog({ @@ -306,9 +315,13 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps): }, []) const handleReposition = useCallback((position: 'top' | 'bottom') => { + if (!supportsProgrammaticReposition) { + return + } + window.electronAPI.repositionWindow(position) setShowReposition(false) - }, []) + }, [supportsProgrammaticReposition]) const handleOpenProfileMenu = useCallback(() => { const buttonRect = profileButtonRef.current?.getBoundingClientRect() @@ -329,50 +342,58 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps): }, [activeProfileId, profiles]) const handleDragStart = useCallback((event: ReactPointerEvent): void => { - if (event.button !== 0) return + if (useNativeDragRegions || event.button !== 0) return event.preventDefault() event.currentTarget.setPointerCapture(event.pointerId) window.electronAPI.startWindowMove() - }, []) + }, [useNativeDragRegions]) const handleDragEnd = useCallback((event: ReactPointerEvent): void => { + if (useNativeDragRegions) { + return + } + if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId) } window.electronAPI.stopWindowMove() - }, []) + }, [useNativeDragRegions]) const handleToolbarDragStart = useCallback((event: ReactPointerEvent): void => { - if (isToolbarInteractiveTarget(event.target) || event.button !== 0) return + if (useNativeDragRegions || isToolbarInteractiveTarget(event.target) || event.button !== 0) return event.preventDefault() event.currentTarget.setPointerCapture(event.pointerId) window.electronAPI.startWindowMove() - }, []) + }, [useNativeDragRegions]) const handleToolbarDragEnd = useCallback((event: ReactPointerEvent): void => { + if (useNativeDragRegions) { + return + } + if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId) } window.electronAPI.stopWindowMove() - }, []) + }, [useNativeDragRegions]) const activeProfile = activeProfileId ? profiles[activeProfileId] : null return (
- {showReposition && ( + {showReposition && supportsProgrammaticReposition && (