JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.58k stars 1.13k forks source link

`NullPointerException` in `UndecoratedWindowResizer.resize(Int)` #2698

Open YektaDev opened 1 year ago

YektaDev commented 1 year ago

Running Windows 11, I get a java.lang.NullPointerException while trying to drag the right corner of an undecorated window to the very right end of my screen. Here's the stacktrace:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "java.awt.PointerInfo.getLocation()" because the return value of "java.awt.MouseInfo.getPointerInfo()" is null
    at androidx.compose.ui.window.UndecoratedWindowResizer.resize(UndecoratedWindowResizer.desktop.kt:139)
    at androidx.compose.ui.window.UndecoratedWindowResizer.access$resize(UndecoratedWindowResizer.desktop.kt:43)
    at androidx.compose.ui.window.UndecoratedWindowResizer$resizeOnDrag$1$1.invokeSuspend(UndecoratedWindowResizer.desktop.kt:118)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:571)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:459)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:472)
    at androidx.compose.ui.node.BackwardsCompatNode.onPointerEvent-H0pRuoY(BackwardsCompatNode.kt:394)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:289)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:276)
    at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:156)
    at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:93)
    at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:98)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui(SkiaBasedOwner.skiko.kt:382)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui$default(SkiaBasedOwner.skiko.kt:375)
    at androidx.compose.ui.ComposeScene.processMove(ComposeScene.skiko.kt:548)
    at androidx.compose.ui.ComposeScene.processPointerInput(ComposeScene.skiko.kt:496)
    at androidx.compose.ui.ComposeScene.sendAsMove(ComposeScene.skiko.kt:488)
    at androidx.compose.ui.ComposeScene.access$sendAsMove(ComposeScene.skiko.kt:93)
    at androidx.compose.ui.ComposeScene$pointerPositionUpdater$2.invoke(ComposeScene.skiko.kt:219)
    at androidx.compose.ui.ComposeScene$pointerPositionUpdater$2.invoke(ComposeScene.skiko.kt:219)
    at androidx.compose.ui.PointerPositionUpdater.sendAsUpdate(PointerPositionUpdater.skiko.kt:66)
    at androidx.compose.ui.PointerPositionUpdater.update(PointerPositionUpdater.skiko.kt:60)
    at androidx.compose.ui.ComposeScene.render(ComposeScene.skiko.kt:414)
    at androidx.compose.ui.awt.ComposeLayer$1$onRender$1.invoke(ComposeLayer.desktop.kt:326)
    at androidx.compose.ui.awt.ComposeLayer$1$onRender$1.invoke(ComposeLayer.desktop.kt:325)
    at androidx.compose.ui.awt.ComposeLayer.catchExceptions(ComposeLayer.desktop.kt:109)
    at androidx.compose.ui.awt.ComposeLayer.access$catchExceptions(ComposeLayer.desktop.kt:87)
    at androidx.compose.ui.awt.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:325)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:525)
    at org.jetbrains.skiko.redrawer.AWTRedrawer.update(AWTRedrawer.kt:54)
    at org.jetbrains.skiko.redrawer.WindowsOpenGLRedrawer.redrawImmediately(WindowsOpenGLRedrawer.kt:60)
    at org.jetbrains.skiko.SkiaLayer.paint(SkiaLayer.awt.kt:367)
    at androidx.compose.ui.awt.ComposeLayer$ComponentImpl.paint(ComposeLayer.desktop.kt:242)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
    at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
    at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
    at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5318)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedFPScales(RepaintManager.java:1721)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1630)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1570)
    at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1337)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1105)
    at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
    at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
    at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
    at java.desktop/java.awt.Container.paint(Container.java:2005)
    at java.desktop/java.awt.Window.paint(Window.java:3959)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:890)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:862)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:862)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:835)
    at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:784)
    at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1898)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
    Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.awt.ComposeLayer$coroutineExceptionHandler$1@2b87a29d, androidx.compose.runtime.BroadcastFrameClock@5fb98bd9, StandaloneCoroutine{Cancelling}@1524a58d, FlushCoroutineDispatcher@146ccfdf]

I checked the source and the solution seems to be:

// Class: androidx.compose.ui.window.UndecoratedWindowResizer
    private fun resize(sides: Int) {
        val pointPos = MouseInfo.getPointerInfo().location
        val diffX = pointPos.x - initialPointPos.x
        val diffY = pointPos.y - initialPointPos.y
        // ...
    }

A null check at MouseInfo.getPointerInfo().location is likely to solve the issue.

eymar commented 1 year ago

Could you please share what Compose version did you use? And a small reproducing sample if you have it handy?

I tried Compose 1.3.0 (macOs for now, I'll try on windows 11 later):

fun main() = application {
    Window(onCloseRequest = ::exitApplication, undecorated = true, resizable = true) {
    //...
    }
}

It didn't crash when I tried to drag the corners of a window.

YektaDev commented 1 year ago

I didn't have a bare minimum reproducible code, so I created an empty Compose project, updated the dependencies, and tried to reproduce the error. It didn't happen the first time, but after some trial and error, I was able to find the condition to reprouduce it and can confirm it happens with:

import androidx.compose.material.Text
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

fun main() = application {
    Window(onCloseRequest = ::exitApplication, undecorated = true, resizable = true) {
        Text("Hello, NPE!")
    }
}

Kotlin 1.8.0 Compose 1.3.0 JVM Target 17 Windows 11 21H2 (build 22000.1455)

The Condition:

This only happens when I have a secondary monitor connected (it's smaller than the primary monitor in pixels). When the app is inside the primary (bigger) monitor and I try to drag the right corner to the very end of it, the exception happenes.

YektaDev commented 1 year ago

Since I posted the reproducer alonside the condition + all details to reproduce, shall we change the labels?

okushnikov commented 2 weeks ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.