Merge pull request #22 from Droid-Deck/feat/steam-touch

Steam client: real touch in Big Picture, and a touchpad cursor that hovers and clicks
This commit is contained in:
The412Banner authored and GitHub committed 2026-09-25 17:06:19 -04:00
commit 8cdedbe18a
6 files changed
+169 -6

No files matched your search

+12 -3
View File
@@ -3210,23 +3210,32 @@ static void touch_down(int id, double x, double y) {
struct surface *target = touch_target_at(x, y);
if (!target) return;
struct wl_client *client = wl_resource_get_client(target->resource);
/* A down on an id that is still held means that sequence's up was lost: end it first. The
* fingers still down are counted only after that - counted before, a lost up left any_active
* set, and on the pointer path every later touch was dropped (a tap or swipe did nothing). */
if (g_touch_points[id].surface) touch_cancel_client(client);
int any_active = 0;
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
struct surface *active = g_touch_points[i].surface;
if (!active) continue;
any_active = 1;
if (active != target) {
touch_cancel_all();
any_active = 0;
break;
}
any_active = 1;
}
if (g_touch_points[id].surface) touch_cancel_client(client);
struct seat_touch *st;
int has_touch = 0;
for_each_touch_of(client, st) has_touch = 1;
/* One line per gesture (first finger), so a test can see where a touch went. */
if (!any_active) {
char name[160];
describe(target, name, sizeof(name));
banner_log("touch", "%s gets %s", name, has_touch ? "touch" : "the pointer (it takes no touch)");
}
if (!has_touch) {
if (any_active) return;
if (any_active) return; /* one finger drives the pointer; a second one is ignored */
g_touch_points[id].surface = target;
g_touch_points[id].pointer_fallback = 1;
pointer_event(x, y, BTN_LEFT, 1);
@@ -1231,7 +1231,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
WaylandCompositor.nativeSendTouch(action, event.getPointerId(index), (x * 1919f).toInt(), (y * 1079f).toInt())
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> sendTouch(0, event.actionIndex)
// A first finger starts a new gesture, and Android has ended every earlier one: whatever
// the compositor still holds (an up lost to the drawer opening mid-touch) goes first.
MotionEvent.ACTION_DOWN -> {
WaylandCompositor.nativeSendTouch(3, -1, 0, 0)
sendTouch(0, event.actionIndex)
}
MotionEvent.ACTION_POINTER_DOWN -> sendTouch(0, event.actionIndex)
MotionEvent.ACTION_MOVE -> for (index in 0 until event.pointerCount) sendTouch(1, index)
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> sendTouch(2, event.actionIndex)
MotionEvent.ACTION_CANCEL -> WaylandCompositor.nativeSendTouch(3, -1, 0, 0)
+11
View File
@@ -16,6 +16,17 @@ library list, before anything is published.
3.16.29: realtime-priority Vulkan queues on request (`GAMESCOPE_FORCE_VULKAN_REALTIME=1`)
without CAP_SYS_NICE, which proot can never have; and the gamepad-driven cursor sprite following
the X pointer that XTest moves (it sat frozen).
- `0110-wayland-backend-touch.patch` - this app: the nested Wayland backend bound only the host's
pointer and keyboard, so a finger on the phone's screen never reached Steam. It now binds
`wl_touch` too and hands each finger to wlserver's touch path (`wlserver_touchdown` / `motion` /
`up`) - the one a Steam Deck's touchscreen drives - so what a touch does follows the client's
touch mode (Steam's Big Picture sets Passthrough: a real touch, rows scroll under a finger).
Finger ids are offset by one, since the nested pointer already moves wlserver's touch 0.
- `0111-wayland-pointer-warps-in-passthrough.patch` - this app: the nested pointer's motion goes to
wlserver as touch 0, and in Passthrough (Big Picture's touch mode) a motion for a touch that is not
down moves nothing - so in the app's touchpad mode the Steam client saw no hover and a click landed
wherever the pointer had last been. The motion now always warps the real pointer as well
(`bAlwaysWarpCursor`), which the other touch modes did already.
Sixteen more of Armada's patches are DRM/lease/HDR-on-KMS work for a native display, which this
app's Wayland-hosted gamescope never reaches, or need a newer gamescope than the runtime has.
@@ -0,0 +1,121 @@
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index d74268b..bf72267 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -601,6 +601,17 @@ namespace gamescope
void Wayland_Keyboard_RepeatInfo( wl_keyboard *pKeyboard, int32_t nRate, int32_t nDelay );
static const wl_keyboard_listener s_KeyboardListener;
+ // Touch from the host compositor (DroidDeck): each finger goes to wlserver's touch path,
+ // the one a Steam Deck's touchscreen drives, instead of being dropped.
+ void Wayland_Touch_Down( wl_touch *pTouch, uint32_t uSerial, uint32_t uTime, wl_surface *pSurface, int32_t nId, wl_fixed_t fX, wl_fixed_t fY );
+ void Wayland_Touch_Up( wl_touch *pTouch, uint32_t uSerial, uint32_t uTime, int32_t nId );
+ void Wayland_Touch_Motion( wl_touch *pTouch, uint32_t uTime, int32_t nId, wl_fixed_t fX, wl_fixed_t fY );
+ void Wayland_Touch_Frame( wl_touch *pTouch );
+ void Wayland_Touch_Cancel( wl_touch *pTouch );
+ static const wl_touch_listener s_TouchListener;
+ bool TouchToOutput( wl_surface *pSurface, wl_fixed_t fX, wl_fixed_t fY, double *pflX, double *pflY );
+ std::unordered_map<int32_t, wl_surface *> m_TouchSurfaces;
+
void Wayland_RelativePointer_RelativeMotion( zwp_relative_pointer_v1 *pRelativePointer, uint32_t uTimeHi, uint32_t uTimeLo, wl_fixed_t fDx, wl_fixed_t fDy, wl_fixed_t fDxUnaccel, wl_fixed_t fDyUnaccel );
static const zwp_relative_pointer_v1_listener s_RelativePointerListener;
};
@@ -636,6 +647,16 @@ namespace gamescope
.modifiers = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Modifiers ),
.repeat_info = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_RepeatInfo ),
};
+ const wl_touch_listener CWaylandInputThread::s_TouchListener =
+ {
+ .down = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Touch_Down ),
+ .up = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Touch_Up ),
+ .motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Touch_Motion ),
+ .frame = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Touch_Frame ),
+ .cancel = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Touch_Cancel ),
+ .shape = WAYLAND_NULL(),
+ .orientation = WAYLAND_NULL(),
+ };
const zwp_relative_pointer_v1_listener CWaylandInputThread::s_RelativePointerListener =
{
.relative_motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ),
@@ -3067,6 +3088,81 @@ namespace gamescope
wl_keyboard_add_listener( m_pKeyboard, &s_KeyboardListener, this );
}
}
+
+ if ( !!( uCapabilities & WL_SEAT_CAPABILITY_TOUCH ) != !!m_pTouch )
+ {
+ if ( m_pTouch )
+ {
+ wl_touch_release( m_pTouch );
+ m_pTouch = nullptr;
+ m_TouchSurfaces.clear();
+ }
+ else
+ {
+ m_pTouch = wl_seat_get_touch( m_pSeat );
+ wl_touch_add_listener( m_pTouch, &s_TouchListener, this );
+ }
+ }
+ }
+
+ // Touch
+
+ bool CWaylandInputThread::TouchToOutput( wl_surface *pSurface, wl_fixed_t fX, wl_fixed_t fY, double *pflX, double *pflY )
+ {
+ if ( !pSurface || !IsGamescopeToplevel( pSurface ) )
+ return false;
+ CWaylandPlane *pPlane = (CWaylandPlane *)wl_surface_get_user_data( pSurface );
+ if ( !pPlane )
+ return false;
+ auto oState = pPlane->GetCurrentState();
+ if ( !oState || !g_nOutputWidth || !g_nOutputHeight )
+ return false;
+ uint32_t uScale = oState->uFractionalScale;
+ *pflX = ( wl_fixed_to_double( fX ) * uScale / 120.0 + oState->nDestX ) / g_nOutputWidth;
+ *pflY = ( wl_fixed_to_double( fY ) * uScale / 120.0 + oState->nDestY ) / g_nOutputHeight;
+ return true;
+ }
+ // Touch ids are offset by one: the nested pointer already moves wlserver's touch 0.
+ void CWaylandInputThread::Wayland_Touch_Down( wl_touch *pTouch, uint32_t uSerial, uint32_t uTime, wl_surface *pSurface, int32_t nId, wl_fixed_t fX, wl_fixed_t fY )
+ {
+ double flX, flY;
+ if ( !TouchToOutput( pSurface, fX, fY, &flX, &flY ) )
+ return;
+ m_TouchSurfaces[ nId ] = pSurface;
+ wlserver_lock();
+ wlserver_touchdown( flX, flY, nId + 1, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ void CWaylandInputThread::Wayland_Touch_Up( wl_touch *pTouch, uint32_t uSerial, uint32_t uTime, int32_t nId )
+ {
+ if ( m_TouchSurfaces.erase( nId ) == 0 )
+ return;
+ wlserver_lock();
+ wlserver_touchup( nId + 1, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ void CWaylandInputThread::Wayland_Touch_Motion( wl_touch *pTouch, uint32_t uTime, int32_t nId, wl_fixed_t fX, wl_fixed_t fY )
+ {
+ auto it = m_TouchSurfaces.find( nId );
+ if ( it == m_TouchSurfaces.end() )
+ return;
+ double flX, flY;
+ if ( !TouchToOutput( it->second, fX, fY, &flX, &flY ) )
+ return;
+ wlserver_lock();
+ wlserver_touchmotion( flX, flY, nId + 1, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ void CWaylandInputThread::Wayland_Touch_Frame( wl_touch *pTouch )
+ {
+ }
+ void CWaylandInputThread::Wayland_Touch_Cancel( wl_touch *pTouch )
+ {
+ wlserver_lock();
+ for ( auto &[ nId, pSurface ] : m_TouchSurfaces )
+ wlserver_touchup( nId + 1, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ m_TouchSurfaces.clear();
}
void CWaylandInputThread::Wayland_Seat_Name( wl_seat *pSeat, const char *pName )
@@ -0,0 +1,16 @@
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index bf72267..c400978 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -3223,7 +3223,10 @@ namespace gamescope
double flY = ( wl_fixed_to_double( fSurfaceY ) * uScale / 120.0 + oState->nDestY ) / g_nOutputHeight;
wlserver_lock();
- wlserver_touchmotion( flX, flY, 0, ++m_uFakeTimestamp );
+ // Always move the real pointer too: in Passthrough (what Steam's Big Picture asks for) a
+ // touch motion for an id that is not down moves nothing, so hover never reached the
+ // client and a click landed wherever the pointer had last been.
+ wlserver_touchmotion( flX, flY, 0, ++m_uFakeTimestamp, true );
wlserver_unlock();
}
void CWaylandInputThread::Wayland_Pointer_Button( wl_pointer *pPointer, uint32_t uSerial, uint32_t uTime, uint32_t uButton, uint32_t uState )
+2 -2
View File
@@ -1,2 +1,2 @@
GAMESCOPE_TAG=gamescope-3.16.29-p1
GAMESCOPE_SHA256=522d1835d84e4a64a843e095bbe341f68b10f8467f38c0e72ae4ef20b675b873
GAMESCOPE_TAG=gamescope-3.16.29-p3
GAMESCOPE_SHA256=566e3b93931f37c30375d440d15ee76452892b842d2dc854cd3c06b926c8451b