[PATCH 0/1] MR9874: Draft: wineandroid.drv: experimental bring-up fixes for Android 7.1.x (discussion starter)
#### 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. It is primarily intended as a discussion starter rather than a final or polished solution. 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. At the moment, all changes are provided as a single patch. This is intentional: until at least one of the proposed changes is confirmed as a valid or acceptable fix, the patchset is kept consolidated to simplify iteration. Once individual changes are reviewed or agreed upon, the history can be rewritten (via force-push) to split them into logical, self-contained commits. #### Test environment and context All testing so far was performed on Android 7.1.2 x86 images from the Android-x86 project. 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. `virtual_alloc_first_teb` failure Wine frequently failed early during startup with: ``` err:virtual:virtual_alloc_first_teb wine: failed to map the shared user data: c0000018 ``` This failure was not observed on older Wine versions. It appears to be related to JNI interaction occurring too early during initialization, interfering with allocation of the shared user data region at the required fixed address. This was resolved by moving the call to `virtual_init()` earlier in `wine_init_jni()`, before JNI-related activity begins. 2. Missing exported JNI symbols The symbols `java_vm`, `java_object`, and `java_gdt_sel` were no longer visible when accessed via `dlopen`/`dlsym`. This appears to be a consequence of newer toolchain and linker behavior. The issue was resolved by explicitly exporting these symbols using `DECLSPEC_EXPORT`. 3. Desktop thread assertion during startup Wine aborted with the following assertion: ``` dlls/wineandroid.drv/window.c:519: int wait_events(int): assertion "GetCurrentThreadId() == desktop_tid" failed ``` The failure happens because `pCreateDesktop()` is now invoked before `pCreateWindow()`, while the existing code expected `desktop_tid` to be set during `pCreateWindow()`. As a result, the event waiting path may execute before `desktop_tid` is initialized. This was addressed by moving `init_event_queue()` and `start_android_device()` from `pCreateWindow()` into `pCreateDesktop()`, ensuring the event queue and device startup occur before `wait_events()` is used. 4. Recursive desktop recreation The change described above introduced a new problem: `create_desktop_window()` could be triggered repeatedly. Each Java-side content window creation/layout initialization caused additional resolution/configuration change notifications, which in turn could cause repeated desktop window recreation. This was mitigated by changing the Java/native glue so that when a window/view is being created, the code explicitly checks whether the target is the desktop window and passes this information to Java as a flag. This avoids triggering repeated content-window initialization paths during desktop recreation. 5. WineAndroid device path change Opening the device using `\\.\WineAndroid` no longer works. Switching to `\Device\WineAndroid` resolved the issue. The fix itself is simple but required for basic functionality. 6. Rendering stalls and USER lock assertion Rendering occasionally stalled or crashed with: ``` err:system:user_check_not_lock BUG: holding USER lock ``` Adding TRACE statements often caused the issue to disappear, making it difficult to debug and reproduce reliably. After extensive experimentation, it was discovered that inserting a full memory barrier: ``` __sync_synchronize(); ``` at a specific point makes the issue reliably disappear in the tested setup. This suggests a race or memory visibility/ordering problem. At this time it is unclear whether the root cause is an internal behavioral change in Wine, NDK toolchain/codegen changes, or some other interaction specific to the Android 7.1.x environment. The current change should be treated as a workaround and a starting point for further investigation rather than a definitive fix. #### 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.** -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874
From: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/ntdll/unix/loader.c | 9 ++++--- dlls/wineandroid.drv/WineActivity.java | 35 ++++++++++++++------------ dlls/wineandroid.drv/device.c | 21 +++++++++------- dlls/wineandroid.drv/dllmain.c | 2 +- dlls/wineandroid.drv/window.c | 6 ++--- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index cb700dd80e8..953030829d7 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1932,9 +1932,9 @@ static void start_main_thread(void) #define WINE_JAVA_CLASS "org/winehq/wine/WineActivity" #endif -JavaVM *java_vm = NULL; -jobject java_object = 0; -unsigned short java_gdt_sel = 0; +DECLSPEC_EXPORT JavaVM *java_vm = NULL; +DECLSPEC_EXPORT jobject java_object = 0; +DECLSPEC_EXPORT unsigned short java_gdt_sel = 0; /* main Wine initialisation */ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jobjectArray environment ) @@ -1944,6 +1944,8 @@ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jo char error[1024]; int i, argc, length; + virtual_init(); + /* get the command line array */ argc = (*env)->GetArrayLength( env, cmdline ); @@ -2011,7 +2013,6 @@ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jo main_argv = argv; init_paths(); - virtual_init(); init_environment(); #ifdef __i386__ diff --git a/dlls/wineandroid.drv/WineActivity.java b/dlls/wineandroid.drv/WineActivity.java index a47a4f89290..ad741f50231 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; @@ -350,9 +351,9 @@ protected class WineWindow protected WineWindowGroup window_group; protected WineWindowGroup client_group; - public WineWindow( int w, WineWindow parent, float scale ) + public WineWindow( int w, WineWindow parent, float scale, boolean is_desktop ) { - Log.i( LOGTAG, String.format( "create hwnd %08x", w )); + Log.i( LOGTAG, String.format( "create hwnd %08x is_desktop %b parent %08x", w, is_desktop, parent != null ? parent.hwnd : 0 )); hwnd = w; owner = 0; style = 0; @@ -761,14 +762,11 @@ 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 = new WineWindow( WineWindow.HWND_MESSAGE, null, 1.0f, false ); message_window.create_window_groups(); } @@ -793,22 +791,27 @@ protected WineWindow get_window( int hwnd ) // Entry points for the device driver - public void create_desktop_window( int hwnd ) + public void create_desktop_window() { - 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, is_desktop ); 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 createDesktopWindow() { - runOnUiThread( new Runnable() { public void run() { create_desktop_window( hwnd ); }} ); + runOnUiThread( new Runnable() { public void run() { create_desktop_window(); }} ); } - 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/device.c b/dlls/wineandroid.drv/device.c index 1c7985ca1a5..6098cfe1fbb 100644 --- a/dlls/wineandroid.drv/device.c +++ b/dlls/wineandroid.drv/device.c @@ -132,6 +132,7 @@ struct ioctl_android_create_window struct ioctl_header hdr; int parent; float scale; + bool is_desktop; }; struct ioctl_android_destroy_window @@ -717,15 +718,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_window(void) { static jmethodID method; jobject object; - if (!(object = load_java_method( &method, "createDesktopWindow", "(I)V" ))) return; + if (!(object = load_java_method( &method, "createDesktopWindow", "()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 +745,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 +1158,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_window(); return STATUS_SUCCESS; } @@ -1188,7 +1189,7 @@ void start_android_device(void) static int android_ioctl( enum android_ioctl code, void *in, DWORD in_size, void *out, DWORD *out_size ) { - static const WCHAR deviceW[] = {'\\','\\','.','\\','W','i','n','e','A','n','d','r','o','i','d',0 }; + static const WCHAR deviceW[] = {'\\','D','e','v','i','c','e','\\','W','i','n','e','A','n','d','r','o','i','d',0 }; static HANDLE device; IO_STATUS_BLOCK iosb; NTSTATUS status; @@ -1254,9 +1255,9 @@ 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; + int ret = 0, use_win32 = !gralloc_module && !gralloc1_device; res.hdr.hwnd = HandleToLong( win->hwnd ); res.hdr.opengl = win->opengl; @@ -1351,6 +1352,7 @@ static int queueBuffer( struct ANativeWindow *window, struct ANativeWindowBuffer static int dequeueBuffer_DEPRECATED( struct ANativeWindow *window, struct ANativeWindowBuffer **buffer ) { int fence, ret = dequeueBuffer( window, buffer, &fence ); + __sync_synchronize(); if (!ret) wait_fence_and_close( fence ); return ret; @@ -1556,6 +1558,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 == NtUserGetDesktopWindow(); android_ioctl( IOCTL_CREATE_WINDOW, &req, sizeof(req), NULL, NULL ); return &win->win; diff --git a/dlls/wineandroid.drv/dllmain.c b/dlls/wineandroid.drv/dllmain.c index c8495d679f9..d8f3346954c 100644 --- a/dlls/wineandroid.drv/dllmain.c +++ b/dlls/wineandroid.drv/dllmain.c @@ -123,6 +123,6 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, LPVOID reserved ) if (__wine_init_unix_call()) return FALSE; params.register_window_callback = register_window_callback; - params.start_device_callback = android_start_device; + params.start_device_callback = (UINT64) android_start_device; return !ANDROID_CALL( init, ¶ms ); } 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
Can you split the changes into separate Git commits? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_126613
On Sun Jan 11 19:09:54 2026 +0000, Aida Jonikienė wrote:
Can you split the changes into separate Git commits? I can split the changes into separate commits, but I would appreciate initial feedback on the individual changes before doing so. I am not fully confident that all fixes are implemented in the best way, as additional issues appeared during testing.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_126614
On Sun Jan 11 19:16:07 2026 +0000, Twaik Yont wrote:
I can split the changes into separate commits, but I would appreciate initial feedback on the individual changes before doing so. I am not fully confident that all fixes are implemented in the best way, as additional issues appeared during testing. @twaik wineandroid has not been maintained for a long while, the other issues should make sense, I think this MR is good enough for now, and doing things one step at a time is the way to go.
And by a long time, I mean at least a few years. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_126625
It seems the latest updates have unfortunately broken the WineAndroid build more severely than expected. To make reviewing easier/faster, I'll split out the straightforward commits into a separate merge request. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_128021
On Mon Jan 12 09:43:09 2026 +0000, Loïc Rebmeister wrote:
@twaik wineandroid has not been maintained for a long while, the other issues should make sense, I think this MR is good enough for now, and doing things one step at a time is the way to go. And by a long time, I mean at least a few years. I am not fully comfortable calling the MR “good enough” in its current form. For example, the desktop window should not be continuously recreated, and some of my changes are hiding an underlying issue rather than fixing it properly. My impression is that something changed in Wine over time, and in parallel there were compensating changes in winex11, while wineandroid was left behind.
I was hoping to get pointers to specific changes that may have triggered these regressions, so I could implement proper compensating fixes on the wineandroid side. Doing a full bisect in this environment is realistically very difficult and time-consuming, and likely impractical for me right now. This concern applies to the overall set of issues addressed in the MR, not only the desktop window recreation. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9874#note_128874
participants (4)
-
Aida Jonikienė (@DodoGTA) -
Loïc Rebmeister (@Fox2Code) -
Twaik Yont -
Twaik Yont (@twaik)