[PATCH v9 0/3] MR9874: wineandroid.drv: experimental bring-up fixes for Android 7.1.x
Requires !10067, fixup/followup to e0d3683d8947cd9a3cb20acdf69091b183a90861 and 4b5311c7e02aa7ab2409f8fdcd22233f116dc4bc. #### Note on authorship and language This text was translated into English and structured with the assistance of ChatGPT. The author is not a native English speaker and may struggle with written technical English; any wording issues are unintentional. #### Description This merge request is an experimental attempt to bring wineandroid.drv back to a minimally functional state on Android 7.1.x. The current Android support appears to have bit-rotted over time, likely due to a combination of internal contract changes in Wine, toolchain and NDK evolution, and the absence of continuous Android testing. The changes proposed here aim to re-establish a working baseline and make the regressions more concrete and easier to reason about. #### Test environment and context All testing so far was performed on Android 7.1.2 x86 images from the Android-x86 project, published on osboxes.org ([link](https://www.osboxes.org/android-x86/)). The primary target is i386 Wine running on x86 Android userspace. Because no up-to-date official Android build scripts were available, Wine was built using Termux as an unsupported but convenient environment for rapid testing. The Java part (WineActivity and related glue code) was built with `sharedUserId = com.termux` purely for convenience (filesystem access and faster prototyping). **Termux is explicitly not considered a supported environment and is mentioned only to provide context for the testing setup.** The build uses Android NDK r29. Since the last known functional Android Wine builds, the NDK and linker behavior have changed multiple times, including symbol visibility and relocation handling. These changes appear to have directly affected several failure modes described below. There is also a known issue with wine-preloader producing excessively large binaries under recent toolchains; a workaround exists but is not included here and is mentioned only as additional context. #### Scope of changes This merge request currently modifies only Android-specific code: - `wineandroid.drv` (native code and Java glue, including WineActivity) - android-only paths in `dlls/ntdll/unix/loader.c` No non-Android platforms are affected. #### Summary of resolved issues 1) Desktop initialization ordering regression After recent `CreateDesktopW`-related changes, `ANDROID_CreateDesktop` started running before the DESKTOP window was created. The Java glue assumed the opposite order. The fix splits desktop initialization into two phases (view setup first, desktop window attachment later via an explicit is_desktop flag) and corrects the `ANDROID_CreateDesktop` success return value. 2) Rendering stalls and `USER lock assertion` Occasional crashes with: ``` err:system:user_check_not_lock BUG: holding USER lock ``` A full memory barrier was added in the dequeue path to avoid a likely memory ordering issue. Additionally, a guard was added for a `NULL` buffer returned on successful dequeue. 3) Early startup failure in `virtual_alloc_first_teb` Wine frequently failed with: ``` err:virtual:virtual_alloc_first_teb wine: failed to map the shared user data: c0000018 ``` `virtual_init()` is now called from `JNI_OnLoad()` to reduce the likelihood of the JVM reserving the required address range before Wine initializes its virtual memory subsystem. #### Remaining issues - Window focus handling is partially broken: windows receive focus on click, but are not always raised to the front. - Despite the available desktop area being larger (e.g. 1024x696), Wine consistently reports a display resolution of 800x600. #### Limitations Wine on Android stopped functioning entirely starting with Android 8. As a result, all testing so far has been limited to Android 7.1.x. No claims are made about behavior on newer Android versions. #### Out of scope This merge request intentionally does not attempt to: - redesign IPC mechanisms - introduce new Android integration models - address Android 8+ compatibility The sole goal is to re-establish a minimally working baseline on Android 7.1.x and enable informed discussion about what regressed and how to proceed. **This is my first contribution to Wine, and I am aware that some parts of this analysis or implementation may be flawed. I would be grateful for any review comments or suggestions.** <details> <summary>Also, the screenshot of the working prototype.</summary> {width=756 height=600} </details> -- v9: ntdll: call virtual_init from JNI_OnLoad to avoid early virtual_alloc_first_teb failure Wineandroid: Sanitize dequeueBuffer result and reject missing buffers. wineandroid: fix desktop init ordering after CreateDesktopW changes https://gitlab.winehq.org/wine/wine/-/merge_requests/9874
From: Twaik Yont <9674930+twaik@users.noreply.github.com> Follow-up to e0d3683d8947cd9a3cb20acdf69091b183a90861 and 4b5311c7e02aa7ab2409f8fdcd22233f116dc4bc. After 4b5311c7e02aa7ab2409f8fdcd22233f116dc4bc, ANDROID_CreateDesktop is invoked from CreateDesktopW before the DESKTOP window is created. The previous code assumed the opposite order and tried to construct the desktop WineWindow from createDesktopWindow(), which no longer works. Split desktop initialization into two steps: create_desktop_view() now only sets up the Java TopView, and the actual desktop WineWindow is created later via create_window(), with an explicit is_desktop flag propagated through the IOCTL/JNI path. Also make ANDROID_CreateDesktop return TRUE on success. After e0d3683d8947cd9a3cb20acdf69091b183a90861 (effectively changing ANDROID_CreateDesktop return type from NTSTATUS to BOOL), returning 0 incorrectly indicated failure. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/wineandroid.drv/WineActivity.java | 31 ++++++++++++++------------ dlls/wineandroid.drv/android.h | 1 + dlls/wineandroid.drv/device.c | 26 ++++++++++++++++----- dlls/wineandroid.drv/init.c | 1 + dlls/wineandroid.drv/window.c | 6 ++--- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/dlls/wineandroid.drv/WineActivity.java b/dlls/wineandroid.drv/WineActivity.java index a47a4f89290..2eee16b2dc1 100644 --- a/dlls/wineandroid.drv/WineActivity.java +++ b/dlls/wineandroid.drv/WineActivity.java @@ -64,6 +64,7 @@ public class WineActivity extends Activity private final String LOGTAG = "wine"; private ProgressDialog progress_dialog; + protected TopView top_view; protected WineWindow desktop_window; protected WineWindow message_window; private PointerIcon current_cursor; @@ -761,13 +762,9 @@ public boolean dispatchKeyEvent( KeyEvent event ) protected class TopView extends ViewGroup { - public TopView( Context context, int hwnd ) + public TopView( Context context ) { super( context ); - desktop_window = new WineWindow( hwnd, null, 1.0f ); - addView( desktop_window.create_whole_view() ); - desktop_window.client_group.bringToFront(); - message_window = new WineWindow( WineWindow.HWND_MESSAGE, null, 1.0f ); message_window.create_window_groups(); } @@ -793,22 +790,28 @@ protected WineWindow get_window( int hwnd ) // Entry points for the device driver - public void create_desktop_window( int hwnd ) + public void create_desktop_view() { - Log.i( LOGTAG, String.format( "create desktop view %08x", hwnd )); - setContentView( new TopView( this, hwnd )); + Log.i( LOGTAG, "create desktop view "); + setContentView( top_view = new TopView( this )); progress_dialog.dismiss(); wine_config_changed( getResources().getConfiguration().densityDpi ); } - public void create_window( int hwnd, boolean opengl, int parent, float scale, int pid ) + public void create_window( int hwnd, boolean is_desktop, boolean opengl, int parent, float scale, int pid ) { WineWindow win = get_window( hwnd ); if (win == null) { - win = new WineWindow( hwnd, get_window( parent ), scale ); + win = new WineWindow( hwnd, is_desktop ? null : get_window( parent ), scale ); win.create_window_groups(); if (win.parent == desktop_window) win.create_whole_view(); + if (is_desktop) + { + desktop_window = win; + top_view.addView( desktop_window.create_whole_view() ); + desktop_window.client_group.bringToFront(); + } } if (opengl) win.create_client_view(); } @@ -847,14 +850,14 @@ public void window_pos_changed( int hwnd, int flags, int insert_after, int owner win.pos_changed( flags, insert_after, owner, style, window_rect, client_rect, visible_rect ); } - public void createDesktopWindow( final int hwnd ) + public void createDesktopView() { - runOnUiThread( new Runnable() { public void run() { create_desktop_window( hwnd ); }} ); + runOnUiThread( new Runnable() { public void run() { create_desktop_view(); }} ); } - public void createWindow( final int hwnd, final boolean opengl, final int parent, final float scale, final int pid ) + public void createWindow( final int hwnd, final boolean is_desktop, final boolean opengl, final int parent, final float scale, final int pid ) { - runOnUiThread( new Runnable() { public void run() { create_window( hwnd, opengl, parent, scale, pid ); }} ); + runOnUiThread( new Runnable() { public void run() { create_window( hwnd, is_desktop, opengl, parent, scale, pid ); }} ); } public void destroyWindow( final int hwnd ) diff --git a/dlls/wineandroid.drv/android.h b/dlls/wineandroid.drv/android.h index 1ed83406cff..16d60911174 100644 --- a/dlls/wineandroid.drv/android.h +++ b/dlls/wineandroid.drv/android.h @@ -85,6 +85,7 @@ extern SHORT ANDROID_VkKeyScanEx( WCHAR ch, HKL hkl ); extern void ANDROID_SetCursor( HWND hwnd, HCURSOR handle ); extern BOOL ANDROID_CreateDesktop( const WCHAR *name, UINT width, UINT height ); extern BOOL ANDROID_CreateWindow( HWND hwnd ); +extern void ANDROID_SetDesktopWindow( HWND hwnd ); extern void ANDROID_DestroyWindow( HWND hwnd ); extern BOOL ANDROID_ProcessEvents( DWORD mask ); extern LRESULT ANDROID_DesktopWindowProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ); diff --git a/dlls/wineandroid.drv/device.c b/dlls/wineandroid.drv/device.c index d936c95b15a..3dce2e7399f 100644 --- a/dlls/wineandroid.drv/device.c +++ b/dlls/wineandroid.drv/device.c @@ -51,6 +51,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(android); static HANDLE thread; static JNIEnv *jni_env; static HWND capture_window; +static HWND desktop_window; #define ANDROIDCONTROLTYPE ((ULONG)'A') #define ANDROID_IOCTL(n) CTL_CODE(ANDROIDCONTROLTYPE, n, METHOD_BUFFERED, FILE_READ_ACCESS) @@ -132,6 +133,7 @@ struct ioctl_android_create_window struct ioctl_header hdr; int parent; float scale; + bool is_desktop; }; struct ioctl_android_destroy_window @@ -717,15 +719,15 @@ static jobject load_java_method( jmethodID *method, const char *name, const char return object; } -static void create_desktop_window( HWND hwnd ) +static void create_desktop_view(void) { static jmethodID method; jobject object; - if (!(object = load_java_method( &method, "createDesktopWindow", "(I)V" ))) return; + if (!(object = load_java_method( &method, "createDesktopView", "()V" ))) return; wrap_java_call(); - (*jni_env)->CallVoidMethod( jni_env, object, method, HandleToLong( hwnd )); + (*jni_env)->CallVoidMethod( jni_env, object, method ); unwrap_java_call(); } @@ -744,10 +746,10 @@ static NTSTATUS createWindow_ioctl( void *data, DWORD in_size, DWORD out_size, U TRACE( "hwnd %08x opengl %u parent %08x\n", res->hdr.hwnd, res->hdr.opengl, res->parent ); - if (!(object = load_java_method( &method, "createWindow", "(IZIFI)V" ))) return STATUS_NOT_SUPPORTED; + if (!(object = load_java_method( &method, "createWindow", "(IZZIFI)V" ))) return STATUS_NOT_SUPPORTED; wrap_java_call(); - (*jni_env)->CallVoidMethod( jni_env, object, method, res->hdr.hwnd, res->hdr.opengl, res->parent, res->scale, pid ); + (*jni_env)->CallVoidMethod( jni_env, object, method, res->hdr.hwnd, res->is_desktop, res->hdr.opengl, res->parent, res->scale, pid ); unwrap_java_call(); return STATUS_SUCCESS; } @@ -1157,7 +1159,7 @@ NTSTATUS android_java_init( void *arg ) if (!(java_vm = *p_java_vm)) return STATUS_UNSUCCESSFUL; /* not running under Java */ init_java_thread( java_vm ); - create_desktop_window( NtUserGetDesktopWindow() ); + create_desktop_view(); return STATUS_SUCCESS; } @@ -1555,6 +1557,7 @@ struct ANativeWindow *create_ioctl_window( HWND hwnd, BOOL opengl, float scale ) req.hdr.opengl = win->opengl; req.parent = get_ioctl_win_parent( NtUserGetAncestor( hwnd, GA_PARENT )); req.scale = scale; + req.is_desktop = hwnd == desktop_window; android_ioctl( IOCTL_CREATE_WINDOW, &req, sizeof(req), NULL, NULL ); return &win->win; @@ -1648,3 +1651,14 @@ int ioctl_set_cursor( int id, int width, int height, free( req ); return ret; } + +/********************************************************************** + * ANDROID_SetDesktopWindow + */ +void ANDROID_SetDesktopWindow( HWND hwnd ) +{ + if (!is_in_desktop_process()) + return; + ERR( "%p\n", hwnd ); + desktop_window = hwnd; +} diff --git a/dlls/wineandroid.drv/init.c b/dlls/wineandroid.drv/init.c index 9ceacacd51a..229a18cfe3f 100644 --- a/dlls/wineandroid.drv/init.c +++ b/dlls/wineandroid.drv/init.c @@ -309,6 +309,7 @@ static const struct user_driver_funcs android_drv_funcs = .pUpdateDisplayDevices = ANDROID_UpdateDisplayDevices, .pCreateDesktop = ANDROID_CreateDesktop, .pCreateWindow = ANDROID_CreateWindow, + .pSetDesktopWindow = ANDROID_SetDesktopWindow, .pDesktopWindowProc = ANDROID_DesktopWindowProc, .pDestroyWindow = ANDROID_DestroyWindow, .pProcessEvents = ANDROID_ProcessEvents, diff --git a/dlls/wineandroid.drv/window.c b/dlls/wineandroid.drv/window.c index 1dca70a20d3..41439c2f697 100644 --- a/dlls/wineandroid.drv/window.c +++ b/dlls/wineandroid.drv/window.c @@ -975,8 +975,6 @@ BOOL ANDROID_CreateWindow( HWND hwnd ) { struct android_win_data *data; - init_event_queue(); - start_android_device(); if (!(data = alloc_win_data( hwnd ))) return FALSE; release_win_data( data ); } @@ -1240,6 +1238,8 @@ BOOL has_client_surface( HWND hwnd ) */ BOOL ANDROID_CreateDesktop( const WCHAR *name, UINT width, UINT height ) { + init_event_queue(); + start_android_device(); /* wait until we receive the surface changed event */ while (!screen_width) { @@ -1250,5 +1250,5 @@ BOOL ANDROID_CreateDesktop( const WCHAR *name, UINT width, UINT height ) } process_events( QS_ALLINPUT ); } - return 0; + return TRUE; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9874
From: Twaik Yont <9674930+twaik@users.noreply.github.com> `dequeueBuffer()` may return success without initializing the `ANativeWindowBuffer` pointer. In that case the caller would observe either a `NULL` pointer or uninitialized stack garbage and proceed to dereference it, leading to crashes or undefined behavior in the rendering path. Initialize buffer variables to `NULL` and explicitly validate the returned pointer. If `dequeueBuffer` reports success but does not provide a valid buffer, treat it as a failure (`STATUS_UNSUCCESSFUL` / `-EWOULDBLOCK`) and abort processing. This avoids dereferencing uninitialized or stale pointers when the backend fails to populate the output buffer while still reporting success. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/wineandroid.drv/device.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dlls/wineandroid.drv/device.c b/dlls/wineandroid.drv/device.c index 3dce2e7399f..609745e26e0 100644 --- a/dlls/wineandroid.drv/device.c +++ b/dlls/wineandroid.drv/device.c @@ -805,7 +805,7 @@ static NTSTATUS dequeueBuffer_ioctl( void *data, DWORD in_size, DWORD out_size, struct ANativeWindow *parent; struct ioctl_android_dequeueBuffer *res = data; struct native_win_data *win_data; - struct ANativeWindowBuffer *buffer; + struct ANativeWindowBuffer *buffer = NULL; int fence, ret, is_new; if (out_size < sizeof( *res )) return STATUS_BUFFER_OVERFLOW; @@ -824,6 +824,11 @@ static NTSTATUS dequeueBuffer_ioctl( void *data, DWORD in_size, DWORD out_size, { HANDLE mapping = 0; + if (!buffer) { + TRACE( "got invalid buffer\n" ); + return STATUS_UNSUCCESSFUL; + } + TRACE( "%08x got buffer %p fence %d\n", res->hdr.hwnd, buffer, fence ); res->width = buffer->width; res->height = buffer->height; @@ -1255,7 +1260,7 @@ static void buffer_decRef( struct android_native_base_t *base ) static int dequeueBuffer( struct ANativeWindow *window, struct ANativeWindowBuffer **buffer, int *fence ) { struct native_win_wrapper *win = (struct native_win_wrapper *)window; - struct ioctl_android_dequeueBuffer res; + struct ioctl_android_dequeueBuffer res = {0}; DWORD size = sizeof(res); int ret, use_win32 = !gralloc_module && !gralloc1_device; @@ -1469,10 +1474,14 @@ static int perform( ANativeWindow *window, int operation, ... ) } case NATIVE_WINDOW_LOCK: { - struct ANativeWindowBuffer *buffer; + struct ANativeWindowBuffer *buffer = NULL; struct ANativeWindow_Buffer *buffer_ret = va_arg( args, ANativeWindow_Buffer * ); ARect *bounds = va_arg( args, ARect * ); int ret = window->dequeueBuffer_DEPRECATED( window, &buffer ); + if (!ret && !buffer) { + ret = -EWOULDBLOCK; + TRACE( "got invalid buffer\n" ); + } if (!ret) { if ((ret = gralloc_lock( buffer, &buffer_ret->bits ))) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9874
From: Twaik Yont <9674930+twaik@users.noreply.github.com> Wine frequently failed early during startup with: ``` err:virtual:virtual_alloc_first_teb wine: failed to map the shared user data: c0000018 ``` This appears to happen because the JVM can reserve address space that Wine later needs at a fixed address for the shared user data mapping. Initializing the virtual memory subsystem earlier reduces the chance of the JVM taking that range. Move `virtual_init()` from `wine_init_jni()` to `JNI_OnLoad()`, so it runs before other JNI activity during library load. Note that this change does not attempt to handle `virtual_init()` reentrance. When starting from an Activity `onCreate()` this is more likely due to the Android activity lifecycle, while calling it from `JNI_OnLoad()` is much less likely since it is only executed during library/class load. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/ntdll/unix/loader.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 28fc09cd354..31aab7cc314 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1960,7 +1960,6 @@ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jo main_argv = argv; init_paths(); - virtual_init(); init_environment(); #ifdef __i386__ @@ -1988,6 +1987,8 @@ jint JNI_OnLoad( JavaVM *vm, void *reserved ) JNIEnv *env; jclass class; + virtual_init(); + java_vm = vm; if ((*vm)->AttachCurrentThread( vm, &env, NULL ) != JNI_OK) return JNI_ERR; if (!(class = (*env)->FindClass( env, WINE_JAVA_CLASS ))) return JNI_ERR; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9874
On Wed Mar 4 09:28:42 2026 +0000, Twaik Yont wrote:
Seems reasonable, but also it looks like the `pSetDesktopWindow` is being called call in non-explorer.exe processes as well (no only at the start of `NtUserCreateWindowEx`, but in a lot of other places during `get_desktop_window` call) so I will add explicit check for being inside jvm process where it is relevant. Done. Does it look fine?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_131218
Rémi Bernon (@rbernon) commented about dlls/wineandroid.drv/device.c:
free( req ); return ret; } + +/********************************************************************** + * ANDROID_SetDesktopWindow + */ +void ANDROID_SetDesktopWindow( HWND hwnd ) +{ + if (!is_in_desktop_process()) + return; + ERR( "%p\n", hwnd );
```suggestion:-0+0 TRACE( "%p\n", hwnd ); ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_131219
Rémi Bernon (@rbernon) commented about dlls/wineandroid.drv/device.c:
{ HANDLE mapping = 0;
+ if (!buffer) {
```suggestion:-0+0 if (!buffer) { ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_131220
Rémi Bernon (@rbernon) commented about dlls/wineandroid.drv/device.c:
} case NATIVE_WINDOW_LOCK: { - struct ANativeWindowBuffer *buffer; + struct ANativeWindowBuffer *buffer = NULL; struct ANativeWindow_Buffer *buffer_ret = va_arg( args, ANativeWindow_Buffer * ); ARect *bounds = va_arg( args, ARect * ); int ret = window->dequeueBuffer_DEPRECATED( window, &buffer ); + if (!ret && !buffer) {
```suggestion:-0+0 if (!ret && !buffer) { ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_131221
participants (3)
-
Rémi Bernon (@rbernon) -
Twaik Yont -
Twaik Yont (@twaik)