From 292c1064edf315ae7166dcbe5fa53efcdbe35cd4 Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Thu, 26 Feb 2026 21:31:59 +0100 Subject: [PATCH 1/5] fix: dynamic cursor visibility based on input source cursor hidden in touchscreen mode on touch, shown on external mouse or controller-as-mouse movement. removes touchscreen mode check that was preventing cursor from showing when app window first appears. --- .../ui/screen/xserver/PhysicalControllerHandler.kt | 6 +++++- .../app/gamenative/ui/screen/xserver/XServerScreen.kt | 2 +- .../main/java/com/winlator/inputcontrols/TouchMouse.java | 4 ++++ .../main/java/com/winlator/widget/InputControlsView.java | 2 ++ app/src/main/java/com/winlator/widget/TouchpadView.java | 8 ++++++++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt index 036e5fb7b..f76318697 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt @@ -150,7 +150,11 @@ class PhysicalControllerHandler( val cursorSpeed = profile?.cursorSpeed ?: 1f val deltaX = (mouseMoveOffset.x * 10 * cursorSpeed).toInt() val deltaY = (mouseMoveOffset.y * 10 * cursorSpeed).toInt() - xServer?.injectPointerMoveDelta(deltaX, deltaY) + xServer?.let { + // show cursor when controller simulates mouse + if (!it.renderer.isCursorVisible) it.renderer.setCursorVisible(true) + it.injectPointerMoveDelta(deltaX, deltaY) + } } }, 0, 1000 / 60) } diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index 48e9a9ac9..7765c2d31 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -819,7 +819,7 @@ fun XServerScreen( } override fun onUpdateWindowContent(window: Window) { if (!xServerState.value.winStarted && window.isApplicationWindow()) { - if (!container.isDisableMouseInput && !container.isTouchscreenMode) renderer?.setCursorVisible(true) + if (!container.isDisableMouseInput) renderer?.setCursorVisible(true) xServerState.value.winStarted = true } if (window.id == frameRatingWindowId) { diff --git a/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java b/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java index 2253e3b02..0cfc6a11b 100644 --- a/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java +++ b/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java @@ -301,6 +301,10 @@ public static boolean isMouseDevice(InputDevice device) { } public boolean onExternalMouseEvent(MotionEvent event) { + // show cursor on first external mouse event + if (!xServer.getRenderer().isCursorVisible()) { + xServer.getRenderer().setCursorVisible(true); + } boolean handled = false; // if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (isMouseDevice(event.getDevice())) { diff --git a/app/src/main/java/com/winlator/widget/InputControlsView.java b/app/src/main/java/com/winlator/widget/InputControlsView.java index ea5baeb6a..2db75af7e 100644 --- a/app/src/main/java/com/winlator/widget/InputControlsView.java +++ b/app/src/main/java/com/winlator/widget/InputControlsView.java @@ -342,6 +342,8 @@ private void createMouseMoveTimer() { mouseMoveTimer.schedule(new TimerTask() { @Override public void run() { + // show cursor when on-screen control simulates mouse + if (!xServer.getRenderer().isCursorVisible()) xServer.getRenderer().setCursorVisible(true); xServer.injectPointerMoveDelta((int)(mouseMoveOffset.x * 10 * cursorSpeed), (int)(mouseMoveOffset.y * 10 * cursorSpeed)); } }, 0, 1000 / 60); diff --git a/app/src/main/java/com/winlator/widget/TouchpadView.java b/app/src/main/java/com/winlator/widget/TouchpadView.java index 890e6ae28..e2ca65f81 100644 --- a/app/src/main/java/com/winlator/widget/TouchpadView.java +++ b/app/src/main/java/com/winlator/widget/TouchpadView.java @@ -226,6 +226,10 @@ public boolean onTouchEvent(MotionEvent event) { && !event.isFromSource(InputDevice.SOURCE_MOUSE)) { return true; // consume without generating mouse events } + // hide cursor on touch in touchscreen mode, it reappears on external mouse event + if (isTouchscreenMode && !event.isFromSource(InputDevice.SOURCE_MOUSE) && xServer.getRenderer().isCursorVisible()) { + xServer.getRenderer().setCursorVisible(false); + } if (toolType == MotionEvent.TOOL_TYPE_STYLUS) { return handleStylusEvent(event); } else if (isTouchscreenMode) { @@ -1142,6 +1146,10 @@ public void setMoveCursorToTouchpoint(boolean moveCursorToTouchpoint) { } public boolean onExternalMouseEvent(MotionEvent event) { + // show cursor on first external mouse event + if (!xServer.getRenderer().isCursorVisible()) { + xServer.getRenderer().setCursorVisible(true); + } // one-shot: capture external mouse on first event, don't re-capture after user release if (capturePointerOnExternalMouse && !pointerCaptureRequested) { pointerCaptureRequested = true; From bf59873ffd8d3863787deed786d09601b9ace4fc Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Thu, 26 Feb 2026 21:42:17 +0100 Subject: [PATCH 2/5] fix: revert incorrect disableMouseInput guards on cursor visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review suggested guarding setCursorVisible behind disableMouseInput checks, but that flag means "don't let touch generate mouse events" not "disable all pointing devices." External mouse and controller- as-mouse should show cursor regardless. Reverted touchscreenMouseDisabled guard in TouchpadView, removed mouseEnabled param from PhysicalControllerHandler. Kept: SOURCE_MOUSE check in TouchpadView and isMouseDevice check in TouchMouse — cursor only shows for actual mouse device events. --- .../ui/screen/xserver/PhysicalControllerHandler.kt | 1 - .../main/java/com/winlator/inputcontrols/TouchMouse.java | 8 ++++---- app/src/main/java/com/winlator/widget/TouchpadView.java | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt index f76318697..fb1aeb877 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt @@ -151,7 +151,6 @@ class PhysicalControllerHandler( val deltaX = (mouseMoveOffset.x * 10 * cursorSpeed).toInt() val deltaY = (mouseMoveOffset.y * 10 * cursorSpeed).toInt() xServer?.let { - // show cursor when controller simulates mouse if (!it.renderer.isCursorVisible) it.renderer.setCursorVisible(true) it.injectPointerMoveDelta(deltaX, deltaY) } diff --git a/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java b/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java index 0cfc6a11b..fa6f5557b 100644 --- a/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java +++ b/app/src/main/java/com/winlator/inputcontrols/TouchMouse.java @@ -301,13 +301,13 @@ public static boolean isMouseDevice(InputDevice device) { } public boolean onExternalMouseEvent(MotionEvent event) { - // show cursor on first external mouse event - if (!xServer.getRenderer().isCursorVisible()) { - xServer.getRenderer().setCursorVisible(true); - } boolean handled = false; // if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (isMouseDevice(event.getDevice())) { + // show cursor on external mouse event + if (!xServer.getRenderer().isCursorVisible()) { + xServer.getRenderer().setCursorVisible(true); + } int actionButton = event.getActionButton(); switch (event.getAction()) { case MotionEvent.ACTION_BUTTON_PRESS: diff --git a/app/src/main/java/com/winlator/widget/TouchpadView.java b/app/src/main/java/com/winlator/widget/TouchpadView.java index e2ca65f81..636fcb27f 100644 --- a/app/src/main/java/com/winlator/widget/TouchpadView.java +++ b/app/src/main/java/com/winlator/widget/TouchpadView.java @@ -1146,8 +1146,9 @@ public void setMoveCursorToTouchpoint(boolean moveCursorToTouchpoint) { } public boolean onExternalMouseEvent(MotionEvent event) { - // show cursor on first external mouse event - if (!xServer.getRenderer().isCursorVisible()) { + // show cursor on external mouse event + if (event.isFromSource(InputDevice.SOURCE_MOUSE) + && !xServer.getRenderer().isCursorVisible()) { xServer.getRenderer().setCursorVisible(true); } // one-shot: capture external mouse on first event, don't re-capture after user release From d58c03e489a5811c0f26129303e66884fb26f1c8 Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Thu, 26 Feb 2026 22:33:39 +0100 Subject: [PATCH 3/5] fix: clarify "Disable Mouse Input" as touchscreen disable rename to "Disable Touchscreen" with description clarifying it only prevents touch-generated mouse events. external mouse and controller still work. touchscreen mode preference disabled when touchscreen is disabled since it has no effect. --- .../java/app/gamenative/ui/component/dialog/ControllerTab.kt | 2 ++ app/src/main/res/values/strings.xml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt index 2ef1543fe..1d96726a1 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt @@ -68,11 +68,13 @@ fun ControllerTabContent(state: ContainerConfigState, default: Boolean) { SettingsSwitch( colors = settingsTileColorsAlt(), title = { Text(text = stringResource(R.string.disable_mouse_input)) }, + subtitle = { Text(text = stringResource(R.string.disable_mouse_input_description)) }, state = config.disableMouseInput, onCheckedChange = { state.config.value = config.copy(disableMouseInput = it) }, ) SettingsMenuLink( colors = settingsTileColorsAlt(), + enabled = !config.disableMouseInput, title = { Text(text = stringResource(R.string.touchscreen_mode)) }, subtitle = { Text(text = stringResource(R.string.touchscreen_mode_description)) }, onClick = { state.config.value = config.copy(touchscreenMode = !config.touchscreenMode) }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c3833d6d..7b484310d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,7 +627,8 @@ Enable XInput API Enable DirectInput API DirectInput Mapper Type - Disable Mouse Input + Disable Touchscreen + Prevent touch from generating mouse events. External mouse and controller still work. Touchscreen Mode For visual novels and RTS games Shooter Mode From 8e27cf4d07659a43cd02d7052b25ec2cd5368e61 Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Mon, 2 Mar 2026 10:33:16 +0100 Subject: [PATCH 4/5] add translations for touchscreen disable strings --- app/src/main/res/values-da/strings.xml | 3 ++- app/src/main/res/values-de/strings.xml | 3 ++- app/src/main/res/values-es/strings.xml | 3 ++- app/src/main/res/values-fr/strings.xml | 3 ++- app/src/main/res/values-it/strings.xml | 3 ++- app/src/main/res/values-ko/strings.xml | 3 ++- app/src/main/res/values-pl/strings.xml | 3 ++- app/src/main/res/values-pt-rBR/strings.xml | 3 ++- app/src/main/res/values-ro/strings.xml | 3 ++- app/src/main/res/values-uk/strings.xml | 3 ++- app/src/main/res/values-zh-rCN/strings.xml | 3 ++- app/src/main/res/values-zh-rTW/strings.xml | 3 ++- 12 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 99487d280..969492748 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -402,7 +402,8 @@ Aktivér XInput API Aktivér DirectInput API DirectInput-mappertype - Deaktivér musinput + Deaktivér touchscreen + Forhindrer berøring i at generere musbegivenheder. Ekstern mus og controller virker stadig. Touchskærmstilstand Til visuelle romaner og RTS-spil Shooter-tilstand diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 77e066904..e804c8cf9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -552,7 +552,8 @@ XInput-API aktivieren DirectInput-API aktivieren DirectInput-Mapper-Typ - Mauseingabe deaktivieren + Touchscreen deaktivieren + Verhindert, dass Berührungen Mausevents erzeugen. Externe Maus und Controller funktionieren weiterhin. Touchscreen-Modus Für Visual Novels und RTS-Spiele Shooter-Modus diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d2f719ffe..466c24317 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -567,7 +567,8 @@ Activar API de XInput Activar API de DirectInput Tipo de mapeador de DirectInput - Desactivar entrada de ratón + Desactivar pantalla táctil + Evita que el toque genere eventos de ratón. El ratón externo y el mando siguen funcionando. Modo de pantalla táctil Para novelas visuales y juegos de estrategia en tiempo real Modo tirador diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9c382ec7b..319a95c1f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -578,7 +578,8 @@ Activer l\'API XInput Activer l\'API DirectInput Type de mappeur DirectInput - Désactiver l\'entrée souris + Désactiver l\'écran tactile + Empêche le toucher de générer des événements souris. La souris externe et la manette fonctionnent toujours. Mode écran tactile Pour les visual novels et jeux de stratégie en temps réel Mode tireur diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b0188a5fd..68819ad26 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -582,7 +582,8 @@ Abilita API XInput Abilita API DirectInput Tipo Mapper DirectInput - Disabilita Input Mouse + Disabilita touchscreen + Impedisce al tocco di generare eventi del mouse. Mouse esterno e controller continuano a funzionare. Modalità Touchscreen Per visual novel e giochi RTS Modalità sparatutto diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 0efaa2997..b6dfe88b3 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -592,7 +592,8 @@ XInput API 활성화 DirectInput API 활성화 DirectInput 매퍼 유형 - 마우스 입력 비활성화 + 터치스크린 비활성화 + 터치로 마우스 이벤트가 생성되지 않습니다. 외부 마우스와 컨트롤러는 계속 작동합니다. 터치스크린 모드 비주얼 노벨 및 RTS 게임용 슈터 모드 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c3a85af35..cade1ecd6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -591,7 +591,8 @@ Włącz API XInput Włącz API DirectInput Typ mapowania DirectInput - Wyłącz wejście myszy + Wyłącz ekran dotykowy + Zapobiega generowaniu zdarzeń myszy przez dotyk. Zewnętrzna mysz i kontroler nadal działają. Tryb ekranu dotykowego Dla powieści wizualnych i gier RTS Tryb strzelanki diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 9968bde8e..ac4242392 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -402,7 +402,8 @@ Habilitar API XInput Habilitar API DirectInput Tipo de mapeador DirectInput - Desabilitar entrada do mouse + Desabilitar tela de toque + Impede que o toque gere eventos de mouse. Mouse externo e controle continuam funcionando. Modo Touchscreen Para visual novels e jogos de estratégia em tempo real Modo tiro diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 20f7800a7..e64b3c03e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -583,7 +583,8 @@ Activează API XInput Activează API DirectInput Tip mapper DirectInput - Dezactivează input-ul de mouse + Dezactivează ecranul tactil + Împiedică atingerea să genereze evenimente de mouse. Mouse-ul extern și controller-ul funcționează în continuare. Mod touchscreen Pentru visual novels și jocuri RTS Mod shooter diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8a6be2140..e200f80e4 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -574,7 +574,8 @@ Увімкнути XInput API Увімкнути DirectInput API Тип мапера DirectInput - Вимкнути введення мишею + Вимкнути сенсорний екран + Запобігає генерації подій миші дотиком. Зовнішня миша та контролер продовжують працювати. Режим сенсорного екрану Для візуальних романів та ігор RTS Режим шутера diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8b19c22cc..354b3380d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -574,7 +574,8 @@ 启用 XInput API 启用 DirectInput API DirectInput 映射器类型 - 禁用鼠标输入 + 禁用触摸屏 + 阻止触摸产生鼠标事件。外接鼠标和手柄仍可正常使用。 触屏模式 适用于视觉小说和即时战略游戏 射击模式 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a9a17f5c6..2fd12baab 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -576,7 +576,8 @@ 啟用 XInput API 啟用 DirectInput API DirectInput Mapper 類型 - 停用滑鼠輸入 + 停用觸控螢幕 + 防止觸控產生滑鼠事件。外接滑鼠和控制器仍可正常使用。 觸屏模式 適用於視覺小說與即時戰略遊戲 射擊模式 From f207c3cb0255045a047a369c2c645b9a84183d55 Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Mon, 2 Mar 2026 10:53:43 +0100 Subject: [PATCH 5/5] fix: disable touchscreen mode controls when touchscreen is disabled --- .../java/app/gamenative/ui/component/dialog/ControllerTab.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt index 1d96726a1..16ca65ef7 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt @@ -80,7 +80,7 @@ fun ControllerTabContent(state: ContainerConfigState, default: Boolean) { onClick = { state.config.value = config.copy(touchscreenMode = !config.touchscreenMode) }, action = { Row(verticalAlignment = Alignment.CenterVertically) { - IconButton(onClick = { showGestureDialog = true }) { + IconButton(enabled = !config.disableMouseInput, onClick = { showGestureDialog = true }) { Icon( imageVector = Icons.Default.Settings, contentDescription = stringResource(R.string.gesture_settings), @@ -88,6 +88,7 @@ fun ControllerTabContent(state: ContainerConfigState, default: Boolean) { } Switch( checked = config.touchscreenMode, + enabled = !config.disableMouseInput, onCheckedChange = { state.config.value = config.copy(touchscreenMode = it) }, ) }