maxrave-dev / maxrave-dev/SimpMusic
Desktop (Linux/XWayland): UI freezes permanently when the window is moved to another monitor
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 11.4k
- Forks
- 599
- Avg merge
- 3d 18h
- Merged PRs (30d)
- 7
Description
Duplicate Check
- I confirm I searched for existing reports and found no duplicates.
Describe the Bug
On Linux desktop (GNOME on Wayland, so the app runs under XWayland) with two monitors, moving the SimpMusic window from one monitor to the other freezes the UI permanently. Audio keeps playing (mpv is on its own native thread) and some clicks still reach the app (they trigger state changes, even open new windows like the lyrics pop-up), but the main window is never repainted again. The only way out is killing the process.
I spent some time digging into it, here is what I found, hoping it saves the maintainers a few hours.
Diagnosis
- Thread dumps (
jcmd <pid> Thread.print) taken while frozen show no deadlock and no busy thread:AWT-EventQueue-0is idle inEventQueue.getNextEvent,AWT-XAWTis idle inXToolkit.waitForEvents, CPU is 0%. The JVM simply never gets asked to draw again. - With
-Dskiko.fps.enabled=truethe FPS counter runs at ~35 fps and stops exactly at the moment the window crosses the monitor boundary. Never resumes. - Under XWayland Java uses Xinerama, so each monitor is a separate
GraphicsDevice(:0.0and:0.1). When the window crosses over,XWindowPeer.checkIfOnNewScreentriggers a display change and the window visibly "closes and reopens" for an instant (peer recreated). Skiko'sSkiaLayergets the hide but apparently never the show again, soredrawer.setVisible(true)/needRedraware never called again. - It is not the render API: it happens both with
LinuxOpenGLRedrawer(default) and with-Dskiko.renderApi=SOFTWARE. It is also not the Java2D pipeline (-Dsun.java2d.xrender=falsechanges nothing).
Workaround that works for me
Make Java see a single screen, so there is no "new screen" event at all. I built a 4-function stub libXinerama.so.1 that reports Xinerama as inactive and put it first in LD_LIBRARY_PATH for the app only:
int XineramaQueryExtension(void *d, int *ev, int *err) { return 0; }
int XineramaQueryVersion(void *d, int *maj, int *min) { return 0; }
int XineramaIsActive(void *d) { return 0; }
void *XineramaQueryScreens(void *d, int *n) { if (n) *n = 0; return 0; }
With that, GraphicsEnvironment.getScreenDevices() returns one 3840x1080 device, the window can be dragged between monitors freely, and the OpenGL redrawer stays at full speed. GTK (tray/file dialogs) is unaffected since it uses RandR.
Side note for anyone trying this with the AppImage/Conveyor build: the Conveyor launcher discards JAVA_TOOL_OPTIONS, and the bundled jlink runtime has no bin/java, so to pass extra JVM flags I had to dump the launcher's arguments with CONVEYOR_DUMP_JVM_ARGS=1 bin/simpmusic and start the JVM directly (copying bin/java from a system JDK 21 into lib/runtime/bin/). It would be nice if the launcher honoured JAVA_TOOL_OPTIONS or an app.jvm.options override, so users could try flags without this dance.
Possible fix on the app side
Either re-arm the Skiko layer after a display change (e.g. listen for graphicsConfiguration property changes on the ComposeWindow and call needRedraw() / re-run checkShowing), or ship the app with Xinerama disabled for AWT when running under XWayland. Happy to test a build.
Steps to Reproduce
- GNOME Wayland session with two monitors (both 1920x1080, scale 1, side by side).
- Start SimpMusic desktop (AppImage 2.1.0) and play something.
- Drag the window to the other monitor.
- UI freezes at the moment the window crosses the boundary; audio continues.
Expected Behavior
The window keeps rendering after being moved to another monitor.
App Version
2.1.0 (desktop, AppImage / Conveyor build, bundled JDK 21.0.2, Skiko 0.148.1)
Android Version
N/A - CachyOS (Arch), kernel 7.2.4, GNOME on Wayland (XWayland for the app)
Device Model
Laptop, AMD Radeon 680M (Mesa), 2 external/internal monitors
Custom ROM
- I am using a custom ROM
Custom ROM Name (if applicable)
No response
Screenshots (optional)
No response
Log / Error Details
Nothing is printed on stderr or in the journal at the moment of the freeze. Relevant thread dump excerpt while frozen:
"AWT-EventQueue-0" ... waiting on condition
java.lang.Thread.State: WAITING (parking)
at java.awt.EventQueue.getNextEvent(EventQueue.java:565)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:190)
"AWT-XAWT" ... runnable
at sun.awt.X11.XToolkit.waitForEvents(Native Method)
at sun.awt.X11.XToolkit.run(XToolkit.java:687)
Skiko FPS log (-Dskiko.fps.enabled=true -Dskiko.fps.periodSeconds=2), last lines before the drag and then silence:
[11930842] FPS 36 (31-38)
[11932845] FPS 34 (32-37)
GraphicsEnvironment.getScreenDevices() on this setup, without / with the stub:
:0.0 java.awt.Rectangle[x=1920,y=0,width=1920,height=1080]
:0.1 java.awt.Rectangle[x=0,y=0,width=1920,height=1080]
---
:0.0 java.awt.Rectangle[x=0,y=0,width=3840,height=1080]
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the desktop ComposeWindow and SkiaLayer integration, focusing on the graphicsConfiguration change when XWayland moves the window between monitors. Reproduce the two-monitor drag with the supplied Skiko flags and thread observations; done means the UI resumes rendering after crossing the monitor boundary without disabling Xinerama.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin, linux
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100