diff --git a/desktop/electron/ipc/capabilities.test.ts b/desktop/electron/ipc/capabilities.test.ts
index 1ceff77d..eb94899a 100644
--- a/desktop/electron/ipc/capabilities.test.ts
+++ b/desktop/electron/ipc/capabilities.test.ts
@@ -26,9 +26,7 @@ describe('Electron IPC capabilities', () => {
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowClose, undefined)).toBe(true)
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowClose, {})).toBe(false)
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowStartDragging, undefined)).toBe(true)
- expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowStartDragging, { deltaX: 4, deltaY: -2 })).toBe(true)
- expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowStartDragging, { deltaX: 4, deltaY: Number.NaN })).toBe(false)
- expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowStartDragging, { deltaX: 4, deltaY: -2, extra: true })).toBe(false)
+ expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.windowStartDragging, { deltaX: 4, deltaY: -2 })).toBe(false)
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.terminalWrite, { sessionId: 1, data: 'pwd\n' })).toBe(true)
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.terminalWrite, { sessionId: '1', data: 'pwd\n' })).toBe(false)
expect(validateElectronIpcPayload(ELECTRON_IPC_CHANNELS.terminalSpawn, { cols: 80, rows: 24, cwd: '/tmp' })).toBe(true)
diff --git a/desktop/electron/ipc/capabilities.ts b/desktop/electron/ipc/capabilities.ts
index 55783673..6f65566b 100644
--- a/desktop/electron/ipc/capabilities.ts
+++ b/desktop/electron/ipc/capabilities.ts
@@ -56,17 +56,6 @@ const boundsPayload: Validator = value =>
&& typeof value.width === 'number'
&& typeof value.height === 'number'
-const windowDragMovePayload: Validator = value =>
- value === undefined
- || (
- isRecord(value)
- && hasOnlyKeys(value, ['deltaX', 'deltaY'])
- && typeof value.deltaX === 'number'
- && Number.isFinite(value.deltaX)
- && typeof value.deltaY === 'number'
- && Number.isFinite(value.deltaY)
- )
-
const urlWithOptionalBounds: Validator = value =>
isRecord(value)
&& typeof value.url === 'string'
@@ -102,7 +91,7 @@ export const ELECTRON_IPC_VALIDATORS = {
[ELECTRON_IPC_CHANNELS.windowMinimize]: noPayload,
[ELECTRON_IPC_CHANNELS.windowToggleMaximize]: noPayload,
[ELECTRON_IPC_CHANNELS.windowClose]: noPayload,
- [ELECTRON_IPC_CHANNELS.windowStartDragging]: windowDragMovePayload,
+ [ELECTRON_IPC_CHANNELS.windowStartDragging]: noPayload,
[ELECTRON_IPC_CHANNELS.windowRequestAttention]: noPayload,
[ELECTRON_IPC_CHANNELS.windowFocus]: noPayload,
[ELECTRON_IPC_CHANNELS.windowIsMaximized]: noPayload,
diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts
index aa072fa8..ecf22a0a 100644
--- a/desktop/electron/main.ts
+++ b/desktop/electron/main.ts
@@ -297,19 +297,7 @@ function registerIpcHandlers() {
else window.maximize()
})
registerHandler(ELECTRON_IPC_CHANNELS.windowClose, event => currentWindow(event).close())
- registerHandler(ELECTRON_IPC_CHANNELS.windowStartDragging, (event, payload) => {
- if (!payload || typeof payload !== 'object') return undefined
- const { deltaX, deltaY } = payload as { deltaX?: number, deltaY?: number }
- if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY)) return undefined
- const window = currentWindow(event)
- if (window.isMaximized()) window.unmaximize()
- const bounds = window.getBounds()
- window.setPosition(
- Math.round(bounds.x + deltaX!),
- Math.round(bounds.y + deltaY!),
- )
- return undefined
- })
+ registerHandler(ELECTRON_IPC_CHANNELS.windowStartDragging, () => undefined)
registerHandler(ELECTRON_IPC_CHANNELS.windowRequestAttention, event => currentWindow(event).flashFrame(true))
registerHandler(ELECTRON_IPC_CHANNELS.windowFocus, event => currentWindow(event).focus())
registerHandler(ELECTRON_IPC_CHANNELS.windowIsMaximized, event => currentWindow(event).isMaximized())
diff --git a/desktop/src/hooks/useElectronWindowDragRegions.test.tsx b/desktop/src/hooks/useElectronWindowDragRegions.test.tsx
index d4a98ce7..09901205 100644
--- a/desktop/src/hooks/useElectronWindowDragRegions.test.tsx
+++ b/desktop/src/hooks/useElectronWindowDragRegions.test.tsx
@@ -56,15 +56,15 @@ describe('useElectronWindowDragRegions', () => {
delete document.documentElement.dataset.desktopDragMode
})
- it('uses the manual drag fallback on Windows only', () => {
- expect(shouldUseManualWindowDrag('Win32')).toBe(true)
+ it('uses native app-region dragging on every platform', () => {
+ expect(shouldUseManualWindowDrag('Win32')).toBe(false)
expect(shouldUseManualWindowDrag('MacIntel')).toBe(false)
expect(shouldUseManualWindowDrag('Linux x86_64')).toBe(false)
})
- it('moves the native window while dragging a desktop drag region', async () => {
+ it('leaves Windows drag regions on the native app-region path', async () => {
render()
- expect(document.documentElement.dataset.desktopDragMode).toBe('manual')
+ expect(document.documentElement.dataset.desktopDragMode).toBeUndefined()
fireEvent.mouseDown(screen.getByTestId('blank-space'), {
button: 0,
@@ -77,10 +77,7 @@ describe('useElectronWindowDragRegions', () => {
})
await waitFor(() => {
- expect(desktopHostMock.host.window.startDragging).toHaveBeenCalledWith({
- deltaX: 30,
- deltaY: 30,
- })
+ expect(desktopHostMock.host.window.startDragging).not.toHaveBeenCalled()
})
fireEvent.mouseUp(window)
@@ -89,10 +86,10 @@ describe('useElectronWindowDragRegions', () => {
screenY: 210,
})
- expect(desktopHostMock.host.window.startDragging).toHaveBeenCalledTimes(1)
+ expect(desktopHostMock.host.window.startDragging).not.toHaveBeenCalled()
})
- it('keeps buttons and tab reorder targets out of the window drag fallback', () => {
+ it('does not route buttons or tab reorder targets through legacy drag IPC', () => {
render()
fireEvent.mouseDown(screen.getByRole('button', { name: 'Button' }), {
diff --git a/desktop/src/hooks/useElectronWindowDragRegions.ts b/desktop/src/hooks/useElectronWindowDragRegions.ts
index c125fd50..efaceb32 100644
--- a/desktop/src/hooks/useElectronWindowDragRegions.ts
+++ b/desktop/src/hooks/useElectronWindowDragRegions.ts
@@ -1,86 +1,9 @@
-import { useEffect } from 'react'
-import { getDesktopHost } from '../lib/desktopHost'
-
-const DRAG_REGION_SELECTOR = '[data-desktop-drag-region]'
-const MANUAL_DRAG_MODE = 'manual'
-const NO_DRAG_SELECTOR = [
- '[data-desktop-no-drag-region]',
- 'button',
- 'input',
- 'textarea',
- 'select',
- 'a',
- '[role="button"]',
- '[draggable="true"]',
- '.tab-bar-interactive',
- '.tab-bar-interactive *',
-].join(',')
-
export function shouldUseManualWindowDrag(platform = typeof navigator === 'undefined' ? '' : navigator.platform) {
- return /Win/i.test(platform)
-}
-
-export function isDesktopDragStartTarget(target: EventTarget | null): target is Element {
- if (!(target instanceof Element)) return false
- if (target.closest(NO_DRAG_SELECTOR)) return false
- return Boolean(target.closest(DRAG_REGION_SELECTOR))
+ void platform
+ return false
}
export function useElectronWindowDragRegions() {
- useEffect(() => {
- const host = getDesktopHost()
- if (!host.isDesktop || !host.capabilities?.windowControls || !host.window?.startDragging) return
- if (!shouldUseManualWindowDrag()) return
-
- const previousDragMode = document.documentElement.dataset.desktopDragMode
- document.documentElement.dataset.desktopDragMode = MANUAL_DRAG_MODE
-
- let dragging = false
- let lastScreenX = 0
- let lastScreenY = 0
-
- const stopDragging = () => {
- dragging = false
- }
-
- const handleMouseDown = (event: MouseEvent) => {
- if (event.button !== 0) return
- if (!isDesktopDragStartTarget(event.target)) return
- dragging = true
- lastScreenX = event.screenX
- lastScreenY = event.screenY
- event.preventDefault()
- }
-
- const handleMouseMove = (event: MouseEvent) => {
- if (!dragging) return
- const deltaX = event.screenX - lastScreenX
- const deltaY = event.screenY - lastScreenY
- if (deltaX === 0 && deltaY === 0) return
-
- lastScreenX = event.screenX
- lastScreenY = event.screenY
- void host.window.startDragging({ deltaX, deltaY }).catch(error => {
- console.error('Window drag fallback failed', error)
- stopDragging()
- })
- }
-
- document.addEventListener('mousedown', handleMouseDown, true)
- window.addEventListener('mousemove', handleMouseMove, true)
- window.addEventListener('mouseup', stopDragging, true)
- window.addEventListener('blur', stopDragging)
-
- return () => {
- if (previousDragMode === undefined) {
- delete document.documentElement.dataset.desktopDragMode
- } else {
- document.documentElement.dataset.desktopDragMode = previousDragMode
- }
- document.removeEventListener('mousedown', handleMouseDown, true)
- window.removeEventListener('mousemove', handleMouseMove, true)
- window.removeEventListener('mouseup', stopDragging, true)
- window.removeEventListener('blur', stopDragging)
- }
- }, [])
+ // Electron's native app-region dragging is the supported frameless-window path.
+ // The previous Windows JS delta fallback could resize frameless windows while moving them.
}
diff --git a/desktop/src/lib/desktopHost/electronHost.test.ts b/desktop/src/lib/desktopHost/electronHost.test.ts
index ab2bbf65..bd4884ca 100644
--- a/desktop/src/lib/desktopHost/electronHost.test.ts
+++ b/desktop/src/lib/desktopHost/electronHost.test.ts
@@ -45,19 +45,16 @@ describe('electron desktop host', () => {
expect(host.capabilities.windowControls).toBe(true)
})
- it('forwards drag-region fallback movement through the window dragging IPC channel', async () => {
+ it('keeps the legacy window dragging IPC channel payload-free', async () => {
const invoke = vi.fn().mockResolvedValue(undefined)
const host = createElectronHost({
invoke,
subscribe: vi.fn(),
})
- await host.window.startDragging({ deltaX: 12, deltaY: -8 })
+ await host.window.startDragging()
- expect(invoke).toHaveBeenCalledWith(ELECTRON_IPC_CHANNELS.windowStartDragging, {
- deltaX: 12,
- deltaY: -8,
- })
+ expect(invoke).toHaveBeenCalledWith(ELECTRON_IPC_CHANNELS.windowStartDragging, undefined)
})
it('opens dedicated trace windows through a narrow IPC channel', async () => {
diff --git a/desktop/src/lib/desktopHost/electronHost.ts b/desktop/src/lib/desktopHost/electronHost.ts
index 3db16684..bb04587a 100644
--- a/desktop/src/lib/desktopHost/electronHost.ts
+++ b/desktop/src/lib/desktopHost/electronHost.ts
@@ -118,7 +118,7 @@ export function createElectronHost(bridge: ElectronHostBridge): DesktopHost {
minimize: () => invoke(ELECTRON_IPC_CHANNELS.windowMinimize),
toggleMaximize: () => invoke(ELECTRON_IPC_CHANNELS.windowToggleMaximize),
close: () => invoke(ELECTRON_IPC_CHANNELS.windowClose),
- startDragging: input => invoke(ELECTRON_IPC_CHANNELS.windowStartDragging, input),
+ startDragging: () => invoke(ELECTRON_IPC_CHANNELS.windowStartDragging),
requestAttention: () => invoke(ELECTRON_IPC_CHANNELS.windowRequestAttention),
focus: () => invoke(ELECTRON_IPC_CHANNELS.windowFocus),
isMaximized: () => invoke(ELECTRON_IPC_CHANNELS.windowIsMaximized),
diff --git a/desktop/src/lib/desktopHost/types.ts b/desktop/src/lib/desktopHost/types.ts
index 2a6500c7..0fb99a9f 100644
--- a/desktop/src/lib/desktopHost/types.ts
+++ b/desktop/src/lib/desktopHost/types.ts
@@ -130,11 +130,6 @@ export type PreviewHostMessage = PreviewCaptureMessage | PreviewPickerMessage
export type AppModeConfig = SettingsAppModeConfig
-export type WindowDragMoveInput = {
- deltaX: number
- deltaY: number
-}
-
export type AppModeSetInput = {
mode: SettingsAppMode
portableDir: string | null
@@ -192,7 +187,7 @@ export type DesktopHost = {
minimize(): Promise
toggleMaximize(): Promise
close(): Promise
- startDragging(input?: WindowDragMoveInput): Promise
+ startDragging(): Promise
requestAttention(): Promise
focus(): Promise
isMaximized(): Promise
diff --git a/desktop/src/theme/globals.css b/desktop/src/theme/globals.css
index 853f0f69..53fc18ff 100644
--- a/desktop/src/theme/globals.css
+++ b/desktop/src/theme/globals.css
@@ -959,14 +959,10 @@ html, body, #root {
}
/* Desktop drag region */
-html:not([data-desktop-drag-mode="manual"]) [data-desktop-drag-region] {
+[data-desktop-drag-region] {
app-region: drag;
-webkit-app-region: drag;
}
-html[data-desktop-drag-mode="manual"] [data-desktop-drag-region] {
- app-region: no-drag;
- -webkit-app-region: no-drag;
-}
button, input, textarea, select, a, [role="button"] {
app-region: no-drag;
-webkit-app-region: no-drag;