summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r--src/plugins/platforms/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/android/CMakeLists.txt35
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.cpp114
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.h21
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.cpp35
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.h4
-rw-r--r--src/plugins/platforms/android/androidjniclipboard.cpp96
-rw-r--r--src/plugins/platforms/android/androidjniclipboard.h27
-rw-r--r--src/plugins/platforms/android/androidjniinput.cpp335
-rw-r--r--src/plugins/platforms/android/androidjniinput.h27
-rw-r--r--src/plugins/platforms/android/androidjnimain.cpp449
-rw-r--r--src/plugins/platforms/android/androidjnimain.h25
-rw-r--r--src/plugins/platforms/android/androidjnimenu.cpp60
-rw-r--r--src/plugins/platforms/android/androidjnimenu.h5
-rw-r--r--src/plugins/platforms/android/androidsurfaceclient.h24
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.cpp70
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.h41
-rw-r--r--src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp53
-rw-r--r--src/plugins/platforms/android/qandroidassetsfileenginehandler.h3
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.cpp152
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.h6
-rw-r--r--src/plugins/platforms/android/qandroidplatformaccessibility.cpp2
-rw-r--r--src/plugins/platforms/android/qandroidplatformbackingstore.cpp52
-rw-r--r--src/plugins/platforms/android/qandroidplatformbackingstore.h29
-rw-r--r--src/plugins/platforms/android/qandroidplatformclipboard.cpp83
-rw-r--r--src/plugins/platforms/android/qandroidplatformclipboard.h16
-rw-r--r--src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp16
-rw-r--r--src/plugins/platforms/android/qandroidplatformdialoghelpers.h5
-rw-r--r--src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp19
-rw-r--r--src/plugins/platforms/android/qandroidplatformfontdatabase.cpp33
-rw-r--r--src/plugins/platforms/android/qandroidplatformforeignwindow.cpp89
-rw-r--r--src/plugins/platforms/android/qandroidplatformforeignwindow.h14
-rw-r--r--src/plugins/platforms/android/qandroidplatformiconengine.cpp616
-rw-r--r--src/plugins/platforms/android/qandroidplatformiconengine.h44
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.cpp96
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.h6
-rw-r--r--src/plugins/platforms/android/qandroidplatformmenu.cpp2
-rw-r--r--src/plugins/platforms/android/qandroidplatformopenglwindow.cpp72
-rw-r--r--src/plugins/platforms/android/qandroidplatformopenglwindow.h9
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.cpp345
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.h43
-rw-r--r--src/plugins/platforms/android/qandroidplatformservices.cpp61
-rw-r--r--src/plugins/platforms/android/qandroidplatformtheme.cpp38
-rw-r--r--src/plugins/platforms/android/qandroidplatformtheme.h9
-rw-r--r--src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp50
-rw-r--r--src/plugins/platforms/android/qandroidplatformvulkanwindow.h11
-rw-r--r--src/plugins/platforms/android/qandroidplatformwindow.cpp268
-rw-r--r--src/plugins/platforms/android/qandroidplatformwindow.h68
-rw-r--r--src/plugins/platforms/android/qandroidsystemlocale.cpp45
-rw-r--r--src/plugins/platforms/android/qandroidsystemlocale.h3
-rw-r--r--src/plugins/platforms/cocoa/CMakeLists.txt18
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibility.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm502
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm101
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.mm134
-rw-r--r--src/plugins/platforms/cocoa/qcocoaclipboard.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.mm28
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.h19
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm62
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm358
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h14
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.mm10
-rw-r--r--src/plugins/platforms/cocoa/qcocoainputcontext.mm11
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.h12
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm78
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm32
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.mm172
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.mm108
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuloader.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.mm164
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.h15
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.mm52
-rw-r--r--src/plugins/platforms/cocoa/qcocoansmenu.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm119
-rw-r--r--src/plugins/platforms/cocoa/qcocoaservices.h10
-rw-r--r--src/plugins/platforms/cocoa/qcocoaservices.mm50
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm19
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.h6
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm57
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h36
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm347
-rw-r--r--src/plugins/platforms/cocoa/qmacclipboard.h6
-rw-r--r--src/plugins/platforms/cocoa/qmacclipboard.mm4
-rw-r--r--src/plugins/platforms/cocoa/qnsview.h6
-rw-r--r--src/plugins/platforms/cocoa/qnsview.mm54
-rw-r--r--src/plugins/platforms/cocoa/qnsview_accessibility.mm14
-rw-r--r--src/plugins/platforms/cocoa/qnsview_complextext.mm66
-rw-r--r--src/plugins/platforms/cocoa/qnsview_dragging.mm4
-rw-r--r--src/plugins/platforms/cocoa/qnsview_drawing.mm41
-rw-r--r--src/plugins/platforms/cocoa/qnsview_keys.mm44
-rw-r--r--src/plugins/platforms/cocoa/qnsview_menus.mm101
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm41
-rw-r--r--src/plugins/platforms/cocoa/qnsview_touch.mm20
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.h2
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm78
-rw-r--r--src/plugins/platforms/cocoa/qnswindowdelegate.mm4
-rw-r--r--src/plugins/platforms/direct2d/CMakeLists.txt35
-rw-r--r--src/plugins/platforms/directfb/CMakeLists.txt15
-rw-r--r--src/plugins/platforms/directfb/qdirectfbblitter.cpp2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbconvenience.h2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbinput.cpp2
-rw-r--r--src/plugins/platforms/eglfs/CMakeLists.txt39
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfscursor.cpp18
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp6
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsintegration.cpp11
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsintegration_p.h2
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsscreen.cpp2
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfswindow.cpp2
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfswindow_p.h4
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp38
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp62
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h3
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt3
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt6
-rw-r--r--src/plugins/platforms/haiku/qhaikuintegration.cpp4
-rw-r--r--src/plugins/platforms/haiku/qhaikuwindow.cpp2
-rw-r--r--src/plugins/platforms/ios/CMakeLists.txt43
-rw-r--r--src/plugins/platforms/ios/optional/CMakeLists.txt2
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h4
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm41
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h9
-rw-r--r--src/plugins/platforms/ios/qiosapplicationdelegate.mm45
-rw-r--r--src/plugins/platforms/ios/qiosclipboard.mm51
-rw-r--r--src/plugins/platforms/ios/qioscolordialog.mm4
-rw-r--r--src/plugins/platforms/ios/qioscontext.mm2
-rw-r--r--src/plugins/platforms/ios/qiosdocumentpickercontroller.mm8
-rw-r--r--src/plugins/platforms/ios/qiosfiledialog.h1
-rw-r--r--src/plugins/platforms/ios/qiosfiledialog.mm59
-rw-r--r--src/plugins/platforms/ios/qiosfontdialog.mm4
-rw-r--r--src/plugins/platforms/ios/qiosglobal.h9
-rw-r--r--src/plugins/platforms/ios/qiosglobal.mm53
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm29
-rw-r--r--src/plugins/platforms/ios/qiosintegration.h15
-rw-r--r--src/plugins/platforms/ios/qiosintegration.mm39
-rw-r--r--src/plugins/platforms/ios/qiosmenu.h2
-rw-r--r--src/plugins/platforms/ios/qiosmenu.mm2
-rw-r--r--src/plugins/platforms/ios/qiosmessagedialog.mm32
-rw-r--r--src/plugins/platforms/ios/qiosplatformaccessibility.mm30
-rw-r--r--src/plugins/platforms/ios/qiosscreen.h21
-rw-r--r--src/plugins/platforms/ios/qiosscreen.mm265
-rw-r--r--src/plugins/platforms/ios/qiostextinputoverlay.mm31
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.mm22
-rw-r--r--src/plugins/platforms/ios/qiostheme.h5
-rw-r--r--src/plugins/platforms/ios/qiostheme.mm61
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.h4
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.mm115
-rw-r--r--src/plugins/platforms/ios/qioswindow.h14
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm237
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.h2
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.mm17
-rw-r--r--src/plugins/platforms/ios/quiview.h3
-rw-r--r--src/plugins/platforms/ios/quiview.mm55
-rw-r--r--src/plugins/platforms/ios/quiview_accessibility.mm1
-rw-r--r--src/plugins/platforms/ios/quiwindow.h13
-rw-r--r--src/plugins/platforms/ios/quiwindow.mm56
-rw-r--r--src/plugins/platforms/linuxfb/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/minimal/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/minimalegl/CMakeLists.txt13
-rw-r--r--src/plugins/platforms/offscreen/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/offscreen/qoffscreenwindow.cpp4
-rw-r--r--src/plugins/platforms/qnx/CMakeLists.txt16
-rw-r--r--src/plugins/platforms/qnx/qqnxabstractnavigator.cpp10
-rw-r--r--src/plugins/platforms/qnx/qqnxabstractnavigator.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxbuffer.cpp24
-rw-r--r--src/plugins/platforms/qnx/qqnxbuffer.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp28
-rw-r--r--src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h6
-rw-r--r--src/plugins/platforms/qnx/qqnxclipboard.cpp24
-rw-r--r--src/plugins/platforms/qnx/qqnxclipboard.h2
-rw-r--r--src/plugins/platforms/qnx/qqnxcursor.cpp12
-rw-r--r--src/plugins/platforms/qnx/qqnxcursor.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxeglwindow.cpp12
-rw-r--r--src/plugins/platforms/qnx/qqnxeglwindow.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxglcontext.cpp12
-rw-r--r--src/plugins/platforms/qnx/qqnxglobal.cpp2
-rw-r--r--src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp116
-rw-r--r--src/plugins/platforms/qnx/qqnxinputcontext_noimf.cpp22
-rw-r--r--src/plugins/platforms/qnx/qqnxintegration.cpp56
-rw-r--r--src/plugins/platforms/qnx/qqnxintegration.h8
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp20
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventhandler.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp31
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatorpps.cpp25
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatorpps.h6
-rw-r--r--src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp20
-rw-r--r--src/plugins/platforms/qnx/qqnxrasterwindow.cpp14
-rw-r--r--src/plugins/platforms/qnx/qqnxscreen.cpp58
-rw-r--r--src/plugins/platforms/qnx/qqnxscreen.h4
-rw-r--r--src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp53
-rw-r--r--src/plugins/platforms/qnx/qqnxscreeneventhandler.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxscreeneventthread.cpp14
-rw-r--r--src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp24
-rw-r--r--src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.h2
-rw-r--r--src/plugins/platforms/qnx/qqnxwindow.cpp58
-rw-r--r--src/plugins/platforms/qnx/qqnxwindow.h3
-rw-r--r--src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp2
-rw-r--r--src/plugins/platforms/vnc/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/wasm/main.cpp4
-rw-r--r--src/plugins/platforms/wasm/qtloader.js827
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp138
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.h9
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.cpp34
-rw-r--r--src/plugins/platforms/wasm/qwasmbase64iconstore.h1
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp68
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.h11
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp344
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.h72
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.cpp80
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.cpp32
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.cpp274
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.h34
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.cpp291
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.h47
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.cpp184
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.h74
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.cpp24
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.cpp339
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.h46
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.cpp418
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.h27
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp98
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h8
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp177
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.h20
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.cpp295
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.h34
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.cpp24
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.h11
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.cpp133
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.h25
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.cpp5
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.h2
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp122
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h29
-rw-r--r--src/plugins/platforms/wasm/qwasmservices.cpp2
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp375
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h59
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.cpp182
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.h10
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp98
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.h21
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.cpp142
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.h23
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.cpp108
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.h53
-rw-r--r--src/plugins/platforms/wasm/wasm_shell.html74
-rw-r--r--src/plugins/platforms/windows/CMakeLists.txt35
-rw-r--r--src/plugins/platforms/windows/qtwindowsglobal.h4
-rw-r--r--src/plugins/platforms/windows/qwindowsapplication.cpp81
-rw-r--r--src/plugins/platforms/windows/qwindowsapplication.h3
-rw-r--r--src/plugins/platforms/windows/qwindowsbackingstore.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowscombase.h80
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.cpp78
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.h8
-rw-r--r--src/plugins/platforms/windows/qwindowscursor.cpp27
-rw-r--r--src/plugins/platforms/windows/qwindowsdialoghelpers.cpp54
-rw-r--r--src/plugins/platforms/windows/qwindowsdrag.cpp17
-rw-r--r--src/plugins/platforms/windows/qwindowsdrag.h6
-rw-r--r--src/plugins/platforms/windows/qwindowsdropdataobject.cpp6
-rw-r--r--src/plugins/platforms/windows/qwindowsglcontext.cpp1
-rw-r--r--src/plugins/platforms/windows/qwindowsiconengine.cpp394
-rw-r--r--src/plugins/platforms/windows/qwindowsiconengine.h47
-rw-r--r--src/plugins/platforms/windows/qwindowsinputcontext.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowsintegration.cpp90
-rw-r--r--src/plugins/platforms/windows/qwindowsintegration.h5
-rw-r--r--src/plugins/platforms/windows/qwindowskeymapper.cpp72
-rw-r--r--src/plugins/platforms/windows/qwindowskeymapper.h8
-rw-r--r--src/plugins/platforms/windows/qwindowsmenu.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowsmenu.h1
-rw-r--r--src/plugins/platforms/windows/qwindowsmimeregistry.cpp11
-rw-r--r--src/plugins/platforms/windows/qwindowsmousehandler.cpp19
-rw-r--r--src/plugins/platforms/windows/qwindowsole.h6
-rw-r--r--src/plugins/platforms/windows/qwindowspointerhandler.cpp41
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.cpp235
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.h15
-rw-r--r--src/plugins/platforms/windows/qwindowsservices.cpp8
-rw-r--r--src/plugins/platforms/windows/qwindowssystemtrayicon.cpp59
-rw-r--r--src/plugins/platforms/windows/qwindowssystemtrayicon.h2
-rw-r--r--src/plugins/platforms/windows/qwindowstabletsupport.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowstheme.cpp460
-rw-r--r--src/plugins/platforms/windows/qwindowstheme.h17
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp402
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.h9
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp26
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h1
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h4
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp132
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h9
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp38
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp168
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h21
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp21
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h19
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp53
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h4
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp74
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h70
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h3
-rw-r--r--src/plugins/platforms/xcb/CMakeLists.txt36
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt2
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp5
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h3
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp17
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt3
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp7
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp11
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp2
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.cpp32
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.h344
-rw-r--r--src/plugins/platforms/xcb/qxcbbackingstore.cpp8
-rw-r--r--src/plugins/platforms/xcb/qxcbclipboard.cpp50
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.cpp43
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.h3
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_screens.cpp24
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_xi2.cpp199
-rw-r--r--src/plugins/platforms/xcb/qxcbcursor.cpp8
-rw-r--r--src/plugins/platforms/xcb/qxcbcursor.h2
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.cpp81
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.h3
-rw-r--r--src/plugins/platforms/xcb/qxcbeventqueue.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbimage.h1
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.cpp50
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.h5
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.cpp14
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.h8
-rw-r--r--src/plugins/platforms/xcb/qxcbmime.cpp35
-rw-r--r--src/plugins/platforms/xcb/qxcbnativeinterface.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.cpp16
-rw-r--r--src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.cpp533
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.h14
-rw-r--r--src/plugins/platforms/xcb/qxcbwmsupport.cpp6
-rw-r--r--src/plugins/platforms/xcb/qxcbxsettings.cpp2
370 files changed, 11912 insertions, 7460 deletions
diff --git a/src/plugins/platforms/CMakeLists.txt b/src/plugins/platforms/CMakeLists.txt
index dfbec51877..69071a22c2 100644
--- a/src/plugins/platforms/CMakeLists.txt
+++ b/src/plugins/platforms/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from platforms.pro.
-
if(ANDROID)
add_subdirectory(android)
endif()
@@ -44,16 +42,16 @@ if(QT_FEATURE_vnc AND TARGET Qt::Network)
add_subdirectory(vnc)
endif()
if(FREEBSD)
- # add_subdirectory(bsdfb) # special case TODO
+ # add_subdirectory(bsdfb) # TODO: QTBUG-112768
endif()
if(HAIKU)
- # add_subdirectory(haiku) # special case TODO
+ # add_subdirectory(haiku) # TODO: QTBUG-112768
endif()
if(WASM)
add_subdirectory(wasm)
endif()
if(QT_FEATURE_integrityfb)
- # add_subdirectory(integrity) # special case TODO
+ # add_subdirectory(integrity) # TODO: QTBUG-112768
endif()
if(QT_FEATURE_vkkhrdisplay)
add_subdirectory(vkkhrdisplay)
diff --git a/src/plugins/platforms/android/CMakeLists.txt b/src/plugins/platforms/android/CMakeLists.txt
index c3deff182e..d5a275a76c 100644
--- a/src/plugins/platforms/android/CMakeLists.txt
+++ b/src/plugins/platforms/android/CMakeLists.txt
@@ -1,37 +1,33 @@
-# Copyright (C) 2022 The Qt Company Ltd.
+# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from android.pro.
-
#####################################################################
## QAndroidIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
qt_internal_add_plugin(QAndroidIntegrationPlugin
OUTPUT_NAME qtforandroid
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES android # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES android
SOURCES
androidcontentfileengine.cpp androidcontentfileengine.h
androiddeadlockprotector.h
androidjniaccessibility.cpp androidjniaccessibility.h
- androidjniclipboard.cpp androidjniclipboard.h
androidjniinput.cpp androidjniinput.h
androidjnimain.cpp androidjnimain.h
androidjnimenu.cpp androidjnimenu.h
- androidsurfaceclient.h
main.cpp
qandroidassetsfileenginehandler.cpp qandroidassetsfileenginehandler.h
qandroideventdispatcher.cpp qandroideventdispatcher.h
qandroidinputcontext.cpp qandroidinputcontext.h
qandroidplatformaccessibility.cpp qandroidplatformaccessibility.h
- qandroidplatformbackingstore.cpp qandroidplatformbackingstore.h
qandroidplatformclipboard.cpp qandroidplatformclipboard.h
qandroidplatformdialoghelpers.cpp qandroidplatformdialoghelpers.h
qandroidplatformfiledialoghelper.cpp qandroidplatformfiledialoghelper.h
qandroidplatformfontdatabase.cpp qandroidplatformfontdatabase.h
qandroidplatformforeignwindow.cpp qandroidplatformforeignwindow.h
+ qandroidplatformiconengine.cpp qandroidplatformiconengine.h
qandroidplatformintegration.cpp qandroidplatformintegration.h
qandroidplatformmenu.cpp qandroidplatformmenu.h
qandroidplatformmenubar.cpp qandroidplatformmenubar.h
@@ -44,8 +40,19 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
qandroidplatformtheme.cpp qandroidplatformtheme.h
qandroidplatformwindow.cpp qandroidplatformwindow.h
qandroidsystemlocale.cpp qandroidsystemlocale.h
- DEFINES
- QT_USE_QSTRINGBUILDER
+ androidwindowembedding.cpp androidwindowembedding.h
+ NO_UNITY_BUILD_SOURCES
+ # Conflicting symbols and macros with androidjnimain.cpp
+ # TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar
+ # macros. Q_JNI_FIND_AND_CHECK_CLASS in `qjnihelpers_p.h`
+ # seems to be doing most of the work already.
+ androidjnimenu.cpp
+ qandroidinputcontext.cpp
+ androidjniaccessibility.cpp
+ qandroidplatformdialoghelpers.cpp
+ # Conflicting JNI classes, and types
+ androidcontentfileengine.cpp
+ qandroidplatformintegration.cpp
INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}
${QtBase_SOURCE_DIR}/src/3rdparty/android
@@ -57,12 +64,9 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
Qt::GuiPrivate
android
jnigraphics
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 1:.:.:android.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/android.json"
-
## Scopes:
#####################################################################
@@ -80,4 +84,7 @@ qt_internal_extend_target(QAndroidIntegrationPlugin CONDITION QT_FEATURE_vulkan
SOURCES
qandroidplatformvulkaninstance.cpp qandroidplatformvulkaninstance.h
qandroidplatformvulkanwindow.cpp qandroidplatformvulkanwindow.h
+ NO_UNITY_BUILD_SOURCES
+ # To avoid undefined symbols due to missing VK_USE_PLATFORM_ANDROID_KHR
+ qandroidplatformvulkaninstance.cpp qandroidplatformvulkanwindow.cpp
)
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 2be7c45bb2..db6c601f33 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -11,14 +11,16 @@
#include <QtCore/qdatetime.h>
#include <QtCore/qmimedatabase.h>
+QT_BEGIN_NAMESPACE
+
using namespace QNativeInterface;
using namespace Qt::StringLiterals;
-Q_DECLARE_JNI_TYPE(ContentResolverType, "Landroid/content/ContentResolver;");
-Q_DECLARE_JNI_TYPE(UriType, "Landroid/net/Uri;");
+Q_DECLARE_JNI_CLASS(ContentResolverType, "android/content/ContentResolver");
+Q_DECLARE_JNI_CLASS(UriType, "android/net/Uri");
Q_DECLARE_JNI_CLASS(Uri, "android/net/Uri");
-Q_DECLARE_JNI_TYPE(ParcelFileDescriptorType, "Landroid/os/ParcelFileDescriptor;");
-Q_DECLARE_JNI_TYPE(CursorType, "Landroid/database/Cursor;");
+Q_DECLARE_JNI_CLASS(ParcelFileDescriptorType, "android/os/ParcelFileDescriptor");
+Q_DECLARE_JNI_CLASS(CursorType, "android/database/Cursor");
Q_DECLARE_JNI_TYPE(StringArray, "[Ljava/lang/String;");
static QJniObject &contentResolverInstance()
@@ -192,10 +194,10 @@ QByteArray AndroidContentFileEngine::id() const
return m_documentFile->id().toUtf8();
}
-QDateTime AndroidContentFileEngine::fileTime(FileTime time) const
+QDateTime AndroidContentFileEngine::fileTime(QFile::FileTime time) const
{
switch (time) {
- case FileTime::ModificationTime:
+ case QFile::FileModificationTime:
return m_documentFile->lastModified();
break;
default:
@@ -246,47 +248,37 @@ QString AndroidContentFileEngine::fileName(FileName f) const
return QString();
}
-QAbstractFileEngine::Iterator *AndroidContentFileEngine::beginEntryList(QDir::Filters filters,
- const QStringList &filterNames)
-{
- return new AndroidContentFileEngineIterator(filters, filterNames);
-}
-
-QAbstractFileEngine::Iterator *AndroidContentFileEngine::endEntryList()
+QAbstractFileEngine::IteratorUniquePtr
+AndroidContentFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
- return nullptr;
+ return std::make_unique<AndroidContentFileEngineIterator>(path, filters, filterNames);
}
AndroidContentFileEngineHandler::AndroidContentFileEngineHandler() = default;
AndroidContentFileEngineHandler::~AndroidContentFileEngineHandler() = default;
-QAbstractFileEngine* AndroidContentFileEngineHandler::create(const QString &fileName) const
+std::unique_ptr<QAbstractFileEngine>
+AndroidContentFileEngineHandler::create(const QString &fileName) const
{
- if (!fileName.startsWith("content"_L1))
- return nullptr;
+ if (fileName.startsWith("content"_L1))
+ return std::make_unique<AndroidContentFileEngine>(fileName);
- return new AndroidContentFileEngine(fileName);
-}
+ return {};
-AndroidContentFileEngineIterator::AndroidContentFileEngineIterator(QDir::Filters filters,
- const QStringList &filterNames)
- : QAbstractFileEngineIterator(filters, filterNames)
-{
}
-AndroidContentFileEngineIterator::~AndroidContentFileEngineIterator()
+AndroidContentFileEngineIterator::AndroidContentFileEngineIterator(
+ const QString &path, QDir::Filters filters, const QStringList &filterNames)
+ : QAbstractFileEngineIterator(path, filters, filterNames)
{
}
-QString AndroidContentFileEngineIterator::next()
+AndroidContentFileEngineIterator::~AndroidContentFileEngineIterator()
{
- if (!hasNext())
- return QString();
- ++m_index;
- return currentFilePath();
}
-bool AndroidContentFileEngineIterator::hasNext() const
+bool AndroidContentFileEngineIterator::advance()
{
if (m_index == -1 && m_files.isEmpty()) {
const auto currentPath = path();
@@ -297,23 +289,32 @@ bool AndroidContentFileEngineIterator::hasNext() const
if (iterDoc->isDirectory())
for (const auto &doc : iterDoc->listFiles())
m_files.append(doc);
+ if (m_files.isEmpty())
+ return false;
+ m_index = 0;
+ return true;
+ }
+
+ if (m_index < m_files.size() - 1) {
+ ++m_index;
+ return true;
}
- return m_index < (m_files.size() - 1);
+ return false;
}
QString AndroidContentFileEngineIterator::currentFileName() const
{
if (m_index < 0 || m_index > m_files.size())
return QString();
- // Returns a full path since contstructing a content path from the file name
- // and a tree URI only will not point to a valid file URI.
- return m_files.at(m_index)->uri().toString();
+ return m_files.at(m_index)->name();
}
QString AndroidContentFileEngineIterator::currentFilePath() const
{
- return currentFileName();
+ if (m_index < 0 || m_index > m_files.size())
+ return QString();
+ return m_files.at(m_index)->uri().toString();
}
// Start of Cursor
@@ -463,7 +464,7 @@ const QLatin1String MIME_TYPE_DIR("vnd.android.document/directory");
QString documentId(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"getDocumentId",
uri.object()).toString();
}
@@ -471,7 +472,7 @@ QString documentId(const QJniObject &uri)
QString treeDocumentId(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"getTreeDocumentId",
uri.object()).toString();
}
@@ -479,7 +480,7 @@ QString treeDocumentId(const QJniObject &uri)
QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString &parentDocumentId)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"buildChildDocumentsUriUsingTree",
uri.object<QtJniTypes::UriType>(),
QJniObject::fromString(parentDocumentId).object<jstring>());
@@ -489,7 +490,7 @@ QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString
QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &documentId)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"buildDocumentUriUsingTree",
treeUri.object<QtJniTypes::UriType>(),
QJniObject::fromString(documentId).object<jstring>());
@@ -498,7 +499,7 @@ QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &d
bool isDocumentUri(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jboolean>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"isDocumentUri",
QNativeInterface::QAndroidApplication::context(),
uri.object<QtJniTypes::UriType>());
@@ -507,7 +508,7 @@ bool isDocumentUri(const QJniObject &uri)
bool isTreeUri(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jboolean>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"isTreeUri",
uri.object<QtJniTypes::UriType>());
}
@@ -516,7 +517,7 @@ QJniObject createDocument(const QJniObject &parentDocumentUri, const QString &mi
const QString &displayName)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"createDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
parentDocumentUri.object<QtJniTypes::UriType>(),
@@ -531,7 +532,7 @@ bool deleteDocument(const QJniObject &documentUri)
return {};
return QJniObject::callStaticMethod<jboolean>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"deleteDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
documentUri.object<QtJniTypes::UriType>());
@@ -546,7 +547,7 @@ QJniObject moveDocument(const QJniObject &sourceDocumentUri,
return {};
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"moveDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
sourceDocumentUri.object<QtJniTypes::UriType>(),
@@ -561,7 +562,7 @@ QJniObject renameDocument(const QJniObject &documentUri, const QString &displayN
return {};
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::DocumentsContract>(),
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"renameDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
documentUri.object<QtJniTypes::UriType>(),
@@ -591,33 +592,38 @@ DocumentFile::DocumentFile(const QJniObject &uri,
QJniObject parseUri(const QString &uri)
{
+ QString uriToParse = uri;
+ if (uriToParse.contains(' '))
+ uriToParse.replace(' ', QUrl::toPercentEncoding(" "));
+
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
- QtJniTypes::className<QtJniTypes::Uri>(),
+ QtJniTypes::Traits<QtJniTypes::Uri>::className(),
"parse",
- QJniObject::fromString(uri).object<jstring>());
+ QJniObject::fromString(uriToParse).object<jstring>());
}
DocumentFilePtr DocumentFile::parseFromAnyUri(const QString &fileName)
{
- const QJniObject uri = parseUri(fileName);
+ const QString encodedUri = QUrl(fileName).toEncoded();
+ const QJniObject uri = parseUri(encodedUri);
- if (DocumentsContract::isDocumentUri(uri))
+ if (DocumentsContract::isDocumentUri(uri) || !DocumentsContract::isTreeUri(uri))
return fromSingleUri(uri);
const QString documentType = "/document/"_L1;
const QString treeType = "/tree/"_L1;
- const int treeIndex = fileName.indexOf(treeType);
- const int documentIndex = fileName.indexOf(documentType);
+ const int treeIndex = encodedUri.indexOf(treeType);
+ const int documentIndex = encodedUri.indexOf(documentType);
const int index = fileName.lastIndexOf("/");
if (index <= treeIndex + treeType.size() || index <= documentIndex + documentType.size())
return fromTreeUri(uri);
- const QString parentUrl = fileName.left(index);
+ const QString parentUrl = encodedUri.left(index);
DocumentFilePtr parentDocFile = fromTreeUri(parseUri(parentUrl));
- const QString baseName = fileName.mid(index);
+ const QString baseName = encodedUri.mid(index);
const QString fileUrl = parentUrl + QUrl::toPercentEncoding(baseName);
DocumentFilePtr docFile = std::make_shared<MakeableDocumentFile>(parseUri(fileUrl));
@@ -823,4 +829,6 @@ bool DocumentFile::rename(const QString &newName)
return false;
}
+QT_END_NAMESPACE
+
// End of DocumentFile
diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h
index 56d9bae8f7..a5dd1b30f3 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.h
+++ b/src/plugins/platforms/android/androidcontentfileengine.h
@@ -9,6 +9,8 @@
#include <QtCore/qjniobject.h>
#include <QtCore/qlist.h>
+QT_BEGIN_NAMESPACE
+
using DocumentFilePtr = std::shared_ptr<class DocumentFile>;
class AndroidContentFileEngine : public QFSFileEngine
@@ -25,11 +27,11 @@ public:
bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
QByteArray id() const override;
bool caseSensitive() const override { return true; }
- QDateTime fileTime(FileTime time) const override;
+ QDateTime fileTime(QFile::FileTime time) const override;
FileFlags fileFlags(FileFlags type = FileInfoAll) const override;
QString fileName(FileName file = DefaultName) const override;
- QAbstractFileEngine::Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- QAbstractFileEngine::Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
private:
void closeNativeFileDescriptor();
@@ -41,19 +43,22 @@ private:
class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
{
+ Q_DISABLE_COPY_MOVE(AndroidContentFileEngineHandler)
public:
AndroidContentFileEngineHandler();
~AndroidContentFileEngineHandler();
- QAbstractFileEngine *create(const QString &fileName) const override;
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
};
class AndroidContentFileEngineIterator : public QAbstractFileEngineIterator
{
public:
- AndroidContentFileEngineIterator(QDir::Filters filters, const QStringList &filterNames);
+ AndroidContentFileEngineIterator(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames);
~AndroidContentFileEngineIterator();
- QString next() override;
- bool hasNext() const override;
+
+ bool advance() override;
+
QString currentFileName() const override;
QString currentFilePath() const override;
private:
@@ -102,4 +107,6 @@ protected:
DocumentFilePtr m_parent;
};
+QT_END_NAMESPACE
+
#endif // ANDROIDCONTENTFILEENGINE_H
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp
index 8990289dc4..da5b63ef21 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.cpp
+++ b/src/plugins/platforms/android/androidjniaccessibility.cpp
@@ -15,11 +15,12 @@
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/QJniObject>
#include <QtGui/private/qhighdpiscaling_p.h>
+
#include <QtCore/QObject>
+#include <QtCore/qpointer.h>
#include <QtCore/qvarlengtharray.h>
static const char m_qtTag[] = "Qt A11Y";
-static const char m_classErrorMsg[] = "Can't find class \"%s\"";
QT_BEGIN_NAMESPACE
@@ -48,7 +49,7 @@ namespace QtAndroidAccessibility
// Because of that almost every method here is split into two parts.
// The _helper part is executed in the context of m_accessibilityContext
// on the main thread. The other part is executed in Java thread.
- static QPointer<QObject> m_accessibilityContext = nullptr;
+ Q_CONSTINIT static QPointer<QObject> m_accessibilityContext = {};
// This method is called from the Qt main thread, and normally a
// QGuiApplication instance will be used as a parent.
@@ -81,8 +82,7 @@ namespace QtAndroidAccessibility
void initialize()
{
- QJniObject::callStaticMethod<void>(QtAndroid::applicationClass(),
- "initializeAccessibility");
+ QtAndroid::qtActivityDelegate().callMethod<void>("initializeAccessibility");
}
bool isActive()
@@ -127,6 +127,12 @@ namespace QtAndroidAccessibility
QtAndroid::notifyObjectHide(accessibilityObjectId, parentObjectId);
}
+ void notifyObjectShow(uint accessibilityObjectId)
+ {
+ const auto parentObjectId = parentId_helper(accessibilityObjectId);
+ QtAndroid::notifyObjectShow(parentObjectId);
+ }
+
void notifyObjectFocus(uint accessibilityObjectId)
{
QtAndroid::notifyObjectFocus(accessibilityObjectId);
@@ -351,16 +357,6 @@ namespace QtAndroidAccessibility
return result && oldPosition != screenRect_helper(firstChildId, false);
}
-
-#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
-clazz = env->FindClass(CLASS_NAME); \
-if (!clazz) { \
- __android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_classErrorMsg, CLASS_NAME); \
- return JNI_FALSE; \
-}
-
- //__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE);
-
static QString textFromValue(QAccessibleInterface *iface)
{
QString valueStr;
@@ -557,7 +553,7 @@ if (!clazz) { \
return true;
}
- static JNINativeMethod methods[] = {
+ static const JNINativeMethod methods[] = {
{"setActive","(Z)V",(void*)setActive},
{"childIdListForAccessibleObject", "(I)[I", (jintArray)childIdListForAccessibleObject},
{"parentId", "(I)I", (void*)parentId},
@@ -577,13 +573,10 @@ if (!clazz) { \
return false; \
}
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
- jclass clazz;
- FIND_AND_CHECK_CLASS("org/qtproject/qt/android/accessibility/QtNativeAccessibility");
- jclass appClass = static_cast<jclass>(env->NewGlobalRef(clazz));
-
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods("org/qtproject/qt/android/QtNativeAccessibility",
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt A11y", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h
index 9bbbe80fe9..6e8e059334 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.h
+++ b/src/plugins/platforms/android/androidjniaccessibility.h
@@ -9,14 +9,16 @@
QT_BEGIN_NAMESPACE
class QObject;
+class QJniEnvironment;
namespace QtAndroidAccessibility
{
void initialize();
bool isActive();
- bool registerNatives(JNIEnv *env);
+ bool registerNatives(QJniEnvironment &env);
void notifyLocationChange(uint accessibilityObjectId);
void notifyObjectHide(uint accessibilityObjectId);
+ void notifyObjectShow(uint accessibilityObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId);
void notifyScrolledEvent(uint accessibilityObjectId);
diff --git a/src/plugins/platforms/android/androidjniclipboard.cpp b/src/plugins/platforms/android/androidjniclipboard.cpp
deleted file mode 100644
index 81663cac0c..0000000000
--- a/src/plugins/platforms/android/androidjniclipboard.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "androidjniclipboard.h"
-#include <QtCore/QUrl>
-#include <QtCore/QJniObject>
-#include <QtCore/QJniEnvironment>
-
-QT_BEGIN_NAMESPACE
-
-using namespace QtAndroid;
-namespace QtAndroidClipboard
-{
- QAndroidPlatformClipboard *m_manager = nullptr;
-
- static JNINativeMethod methods[] = {
- {"onClipboardDataChanged", "()V", (void *)onClipboardDataChanged}
- };
-
- void setClipboardManager(QAndroidPlatformClipboard *manager)
- {
- m_manager = manager;
- QJniObject::callStaticMethod<void>(applicationClass(), "registerClipboardManager");
- jclass appClass = QtAndroid::applicationClass();
- QJniEnvironment env;
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
- __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
- return;
- }
- }
- void clearClipboardData()
- {
- QJniObject::callStaticMethod<void>(applicationClass(), "clearClipData");
- }
- void setClipboardMimeData(QMimeData *data)
- {
- clearClipboardData();
- if (data->hasUrls()) {
- QList<QUrl> urls = data->urls();
- for (const auto &u : std::as_const(urls)) {
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardUri",
- "(Ljava/lang/String;)V",
- QJniObject::fromString(u.toEncoded()).object());
- }
- } else if (data->hasText()) { // hasText || hasUrls, so the order matter here.
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardText", "(Ljava/lang/String;)V",
- QJniObject::fromString(data->text()).object());
- } else if (data->hasHtml()) {
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardHtml",
- "(Ljava/lang/String;Ljava/lang/String;)V",
- QJniObject::fromString(data->text()).object(),
- QJniObject::fromString(data->html()).object());
- }
- }
-
- QMimeData *getClipboardMimeData()
- {
- QMimeData *data = new QMimeData;
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardText")) {
- data->setText(QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardText",
- "()Ljava/lang/String;").toString());
- }
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardHtml")) {
- data->setHtml(QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardHtml",
- "()Ljava/lang/String;").toString());
- }
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardUri")) {
- QJniObject uris = QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardUris",
- "()[Ljava/lang/String;");
- if (uris.isValid()) {
- QList<QUrl> urls;
- QJniEnvironment env;
- jobjectArray juris = uris.object<jobjectArray>();
- const jint nUris = env->GetArrayLength(juris);
- urls.reserve(static_cast<int>(nUris));
- for (int i = 0; i < nUris; ++i)
- urls << QUrl(QJniObject(env->GetObjectArrayElement(juris, i)).toString());
- data->setUrls(urls);
- }
- }
- return data;
- }
-
- void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/)
- {
- m_manager->emitChanged(QClipboard::Clipboard);
- }
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjniclipboard.h b/src/plugins/platforms/android/androidjniclipboard.h
deleted file mode 100644
index 24feeef9b3..0000000000
--- a/src/plugins/platforms/android/androidjniclipboard.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef ANDROIDJNICLIPBOARD_H
-#define ANDROIDJNICLIPBOARD_H
-
-#include <QString>
-#include "qandroidplatformclipboard.h"
-#include "androidjnimain.h"
-
-QT_BEGIN_NAMESPACE
-
-class QAndroidPlatformClipboard;
-namespace QtAndroidClipboard
-{
- // Clipboard support
- void setClipboardManager(QAndroidPlatformClipboard *manager);
- void setClipboardMimeData(QMimeData *data);
- QMimeData *getClipboardMimeData();
- void clearClipboardData();
- void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/);
- // Clipboard support
-}
-
-QT_END_NAMESPACE
-
-#endif // ANDROIDJNICLIPBOARD_H
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index fae8668f63..d074e73b9e 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -1,3 +1,4 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@@ -8,6 +9,7 @@
#include "androidjnimain.h"
#include "qandroidplatformintegration.h"
+#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
#include <QTouchEvent>
#include <QPointer>
@@ -17,25 +19,92 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
+
using namespace QtAndroid;
+Q_DECLARE_JNI_CLASS(QtLayout, "org/qtproject/qt/android/QtLayout")
+
namespace QtAndroidInput
{
static bool m_ignoreMouseEvents = false;
+ static Qt::MouseButtons m_buttons = Qt::NoButton;
+
static QRect m_softwareKeyboardRect;
static QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
static QPointer<QWindow> m_mouseGrabber;
+ GenericMotionEventListener::~GenericMotionEventListener() {}
+ namespace {
+ struct GenericMotionEventListeners {
+ QMutex mutex;
+ QList<QtAndroidInput::GenericMotionEventListener *> listeners;
+ };
+ }
+ Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners)
+
+ static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event)
+ {
+ jboolean ret = JNI_FALSE;
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ for (auto *listener : std::as_const(g_genericMotionEventListeners()->listeners))
+ ret |= listener->handleGenericMotionEvent(event);
+ return ret;
+ }
+
+ KeyEventListener::~KeyEventListener() {}
+ namespace {
+ struct KeyEventListeners {
+ QMutex mutex;
+ QList<QtAndroidInput::KeyEventListener *> listeners;
+ };
+ }
+ Q_GLOBAL_STATIC(KeyEventListeners, g_keyEventListeners)
+
+ static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
+ {
+ jboolean ret = JNI_FALSE;
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ for (auto *listener : std::as_const(g_keyEventListeners()->listeners))
+ ret |= listener->handleKeyEvent(event);
+ return ret;
+ }
+
+ void registerGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
+ {
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ g_genericMotionEventListeners()->listeners.push_back(listener);
+ }
+
+ void unregisterGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
+ {
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ g_genericMotionEventListeners()->listeners.removeOne(listener);
+ }
+
+ void registerKeyEventListener(QtAndroidInput::KeyEventListener *listener)
+ {
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ g_keyEventListeners()->listeners.push_back(listener);
+ }
+
+ void unregisterKeyEventListener(QtAndroidInput::KeyEventListener *listener)
+ {
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ g_keyEventListeners()->listeners.removeOne(listener);
+ }
+
+ QJniObject qtLayout()
+ {
+ return qtActivityDelegate().callMethod<QtJniTypes::QtLayout>("getQtLayout");
+ }
+
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
{
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
-#endif
- QJniObject::callStaticMethod<void>(applicationClass(),
- "updateSelection",
- "(IIII)V",
+ qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
+ qtInputDelegate().callMethod<void>("updateSelection",
selStart,
selEnd,
candidatesStart,
@@ -44,39 +113,33 @@ namespace QtAndroidInput
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
- QJniObject::callStaticMethod<void>(applicationClass(),
- "showSoftwareKeyboard",
- "(IIIIII)V",
+ qtInputDelegate().callMethod<void>("showSoftwareKeyboard",
+ QtAndroidPrivate::activity(),
+ qtLayout().object<QtJniTypes::QtLayout>(),
left,
top,
width,
height,
inputHints,
enterKeyType);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
}
void resetSoftwareKeyboard()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "resetSoftwareKeyboard");
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ RESETSOFTWAREKEYBOARD");
-#endif
+ qtInputDelegate().callMethod<void>("resetSoftwareKeyboard");
+ qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD";
}
void hideSoftwareKeyboard()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "hideSoftwareKeyboard");
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ HIDESOFTWAREKEYBOARD");
-#endif
+ qtInputDelegate().callMethod<void>("hideSoftwareKeyboard");
+ qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD";
}
bool isSoftwareKeyboardVisible()
{
- return QJniObject::callStaticMethod<jboolean>(applicationClass(), "isSoftwareKeyboardVisible");
+ return qtInputDelegate().callMethod<jboolean>("isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
@@ -86,81 +149,150 @@ namespace QtAndroidInput
int getSelectHandleWidth()
{
- return QJniObject::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth");
+ return qtInputDelegate().callMethod<jint>("getSelectHandleWidth");
}
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
- QJniObject::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V",
+ qtInputDelegate().callMethod<void>("updateHandles",
+ QtAndroidPrivate::activity(),
+ qtLayout().object<QtJniTypes::QtLayout>(),
mode, editMenuPos.x(), editMenuPos.y(), editButtons,
cursor.x(), cursor.y(),
anchor.x(), anchor.y(), rtl);
}
- static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ // from https://developer.android.com/reference/android/view/MotionEvent#getButtonState()
+ enum AndroidMouseButton {
+ BUTTON_PRIMARY = 0x00000001,
+ BUTTON_SECONDARY = 0x00000002,
+ BUTTON_TERTIARY = 0x00000004,
+ BUTTON_BACK = 0x00000008,
+ BUTTON_FORWARD = 0x00000010,
+ BUTTON_STYLUS_PRIMARY = 0x00000020,
+ BUTTON_STYLUS_SECONDARY = 0x00000040,
+ };
+ Q_DECLARE_FLAGS(AndroidMouseButtons, AndroidMouseButton)
+
+ static Qt::MouseButtons toMouseButtons(jint j_buttons)
+ {
+ const auto buttons = static_cast<AndroidMouseButtons>(j_buttons);
+ Qt::MouseButtons mouseButtons;
+ if (buttons.testFlag(BUTTON_PRIMARY))
+ mouseButtons.setFlag(Qt::LeftButton);
+
+ if (buttons.testFlag(BUTTON_SECONDARY))
+ mouseButtons.setFlag(Qt::RightButton);
+
+ if (buttons.testFlag(BUTTON_TERTIARY))
+ mouseButtons.setFlag(Qt::MiddleButton);
+
+ if (buttons.testFlag(BUTTON_BACK))
+ mouseButtons.setFlag(Qt::BackButton);
+
+ if (buttons.testFlag(BUTTON_FORWARD))
+ mouseButtons.setFlag(Qt::ForwardButton);
+
+ if (buttons.testFlag(BUTTON_STYLUS_PRIMARY))
+ mouseButtons.setFlag(Qt::LeftButton);
+
+ if (buttons.testFlag(BUTTON_STYLUS_SECONDARY))
+ mouseButtons.setFlag(Qt::RightButton);
+
+ // Fall back to left button
+ if (Q_UNLIKELY(buttons != 0 && mouseButtons == Qt::NoButton)) {
+ qWarning() << "Unhandled button value:" << buttons << "Falling back to Qt::LeftButton";
+ mouseButtons = Qt::LeftButton;
+ }
+ return mouseButtons;
+ }
+
+ static void sendMouseButtonEvents(QWindow *topLevel, QPoint localPos, QPoint globalPos,
+ jint mouseButtonState, QEvent::Type type)
+ {
+ const Qt::MouseButtons mouseButtons = toMouseButtons(mouseButtonState);
+ const Qt::MouseButtons changedButtons = mouseButtons & ~m_buttons;
+
+ if (changedButtons == Qt::NoButton)
+ return;
+
+ static_assert (sizeof(changedButtons) <= sizeof(uint), "Qt::MouseButtons size changed. Adapt code.");
+
+ for (uint buttonInt = 0x1; static_cast<uint>(changedButtons) >= buttonInt; buttonInt <<= 1) {
+ const auto button = static_cast<Qt::MouseButton>(buttonInt);
+ if (changedButtons.testFlag(button)) {
+ QWindowSystemInterface::handleMouseEvent(topLevel, localPos, globalPos,
+ mouseButtons, button, type);
+ }
+ }
+ }
+
+ static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y, jint mouseButtonState)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- m_mouseGrabber = tlw;
- QPoint localPos = tlw ? (globalPos - tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
- Qt::MouseButtons(Qt::LeftButton),
- Qt::LeftButton, QEvent::MouseButtonPress);
- }
-
- static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
- {
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos -tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
- Qt::MouseButtons(Qt::NoButton),
- Qt::LeftButton, QEvent::MouseButtonRelease);
+ const QPoint globalPos(x,y);
+ QWindow *window = windowFromId(winId);
+ m_mouseGrabber = window;
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ sendMouseButtonEvents(window, localPos, globalPos, mouseButtonState, QEvent::MouseButtonPress);
+ }
+
+ static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y, jint mouseButtonState)
+ {
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+
+ sendMouseButtonEvents(window, localPos, globalPos, mouseButtonState, QEvent::MouseButtonRelease);
m_ignoreMouseEvents = false;
- m_mouseGrabber = 0;
+ m_mouseGrabber.clear();
}
- static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(m_mouseGrabber ? Qt::LeftButton : Qt::NoButton),
Qt::NoButton, QEvent::MouseMove);
}
- static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jfloat hdelta, jfloat vdelta)
+ static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y, jfloat hdelta, jfloat vdelta)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
- QPoint angleDelta(hdelta * 120, vdelta * 120);
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ const QPoint angleDelta(hdelta * 120, vdelta * 120);
- QWindowSystemInterface::handleWheelEvent(tlw,
+ QWindowSystemInterface::handleWheelEvent(window,
localPos,
globalPos,
QPoint(),
angleDelta);
}
- static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
@@ -171,16 +303,17 @@ namespace QtAndroidInput
if (!rightMouseFromLongPress)
return;
m_ignoreMouseEvents = true;
- QPoint globalPos(x,y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
+ const QPoint globalPos(x,y);
+ QWindow *window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
// Click right button if no other button is already pressed.
if (!m_mouseGrabber) {
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::RightButton), Qt::RightButton,
QEvent::MouseButtonPress);
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::NoButton), Qt::RightButton,
QEvent::MouseButtonRelease);
}
@@ -218,12 +351,11 @@ namespace QtAndroidInput
touchPoint.rotation = qRadiansToDegrees(rotation);
touchPoint.normalPosition = QPointF(double(x / dw), double(y / dh));
touchPoint.state = state;
- touchPoint.area = QRectF(x - double(minor),
- y - double(major),
- double(minor * 2),
- double(major * 2));
+ touchPoint.area = QRectF(x - double(minor * 0.5f),
+ y - double(major * 0.5f),
+ double(minor),
+ double(major));
m_touchPoints.push_back(touchPoint);
-
if (state == QEventPoint::State::Pressed) {
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
@@ -254,31 +386,35 @@ namespace QtAndroidInput
return touchDevice;
}
- static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/)
+ static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint winId, jint /*action*/)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
- QPointingDevice *touchDevice = getTouchDevice();
+ const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
- QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
+ QWindow *window = QtAndroid::windowFromId(winId);
+ if (!window)
+ return;
QWindowSystemInterface::handleTouchEvent(window, touchDevice, m_touchPoints);
}
- static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/)
+ static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint winId)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
- QPointingDevice *touchDevice = getTouchDevice();
+ const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
- QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
+ QWindow *window = QtAndroid::windowFromId(winId);
+ if (!window)
+ return;
QWindowSystemInterface::handleTouchCancelEvent(window, touchDevice);
}
@@ -291,14 +427,14 @@ namespace QtAndroidInput
#endif // QT_CONFIG(tabletevent)
}
- static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint deviceId, jlong time, jint action,
+ static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint deviceId, jlong time, jint action,
jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure)
{
#if QT_CONFIG(tabletevent)
- QPointF globalPosF(x, y);
- QPoint globalPos((int)x, (int)y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- QPointF localPos = tlw ? (globalPosF - tlw->position()) : globalPosF;
+ const QPointF globalPosF(x, y);
+ QWindow *window = windowFromId(winId);
+ const QPointF localPos = window && window->handle() ?
+ window->handle()->mapFromGlobalF(globalPosF) : globalPosF;
// Galaxy Note with plain Android:
// 0 1 0 stylus press
@@ -318,6 +454,7 @@ namespace QtAndroidInput
Qt::MouseButtons buttons = Qt::NoButton;
switch (action) {
case 1: // ACTION_UP
+ case 6: // ACTION_POINTER_UP, happens if stylus is not the primary pointer
case 212: // stylus release while side-button held on Galaxy Note 4
buttons = Qt::NoButton;
break;
@@ -329,11 +466,9 @@ namespace QtAndroidInput
break;
}
-#ifdef QT_DEBUG_ANDROID_STYLUS
- qDebug() << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
-#endif
+ qCDebug(lcQpaInputMethods) << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
- QWindowSystemInterface::handleTabletEvent(tlw, ulong(time),
+ QWindowSystemInterface::handleTabletEvent(window, ulong(time),
localPos, globalPosF, int(QInputDevice::DeviceType::Stylus), pointerType,
buttons, pressure, 0, 0, 0., 0., 0, deviceId, Qt::NoModifier);
#endif // QT_CONFIG(tabletevent)
@@ -797,9 +932,7 @@ namespace QtAndroidInput
QMetaObject::invokeMethod(inputContext, "hideSelectionHandles", Qt::QueuedConnection);
}
}
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
}
static void keyboardGeometryChanged(JNIEnv */*env*/, jobject /*thiz*/, jint x, jint y, jint w, jint h)
@@ -812,16 +945,12 @@ namespace QtAndroidInput
if (inputContext && qGuiApp)
inputContext->emitKeyboardRectChanged();
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ KEYBOARDRECTCHANGED" << m_softwareKeyboardRect;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDRECTCHANGED" << m_softwareKeyboardRect;
}
static void handleLocationChanged(JNIEnv */*env*/, jobject /*thiz*/, int id, int x, int y)
{
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ handleLocationChanged" << id << x << y;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ handleLocationChanged" << id << x << y;
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
QMetaObject::invokeMethod(inputContext, "handleLocationChanged", Qt::BlockingQueuedConnection,
@@ -829,13 +958,14 @@ namespace QtAndroidInput
}
- static JNINativeMethod methods[] = {
+
+ static const JNINativeMethod methods[] = {
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(II)V",(void*)touchEnd},
{"touchCancel", "(I)V", (void *)touchCancel},
- {"mouseDown", "(III)V", (void *)mouseDown},
- {"mouseUp", "(III)V", (void *)mouseUp},
+ {"mouseDown", "(IIII)V", (void *)mouseDown},
+ {"mouseUp", "(IIII)V", (void *)mouseUp},
{"mouseMove", "(III)V", (void *)mouseMove},
{"mouseWheel", "(IIIFF)V", (void *)mouseWheel},
{"longPress", "(III)V", (void *)longPress},
@@ -845,14 +975,15 @@ namespace QtAndroidInput
{"keyUp", "(IIIZ)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
{"keyboardGeometryChanged", "(IIII)V", (void *)keyboardGeometryChanged},
- {"handleLocationChanged", "(III)V", (void *)handleLocationChanged}
+ {"handleLocationChanged", "(III)V", (void *)handleLocationChanged},
+ {"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
+ {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
};
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
- jclass appClass = QtAndroid::applicationClass();
-
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtInputDelegate>::className(),
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h
index 7ef51ef9f7..28a2665bf6 100644
--- a/src/plugins/platforms/android/androidjniinput.h
+++ b/src/plugins/platforms/android/androidjniinput.h
@@ -6,10 +6,15 @@
#include <jni.h>
#include <QtCore/qglobal.h>
+#include <QtCore/QLoggingCategory>
#include <QtCore/QRect>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods);
+
+class QJniEnvironment;
+
namespace QtAndroidInput
{
// Software keyboard support
@@ -26,7 +31,27 @@ namespace QtAndroidInput
QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false);
int getSelectHandleWidth();
- bool registerNatives(JNIEnv *env);
+ class GenericMotionEventListener
+ {
+ public:
+ virtual ~GenericMotionEventListener();
+ virtual bool handleGenericMotionEvent(jobject event) = 0;
+ };
+
+ class KeyEventListener
+ {
+ public:
+ virtual ~KeyEventListener();
+ virtual bool handleKeyEvent(jobject event) = 0;
+ };
+
+ void registerGenericMotionEventListener(GenericMotionEventListener *listener);
+ void unregisterGenericMotionEventListener(GenericMotionEventListener *listener);
+
+ void registerKeyEventListener(KeyEventListener *listener);
+ void unregisterKeyEventListener(KeyEventListener *listener);
+
+ bool registerNatives(QJniEnvironment &env);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp
index 5b066fb5a5..9fdcf3936b 100644
--- a/src/plugins/platforms/android/androidjnimain.cpp
+++ b/src/plugins/platforms/android/androidjnimain.cpp
@@ -10,14 +10,16 @@
#include "androidcontentfileengine.h"
#include "androiddeadlockprotector.h"
#include "androidjniaccessibility.h"
-#include "androidjniclipboard.h"
#include "androidjniinput.h"
#include "androidjnimain.h"
#include "androidjnimenu.h"
+#include "androidwindowembedding.h"
#include "qandroidassetsfileenginehandler.h"
#include "qandroideventdispatcher.h"
#include "qandroidplatformdialoghelpers.h"
#include "qandroidplatformintegration.h"
+#include "qandroidplatformclipboard.h"
+#include "qandroidplatformwindow.h"
#include <android/api-level.h>
#include <android/asset_manager_jni.h>
@@ -29,12 +31,16 @@
#include <QtCore/qjniobject.h>
#include <QtCore/qprocess.h>
#include <QtCore/qresource.h>
+#include <QtCore/qscopeguard.h>
#include <QtCore/qthread.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qwindowsysteminterface.h>
+
+using namespace Qt::StringLiterals;
+
QT_BEGIN_NAMESPACE
static JavaVM *m_javaVM = nullptr;
@@ -44,11 +50,12 @@ static jmethodID m_loadClassMethodID = nullptr;
static AAssetManager *m_assetManager = nullptr;
static jobject m_assets = nullptr;
static jobject m_resourcesObj = nullptr;
-static QtJniTypes::Activity m_activityObject = nullptr;
-static jmethodID m_createSurfaceMethodID = nullptr;
-static QtJniTypes::Service m_serviceObject = nullptr;
-static jmethodID m_setSurfaceGeometryMethodID = nullptr;
-static jmethodID m_destroySurfaceMethodID = nullptr;
+
+static jclass m_qtActivityClass = nullptr;
+static jclass m_qtServiceClass = nullptr;
+
+static QtJniTypes::QtActivityDelegateBase m_activityDelegate = nullptr;
+static QtJniTypes::QtInputDelegate m_inputDelegate = nullptr;
static int m_pendingApplicationState = -1;
static QBasicMutex m_platformMutex;
@@ -67,10 +74,6 @@ static void *m_mainLibraryHnd = nullptr;
static QList<QByteArray> m_applicationParams;
static sem_t m_exitSemaphore, m_terminateSemaphore;
-QHash<int, AndroidSurfaceClient *> m_surfaces;
-
-Q_CONSTINIT static QBasicMutex m_surfacesMutex;
-
static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr;
@@ -90,6 +93,8 @@ static const char m_methodErrorMsg[] = "Can't find method \"%s%s\"";
Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_DECLARE_JNI_CLASS(QtEmbeddedDelegateFactory, "org/qtproject/qt/android/QtEmbeddedDelegateFactory")
+
namespace QtAndroid
{
QBasicMutex *platformInterfaceMutex()
@@ -100,6 +105,7 @@ namespace QtAndroid
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration)
{
m_androidPlatformIntegration = androidPlatformIntegration;
+ QtAndroid::notifyNativePluginIntegrationReady((bool)m_androidPlatformIntegration);
// flush the pending state if necessary.
if (m_androidPlatformIntegration && (m_pendingApplicationState != -1)) {
@@ -125,6 +131,21 @@ namespace QtAndroid
: 0;
}
+ QWindow *windowFromId(int windowId)
+ {
+ if (!qGuiApp)
+ return nullptr;
+
+ for (QWindow *w : qGuiApp->allWindows()) {
+ if (!w->handle())
+ continue;
+ QAndroidPlatformWindow *window = static_cast<QAndroidPlatformWindow *>(w->handle());
+ if (window->nativeViewId() == windowId)
+ return w;
+ }
+ return nullptr;
+ }
+
int availableWidthPixels()
{
return m_availableWidthPixels;
@@ -160,53 +181,96 @@ namespace QtAndroid
return m_applicationClass;
}
- QtJniTypes::Activity activity()
+ // TODO move calls from here to where they logically belong
+ void setSystemUiVisibility(SystemUiVisibility uiVisibility)
{
- return m_activityObject;
+ qtActivityDelegate().callMethod<void>("setSystemUiVisibility", jint(uiVisibility));
}
- QtJniTypes::Service service()
+ // FIXME: avoid direct access to QtActivityDelegate
+ QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
{
- return m_serviceObject;
+ using namespace QtJniTypes;
+ if (!m_activityDelegate.isValid()) {
+ if (isQtApplication()) {
+ auto context = QtAndroidPrivate::activity();
+ m_activityDelegate = context.callMethod<QtActivityDelegateBase>("getActivityDelegate");
+ } else {
+ m_activityDelegate = QJniObject::callStaticMethod<QtActivityDelegateBase>(
+ Traits<QtEmbeddedDelegateFactory>::className(),
+ "getActivityDelegate",
+ QtAndroidPrivate::activity());
+ }
+ }
+
+ return m_activityDelegate;
}
- void setSystemUiVisibility(SystemUiVisibility uiVisibility)
+ QtJniTypes::QtInputDelegate qtInputDelegate()
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "setSystemUiVisibility", "(I)V", jint(uiVisibility));
+ if (!m_inputDelegate.isValid()) {
+ m_inputDelegate = qtActivityDelegate().callMethod<QtJniTypes::QtInputDelegate>(
+ "getInputDelegate");
+ }
+
+ return m_inputDelegate;
+ }
+
+ bool isQtApplication()
+ {
+ // Returns true if the app is a Qt app, i.e. Qt controls the whole app and
+ // the Activity/Service is created by Qt. Returns false if instead Qt is
+ // embedded into a native Android app, where the Activity/Service is created
+ // by the user, outside of Qt, and Qt content is added as a view.
+ JNIEnv *env = QJniEnvironment::getJniEnv();
+ auto activity = QtAndroidPrivate::activity();
+ if (activity.isValid())
+ return env->IsInstanceOf(activity.object(), m_qtActivityClass);
+ auto service = QtAndroidPrivate::service();
+ if (service.isValid())
+ return env->IsInstanceOf(QtAndroidPrivate::service().object(), m_qtServiceClass);
+ // return true as default as Qt application is our default use case.
+ // famous last words: we should not end up here
+ return true;
}
void notifyAccessibilityLocationChange(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyAccessibilityLocationChange",
- "(I)V", accessibilityObjectId);
+ qtActivityDelegate().callMethod<void>("notifyLocationChange", accessibilityObjectId);
}
void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyObjectHide", "(II)V",
- accessibilityObjectId, parentObjectId);
+ qtActivityDelegate().callMethod<void>("notifyObjectHide",
+ accessibilityObjectId, parentObjectId);
+ }
+
+ void notifyObjectShow(uint parentObjectId)
+ {
+ qtActivityDelegate().callMethod<void>("notifyObjectShow",
+ parentObjectId);
}
void notifyObjectFocus(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId);
+ qtActivityDelegate().callMethod<void>("notifyObjectFocus", accessibilityObjectId);
}
void notifyValueChanged(uint accessibilityObjectId, jstring value)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyValueChanged",
- "(ILjava/lang/String;)V", accessibilityObjectId, value);
+ qtActivityDelegate().callMethod<void>("notifyValueChanged", accessibilityObjectId, value);
}
void notifyScrolledEvent(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyScrolledEvent", "(I)V",
- accessibilityObjectId);
+ qtActivityDelegate().callMethod<void>("notifyScrolledEvent", accessibilityObjectId);
}
- void notifyQtAndroidPluginRunning(bool running)
+ void notifyNativePluginIntegrationReady(bool ready)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running);
+ QJniObject::callStaticMethod<void>(m_applicationClass,
+ "notifyNativePluginIntegrationReady",
+ ready);
}
jobject createBitmap(QImage img, JNIEnv *env)
@@ -215,7 +279,7 @@ namespace QtAndroid
return 0;
if (img.format() != QImage::Format_RGBA8888 && img.format() != QImage::Format_RGB16)
- img = img.convertToFormat(QImage::Format_RGBA8888);
+ img = std::move(img).convertToFormat(QImage::Format_RGBA8888);
jobject bitmap = env->CallStaticObjectMethod(m_bitmapClass,
m_createBitmapMethodID,
@@ -303,62 +367,6 @@ namespace QtAndroid
return manufacturer + u' ' + model;
}
- jint generateViewId()
- {
- return QJniObject::callStaticMethod<jint>("android/view/View", "generateViewId", "()I");
- }
-
- int createSurface(AndroidSurfaceClient *client, const QRect &geometry, bool onTop, int imageDepth)
- {
- QJniEnvironment env;
- if (!env.jniEnv())
- return -1;
-
- m_surfacesMutex.lock();
- jint surfaceId = generateViewId();
- m_surfaces[surfaceId] = client;
- m_surfacesMutex.unlock();
-
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull()) {
- x = geometry.x();
- y = geometry.y();
- w = std::max(geometry.width(), 1);
- h = std::max(geometry.height(), 1);
- }
- env->CallStaticVoidMethod(m_applicationClass,
- m_createSurfaceMethodID,
- surfaceId,
- jboolean(onTop),
- x, y, w, h,
- imageDepth);
- return surfaceId;
- }
-
- int insertNativeView(jobject view, const QRect &geometry)
- {
- m_surfacesMutex.lock();
- jint surfaceId = generateViewId();
- m_surfaces[surfaceId] = nullptr; // dummy
- m_surfacesMutex.unlock();
-
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull())
- geometry.getRect(&x, &y, &w, &h);
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "insertNativeView",
- "(ILandroid/view/View;IIII)V",
- surfaceId,
- view,
- x,
- y,
- qMax(w, 1),
- qMax(h, 1));
-
- return surfaceId;
- }
-
void setViewVisibility(jobject view, bool visible)
{
QJniObject::callStaticMethod<void>(m_applicationClass,
@@ -368,69 +376,6 @@ namespace QtAndroid
visible);
}
- void setSurfaceGeometry(int surfaceId, const QRect &geometry)
- {
- if (surfaceId == -1)
- return;
-
- QJniEnvironment env;
- if (!env.jniEnv())
- return;
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull()) {
- x = geometry.x();
- y = geometry.y();
- w = geometry.width();
- h = geometry.height();
- }
- env->CallStaticVoidMethod(m_applicationClass,
- m_setSurfaceGeometryMethodID,
- surfaceId,
- x, y, w, h);
- }
-
-
- void destroySurface(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- {
- QMutexLocker lock(&m_surfacesMutex);
- const auto &it = m_surfaces.find(surfaceId);
- if (it != m_surfaces.end())
- m_surfaces.erase(it);
- }
-
- QJniEnvironment env;
- if (env.jniEnv())
- env->CallStaticVoidMethod(m_applicationClass,
- m_destroySurfaceMethodID,
- surfaceId);
- }
-
- void bringChildToFront(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "bringChildToFront",
- "(I)V",
- surfaceId);
- }
-
- void bringChildToBack(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "bringChildToBack",
- "(I)V",
- surfaceId);
- }
-
bool blockEventLoopsWhenSuspended()
{
static bool block = qEnvironmentVariableIntValue("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED");
@@ -446,14 +391,14 @@ namespace QtAndroid
static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring paramsString)
{
+ Q_UNUSED(env)
+
m_androidPlatformIntegration = nullptr;
m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();
m_androidContentFileEngineHandler = new AndroidContentFileEngineHandler();
m_mainLibraryHnd = nullptr;
- const char *nativeString = env->GetStringUTFChars(paramsString, 0);
- const QStringList argsList = QProcess::splitCommand(QString::fromUtf8(nativeString));
- env->ReleaseStringUTFChars(paramsString, nativeString);
+ const QStringList argsList = QProcess::splitCommand(QJniObject(paramsString).toString());
for (const QString &arg : argsList)
m_applicationParams.append(arg.toUtf8());
@@ -496,7 +441,7 @@ static void waitForServiceSetup(JNIEnv *env, jclass /*clazz*/)
Q_UNUSED(env);
// The service must wait until the QCoreApplication starts otherwise onBind will be
// called too early
- if (m_serviceObject)
+ if (QtAndroidPrivate::service().isValid() && QtAndroid::isQtApplication())
QtAndroidPrivate::waitForServiceSetup();
}
@@ -527,7 +472,8 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/)
argv[argc] = nullptr;
startQtAndroidPluginCalled.fetchAndAddRelease(1);
- int ret = m_main(argc, argv.data());
+ const int ret = m_main(argc, argv.data());
+ qInfo() << "main() returned" << ret;
if (m_mainLibraryHnd) {
int res = dlclose(m_mainLibraryHnd);
@@ -535,10 +481,8 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/)
qWarning() << "dlclose failed:" << dlerror();
}
- if (m_applicationClass) {
- qWarning("exit app 0");
+ if (m_applicationClass)
QJniObject::callStaticMethod<void>(m_applicationClass, "quitApp", "()V");
- }
sem_post(&m_terminateSemaphore);
sem_wait(&m_exitSemaphore);
@@ -583,10 +527,6 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
env->DeleteGlobalRef(m_classLoaderObject);
if (m_resourcesObj)
env->DeleteGlobalRef(m_resourcesObj);
- if (m_activityObject)
- env->DeleteGlobalRef(m_activityObject);
- if (m_serviceObject)
- env->DeleteGlobalRef(m_serviceObject);
if (m_bitmapClass)
env->DeleteGlobalRef(m_bitmapClass);
if (m_ARGB_8888_BitmapConfigValue)
@@ -597,24 +537,16 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
env->DeleteGlobalRef(m_bitmapDrawableClass);
if (m_assets)
env->DeleteGlobalRef(m_assets);
+ if (m_qtActivityClass)
+ env->DeleteGlobalRef(m_qtActivityClass);
+ if (m_qtServiceClass)
+ env->DeleteGlobalRef(m_qtServiceClass);
m_androidPlatformIntegration = nullptr;
delete m_androidAssetsFileEngineHandler;
m_androidAssetsFileEngineHandler = nullptr;
sem_post(&m_exitSemaphore);
}
-static void setSurface(JNIEnv *env, jobject /*thiz*/, jint id, jobject jSurface, jint w, jint h)
-{
- QMutexLocker lock(&m_surfacesMutex);
- const auto &it = m_surfaces.find(id);
- if (it == m_surfaces.end())
- return;
-
- auto surfaceClient = it.value();
- if (surfaceClient)
- surfaceClient->surfaceChanged(env, jSurface, w, h);
-}
-
static void setDisplayMetrics(JNIEnv * /*env*/, jclass /*clazz*/, jint screenWidthPixels,
jint screenHeightPixels, jint availableLeftPixels,
jint availableTopPixels, jint availableWidthPixels,
@@ -647,6 +579,7 @@ static void setDisplayMetrics(JNIEnv * /*env*/, jclass /*clazz*/, jint screenWid
m_androidPlatformIntegration->setRefreshRate(refreshRate);
}
}
+Q_DECLARE_JNI_NATIVE_METHOD(setDisplayMetrics)
static void updateWindow(JNIEnv */*env*/, jobject /*thiz*/)
{
@@ -666,10 +599,6 @@ static void updateWindow(JNIEnv */*env*/, jobject /*thiz*/)
QWindowSystemInterface::handleExposeEvent(w, QRegion(QRect(QPoint(), w->geometry().size())));
}
}
-
- QAndroidPlatformScreen *screen = static_cast<QAndroidPlatformScreen *>(m_androidPlatformIntegration->screen());
- if (screen->rasterSurfaces())
- QMetaObject::invokeMethod(screen, "setDirty", Qt::QueuedConnection, Q_ARG(QRect,screen->geometry()));
}
static void updateApplicationState(JNIEnv */*env*/, jobject /*thiz*/, jint state)
@@ -748,36 +677,42 @@ static void handleOrientationChanged(JNIEnv */*env*/, jobject /*thiz*/, jint new
}
}
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleOrientationChanged)
static void handleRefreshRateChanged(JNIEnv */*env*/, jclass /*cls*/, jfloat refreshRate)
{
if (m_androidPlatformIntegration)
m_androidPlatformIntegration->setRefreshRate(refreshRate);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleRefreshRateChanged)
static void handleScreenAdded(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
{
if (m_androidPlatformIntegration)
m_androidPlatformIntegration->handleScreenAdded(displayId);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenAdded)
static void handleScreenChanged(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
{
if (m_androidPlatformIntegration)
m_androidPlatformIntegration->handleScreenChanged(displayId);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenChanged)
static void handleScreenRemoved(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
{
if (m_androidPlatformIntegration)
m_androidPlatformIntegration->handleScreenRemoved(displayId);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenRemoved)
static void handleUiDarkModeChanged(JNIEnv */*env*/, jobject /*thiz*/, jint newUiMode)
{
- QAndroidPlatformIntegration::setAppearance(
- (newUiMode == 1 ) ? Qt::Appearance::Dark : Qt::Appearance::Light);
+ QAndroidPlatformIntegration::updateColorScheme(
+ (newUiMode == 1 ) ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleUiDarkModeChanged)
static void onActivityResult(JNIEnv */*env*/, jclass /*cls*/,
jint requestCode,
@@ -804,116 +739,131 @@ static JNINativeMethod methods[] = {
{ "quitQtCoreApplication", "()V", (void *)quitQtCoreApplication },
{ "terminateQt", "()V", (void *)terminateQt },
{ "waitForServiceSetup", "()V", (void *)waitForServiceSetup },
- { "setDisplayMetrics", "(IIIIIIDDDDF)V", (void *)setDisplayMetrics },
- { "setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface },
{ "updateWindow", "()V", (void *)updateWindow },
{ "updateApplicationState", "(I)V", (void *)updateApplicationState },
- { "handleUiDarkModeChanged", "(I)V", (void *)handleUiDarkModeChanged },
- { "handleOrientationChanged", "(II)V", (void *)handleOrientationChanged },
{ "onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult },
{ "onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent },
- { "onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind },
- { "handleRefreshRateChanged", "(F)V", (void *)handleRefreshRateChanged },
- { "handleScreenAdded", "(I)V", (void *)handleScreenAdded },
- { "handleScreenChanged", "(I)V", (void *)handleScreenChanged },
- { "handleScreenRemoved", "(I)V", (void *)handleScreenRemoved }
+ { "onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind }
};
#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
clazz = env->FindClass(CLASS_NAME); \
if (!clazz) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_classErrorMsg, CLASS_NAME); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
VAR = env->GetFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
-static int registerNatives(JNIEnv *env)
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
+
+static bool registerNatives(QJniEnvironment &env)
{
jclass clazz;
FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNative");
m_applicationClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- if (env->RegisterNatives(m_applicationClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(m_applicationClass,
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
- return JNI_FALSE;
+ return false;
}
- GET_AND_CHECK_STATIC_METHOD(m_createSurfaceMethodID, m_applicationClass, "createSurface", "(IZIIIII)V");
- GET_AND_CHECK_STATIC_METHOD(m_setSurfaceGeometryMethodID, m_applicationClass, "setSurfaceGeometry", "(IIIII)V");
- GET_AND_CHECK_STATIC_METHOD(m_destroySurfaceMethodID, m_applicationClass, "destroySurface", "(I)V");
+ bool success = env.registerNativeMethods(
+ QtJniTypes::Traits<QtJniTypes::QtDisplayManager>::className(),
+ {
+ Q_JNI_NATIVE_METHOD(setDisplayMetrics),
+ Q_JNI_NATIVE_METHOD(handleOrientationChanged),
+ Q_JNI_NATIVE_METHOD(handleRefreshRateChanged),
+ Q_JNI_NATIVE_METHOD(handleScreenAdded),
+ Q_JNI_NATIVE_METHOD(handleScreenChanged),
+ Q_JNI_NATIVE_METHOD(handleScreenRemoved),
+ Q_JNI_NATIVE_METHOD(handleUiDarkModeChanged)
+ });
+
+ if (!success) {
+ qCritical() << "QtDisplayManager: registerNativeMethods() failed";
+ return JNI_FALSE;
+ }
jmethodID methodID;
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
- jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
- GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
- jobject serviceObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ jobject contextObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ if (!contextObject) {
+ GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
+ contextObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ }
+
+ if (!contextObject) {
+ __android_log_print(ANDROID_LOG_FATAL,"Qt", "Failed to get Activity or Service object");
+ return false;
+ }
+ const auto releaseContextObject = qScopeGuard([&env, contextObject]{
+ env->DeleteLocalRef(contextObject);
+ });
+
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));
clazz = env->GetObjectClass(m_classLoaderObject);
GET_AND_CHECK_METHOD(m_loadClassMethodID, clazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
- if (serviceObject)
- m_serviceObject = env->NewGlobalRef(serviceObject);
-
- if (activityObject)
- m_activityObject = env->NewGlobalRef(activityObject);
-
- jobject object = activityObject ? activityObject : serviceObject;
- if (object) {
- FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
- GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
- m_assets = env->NewGlobalRef(env->CallObjectMethod(object, methodID));
- m_assetManager = AAssetManager_fromJava(env, m_assets);
-
- GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
- m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(object, methodID));
-
- FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
- m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- GET_AND_CHECK_STATIC_METHOD(m_createBitmapMethodID, m_bitmapClass
- , "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
- FIND_AND_CHECK_CLASS("android/graphics/Bitmap$Config");
- jfieldID fieldId;
- GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
- m_ARGB_8888_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
- GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
- m_RGB_565_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
-
- FIND_AND_CHECK_CLASS("android/graphics/drawable/BitmapDrawable");
- m_bitmapDrawableClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- GET_AND_CHECK_METHOD(m_bitmapDrawableConstructorMethodID,
- m_bitmapDrawableClass,
- "<init>",
- "(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
- }
-
- return JNI_TRUE;
+
+ FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
+ GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
+ m_assets = env->NewGlobalRef(env->CallObjectMethod(contextObject, methodID));
+ m_assetManager = AAssetManager_fromJava(env.jniEnv(), m_assets);
+
+ GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
+ m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(contextObject, methodID));
+
+ FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
+ m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_AND_CHECK_STATIC_METHOD(m_createBitmapMethodID, m_bitmapClass,
+ "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
+ FIND_AND_CHECK_CLASS("android/graphics/Bitmap$Config");
+ jfieldID fieldId;
+ GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
+ m_ARGB_8888_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
+ GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
+ m_RGB_565_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
+
+ FIND_AND_CHECK_CLASS("android/graphics/drawable/BitmapDrawable");
+ m_bitmapDrawableClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_AND_CHECK_METHOD(m_bitmapDrawableConstructorMethodID,
+ m_bitmapDrawableClass,
+ "<init>", "(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
+
+ FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtActivityBase");
+ m_qtActivityClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtServiceBase");
+ m_qtServiceClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ return true;
}
QT_END_NAMESPACE
@@ -926,36 +876,29 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
initialized = true;
QT_USE_NAMESPACE
- typedef union {
- JNIEnv *nativeEnvironment;
- void *venv;
- } UnionJNIEnvToVoid;
-
- UnionJNIEnvToVoid uenv;
- uenv.venv = nullptr;
- m_javaVM = nullptr;
-
- if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
- __android_log_print(ANDROID_LOG_FATAL, "Qt", "GetEnv failed");
+ m_javaVM = vm;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ m_javaVM = nullptr;
+ __android_log_print(ANDROID_LOG_FATAL, "Qt", "Failed to initialize the JNI Environment");
return -1;
}
- JNIEnv *env = uenv.nativeEnvironment;
if (!registerNatives(env)
|| !QtAndroidInput::registerNatives(env)
|| !QtAndroidMenu::registerNatives(env)
|| !QtAndroidAccessibility::registerNatives(env)
- || !QtAndroidDialogHelpers::registerNatives(env)) {
+ || !QtAndroidDialogHelpers::registerNatives(env)
+ || !QAndroidPlatformClipboard::registerNatives(env)
+ || !QAndroidPlatformWindow::registerNatives(env)
+ || !QtAndroidWindowEmbedding::registerNatives(env)) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
}
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
- m_javaVM = vm;
// attach qt main thread data to this thread
- QObject threadSetter;
- if (threadSetter.thread())
- threadSetter.thread()->setObjectName("QtMainLoopThread");
+ QThread::currentThread()->setObjectName("QtMainLoopThread");
__android_log_print(ANDROID_LOG_INFO, "Qt", "qt started");
return JNI_VERSION_1_6;
}
diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h
index 4879d7e5b2..99fff96d2b 100644
--- a/src/plugins/platforms/android/androidjnimain.h
+++ b/src/plugins/platforms/android/androidjnimain.h
@@ -12,6 +12,7 @@
#include <QImage>
#include <private/qjnihelpers_p.h>
+#include <QtCore/QJniObject>
QT_BEGIN_NAMESPACE
@@ -22,26 +23,22 @@ class QAndroidPlatformIntegration;
class QWidget;
class QString;
class QWindow;
-class AndroidSurfaceClient;
+class QAndroidPlatformWindow;
class QBasicMutex;
+Q_DECLARE_JNI_CLASS(QtActivityDelegateBase, "org/qtproject/qt/android/QtActivityDelegateBase")
+Q_DECLARE_JNI_CLASS(QtInputDelegate, "org/qtproject/qt/android/QtInputDelegate")
+
namespace QtAndroid
{
QBasicMutex *platformInterfaceMutex();
QAndroidPlatformIntegration *androidPlatformIntegration();
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration);
void setQtThread(QThread *thread);
-
-
- int createSurface(AndroidSurfaceClient * client, const QRect &geometry, bool onTop, int imageDepth);
- int insertNativeView(jobject view, const QRect &geometry);
void setViewVisibility(jobject view, bool visible);
- void setSurfaceGeometry(int surfaceId, const QRect &geometry);
- void destroySurface(int surfaceId);
- void bringChildToFront(int surfaceId);
- void bringChildToBack(int surfaceId);
QWindow *topLevelWindowAt(const QPoint &globalPos);
+ QWindow *windowFromId(int windowId);
int availableWidthPixels();
int availableHeightPixels();
double scaledDensity();
@@ -50,8 +47,9 @@ namespace QtAndroid
jobject assets();
AAssetManager *assetManager();
jclass applicationClass();
- QtJniTypes::Activity activity();
- QtJniTypes::Service service();
+
+ QtJniTypes::QtActivityDelegateBase qtActivityDelegate();
+ QtJniTypes::QtInputDelegate qtInputDelegate();
// Keep synchronized with flags in ActivityDelegate.java
enum SystemUiVisibility {
@@ -67,10 +65,11 @@ namespace QtAndroid
void notifyAccessibilityLocationChange(uint accessibilityObjectId);
void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId);
+ void notifyObjectShow(uint parentObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId, jstring value);
void notifyScrolledEvent(uint accessibilityObjectId);
- void notifyQtAndroidPluginRunning(bool running);
+ void notifyNativePluginIntegrationReady(bool ready);
const char *classErrorMsgFmt();
const char *methodErrorMsgFmt();
@@ -78,6 +77,8 @@ namespace QtAndroid
QString deviceName();
bool blockEventLoopsWhenSuspended();
+
+ bool isQtApplication();
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjnimenu.cpp b/src/plugins/platforms/android/androidjnimenu.cpp
index 7aaa5d921c..8bf37d1af2 100644
--- a/src/plugins/platforms/android/androidjnimenu.cpp
+++ b/src/plugins/platforms/android/androidjnimenu.cpp
@@ -31,8 +31,6 @@ namespace QtAndroidMenu
static QWindow *activeTopLevelWindow = nullptr;
Q_CONSTINIT static QRecursiveMutex menuBarMutex;
- static jmethodID openContextMenuMethodID = 0;
-
static jmethodID clearMenuMethodID = 0;
static jmethodID addMenuItemMethodID = 0;
static int menuNoneValue = 0;
@@ -46,29 +44,31 @@ namespace QtAndroidMenu
void resetMenuBar()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "resetOptionsMenu");
+ qtActivityDelegate().callMethod<void>("resetOptionsMenu");
}
void openOptionsMenu()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "openOptionsMenu");
+ qtActivityDelegate().callMethod<void>("openOptionsMenu");
}
- void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env)
+ void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect)
{
QMutexLocker lock(&visibleMenuMutex);
if (visibleMenu)
pendingContextMenus.append(visibleMenu);
visibleMenu = menu;
menu->aboutToShow();
- env->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID, anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height());
+ qtActivityDelegate().callMethod<void>("openContextMenu",
+ anchorRect.x(), anchorRect.y(),
+ anchorRect.width(), anchorRect.height());
}
void hideContextMenu(QAndroidPlatformMenu *menu)
{
QMutexLocker lock(&visibleMenuMutex);
if (visibleMenu == menu) {
- QJniObject::callStaticMethod<void>(applicationClass(), "closeContextMenu");
+ qtActivityDelegate().callMethod<void>("closeContextMenu");
pendingContextMenus.clear();
} else {
pendingContextMenus.removeOne(menu);
@@ -211,8 +211,10 @@ namespace QtAndroidMenu
return order;
}
- static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
+
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&menuBarMutex);
if (!visibleMenuBar)
@@ -249,8 +251,11 @@ namespace QtAndroidMenu
return order ? JNI_TRUE : JNI_FALSE;
}
- static jboolean onOptionsItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
+ static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+
QMutexLocker lock(&menuBarMutex);
if (!visibleMenuBar)
return JNI_FALSE;
@@ -260,7 +265,7 @@ namespace QtAndroidMenu
QAndroidPlatformMenuItem *item = static_cast<QAndroidPlatformMenuItem *>(menus.front()->menuItemForId(menuId));
if (item) {
if (item->menu()) {
- showContextMenu(item->menu(), QRect(), env);
+ showContextMenu(item->menu(), QRect());
} else {
if (item->isCheckable())
item->setChecked(checked);
@@ -270,18 +275,23 @@ namespace QtAndroidMenu
} else {
QAndroidPlatformMenu *menu = static_cast<QAndroidPlatformMenu *>(visibleMenuBar->menuForId(menuId));
if (menu)
- showContextMenu(menu, QRect(), env);
+ showContextMenu(menu, QRect());
}
return JNI_TRUE;
}
- static void onOptionsMenuClosed(JNIEnv */*env*/, jobject /*thiz*/, jobject /*menu*/)
+ static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+ Q_UNUSED(menu)
}
- static void onCreateContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
+
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
@@ -295,8 +305,9 @@ namespace QtAndroidMenu
addAllMenuItemsToMenu(env, menu, visibleMenu);
}
- static void fillContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
@@ -305,13 +316,16 @@ namespace QtAndroidMenu
addAllMenuItemsToMenu(env, menu, visibleMenu);
}
- static jboolean onContextItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
+ static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+
QMutexLocker lock(&visibleMenuMutex);
QAndroidPlatformMenuItem * item = static_cast<QAndroidPlatformMenuItem *>(visibleMenu->menuItemForId(menuId));
if (item) {
if (item->menu()) {
- showContextMenu(item->menu(), QRect(), env);
+ showContextMenu(item->menu(), QRect());
} else {
if (item->isCheckable())
item->setChecked(checked);
@@ -328,8 +342,12 @@ namespace QtAndroidMenu
return JNI_TRUE;
}
- static void onContextMenuClosed(JNIEnv *env, jobject /*thiz*/, jobject /*menu*/)
+ static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+ Q_UNUSED(menu)
+
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
return;
@@ -337,7 +355,7 @@ namespace QtAndroidMenu
visibleMenu->aboutToHide();
visibleMenu = 0;
if (!pendingContextMenus.empty())
- showContextMenu(pendingContextMenus.takeLast(), QRect(), env);
+ showContextMenu(pendingContextMenus.takeLast(), QRect());
}
static JNINativeMethod methods[] = {
@@ -378,17 +396,15 @@ namespace QtAndroidMenu
return false; \
}
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
jclass appClass = applicationClass();
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(appClass, methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
- GET_AND_CHECK_STATIC_METHOD(openContextMenuMethodID, appClass, "openContextMenu", "(IIII)V");
-
jclass clazz;
FIND_AND_CHECK_CLASS("android/view/Menu");
GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V");
diff --git a/src/plugins/platforms/android/androidjnimenu.h b/src/plugins/platforms/android/androidjnimenu.h
index 1645ce750b..e10ad930d9 100644
--- a/src/plugins/platforms/android/androidjnimenu.h
+++ b/src/plugins/platforms/android/androidjnimenu.h
@@ -15,12 +15,13 @@ class QAndroidPlatformMenuItem;
class QWindow;
class QRect;
class QPoint;
+class QJniEnvironment;
namespace QtAndroidMenu
{
// Menu support
void openOptionsMenu();
- void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env);
+ void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect);
void hideContextMenu(QAndroidPlatformMenu *menu);
void syncMenu(QAndroidPlatformMenu *menu);
void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu);
@@ -31,7 +32,7 @@ namespace QtAndroidMenu
void removeMenuBar(QAndroidPlatformMenuBar *menuBar);
// Menu support
- bool registerNatives(JNIEnv *env);
+ bool registerNatives(QJniEnvironment &env);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidsurfaceclient.h b/src/plugins/platforms/android/androidsurfaceclient.h
deleted file mode 100644
index dded9a1f66..0000000000
--- a/src/plugins/platforms/android/androidsurfaceclient.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef ANDROIDSURFACECLIENT_H
-#define ANDROIDSURFACECLIENT_H
-#include <QMutex>
-#include <jni.h>
-
-QT_BEGIN_NAMESPACE
-
-class AndroidSurfaceClient
-{
-public:
- virtual void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) = 0;
- void lockSurface() { m_surfaceMutex.lock(); }
- void unlockSurface() { m_surfaceMutex.unlock(); }
-
-protected:
- QMutex m_surfaceMutex;
-};
-
-QT_END_NAMESPACE
-
-#endif // ANDROIDSURFACECLIENT_H
diff --git a/src/plugins/platforms/android/androidwindowembedding.cpp b/src/plugins/platforms/android/androidwindowembedding.cpp
new file mode 100644
index 0000000000..65dabcac66
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.cpp
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "androidwindowembedding.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnitypes.h>
+#include <QtGui/qwindow.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_JNI_CLASS(QtView, "org/qtproject/qt/android/QtView");
+
+namespace QtAndroidWindowEmbedding {
+ void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView,
+ jint x, jint y, jint width, jint height)
+ {
+ // QWindow should be constructed on the Qt thread rather than directly in the caller thread
+ // To avoid hitting checkReceiverThread assert in QCoreApplication::doNotify
+ QMetaObject::invokeMethod(qApp, [rootView, x, y, width, height] {
+ QWindow *parentWindow = QWindow::fromWinId(reinterpret_cast<WId>(rootView.object()));
+ parentWindow->setGeometry(x, y, width, height);
+ rootView.callMethod<void>("createWindow", reinterpret_cast<jlong>(parentWindow));
+ });
+ }
+
+ void deleteWindow(JNIEnv *, jclass, jlong windowRef)
+ {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ window->deleteLater();
+ }
+
+ void setWindowVisible(JNIEnv *, jclass, jlong windowRef, jboolean visible)
+ {
+ QMetaObject::invokeMethod(qApp, [windowRef, visible] {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ if (visible) {
+ window->showNormal();
+ if (!window->parent()->isVisible())
+ window->parent()->showNormal();
+ } else {
+ window->hide();
+ }
+ });
+ }
+
+ void resizeWindow(JNIEnv *, jclass, jlong windowRef, jint x, jint y, jint width, jint height)
+ {
+ QMetaObject::invokeMethod(qApp, [windowRef, x, y, width, height] {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ QWindow *parent = window->parent();
+ if (parent)
+ parent->setGeometry(x, y, width, height);
+ window->setGeometry(0, 0, width, height);
+ });
+ }
+
+ bool registerNatives(QJniEnvironment& env) {
+ return env.registerNativeMethods(
+ QtJniTypes::Traits<QtJniTypes::QtView>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(createRootWindow, QtAndroidWindowEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(deleteWindow, QtAndroidWindowEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(setWindowVisible, QtAndroidWindowEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(resizeWindow, QtAndroidWindowEmbedding) });
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidwindowembedding.h b/src/plugins/platforms/android/androidwindowembedding.h
new file mode 100644
index 0000000000..b7b0e1205f
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTANDROIDWINDOWEMBEDDING_H
+#define QTANDROIDWINDOWEMBEDDING_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_JNI_CLASS(View, "android/view/View");
+
+namespace QtAndroidWindowEmbedding
+{
+ bool registerNatives(QJniEnvironment& env);
+ void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView,
+ jint x, jint y,jint width, jint height);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(createRootWindow)
+ void deleteWindow(JNIEnv *, jclass, jlong window);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(deleteWindow)
+ void setWindowVisible(JNIEnv *, jclass, jlong window, jboolean visible);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setWindowVisible)
+ void resizeWindow(JNIEnv *, jclass, jlong windowRef, jint x, jint y, jint width, jint height);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(resizeWindow)
+};
+
+QT_END_NAMESPACE
+
+#endif // QTANDROIDWINDOWEMBEDDING_H
diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
index c7785486b0..4ea6536cef 100644
--- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
+++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
@@ -142,17 +142,13 @@ public:
return m_path + at(m_index).name;
}
- bool hasNext() const
+ bool advance()
{
- return !empty() && m_index + 1 < size();
- }
-
- std::optional<std::pair<QString, AssetItem>> next()
- {
- if (!hasNext())
- return {};
- ++m_index;
- return std::pair<QString, AssetItem>(currentFileName(), at(m_index));
+ if (!empty() && m_index + 1 < size()) {
+ ++m_index;
+ return true;
+ }
+ return false;
}
private:
@@ -171,7 +167,7 @@ public:
AndroidAbstractFileEngineIterator(QDir::Filters filters,
const QStringList &nameFilters,
const QString &path)
- : QAbstractFileEngineIterator(filters, nameFilters)
+ : QAbstractFileEngineIterator(path, filters, nameFilters)
{
m_currentIterator = FolderIterator::fromCache(cleanedAssetPath(path), true);
}
@@ -195,21 +191,9 @@ public:
return m_currentIterator->currentFilePath();
}
- bool hasNext() const override
- {
- if (!m_currentIterator)
- return false;
- return m_currentIterator->hasNext();
- }
-
- QString next() override
+ bool advance() override
{
- if (!m_currentIterator)
- return {};
- auto res = m_currentIterator->next();
- if (!res)
- return {};
- return res->first;
+ return m_currentIterator ? m_currentIterator->advance() : false;
}
private:
@@ -307,9 +291,9 @@ public:
return prefixedPath(m_fileName);
case BaseName:
if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
- return prefixedPath(m_fileName.mid(pos));
+ return m_fileName.mid(pos + 1);
else
- return prefixedPath(m_fileName);
+ return m_fileName;
case PathName:
case AbsolutePathName:
case CanonicalPathName:
@@ -367,10 +351,12 @@ public:
m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr);
}
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override
+ IteratorUniquePtr
+ beginEntryList(const QString &, QDir::Filters filters, const QStringList &filterNames) override
{
+ // AndroidAbstractFileEngineIterator use `m_fileName` as the path
if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder)
- return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName);
+ return std::make_unique<AndroidAbstractFileEngineIterator>(filters, filterNames, m_fileName);
return nullptr;
}
@@ -393,13 +379,14 @@ AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler()
m_assetManager = QtAndroid::assetManager();
}
-QAbstractFileEngine * AndroidAssetsFileEngineHandler::create(const QString &fileName) const
+std::unique_ptr<QAbstractFileEngine>
+AndroidAssetsFileEngineHandler::create(const QString &fileName) const
{
if (fileName.isEmpty())
- return nullptr;
+ return {};
if (!fileName.startsWith(assetsPrefix))
- return nullptr;
+ return {};
QString path = fileName.mid(prefixSize);
path.replace("//"_L1, "/"_L1);
@@ -407,7 +394,7 @@ QAbstractFileEngine * AndroidAssetsFileEngineHandler::create(const QString &file
path.remove(0, 1);
if (path.endsWith(u'/'))
path.chop(1);
- return new AndroidAbstractFileEngine(m_assetManager, path);
+ return std::make_unique<AndroidAbstractFileEngine>(m_assetManager, path);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
index 50c6914c24..973a61fbfa 100644
--- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
+++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
@@ -15,9 +15,10 @@ QT_BEGIN_NAMESPACE
class AndroidAssetsFileEngineHandler: public QAbstractFileEngineHandler
{
+ Q_DISABLE_COPY_MOVE(AndroidAssetsFileEngineHandler)
public:
AndroidAssetsFileEngineHandler();
- QAbstractFileEngine *create(const QString &fileName) const override;
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
private:
AAssetManager *m_assetManager;
diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp
index 4c111be829..62212ff63d 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.cpp
+++ b/src/plugins/platforms/android/qandroidinputcontext.cpp
@@ -80,9 +80,7 @@ static jboolean beginBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ BEGINBATCH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ BEGINBATCH";
jboolean res = JNI_FALSE;
runOnQtThread([&res]{res = m_androidInputContext->beginBatchEdit();});
return res;
@@ -93,9 +91,7 @@ static jboolean endBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ ENDBATCH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ ENDBATCH";
jboolean res = JNI_FALSE;
runOnQtThread([&res]{res = m_androidInputContext->endBatchEdit();});
@@ -113,9 +109,7 @@ static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint new
QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
env->ReleaseStringChars(text, jstr);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ COMMIT" << str << newCursorPosition;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ COMMIT" << str << newCursorPosition;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->commitText(str, newCursorPosition);});
return res;
@@ -126,9 +120,7 @@ static jboolean deleteSurroundingText(JNIEnv */*env*/, jobject /*thiz*/, jint le
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ DELETE" << leftLength << rightLength;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ DELETE" << leftLength << rightLength;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->deleteSurroundingText(leftLength, rightLength);});
return res;
@@ -139,9 +131,7 @@ static jboolean finishComposingText(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ FINISH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ FINISH";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->finishComposingText();});
return res;
@@ -165,9 +155,7 @@ static jobject getExtractedText(JNIEnv *env, jobject /*thiz*/, int hintMaxChars,
QAndroidInputContext::ExtractedText extractedText;
runOnQtThread([&]{extractedText = m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1("0x") + QString::number(flags,16) << extractedText.text << "partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset << "sel:" << extractedText.selectionStart << extractedText.selectionEnd << "offset:" << extractedText.startOffset;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1("0x") + QString::number(flags,16) << extractedText.text << "partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset << "sel:" << extractedText.selectionStart << extractedText.selectionEnd << "offset:" << extractedText.startOffset;
jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
@@ -190,9 +178,7 @@ static jstring getSelectedText(JNIEnv *env, jobject /*thiz*/, jint flags)
QString text;
runOnQtThread([&]{text = m_androidInputContext->getSelectedText(flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETSEL" << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETSEL" << text;
if (text.isEmpty())
return 0;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
@@ -205,9 +191,7 @@ static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, ji
QString text;
runOnQtThread([&]{text = m_androidInputContext->getTextAfterCursor(length, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETA" << length << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETA" << length << text;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
}
@@ -218,9 +202,7 @@ static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, j
QString text;
runOnQtThread([&]{text = m_androidInputContext->getTextBeforeCursor(length, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETB" << length << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETB" << length << text;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
}
@@ -234,9 +216,7 @@ static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, ji
QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
env->ReleaseStringChars(text, jstr);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SET" << str << newCursorPosition;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SET" << str << newCursorPosition;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setComposingText(str, newCursorPosition);});
return res;
@@ -247,9 +227,7 @@ static jboolean setComposingRegion(JNIEnv */*env*/, jobject /*thiz*/, jint start
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SETR" << start << end;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SETR" << start << end;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setComposingRegion(start, end);});
return res;
@@ -261,9 +239,7 @@ static jboolean setSelection(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SETSEL" << start << end;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SETSEL" << start << end;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setSelection(start, end);});
return res;
@@ -275,9 +251,7 @@ static jboolean selectAll(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ SELALL");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SELALL";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->selectAll();});
return res;
@@ -288,9 +262,7 @@ static jboolean cut(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->cut();});
return res;
@@ -301,9 +273,7 @@ static jboolean copy(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->copy();});
return res;
@@ -314,9 +284,7 @@ static jboolean copyURL(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->copyURL();});
return res;
@@ -327,9 +295,7 @@ static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ PASTE");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ PASTE";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->paste();});
return res;
@@ -340,14 +306,24 @@ static jboolean updateCursorPosition(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ UPDATECURSORPOS");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ UPDATECURSORPOS";
runOnQtThread([&]{m_androidInputContext->updateCursorPosition();});
return true;
}
+static void reportFullscreenMode(JNIEnv */*env*/, jobject /*thiz*/, jboolean enabled)
+{
+ if (!m_androidInputContext)
+ return;
+
+ runOnQtThread([&]{m_androidInputContext->reportFullscreenMode(enabled);});
+}
+
+static jboolean fullscreenMode(JNIEnv */*env*/, jobject /*thiz*/)
+{
+ return m_androidInputContext ? m_androidInputContext->fullscreenMode() : false;
+}
static JNINativeMethod methods[] = {
{"beginBatchEdit", "()Z", (void *)beginBatchEdit},
@@ -368,7 +344,9 @@ static JNINativeMethod methods[] = {
{"copy", "()Z", (void *)copy},
{"copyURL", "()Z", (void *)copyURL},
{"paste", "()Z", (void *)paste},
- {"updateCursorPosition", "()Z", (void *)updateCursorPosition}
+ {"updateCursorPosition", "()Z", (void *)updateCursorPosition},
+ {"reportFullscreenMode", "(Z)V", (void *)reportFullscreenMode},
+ {"fullscreenMode", "()Z", (void *)fullscreenMode}
};
static QRect screenInputItemRectangle()
@@ -385,6 +363,7 @@ QAndroidInputContext::QAndroidInputContext()
, m_handleMode(Hidden)
, m_batchEditNestingLevel(0)
, m_focusObject(0)
+ , m_fullScreenMode(false)
{
QJniEnvironment env;
jclass clazz = env.findClass(QtNativeInputConnectionClassName);
@@ -567,12 +546,29 @@ void QAndroidInputContext::updateCursorPosition()
}
}
+bool QAndroidInputContext::isImhNoTextHandlesSet()
+{
+ QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
+ if (query.isNull())
+ return false;
+ return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
+}
+
void QAndroidInputContext::updateSelectionHandles()
{
+ if (m_fullScreenMode) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES");
if (noHandles || !m_focusObject)
return;
+ if (isImhNoTextHandlesSet()) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
+
auto im = qGuiApp->inputMethod();
QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
@@ -586,6 +582,11 @@ void QAndroidInputContext::updateSelectionHandles()
bool readOnly = readOnlyVariant.toBool();
QPlatformWindow *qPlatformWindow = qGuiApp->focusWindow()->handle();
+ if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
+
if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
QtAndroidInput::updateHandles(Hidden);
return;
@@ -612,9 +613,8 @@ void QAndroidInputContext::updateSelectionHandles()
if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
buttons |= EditContext::SelectAllButton;
QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
- // The VK is hidden, reset the timer
- if (m_hideCursorHandleTimer.isActive())
- m_hideCursorHandleTimer.start();
+ m_hideCursorHandleTimer.start();
+
return;
}
@@ -788,7 +788,15 @@ void QAndroidInputContext::touchDown(int x, int y)
focusObjectStopComposing();
}
- updateSelectionHandles();
+ // Check if cursor is visible in focused window before updating handles
+ QPlatformWindow *window = qGuiApp->focusWindow()->handle();
+ const QRectF curRect = cursorRectangle();
+ const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
+ const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
+ const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
+
+ if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
+ updateSelectionHandles();
}
}
@@ -895,6 +903,9 @@ void QAndroidInputContext::showInputPanel()
if (query.isNull())
return;
+ if (!qGuiApp->focusWindow()->handle())
+ return; // not a real window, probably VR/XR
+
disconnect(m_updateCursorPosConnection);
m_updateCursorPosConnection = {};
@@ -1112,6 +1123,25 @@ jboolean QAndroidInputContext::finishComposingText()
return JNI_TRUE;
}
+void QAndroidInputContext::reportFullscreenMode(jboolean enabled)
+{
+ m_fullScreenMode = enabled;
+ BatchEditLock batchEditLock(this);
+ if (!focusObjectStopComposing())
+ return;
+
+ if (enabled)
+ m_handleMode = Hidden;
+
+ updateSelectionHandles();
+}
+
+// Called in calling thread's context
+jboolean QAndroidInputContext::fullscreenMode()
+{
+ return m_fullScreenMode;
+}
+
bool QAndroidInputContext::focusObjectIsComposing() const
{
return m_composingCursor != -1;
@@ -1534,9 +1564,7 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end)
}
if (start < textOffset || end - textOffset > text.length()) {
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qWarning("setComposingRegion: failed to retrieve text from composing region");
-#endif
+ qCDebug(lcQpaInputMethods) << "Warning: setComposingRegion: failed to retrieve text from composing region";
return JNI_TRUE;
}
diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h
index c93aae142a..038286c4b8 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.h
+++ b/src/plugins/platforms/android/qandroidinputcontext.h
@@ -9,6 +9,8 @@
#include <functional>
#include <jni.h>
#include <qevent.h>
+
+#include <QtCore/qpointer.h>
#include <QTimer>
QT_BEGIN_NAMESPACE
@@ -98,6 +100,8 @@ public:
jboolean copy();
jboolean copyURL();
jboolean paste();
+ void reportFullscreenMode(jboolean enabled);
+ jboolean fullscreenMode();
public slots:
void safeCall(const std::function<void()> &func, Qt::ConnectionType conType = Qt::BlockingQueuedConnection);
@@ -113,6 +117,7 @@ private slots:
void showInputPanelLater(Qt::ApplicationState);
private:
+ bool isImhNoTextHandlesSet();
void sendInputMethodEvent(QInputMethodEvent *event);
QSharedPointer<QInputMethodQueryEvent> focusObjectInputMethodQuery(Qt::InputMethodQueries queries = Qt::ImQueryAll);
bool focusObjectIsComposing() const;
@@ -129,6 +134,7 @@ private:
int m_batchEditNestingLevel;
QPointer<QObject> m_focusObject;
QTimer m_hideCursorHandleTimer;
+ bool m_fullScreenMode;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QAndroidInputContext::HandleModes)
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
index 61fc21a6bc..ea7f22295d 100644
--- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
+++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
@@ -28,6 +28,8 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *
QtAndroidAccessibility::notifyLocationChange(event->uniqueId());
} else if (event->type() == QAccessible::ObjectHide) {
QtAndroidAccessibility::notifyObjectHide(event->uniqueId());
+ } else if (event->type() == QAccessible::ObjectShow) {
+ QtAndroidAccessibility::notifyObjectShow(event->uniqueId());
} else if (event->type() == QAccessible::Focus) {
QtAndroidAccessibility::notifyObjectFocus(event->uniqueId());
} else if (event->type() == QAccessible::ValueChanged) {
diff --git a/src/plugins/platforms/android/qandroidplatformbackingstore.cpp b/src/plugins/platforms/android/qandroidplatformbackingstore.cpp
deleted file mode 100644
index 07a1c835d3..0000000000
--- a/src/plugins/platforms/android/qandroidplatformbackingstore.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "qandroidplatformbackingstore.h"
-#include "qandroidplatformscreen.h"
-#include "qandroidplatformwindow.h"
-#include <qpa/qplatformscreen.h>
-
-QT_BEGIN_NAMESPACE
-
-QAndroidPlatformBackingStore::QAndroidPlatformBackingStore(QWindow *window)
- : QPlatformBackingStore(window)
-{
- if (window->handle())
- setBackingStore(window);
-}
-
-QPaintDevice *QAndroidPlatformBackingStore::paintDevice()
-{
- return &m_image;
-}
-
-void QAndroidPlatformBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
-{
- Q_UNUSED(offset);
-
- if (!m_backingStoreSet)
- setBackingStore(window);
-
- (static_cast<QAndroidPlatformWindow *>(window->handle()))->repaint(region);
-}
-
-void QAndroidPlatformBackingStore::resize(const QSize &size, const QRegion &staticContents)
-{
- Q_UNUSED(staticContents);
-
- if (m_image.size() != size)
- m_image = QImage(size, window()->screen()->handle()->format());
-}
-
-void QAndroidPlatformBackingStore::setBackingStore(QWindow *window)
-{
- if (window->surfaceType() == QSurface::RasterSurface || window->surfaceType() == QSurface::RasterGLSurface) {
- (static_cast<QAndroidPlatformWindow *>(window->handle()))->setBackingStore(this);
- m_backingStoreSet = true;
- } else {
- qWarning("QAndroidPlatformBackingStore does not support OpenGL-only windows.");
- }
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformbackingstore.h b/src/plugins/platforms/android/qandroidplatformbackingstore.h
deleted file mode 100644
index 810305ac45..0000000000
--- a/src/plugins/platforms/android/qandroidplatformbackingstore.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QANDROIDPLATFORMBACKINGSTORE_H
-#define QANDROIDPLATFORMBACKINGSTORE_H
-
-#include <qpa/qplatformbackingstore.h>
-#include <qpa/qwindowsysteminterface.h>
-
-QT_BEGIN_NAMESPACE
-
-class QAndroidPlatformBackingStore : public QPlatformBackingStore
-{
-public:
- explicit QAndroidPlatformBackingStore(QWindow *window);
- QPaintDevice *paintDevice() override;
- void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
- void resize(const QSize &size, const QRegion &staticContents) override;
- QImage toImage() const override { return m_image; }
- void setBackingStore(QWindow *window);
-protected:
- QImage m_image;
- bool m_backingStoreSet = false;
-};
-
-QT_END_NAMESPACE
-
-#endif // QANDROIDPLATFORMBACKINGSTORE_H
diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.cpp b/src/plugins/platforms/android/qandroidplatformclipboard.cpp
index 39a508a1b3..e5ed33b9b0 100644
--- a/src/plugins/platforms/android/qandroidplatformclipboard.cpp
+++ b/src/plugins/platforms/android/qandroidplatformclipboard.cpp
@@ -1,15 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qandroidplatformclipboard.h"
-#include "androidjniclipboard.h"
+
+#include <QtCore/QUrl>
+#include <QtCore/QJniEnvironment>
+#include <QtCore/QJniObject>
+#include <QtCore/private/qjnihelpers_p.h>
+
#ifndef QT_NO_CLIPBOARD
+using namespace QtJniTypes;
+
QT_BEGIN_NAMESPACE
+void QAndroidPlatformClipboard::onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(obj)
+
+ auto *clipboardManager = reinterpret_cast<QAndroidPlatformClipboard *>(nativePointer);
+ if (clipboardManager)
+ clipboardManager->emitChanged(QClipboard::Clipboard);
+}
+
QAndroidPlatformClipboard::QAndroidPlatformClipboard()
{
- QtAndroidClipboard::setClipboardManager(this);
+ m_clipboardManager = QtClipboardManager::construct(QtAndroidPrivate::context(),
+ reinterpret_cast<jlong>(this));
}
QAndroidPlatformClipboard::~QAndroidPlatformClipboard()
@@ -18,24 +37,66 @@ QAndroidPlatformClipboard::~QAndroidPlatformClipboard()
delete data;
}
+QMimeData *QAndroidPlatformClipboard::getClipboardMimeData()
+{
+ QMimeData *data = new QMimeData;
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardText")) {
+ data->setText(m_clipboardManager.callMethod<QString>("getClipboardText"));
+ }
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardHtml")) {
+ data->setHtml(m_clipboardManager.callMethod<QString>("getClipboardHtml"));
+ }
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardUri")) {
+ auto uris = m_clipboardManager.callMethod<QString[]>("getClipboardUris");
+ if (uris.isValid()) {
+ QList<QUrl> urls;
+ for (const QString &uri : uris)
+ urls << QUrl(uri);
+ data->setUrls(urls);
+ }
+ }
+ return data;
+}
+
QMimeData *QAndroidPlatformClipboard::mimeData(QClipboard::Mode mode)
{
Q_UNUSED(mode);
Q_ASSERT(supportsMode(mode));
if (data)
data->deleteLater();
- data = QtAndroidClipboard::getClipboardMimeData();
+ data = getClipboardMimeData();
return data;
}
+void QAndroidPlatformClipboard::clearClipboardData()
+{
+ m_clipboardManager.callMethod<void>("clearClipData");
+}
+
+void QAndroidPlatformClipboard::setClipboardMimeData(QMimeData *data)
+{
+ clearClipboardData();
+ auto context = QtAndroidPrivate::context();
+ if (data->hasUrls()) {
+ QList<QUrl> urls = data->urls();
+ for (const auto &u : std::as_const(urls))
+ m_clipboardManager.callMethod<void>("setClipboardUri", context, u.toEncoded());
+ } else if (data->hasHtml()) { // html can contain text
+ m_clipboardManager.callMethod<void>("setClipboardHtml",
+ context, data->text(), data->html());
+ } else if (data->hasText()) { // hasText must be the last (the order matter here)
+ m_clipboardManager.callMethod<void>("setClipboardText", context, data->text());
+ }
+}
+
void QAndroidPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
{
if (!data) {
- QtAndroidClipboard::clearClipboardData();
+ clearClipboardData();
return;
}
if (data && supportsMode(mode))
- QtAndroidClipboard::setClipboardMimeData(data);
+ setClipboardMimeData(data);
if (data != 0)
data->deleteLater();
}
@@ -45,6 +106,18 @@ bool QAndroidPlatformClipboard::supportsMode(QClipboard::Mode mode) const
return QClipboard::Clipboard == mode;
}
+bool QAndroidPlatformClipboard::registerNatives(QJniEnvironment &env)
+{
+ bool success = env.registerNativeMethods(Traits<QtClipboardManager>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(onClipboardDataChanged, QAndroidPlatformClipboard) });
+ if (!success) {
+ qCritical() << "QtClipboardManager: registerNativeMethods() failed";
+ return false;
+ }
+
+ return true;
+}
+
QT_END_NAMESPACE
#endif // QT_NO_CLIPBOARD
diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.h b/src/plugins/platforms/android/qandroidplatformclipboard.h
index 1778ca5b28..ab5c527f88 100644
--- a/src/plugins/platforms/android/qandroidplatformclipboard.h
+++ b/src/plugins/platforms/android/qandroidplatformclipboard.h
@@ -1,3 +1,4 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@@ -6,10 +7,14 @@
#include <qpa/qplatformclipboard.h>
#include <QMimeData>
+#include <QtCore/qjnitypes.h>
#ifndef QT_NO_CLIPBOARD
+
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(QtClipboardManager, "org/qtproject/qt/android/QtClipboardManager");
+
class QAndroidPlatformClipboard : public QPlatformClipboard
{
public:
@@ -18,8 +23,19 @@ public:
QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override;
+
+ static bool registerNatives(QJniEnvironment &env);
+
private:
+ QMimeData *getClipboardMimeData();
+ void setClipboardMimeData(QMimeData *data);
+ void clearClipboardData();
+
+ static void onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(onClipboardDataChanged)
+
QMimeData *data = nullptr;
+ QtJniTypes::QtClipboardManager m_clipboardManager = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
index 81c6b41d65..2b9a1194d2 100644
--- a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
+++ b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
@@ -19,7 +19,7 @@ static jclass g_messageDialogHelperClass = nullptr;
QAndroidPlatformMessageDialogHelper::QAndroidPlatformMessageDialogHelper()
: m_javaMessageDialog(g_messageDialogHelperClass, "(Landroid/app/Activity;)V",
- QtAndroid::activity())
+ QtAndroidPrivate::activity().object())
{
}
@@ -54,7 +54,7 @@ bool QAndroidPlatformMessageDialogHelper::show(Qt::WindowFlags windowFlags,
if (!opt.data())
return false;
- if (opt->supressionCheckBoxEnabled())
+ if (!opt->checkBoxLabel().isNull())
return false; // Can't support
m_javaMessageDialog.callMethod<void>("setStandardIcon", "(I)V", opt->standardIcon());
@@ -150,7 +150,7 @@ static void dialogResult(JNIEnv * /*env*/, jobject /*thiz*/, jlong handler, int
QMetaObject::invokeMethod(object, "dialogResult", Qt::QueuedConnection, Q_ARG(int, buttonID));
}
-static JNINativeMethod methods[] = {
+static const JNINativeMethod methods[] = {
{"dialogResult", "(JI)V", (void *)dialogResult}
};
@@ -162,21 +162,19 @@ static JNINativeMethod methods[] = {
return false; \
}
-bool registerNatives(JNIEnv *env)
+bool registerNatives(QJniEnvironment &env)
{
const char QtMessageHandlerHelperClassName[] = "org/qtproject/qt/android/QtMessageDialogHelper";
- QJniEnvironment qenv;
- jclass clazz = qenv.findClass(QtMessageHandlerHelperClassName);
+ jclass clazz = env.findClass(QtMessageHandlerHelperClassName);
if (!clazz) {
__android_log_print(ANDROID_LOG_FATAL, QtAndroid::qtTagText(), QtAndroid::classErrorMsgFmt()
, QtMessageHandlerHelperClassName);
return false;
}
g_messageDialogHelperClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNativeDialogHelper");
- jclass appClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods("org/qtproject/qt/android/QtNativeDialogHelper",
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/qandroidplatformdialoghelpers.h b/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
index f06d2a9990..86f1cf77e7 100644
--- a/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
+++ b/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
@@ -8,12 +8,13 @@
#include <jni.h>
#include <QEventLoop>
-#include <QtCore/QJniEnvironment>
#include <QtCore/QJniObject>
#include <qpa/qplatformdialoghelper.h>
QT_BEGIN_NAMESPACE
+class QJniEnvironment;
+
namespace QtAndroidDialogHelpers {
class QAndroidPlatformMessageDialogHelper: public QPlatformMessageDialogHelper
@@ -41,7 +42,7 @@ private:
};
-bool registerNatives(JNIEnv *env);
+bool registerNatives(QJniEnvironment &env);
}
diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
index 3723e33371..d8a5c58d2c 100644
--- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
@@ -25,7 +25,7 @@ const char JniIntentClass[] = "android/content/Intent";
QAndroidPlatformFileDialogHelper::QAndroidPlatformFileDialogHelper()
: QPlatformFileDialogHelper(),
- m_activity(QtAndroid::activity())
+ m_activity(QtAndroidPrivate::activity())
{
}
@@ -45,8 +45,8 @@ bool QAndroidPlatformFileDialogHelper::handleActivityResult(jint requestCode, ji
if (uri.isValid()) {
takePersistableUriPermission(uri);
m_selectedFile.append(QUrl(uri.toString()));
- Q_EMIT fileSelected(m_selectedFile.first());
- Q_EMIT currentChanged(m_selectedFile.first());
+ Q_EMIT fileSelected(m_selectedFile.constFirst());
+ Q_EMIT currentChanged(m_selectedFile.constFirst());
Q_EMIT accept();
return true;
@@ -65,7 +65,7 @@ bool QAndroidPlatformFileDialogHelper::handleActivityResult(jint requestCode, ji
m_selectedFile.append(itemUri.toString());
}
Q_EMIT filesSelected(m_selectedFile);
- Q_EMIT currentChanged(m_selectedFile.first());
+ Q_EMIT currentChanged(m_selectedFile.constFirst());
Q_EMIT accept();
}
@@ -146,12 +146,15 @@ void QAndroidPlatformFileDialogHelper::setMimeTypes()
{
QStringList mimeTypes = options()->mimeTypeFilters();
const QStringList nameFilters = options()->nameFilters();
- const QString nameFilter = nameFilters.isEmpty() ? QString() : nameFilters.first();
- if (!nameFilter.isEmpty()) {
+ if (!nameFilters.isEmpty()) {
QMimeDatabase db;
- for (const QString &filter : nameFilterExtensions(nameFilter))
- mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
+ for (auto filter : nameFilters) {
+ if (!filter.isEmpty()) {
+ for (const QString &filter : nameFilterExtensions(filter))
+ mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
+ }
+ }
}
const QString initialType = mimeTypes.size() == 1 ? mimeTypes.at(0) : "*/*"_L1;
diff --git a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
index 01636d2ba5..82a10dac07 100644
--- a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QDir>
+#include <QLocale>
#include "qandroidplatformfontdatabase.h"
@@ -47,6 +48,38 @@ QStringList QAndroidPlatformFontDatabase::fallbacksForFamily(const QString &fami
QChar::Script script) const
{
QStringList result;
+
+ // Prepend CJK fonts by the locale.
+ QLocale locale = QLocale::system();
+ switch (locale.language()) {
+ case QLocale::Chinese: {
+ switch (locale.territory()) {
+ case QLocale::China:
+ case QLocale::Singapore:
+ result.append(QStringLiteral("Noto Sans Mono CJK SC"));
+ break;
+ case QLocale::Taiwan:
+ case QLocale::HongKong:
+ case QLocale::Macao:
+ result.append(QStringLiteral("Noto Sans Mono CJK TC"));
+ break;
+ default:
+ // no modifications.
+ break;
+ }
+ break;
+ }
+ case QLocale::Japanese:
+ result.append(QStringLiteral("Noto Sans Mono CJK JP"));
+ break;
+ case QLocale::Korean:
+ result.append(QStringLiteral("Noto Sans Mono CJK KR"));
+ break;
+ default:
+ // no modifications.
+ break;
+ }
+
if (styleHint == QFont::Monospace || styleHint == QFont::Courier)
result.append(QString(qgetenv("QT_ANDROID_FONTS_MONOSPACE")).split(u';'));
else if (styleHint == QFont::Serif)
diff --git a/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp b/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
index c2ce2c84b2..e84a481a2b 100644
--- a/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
@@ -6,84 +6,101 @@
#include <QtCore/qvariant.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/private/qjnihelpers_p.h>
+#include <QtCore/qjnitypes.h>
QT_BEGIN_NAMESPACE
QAndroidPlatformForeignWindow::QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle)
- : QAndroidPlatformWindow(window),
- m_surfaceId(-1)
+ : QAndroidPlatformWindow(window), m_view(nullptr), m_nativeViewInserted(false)
{
m_view = reinterpret_cast<jobject>(nativeHandle);
- if (m_view.isValid())
- QtAndroid::setViewVisibility(m_view.object(), false);
-}
+ if (isEmbeddingContainer()) {
+ m_nativeViewId = m_view.callMethod<jint>("getId");
+ return;
+ }
-QAndroidPlatformForeignWindow::~QAndroidPlatformForeignWindow()
-{
if (m_view.isValid())
QtAndroid::setViewVisibility(m_view.object(), false);
- if (m_surfaceId != -1)
- QtAndroid::destroySurface(m_surfaceId);
}
-void QAndroidPlatformForeignWindow::lower()
+QAndroidPlatformForeignWindow::~QAndroidPlatformForeignWindow()
{
- if (m_surfaceId == -1)
+ if (isEmbeddingContainer())
return;
- QAndroidPlatformWindow::lower();
- QtAndroid::bringChildToBack(m_surfaceId);
-}
+ if (m_view.isValid())
+ QtAndroid::setViewVisibility(m_view.object(), false);
-void QAndroidPlatformForeignWindow::raise()
-{
- if (m_surfaceId == -1)
- return;
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
- QAndroidPlatformWindow::raise();
- QtAndroid::bringChildToFront(m_surfaceId);
}
void QAndroidPlatformForeignWindow::setGeometry(const QRect &rect)
{
QAndroidPlatformWindow::setGeometry(rect);
- if (m_surfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_surfaceId, rect);
+ if (isEmbeddingContainer())
+ return;
+
+ if (m_nativeViewInserted)
+ setNativeGeometry(rect);
}
void QAndroidPlatformForeignWindow::setVisible(bool visible)
{
+ if (isEmbeddingContainer()) {
+ QAndroidPlatformWindow::setVisible(visible);
+ return;
+ }
+
if (!m_view.isValid())
return;
QtAndroid::setViewVisibility(m_view.object(), visible);
- QAndroidPlatformWindow::setVisible(visible);
- if (!visible && m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceId = -1;
- } else if (m_surfaceId == -1) {
- m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
+ if (!visible && m_nativeViewInserted) {
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
+ m_nativeViewInserted = false;
+ } else if (!m_nativeViewInserted) {
+ addViewToWindow();
}
}
void QAndroidPlatformForeignWindow::applicationStateChanged(Qt::ApplicationState state)
{
- if (state <= Qt::ApplicationHidden
- && m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceId = -1;
- } else if (m_view.isValid() && m_surfaceId == -1){
- m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
+ if (!isEmbeddingContainer()) {
+ if (state <= Qt::ApplicationHidden
+ && m_nativeViewInserted) {
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
+ m_nativeViewInserted = false;
+ } else if (m_view.isValid() && !m_nativeViewInserted){
+ addViewToWindow();
+ }
}
QAndroidPlatformWindow::applicationStateChanged(state);
}
-void QAndroidPlatformForeignWindow::setParent(const QPlatformWindow *window)
+WId QAndroidPlatformForeignWindow::winId() const
{
- Q_UNUSED(window);
+ if (isEmbeddingContainer() && m_view.isValid())
+ return reinterpret_cast<WId>(m_view.object());
+ if (m_nativeQtWindow.isValid())
+ return reinterpret_cast<WId>(m_nativeQtWindow.object());
+ return 0L;
+}
+
+void QAndroidPlatformForeignWindow::addViewToWindow()
+{
+ if (isEmbeddingContainer())
+ return;
+
+ jint x = 0, y = 0, w = -1, h = -1;
+ if (!geometry().isNull())
+ geometry().getRect(&x, &y, &w, &h);
+
+ m_nativeQtWindow.callMethod<void>("setNativeView", m_view, x, y, qMax(w, 1), qMax(h, 1));
+ m_nativeViewInserted = true;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformforeignwindow.h b/src/plugins/platforms/android/qandroidplatformforeignwindow.h
index 7b576ecec4..503524cced 100644
--- a/src/plugins/platforms/android/qandroidplatformforeignwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformforeignwindow.h
@@ -4,29 +4,31 @@
#ifndef QANDROIDPLATFORMFOREIGNWINDOW_H
#define QANDROIDPLATFORMFOREIGNWINDOW_H
-#include "androidsurfaceclient.h"
#include "qandroidplatformwindow.h"
#include <QtCore/QJniObject>
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(View, "android/view/View")
+
class QAndroidPlatformForeignWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle);
~QAndroidPlatformForeignWindow();
- void lower() override;
- void raise() override;
void setGeometry(const QRect &rect) override;
void setVisible(bool visible) override;
void applicationStateChanged(Qt::ApplicationState state) override;
- void setParent(const QPlatformWindow *window) override;
bool isForeignWindow() const override { return true; }
+ WId winId() const override;
+
private:
- int m_surfaceId;
- QJniObject m_view;
+ void addViewToWindow();
+
+ QtJniTypes::View m_view;
+ bool m_nativeViewInserted;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.cpp b/src/plugins/platforms/android/qandroidplatformiconengine.cpp
new file mode 100644
index 0000000000..faa63dcca1
--- /dev/null
+++ b/src/plugins/platforms/android/qandroidplatformiconengine.cpp
@@ -0,0 +1,616 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qandroidplatformiconengine.h"
+#include "androidjnimain.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qjniarray.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qset.h>
+
+#include <QtGui/qfontdatabase.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+Q_LOGGING_CATEGORY(lcIconEngineFontDownload, "qt.qpa.iconengine.fontdownload")
+
+// the primary types to work with the FontRequest API
+Q_DECLARE_JNI_CLASS(FontRequest, "androidx/core/provider/FontRequest")
+Q_DECLARE_JNI_CLASS(FontsContractCompat, "androidx/core/provider/FontsContractCompat")
+Q_DECLARE_JNI_CLASS(FontFamilyResult, "androidx/core/provider/FontsContractCompat$FontFamilyResult")
+Q_DECLARE_JNI_CLASS(FontInfo, "androidx/core/provider/FontsContractCompat$FontInfo")
+
+// various utility types
+Q_DECLARE_JNI_CLASS(List, "java/util/List"); // List is just an Interface
+Q_DECLARE_JNI_CLASS(ArrayList, "java/util/ArrayList");
+Q_DECLARE_JNI_CLASS(HashSet, "java/util/HashSet");
+Q_DECLARE_JNI_CLASS(Uri, "android/net/Uri")
+Q_DECLARE_JNI_CLASS(CancellationSignal, "android/os/CancellationSignal")
+Q_DECLARE_JNI_CLASS(ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
+Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver")
+Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager")
+Q_DECLARE_JNI_CLASS(ProviderInfo, "android/content/pm/ProviderInfo")
+Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo")
+Q_DECLARE_JNI_CLASS(Signature, "android/content/pm/Signature")
+
+namespace FontProvider {
+
+static QString fetchFont(const QString &query)
+{
+ using namespace QtJniTypes;
+
+ static QMap<QString, QString> triedFonts;
+ const auto it = triedFonts.find(query);
+ if (it != triedFonts.constEnd())
+ return it.value();
+
+ QString fontFamily;
+ triedFonts[query] = fontFamily; // mark as tried
+
+ QStringList loadedFamilies;
+ if (QFile file(query); file.open(QIODevice::ReadOnly)) {
+ qCDebug(lcIconEngineFontDownload) << "Loading font from resource" << query;
+ const QByteArray fontData = file.readAll();
+ int fontId = QFontDatabase::addApplicationFontFromData(fontData);
+ loadedFamilies << QFontDatabase::applicationFontFamilies(fontId);
+ } else if (!query.startsWith(u":/"_s)) {
+ const QString package = u"com.google.android.gms"_s;
+ const QString authority = u"com.google.android.gms.fonts"_s;
+
+ // First we access the content provider to get the signatures of the authority for the package
+ const auto context = QtAndroidPrivate::context();
+
+ auto packageManager = context.callMethod<PackageManager>("getPackageManager");
+ if (!packageManager.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to instantiate PackageManager");
+ return fontFamily;
+ }
+ const int signaturesField = PackageManager::getStaticField<int>("GET_SIGNATURES");
+ auto providerInfo = packageManager.callMethod<ProviderInfo>("resolveContentProvider",
+ authority, 0);
+ if (!providerInfo.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to resolve content provider");
+ return fontFamily;
+ }
+ const QString packageName = providerInfo.getField<QString>("packageName");
+ if (packageName != package) {
+ qCWarning(lcIconEngineFontDownload, "Mismatched provider package - expected '%s', got '%s'",
+ package.toUtf8().constData(), packageName.toUtf8().constData());
+ return fontFamily;
+ }
+ auto packageInfo = packageManager.callMethod<PackageInfo>("getPackageInfo",
+ package, signaturesField);
+ if (!packageInfo.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to get package info with signature field %d",
+ signaturesField);
+ return fontFamily;
+ }
+ const auto signatures = packageInfo.getField<Signature[]>("signatures");
+ if (!signatures.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to get signature array from package info");
+ return fontFamily;
+ }
+
+ // FontRequest wants a list of sets for the certificates
+ ArrayList outerList;
+ HashSet innerSet;
+ Q_ASSERT(outerList.isValid() && innerSet.isValid());
+
+ for (const auto &signature : signatures) {
+ const QJniArray<jbyte> byteArray = signature.callMethod<jbyte[]>("toByteArray");
+
+ // add takes an Object, not an Array
+ if (!innerSet.callMethod<jboolean>("add", byteArray.object<jobject>()))
+ qCWarning(lcIconEngineFontDownload, "Failed to add signature to set");
+ }
+ // Add the set to the list
+ if (!outerList.callMethod<jboolean>("add", innerSet.object()))
+ qCWarning(lcIconEngineFontDownload, "Failed to add set to certificate list");
+
+ // FontRequest constructor wants a List interface, not an ArrayList
+ FontRequest fontRequest(authority, package, query, outerList.object<List>());
+ if (!fontRequest.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to create font request for '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ // Call FontsContractCompat::fetchFonts with the FontRequest object
+ auto fontFamilyResult = FontsContractCompat::callStaticMethod<FontFamilyResult>(
+ "fetchFonts",
+ context,
+ CancellationSignal(nullptr),
+ fontRequest);
+ if (!fontFamilyResult.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to fetch fonts for query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ enum class StatusCode {
+ OK = 0,
+ UNEXPECTED_DATA_PROVIDED = 1,
+ WRONG_CERTIFICATES = 2,
+ };
+
+ const StatusCode statusCode = fontFamilyResult.callMethod<StatusCode>("getStatusCode");
+ switch (statusCode) {
+ case StatusCode::OK:
+ break;
+ case StatusCode::UNEXPECTED_DATA_PROVIDED:
+ qCWarning(lcIconEngineFontDownload, "Provider returned unexpected data for query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ case StatusCode::WRONG_CERTIFICATES:
+ qCWarning(lcIconEngineFontDownload, "Wrong Certificates provided in query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ const auto fontInfos = fontFamilyResult.callMethod<FontInfo[]>("getFonts");
+ if (!fontInfos.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "FontFamilyResult::getFonts returned null object for '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ auto contentResolver = context.callMethod<ContentResolver>("getContentResolver");
+
+ for (QJniObject fontInfo : fontInfos) {
+ if (!fontInfo.isValid()) {
+ qCDebug(lcIconEngineFontDownload, "Received null-fontInfo object, skipping");
+ continue;
+ }
+ enum class ResultCode {
+ OK = 0,
+ FONT_NOT_FOUND = 1,
+ FONT_UNAVAILABLE = 2,
+ MALFORMED_QUERY = 3,
+ };
+ const ResultCode resultCode = fontInfo.callMethod<ResultCode>("getResultCode");
+ switch (resultCode) {
+ case ResultCode::OK:
+ break;
+ case ResultCode::FONT_NOT_FOUND:
+ qCWarning(lcIconEngineFontDownload, "Font '%s' could not be found",
+ query.toUtf8().constData());
+ return fontFamily;
+ case ResultCode::FONT_UNAVAILABLE:
+ qCWarning(lcIconEngineFontDownload, "Font '%s' is unavailable at",
+ query.toUtf8().constData());
+ return fontFamily;
+ case ResultCode::MALFORMED_QUERY:
+ qCWarning(lcIconEngineFontDownload, "Query string '%s' is malformed",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+ auto fontUri = fontInfo.callMethod<Uri>("getUri");
+ // in this case the Font URI is always a content scheme file, made
+ // so the app requesting it has permissions to open
+ auto fileDescriptor = contentResolver.callMethod<ParcelFileDescriptor>("openFileDescriptor",
+ fontUri, u"r"_s);
+ if (!fileDescriptor.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Font file '%s' not accessible",
+ fontUri.toString().toUtf8().constData());
+ continue;
+ }
+
+ int fd = fileDescriptor.callMethod<int>("detachFd");
+ QFile file;
+ file.open(fd, QFile::OpenModeFlag::ReadOnly, QFile::FileHandleFlag::AutoCloseHandle);
+ const QByteArray fontData = file.readAll();
+ qCDebug(lcIconEngineFontDownload) << "Font file read:" << fontData.size() << "bytes";
+ int fontId = QFontDatabase::addApplicationFontFromData(fontData);
+ loadedFamilies << QFontDatabase::applicationFontFamilies(fontId);
+ }
+ }
+
+ qCDebug(lcIconEngineFontDownload) << "Query '" << query << "' added families" << loadedFamilies;
+ if (!loadedFamilies.isEmpty())
+ fontFamily = loadedFamilies.first();
+ triedFonts[query] = fontFamily;
+ return fontFamily;
+}
+}
+
+QString QAndroidPlatformIconEngine::glyphs() const
+{
+ if (!QFontInfo(m_iconFont).exactMatch())
+ return {};
+
+ static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
+ {"address-book-new"_L1, u"\ue0e0"},
+ {"application-exit"_L1, u"\ue5cd"},
+ {"appointment-new"_L1, u"\ue878"},
+ {"call-start"_L1, u"\ue0b0"},
+ {"call-stop"_L1, u"\ue0b1"},
+ {"contact-new"_L1, u"\uf22e"},
+ {"document-new"_L1, u"\ue89c"},
+ {"document-open"_L1, u"\ue2c8"},
+ {"document-open-recent"_L1, u"\ue4a7"},
+ {"document-page-setup"_L1, u"\uf88c"},
+ {"document-print"_L1, u"\ue8ad"},
+ {"document-print-preview"_L1, u"\uefb2"},
+ {"document-properties"_L1, u"\uf775"},
+ {"document-revert"_L1, u"\ue929"},
+ {"document-save"_L1, u"\ue161"},
+ {"document-save-as"_L1, u"\ueb60"},
+ {"document-send"_L1, u"\uf09b"},
+ {"edit-clear"_L1, u"\ue872"},
+ {"edit-copy"_L1, u"\ue14d"},
+ {"edit-cut"_L1, u"\ue14e"},
+ {"edit-delete"_L1, u"\ue14a"},
+ {"edit-find"_L1, u"\ue8b6"},
+ {"edit-find-replace"_L1, u"\ue881"},
+ {"edit-paste"_L1, u"\ue14f"},
+ {"edit-redo"_L1, u"\ue15a"},
+ {"edit-select-all"_L1, u"\ue162"},
+ {"edit-undo"_L1, u"\ue166"},
+ {"folder-new"_L1, u"\ue2cc"},
+ {"format-indent-less"_L1, u"\ue23d"},
+ {"format-indent-more"_L1, u"\ue23e"},
+ {"format-justify-center"_L1, u"\ue234"},
+ {"format-justify-fill"_L1, u"\ue235"},
+ {"format-justify-left"_L1, u"\ue236"},
+ {"format-justify-right"_L1, u"\ue237"},
+ {"format-text-direction-ltr"_L1, u"\ue247"},
+ {"format-text-direction-rtl"_L1, u"\ue248"},
+ {"format-text-bold"_L1, u"\ue238"},
+ {"format-text-italic"_L1, u"\ue23f"},
+ {"format-text-underline"_L1, u"\ue249"},
+ {"format-text-strikethrough"_L1, u"\ue246"},
+ {"go-bottom"_L1,u"\ue258"},
+ {"go-down"_L1,u"\uf1e3"},
+ {"go-first"_L1, u"\ue5dc"},
+ {"go-home"_L1, u"\ue88a"},
+ {"go-jump"_L1, u"\uf719"},
+ {"go-last"_L1, u"\ue5dd"},
+ {"go-next"_L1, u"\ue5c8"},
+ {"go-previous"_L1, u"\ue5c4"},
+ {"go-top"_L1, u"\ue25a"},
+ {"go-up"_L1, u"\uf1e0"},
+ {"help-about"_L1, u"\ue88e"},
+ {"help-contents"_L1, u"\ue8de"},
+ {"help-faq"_L1, u"\uf04c"},
+ {"insert-image"_L1, u"\ue43e"},
+ {"insert-link"_L1, u"\ue178"},
+ //{"insert-object"_L1, u"\u"},
+ {"insert-text"_L1, u"\uf827"},
+ {"list-add"_L1, u"\ue145"},
+ {"list-remove"_L1, u"\ue15b"},
+ {"mail-forward"_L1, u"\ue154"},
+ {"mail-mark-important"_L1, u"\ue937"},
+ //{"mail-mark-junk"_L1, u"\u"},
+ //{"mail-mark-notjunk"_L1, u"\u"},
+ {"mail-mark-read"_L1, u"\uf18c"},
+ {"mail-mark-unread"_L1, u"\ue9bc"},
+ {"mail-message-new"_L1, u"\ue3c9"},
+ {"mail-reply-all"_L1, u"\ue15f"},
+ {"mail-reply-sender"_L1, u"\ue15e"},
+ {"mail-send"_L1, u"\ue163"},
+ //{"mail-send-receive"_L1, u"\u"},
+ {"media-eject"_L1, u"\ue8fb"},
+ {"media-playback-pause"_L1, u"\ue034"},
+ {"media-playback-start"_L1, u"\ue037"},
+ {"media-playback-stop"_L1, u"\ue047"},
+ {"media-record"_L1, u"\uf679"},
+ {"media-seek-backward"_L1, u"\ue020"},
+ {"media-seek-forward"_L1, u"\ue01f"},
+ {"media-skip-backward"_L1, u"\ue045"},
+ {"media-skip-forward"_L1, u"\ue044"},
+ //{"object-flip-horizontal"_L1, u"\u"},
+ //{"object-flip-vertical"_L1, u"\u"},
+ {"object-rotate-left"_L1, u"\ue419"},
+ {"object-rotate-right"_L1, u"\ue41a"},
+ {"process-stop"_L1, u"\ue5c9"},
+ {"system-lock-screen"_L1, u"\ue897"},
+ {"system-log-out"_L1, u"\ue9ba"},
+ //{"system-run"_L1, u"\u"},
+ {"system-search"_L1, u"\uef70"},
+ {"system-reboot"_L1, u"\uf053"},
+ {"system-shutdown"_L1, u"\ue8ac"},
+ {"tools-check-spelling"_L1, u"\ue8ce"},
+ {"view-fullscreen"_L1, u"\ue5d0"},
+ {"view-refresh"_L1, u"\ue5d5"},
+ {"view-restore"_L1, u"\uf1cf"},
+ {"view-sort-ascending"_L1, u"\ue25a"},
+ {"view-sort-descending"_L1, u"\ue258"},
+ {"window-close"_L1, u"\ue5cd"},
+ {"window-new"_L1, u"\uf710"},
+ {"zoom-fit-best"_L1, u"\uea10"},
+ {"zoom-in"_L1, u"\ue8ff"},
+ {"zoom-original"_L1, u"\ue5d1"},
+ {"zoom-out"_L1, u"\ue900"},
+ {"process-working"_L1, u"\uef64"},
+ {"accessories-calculator"_L1, u"\uea5f"},
+ {"accessories-character-map"_L1, u"\uf8a3"},
+ {"accessories-dictionary"_L1, u"\uf539"},
+ {"accessories-text-editor"_L1, u"\ue262"},
+ {"help-browser"_L1, u"\ue887"},
+ {"multimedia-volume-control"_L1, u"\ue050"},
+ {"preferences-desktop-accessibility"_L1, u"\uf05d"},
+ {"preferences-desktop-font"_L1, u"\ue165"},
+ {"preferences-desktop-keyboard"_L1, u"\ue312"},
+ //{"preferences-desktop-locale"_L1, u"\u"},
+ {"preferences-desktop-multimedia"_L1, u"\uea75"},
+ //{"preferences-desktop-screensaver"_L1, u"\u"},
+ {"preferences-desktop-theme"_L1, u"\uf560"},
+ {"preferences-desktop-wallpaper"_L1, u"\ue1bc"},
+ {"system-file-manager"_L1, u"\ue2c7"},
+ {"system-software-install"_L1, u"\ueb71"},
+ {"system-software-update"_L1, u"\ue8d7"},
+ {"utilities-system-monitor"_L1, u"\uef5b"},
+ {"utilities-terminal"_L1, u"\ueb8e"},
+ //{"applications-accessories"_L1, u"\u"},
+ {"applications-development"_L1, u"\ue720"},
+ {"applications-engineering"_L1, u"\uea3d"},
+ {"applications-games"_L1, u"\uf135"},
+ //{"applications-graphics"_L1, u"\u"},
+ {"applications-internet"_L1, u"\ue80b"},
+ {"applications-multimedia"_L1, u"\uf06a"},
+ //{"applications-office"_L1, u"\u"},
+ //{"applications-other"_L1, u"\u"},
+ {"applications-science"_L1, u"\uea4b"},
+ //{"applications-system"_L1, u"\u"},
+ //{"applications-utilities"_L1, u"\u"},
+ {"preferences-desktop"_L1, u"\ueb97"},
+ //{"preferences-desktop-peripherals"_L1, u"\u"},
+ {"preferences-desktop-personal"_L1, u"\uf835"},
+ //{"preferences-other"_L1, u"\u"},
+ {"preferences-system"_L1, u"\ue8b8"},
+ {"preferences-system-network"_L1, u"\ue894"},
+ {"system-help"_L1, u"\ue887"},
+ //{"audio-card"_L1, u"\u"},
+ {"audio-input-microphone"_L1, u"\ue029"},
+ {"battery"_L1, u"\ue1a4"},
+ {"camera-photo"_L1, u"\ue412"},
+ {"camera-video"_L1, u"\ue04b"},
+ {"camera-web"_L1, u"\uf7a6"},
+ {"computer"_L1, u"\ue30a"},
+ {"drive-harddisk"_L1, u"\uf80e"},
+ {"drive-optical"_L1, u"\ue019"}, // same as media-optical
+ //{"drive-removable-media"_L1, u"\u"},
+ {"input-gaming"_L1, u"\uf5ee"},
+ {"input-keyboard"_L1, u"\ue312"},
+ {"input-mouse"_L1, u"\ue323"},
+ //{"input-tablet"_L1, u"\u"},
+ //{"media-flash"_L1, u"\u"},
+ //{"media-floppy"_L1, u"\u"},
+ {"media-optical"_L1, u"\ue019"},
+ //{"media-tape"_L1, u"\u"},
+ //{"modem"_L1, u"\u"},
+ //{"multimedia-player"_L1, u"\u"},
+ //{"network-wired"_L1, u"\u"},
+ {"network-wireless"_L1, u"\ue63e"},
+ //{"pda"_L1, u"\u"},
+ {"phone"_L1, u"\ue32c"},
+ {"printer"_L1, u"\ue8ad"},
+ {"scanner"_L1, u"\ue329"},
+ {"video-display"_L1, u"\uf06a"},
+ //{"emblem-default"_L1, u"\u"},
+ {"emblem-documents"_L1, u"\ue873"},
+ {"emblem-downloads"_L1, u"\uf090"},
+ {"emblem-favorite"_L1, u"\uf090"},
+ {"emblem-important"_L1, u"\ue645"},
+ {"emblem-mail"_L1, u"\ue158"},
+ {"emblem-photos"_L1, u"\ue413"},
+ //{"emblem-readonly"_L1, u"\u"},
+ {"emblem-shared"_L1, u"\ue413"},
+ //{"emblem-symbolic-link"_L1, u"\u"},
+ //{"emblem-synchronized"_L1, u"\u"},
+ {"emblem-system"_L1, u"\ue8b8"},
+ //{"emblem-unreadable"_L1, u"\u"},
+ {"folder"_L1, u"\ue2c7"},
+ //{"folder-remote"_L1, u"\u"},
+ {"network-server"_L1, u"\ue875"},
+ {"network-workgroup"_L1, u"\ue1a0"},
+ {"start-here"_L1, u"\ue089"},
+ {"user-bookmarks"_L1, u"\ue98b"},
+ {"user-desktop"_L1, u"\ue30a"},
+ {"user-home"_L1, u"\ue88a"},
+ {"user-trash"_L1, u"\ue872"},
+ {"appointment-missed"_L1, u"\ue615"},
+ {"appointment-soon"_L1, u"\uf540"},
+ {"audio-volume-high"_L1, u"\ue050"},
+ {"audio-volume-low"_L1, u"\ue04d"},
+ //{"audio-volume-medium"_L1, u"\u"},
+ {"audio-volume-muted"_L1, u"\ue04e"},
+ {"battery-caution"_L1, u"\ue19c"},
+ {"battery-low"_L1, u"\uf147"},
+ {"dialog-error"_L1, u"\ue000"},
+ {"dialog-information"_L1, u"\ue88e"},
+ {"dialog-password"_L1, u"\uf042"},
+ {"dialog-question"_L1, u"\ueb8b"},
+ {"dialog-warning"_L1, u"\ue002"},
+ {"folder-drag-accept"_L1, u"\ue9a3"},
+ {"folder-open"_L1, u"\ue2c8"},
+ {"folder-visiting"_L1, u"\ue8a7"},
+ {"image-loading"_L1, u"\ue41a"},
+ {"image-missing"_L1, u"\ue3ad"},
+ {"mail-attachment"_L1, u"\ue2bc"},
+ {"mail-unread"_L1, u"\uf18a"},
+ {"mail-read"_L1, u"\uf18c"},
+ //{"mail-replied"_L1, u"\u"},
+ //{"mail-signed"_L1, u"\u"},
+ //{"mail-signed-verified"_L1, u"\u"},
+ {"media-playlist-repeat"_L1, u"\ue040"},
+ {"media-playlist-shuffle"_L1, u"\ue043"},
+ {"network-error"_L1, u"\uead9"},
+ {"network-idle"_L1, u"\ue51f"},
+ {"network-offline"_L1, u"\uf239"},
+ {"network-receive"_L1, u"\ue2c0"},
+ {"network-transmit"_L1, u"\ue2c3"},
+ {"network-transmit-receive"_L1, u"\uea18"},
+ {"printer-error"_L1, u"\uf7a0"},
+ {"printer-printing"_L1, u"\uf7a1"},
+ {"security-high"_L1, u"\ue32a"},
+ {"security-medium"_L1, u"\ue9e0"},
+ {"security-low"_L1, u"\uf012"},
+ {"software-update-available"_L1, u"\ue923"},
+ {"software-update-urgent"_L1, u"\uf05a"},
+ {"sync-error"_L1, u"\ue629"},
+ {"sync-synchronizing"_L1, u"\ue627"},
+ //{"task-due"_L1, u"\u"},
+ //{"task-past-due"_L1, u"\u"},
+ {"user-available"_L1, u"\uf565"},
+ {"user-away"_L1, u"\ue510"},
+ //{"user-idle"_L1, u"\u"},
+ {"user-offline"_L1, u"\uf7b3"},
+ {"user-trash-full"_L1, u"\ue872"}, //delete
+ //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever
+ {"weather-clear"_L1, u"\uf157"},
+ {"weather-clear-night"_L1, u"\uf159"},
+ {"weather-few-clouds"_L1, u"\uf172"},
+ {"weather-few-clouds-night"_L1, u"\uf174"},
+ {"weather-fog"_L1, u"\ue818"},
+ //{"weather-overcast"_L1, u"\u"},
+ {"weather-severe-alert"_L1, u"\ue002"}, //warning
+ //{"weather-severe-alert"_L1, u"\uebd3"},//severe_cold
+ {"weather-showers"_L1, u"\uf176"},
+ //{"weather-showers-scattered"_L1, u"\u"},
+ {"weather-snow"_L1, u"\ue80f"}, //snowing
+ //{"weather-snow"_L1, u"\ue2cd"}, //weather_snowy
+ //{"weather-snow"_L1, u"\ue810"},//cloudy_snowing
+ {"weather-storm"_L1, u"\uf070"},
+ };
+
+ const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){
+ return c.first == m_iconName;
+ });
+ return it != std::end(glyphMap) ? it->second.toString()
+ : (m_iconName.length() == 1 ? m_iconName : QString());
+}
+
+QAndroidPlatformIconEngine::QAndroidPlatformIconEngine(const QString &iconName)
+ : m_iconName(iconName)
+ , m_glyphs(glyphs())
+{
+ QString fontFamily;
+ // The MaterialIcons-*.ttf and MaterialSymbols* font files are available from
+ // https://github.com/google/material-design-icons/tree/master. If one of them is
+ // packaged as a resource with the application, then we use it. We prioritize
+ // a variable font.
+ const QStringList fontCandidates = {
+ "MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf",
+ "MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf",
+ "MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf",
+ "MaterialIcons-Regular.ttf",
+ "MaterialIconsOutlined-Regular.otf",
+ "MaterialIconsRound-Regular.otf",
+ "MaterialIconsSharp-Regular.otf",
+ "MaterialIconsTwoTone-Regular.otf",
+ };
+ for (const auto &fontCandidate : fontCandidates) {
+ fontFamily = FontProvider::fetchFont(u":/qt-project.org/icons/%1"_s.arg(fontCandidate));
+ if (!fontFamily.isEmpty())
+ break;
+ }
+
+ // Otherwise we try to download the Outlined version of Material Symbols
+ const QString key = qEnvironmentVariable("QT_GOOGLE_FONTS_KEY");
+ if (fontFamily.isEmpty() && !key.isEmpty())
+ fontFamily = FontProvider::fetchFont(u"key=%1&name=Material+Symbols+Outlined"_s.arg(key));
+
+ // last resort - use any Material Icons
+ if (fontFamily.isEmpty())
+ fontFamily = u"Material Icons"_s;
+ m_iconFont = QFont(fontFamily);
+}
+
+QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine()
+{}
+
+QIconEngine *QAndroidPlatformIconEngine::clone() const
+{
+ return new QAndroidPlatformIconEngine(m_iconName);
+}
+
+QString QAndroidPlatformIconEngine::key() const
+{
+ return u"QAndroidPlatformIconEngine"_s;
+}
+
+QString QAndroidPlatformIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QAndroidPlatformIconEngine::isNull()
+{
+ if (m_glyphs.isEmpty())
+ return true;
+ const QChar c0 = m_glyphs.at(0);
+ const QFontMetrics fontMetrics(m_iconFont);
+ if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1)
+ return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1)));
+ return !fontMetrics.inFont(c0);
+}
+
+QList<QSize> QAndroidPlatformIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
+}
+
+QSize QAndroidPlatformIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return QIconEngine::actualSize(size, mode, state);
+}
+
+QPixmap QAndroidPlatformIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+QPixmap QAndroidPlatformIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.fill(Qt::transparent);
+ m_pixmap.setDevicePixelRatio(scale);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(QPoint(), size), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+
+ return m_pixmap;
+}
+
+void QAndroidPlatformIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ painter->save();
+ QFont renderFont(m_iconFont);
+ renderFont.setPixelSize(rect.height());
+ painter->setFont(renderFont);
+
+ QPalette palette;
+ switch (mode) {
+ case QIcon::Active:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Normal:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Disabled:
+ painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
+ break;
+ case QIcon::Selected:
+ painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText));
+ break;
+ }
+
+ painter->drawText(rect, Qt::AlignCenter, m_glyphs);
+ painter->restore();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.h b/src/plugins/platforms/android/qandroidplatformiconengine.h
new file mode 100644
index 0000000000..cac54481d9
--- /dev/null
+++ b/src/plugins/platforms/android/qandroidplatformiconengine.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDPLATFORMICONENGINE_H
+#define QANDROIDPLATFORMICONENGINE_H
+
+#include <QtGui/qiconengine.h>
+#include <QtGui/qfont.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidPlatformIconEngine : public QIconEngine
+{
+public:
+ QAndroidPlatformIconEngine(const QString &iconName);
+ ~QAndroidPlatformIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+ QString glyphs() const;
+
+ const QString m_iconName;
+ QFont m_iconFont;
+ const QString m_glyphs;
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDPLATFORMICONENGINE_H
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp
index dec9082eca..6efd3fc631 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.cpp
+++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp
@@ -9,7 +9,6 @@
#include "qabstracteventdispatcher.h"
#include "qandroideventdispatcher.h"
#include "qandroidplatformaccessibility.h"
-#include "qandroidplatformbackingstore.h"
#include "qandroidplatformclipboard.h"
#include "qandroidplatformfontdatabase.h"
#include "qandroidplatformforeignwindow.h"
@@ -29,6 +28,7 @@
#include <QtGui/private/qeglpbuffer_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qoffscreensurface_p.h>
+#include <QtGui/private/qrhibackingstore_p.h>
#include <qpa/qplatformoffscreensurface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
@@ -54,36 +54,44 @@ Qt::ScreenOrientation QAndroidPlatformIntegration::m_orientation = Qt::PrimaryOr
Qt::ScreenOrientation QAndroidPlatformIntegration::m_nativeOrientation = Qt::PrimaryOrientation;
bool QAndroidPlatformIntegration::m_showPasswordEnabled = false;
-static bool m_running = false;
Q_DECLARE_JNI_CLASS(QtNative, "org/qtproject/qt/android/QtNative")
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
Q_DECLARE_JNI_CLASS(Display, "android/view/Display")
-Q_DECLARE_JNI_TYPE(List, "Ljava/util/List;")
+Q_DECLARE_JNI_CLASS(List, "java/util/List")
namespace {
QAndroidPlatformScreen* createScreenForDisplayId(int displayId)
{
- const QJniObject display = QJniObject::callStaticObjectMethod<QtJniTypes::Display>(
- QtJniTypes::className<QtJniTypes::QtNative>(),
- "getDisplay",
- displayId);
+ const QJniObject display = QtJniTypes::QtDisplayManager::callStaticMethod<QtJniTypes::Display>(
+ "getDisplay", QtAndroidPrivate::context(), displayId);
if (!display.isValid())
return nullptr;
return new QAndroidPlatformScreen(display);
}
+static bool isValidAndroidContextForRendering()
+{
+ return QtAndroid::isQtApplication() ? QtAndroidPrivate::activity().isValid()
+ : QtAndroidPrivate::context().isValid();
+}
+
} // anonymous namespace
void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteArray &resource)
{
if (resource=="JavaVM")
return QtAndroid::javaVM();
- if (resource == "QtActivity")
- return QtAndroid::activity();
- if (resource == "QtService")
- return QtAndroid::service();
+ if (resource == "QtActivity") {
+ extern Q_CORE_EXPORT jobject qt_androidActivity();
+ return qt_androidActivity();
+ }
+ if (resource == "QtService") {
+ extern Q_CORE_EXPORT jobject qt_androidService();
+ return qt_androidService();
+ }
if (resource == "AndroidStyleData") {
if (m_androidStyle) {
if (m_androidStyle->m_styleData.isEmpty())
@@ -156,10 +164,6 @@ void QAndroidPlatformNativeInterface::customEvent(QEvent *event)
api->accessibility()->setActive(QtAndroidAccessibility::isActive());
#endif // QT_CONFIG(accessibility)
- if (!m_running) {
- m_running = true;
- QtAndroid::notifyQtAndroidPluginRunning(m_running);
- }
api->flushPendingUpdates();
}
@@ -183,12 +187,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
if (Q_UNLIKELY(!eglBindAPI(EGL_OPENGL_ES_API)))
qFatal("Could not bind GL_ES API");
- m_primaryDisplayId = QJniObject::getStaticField<jint>(
- QtJniTypes::className<QtJniTypes::Display>(), "DEFAULT_DISPLAY");
-
- const QJniObject nativeDisplaysList = QJniObject::callStaticObjectMethod<QtJniTypes::List>(
- QtJniTypes::className<QtJniTypes::QtNative>(),
- "getAvailableDisplays");
+ using namespace QtJniTypes;
+ m_primaryDisplayId = Display::getStaticField<jint>("DEFAULT_DISPLAY");
+ const QJniObject nativeDisplaysList = QtDisplayManager::callStaticMethod<List>(
+ "getAvailableDisplays", QtAndroidPrivate::context());
const int numberOfAvailableDisplays = nativeDisplaysList.callMethod<jint>("size");
for (int i = 0; i < numberOfAvailableDisplays; ++i) {
@@ -227,9 +229,9 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
m_accessibility = new QAndroidPlatformAccessibility();
#endif // QT_CONFIG(accessibility)
- QJniObject javaActivity(QtAndroid::activity());
+ QJniObject javaActivity = QtAndroidPrivate::activity();
if (!javaActivity.isValid())
- javaActivity = QtAndroid::service();
+ javaActivity = QtAndroidPrivate::service();
if (javaActivity.isValid()) {
QJniObject resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
@@ -269,6 +271,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
maxTouchPoints,
0);
QWindowSystemInterface::registerInputDevice(m_touchDevice);
+
+ QWindowSystemInterface::registerInputDevice(
+ new QInputDevice("Virtual keyboard"_L1, 0, QInputDevice::DeviceType::Keyboard,
+ {}, qApp));
}
auto contentResolver = javaActivity.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
@@ -304,11 +310,11 @@ static bool needsBasicRenderloopWorkaround()
void QAndroidPlatformIntegration::initialize()
{
- const QString icStr = QPlatformInputContextFactory::requested();
- if (icStr.isNull())
+ const auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty())
m_inputContext.reset(new QAndroidInputContext);
else
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
}
bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
@@ -316,13 +322,19 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
switch (cap) {
case ApplicationState: return true;
case ThreadedPixmaps: return true;
- case NativeWidgets: return QtAndroid::activity();
- case OpenGL: return QtAndroid::activity();
- case ForeignWindows: return QtAndroid::activity();
- case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroid::activity();
- case RasterGLSurface: return QtAndroid::activity();
+ case NativeWidgets: return QtAndroidPrivate::activity().isValid();
+ case OpenGL:
+ return isValidAndroidContextForRendering();
+ case ForeignWindows:
+ return isValidAndroidContextForRendering();
+ case ThreadedOpenGL:
+ return !needsBasicRenderloopWorkaround() && isValidAndroidContextForRendering();
+ case RasterGLSurface: return QtAndroidPrivate::activity().isValid();
case TopStackedNativeChildWindows: return false;
case MaximizeUsingFullscreenGeometry: return true;
+ // FIXME QTBUG-118849 - we do not implement grabWindow() anymore, calling it will return
+ // a null QPixmap also for raster windows - for OpenGL windows this was always true
+ case ScreenWindowGrabbing: return false;
default:
return QPlatformIntegration::hasCapability(cap);
}
@@ -330,15 +342,15 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
QPlatformBackingStore *QAndroidPlatformIntegration::createPlatformBackingStore(QWindow *window) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
- return new QAndroidPlatformBackingStore(window);
+ return new QRhiBackingStore(window);
}
QPlatformOpenGLContext *QAndroidPlatformIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- if (!QtAndroid::activity())
+ if (!isValidAndroidContextForRendering())
return nullptr;
QSurfaceFormat format(context->format());
format.setAlphaBufferSize(8);
@@ -356,7 +368,7 @@ QOpenGLContext *QAndroidPlatformIntegration::createOpenGLContext(EGLContext cont
QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
QSurfaceFormat format(surface->requestedFormat());
@@ -370,7 +382,7 @@ QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenS
QOffscreenSurface *QAndroidPlatformIntegration::createOffscreenSurface(ANativeWindow *nativeSurface) const
{
- if (!QtAndroid::activity() || !nativeSurface)
+ if (!QtAndroidPrivate::activity().isValid() || !nativeSurface)
return nullptr;
auto *surface = new QOffscreenSurface;
@@ -381,7 +393,7 @@ QOffscreenSurface *QAndroidPlatformIntegration::createOffscreenSurface(ANativeWi
QPlatformWindow *QAndroidPlatformIntegration::createPlatformWindow(QWindow *window) const
{
- if (!QtAndroid::activity())
+ if (!isValidAndroidContextForRendering())
return nullptr;
#if QT_CONFIG(vulkan)
@@ -532,16 +544,16 @@ void QAndroidPlatformIntegration::setScreenSize(int width, int height)
QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height)));
}
-Qt::Appearance QAndroidPlatformIntegration::m_appearance = Qt::Appearance::Light;
+Qt::ColorScheme QAndroidPlatformIntegration::m_colorScheme = Qt::ColorScheme::Light;
-void QAndroidPlatformIntegration::setAppearance(Qt::Appearance newAppearance)
+void QAndroidPlatformIntegration::updateColorScheme(Qt::ColorScheme colorScheme)
{
- if (m_appearance == newAppearance)
+ if (m_colorScheme == colorScheme)
return;
- m_appearance = newAppearance;
+ m_colorScheme = colorScheme;
QMetaObject::invokeMethod(qGuiApp,
- [] () { QAndroidPlatformTheme::instance()->updateAppearance();});
+ [] () { QAndroidPlatformTheme::instance()->updateColorScheme();});
}
void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize,
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h
index 8463dc8949..b7bfb58d1d 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.h
+++ b/src/plugins/platforms/android/qandroidplatformintegration.h
@@ -110,8 +110,8 @@ public:
void flushPendingUpdates();
- static void setAppearance(Qt::Appearance newAppearance);
- static Qt::Appearance appearance() { return m_appearance; }
+ static void updateColorScheme(Qt::ColorScheme colorScheme);
+ static Qt::ColorScheme colorScheme() { return m_colorScheme; }
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override;
#endif
@@ -124,7 +124,7 @@ private:
QThread *m_mainThread;
- static Qt::Appearance m_appearance;
+ static Qt::ColorScheme m_colorScheme;
static QRect m_defaultAvailableGeometry;
static QSize m_defaultPhysicalSize;
diff --git a/src/plugins/platforms/android/qandroidplatformmenu.cpp b/src/plugins/platforms/android/qandroidplatformmenu.cpp
index 4ddd6ea29a..e59fd2089d 100644
--- a/src/plugins/platforms/android/qandroidplatformmenu.cpp
+++ b/src/plugins/platforms/android/qandroidplatformmenu.cpp
@@ -119,7 +119,7 @@ void QAndroidPlatformMenu::showPopup(const QWindow *parentWindow, const QRect &t
Q_UNUSED(parentWindow);
Q_UNUSED(item);
setVisible(true);
- QtAndroidMenu::showContextMenu(this, targetRect, QJniEnvironment().jniEnv());
+ QtAndroidMenu::showContextMenu(this, targetRect);
}
QPlatformMenuItem *QAndroidPlatformMenu::menuItemForTag(quintptr tag) const
diff --git a/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp b/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
index c1ec2fbdd6..13d14eb391 100644
--- a/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
@@ -24,41 +24,19 @@ QT_BEGIN_NAMESPACE
QAndroidPlatformOpenGLWindow::QAndroidPlatformOpenGLWindow(QWindow *window, EGLDisplay display)
:QAndroidPlatformWindow(window), m_eglDisplay(display)
{
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::OpenGLSurface);
}
QAndroidPlatformOpenGLWindow::~QAndroidPlatformOpenGLWindow()
{
m_surfaceWaitCondition.wakeOne();
lockSurface();
- if (m_nativeSurfaceId != -1)
- QtAndroid::destroySurface(m_nativeSurfaceId);
+ destroySurface();
clearEgl();
unlockSurface();
}
-void QAndroidPlatformOpenGLWindow::repaint(const QRegion &region)
-{
- // This is only for real raster top-level windows. Stop in all other cases.
- if ((window()->surfaceType() == QSurface::RasterGLSurface && qt_window_private(window())->compositing)
- || window()->surfaceType() == QSurface::OpenGLSurface
- || QAndroidPlatformWindow::parent())
- return;
-
- QRect currentGeometry = geometry();
-
- QRect dirtyClient = region.boundingRect();
- QRect dirtyRegion(currentGeometry.left() + dirtyClient.left(),
- currentGeometry.top() + dirtyClient.top(),
- dirtyClient.width(),
- dirtyClient.height());
- QRect mOldGeometryLocal = m_oldGeometry;
- m_oldGeometry = currentGeometry;
- // If this is a move, redraw the previous location
- if (mOldGeometryLocal != currentGeometry)
- platformScreen()->setDirty(mOldGeometryLocal);
- platformScreen()->setDirty(dirtyRegion);
-}
-
void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
{
if (rect == geometry())
@@ -67,8 +45,9 @@ void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
m_oldGeometry = geometry();
QAndroidPlatformWindow::setGeometry(rect);
- if (m_nativeSurfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect);
+
+
+ setNativeGeometry(rect);
QRect availableGeometry = screen()->availableGeometry();
if (rect.width() > 0
@@ -77,25 +56,23 @@ void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
&& availableGeometry.height() > 0) {
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size()));
}
-
- if (rect.topLeft() != m_oldGeometry.topLeft())
- repaint(QRegion(rect));
}
EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
{
- if (QAndroidEventDispatcherStopper::stopped() || QGuiApplication::applicationState() == Qt::ApplicationSuspended)
+ if (QAndroidEventDispatcherStopper::stopped() ||
+ QGuiApplication::applicationState() == Qt::ApplicationSuspended) {
return m_eglSurface;
+ }
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1) {
+ if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return m_eglSurface;
- const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
- m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32);
+ createSurface();
m_surfaceWaitCondition.wait(&m_surfaceMutex);
}
@@ -110,7 +87,7 @@ EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
bool QAndroidPlatformOpenGLWindow::checkNativeSurface(EGLConfig config)
{
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
+ if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return false; // makeCurrent is NOT needed.
createEgl(config);
@@ -127,10 +104,7 @@ void QAndroidPlatformOpenGLWindow::applicationStateChanged(Qt::ApplicationState
QAndroidPlatformWindow::applicationStateChanged(state);
if (state <= Qt::ApplicationHidden) {
lockSurface();
- if (m_nativeSurfaceId != -1) {
- QtAndroid::destroySurface(m_nativeSurfaceId);
- m_nativeSurfaceId = -1;
- }
+ destroySurface();
clearEgl();
unlockSurface();
}
@@ -173,24 +147,4 @@ void QAndroidPlatformOpenGLWindow::clearEgl()
}
}
-void QAndroidPlatformOpenGLWindow::surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h)
-{
- Q_UNUSED(jniEnv);
- Q_UNUSED(w);
- Q_UNUSED(h);
-
- lockSurface();
- m_androidSurfaceObject = surface;
- if (surface) // wait until we have a valid surface to draw into
- m_surfaceWaitCondition.wakeOne();
- unlockSurface();
-
- if (surface) {
- // repaint the window, when we have a valid surface
- QRect availableGeometry = screen()->availableGeometry();
- if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
- QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(), geometry().size())));
- }
-}
-
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformopenglwindow.h b/src/plugins/platforms/android/qandroidplatformopenglwindow.h
index 8c31368b65..c1ae57fe85 100644
--- a/src/plugins/platforms/android/qandroidplatformopenglwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformopenglwindow.h
@@ -5,7 +5,6 @@
#ifndef QANDROIDPLATFORMOPENGLWINDOW_H
#define QANDROIDPLATFORMOPENGLWINDOW_H
-#include "androidsurfaceclient.h"
#include "qandroidplatformwindow.h"
#include <QWaitCondition>
@@ -16,7 +15,7 @@
QT_BEGIN_NAMESPACE
-class QAndroidPlatformOpenGLWindow : public QAndroidPlatformWindow, public AndroidSurfaceClient
+class QAndroidPlatformOpenGLWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformOpenGLWindow(QWindow *window, EGLDisplay display);
@@ -30,10 +29,7 @@ public:
void applicationStateChanged(Qt::ApplicationState) override;
- void repaint(const QRegion &region) override;
-
protected:
- void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) override;
void createEgl(EGLConfig config);
void clearEgl();
@@ -42,9 +38,6 @@ private:
EGLSurface m_eglSurface = EGL_NO_SURFACE;
EGLNativeWindowType m_nativeWindow = nullptr;
- int m_nativeSurfaceId = -1;
- QJniObject m_androidSurfaceObject;
- QWaitCondition m_surfaceWaitCondition;
QSurfaceFormat m_format;
QRect m_oldGeometry;
};
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp
index b2fa2ed3e5..9e20b7ac4b 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.cpp
+++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp
@@ -8,7 +8,6 @@
#include <qpa/qwindowsysteminterface.h>
#include "qandroidplatformscreen.h"
-#include "qandroidplatformbackingstore.h"
#include "qandroidplatformintegration.h"
#include "qandroidplatformwindow.h"
#include "androidjnimain.h"
@@ -53,8 +52,13 @@ private:
#endif
Q_DECLARE_JNI_CLASS(Display, "android/view/Display")
+Q_DECLARE_JNI_CLASS(DisplayMetrics, "android/util/DisplayMetrics")
+Q_DECLARE_JNI_CLASS(Resources, "android/content/res/Resources")
+Q_DECLARE_JNI_CLASS(Size, "android/util/Size")
+Q_DECLARE_JNI_CLASS(QtNative, "org/qtproject/qt/android/QtNative")
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
-Q_DECLARE_JNI_TYPE(DisplayMode, "Landroid/view/Display$Mode;")
+Q_DECLARE_JNI_CLASS(DisplayMode, "android/view/Display$Mode")
QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
: QObject(), QPlatformScreen()
@@ -79,14 +83,33 @@ QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
if (!displayObject.isValid())
return;
- m_size = QSize(displayObject.callMethod<jint>("getWidth"), displayObject.callMethod<jint>("getHeight"));
m_name = displayObject.callObjectMethod<jstring>("getName").toString();
m_refreshRate = displayObject.callMethod<jfloat>("getRefreshRate");
m_displayId = displayObject.callMethod<jint>("getDisplayId");
+ const QJniObject context = QNativeInterface::QAndroidApplication::context();
+ const auto displayContext = context.callMethod<QtJniTypes::Context>("createDisplayContext",
+ displayObject.object<QtJniTypes::Display>());
+
+ const auto sizeObj = QtJniTypes::QtDisplayManager::callStaticMethod<QtJniTypes::Size>(
+ "getDisplaySize", displayContext,
+ displayObject.object<QtJniTypes::Display>());
+ m_size = QSize(sizeObj.callMethod<int>("getWidth"), sizeObj.callMethod<int>("getHeight"));
+
+ const auto resources = displayContext.callMethod<QtJniTypes::Resources>("getResources");
+ const auto metrics = resources.callMethod<QtJniTypes::DisplayMetrics>("getDisplayMetrics");
+ const float xdpi = metrics.getField<float>("xdpi");
+ const float ydpi = metrics.getField<float>("ydpi");
+
+ // Potentially densityDpi could be used instead of xpdi/ydpi to do the calculation,
+ // but the results are not consistent with devices specs.
+ // (https://issuetracker.google.com/issues/194120500)
+ m_physicalSize.setWidth(qRound(m_size.width() / xdpi * 25.4));
+ m_physicalSize.setHeight(qRound(m_size.height() / ydpi * 25.4));
+
if (QNativeInterface::QAndroidApplication::sdkVersion() >= 23) {
const QJniObject currentMode = displayObject.callObjectMethod<QtJniTypes::DisplayMode>("getMode");
- const jint currentModeId = currentMode.callMethod<jint>("getModeId");
+ m_currentMode = currentMode.callMethod<jint>("getModeId");
const QJniObject supportedModes = displayObject.callObjectMethod<QtJniTypes::DisplayMode[]>(
"getSupportedModes");
@@ -96,19 +119,9 @@ QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
const auto size = env->GetArrayLength(modeArray);
for (jsize i = 0; i < size; ++i) {
const auto mode = QJniObject::fromLocalRef(env->GetObjectArrayElement(modeArray, i));
- const int physicalWidth = mode.callMethod<jint>("getPhysicalWidth");
- const int physicalHeight = mode.callMethod<jint>("getPhysicalHeight");
-
- if (currentModeId == mode.callMethod<jint>("getModeId")) {
- m_currentMode = i;
- m_physicalSize = QSize {
- physicalWidth,
- physicalHeight
- };
- }
-
m_modes << QPlatformScreen::Mode {
- .size = QSize { physicalWidth, physicalHeight },
+ .size = QSize { mode.callMethod<jint>("getPhysicalWidth"),
+ mode.callMethod<jint>("getPhysicalHeight") },
.refreshRate = mode.callMethod<jfloat>("getRefreshRate")
};
}
@@ -117,23 +130,18 @@ QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
QAndroidPlatformScreen::~QAndroidPlatformScreen()
{
- if (m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceWaitCondition.wakeOne();
- releaseSurface();
- }
}
-QWindow *QAndroidPlatformScreen::topWindow() const
+QWindow *QAndroidPlatformScreen::topVisibleWindow() const
{
for (QAndroidPlatformWindow *w : m_windowStack) {
- if (w->window()->type() == Qt::Window ||
- w->window()->type() == Qt::Popup ||
- w->window()->type() == Qt::Dialog) {
+ Qt::WindowType type = w->window()->type();
+ if (w->window()->isVisible() &&
+ (type == Qt::Window || type == Qt::Popup || type == Qt::Dialog)) {
return w->window();
}
}
- return 0;
+ return nullptr;
}
QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const
@@ -145,16 +153,6 @@ QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const
return 0;
}
-bool QAndroidPlatformScreen::event(QEvent *event)
-{
- if (event->type() == QEvent::UpdateRequest) {
- doRedraw();
- m_updatePending = false;
- return true;
- }
- return QObject::event(event);
-}
-
void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window)
{
if (window->parent() && window->isRaster())
@@ -164,83 +162,45 @@ void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window)
return;
m_windowStack.prepend(window);
- if (window->isRaster()) {
- m_rasterSurfaces.ref();
- setDirty(window->geometry());
- }
+ QtAndroid::qtActivityDelegate().callMethod<void>("addTopLevelWindow", window->nativeWindow());
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ if (window->window()->isVisible())
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::removeWindow(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
m_windowStack.removeOne(window);
if (m_windowStack.contains(window))
qWarning() << "Failed to remove window";
- if (window->isRaster()) {
- m_rasterSurfaces.deref();
- setDirty(window->geometry());
- }
+ QtAndroid::qtActivityDelegate().callMethod<void>("removeTopLevelWindow", window->nativeViewId());
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::raise(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
int index = m_windowStack.indexOf(window);
- if (index <= 0)
+ if (index < 0)
return;
- m_windowStack.move(index, 0);
- if (window->isRaster()) {
- setDirty(window->geometry());
+ if (index > 0) {
+ m_windowStack.move(index, 0);
+ QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToFront", window->nativeViewId());
}
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
int index = m_windowStack.indexOf(window);
if (index == -1 || index == (m_windowStack.size() - 1))
return;
m_windowStack.move(index, m_windowStack.size() - 1);
- if (window->isRaster()) {
- setDirty(window->geometry());
- }
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
-}
-
-void QAndroidPlatformScreen::scheduleUpdate()
-{
- if (!m_updatePending) {
- m_updatePending = true;
- QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
- }
-}
+ QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToBack", window->nativeViewId());
-void QAndroidPlatformScreen::setDirty(const QRect &rect)
-{
- QRect intersection = rect.intersected(m_availableGeometry);
- m_dirtyRect |= intersection;
- scheduleUpdate();
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::setPhysicalSize(const QSize &size)
@@ -292,7 +252,6 @@ void QAndroidPlatformScreen::setOrientation(Qt::ScreenOrientation orientation)
void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect)
{
- QMutexLocker lock(&m_surfaceMutex);
if (m_availableGeometry == rect)
return;
@@ -313,165 +272,26 @@ void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect)
}
}
}
-
- if (m_surfaceId != -1) {
- releaseSurface();
- QtAndroid::setSurfaceGeometry(m_surfaceId, rect);
- }
}
void QAndroidPlatformScreen::applicationStateChanged(Qt::ApplicationState state)
{
for (QAndroidPlatformWindow *w : std::as_const(m_windowStack))
w->applicationStateChanged(state);
-
- if (state <= Qt::ApplicationHidden) {
- lockSurface();
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceId = -1;
- releaseSurface();
- unlockSurface();
- }
}
-void QAndroidPlatformScreen::topWindowChanged(QWindow *w)
+void QAndroidPlatformScreen::topVisibleWindowChanged()
{
+ QWindow *w = topVisibleWindow();
+ QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
QtAndroidMenu::setActiveTopLevelWindow(w);
-
- if (w != 0) {
+ if (w && w->handle()) {
QAndroidPlatformWindow *platformWindow = static_cast<QAndroidPlatformWindow *>(w->handle());
- if (platformWindow != 0)
+ if (platformWindow)
platformWindow->updateSystemUiVisibility();
}
}
-int QAndroidPlatformScreen::rasterSurfaces()
-{
- return m_rasterSurfaces;
-}
-
-void QAndroidPlatformScreen::doRedraw(QImage* screenGrabImage)
-{
- PROFILE_SCOPE;
- if (!QtAndroid::activity())
- return;
-
- if (m_dirtyRect.isEmpty())
- return;
-
- // Stop if there are no visible raster windows. If we only have RasterGLSurface
- // windows that have renderToTexture children (i.e. they need the OpenGL path) then
- // we do not need an overlay surface.
- bool hasVisibleRasterWindows = false;
- for (QAndroidPlatformWindow *window : std::as_const(m_windowStack)) {
- if (window->window()->isVisible() && window->isRaster() && !qt_window_private(window->window())->compositing) {
- hasVisibleRasterWindows = true;
- break;
- }
- }
- if (!hasVisibleRasterWindows) {
- lockSurface();
- if (m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- releaseSurface();
- m_surfaceId = -1;
- }
- unlockSurface();
- return;
- }
- QMutexLocker lock(&m_surfaceMutex);
- if (m_surfaceId == -1 && m_rasterSurfaces) {
- m_surfaceId = QtAndroid::createSurface(this, geometry(), true, m_depth);
- AndroidDeadlockProtector protector;
- if (!protector.acquire())
- return;
- m_surfaceWaitCondition.wait(&m_surfaceMutex);
- }
-
- if (!m_nativeSurface)
- return;
-
- ANativeWindow_Buffer nativeWindowBuffer;
- ARect nativeWindowRect;
- nativeWindowRect.top = m_dirtyRect.top();
- nativeWindowRect.left = m_dirtyRect.left();
- nativeWindowRect.bottom = m_dirtyRect.bottom() + 1; // for some reason that I don't understand the QRect bottom needs to +1 to be the same with ARect bottom
- nativeWindowRect.right = m_dirtyRect.right() + 1; // same for the right
-
- int ret;
- if ((ret = ANativeWindow_lock(m_nativeSurface, &nativeWindowBuffer, &nativeWindowRect)) < 0) {
- qWarning() << "ANativeWindow_lock() failed! error=" << ret;
- return;
- }
-
- int bpp = 4;
- if (nativeWindowBuffer.format == WINDOW_FORMAT_RGB_565) {
- bpp = 2;
- m_pixelFormat = QImage::Format_RGB16;
- }
-
- QImage screenImage(reinterpret_cast<uchar *>(nativeWindowBuffer.bits)
- , nativeWindowBuffer.width, nativeWindowBuffer.height
- , nativeWindowBuffer.stride * bpp , m_pixelFormat);
-
- QPainter compositePainter(&screenImage);
- compositePainter.setCompositionMode(QPainter::CompositionMode_Source);
-
- QRegion visibleRegion(m_dirtyRect);
- for (QAndroidPlatformWindow *window : std::as_const(m_windowStack)) {
- if (!window->window()->isVisible()
- || qt_window_private(window->window())->compositing
- || !window->isRaster())
- continue;
-
- for (const QRect &rect : std::vector<QRect>(visibleRegion.begin(), visibleRegion.end())) {
- QRect targetRect = window->geometry();
- targetRect &= rect;
-
- if (targetRect.isNull())
- continue;
-
- visibleRegion -= targetRect;
- QRect windowRect = targetRect.translated(-window->geometry().topLeft());
- QAndroidPlatformBackingStore *backingStore = static_cast<QAndroidPlatformWindow *>(window)->backingStore();
- if (backingStore)
- compositePainter.drawImage(targetRect.topLeft(), backingStore->toImage(), windowRect);
- }
- }
-
- for (const QRect &rect : visibleRegion)
- compositePainter.fillRect(rect, QColor(Qt::transparent));
-
- ret = ANativeWindow_unlockAndPost(m_nativeSurface);
- if (ret >= 0)
- m_dirtyRect = QRect();
-
- if (screenGrabImage) {
- if (screenGrabImage->size() != screenImage.size()) {
- uchar* bytes = static_cast<uchar*>(malloc(screenImage.height() * screenImage.bytesPerLine()));
- *screenGrabImage = QImage(bytes, screenImage.width(), screenImage.height(),
- screenImage.bytesPerLine(), m_pixelFormat,
- [](void* ptr){ if (ptr) free (ptr);});
- }
- memcpy(screenGrabImage->bits(),
- screenImage.bits(),
- screenImage.bytesPerLine() * screenImage.height());
- }
- m_repaintOccurred = true;
-}
-
-QPixmap QAndroidPlatformScreen::doScreenShot(QRect grabRect)
-{
- if (!m_repaintOccurred)
- return QPixmap::fromImage(m_lastScreenshot.copy(grabRect));
- QRect tmp = m_dirtyRect;
- m_dirtyRect = geometry();
- doRedraw(&m_lastScreenshot);
- m_dirtyRect = tmp;
- m_repaintOccurred = false;
- return QPixmap::fromImage(m_lastScreenshot.copy(grabRect));
-}
-
static const int androidLogicalDpi = 72;
QDpi QAndroidPlatformScreen::logicalDpi() const
@@ -494,67 +314,4 @@ Qt::ScreenOrientation QAndroidPlatformScreen::nativeOrientation() const
{
return QAndroidPlatformIntegration::m_nativeOrientation;
}
-
-void QAndroidPlatformScreen::surfaceChanged(JNIEnv *env, jobject surface, int w, int h)
-{
- lockSurface();
- if (surface && w > 0 && h > 0) {
- releaseSurface();
- m_nativeSurface = ANativeWindow_fromSurface(env, surface);
- QMetaObject::invokeMethod(this, "setDirty", Qt::QueuedConnection, Q_ARG(QRect, QRect(0, 0, w, h)));
- } else {
- releaseSurface();
- }
- unlockSurface();
- m_surfaceWaitCondition.wakeOne();
-}
-
-void QAndroidPlatformScreen::releaseSurface()
-{
- if (m_nativeSurface) {
- ANativeWindow_release(m_nativeSurface);
- m_nativeSurface = 0;
- }
-}
-
-/*!
- This function is called when Qt needs to be able to grab the content of a window.
-
- Returns the content of the window specified with the WId handle within the boundaries of
- QRect(x, y, width, height).
-*/
-QPixmap QAndroidPlatformScreen::grabWindow(WId window, int x, int y, int width, int height) const
-{
- QRectF screenshotRect(x, y, width, height);
- QWindow* wnd = 0;
- if (window)
- {
- const auto windowList = qApp->allWindows();
- for (QWindow *w : windowList)
- if (w->winId() == window) {
- wnd = w;
- break;
- }
- }
- if (wnd) {
- const qreal factor = logicalDpi().first / androidLogicalDpi; //HighDPI factor;
- QRectF wndRect = wnd->geometry();
- if (wnd->parent())
- wndRect.moveTopLeft(wnd->parent()->mapToGlobal(wndRect.topLeft().toPoint()));
- if (!qFuzzyCompare(factor, 1))
- wndRect = QRectF(wndRect.left() * factor, wndRect.top() * factor,
- wndRect.width() * factor, wndRect.height() * factor);
-
- if (!screenshotRect.isEmpty()) {
- screenshotRect.moveTopLeft(wndRect.topLeft() + screenshotRect.topLeft());
- screenshotRect = screenshotRect.intersected(wndRect);
- } else {
- screenshotRect = wndRect;
- }
- } else {
- screenshotRect = screenshotRect.isValid() ? screenshotRect : geometry();
- }
- return const_cast<QAndroidPlatformScreen *>(this)->doScreenShot(screenshotRect.toRect());
-}
-
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h
index 076530613b..d850d0db09 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.h
+++ b/src/plugins/platforms/android/qandroidplatformscreen.h
@@ -5,25 +5,22 @@
#ifndef QANDROIDPLATFORMSCREEN_H
#define QANDROIDPLATFORMSCREEN_H
-#include "androidsurfaceclient.h"
-
#include <QList>
#include <QPainter>
#include <QTimer>
#include <QWaitCondition>
#include <QtCore/QJniObject>
#include <qpa/qplatformscreen.h>
-#include <qpa/qplatformscreen_p.h>
-
-#include <android/native_window.h>
+#include <QtGui/qscreen_platform.h>
QT_BEGIN_NAMESPACE
class QAndroidPlatformWindow;
-class QAndroidPlatformScreen: public QObject,
- public QPlatformScreen, public AndroidSurfaceClient,
- public QNativeInterface::Private::QAndroidScreen
+
+class QAndroidPlatformScreen : public QObject,
+ public QPlatformScreen,
+ public QNativeInterface::QAndroidScreen
{
Q_OBJECT
public:
@@ -41,22 +38,17 @@ public:
int currentMode() const override { return m_currentMode; }
int preferredMode() const override { return m_currentMode; }
qreal refreshRate() const override { return m_refreshRate; }
- inline QWindow *topWindow() const;
+ inline QWindow *topVisibleWindow() const;
QWindow *topLevelAt(const QPoint & p) const override;
- // compositor api
void addWindow(QAndroidPlatformWindow *window);
void removeWindow(QAndroidPlatformWindow *window);
void raise(QAndroidPlatformWindow *window);
void lower(QAndroidPlatformWindow *window);
-
- void scheduleUpdate();
- void topWindowChanged(QWindow *w);
- int rasterSurfaces();
+ void topVisibleWindowChanged();
int displayId() const override;
public slots:
- void setDirty(const QRect &rect);
void setPhysicalSize(const QSize &size);
void setAvailableGeometry(const QRect &rect);
void setSize(const QSize &size);
@@ -66,13 +58,8 @@ public slots:
void setOrientation(Qt::ScreenOrientation orientation);
protected:
- bool event(QEvent *event) override;
-
typedef QList<QAndroidPlatformWindow *> WindowStackType;
WindowStackType m_windowStack;
- QRect m_dirtyRect;
- bool m_updatePending = false;
-
QRect m_availableGeometry;
int m_depth;
QImage::Format m_format;
@@ -88,25 +75,9 @@ private:
QDpi logicalBaseDpi() const override;
Qt::ScreenOrientation orientation() const override;
Qt::ScreenOrientation nativeOrientation() const override;
- QPixmap grabWindow(WId window, int x, int y, int width, int height) const override;
- void surfaceChanged(JNIEnv *env, jobject surface, int w, int h) override;
- void releaseSurface();
void applicationStateChanged(Qt::ApplicationState);
- QPixmap doScreenShot(QRect grabRect = QRect());
-
-private slots:
- void doRedraw(QImage *screenGrabImage = nullptr);
-
private:
- int m_surfaceId = -1;
- QAtomicInt m_rasterSurfaces = 0;
- ANativeWindow* m_nativeSurface = nullptr;
- QWaitCondition m_surfaceWaitCondition;
QSize m_size;
-
- QImage m_lastScreenshot;
- QImage::Format m_pixelFormat = QImage::Format_RGBA8888_Premultiplied;
- bool m_repaintOccurred = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp
index 2599fe6db2..39287aa905 100644
--- a/src/plugins/platforms/android/qandroidplatformservices.cpp
+++ b/src/plugins/platforms/android/qandroidplatformservices.cpp
@@ -24,17 +24,25 @@ QAndroidPlatformServices::QAndroidPlatformServices()
QtAndroidPrivate::registerNewIntentListener(this);
- QMetaObject::invokeMethod(
- this,
- [this] {
- QJniObject context = QJniObject(QtAndroidPrivate::context());
- QJniObject intent =
- context.callObjectMethod("getIntent", "()Landroid/content/Intent;");
- handleNewIntent(nullptr, intent.object());
- },
- Qt::QueuedConnection);
+ // Qt applications without Activity contexts cannot retrieve intents from the Activity.
+ if (QNativeInterface::QAndroidApplication::isActivityContext()) {
+ QMetaObject::invokeMethod(
+ this,
+ [this] {
+ QJniObject context = QJniObject(QtAndroidPrivate::context());
+ QJniObject intent =
+ context.callObjectMethod("getIntent", "()Landroid/content/Intent;");
+ handleNewIntent(nullptr, intent.object());
+ },
+ Qt::QueuedConnection);
+ }
}
+Q_DECLARE_JNI_CLASS(UriType, "android/net/Uri")
+Q_DECLARE_JNI_CLASS(FileType, "java/io/File")
+Q_DECLARE_JNI_CLASS(File, "java/io/File")
+Q_DECLARE_JNI_CLASS(FileProvider, "androidx/core/content/FileProvider");
+
bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
{
QString mime;
@@ -55,13 +63,36 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
if (url.scheme() == fileScheme)
mime = QMimeDatabase().mimeTypeForUrl(url).name();
+ const QJniObject mimeString = QJniObject::fromString(mime);
+
using namespace QNativeInterface;
- QJniObject urlString = QJniObject::fromString(url.toString());
- QJniObject mimeString = QJniObject::fromString(mime);
- return QJniObject::callStaticMethod<jboolean>(
- QtAndroid::applicationClass(), "openURL",
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z",
- QAndroidApplication::context(), urlString.object(), mimeString.object());
+
+ auto openUrl = [mimeString](const QJniObject &url) {
+ return QJniObject::callStaticMethod<jboolean>(QtAndroid::applicationClass(), "openURL",
+ QAndroidApplication::context(), url.object<jstring>(), mimeString.object<jstring>());
+ };
+
+ if (url.scheme() != fileScheme || QNativeInterface::QAndroidApplication::sdkVersion() < 24)
+ return openUrl(QJniObject::fromString(url.toString()));
+
+ // Use FileProvider for file scheme with sdk >= 24
+ const QJniObject context = QAndroidApplication::context();
+ const auto appId = context.callMethod<jstring>("getPackageName").toString();
+ const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1);
+
+ const auto urlPath = QJniObject::fromString(url.path());
+ const auto urlFile = QJniObject(QtJniTypes::Traits<QtJniTypes::File>::className(),
+ urlPath.object<jstring>());
+
+ const auto fileProviderUri = QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::FileProvider>::className(), "getUriForFile",
+ QAndroidApplication::context(), providerName.object<jstring>(),
+ urlFile.object<QtJniTypes::FileType>());
+
+ if (fileProviderUri.isValid())
+ return openUrl(fileProviderUri.callMethod<jstring>("toString"));
+
+ return false;
}
bool QAndroidPlatformServices::openDocument(const QUrl &url)
diff --git a/src/plugins/platforms/android/qandroidplatformtheme.cpp b/src/plugins/platforms/android/qandroidplatformtheme.cpp
index 1d090024be..7b9072df69 100644
--- a/src/plugins/platforms/android/qandroidplatformtheme.cpp
+++ b/src/plugins/platforms/android/qandroidplatformtheme.cpp
@@ -4,6 +4,7 @@
#include "androidjnimain.h"
#include "androidjnimenu.h"
#include "qandroidplatformtheme.h"
+#include "qandroidplatformiconengine.h"
#include "qandroidplatformmenubar.h"
#include "qandroidplatformmenu.h"
#include "qandroidplatformmenuitem.h"
@@ -22,6 +23,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
+
using namespace Qt::StringLiterals;
namespace {
@@ -158,18 +161,11 @@ QJsonObject AndroidStyle::loadStyleData()
if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
stylePath += slashChar;
- if (QAndroidPlatformIntegration::appearance() == Qt::Appearance::Dark)
+ if (QAndroidPlatformIntegration::colorScheme() == Qt::ColorScheme::Dark)
stylePath += "darkUiMode/"_L1;
Q_ASSERT(!stylePath.isEmpty());
- QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME"));
- if (!androidTheme.isEmpty() && !androidTheme.endsWith(slashChar))
- androidTheme += slashChar;
-
- if (!androidTheme.isEmpty() && QFileInfo::exists(stylePath + androidTheme + "style.json"_L1))
- stylePath += androidTheme;
-
QFile f(stylePath + "style.json"_L1);
if (!f.open(QIODevice::ReadOnly))
return QJsonObject();
@@ -333,7 +329,7 @@ QAndroidPlatformTheme::~QAndroidPlatformTheme()
m_instance = nullptr;
}
-void QAndroidPlatformTheme::updateAppearance()
+void QAndroidPlatformTheme::updateColorScheme()
{
updateStyle();
QWindowSystemInterface::handleThemeChange();
@@ -358,7 +354,7 @@ void QAndroidPlatformTheme::updateStyle()
QColor highlight(148, 210, 231);
QColor disabledShadow = shadow.lighter(150);
- if (appearance() == Qt::Appearance::Dark) {
+ if (colorScheme() == Qt::ColorScheme::Dark) {
// Colors were prepared based on Theme.DeviceDefault.DayNight
windowText = QColor(250, 250, 250);
background = QColor(48, 48, 48);
@@ -400,27 +396,34 @@ void QAndroidPlatformTheme::updateStyle()
QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const
{
- return new QAndroidPlatformMenuBar;
+ auto *menuBar = new QAndroidPlatformMenuBar;
+ qCDebug(lcQpaMenus) << "Created" << menuBar;
+ return menuBar;
}
QPlatformMenu *QAndroidPlatformTheme::createPlatformMenu() const
{
- return new QAndroidPlatformMenu;
+ auto *menu = new QAndroidPlatformMenu;
+ qCDebug(lcQpaMenus) << "Created" << menu;
+ return menu;
}
QPlatformMenuItem *QAndroidPlatformTheme::createPlatformMenuItem() const
{
- return new QAndroidPlatformMenuItem;
+ auto *menuItem = new QAndroidPlatformMenuItem;
+ qCDebug(lcQpaMenus) << "Created" << menuItem;
+ return menuItem;
}
void QAndroidPlatformTheme::showPlatformMenuBar()
{
+ qCDebug(lcQpaMenus) << "Showing platform menu bar";
QtAndroidMenu::openOptionsMenu();
}
-Qt::Appearance QAndroidPlatformTheme::appearance() const
+Qt::ColorScheme QAndroidPlatformTheme::colorScheme() const
{
- return QAndroidPlatformIntegration::appearance();
+ return QAndroidPlatformIntegration::colorScheme();
}
static inline int paletteType(QPlatformTheme::Palette type)
@@ -488,6 +491,11 @@ const QFont *QAndroidPlatformTheme::font(Font type) const
return 0;
}
+QIconEngine *QAndroidPlatformTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAndroidPlatformIconEngine(iconName);
+}
+
QVariant QAndroidPlatformTheme::themeHint(ThemeHint hint) const
{
switch (hint) {
diff --git a/src/plugins/platforms/android/qandroidplatformtheme.h b/src/plugins/platforms/android/qandroidplatformtheme.h
index 38e653ab71..ce3d6d5f73 100644
--- a/src/plugins/platforms/android/qandroidplatformtheme.h
+++ b/src/plugins/platforms/android/qandroidplatformtheme.h
@@ -9,13 +9,15 @@
#include <QtGui/qpalette.h>
#include <QtCore/qhash.h>
#include <QtCore/qbytearray.h>
-
+#include <QtCore/qloggingcategory.h>
#include <QJsonObject>
#include <memory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
+
struct AndroidStyle
{
static QJsonObject loadStyleData();
@@ -31,15 +33,16 @@ class QAndroidPlatformTheme: public QPlatformTheme
{
public:
~QAndroidPlatformTheme();
- void updateAppearance();
+ void updateColorScheme();
void updateStyle();
QPlatformMenuBar *createPlatformMenuBar() const override;
QPlatformMenu *createPlatformMenu() const override;
QPlatformMenuItem *createPlatformMenuItem() const override;
void showPlatformMenuBar() override;
- Qt::Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
const QPalette *palette(Palette type = SystemPalette) const override;
const QFont *font(Font type = SystemFont) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
QVariant themeHint(ThemeHint hint) const override;
QString standardButtonText(int button) const override;
bool usePlatformNativeDialog(DialogType type) const override;
diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
index cb6bb6d390..4bf4f44fa1 100644
--- a/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
@@ -18,7 +18,6 @@ QT_BEGIN_NAMESPACE
QAndroidPlatformVulkanWindow::QAndroidPlatformVulkanWindow(QWindow *window)
: QAndroidPlatformWindow(window),
- m_nativeSurfaceId(-1),
m_nativeWindow(nullptr),
m_vkSurface(0),
m_createVkSurface(nullptr),
@@ -29,11 +28,7 @@ QAndroidPlatformVulkanWindow::QAndroidPlatformVulkanWindow(QWindow *window)
QAndroidPlatformVulkanWindow::~QAndroidPlatformVulkanWindow()
{
m_surfaceWaitCondition.wakeOne();
- lockSurface();
- if (m_nativeSurfaceId != -1)
- QtAndroid::destroySurface(m_nativeSurfaceId);
- clearSurface();
- unlockSurface();
+ destroyAndClearSurface();
}
void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
@@ -44,8 +39,8 @@ void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
m_oldGeometry = geometry();
QAndroidPlatformWindow::setGeometry(rect);
- if (m_nativeSurfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect);
+ if (m_surfaceCreated)
+ setNativeGeometry(rect);
QRect availableGeometry = screen()->availableGeometry();
if (rect.width() > 0
@@ -54,22 +49,13 @@ void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
&& availableGeometry.height() > 0) {
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size()));
}
-
- if (rect.topLeft() != m_oldGeometry.topLeft())
- repaint(QRegion(rect));
}
void QAndroidPlatformVulkanWindow::applicationStateChanged(Qt::ApplicationState state)
{
QAndroidPlatformWindow::applicationStateChanged(state);
if (state <= Qt::ApplicationHidden) {
- lockSurface();
- if (m_nativeSurfaceId != -1) {
- QtAndroid::destroySurface(m_nativeSurfaceId);
- m_nativeSurfaceId = -1;
- }
- clearSurface();
- unlockSurface();
+ destroyAndClearSurface();
}
}
@@ -91,27 +77,12 @@ void QAndroidPlatformVulkanWindow::clearSurface()
}
}
-void QAndroidPlatformVulkanWindow::sendExpose()
-{
- QRect availableGeometry = screen()->availableGeometry();
- if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
- QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(), geometry().size())));
-}
-
-void QAndroidPlatformVulkanWindow::surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h)
+void QAndroidPlatformVulkanWindow::destroyAndClearSurface()
{
- Q_UNUSED(jniEnv);
- Q_UNUSED(w);
- Q_UNUSED(h);
-
lockSurface();
- m_androidSurfaceObject = surface;
- if (surface)
- m_surfaceWaitCondition.wakeOne();
+ destroySurface();
+ clearSurface();
unlockSurface();
-
- if (surface)
- sendExpose();
}
VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
@@ -124,16 +95,15 @@ VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
clearSurface();
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1) {
+ if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return &m_vkSurface;
- const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
- m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32);
+ createSurface();
m_surfaceWaitCondition.wait(&m_surfaceMutex);
}
- if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
+ if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return &m_vkSurface;
QJniEnvironment env;
diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.h b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
index 14d5b7fc0e..fa959239d1 100644
--- a/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
@@ -10,7 +10,6 @@
#define VK_USE_PLATFORM_ANDROID_KHR
-#include "androidsurfaceclient.h"
#include "qandroidplatformvulkaninstance.h"
#include "qandroidplatformwindow.h"
@@ -20,7 +19,7 @@
QT_BEGIN_NAMESPACE
-class QAndroidPlatformVulkanWindow : public QAndroidPlatformWindow, public AndroidSurfaceClient
+class QAndroidPlatformVulkanWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformVulkanWindow(QWindow *window);
@@ -32,17 +31,11 @@ public:
VkSurfaceKHR *vkSurface();
-protected:
- void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) override;
-
private:
- void sendExpose();
void clearSurface();
+ void destroyAndClearSurface();
- int m_nativeSurfaceId;
ANativeWindow *m_nativeWindow;
- QJniObject m_androidSurfaceObject;
- QWaitCondition m_surfaceWaitCondition;
QSurfaceFormat m_format;
QRect m_oldGeometry;
VkSurfaceKHR m_vkSurface;
diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp
index b1eba17d04..979f0fb98a 100644
--- a/src/plugins/platforms/android/qandroidplatformwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp
@@ -14,40 +14,95 @@
QT_BEGIN_NAMESPACE
-Q_CONSTINIT static QBasicAtomicInt winIdGenerator = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
- : QPlatformWindow(window)
+ : QPlatformWindow(window), m_nativeQtWindow(nullptr),
+ m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr),
+ m_androidSurfaceObject(nullptr)
{
m_windowFlags = Qt::Widget;
m_windowState = Qt::WindowNoState;
- m_windowId = winIdGenerator.fetchAndAddRelaxed(1) + 1;
+ // the surfaceType is overwritten in QAndroidPlatformOpenGLWindow ctor so let's save
+ // the fact that it's a raster window for now
+ m_isRaster = window->surfaceType() == QSurface::RasterSurface;
setWindowState(window->windowStates());
// the following is in relation to the virtual geometry
const bool forceMaximize = m_windowState & (Qt::WindowMaximized | Qt::WindowFullScreen);
- const QRect requestedNativeGeometry =
- forceMaximize ? QRect() : QHighDpi::toNativePixels(window->geometry(), window);
- const QRect availableDeviceIndependentGeometry = (window->parent())
- ? window->parent()->geometry()
- : QHighDpi::fromNativePixels(platformScreen()->availableGeometry(), window);
+ const QRect nativeScreenGeometry = platformScreen()->availableGeometry();
+ if (forceMaximize) {
+ setGeometry(nativeScreenGeometry);
+ } else {
+ const QRect requestedNativeGeometry = QHighDpi::toNativePixels(window->geometry(), window);
+ const QRect availableDeviceIndependentGeometry = (window->parent())
+ ? window->parent()->geometry()
+ : QHighDpi::fromNativePixels(nativeScreenGeometry, window);
+ // initialGeometry returns in native pixels
+ const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
+ window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
+ availableDeviceIndependentGeometry.height());
+ if (requestedNativeGeometry != finalNativeGeometry)
+ setGeometry(finalNativeGeometry);
+ }
+
+ if (isEmbeddingContainer())
+ return;
+
+ if (parent()) {
+ QAndroidPlatformWindow *androidParent = static_cast<QAndroidPlatformWindow*>(parent());
+ if (!androidParent->isEmbeddingContainer())
+ m_nativeParentQtWindow = androidParent->nativeWindow();
+ }
+
+ m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>(
+ QNativeInterface::QAndroidApplication::context(),
+ m_nativeParentQtWindow,
+ QtAndroid::qtInputDelegate());
+ m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
- // initialGeometry returns in native pixels
- const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
- window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
- availableDeviceIndependentGeometry.height());
+ if (window->isTopLevel())
+ platformScreen()->addWindow(this);
- if (requestedNativeGeometry != finalNativeGeometry)
- setGeometry(finalNativeGeometry);
+ static bool ok = false;
+ static const int value = qEnvironmentVariableIntValue("QT_ANDROID_SURFACE_CONTAINER_TYPE", &ok);
+ if (ok) {
+ static const SurfaceContainer type = static_cast<SurfaceContainer>(value);
+ if (type == SurfaceContainer::SurfaceView || type == SurfaceContainer::TextureView)
+ m_surfaceContainerType = type;
+ } else if (platformScreen()->windows().size() <= 1) {
+ // TODO should handle case where this changes at runtime -> need to change existing window
+ // into TextureView (or perhaps not, if the parent window would be SurfaceView, as long as
+ // onTop was false it would stay below the children)
+ m_surfaceContainerType = SurfaceContainer::SurfaceView;
+ }
+ qCDebug(lcQpaWindow) << "Window" << m_nativeViewId << "using surface container type"
+ << static_cast<int>(m_surfaceContainerType);
}
+QAndroidPlatformWindow::~QAndroidPlatformWindow()
+{
+ if (window()->isTopLevel())
+ platformScreen()->removeWindow(this);
+}
+
+
void QAndroidPlatformWindow::lower()
{
+ if (m_nativeParentQtWindow.isValid()) {
+ m_nativeParentQtWindow.callMethod<void>("bringChildToBack", nativeViewId());
+ return;
+ }
platformScreen()->lower(this);
}
void QAndroidPlatformWindow::raise()
{
+ if (m_nativeParentQtWindow.isValid()) {
+ m_nativeParentQtWindow.callMethod<void>("bringChildToFront", nativeViewId());
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
+ return;
+ }
updateSystemUiVisibility();
platformScreen()->raise(this);
}
@@ -71,23 +126,25 @@ void QAndroidPlatformWindow::setGeometry(const QRect &rect)
void QAndroidPlatformWindow::setVisible(bool visible)
{
- if (visible)
- updateSystemUiVisibility();
+ if (isEmbeddingContainer())
+ return;
+ m_nativeQtWindow.callMethod<void>("setVisible", visible);
if (visible) {
- if ((m_windowState & Qt::WindowFullScreen)
- || ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint))) {
- setGeometry(platformScreen()->geometry());
- } else if (m_windowState & Qt::WindowMaximized) {
- setGeometry(platformScreen()->availableGeometry());
+ if (window()->isTopLevel()) {
+ updateSystemUiVisibility();
+ if ((m_windowState & Qt::WindowFullScreen)
+ || ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint))) {
+ setGeometry(platformScreen()->geometry());
+ } else if (m_windowState & Qt::WindowMaximized) {
+ setGeometry(platformScreen()->availableGeometry());
+ }
+ requestActivateWindow();
}
+ } else if (window()->isTopLevel() && window() == qGuiApp->focusWindow()) {
+ platformScreen()->topVisibleWindowChanged();
}
- if (visible)
- platformScreen()->addWindow(this);
- else
- platformScreen()->removeWindow(this);
-
QRect availableGeometry = screen()->availableGeometry();
if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
QPlatformWindow::setVisible(visible);
@@ -120,7 +177,30 @@ Qt::WindowFlags QAndroidPlatformWindow::windowFlags() const
void QAndroidPlatformWindow::setParent(const QPlatformWindow *window)
{
- Q_UNUSED(window);
+ using namespace QtJniTypes;
+
+ if (window) {
+ auto androidWindow = static_cast<const QAndroidPlatformWindow*>(window);
+ if (androidWindow->isEmbeddingContainer())
+ return;
+ // If we were a top level window, remove from screen
+ if (!m_nativeParentQtWindow.isValid())
+ platformScreen()->removeWindow(this);
+
+ const QtWindow parentWindow = androidWindow->nativeWindow();
+ // If this was a child window of another window, the java method takes care of that
+ m_nativeQtWindow.callMethod<void, QtWindow>("setParent", parentWindow.object());
+ m_nativeParentQtWindow = parentWindow;
+ } else if (QtAndroid::isQtApplication()) {
+ m_nativeQtWindow.callMethod<void, QtWindow>("setParent", nullptr);
+ m_nativeParentQtWindow = QJniObject();
+ platformScreen()->addWindow(this);
+ }
+}
+
+WId QAndroidPlatformWindow::winId() const
+{
+ return m_nativeQtWindow.isValid() ? reinterpret_cast<WId>(m_nativeQtWindow.object()) : 0L;
}
QAndroidPlatformScreen *QAndroidPlatformWindow::platformScreen() const
@@ -135,7 +215,9 @@ void QAndroidPlatformWindow::propagateSizeHints()
void QAndroidPlatformWindow::requestActivateWindow()
{
- platformScreen()->topWindowChanged(window());
+ // raise() will handle differences between top level and child windows, and requesting focus
+ if (!blockedByModal())
+ raise();
}
void QAndroidPlatformWindow::updateSystemUiVisibility()
@@ -169,4 +251,134 @@ void QAndroidPlatformWindow::applicationStateChanged(Qt::ApplicationState)
QWindowSystemInterface::flushWindowSystemEvents();
}
+void QAndroidPlatformWindow::createSurface()
+{
+ const QRect rect = geometry();
+ jint x = 0, y = 0, w = -1, h = -1;
+ if (!rect.isNull()) {
+ x = rect.x();
+ y = rect.y();
+ w = std::max(rect.width(), 1);
+ h = std::max(rect.height(), 1);
+ }
+
+ const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
+ const bool isOpaque = !format().hasAlpha() && qFuzzyCompare(window()->opacity(), 1.0);
+
+ m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, x, y, w, h, 32, isOpaque,
+ m_surfaceContainerType);
+ m_surfaceCreated = true;
+}
+
+void QAndroidPlatformWindow::destroySurface()
+{
+ if (m_surfaceCreated) {
+ m_nativeQtWindow.callMethod<void>("destroySurface");
+ m_surfaceCreated = false;
+ }
+}
+
+void QAndroidPlatformWindow::setNativeGeometry(const QRect &geometry)
+{
+ if (!m_surfaceCreated)
+ return;
+
+ jint x = 0;
+ jint y = 0;
+ jint w = -1;
+ jint h = -1;
+ if (!geometry.isNull()) {
+ x = geometry.x();
+ y = geometry.y();
+ w = geometry.width();
+ h = geometry.height();
+ }
+ m_nativeQtWindow.callMethod<void>("setGeometry", x, y, w, h);
+}
+
+void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
+{
+ lockSurface();
+ m_androidSurfaceObject = surface;
+ if (m_androidSurfaceObject.isValid()) // wait until we have a valid surface to draw into
+ m_surfaceWaitCondition.wakeOne();
+ unlockSurface();
+
+ if (m_androidSurfaceObject.isValid()) {
+ // repaint the window, when we have a valid surface
+ sendExpose();
+ }
+}
+
+void QAndroidPlatformWindow::sendExpose() const
+{
+ QRect availableGeometry = screen()->availableGeometry();
+ if (!geometry().isNull() && !availableGeometry.isNull()) {
+ QWindowSystemInterface::handleExposeEvent(window(),
+ QRegion(QRect(QPoint(), geometry().size())));
+ }
+}
+
+bool QAndroidPlatformWindow::blockedByModal() const
+{
+ QWindow *modalWindow = QGuiApplication::modalWindow();
+ return modalWindow && modalWindow != window();
+}
+
+bool QAndroidPlatformWindow::isEmbeddingContainer() const
+{
+ // Returns true if the window is a wrapper for a foreign window solely to allow embedding Qt
+ // into a native Android app, in which case we should not try to control it more than we "need" to
+ return !QtAndroid::isQtApplication() && window()->isTopLevel();
+}
+
+void QAndroidPlatformWindow::setSurface(JNIEnv *env, jobject object, jint windowId,
+ QtJniTypes::Surface surface)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(object)
+
+ if (!qGuiApp)
+ return;
+
+ const QList<QWindow*> windows = qGuiApp->allWindows();
+ for (QWindow * window : windows) {
+ if (!window->handle())
+ continue;
+ QAndroidPlatformWindow *platformWindow =
+ static_cast<QAndroidPlatformWindow *>(window->handle());
+ if (platformWindow->nativeViewId() == windowId)
+ platformWindow->onSurfaceChanged(surface);
+ }
+}
+
+void QAndroidPlatformWindow::windowFocusChanged(JNIEnv *env, jobject object,
+ jboolean focus, jint windowId)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(object)
+ QWindow* window = QtAndroid::windowFromId(windowId);
+ Q_ASSERT_X(window, "QAndroidPlatformWindow", "windowFocusChanged event window should exist");
+ if (focus) {
+ QWindowSystemInterface::handleFocusWindowChanged(window);
+ } else if (!focus && window == qGuiApp->focusWindow()) {
+ // Clear focus if current window has lost focus
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr);
+ }
+}
+
+bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env)
+{
+ if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtWindow>::className(),
+ {
+ Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow),
+ Q_JNI_NATIVE_SCOPED_METHOD(windowFocusChanged, QAndroidPlatformWindow)
+ })) {
+ qCCritical(lcQpaWindow) << "RegisterNatives failed for"
+ << QtJniTypes::Traits<QtJniTypes::QtWindow>::className();
+ return false;
+ }
+ return true;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformwindow.h b/src/plugins/platforms/android/qandroidplatformwindow.h
index 6fccc2e7fe..3f1e8ac992 100644
--- a/src/plugins/platforms/android/qandroidplatformwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformwindow.h
@@ -7,17 +7,32 @@
#include <qobject.h>
#include <qrect.h>
#include <qpa/qplatformwindow.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnitypes.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qwaitcondition.h>
+#include <jni.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
+Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow")
+Q_DECLARE_JNI_CLASS(Surface, "android/view/Surface")
+
class QAndroidPlatformScreen;
-class QAndroidPlatformBackingStore;
class QAndroidPlatformWindow: public QPlatformWindow
{
public:
- explicit QAndroidPlatformWindow(QWindow *window);
+ enum class SurfaceContainer {
+ SurfaceView,
+ TextureView
+ };
+ explicit QAndroidPlatformWindow(QWindow *window);
+ ~QAndroidPlatformWindow();
void lower() override;
void raise() override;
@@ -27,7 +42,8 @@ public:
void setWindowFlags(Qt::WindowFlags flags) override;
Qt::WindowFlags windowFlags() const;
void setParent(const QPlatformWindow *window) override;
- WId winId() const override { return m_windowId; }
+
+ WId winId() const override;
bool setMouseGrabEnabled(bool grab) override { Q_UNUSED(grab); return false; }
bool setKeyboardGrabEnabled(bool grab) override { Q_UNUSED(grab); return false; }
@@ -39,32 +55,46 @@ public:
void propagateSizeHints() override;
void requestActivateWindow() override;
void updateSystemUiVisibility();
- inline bool isRaster() const {
- if (isForeignWindow())
- return false;
-
- return window()->surfaceType() == QSurface::RasterSurface
- || window()->surfaceType() == QSurface::RasterGLSurface;
- }
+ inline bool isRaster() const { return m_isRaster; }
bool isExposed() const override;
+ QtJniTypes::QtWindow nativeWindow() const { return m_nativeQtWindow; }
virtual void applicationStateChanged(Qt::ApplicationState);
+ int nativeViewId() const { return m_nativeViewId; }
- void setBackingStore(QAndroidPlatformBackingStore *store) { m_backingStore = store; }
- QAndroidPlatformBackingStore *backingStore() const { return m_backingStore; }
-
- virtual void repaint(const QRegion &) { }
+ static bool registerNatives(QJniEnvironment &env);
+ void onSurfaceChanged(QtJniTypes::Surface surface);
protected:
void setGeometry(const QRect &rect) override;
+ void lockSurface() { m_surfaceMutex.lock(); }
+ void unlockSurface() { m_surfaceMutex.unlock(); }
+ void createSurface();
+ void destroySurface();
+ void setNativeGeometry(const QRect &geometry);
+ void sendExpose() const;
+ bool blockedByModal() const;
+ bool isEmbeddingContainer() const;
-protected:
Qt::WindowFlags m_windowFlags;
Qt::WindowStates m_windowState;
-
- WId m_windowId;
-
- QAndroidPlatformBackingStore *m_backingStore = nullptr;
+ bool m_isRaster;
+
+ int m_nativeViewId = -1;
+ QtJniTypes::QtWindow m_nativeQtWindow;
+ SurfaceContainer m_surfaceContainerType = SurfaceContainer::SurfaceView;
+ QtJniTypes::QtWindow m_nativeParentQtWindow;
+ // The Android Surface, accessed from multiple threads, guarded by m_surfaceMutex
+ QtJniTypes::Surface m_androidSurfaceObject;
+ QWaitCondition m_surfaceWaitCondition;
+ bool m_surfaceCreated = false;
+ QMutex m_surfaceMutex;
+
+private:
+ static void setSurface(JNIEnv *env, jobject obj, jint windowId, QtJniTypes::Surface surface);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setSurface)
+ static void windowFocusChanged(JNIEnv *env, jobject object, jboolean focus, jint windowId);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(windowFocusChanged)
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidsystemlocale.cpp b/src/plugins/platforms/android/qandroidsystemlocale.cpp
index 858934b1f8..c5f2e59097 100644
--- a/src/plugins/platforms/android/qandroidsystemlocale.cpp
+++ b/src/plugins/platforms/android/qandroidsystemlocale.cpp
@@ -12,34 +12,38 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(Locale, "java/util/Locale")
+Q_DECLARE_JNI_CLASS(Resources, "android/content/res/Resources")
+Q_DECLARE_JNI_CLASS(Configuration, "android/content/res/Configuration")
+Q_DECLARE_JNI_CLASS(LocaleList, "android/os/LocaleList")
+
+using namespace QtJniTypes;
+
QAndroidSystemLocale::QAndroidSystemLocale() : m_locale(QLocale::C)
{
}
void QAndroidSystemLocale::getLocaleFromJava() const
{
- QWriteLocker locker(&m_lock);
-
- QJniObject javaLocaleObject;
- QJniObject javaActivity(QtAndroid::activity());
- if (!javaActivity.isValid())
- javaActivity = QtAndroid::service();
- if (javaActivity.isValid()) {
- QJniObject resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
- QJniObject configuration = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
-
- javaLocaleObject = configuration.getObjectField("locale", "Ljava/util/Locale;");
- } else {
- javaLocaleObject = QJniObject::callStaticObjectMethod("java/util/Locale", "getDefault", "()Ljava/util/Locale;");
- }
+ const Locale javaLocaleObject = []{
+ const QJniObject javaContext = QtAndroidPrivate::context();
+ if (javaContext.isValid()) {
+ const QJniObject resources = javaContext.callMethod<Resources>("getResources");
+ const QJniObject configuration = resources.callMethod<Configuration>("getConfiguration");
+ return configuration.getField<Locale>("locale");
+ } else {
+ return Locale::callStaticMethod<Locale>("getDefault");
+ }
+ }();
- QString languageCode = javaLocaleObject.callObjectMethod("getLanguage", "()Ljava/lang/String;").toString();
- QString countryCode = javaLocaleObject.callObjectMethod("getCountry", "()Ljava/lang/String;").toString();
+ const QString languageCode = javaLocaleObject.callMethod<QString>("getLanguage");
+ const QString countryCode = javaLocaleObject.callMethod<QString>("getCountry");
+ QWriteLocker locker(&m_lock);
m_locale = QLocale(languageCode + u'_' + countryCode);
}
-QVariant QAndroidSystemLocale::query(QueryType type, QVariant in) const
+QVariant QAndroidSystemLocale::query(QueryType type, QVariant &&in) const
{
if (type == LocaleChanged) {
getLocaleFromJava();
@@ -142,12 +146,9 @@ QVariant QAndroidSystemLocale::query(QueryType type, QVariant in) const
Q_ASSERT_X(false, Q_FUNC_INFO, "This can't happen.");
case UILanguages: {
if (QtAndroidPrivate::androidSdkVersion() >= 24) {
- QJniObject localeListObject =
- QJniObject::callStaticObjectMethod("android/os/LocaleList", "getDefault",
- "()Landroid/os/LocaleList;");
+ LocaleList localeListObject = LocaleList::callStaticMethod<LocaleList>("getDefault");
if (localeListObject.isValid()) {
- QString lang = localeListObject.callObjectMethod("toLanguageTags",
- "()Ljava/lang/String;").toString();
+ QString lang = localeListObject.callMethod<QString>("toLanguageTags");
// Some devices return with it enclosed in []'s so check if both exists before
// removing to ensure it is formatted correctly
if (lang.startsWith(QChar('[')) && lang.endsWith(QChar(']')))
diff --git a/src/plugins/platforms/android/qandroidsystemlocale.h b/src/plugins/platforms/android/qandroidsystemlocale.h
index 48e1d94a56..cd37b48270 100644
--- a/src/plugins/platforms/android/qandroidsystemlocale.h
+++ b/src/plugins/platforms/android/qandroidsystemlocale.h
@@ -11,10 +11,11 @@ QT_BEGIN_NAMESPACE
class QAndroidSystemLocale : public QSystemLocale
{
+ Q_DISABLE_COPY_MOVE(QAndroidSystemLocale)
public:
QAndroidSystemLocale();
- QVariant query(QueryType type, QVariant in) const override;
+ QVariant query(QueryType type, QVariant &&in) const override;
QLocale fallbackLocale() const override;
private:
diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt
index cc4722cb3e..92e681d8fb 100644
--- a/src/plugins/platforms/cocoa/CMakeLists.txt
+++ b/src/plugins/platforms/cocoa/CMakeLists.txt
@@ -1,17 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from cocoa.pro.
-
-# special case:
-
#####################################################################
## QCocoaIntegrationPlugin Plugin:
#####################################################################
qt_internal_add_plugin(QCocoaIntegrationPlugin
OUTPUT_NAME qcocoa
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa
PLUGIN_TYPE platforms
SOURCES
main.mm
@@ -60,15 +56,14 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin
${FWIOSurface}
${FWMetal}
${FWQuartzCore}
+ ${FWUniformTypeIdentifiers}
Qt::Core
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)
-# special case begin
qt_disable_apple_app_extension_api_only(QCocoaIntegrationPlugin)
-# special case end
# Resources:
set(qcocoaresources_resource_files
@@ -84,10 +79,6 @@ qt_internal_add_resource(QCocoaIntegrationPlugin "qcocoaresources"
${qcocoaresources_resource_files}
)
-
-#### Keys ignored in scope 1:.:.:cocoa.pro:<TRUE>:
-# OTHER_FILES = "cocoa.json"
-
## Scopes:
#####################################################################
@@ -111,8 +102,3 @@ qt_internal_extend_target(QCocoaIntegrationPlugin CONDITION QT_FEATURE_sessionma
SOURCES
qcocoasessionmanager.cpp qcocoasessionmanager.h
)
-
-#### Keys ignored in scope 7:.:.:cocoa.pro:TARGET Qt::Widgets:
-# QT_FOR_CONFIG = "widgets"
-#### Keys ignored in scope 12:.:.:cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
index dc2baa6961..c5e40a4087 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
@@ -117,7 +117,6 @@ static void populateRoleMap()
roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
roleMap[QAccessible::Row] = NSAccessibilityRowRole;
roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
- roleMap[QAccessible::Cell] = NSAccessibilityTextFieldRole;
roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
@@ -125,7 +124,7 @@ static void populateRoleMap()
roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
roleMap[QAccessible::List] = NSAccessibilityListRole;
roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
- roleMap[QAccessible::Cell] = NSAccessibilityStaticTextRole;
+ roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
@@ -137,6 +136,7 @@ static void populateRoleMap()
roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
+ roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
}
/*
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
index 1f121e2fd8..a96ab55735 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
@@ -15,7 +15,9 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMacAccessibilityElement, NSObject <NSAcces
- (instancetype)initWithId:(QAccessible::Id)anId;
- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role;
+ (instancetype)elementWithId:(QAccessible::Id)anId;
++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface;
- (void)updateTableModel;
+- (QAccessibleInterface *)qtInterface;
)
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
index 68e7947162..8d4d6d683d 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
@@ -9,12 +9,17 @@
#include "qcocoawindow.h"
#include "qcocoascreen.h"
+#include <QtCore/qlogging.h>
#include <QtGui/private/qaccessiblecache_p.h>
#include <QtGui/private/qaccessiblebridgeutils_p.h>
#include <QtGui/qaccessible.h>
QT_USE_NAMESPACE
+Q_LOGGING_CATEGORY(lcAccessibilityTable, "qt.accessibility.table")
+
+using namespace Qt::Literals::StringLiterals;
+
#if QT_CONFIG(accessibility)
/**
@@ -80,6 +85,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
@implementation QMacAccessibilityElement {
QAccessible::Id axid;
+ int m_rowIndex;
+ int m_columnIndex;
// used by NSAccessibilityTable
NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows
@@ -105,12 +112,65 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
self = [super init];
if (self) {
axid = anId;
+ m_rowIndex = -1;
+ m_columnIndex = -1;
rows = nil;
columns = nil;
synthesizedRole = role;
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->tableInterface() && !synthesizedRole)
- [self updateTableModel];
+ // table: if this is not created as an element managed by the table, then
+ // it's either the table itself, or an element created for an already existing
+ // cell interface (or an element that's not at all related to a table).
+ if (!synthesizedRole) {
+ if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) {
+ if (iface->tableInterface()) {
+ [self updateTableModel];
+ } else if (const auto *cell = iface->tableCellInterface()) {
+ // If we create an element for a table cell, initialize it with row/column
+ // and insert it into the corresponding row's columns array.
+ m_rowIndex = cell->rowIndex();
+ m_columnIndex = cell->columnIndex();
+ QAccessibleInterface *table = cell->table();
+ Q_ASSERT(table);
+ QAccessibleTableInterface *tableInterface = table->tableInterface();
+ if (tableInterface) {
+ auto *tableElement = [QMacAccessibilityElement elementWithInterface:table];
+ Q_ASSERT(tableElement);
+ if (!tableElement->rows
+ || int(tableElement->rows.count) <= m_rowIndex
+ || int(tableElement->rows.count) != tableInterface->rowCount()) {
+ qCWarning(lcAccessibilityTable)
+ << "Cell requested for row" << m_rowIndex << "is out of"
+ << "bounds for table with" << (tableElement->rows ?
+ tableElement->rows.count : tableInterface->rowCount())
+ << "rows! Resizing table model.";
+ [tableElement updateTableModel];
+ }
+
+ Q_ASSERT(tableElement->rows);
+ Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
+
+ auto *rowElement = tableElement->rows[m_rowIndex];
+ if (!rowElement->columns || int(rowElement->columns.count) != tableInterface->columnCount()) {
+ if (rowElement->columns) {
+ qCWarning(lcAccessibilityTable)
+ << "Table representation column count is out of sync:"
+ << rowElement->columns.count << "!=" << tableInterface->columnCount();
+ }
+ rowElement->columns = [rowElement populateTableRow:rowElement->columns
+ count:tableInterface->columnCount()];
+ }
+
+ qCDebug(lcAccessibilityTable) << "Creating cell representation for"
+ << m_rowIndex << m_columnIndex
+ << "in table with"
+ << tableElement->rows.count << "rows and"
+ << rowElement->columns.count << "columns";
+
+ rowElement->columns[m_columnIndex] = self;
+ }
+ }
+ }
+ }
}
return self;
@@ -126,16 +186,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QMacAccessibilityElement *element = cache->elementForId(anId);
if (!element) {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(anId);
- Q_ASSERT(iface);
- if (!iface || !iface->isValid())
- return nil;
+ Q_ASSERT(QAccessible::accessibleInterface(anId));
element = [[self alloc] initWithId:anId];
cache->insertElement(anId, element);
}
return element;
}
++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface
+{
+ Q_ASSERT(iface);
+ if (!iface)
+ return nil;
+
+ const QAccessible::Id anId = QAccessible::uniqueId(iface);
+ return [self elementWithId:anId];
+}
+
- (void)invalidate {
axid = 0;
rows = nil;
@@ -173,8 +240,11 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count
{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (array && int(array.count) != count) {
+ [array release];
+ array = nil;
+ }
if (!array) {
array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
[array retain];
@@ -187,6 +257,10 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QMacAccessibilityElement *element =
[[QMacAccessibilityElement alloc] initWithId:axid role:role];
if (element) {
+ if (role == NSAccessibilityRowRole)
+ element->m_rowIndex = n;
+ else if (role == NSAccessibilityColumnRole)
+ element->m_columnIndex = n;
[array addObject:element];
[element release];
} else {
@@ -198,29 +272,99 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return nil;
}
+- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count
+{
+ Q_ASSERT(synthesizedRole == NSAccessibilityRowRole);
+ if (array && int(array.count) != count) {
+ [array release];
+ array = nil;
+ }
+
+ if (!array) {
+ array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
+ [array retain];
+ // When macOS asks for the children of a row, then we populate the row's column
+ // array with synthetic elements as place holders. This way, we don't have to
+ // create QAccessibleInterfaces for every cell before they are really needed.
+ // We don't add those synthetic elements into the cache, and we give them the
+ // same axid as the table. This way, we can get easily to the table, and from
+ // there to the QAccessibleInterface for the cell, when we have to eventually
+ // associate such an interface with the element (at which point it is no longer
+ // a placeholder).
+ for (int n = 0; n < count; ++n) {
+ // columns will have same axid as table (but not inserted in cache)
+ QMacAccessibilityElement *cell =
+ [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole];
+ if (cell) {
+ cell->m_rowIndex = m_rowIndex;
+ cell->m_columnIndex = n;
+ [array addObject:cell];
+ }
+ }
+ }
+ Q_ASSERT(array);
+ return array;
+}
- (void)updateTableModel
{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
if (QAccessibleTableInterface *table = iface->tableInterface()) {
Q_ASSERT(!self.isManagedByParent);
+ qCDebug(lcAccessibilityTable) << "Updating table representation with"
+ << table->rowCount() << table->columnCount();
rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()];
columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()];
}
}
}
+- (QAccessibleInterface *)qtInterface
+{
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
+ if (!iface || !iface->isValid())
+ return nullptr;
+
+ // If this is a placeholder element for a table cell, associate it with the
+ // cell interface (which will be created now, if needed). The current axid is
+ // for the table to which the cell belongs, so iface is pointing at the table.
+ if (synthesizedRole == NSAccessibilityCellRole) {
+ // get the cell interface - there must be a valid one
+ QAccessibleTableInterface *table = iface->tableInterface();
+ Q_ASSERT(table);
+ QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex);
+ if (!cell)
+ return nullptr;
+ Q_ASSERT(cell->isValid());
+ iface = cell;
+
+ // no longer a placeholder
+ axid = QAccessible::uniqueId(cell);
+ synthesizedRole = nil;
+
+ QAccessibleCache *cache = QAccessibleCache::instance();
+ if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) {
+ // there already is another, non-placeholder element in the cache
+ Q_ASSERT(cellElement->synthesizedRole == nil);
+ // we have to release it if it's not us
+ if (cellElement != self) {
+ // for the same cell position
+ Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex);
+ [cellElement release];
+ }
+ }
+
+ cache->insertElement(axid, self);
+ }
+ return iface;
+}
+
//
// accessibility protocol
//
- (BOOL)isAccessibilityFocused
{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
- return false;
- }
// Just check if the app thinks we're focused.
id focusedElement = NSApp.accessibilityApplicationFocusedUIElement;
return [focusedElement isEqual:self];
@@ -240,31 +384,33 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityRole {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
+ // shortcut for cells, rows, and columns in a table
if (synthesizedRole)
return synthesizedRole;
- return QCocoaAccessible::macRole(iface);
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::macRole(iface);
+ return NSAccessibilityUnknownRole;
}
- (NSString *) accessibilitySubRole {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
- return QCocoaAccessible::macSubrole(iface);
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::macSubrole(iface);
+ return NSAccessibilityUnknownRole;
}
- (NSString *) accessibilityRoleDescription {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
- return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
+ return NSAccessibilityUnknownRole;
}
- (NSArray *) accessibilityChildren {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ // shortcut for cells
+ if (synthesizedRole == NSAccessibilityCellRole)
+ return nil;
+
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nil;
if (QAccessibleTableInterface *table = iface->tableInterface()) {
// either a table or table rows/columns
@@ -320,30 +466,39 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
} else if (synthesizedRole == NSAccessibilityRowRole) {
// axid matches the parent table axid so that we can easily find the parent table
// children of row are cell/any items
- QMacAccessibilityElement *tableElement = [QMacAccessibilityElement elementWithId:axid];
- Q_ASSERT(tableElement->rows);
- NSUInteger rowIndex = [tableElement->rows indexOfObjectIdenticalTo:self];
- Q_ASSERT(rowIndex != NSNotFound);
- int numColumns = table->columnCount();
- NSMutableArray<QMacAccessibilityElement *> *cells =
- [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numColumns];
- for (int i = 0; i < numColumns; ++i) {
- QAccessibleInterface *cell = table->cellAt(rowIndex, i);
- if (cell && cell->isValid()) {
- QAccessible::Id cellId = QAccessible::uniqueId(cell);
- QMacAccessibilityElement *element =
- [QMacAccessibilityElement elementWithId:cellId];
- if (element) {
- [cells addObject:element];
- }
- }
- }
- return NSAccessibilityUnignoredChildren(cells);
+ Q_ASSERT(m_rowIndex >= 0);
+ const int numColumns = table->columnCount();
+ columns = [self populateTableRow:columns count:numColumns];
+ return NSAccessibilityUnignoredChildren(columns);
}
}
return QCocoaAccessible::unignoredChildren(iface);
}
+- (NSArray *) accessibilitySelectedChildren {
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
+ if (!iface || !iface->isValid())
+ return nil;
+
+ QAccessibleSelectionInterface *selection = iface->selectionInterface();
+ if (!selection)
+ return nil;
+
+ const QList<QAccessibleInterface *> selectedList = selection->selectedItems();
+ const qsizetype numSelected = selectedList.size();
+ NSMutableArray<QMacAccessibilityElement *> *selectedChildren =
+ [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected];
+ for (QAccessibleInterface *selectedChild : selectedList) {
+ if (selectedChild && selectedChild->isValid()) {
+ QAccessible::Id id = QAccessible::uniqueId(selectedChild);
+ QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id];
+ if (element)
+ [selectedChildren addObject:element];
+ }
+ }
+ return NSAccessibilityUnignoredChildren(selectedChildren);
+}
+
- (id) accessibilityWindow {
// We're in the same window as our parent.
return [self.accessibilityParent accessibilityWindow];
@@ -355,26 +510,35 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityTitle {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
- if (iface->role() == QAccessible::StaticText)
- return nil;
- if (self.isManagedByParent)
- return nil;
- return iface->text(QAccessible::Name).toNSString();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (iface->role() == QAccessible::StaticText)
+ return nil;
+ if (self.isManagedByParent)
+ return nil;
+ return iface->text(QAccessible::Name).toNSString();
+ }
+ return nil;
}
- (BOOL) isAccessibilityEnabled {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return false;
- return !iface->state().disabled;
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return !iface->state().disabled;
+ return false;
}
- (id)accessibilityParent {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ if (synthesizedRole == NSAccessibilityCellRole) {
+ // a synthetic cell without interface - shortcut to the row
+ QMacAccessibilityElement *tableElement =
+ [QMacAccessibilityElement elementWithId:axid];
+ Q_ASSERT(tableElement && tableElement->rows);
+ Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
+ QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex];
+ return rowElement;
+ }
+
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nil;
if (self.isManagedByParent) {
@@ -389,23 +553,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
if (QAccessibleInterface *parent = iface->parent()) {
if (parent->tableInterface()) {
- if (QAccessibleTableCellInterface *cell = iface->tableCellInterface()) {
- // parent of cell should be row
- QAccessible::Id parentId = QAccessible::uniqueId(parent);
- QMacAccessibilityElement *tableElement =
- [QMacAccessibilityElement elementWithId:parentId];
-
- const int rowIndex = cell->rowIndex();
- if (tableElement->rows && int([tableElement->rows count]) > rowIndex) {
- QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
- return NSAccessibilityUnignoredAncestor(rowElement);
- }
- }
- }
- if (parent->role() != QAccessible::Application) {
- QAccessible::Id parentId = QAccessible::uniqueId(parent);
- return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]);
+ QMacAccessibilityElement *tableElement =
+ [QMacAccessibilityElement elementWithInterface:parent];
+
+ // parent of cell should be row
+ int rowIndex = -1;
+ if (m_rowIndex >= 0 && m_columnIndex >= 0)
+ rowIndex = m_rowIndex;
+ else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface())
+ rowIndex = cell->rowIndex();
+ Q_ASSERT(tableElement->rows);
+ if (rowIndex > int([tableElement->rows count]) || rowIndex == -1)
+ return nil;
+ QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
+ return NSAccessibilityUnignoredAncestor(rowElement);
}
+ if (parent->role() != QAccessible::Application)
+ return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]);
}
if (QWindow *window = iface->window()) {
@@ -419,8 +583,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSRect)accessibilityFrame {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSZeroRect;
QRect rect;
@@ -437,10 +601,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
int &row = isRow ? cellPos.ry() : cellPos.rx();
int &col = isRow ? cellPos.rx() : cellPos.ry();
- QMacAccessibilityElement *tableElement =
- [QMacAccessibilityElement elementWithId:axid];
- NSArray *tracks = isRow ? tableElement->rows : tableElement->columns;
- NSUInteger trackIndex = [tracks indexOfObjectIdenticalTo:self];
+ NSUInteger trackIndex = self.accessibilityIndex;
if (trackIndex != NSNotFound) {
row = int(trackIndex);
if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) {
@@ -463,71 +624,50 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString*)accessibilityLabel {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
- qWarning() << "Called accessibilityLabel on invalid object: " << axid;
- return nil;
- }
- return iface->text(QAccessible::Description).toNSString();
-}
-
-- (void)setAccessibilityLabel:(NSString*)label{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return;
- iface->setText(QAccessible::Description, QString::fromNSString(label));
-}
-
-- (id) accessibilityMinValue:(QAccessibleInterface*)iface {
- if (QAccessibleValueInterface *val = iface->valueInterface())
- return @(val->minimumValue().toDouble());
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return iface->text(QAccessible::Description).toNSString();
+ qWarning() << "Called accessibilityLabel on invalid object: " << axid;
return nil;
}
-- (id) accessibilityMaxValue:(QAccessibleInterface*)iface {
- if (QAccessibleValueInterface *val = iface->valueInterface())
- return @(val->maximumValue().toDouble());
- return nil;
+- (void)setAccessibilityLabel:(NSString*)label{
+ if (QAccessibleInterface *iface = self.qtInterface)
+ iface->setText(QAccessible::Description, QString::fromNSString(label));
}
- (id) accessibilityValue {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
-
- // VoiceOver asks for the value attribute for all elements. Return nil
- // if we don't want the element to have a value attribute.
- if (!QCocoaAccessible::hasValueAttribute(iface))
- return nil;
-
- return QCocoaAccessible::getValueAttribute(iface);
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ // VoiceOver asks for the value attribute for all elements. Return nil
+ // if we don't want the element to have a value attribute.
+ if (QCocoaAccessible::hasValueAttribute(iface))
+ return QCocoaAccessible::getValueAttribute(iface);
+ }
+ return nil;
}
- (NSInteger) accessibilityNumberOfCharacters {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return 0;
- if (QAccessibleTextInterface *text = iface->textInterface())
- return text->characterCount();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (QAccessibleTextInterface *text = iface->textInterface())
+ return text->characterCount();
+ }
return 0;
}
- (NSString *) accessibilitySelectedText {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
- if (QAccessibleTextInterface *text = iface->textInterface()) {
- int start = 0;
- int end = 0;
- text->selection(0, &start, &end);
- return text->text(start, end).toNSString();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (QAccessibleTextInterface *text = iface->textInterface()) {
+ int start = 0;
+ int end = 0;
+ text->selection(0, &start, &end);
+ return text->text(start, end).toNSString();
+ }
}
return nil;
}
- (NSRange) accessibilitySelectedTextRange {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSRange();
if (QAccessibleTextInterface *text = iface->textInterface()) {
int start = 0;
@@ -544,8 +684,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return 0;
if (QAccessibleTextInterface *text = iface->textInterface()) {
QString textToPos = text->text(0, index);
@@ -555,8 +695,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSRange)accessibilityVisibleCharacterRange {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSRange();
// FIXME This is not correct and may impact performance for big texts
if (QAccessibleTextInterface *text = iface->textInterface())
@@ -565,8 +705,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSInteger) accessibilityInsertionPointLineNumber {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return 0;
if (QAccessibleTextInterface *text = iface->textInterface()) {
int position = text->cursorPosition();
@@ -577,8 +717,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
- (NSArray *)accessibilityParameterizedAttributeNames {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning() << "Called attribute on invalid object: " << axid;
return nil;
}
@@ -601,8 +741,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning() << "Called attribute on invalid object: " << axid;
return nil;
}
@@ -642,7 +782,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QRectF rect;
if (range.length > 0) {
NSUInteger position = range.location + range.length - 1;
- if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n"))
+ if (position > range.location && iface->textInterface()->text(position, position + 1) == "\n"_L1)
--position;
QRect lastRect = iface->textInterface()->characterRect(position);
rect = firstRect.united(lastRect);
@@ -670,8 +810,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NO;
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
@@ -689,8 +829,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return;
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
if (QAccessibleActionInterface *action = iface->actionInterface())
@@ -718,8 +858,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
- (NSArray *)accessibilityActionNames {
NSMutableArray *nsActions = [[NSMutableArray new] autorelease];
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nsActions;
const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
@@ -733,8 +873,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *)accessibilityActionDescription:(NSString *)action {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nil; // FIXME is that the right return type??
QString qtAction = QCocoaAccessible::translateAction(action, iface);
QString description;
@@ -751,8 +891,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (void)accessibilityPerformAction:(NSString *)action {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
const QString qtAction = QCocoaAccessible::translateAction(action, iface);
QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction);
}
@@ -761,15 +900,20 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
// misc
- (BOOL)accessibilityIsIgnored {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return true;
- return QCocoaAccessible::shouldBeIgnored(iface);
+ // Short-cut for placeholders and synthesized elements. Working around a bug
+ // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise
+ // we could ignore rows and columns that are outside the table.
+ if (self.isManagedByParent)
+ return false;
+
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::shouldBeIgnored(iface);
+ return true;
}
- (id)accessibilityHitTest:(NSPoint)point {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
// qDebug("Hit test: INVALID");
return NSAccessibilityUnignoredAncestor(self);
}
@@ -788,26 +932,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
childInterface = childOfChildInterface;
} while (childOfChildInterface && childOfChildInterface->isValid());
- QAccessible::Id childId = QAccessible::uniqueId(childInterface);
// hit a child, forward to child accessible interface.
- QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childId];
+ QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
if (accessibleElement)
return NSAccessibilityUnignoredAncestor(accessibleElement);
return NSAccessibilityUnignoredAncestor(self);
}
- (id)accessibilityFocusedUIElement {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
-
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning("FocusedUIElement for INVALID");
return nil;
}
QAccessibleInterface *childInterface = iface->focusChild();
if (childInterface && childInterface->isValid()) {
- QAccessible::Id childAxid = QAccessible::uniqueId(childInterface);
- QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childAxid];
+ QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
return NSAccessibilityUnignoredAncestor(accessibleElement);
}
@@ -815,8 +956,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityHelp {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
const QString helpText = iface->text(QAccessible::Help);
if (!helpText.isEmpty())
return helpText.toNSString();
@@ -829,19 +969,17 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
*/
- (NSInteger) accessibilityIndex {
NSInteger index = 0;
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (synthesizedRole == NSAccessibilityCellRole)
+ return m_columnIndex;
+ if (QAccessibleInterface *iface = self.qtInterface) {
if (self.isManagedByParent) {
// axid matches the parent table axid so that we can easily find the parent table
// children of row are cell/any items
if (QAccessibleTableInterface *table = iface->tableInterface()) {
- QMacAccessibilityElement *tableElement = [QMacAccessibilityElement elementWithId:axid];
- NSArray *track = synthesizedRole == NSAccessibilityRowRole
- ? tableElement->rows : tableElement->columns;
- if (track) {
- NSUInteger trackIndex = [track indexOfObjectIdenticalTo:self];
- index = (NSInteger)trackIndex;
- }
+ if (m_rowIndex >= 0)
+ index = NSInteger(m_rowIndex);
+ else if (m_columnIndex >= 0)
+ index = NSInteger(m_columnIndex);
}
}
}
@@ -849,18 +987,18 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSArray *) accessibilityRows {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid() && iface->tableInterface() && !synthesizedRole) {
- if (rows)
+ if (!synthesizedRole && rows) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (iface && iface->tableInterface())
return NSAccessibilityUnignoredChildren(rows);
}
return nil;
}
- (NSArray *) accessibilityColumns {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid() && iface->tableInterface() && !synthesizedRole) {
- if (columns)
+ if (!synthesizedRole && columns) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (iface && iface->tableInterface())
return NSAccessibilityUnignoredChildren(columns);
}
return nil;
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index b13ec3bee8..d642115926 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -148,7 +148,8 @@ QT_USE_NAMESPACE
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
- Q_UNUSED(notification);
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ [reflectionDelegate applicationWillFinishLaunching:notification];
/*
From the Cocoa documentation: "A good place to install event handlers
@@ -185,15 +186,34 @@ QT_USE_NAMESPACE
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
- Q_UNUSED(aNotification);
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ [reflectionDelegate applicationDidFinishLaunching:aNotification];
+
inLaunch = false;
if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) {
- // Move the application window to front to avoid launching behind the terminal.
- // Ignoring other apps is necessary (we must ignore the terminal), but makes
- // Qt apps play slightly less nice with other apps when lanching from Finder
- // (See the activateIgnoringOtherApps docs.)
- [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+ auto frontmostApplication = NSWorkspace.sharedWorkspace.frontmostApplication;
+ auto currentApplication = NSRunningApplication.currentApplication;
+ if (frontmostApplication != currentApplication) {
+ // Move the application to front to avoid launching behind the terminal.
+ // Ignoring other apps is necessary (we must ignore the terminal), but makes
+ // Qt apps play slightly less nice with other apps when launching from Finder
+ // (see the activateIgnoringOtherApps docs). FIXME: Try to distinguish between
+ // being non-active here because another application stole activation in the
+ // time it took us to launch from Finder, and being non-active because we were
+ // launched from Terminal or something that doesn't activate us at all.
+ qCDebug(lcQpaApplication) << "Launched with" << frontmostApplication
+ << "as frontmost application. Activating" << currentApplication << "instead.";
+ [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
+ }
+
+ // Qt windows are typically shown in main(), at which point the application
+ // is not active yet. When the application is activated, either externally
+ // or via the override above, it will only bring the main and key windows
+ // forward, which differs from the behavior if these windows had been shown
+ // once the application was already active. To work around this, we explicitly
+ // activate the current application again, bringing all windows to the front.
+ [currentApplication activateWithOptions:NSApplicationActivateAllWindows];
}
QCocoaMenuBar::insertWindowMenu();
@@ -314,18 +334,80 @@ QT_USE_NAMESPACE
[self doesNotRecognizeSelector:invocationSelector];
}
+- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity
+ restorationHandler:(void(^)(NSArray<id<NSUserActivityRestoring>> *restorableObjects))restorationHandler
+{
+ // Check if eg. user has installed an app delegate capable of handling this
+ if ([reflectionDelegate respondsToSelector:_cmd]
+ && [reflectionDelegate application:application continueUserActivity:userActivity
+ restorationHandler:restorationHandler] == YES) {
+ return YES;
+ }
+
+ if (!QGuiApplication::instance())
+ return NO;
+
+ if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
+ QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance();
+ Q_ASSERT(cocoaIntegration);
+ return cocoaIntegration->services()->handleUrl(QUrl::fromNSURL(userActivity.webpageURL));
+ }
+
+ return NO;
+}
+
- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
Q_UNUSED(replyEvent);
+
NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
- QWindowSystemInterface::handleFileOpenEvent(QUrl(QString::fromNSString(urlString)));
+ const QString qurlString = QString::fromNSString(urlString);
+
+ if (event.eventClass == kInternetEventClass && event.eventID == kAEGetURL) {
+ // 'GURL' (Get URL) event this application should handle
+ if (!QGuiApplication::instance())
+ return;
+ QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance();
+ Q_ASSERT(cocoaIntegration);
+ cocoaIntegration->services()->handleUrl(QUrl(qurlString));
+ return;
+ }
+
+ // The string we get from the requesting application might not necessarily meet
+ // QUrl's requirement for a IDN-compliant host. So if we can't parse into a QUrl,
+ // then we pass the string on to the application as the name of a file (and
+ // QFileOpenEvent::file is not guaranteed to be the path to a local, open'able
+ // file anyway).
+ if (const QUrl url(qurlString); url.isValid())
+ QWindowSystemInterface::handleFileOpenEvent(url);
+ else
+ QWindowSystemInterface::handleFileOpenEvent(qurlString);
+}
+
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)application
+{
+ if (@available(macOS 12, *)) {
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ return [reflectionDelegate applicationSupportsSecureRestorableState:application];
+ }
+
+ // We don't support or implement state restorations via the AppKit
+ // state restoration APIs, but if we did, we would/should support
+ // secure state restoration. This is the default for apps linked
+ // against the macOS 14 SDK, but as we target versions below that
+ // as well we need to return YES here explicitly to silence a runtime
+ // warning.
+ return YES;
}
+
@end
@implementation QCocoaApplicationDelegate (Menus)
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
+ qCDebug(lcQpaMenus) << "Validating" << item << "for" << self;
+
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
if (!nativeItem)
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
@@ -347,6 +429,8 @@ QT_USE_NAMESPACE
- (void)qt_itemFired:(QCocoaNSMenuItem *)item
{
+ qCDebug(lcQpaMenus) << "Activating" << item;
+
if (item.hasSubmenu)
return;
@@ -358,7 +442,6 @@ QT_USE_NAMESPACE
if (!platformItem || platformItem->menu())
return;
- QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed());
QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]);
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h
index 6db88f923c..71b6015a54 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.h
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h
@@ -54,6 +54,7 @@ private:
bool eventFilter(QObject *watched, QEvent *event) override;
QSize m_requestedSize;
+ QRegion m_staticContents;
class GraphicsBuffer : public QIOSurfaceGraphicsBuffer
{
@@ -78,7 +79,8 @@ private:
bool recreateBackBufferIfNeeded();
void finalizeBackBuffer();
- void preserveFromFrontBuffer(const QRegion &region, const QPoint &offset = QPoint());
+ void blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
+ GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset = QPoint());
void backingPropertiesChanged();
QMacNotificationObserver m_backingPropertiesObserver;
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
index 8f50bc5834..b211b5d02d 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
@@ -23,8 +23,9 @@ QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const
{
- NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
- return QCFType<CGColorSpaceRef>::constructFromGet(view.window.colorSpace.CGColorSpace);
+ const auto *platformWindow = static_cast<QCocoaWindow *>(window()->handle());
+ const QNSView *view = qnsview_cast(platformWindow->view());
+ return QCFType<CGColorSpaceRef>::constructFromGet(view.colorSpace.CGColorSpace);
}
// ----------------------------------------------------------------------------
@@ -72,12 +73,11 @@ bool QCALayerBackingStore::eventFilter(QObject *watched, QEvent *event)
void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
- qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
-
- if (!staticContents.isNull())
- qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents";
+ qCDebug(lcQpaBackingStore) << "Resize requested to" << size
+ << "with static contents" << staticContents;
m_requestedSize = size;
+ m_staticContents = staticContents;
}
void QCALayerBackingStore::beginPaint(const QRegion &region)
@@ -189,11 +189,50 @@ bool QCALayerBackingStore::recreateBackBufferIfNeeded()
}
#endif
- qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize
- << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio;
+ qCInfo(lcQpaBackingStore)<< "Creating surface of" << requestedBufferSize
+ << "for" << window() << "based on requested" << m_requestedSize
+ << "dpr =" << devicePixelRatio << "and color space" << colorSpace();
static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied);
- m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace()));
+ auto *newBackBuffer = new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace());
+
+ if (!m_staticContents.isEmpty() && m_buffers.back()) {
+ // We implicitly support static backingstore content as a result of
+ // finalizing the back buffer on flush, where we copy any non-painted
+ // areas from the front buffer. But there is no guarantee that a resize
+ // will always come after a flush, where we have a pristine front buffer
+ // to copy from. It may come after a few begin/endPaints, where the back
+ // buffer then contains (part of) the latest state. We also have the case
+ // of single-buffered backingstore, where the front and back buffer is
+ // the same, which means we must do the copy from the old back buffer
+ // to the newly resized buffer now, before we replace it below.
+
+ // If the back buffer has been partially filled already, we need to
+ // copy parts of the static content from that. The rest we copy from
+ // the front buffer.
+ const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion;
+ const QRegion frontBufferRegion = m_staticContents - backBufferRegion;
+
+ qCInfo(lcQpaBackingStore) << "Preserving static content" << backBufferRegion
+ << "from back buffer, and" << frontBufferRegion << "from front buffer";
+
+ newBackBuffer->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+ blitBuffer(m_buffers.back().get(), backBufferRegion, newBackBuffer);
+ Q_ASSERT(frontBufferRegion.isEmpty() || m_buffers.front());
+ blitBuffer(m_buffers.front().get(), frontBufferRegion, newBackBuffer);
+ newBackBuffer->unlock();
+
+ // The new back buffer now is valid for the static contents region.
+ // We don't need to maintain the static contents region for resizes
+ // of any other buffers in the swap chain, as these will finalize
+ // their content on flush from the buffer we just filled, and we
+ // don't need to mark them dirty for the area we just filled, as
+ // new buffers are fully dirty when created.
+ newBackBuffer->dirtyRegion -= m_staticContents;
+ m_staticContents = {};
+ }
+
+ m_buffers.back().reset(newBackBuffer);
return true;
}
@@ -256,7 +295,7 @@ bool QCALayerBackingStore::scroll(const QRegion &region, int dx, int dy)
if (!frontBufferRegion.isEmpty()) {
qCDebug(lcQpaBackingStore) << "Scrolling" << frontBufferRegion << "by copying from front buffer";
- preserveFromFrontBuffer(frontBufferRegion, scrollDelta);
+ blitBuffer(m_buffers.front().get(), frontBufferRegion, m_buffers.back().get(), scrollDelta);
}
m_buffers.back()->unlock();
@@ -440,10 +479,11 @@ void QCALayerBackingStore::backingPropertiesChanged()
qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change";
- qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers";
+ const auto newColorSpace = colorSpace();
+ qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers to" << newColorSpace;
for (auto &buffer : m_buffers) {
if (buffer)
- buffer->setColorSpace(colorSpace());
+ buffer->setColorSpace(newColorSpace);
}
}
@@ -475,54 +515,76 @@ void QCALayerBackingStore::finalizeBackBuffer()
if (!m_buffers.back()->isDirty())
return;
- m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
- preserveFromFrontBuffer(m_buffers.back()->dirtyRegion);
- m_buffers.back()->unlock();
+ qCDebug(lcQpaBackingStore) << "Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
+
+ if (m_buffers.back() != m_buffers.front()) {
+ m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+ blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get());
+ m_buffers.back()->unlock();
+ } else {
+ qCDebug(lcQpaBackingStore) << "Front and back buffer is the same. Can not finalize back buffer.";
+ }
// The back buffer is now completely in sync, ready to be presented
m_buffers.back()->dirtyRegion = QRegion();
}
-void QCALayerBackingStore::preserveFromFrontBuffer(const QRegion &region, const QPoint &offset)
+/*
+ \internal
+
+ Blits \a sourceRegion from \a sourceBuffer to \a destinationBuffer,
+ at offset \a destinationOffset.
+
+ The source buffer is automatically locked for read only access
+ during the blit.
+
+ The destination buffer has to be locked for write access by the
+ caller.
+*/
+
+void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
+ GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset)
{
+ Q_ASSERT(sourceBuffer && destinationBuffer);
+ Q_ASSERT(sourceBuffer != destinationBuffer);
- if (m_buffers.front() == m_buffers.back())
- return; // Nothing to preserve from
+ if (sourceRegion.isEmpty())
+ return;
- qCDebug(lcQpaBackingStore) << "Preserving" << region << "of front buffer to"
- << region.translated(offset) << "of back buffer";
+ qCDebug(lcQpaBackingStore) << "Blitting" << sourceRegion << "of" << sourceBuffer
+ << "to" << sourceRegion.translated(destinationOffset) << "of" << destinationBuffer;
- Q_ASSERT(m_buffers.back()->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
+ Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
- m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess);
- const QImage *frontBuffer = m_buffers.front()->asImage();
+ sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
+ const QImage *sourceImage = sourceBuffer->asImage();
- const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size());
- const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio();
+ const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size());
+ const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
- QPainter painter(m_buffers.back()->asImage());
+ QPainter painter(destinationBuffer->asImage());
painter.setCompositionMode(QPainter::CompositionMode_Source);
// Let painter operate in device pixels, to make it easier to compare coordinates
- const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
- painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
+ const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio();
+ painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
- for (const QRect &rect : region) {
+ for (const QRect &rect : sourceRegion) {
QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio,
rect.size() * sourceDevicePixelRatio);
- QRect targetRect((rect.topLeft() + offset) * targetDevicePixelRatio,
- rect.size() * targetDevicePixelRatio);
+ QRect destinationRect((rect.topLeft() + destinationOffset) * destinationDevicePixelRatio,
+ rect.size() * destinationDevicePixelRatio);
#ifdef QT_DEBUG
- if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) {
- qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve"
- << QRegion(sourceRect).subtracted(frontSurfaceBounds);
+ if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
+ qCWarning(lcQpaBackingStore) << "Source buffer of size" << sourceBuffer->size()
+ << "is too small to blit" << sourceRect;
}
#endif
- painter.drawImage(targetRect, *frontBuffer, sourceRect);
+ painter.drawImage(destinationRect, *sourceImage, sourceRect);
}
- m_buffers.front()->unlock();
+ sourceBuffer->unlock();
}
// ----------------------------------------------------------------------------
diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.mm b/src/plugins/platforms/cocoa/qcocoaclipboard.mm
index 0c8da26137..241faadbec 100644
--- a/src/plugins/platforms/cocoa/qcocoaclipboard.mm
+++ b/src/plugins/platforms/cocoa/qcocoaclipboard.mm
@@ -10,8 +10,8 @@
QT_BEGIN_NAMESPACE
QCocoaClipboard::QCocoaClipboard()
- :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QUtiMimeConverter::HandlerScope::Clipboard))
- ,m_find(new QMacPasteboard(kPasteboardFind, QUtiMimeConverter::HandlerScope::Clipboard))
+ :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QUtiMimeConverter::HandlerScopeFlag::Clipboard))
+ ,m_find(new QMacPasteboard(kPasteboardFind, QUtiMimeConverter::HandlerScopeFlag::Clipboard))
{
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QCocoaClipboard::handleApplicationStateChanged);
}
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h
index 24485ac6ac..dedf8a7fd9 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.h
+++ b/src/plugins/platforms/cocoa/qcocoadrag.h
@@ -44,7 +44,7 @@ private:
NSEvent *m_lastEvent;
NSView *m_lastView;
Qt::DropAction m_executed_drop_action;
- QEventLoop internalDragLoop;
+ QEventLoop *m_internalDragLoop = nullptr;
bool maybeDragMultipleItems();
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm
index ec46dda38a..a8404889e9 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.mm
+++ b/src/plugins/platforms/cocoa/qcocoadrag.mm
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <AppKit/AppKit.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include "qcocoadrag.h"
#include "qmacclipboard.h"
@@ -98,7 +99,7 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o)
m_drag = o;
m_executed_drop_action = Qt::IgnoreAction;
- QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScope::DnD);
+ QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD);
m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy"));
dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest);
@@ -161,8 +162,7 @@ bool QCocoaDrag::maybeDragMultipleItems()
for (NSPasteboardItem *item in dragBoard.pasteboardItems) {
bool isUrl = false;
for (NSPasteboardType type in item.types) {
- using NSStringRef = NSString *;
- if ([type isEqualToString:NSStringRef(kUTTypeFileURL)]) {
+ if ([type isEqualToString:UTTypeFileURL.identifier]) {
isUrl = true;
break;
}
@@ -213,7 +213,9 @@ bool QCocoaDrag::maybeDragMultipleItems()
}
[sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
- internalDragLoop.exec();
+ QEventLoop eventLoop;
+ QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop);
+ eventLoop.exec();
return true;
}
@@ -224,8 +226,10 @@ void QCocoaDrag::setAcceptedAction(Qt::DropAction act)
void QCocoaDrag::exitDragLoop()
{
- if (internalDragLoop.isRunning())
- internalDragLoop.exit();
+ if (m_internalDragLoop) {
+ Q_ASSERT(m_internalDragLoop->isRunning());
+ m_internalDragLoop->exit();
+ }
}
@@ -240,14 +244,14 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
QFontMetrics fm(f);
if (data->hasImage()) {
- const QImage img = data->imageData().value<QImage>();
+ QImage img = data->imageData().value<QImage>();
if (!img.isNull()) {
- pm = QPixmap::fromImage(img).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
+ pm = QPixmap::fromImage(std::move(img)).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
}
}
if (pm.isNull() && (data->hasText() || data->hasUrls()) ) {
- QString s = data->hasText() ? data->text() : data->urls().first().toString();
+ QString s = data->hasText() ? data->text() : data->urls().constFirst().toString();
if (s.length() > dragImageMaxChars)
s = s.left(dragImageMaxChars -3) + QChar(0x2026);
if (!s.isEmpty()) {
@@ -305,7 +309,7 @@ QStringList QCocoaDropData::formats_sys() const
qDebug("DnD: Cannot get PasteBoard!");
return formats;
}
- formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScope::DnD).formats();
+ formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).formats();
return formats;
}
@@ -317,7 +321,7 @@ QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType) co
qDebug("DnD: Cannot get PasteBoard!");
return data;
}
- data = QMacPasteboard(board, QUtiMimeConverter::HandlerScope::DnD).retrieveData(mimeType);
+ data = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).retrieveData(mimeType);
CFRelease(board);
return data;
}
@@ -330,7 +334,7 @@ bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const
qDebug("DnD: Cannot get PasteBoard!");
return has;
}
- has = QMacPasteboard(board, QUtiMimeConverter::HandlerScope::DnD).hasFormat(mimeType);
+ has = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).hasFormat(mimeType);
CFRelease(board);
return has;
}
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
index 787b23ec4d..96eb70dabc 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
@@ -56,9 +56,12 @@
#include <QtCore/private/qcfsocketnotifier_p.h>
#include <QtCore/private/qtimerinfo_unix_p.h>
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qpointer.h>
#include <CoreFoundation/CoreFoundation.h>
+Q_FORWARD_DECLARE_OBJC_CLASS(NSWindow);
+
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcEventDispatcher);
@@ -67,11 +70,11 @@ typedef struct _NSModalSession *NSModalSession;
typedef struct _QCocoaModalSessionInfo {
QPointer<QWindow> window;
NSModalSession session;
- void *nswindow;
+ NSWindow *nswindow;
} QCocoaModalSessionInfo;
class QCocoaEventDispatcherPrivate;
-class QCocoaEventDispatcher : public QAbstractEventDispatcher
+class QCocoaEventDispatcher : public QAbstractEventDispatcherV2
{
Q_OBJECT
Q_DECLARE_PRIVATE(QCocoaEventDispatcher)
@@ -86,12 +89,12 @@ public:
void registerSocketNotifier(QSocketNotifier *notifier);
void unregisterSocketNotifier(QSocketNotifier *notifier);
- void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object);
- bool unregisterTimer(int timerId);
- bool unregisterTimers(QObject *object);
- QList<TimerInfo> registeredTimers(QObject *object) const;
-
- int remainingTime(int timerId);
+ void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType,
+ QObject *object) final;
+ bool unregisterTimer(Qt::TimerId timerId) final;
+ bool unregisterTimers(QObject *object) final;
+ QList<TimerInfoV2> timersForObject(QObject *object) const final;
+ Duration remainingTime(Qt::TimerId timerId) const final;
void wakeUp();
void interrupt();
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
index fd0a4b4717..739fbda4f5 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
@@ -115,6 +115,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
return;
}
+ using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
if (!runLoopTimerRef) {
// start the CFRunLoopTimer
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
@@ -122,10 +123,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// Q: when should the CFRunLoopTimer fire for the first time?
- struct timespec tv;
- if (timerInfoList.timerWait(tv)) {
+ if (auto opt = timerInfoList.timerWait()) {
// A: when we have timers to fire, of course
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
+ DoubleSeconds secs{*opt};
+ interval = qMax(secs.count(), 0.0000001);
} else {
// this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
interval = oneyear;
@@ -145,10 +146,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval interval;
// Q: when should the timer first next?
- struct timespec tv;
- if (timerInfoList.timerWait(tv)) {
+ if (auto opt = timerInfoList.timerWait()) {
// A: when we have timers to fire, of course
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
+ DoubleSeconds secs{*opt};
+ interval = qMax(secs.count(), 0.0000001);
} else {
// no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
// point in the distant future (the timer interval is one year)
@@ -170,10 +171,11 @@ void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer()
runLoopTimerRef = nullptr;
}
-void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *obj)
+void QCocoaEventDispatcher::registerTimer(Qt::TimerId timerId, Duration interval,
+ Qt::TimerType timerType, QObject *obj)
{
#ifndef QT_NO_DEBUG
- if (timerId < 1 || interval < 0 || !obj) {
+ if (qToUnderlying(timerId) < 1 || interval.count() < 0 || !obj) {
qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
return;
} else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
@@ -187,10 +189,10 @@ void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::Time
d->maybeStartCFRunLoopTimer();
}
-bool QCocoaEventDispatcher::unregisterTimer(int timerId)
+bool QCocoaEventDispatcher::unregisterTimer(Qt::TimerId timerId)
{
#ifndef QT_NO_DEBUG
- if (timerId < 1) {
+ if (qToUnderlying(timerId) < 1) {
qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
return false;
} else if (thread() != QThread::currentThread()) {
@@ -229,13 +231,13 @@ bool QCocoaEventDispatcher::unregisterTimers(QObject *obj)
return returnValue;
}
-QList<QCocoaEventDispatcher::TimerInfo>
-QCocoaEventDispatcher::registeredTimers(QObject *object) const
+QList<QCocoaEventDispatcher::TimerInfoV2>
+QCocoaEventDispatcher::timersForObject(QObject *object) const
{
#ifndef QT_NO_DEBUG
if (!object) {
qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
- return QList<TimerInfo>();
+ return {};
}
#endif
@@ -374,6 +376,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// [NSApp run], which is the normal code path for cocoa applications.
if (NSModalSession session = d->currentModalSession()) {
QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
+ qCDebug(lcEventDispatcher) << "Running modal session" << session;
while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) {
qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
if (session != d->currentModalSessionCached) {
@@ -417,6 +420,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// to use cocoa's native way of running modal sessions:
if (flags & QEventLoop::WaitForMoreEvents)
qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
+ qCDebug(lcEventDispatcher) << "Running modal session" << session;
NSInteger status = [NSApp runModalSession:session];
if (status != NSModalResponseContinue && session == d->currentModalSessionCached) {
// INVARIANT: Someone called [NSApp stopModal:] from outside the event
@@ -537,17 +541,17 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
return retVal;
}
-int QCocoaEventDispatcher::remainingTime(int timerId)
+auto QCocoaEventDispatcher::remainingTime(Qt::TimerId timerId) const -> Duration
{
#ifndef QT_NO_DEBUG
- if (timerId < 1) {
+ if (qToUnderlying(timerId) < 1) {
qWarning("QCocoaEventDispatcher::remainingTime: invalid argument");
- return -1;
+ return Duration::min();
}
#endif
- Q_D(QCocoaEventDispatcher);
- return d->timerInfoList.timerRemainingTime(timerId);
+ Q_D(const QCocoaEventDispatcher);
+ return d->timerInfoList.remainingDuration(timerId);
}
void QCocoaEventDispatcher::wakeUp()
@@ -617,6 +621,8 @@ void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions()
for (int i=0; i<stackSize; ++i) {
QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
if (info.session) {
+ qCDebug(lcEventDispatcher) << "Temporarily ending modal session" << info.session
+ << "for" << info.nswindow;
[NSApp endModalSession:info.session];
info.session = nullptr;
[(NSWindow*) info.nswindow release];
@@ -656,6 +662,8 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession()
[(NSWindow*) info.nswindow retain];
QRect rect = cocoaWindow->geometry();
info.session = [NSApp beginModalSessionForWindow:nswindow];
+ qCDebug(lcEventDispatcher) << "Begun modal session" << info.session
+ << "for" << nswindow;
// The call to beginModalSessionForWindow above processes events and may
// have deleted or destroyed the window. Check if it's still valid.
@@ -704,6 +712,8 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions()
currentModalSessionCached = nullptr;
if (info.session) {
Q_ASSERT(info.nswindow);
+ qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
+ << "for" << info.nswindow;
[NSApp endModalSession:info.session];
[(NSWindow *)info.nswindow release];
}
@@ -716,6 +726,14 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions()
void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
{
+ qCDebug(lcEventDispatcher) << "Adding modal session for" << window;
+
+ if (std::any_of(cocoaModalSessionStack.constBegin(), cocoaModalSessionStack.constEnd(),
+ [&](const auto &sessionInfo) { return sessionInfo.window == window; })) {
+ qCWarning(lcEventDispatcher) << "Modal session for" << window << "already exists!";
+ return;
+ }
+
// We need to start spinning the modal session. Usually this is done with
// QDialog::exec() for Qt Widgets based applications, but for others that
// just call show(), we need to interrupt().
@@ -736,6 +754,8 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window)
{
+ qCDebug(lcEventDispatcher) << "Removing modal session for" << window;
+
Q_Q(QCocoaEventDispatcher);
// Mark all sessions attached to window as pending to be stopped. We do this
@@ -782,7 +802,7 @@ void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *even
}
QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
- : QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent)
+ : QAbstractEventDispatcherV2(*new QCocoaEventDispatcherPrivate, parent)
{
Q_D(QCocoaEventDispatcher);
@@ -957,7 +977,7 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
{
Q_D(QCocoaEventDispatcher);
- qDeleteAll(d->timerInfoList);
+ d->timerInfoList.clearTimers();
d->maybeStopCFRunLoopTimer();
CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
CFRelease(d->activateTimersSourceRef);
@@ -966,6 +986,8 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) {
QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i];
if (info.session) {
+ qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
+ << "for" << info.nswindow << "during shutdown";
[NSApp endModalSession:info.session];
[(NSWindow *)info.nswindow release];
}
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
index 85e317b8ef..3ffccb10fd 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
@@ -38,6 +38,7 @@ public:
public: // for QNSOpenSavePanelDelegate
void panelClosed(NSInteger result);
+ void panelDirectoryDidChange(NSString *path);
private:
void createNSOpenSavePanelDelegate();
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index 91d76fa254..044a282686 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -27,6 +27,8 @@
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformnativeinterface.h>
+#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
QT_USE_NAMESPACE
using namespace Qt::StringLiterals;
@@ -55,12 +57,11 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
NSPopUpButton *m_popupButton;
NSTextField *m_textField;
QPointer<QCocoaFileDialogHelper> m_helper;
- NSString *m_currentDirectory;
SharedPointerFileDialogOptions m_options;
- QString *m_currentSelection;
- QStringList *m_nameFilterDropDownList;
- QStringList *m_selectedNameFilter;
+ QString m_currentSelection;
+ QStringList m_nameFilterDropDownList;
+ QStringList m_selectedNameFilter;
}
- (instancetype)initWithAcceptMode:(const QString &)selectFile
@@ -80,26 +81,56 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_helper = helper;
- m_nameFilterDropDownList = new QStringList(m_options->nameFilters());
+ m_nameFilterDropDownList = m_options->nameFilters();
QString selectedVisualNameFilter = m_options->initiallySelectedNameFilter();
- m_selectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
-
- QFileInfo sel(selectFile);
+ m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter];
+
+ m_panel.extensionHidden = [&]{
+ for (const auto &nameFilter : m_nameFilterDropDownList) {
+ const auto extensions = QPlatformFileDialogHelper::cleanFilterList(nameFilter);
+ for (const auto &extension : extensions) {
+ // Explicitly show extensions if we detect a filter
+ // of "all files", as clicking a single file with
+ // extensions hidden will then populate the name
+ // field with only the file name, without any
+ // extension.
+ if (extension == "*"_L1 || extension == "*.*"_L1)
+ return false;
+
+ // Explicitly show extensions if we detect a filter
+ // that has a multi-part extension. This prevents
+ // confusing situations where the user clicks e.g.
+ // 'foo.tar.gz' and 'foo.tar' is populated in the
+ // file name box, but when then clicking save macOS
+ // will warn that the file needs to end in .gz,
+ // due to thinking the user tried to save the file
+ // as a 'tar' file instead. Unfortunately this
+ // property can only be set before the panel is
+ // shown, so we can't toggle it on and off based
+ // on the active filter.
+ if (extension.count('.') > 1)
+ return false;
+ }
+ }
+ return true;
+ }();
+
+ const QFileInfo sel(selectFile);
if (sel.isDir() && !sel.isBundle()){
- m_currentDirectory = [sel.absoluteFilePath().toNSString() retain];
- m_currentSelection = new QString;
+ m_panel.directoryURL = [NSURL fileURLWithPath:sel.absoluteFilePath().toNSString()];
+ m_currentSelection.clear();
} else {
- m_currentDirectory = [sel.absolutePath().toNSString() retain];
- m_currentSelection = new QString(sel.absoluteFilePath());
+ m_panel.directoryURL = [NSURL fileURLWithPath:sel.absolutePath().toNSString()];
+ m_currentSelection = sel.absoluteFilePath();
}
[self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)];
[self createTextField];
[self createAccessory];
- m_panel.accessoryView = m_nameFilterDropDownList->size() > 1 ? m_accessoryView : nil;
+ m_panel.accessoryView = m_nameFilterDropDownList.size() > 1 ? m_accessoryView : nil;
// -setAccessoryView: can result in -panel:directoryDidChange:
- // resetting our m_currentDirectory, set the delegate
+ // resetting our current directory. Set the delegate
// here to make sure it gets the correct value.
m_panel.delegate = self;
@@ -113,10 +144,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (void)dealloc
{
- delete m_nameFilterDropDownList;
- delete m_selectedNameFilter;
- delete m_currentSelection;
-
[m_panel orderOut:m_panel];
m_panel.accessoryView = nil;
[m_popupButton release];
@@ -124,19 +151,17 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
[m_accessoryView release];
m_panel.delegate = nil;
[m_panel release];
- [m_currentDirectory release];
[super dealloc];
}
- (bool)showPanel:(Qt::WindowModality) windowModality withParent:(QWindow *)parent
{
- QFileInfo info(*m_currentSelection);
+ const QFileInfo info(m_currentSelection);
NSString *filepath = info.filePath().toNSString();
NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()];
bool selectable = (m_options->acceptMode() == QFileDialogOptions::AcceptSave)
|| [self panel:m_panel shouldEnableURL:url];
- m_panel.directoryURL = [NSURL fileURLWithPath:m_currentDirectory];
m_panel.nameFieldStringValue = selectable ? info.fileName().toNSString() : @"";
[self updateProperties];
@@ -184,7 +209,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (void)closePanel
{
- *m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
+ m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
if (m_panel.sheet)
[NSApp endSheet:m_panel];
@@ -194,19 +219,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
[m_panel close];
}
-- (BOOL)isHiddenFileAtURL:(NSURL *)url
-{
- BOOL hidden = NO;
- if (url) {
- CFBooleanRef isHiddenProperty;
- if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, nullptr)) {
- hidden = CFBooleanGetValue(isHiddenProperty);
- CFRelease(isHiddenProperty);
- }
- }
- return hidden;
-}
-
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url
{
Q_UNUSED(sender);
@@ -215,64 +227,140 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
if (!filename.length)
return NO;
- // Always accept directories regardless of their names (unless it is a bundle):
- NSFileManager *fm = NSFileManager.defaultManager;
- NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil];
- if (!fileAttrs)
- return NO; // Error accessing the file means 'no'.
- NSString *fileType = fileAttrs.fileType;
- bool isDir = [fileType isEqualToString:NSFileTypeDirectory];
- if (isDir) {
- if (!m_panel.treatsFilePackagesAsDirectories) {
- if ([NSWorkspace.sharedWorkspace isFilePackageAtPath:filename] == NO)
- return YES;
- }
+ const QFileInfo fileInfo(QString::fromNSString(filename));
+
+ // Always accept directories regardless of their names.
+ // This also includes symlinks and aliases to directories.
+ if (fileInfo.isDir()) {
+ // Unless it's a bundle, and we should treat bundles as files.
+ // FIXME: We'd like to use QFileInfo::isBundle() here, but the
+ // detection in QFileInfo goes deeper than NSWorkspace does
+ // (likely a bug), and as a result causes TCC permission
+ // dialogs to pop up when used.
+ bool treatBundlesAsFiles = !m_panel.treatsFilePackagesAsDirectories;
+ if (!(treatBundlesAsFiles && [NSWorkspace.sharedWorkspace isFilePackageAtPath:filename]))
+ return YES;
}
- // Treat symbolic links and aliases to directories like directories
- QFileInfo fileInfo(QString::fromNSString(filename));
- if (fileInfo.isSymLink() && QFileInfo(fileInfo.symLinkTarget()).isDir())
- return YES;
-
- QString qtFileName = fileInfo.fileName();
- // No filter means accept everything
- bool nameMatches = m_selectedNameFilter->isEmpty();
- // Check if the current file name filter accepts the file:
- for (int i = 0; !nameMatches && i < m_selectedNameFilter->size(); ++i) {
- if (QDir::match(m_selectedNameFilter->at(i), qtFileName))
- nameMatches = true;
- }
- if (!nameMatches)
+ if (![self fileInfoMatchesCurrentNameFilter:fileInfo])
return NO;
QDir::Filters filter = m_options->filter();
- if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir)
- || (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular])
- || ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink]))
+ if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && fileInfo.isDir())
+ || (!(filter & QDir::Files) && (fileInfo.isFile() && !fileInfo.isSymLink()))
+ || ((filter & QDir::NoSymLinks) && fileInfo.isSymLink()))
return NO;
bool filterPermissions = ((filter & QDir::PermissionMask)
&& (filter & QDir::PermissionMask) != QDir::PermissionMask);
if (filterPermissions) {
- if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename])
- || (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename])
- || (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename]))
+ if ((!(filter & QDir::Readable) && fileInfo.isReadable())
+ || (!(filter & QDir::Writable) && fileInfo.isWritable())
+ || (!(filter & QDir::Executable) && fileInfo.isExecutable()))
return NO;
}
- if (!(filter & QDir::Hidden)
- && (qtFileName.startsWith(u'.') || [self isHiddenFileAtURL:url]))
+
+ // We control the visibility of hidden files via the showsHiddenFiles
+ // property on the panel, based on QDir::Hidden being set. But the user
+ // can also toggle this via the Command+Shift+. keyboard shortcut,
+ // in which case they have explicitly requested to show hidden files,
+ // and we should enable them even if QDir::Hidden was not set. In
+ // effect, we don't need to filter on QDir::Hidden here.
+
+ return YES;
+}
+
+- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError
+{
+ Q_ASSERT(sender == m_panel);
+
+ if (!m_panel.allowedFileTypes && !m_selectedNameFilter.isEmpty()) {
+ // The save panel hasn't done filtering on our behalf,
+ // either because we couldn't represent the filter via
+ // allowedFileTypes, or we opted out due to a multi part
+ // extension, so do the filtering/validation ourselves.
+ QFileInfo fileInfo(QString::fromNSString(url.path).normalized(QString::NormalizationForm_C));
+
+ if ([self fileInfoMatchesCurrentNameFilter:fileInfo])
+ return YES;
+
+ if (fileInfo.suffix().isEmpty()) {
+ // The filter requires a file name with an extension.
+ // We're going to add a default file name in selectedFiles,
+ // to match the native behavior. Check now that we can
+ // overwrite the file, if is already exists.
+ fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
+
+ if (!fileInfo.exists() || m_options->testOption(QFileDialogOptions::DontConfirmOverwrite))
+ return YES;
+
+ QMacAutoReleasePool pool;
+ auto *alert = [[NSAlert new] autorelease];
+ alert.alertStyle = NSAlertStyleCritical;
+
+ alert.messageText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"\\U201c%@\\U201d already exists. Do you want to replace it?"),
+ fileInfo.fileName().toNSString()];
+ alert.informativeText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"A file or folder with the same name already exists in the folder %@. "
+ "Replacing it will overwrite its current contents."),
+ fileInfo.absoluteDir().dirName().toNSString()];
+
+ auto *replaceButton = [alert addButtonWithTitle:qt_mac_AppKitString(@"SavePanel", @"Replace")];
+ replaceButton.hasDestructiveAction = YES;
+ replaceButton.tag = 1337;
+ [alert addButtonWithTitle:qt_mac_AppKitString(@"Common", @"Cancel")];
+
+ [alert beginSheetModalForWindow:m_panel
+ completionHandler:^(NSModalResponse returnCode) {
+ [NSApp stopModalWithCode:returnCode];
+ }];
+ return [NSApp runModalForWindow:alert.window] == replaceButton.tag;
+ } else {
+ QFileInfo firstFilter(m_selectedNameFilter.first());
+ auto *domain = qGuiApp->organizationDomain().toNSString();
+ *outError = [NSError errorWithDomain:domain code:0 userInfo:@{
+ NSLocalizedDescriptionKey:[NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"You cannot save this document with extension \\U201c.%1$@\\U201d at the end "
+ "of the name. The required extension is \\U201c.%2$@\\U201d."),
+ fileInfo.completeSuffix().toNSString(), firstFilter.completeSuffix().toNSString()]
+ }];
return NO;
+ }
+ }
return YES;
}
+- (QFileInfo)applyDefaultSuffixFromCurrentNameFilter:(const QFileInfo &)fileInfo
+{
+ QFileInfo filterInfo(m_selectedNameFilter.first());
+ return QFileInfo(fileInfo.absolutePath(),
+ fileInfo.baseName() + '.' + filterInfo.completeSuffix());
+}
+
+- (bool)fileInfoMatchesCurrentNameFilter:(const QFileInfo &)fileInfo
+{
+ // No filter means accept everything
+ if (m_selectedNameFilter.isEmpty())
+ return true;
+
+ // Check if the current file name filter accepts the file
+ for (const auto &filter : m_selectedNameFilter) {
+ if (QDir::match(filter, fileInfo.fileName()))
+ return true;
+ }
+
+ return false;
+}
+
- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails
{
[m_popupButton removeAllItems];
- *m_nameFilterDropDownList = filters;
+ m_nameFilterDropDownList = filters;
if (filters.size() > 0){
for (int i = 0; i < filters.size(); ++i) {
- QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
+ const QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
[m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
}
[m_popupButton selectItemAtIndex:0];
@@ -290,8 +378,8 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
Q_UNUSED(sender);
if (!m_helper)
return;
- QString selection = m_nameFilterDropDownList->value([m_popupButton indexOfSelectedItem]);
- *m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
+ const QString selection = m_nameFilterDropDownList.value([m_popupButton indexOfSelectedItem]);
+ m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
[m_panel validateVisibleColumns];
[self updateProperties];
@@ -310,18 +398,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
}
return result;
} else {
- QList<QUrl> result;
QString filename = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
- const QString defaultSuffix = m_options->defaultSuffix();
- const QFileInfo fileInfo(filename);
+ QFileInfo fileInfo(filename);
+
+ if (fileInfo.suffix().isEmpty() && ![self fileInfoMatchesCurrentNameFilter:fileInfo]) {
+ // We end up in this situation if we accept a file name without extension
+ // in panel:validateURL:error. If so, we match the behavior of the native
+ // save dialog and add the first of the accepted extension from the filter.
+ fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
+ }
// If neither the user or the NSSavePanel have provided a suffix, use
// the default suffix (if it exists).
- if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty())
- filename.append('.').append(defaultSuffix);
+ const QString defaultSuffix = m_options->defaultSuffix();
+ if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) {
+ fileInfo.setFile(fileInfo.absolutePath(),
+ fileInfo.baseName() + '.' + defaultSuffix);
+ }
- result << QUrl::fromLocalFile(filename);
- return result;
+ return { QUrl::fromLocalFile(fileInfo.filePath()) };
}
}
@@ -353,19 +448,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_panel.allowedFileTypes = [self computeAllowedFileTypes];
- // Explicitly show extensions if we detect a filter
- // that has a multi-part extension. This prevents
- // confusing situations where the user clicks e.g.
- // 'foo.tar.gz' and 'foo.tar' is populated in the
- // file name box, but when then clicking save macOS
- // will warn that the file needs to end in .gz,
- // due to thinking the user tried to save the file
- // as a 'tar' file instead. Unfortunately this
- // property can only be set before the panel is
- // shown, so it will not have any effect when
- // switching filters in an already opened dialog.
- if (m_panel.allowedFileTypes.count > 2)
- m_panel.extensionHidden = NO;
+ // Setting allowedFileTypes to nil is not enough to reset any
+ // automatically added extension based on a previous filter.
+ // This is problematic because extensions can in some cases
+ // be hidden from the user, resulting in confusion when the
+ // resulting file name doesn't match the current empty filter.
+ // We work around this by temporarily resetting the allowed
+ // content type to one without an extension, which forces
+ // the save panel to update and remove the extension.
+ const bool nameFieldHasExtension = m_panel.nameFieldStringValue.pathExtension.length > 0;
+ if (!m_panel.allowedFileTypes && !nameFieldHasExtension && !openpanel_cast(m_panel)) {
+ if (!UTTypeDirectory.preferredFilenameExtension) {
+ m_panel.allowedContentTypes = @[ UTTypeDirectory ];
+ m_panel.allowedFileTypes = nil;
+ } else {
+ qWarning() << "UTTypeDirectory unexpectedly reported an extension";
+ }
+ }
+
+ m_panel.showsHiddenFiles = m_options->filter().testFlag(QDir::Hidden);
if (m_panel.visible)
[m_panel validateVisibleColumns];
@@ -378,10 +479,18 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
if (!m_helper)
return;
+ // Save panels only allow you to select directories, which
+ // means currentChanged will only be emitted when selecting
+ // a directory, and if so, with the latest chosen file name,
+ // which is confusing and inconsistent. We choose to bail
+ // out entirely for save panels, to give consistent behavior.
+ if (!openpanel_cast(m_panel))
+ return;
+
if (m_panel.visible) {
- QString selection = QString::fromNSString(m_panel.URL.path);
- if (selection != *m_currentSelection) {
- *m_currentSelection = selection;
+ const QString selection = QString::fromNSString(m_panel.URL.path);
+ if (selection != m_currentSelection) {
+ m_currentSelection = selection;
emit m_helper->currentChanged(QUrl::fromLocalFile(selection));
}
}
@@ -394,14 +503,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
if (!m_helper)
return;
- if (!(path && path.length) || [path isEqualToString:m_currentDirectory])
- return;
-
- [m_currentDirectory release];
- m_currentDirectory = [path retain];
-
- // ### fixme: priv->setLastVisitedDirectory(newDir);
- emit m_helper->directoryEntered(QUrl::fromLocalFile(QString::fromNSString(m_currentDirectory)));
+ m_helper->panelDirectoryDidChange(path);
}
/*
@@ -409,11 +511,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
for the current name filter, and updates the save panel.
If a filter do not conform to the format *.xyz or * or *.*,
- all files types are allowed.
-
- Extensions with more than one part (e.g. "tar.gz") are
- reduced to their final part, as NSSavePanel does not deal
- well with multi-part extensions.
+ or contains an extensions with more than one part (e.g. "tar.gz")
+ we treat that as allowing all file types, and do our own
+ validation in panel:validateURL:error.
*/
- (NSArray<NSString*>*)computeAllowedFileTypes
{
@@ -421,7 +521,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
return nil; // panel:shouldEnableURL: does the file filtering for NSOpenPanel
QStringList fileTypes;
- for (const QString &filter : *m_selectedNameFilter) {
+ for (const QString &filter : std::as_const(m_selectedNameFilter)) {
if (!filter.startsWith("*."_L1))
continue;
@@ -432,6 +532,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
continue;
auto extensions = filter.split('.', Qt::SkipEmptyParts);
+ if (extensions.count() > 2)
+ return nil;
+
fileTypes += extensions.last();
}
@@ -468,10 +571,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_popupButton.target = self;
m_popupButton.action = @selector(filterChanged:);
- if (m_nameFilterDropDownList->size() > 0) {
+ if (!m_nameFilterDropDownList.isEmpty()) {
int filterToUse = -1;
- for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) {
- QString currentFilter = m_nameFilterDropDownList->at(i);
+ for (int i = 0; i < m_nameFilterDropDownList.size(); ++i) {
+ const QString currentFilter = m_nameFilterDropDownList.at(i);
if (selectedFilter == currentFilter ||
(filterToUse == -1 && currentFilter.startsWith(selectedFilter)))
filterToUse = i;
@@ -485,9 +588,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name
{
- for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) {
- if (m_nameFilterDropDownList->at(i).startsWith(name))
- return QPlatformFileDialogHelper::cleanFilterList(m_nameFilterDropDownList->at(i));
+ for (const QString &currentFilter : std::as_const(m_nameFilterDropDownList)) {
+ if (currentFilter.startsWith(name))
+ return QPlatformFileDialogHelper::cleanFilterList(currentFilter);
}
return QStringList();
}
@@ -528,21 +631,32 @@ void QCocoaFileDialogHelper::panelClosed(NSInteger result)
void QCocoaFileDialogHelper::setDirectory(const QUrl &directory)
{
+ m_directory = directory;
+
if (m_delegate)
m_delegate->m_panel.directoryURL = [NSURL fileURLWithPath:directory.toLocalFile().toNSString()];
- else
- m_directory = directory;
}
QUrl QCocoaFileDialogHelper::directory() const
{
- if (m_delegate) {
- QString path = QString::fromNSString(m_delegate->m_panel.directoryURL.path).normalized(QString::NormalizationForm_C);
- return QUrl::fromLocalFile(path);
- }
return m_directory;
}
+void QCocoaFileDialogHelper::panelDirectoryDidChange(NSString *path)
+{
+ if (!path || [path isEqual:NSNull.null] || !path.length)
+ return;
+
+ const auto oldDirectory = m_directory;
+ m_directory = QUrl::fromLocalFile(
+ QString::fromNSString(path).normalized(QString::NormalizationForm_C));
+
+ if (m_directory != oldDirectory) {
+ // FIXME: Plumb old directory back to QFileDialog's lastVisitedDir?
+ emit directoryEntered(m_directory);
+ }
+}
+
void QCocoaFileDialogHelper::selectFile(const QUrl &filename)
{
QString filePath = filename.toLocalFile();
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h
index 369f752dc9..c6862a9e65 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.h
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.h
@@ -42,6 +42,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaApplication)
Q_DECLARE_LOGGING_CATEGORY(lcQpaClipboard)
Q_DECLARE_LOGGING_CATEGORY(lcInputDevices)
Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
class QPixmap;
class QString;
@@ -55,16 +56,6 @@ NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions);
Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions);
Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions);
-template <typename T>
-typename std::enable_if<std::is_pointer<T>::value, T>::type
-qt_objc_cast(id object)
-{
- if ([object isKindOfClass:[typename std::remove_pointer<T>::type class]])
- return static_cast<T>(object);
-
- return nil;
-}
-
QT_MANGLE_NAMESPACE(QNSView) *qnsview_cast(NSView *view);
// Misc
@@ -87,6 +78,9 @@ Qt::MouseButtons currentlyPressedMouseButtons();
// accelerators.
QString qt_mac_removeAmpersandEscapes(QString s);
+// Similar to __NXKitString for localized AppKit strings
+NSString *qt_mac_AppKitString(NSString *table, NSString *key);
+
enum {
QtCocoaEventSubTypeWakeup = SHRT_MAX,
QtCocoaEventSubTypePostMessage = SHRT_MAX-1
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 0810324784..1eba88d5e3 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -29,6 +29,7 @@ Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard")
Q_LOGGING_CATEGORY(lcInputDevices, "qt.qpa.input.devices")
Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs")
+Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
//
// Conversion Functions
@@ -335,6 +336,15 @@ QString qt_mac_removeAmpersandEscapes(QString s)
return QPlatformTheme::removeMnemonics(s).trimmed();
}
+NSString *qt_mac_AppKitString(NSString *table, NSString *key)
+{
+ static const NSBundle *appKit = [NSBundle bundleForClass:NSApplication.class];
+ if (!appKit)
+ return key;
+
+ return [appKit localizedStringForKey:key value:nil table:table];
+}
+
QT_END_NAMESPACE
/*! \internal
diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.mm b/src/plugins/platforms/cocoa/qcocoainputcontext.mm
index b242cd69c6..70461376e2 100644
--- a/src/plugins/platforms/cocoa/qcocoainputcontext.mm
+++ b/src/plugins/platforms/cocoa/qcocoainputcontext.mm
@@ -150,11 +150,18 @@ void QCocoaInputContext::updateLocale()
QString language = QString::fromNSString(languages.firstObject);
QLocale locale(language);
- if (m_locale != locale) {
+
+ bool localeUpdated = m_locale != locale;
+ static bool firstUpdate = true;
+
+ m_locale = locale;
+
+ if (localeUpdated && !firstUpdate) {
qCDebug(lcQpaInputMethods) << "Reporting new locale" << locale;
- m_locale = locale;
emitLocaleChanged();
}
+
+ firstUpdate = false;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h
index 256b7b36ad..664700cf51 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.h
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.h
@@ -20,7 +20,9 @@
#include <QtCore/QScopedPointer>
#include <qpa/qplatformintegration.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#ifndef QT_NO_OPENGL
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qapplekeymapper_p.h>
Q_FORWARD_DECLARE_OBJC_CLASS(NSToolbar);
@@ -82,12 +84,7 @@ public:
QCocoaServices *services() const override;
QVariant styleHint(StyleHint hint) const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *event) const override;
-
- void setToolbar(QWindow *window, NSToolbar *toolbar);
- NSToolbar *toolbar(QWindow *window) const;
- void clearToolbars();
+ QPlatformKeyMapper *keyMapper() const override;
void setApplicationIcon(const QIcon &icon) const override;
void setApplicationBadge(qint64 number) override;
@@ -120,7 +117,6 @@ private:
#if QT_CONFIG(vulkan)
mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr;
#endif
- QHash<QWindow *, NSToolbar *> mToolbars;
QCocoaWindowManager m_windowManager;
};
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 2ec225cbea..2ce39ff897 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -33,11 +33,16 @@
#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/private/qcoregraphics_p.h>
#include <QtGui/private/qmacmimeregistry_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#ifndef QT_NO_OPENGL
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qrhibackingstore_p.h>
#include <QtGui/private/qfontengine_coretext_p.h>
#include <IOKit/graphics/IOGraphicsLib.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
+
+#include <inttypes.h>
static void initResources()
{
@@ -120,9 +125,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
#endif
mFontDb.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>);
- QString icStr = QPlatformInputContextFactory::requested();
- icStr.isNull() ? mInputContext.reset(new QCocoaInputContext)
- : mInputContext.reset(QPlatformInputContextFactory::create(icStr));
+ auto icStrs = QPlatformInputContextFactory::requested();
+ icStrs.isEmpty() ? mInputContext.reset(new QCocoaInputContext)
+ : mInputContext.reset(QPlatformInputContextFactory::create(icStrs));
initResources();
QMacAutoReleasePool pool;
@@ -137,16 +142,6 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
// wants to be foreground applications so change the process type. (But
// see the function implementation for exceptions.)
qt_mac_transformProccessToForegroundApplication();
-
- // Move the application window to front to make it take focus, also when launching
- // from the terminal. On 10.12+ this call has been moved to applicationDidFinishLauching
- // to work around issues with loss of focus at startup.
- if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSSierra) {
- // Ignoring other apps is necessary (we must ignore the terminal), but makes
- // Qt apps play slightly less nice with other apps when lanching from Finder
- // (See the activateIgnoringOtherApps docs.)
- [cocoaApplication activateIgnoringOtherApps : YES];
- }
}
// Qt 4 also does not set the application delegate, so that behavior
@@ -190,6 +185,9 @@ QCocoaIntegration::~QCocoaIntegration()
[[NSApplication sharedApplication] setDelegate:nil];
}
+ // Stop global mouse event and app activation monitoring
+ QCocoaWindow::removePopupMonitor();
+
#ifndef QT_NO_CLIPBOARD
// Delete the clipboard integration and destroy mime type converters.
// Deleting the clipboard integration flushes promised pastes using
@@ -199,8 +197,6 @@ QCocoaIntegration::~QCocoaIntegration()
#endif
QCocoaScreen::cleanupScreens();
-
- clearToolbars();
}
QCocoaIntegration *QCocoaIntegration::instance()
@@ -240,6 +236,7 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons
case RasterGLSurface:
case ApplicationState:
case ApplicationIcon:
+ case BackingStoreStaticContents:
return true;
default:
return QPlatformIntegration::hasCapability(cap);
@@ -307,6 +304,18 @@ QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *wi
return new QCALayerBackingStore(window);
case QSurface::MetalSurface:
case QSurface::OpenGLSurface:
+ case QSurface::VulkanSurface:
+ // If the window is a widget window, we know that the QWidgetRepaintManager
+ // will explicitly use rhiFlush() for the window owning the backingstore,
+ // and any child window with the same surface format. This means we can
+ // safely return a QCALayerBackingStore here, to ensure that any plain
+ // flush() for child windows that don't have a matching surface format
+ // will still work, by setting the layer's contents property.
+ if (window->inherits("QWidgetWindow"))
+ return new QCALayerBackingStore(window);
+
+ // Otherwise we return a QRhiBackingStore, that implements flush() in
+ // terms of rhiFlush().
return new QRhiBackingStore(window);
default:
return nullptr;
@@ -397,38 +406,9 @@ QVariant QCocoaIntegration::styleHint(StyleHint hint) const
return QPlatformIntegration::styleHint(hint);
}
-Qt::KeyboardModifiers QCocoaIntegration::queryKeyboardModifiers() const
-{
- return QAppleKeyMapper::queryKeyboardModifiers();
-}
-
-QList<int> QCocoaIntegration::possibleKeys(const QKeyEvent *event) const
-{
- return mKeyboardMapper->possibleKeys(event);
-}
-
-void QCocoaIntegration::setToolbar(QWindow *window, NSToolbar *toolbar)
-{
- if (NSToolbar *prevToolbar = mToolbars.value(window))
- [prevToolbar release];
-
- [toolbar retain];
- mToolbars.insert(window, toolbar);
-}
-
-NSToolbar *QCocoaIntegration::toolbar(QWindow *window) const
+QPlatformKeyMapper *QCocoaIntegration::keyMapper() const
{
- return mToolbars.value(window);
-}
-
-void QCocoaIntegration::clearToolbars()
-{
- QHash<QWindow *, NSToolbar *>::const_iterator it = mToolbars.constBegin();
- while (it != mToolbars.constEnd()) {
- [it.value() release];
- ++it;
- }
- mToolbars.clear();
+ return mKeyboardMapper.data();
}
void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const
@@ -461,8 +441,8 @@ void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow)
return;
static bool hasDefaultApplicationIcon = [](){
- NSImage *genericApplicationIcon = [[NSWorkspace sharedWorkspace]
- iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
+ NSImage *genericApplicationIcon = [NSWorkspace.sharedWorkspace
+ iconForContentType:UTTypeApplicationBundle];
NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon];
NSRect rect = NSMakeRect(0, 0, 32, 32);
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm
index b8f9a1aa8c..fa88a19d45 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenu.mm
@@ -19,6 +19,7 @@
#include "qcocoaapplicationdelegate.h"
#include <QtCore/private/qcore_mac_p.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -42,6 +43,8 @@ QCocoaMenu::~QCocoaMenu()
item->setMenuParent(nullptr);
}
+ if (isOpen())
+ dismiss();
[m_nativeMenu release];
}
@@ -60,7 +63,7 @@ void QCocoaMenu::setMinimumWidth(int width)
void QCocoaMenu::setFont(const QFont &font)
{
if (font.resolveMask()) {
- NSFont *customMenuFont = [NSFont fontWithName:font.families().first().toNSString()
+ NSFont *customMenuFont = [NSFont fontWithName:font.families().constFirst().toNSString()
size:font.pointSize()];
m_nativeMenu.font = customMenuFont;
}
@@ -88,7 +91,7 @@ void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *
int index = m_menuItems.indexOf(beforeItem);
// if a before item is supplied, it should be in the menu
if (index < 0) {
- qWarning("Before menu item not found");
+ qCWarning(lcQpaMenus) << beforeItem << "not in" << m_menuItems;
return;
}
m_menuItems.insert(index, cocoaItem);
@@ -126,13 +129,13 @@ void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
}
if (nativeItem.menu) {
- qWarning() << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title);
+ qCWarning(lcQpaMenus) << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title);
return;
}
if (beforeItem) {
if (beforeItem->isMerged()) {
- qWarning("No non-merged before menu item found");
+ qCWarning(lcQpaMenus, "No non-merged before menu item found");
return;
}
const NSInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
@@ -168,7 +171,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
QMacAutoReleasePool pool;
QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
if (!m_menuItems.contains(cocoaItem)) {
- qWarning("Menu does not contain the item to be removed");
+ qCWarning(lcQpaMenus) << m_menuItems << "does not contain" << cocoaItem;
return;
}
@@ -181,7 +184,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
m_menuItems.removeOne(cocoaItem);
if (!cocoaItem->isMerged()) {
if (m_nativeMenu != cocoaItem->nsItem().menu) {
- qWarning("Item to remove does not belong to this menu");
+ qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << m_nativeMenu;
return;
}
[m_nativeMenu removeItem:cocoaItem->nsItem()];
@@ -221,7 +224,7 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp
QMacAutoReleasePool pool;
QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
if (!m_menuItems.contains(cocoaItem)) {
- qWarning("Item does not belong to this menu");
+ qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << this;
return;
}
@@ -320,8 +323,12 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
{
QMacAutoReleasePool pool;
+ QPointer<QCocoaMenu> guard = this;
+
QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height());
- QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr;
+ // If the app quits while the menu is open (e.g. through a timer that starts before the menu was opened),
+ // then the window will have been destroyed before this function finishes executing. Account for that with QPointer.
+ QPointer<QCocoaWindow> cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr;
NSView *view = cocoaWindow ? cocoaWindow->view() : nil;
NSMenuItem *nsItem = item ? ((QCocoaMenuItem *)item)->nsItem() : nil;
@@ -404,6 +411,11 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
}
}
+ if (!guard) {
+ menuParentGuard.dismiss();
+ return;
+ }
+
// The calls above block, and also swallow any mouse release event,
// so we need to clear any mouse button that triggered the menu popup.
if (cocoaWindow && !cocoaWindow->isForeignWindow())
@@ -483,6 +495,10 @@ void QCocoaMenu::setAttachedItem(NSMenuItem *item)
if (m_attachedItem)
m_attachedItem.submenu = m_nativeMenu;
+ // NSMenuItems with a submenu and submenuAction: as the item's action
+ // will not take part in NSMenuValidation, so explicitly enable/disable
+ // the item here. See also QCocoaMenuItem::resolveTargetAction()
+ m_attachedItem.enabled = m_attachedItem.hasSubmenu;
}
NSMenuItem *QCocoaMenu::attachedItem() const
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h
index b30f8569ab..785de9c0f6 100644
--- a/src/plugins/platforms/cocoa/qcocoamenubar.h
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.h
@@ -9,6 +9,8 @@
#include <qpa/qplatformmenu.h>
#include "qcocoamenu.h"
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QCocoaWindow;
@@ -45,14 +47,12 @@ private:
bool needsImmediateUpdate();
bool shouldDisable(QCocoaWindow *active) const;
- void insertDefaultEditItems(QCocoaMenu *menu);
NSMenuItem *nativeItemForMenu(QCocoaMenu *menu) const;
QList<QPointer<QCocoaMenu> > m_menus;
NSMenu *m_nativeMenu;
QPointer<QCocoaWindow> m_window;
- QList<QPointer<QCocoaMenuItem>> m_defaultEditMenuItems;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm
index 87eb3e1450..2493d90724 100644
--- a/src/plugins/platforms/cocoa/qcocoamenubar.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm
@@ -9,6 +9,7 @@
#include "qcocoamenuloader.h"
#include "qcocoaapplication.h" // for custom application category
#include "qcocoaapplicationdelegate.h"
+#include "qcocoahelpers.h"
#include <QtGui/QGuiApplication>
#include <QtCore/QDebug>
@@ -30,16 +31,12 @@ QCocoaMenuBar::QCocoaMenuBar()
});
m_nativeMenu = [[NSMenu alloc] init];
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "Construct QCocoaMenuBar" << this << m_nativeMenu;
-#endif
+ qCDebug(lcQpaMenus) << "Constructed" << this << "with" << m_nativeMenu;
}
QCocoaMenuBar::~QCocoaMenuBar()
{
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "~QCocoaMenuBar" << this;
-#endif
+ qCDebug(lcQpaMenus) << "Destructing" << this << "with" << m_nativeMenu;
for (auto menu : std::as_const(m_menus)) {
if (!menu)
continue;
@@ -93,17 +90,16 @@ void QCocoaMenuBar::insertMenu(QPlatformMenu *platformMenu, QPlatformMenu *befor
{
QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
QCocoaMenu *beforeMenu = static_cast<QCocoaMenu *>(before);
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << this << "insertMenu" << menu << "before" << before;
-#endif
+
+ qCDebug(lcQpaMenus) << "Inserting" << menu << "before" << before << "into" << this;
if (m_menus.contains(QPointer<QCocoaMenu>(menu))) {
- qWarning("This menu already belongs to the menubar, remove it first");
+ qCWarning(lcQpaMenus, "This menu already belongs to the menubar, remove it first");
return;
}
if (beforeMenu && !m_menus.contains(QPointer<QCocoaMenu>(beforeMenu))) {
- qWarning("The before menu does not belong to the menubar");
+ qCWarning(lcQpaMenus, "The before menu does not belong to the menubar");
return;
}
@@ -137,7 +133,7 @@ void QCocoaMenuBar::removeMenu(QPlatformMenu *platformMenu)
{
QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
if (!m_menus.contains(menu)) {
- qWarning("Trying to remove a menu that does not belong to the menubar");
+ qCWarning(lcQpaMenus) << "Trying to remove" << menu << "that does not belong to" << this;
return;
}
@@ -178,10 +174,44 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate)
}
}
- if (NSMenuItem *attachedItem = cocoaMenu->attachedItem()) {
- // Non-nil attached item means the item's submenu is set
- attachedItem.title = cocoaMenu->nsMenu().title;
- attachedItem.hidden = shouldHide;
+ if (NSMenuItem *menuItem = cocoaMenu->attachedItem()) {
+ // Non-nil menu item means the item's sub menu is set
+
+ NSString *menuTitle = cocoaMenu->nsMenu().title;
+
+ // The NSMenu's title is what's visible to the user, and AppKit uses this
+ // for some of its heuristics of when to add special items to the menus,
+ // such as 'Enter Full Screen' in the View menu, the search bare in the
+ // Help menu, and the "Send App feedback to Apple" in the Help menu.
+ // This relies on the title matching AppKit's localized value from the
+ // MenuCommands table, which in turn depends on the preferredLocalizations
+ // of the AppKit bundle. We don't do any automatic translation of menu
+ // titles visible to the user, so this relies on the application developer
+ // having chosen translated titles that match AppKit's, and that the Qt
+ // preferred UI languages match AppKit's preferredLocalizations.
+
+ // In the case of the Edit menu, AppKit uses the NSMenuItem's title
+ // for its heuristics of when to add the dictation and emoji entries,
+ // and this title is not visible to the user. But like above, the
+ // heuristics are based on the localized title of the menu, so we need
+ // to ensure the title matches AppKit's localization.
+
+ // Unfortunately, the title we have at this point may have gone through
+ // Qt's i18n machinery already, via e.g. tr("Edit") in the application,
+ // in which case we don't know the context of the translation, and can't
+ // do a reverse lookup to go back to the untranslated title to pass to
+ // AppKit. As a workaround we translate the title via a our context,
+ // and document that the user needs to ensure their application matches
+ // this translation.
+ if ([menuTitle isEqual:@"Edit"] || [menuTitle isEqual:tr("Edit").toNSString()]) {
+ menuItem.title = qt_mac_AppKitString(@"InputManager", @"Edit");
+ } else {
+ // The Edit menu is the only case we know of so far, but to be on
+ // the safe side we always sync the menu title.
+ menuItem.title = menuTitle;
+ }
+
+ menuItem.hidden = shouldHide;
}
}
@@ -195,9 +225,7 @@ NSMenuItem *QCocoaMenuBar::nativeItemForMenu(QCocoaMenu *menu) const
void QCocoaMenuBar::handleReparent(QWindow *newParentWindow)
{
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << this << "handleReparent" << newParentWindow;
-#endif
+ qCDebug(lcQpaMenus) << "Reparenting" << this << "to" << newParentWindow;
if (!m_window.isNull())
m_window->setMenubar(nullptr);
@@ -265,9 +293,8 @@ void QCocoaMenuBar::updateMenuBarImmediately()
if (!mb)
return;
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << "updateMenuBarImmediately" << cw;
-#endif
+ qCDebug(lcQpaMenus) << "Updating" << mb << "immediately for" << cw;
+
bool disableForModal = mb->shouldDisable(cw);
for (auto menu : std::as_const(mb->m_menus)) {
@@ -299,25 +326,23 @@ void QCocoaMenuBar::updateMenuBarImmediately()
}
[mergedItems release];
- [NSApp setMainMenu:mb->nsMenu()];
- insertWindowMenu();
- [loader qtTranslateApplicationMenu];
-
- for (auto menu : std::as_const(mb->m_menus)) {
- if (!menu)
- continue;
-
- const QString captionNoAmpersand = QString::fromNSString(menu->nsMenu().title).remove(u'&');
- if (captionNoAmpersand != QCoreApplication::translate("QCocoaMenu", "Edit"))
- continue;
- NSMenuItem *item = mb->nativeItemForMenu(menu);
- auto *nsMenu = item.submenu;
- if ([nsMenu indexOfItemWithTarget:NSApp andAction:@selector(startDictation:)] == -1) {
- // AppKit was not able to recognize the special role of this menu item.
- mb->insertDefaultEditItems(menu);
- }
+ NSMenu *newMainMenu = mb->nsMenu();
+ if (NSApp.mainMenu == newMainMenu) {
+ // NSApplication triggers _customizeMainMenu when the menu
+ // changes, which takes care of adding text input items to
+ // the edit menu e.g., but this doesn't happen if the menu
+ // is the same. In our case we might be re-using an existing
+ // menu, but the menu might have new sub menus that need to
+ // be customized. To ensure NSApplication does the right
+ // thing we reset the main menu first.
+ qCDebug(lcQpaMenus) << "Clearing main menu temporarily";
+ NSApp.mainMenu = nil;
}
+ NSApp.mainMenu = newMainMenu;
+
+ insertWindowMenu();
+ [loader qtTranslateApplicationMenu];
}
void QCocoaMenuBar::insertWindowMenu()
@@ -336,6 +361,15 @@ void QCocoaMenuBar::insertWindowMenu()
winMenuItem.hidden = YES;
winMenuItem.submenu = [[[NSMenu alloc] initWithTitle:@"QtWindowMenu"] autorelease];
+
+ // AppKit has a bug in [NSApplication setWindowsMenu:] where it will resolve
+ // the last item of the window menu's itemArray, but not account for the array
+ // being empty, resulting in a lookup of itemAtIndex:-1. To work around this,
+ // we insert a hidden dummy item into the menu. See FB13369198.
+ auto *dummyItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+ dummyItem.hidden = YES;
+ [winMenuItem.submenu addItem:[dummyItem autorelease]];
+
[mainMenu insertItem:winMenuItem atIndex:mainMenu.itemArray.count];
app.windowsMenu = winMenuItem.submenu;
@@ -355,8 +389,11 @@ void QCocoaMenuBar::insertWindowMenu()
QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const
{
QList<QCocoaMenuItem*> r;
- for (auto menu : std::as_const(m_menus))
+ for (auto menu : std::as_const(m_menus)) {
+ if (!menu)
+ continue;
r.append(menu->merged());
+ }
return r;
}
@@ -400,7 +437,7 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const
QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
{
for (auto menu : std::as_const(m_menus))
- if (menu->tag() == tag)
+ if (menu && menu->tag() == tag)
return menu;
return nullptr;
@@ -408,10 +445,13 @@ QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole role)
{
- for (auto menu : std::as_const(m_menus))
- for (auto *item : menu->items())
- if (item->effectiveRole() == role)
- return item->nsItem();
+ for (auto menu : std::as_const(m_menus)) {
+ if (menu) {
+ for (auto *item : menu->items())
+ if (item->effectiveRole() == role)
+ return item->nsItem();
+ }
+ }
return nil;
}
@@ -421,48 +461,6 @@ QCocoaWindow *QCocoaMenuBar::cocoaWindow() const
return m_window.data();
}
-void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu)
-{
- if (menu->items().isEmpty())
- return;
-
- NSMenu *nsEditMenu = menu->nsMenu();
- if ([nsEditMenu itemAtIndex:nsEditMenu.numberOfItems - 1].action
- == @selector(orderFrontCharacterPalette:)) {
- for (auto defaultEditMenuItem : std::as_const(m_defaultEditMenuItems)) {
- if (menu->items().contains(defaultEditMenuItem))
- menu->removeMenuItem(defaultEditMenuItem);
- }
- qDeleteAll(m_defaultEditMenuItems);
- m_defaultEditMenuItems.clear();
- } else {
- if (m_defaultEditMenuItems.isEmpty()) {
- QCocoaMenuItem *separator = new QCocoaMenuItem;
- separator->setIsSeparator(true);
-
- QCocoaMenuItem *dictationItem = new QCocoaMenuItem;
- dictationItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Start Dictation..."));
- QObject::connect(dictationItem, &QPlatformMenuItem::activated, this, []{
- [NSApplication.sharedApplication performSelector:@selector(startDictation:)];
- });
-
- QCocoaMenuItem *emojiItem = new QCocoaMenuItem;
- emojiItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Emoji && Symbols"));
- emojiItem->setShortcut(QKeyCombination(Qt::MetaModifier|Qt::ControlModifier, Qt::Key_Space));
- QObject::connect(emojiItem, &QPlatformMenuItem::activated, this, []{
- [NSApplication.sharedApplication orderFrontCharacterPalette:nil];
- });
-
- m_defaultEditMenuItems << separator << dictationItem << emojiItem;
- }
- for (auto defaultEditMenuItem : std::as_const(m_defaultEditMenuItems)) {
- if (menu->items().contains(defaultEditMenuItem))
- menu->removeMenuItem(defaultEditMenuItem);
- menu->insertMenuItem(defaultEditMenuItem, nullptr);
- }
- }
-}
-
QT_END_NAMESPACE
#include "moc_qcocoamenubar.cpp"
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h
index 6e8c17d30c..f677ffb7a7 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.h
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h
@@ -8,7 +8,7 @@
#include <qpa/qplatformmenu.h>
#include <QtGui/QImage>
-//#define QT_COCOA_ENABLE_MENU_DEBUG
+#include <QtCore/qpointer.h>
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenuItem);
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenu);
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
index c526a7a8fb..3a0f71bc50 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
@@ -180,38 +180,71 @@ void QCocoaMenuItem::setNativeContents(WId item)
m_itemView.needsDisplay = YES;
}
-static QPlatformMenuItem::MenuRole detectMenuRole(const QString &caption)
+static QPlatformMenuItem::MenuRole detectMenuRole(const QString &captionWithPossibleMnemonic)
{
- QString captionNoAmpersand(caption);
- captionNoAmpersand.remove(u'&');
- const QString aboutString = QCoreApplication::translate("QCocoaMenuItem", "About");
- if (captionNoAmpersand.startsWith(aboutString, Qt::CaseInsensitive)
- || captionNoAmpersand.endsWith(aboutString, Qt::CaseInsensitive)) {
+ QString itemCaption(captionWithPossibleMnemonic);
+ itemCaption.remove(u'&');
+
+ static const std::tuple<QPlatformMenuItem::MenuRole, std::vector<std::tuple<Qt::MatchFlags, const char *>>> roleMap[] = {
+ { QPlatformMenuItem::AboutRole, {
+ { Qt::MatchStartsWith | Qt::MatchEndsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "About") }
+ }},
+ { QPlatformMenuItem::PreferencesRole, {
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Config") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Preference") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Options") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setting") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setup") },
+ }},
+ { QPlatformMenuItem::QuitRole, {
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Quit") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Exit") },
+ }},
+ { QPlatformMenuItem::CutRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Cut") }
+ }},
+ { QPlatformMenuItem::CopyRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Copy") }
+ }},
+ { QPlatformMenuItem::PasteRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Paste") }
+ }},
+ { QPlatformMenuItem::SelectAllRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Select All") }
+ }},
+ };
+
+ auto match = [](const QString &caption, const QString &itemCaption, Qt::MatchFlags matchFlags) {
+ if (matchFlags.testFlag(Qt::MatchExactly))
+ return !itemCaption.compare(caption, Qt::CaseInsensitive);
+ if (matchFlags.testFlag(Qt::MatchStartsWith) && itemCaption.startsWith(caption, Qt::CaseInsensitive))
+ return true;
+ if (matchFlags.testFlag(Qt::MatchEndsWith) && itemCaption.endsWith(caption, Qt::CaseInsensitive))
+ return true;
+ return false;
+ };
+
+ QPlatformMenuItem::MenuRole detectedRole = [&]{
+ for (const auto &[role, captions] : roleMap) {
+ for (const auto &[matchFlags, caption] : captions) {
+ // Check for untranslated match
+ if (match(caption, itemCaption, matchFlags))
+ return role;
+ // Then translated with the current Qt translation
+ if (match(QCoreApplication::translate("QCocoaMenuItem", caption), itemCaption, matchFlags))
+ return role;
+ }
+ }
+ return QPlatformMenuItem::NoRole;
+ }();
+
+ if (detectedRole == QPlatformMenuItem::AboutRole) {
static const QRegularExpression qtRegExp("qt$"_L1, QRegularExpression::CaseInsensitiveOption);
- if (captionNoAmpersand.contains(qtRegExp))
- return QPlatformMenuItem::AboutQtRole;
- return QPlatformMenuItem::AboutRole;
- }
- if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Config"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Preference"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Options"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setting"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setup"), Qt::CaseInsensitive)) {
- return QPlatformMenuItem::PreferencesRole;
+ if (itemCaption.contains(qtRegExp))
+ detectedRole = QPlatformMenuItem::AboutQtRole;
}
- if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Quit"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Exit"), Qt::CaseInsensitive)) {
- return QPlatformMenuItem::QuitRole;
- }
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Cut"), Qt::CaseInsensitive))
- return QPlatformMenuItem::CutRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Copy"), Qt::CaseInsensitive))
- return QPlatformMenuItem::CopyRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Paste"), Qt::CaseInsensitive))
- return QPlatformMenuItem::PasteRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Select All"), Qt::CaseInsensitive))
- return QPlatformMenuItem::SelectAllRole;
- return QPlatformMenuItem::NoRole;
+
+ return detectedRole;
}
NSMenuItem *QCocoaMenuItem::sync()
@@ -383,7 +416,7 @@ QKeySequence QCocoaMenuItem::mergeAccel()
void QCocoaMenuItem::syncMerged()
{
if (!m_merged) {
- qWarning("Trying to sync a non-merged item");
+ qCWarning(lcQpaMenus) << "Trying to sync non-merged" << this;
return;
}
@@ -440,7 +473,20 @@ void QCocoaMenuItem::resolveTargetAction()
roleAction = @selector(selectAll:);
break;
default:
- roleAction = @selector(qt_itemFired:);
+ if (m_menu) {
+ // Menu items that represent sub menus should have submenuAction: as their
+ // action, so that clicking the menu item opens the sub menu without closing
+ // the entire menu hierarchy. A menu item with this action and a valid submenu
+ // will disable NSMenuValidation for the item, which is normally not an issue
+ // as NSMenuItems are enabled by default. But in our case, we haven't attached
+ // the submenu yet, which results in AppKit concluding that there's no validator
+ // for the item (the target is nil, and nothing responds to submenuAction:), and
+ // will in response disable the menu item. To work around this we explicitly
+ // enable the menu item in QCocoaMenu::setAttachedItem() once we have a submenu.
+ roleAction = @selector(submenuAction:);
+ } else {
+ roleAction = @selector(qt_itemFired:);
+ }
}
m_native.action = roleAction;
diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
index 6d3c668b87..b5ee3479aa 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
@@ -186,8 +186,8 @@
if (appMenu.supermenu)
unparentAppMenu(appMenu.supermenu);
- NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple"
- action:nil keyEquivalent:@""];
+ NSMenuItem *appMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Apple"
+ action:nil keyEquivalent:@""] autorelease];
appMenuItem.submenu = appMenu;
[menu insertItem:appMenuItem atIndex:0];
}
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.h b/src/plugins/platforms/cocoa/qcocoamessagedialog.h
index 564dd915c5..b8c273469a 100644
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.h
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.h
@@ -28,6 +28,7 @@ private:
Qt::WindowModality modality() const;
NSAlert *m_alert = nullptr;
QEventLoop *m_eventLoop = nullptr;
+ NSModalResponse runModal() const;
void processResponse(NSModalResponse response);
};
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
index d5d9469832..84525099c9 100644
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
@@ -5,6 +5,7 @@
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
+#include "qcocoaeventdispatcher.h"
#include <QtCore/qmetaobject.h>
#include <QtCore/qscopedvaluerollback.h>
@@ -46,6 +47,16 @@ static QString toPlainText(const QString &text)
return textDocument.toPlainText();
}
+static NSControlStateValue controlStateFor(Qt::CheckState state)
+{
+ switch (state) {
+ case Qt::Checked: return NSControlStateValueOn;
+ case Qt::Unchecked: return NSControlStateValueOff;
+ case Qt::PartiallyChecked: return NSControlStateValueMixed;
+ }
+ Q_UNREACHABLE();
+}
+
/*
Called from QDialogPrivate::setNativeDialogVisible() when the message box
is ready to be shown.
@@ -80,14 +91,25 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
if (!options())
return false;
+ // NSAlert doesn't have a section for detailed text
+ if (!options()->detailedText().isEmpty()) {
+ qCWarning(lcQpaDialogs, "Message box contains detailed text");
+ return false;
+ }
+
+ if (Qt::mightBeRichText(options()->text()) ||
+ Qt::mightBeRichText(options()->informativeText())) {
+ // Let's fallback to non-native message box,
+ // we only have plain NSString/text in NSAlert.
+ qCDebug(lcQpaDialogs, "Message box contains text in rich text format");
+ return false;
+ }
+
Q_ASSERT(!m_alert);
m_alert = [NSAlert new];
m_alert.window.title = options()->windowTitle().toNSString();
- QString text = toPlainText(options()->text());
- QString details = toPlainText(options()->detailedText());
- if (!details.isEmpty())
- text += u"\n\n"_s + details;
+ const QString text = toPlainText(options()->text());
m_alert.messageText = text.toNSString();
m_alert.informativeText = toPlainText(options()->informativeText()).toNSString();
@@ -115,8 +137,8 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
break;
}
- bool defaultButtonAdded = false;
- bool cancelButtonAdded = false;
+ auto defaultButton = options()->defaultButton();
+ auto escapeButton = options()->escapeButton();
const auto addButton = [&](auto title, auto tag, auto role) {
title = QPlatformTheme::removeMnemonics(title);
@@ -126,17 +148,27 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
// and going toward the left/bottom. By default, the first button has a key equivalent of
// Return, any button with a title of "Cancel" has a key equivalent of Escape, and any button
// with the title "Don't Save" has a key equivalent of Command-D (but only if it's not the first
- // button). Unfortunately QMessageBox does not currently plumb setDefaultButton/setEscapeButton
- // through the dialog options, so we can't forward this information directly. The closest we
- // can get right now is to use the role to set the button's key equivalent.
+ // button). If an explicit default or escape button has been set, we respect these,
+ // and otherwise we fall back to role-based default and escape buttons.
+
+ qCDebug(lcQpaDialogs).verbosity(0) << "Adding button" << title << "with" << role;
+
+ if (!defaultButton && role == AcceptRole)
+ defaultButton = tag;
- if (role == AcceptRole && !defaultButtonAdded) {
+ if (tag == defaultButton)
button.keyEquivalent = @"\r";
- defaultButtonAdded = true;
- } else if (role == RejectRole && !cancelButtonAdded) {
+ else if ([button.keyEquivalent isEqualToString:@"\r"])
+ button.keyEquivalent = @"";
+
+ if (!escapeButton && role == RejectRole)
+ escapeButton = tag;
+
+ // Don't override default button with escape button, to match AppKit default
+ if (tag == escapeButton && ![button.keyEquivalent isEqualToString:@"\r"])
button.keyEquivalent = @"\e";
- cancelButtonAdded = true;
- }
+ else if ([button.keyEquivalent isEqualToString:@"\e"])
+ button.keyEquivalent = @"";
if (@available(macOS 11, *))
button.hasDestructiveAction = role == DestructiveRole;
@@ -156,31 +188,77 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
button.tag = tag;
};
+ // Resolve all dialog buttons from the options, both standard and custom
+
+ struct Button { QString title; int identifier; ButtonRole role; };
+ std::vector<Button> buttons;
+
const auto *platformTheme = QGuiApplicationPrivate::platformTheme();
if (auto standardButtons = options()->standardButtons()) {
- for (int standardButton = FirstButton; standardButton < LastButton; standardButton <<= 1) {
+ for (int standardButton = FirstButton; standardButton <= LastButton; standardButton <<= 1) {
if (standardButtons & standardButton) {
auto title = platformTheme->standardButtonText(standardButton);
- addButton(title, standardButton, buttonRole(StandardButton(standardButton)));
+ buttons.push_back({
+ title, standardButton, buttonRole(StandardButton(standardButton))
+ });
}
}
}
-
const auto customButtons = options()->customButtons();
for (auto customButton : customButtons)
- addButton(customButton.label, customButton.id, customButton.role);
+ buttons.push_back({customButton.label, customButton.id, customButton.role});
+
+ // Sort them according to the QPlatformDialogHelper::ButtonLayout for macOS
+
+ // The ButtonLayout adds one additional role, AlternateRole, which is used
+ // for any AcceptRole beyond the first one, and should be ordered before the
+ // AcceptRole. Set this up by fixing the roles up front.
+ bool seenAccept = false;
+ for (auto &button : buttons) {
+ if (button.role == AcceptRole) {
+ if (!seenAccept)
+ seenAccept = true;
+ else
+ button.role = AlternateRole;
+ }
+ }
+
+ std::vector<Button> orderedButtons;
+ const int *layoutEntry = buttonLayout(Qt::Horizontal, ButtonLayout::MacLayout);
+ while (*layoutEntry != QPlatformDialogHelper::EOL) {
+ const auto role = ButtonRole(*layoutEntry & ~ButtonRole::Reverse);
+ const bool reverse = *layoutEntry & ButtonRole::Reverse;
+ auto addButton = [&](const Button &button) {
+ if (button.role == role)
+ orderedButtons.push_back(button);
+ };
- // QMessageDialog's logic for adding a fallback OK button if no other buttons
- // are added depends on QMessageBox::showEvent(), which is too late when
- // native dialogs are in use. To ensure there's always an OK button with a tag
- // we recognize we add it explicitly here as a fallback.
- if (!m_alert.buttons.count) {
- addButton(platformTheme->standardButtonText(StandardButton::Ok),
- StandardButton::Ok, ButtonRole::AcceptRole);
+ if (reverse)
+ std::for_each(std::crbegin(buttons), std::crend(buttons), addButton);
+ else
+ std::for_each(std::cbegin(buttons), std::cend(buttons), addButton);
+
+ ++layoutEntry;
}
- m_alert.showsSuppressionButton = options()->supressionCheckBoxEnabled();
+ // Add them to the alert in reverse order, since buttons are added right to left
+ for (auto button = orderedButtons.crbegin(); button != orderedButtons.crend(); ++button)
+ addButton(button->title, button->identifier, button->role);
+
+ // If we didn't find a an explicit or implicit default button above
+ // we restore the AppKit behavior of making the first button default.
+ if (!defaultButton)
+ m_alert.buttons.firstObject.keyEquivalent = @"\r";
+
+ if (auto checkBoxLabel = options()->checkBoxLabel(); !checkBoxLabel.isNull()) {
+ checkBoxLabel = QPlatformTheme::removeMnemonics(checkBoxLabel);
+ m_alert.suppressionButton.title = checkBoxLabel.toNSString();
+ auto state = options()->checkBoxState();
+ m_alert.suppressionButton.allowsMixedState = state == Qt::PartiallyChecked;
+ m_alert.suppressionButton.state = controlStateFor(state);
+ m_alert.showsSuppressionButton = YES;
+ }
qCDebug(lcQpaDialogs) << "Showing" << m_alert;
@@ -200,9 +278,10 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
// but also make sure that if the user returns to the main runloop
// we'll run the modal dialog from there.
QTimer::singleShot(0, this, [this]{
- if (m_alert && NSApp.modalWindow != m_alert.window) {
+ if (m_alert && !m_alert.window.visible) {
qCDebug(lcQpaDialogs) << "Running deferred modal" << m_alert;
- processResponse([m_alert runModal]);
+ QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
+ processResponse(runModal());
}
});
}
@@ -210,6 +289,20 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
return true;
}
+// We shouldn't get NSModalResponseContinue as a response from NSAlert::runModal,
+// and processResponse must not be called with that value (if we are there, it's
+// too late to do anything about it.
+// However, as QTBUG-114546 shows, there are scenarios where we might get that
+// response anyway. We interpret it to keep the modal loop running, and we only
+// return if we got something else to pass to processResponse.
+NSModalResponse QCocoaMessageDialog::runModal() const
+{
+ NSModalResponse response = NSModalResponseContinue;
+ while (response == NSModalResponseContinue)
+ response = [m_alert runModal];
+ return response;
+}
+
void QCocoaMessageDialog::exec()
{
Q_ASSERT(m_alert);
@@ -221,13 +314,24 @@ void QCocoaMessageDialog::exec()
m_eventLoop->exec(QEventLoop::DialogExec);
} else {
qCDebug(lcQpaDialogs) << "Running modal" << m_alert;
- processResponse([m_alert runModal]);
+ QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
+ processResponse(runModal());
}
}
// Custom modal response code to record that the dialog was hidden by us
static const NSInteger kModalResponseDialogHidden = NSAlertThirdButtonReturn + 1;
+static Qt::CheckState checkStateFor(NSControlStateValue state)
+{
+ switch (state) {
+ case NSControlStateValueOn: return Qt::Checked;
+ case NSControlStateValueOff: return Qt::Unchecked;
+ case NSControlStateValueMixed: return Qt::PartiallyChecked;
+ }
+ Q_UNREACHABLE();
+}
+
void QCocoaMessageDialog::processResponse(NSModalResponse response)
{
qCDebug(lcQpaDialogs) << "Processing response" << response << "for" << m_alert;
@@ -239,7 +343,7 @@ void QCocoaMessageDialog::processResponse(NSModalResponse response)
[alert autorelease];
if (alert.showsSuppressionButton)
- emit supressionCheckBoxChanged(alert.suppressionButton.state == NSControlStateValueOn);
+ emit checkBoxStateChanged(checkStateFor(alert.suppressionButton.state));
if (response >= NSAlertFirstButtonReturn) {
// Safe range for user-defined modal responses
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
index 344c8523ce..a406cae366 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
@@ -31,12 +31,6 @@ public Q_SLOTS:
void onAppFocusWindowChanged(QWindow *window);
private:
- /*
- Function to return the default background pixmap.
- Needed by QWizard in the Qt widget module.
- */
- Q_INVOKABLE QPixmap defaultBackgroundPixmapForQWizard();
-
Q_INVOKABLE void clearCurrentThreadCocoaEventDispatcherInterruptFlag();
static void registerDraggedTypes(const QStringList &types);
@@ -53,9 +47,6 @@ private:
// deregisters.
static void registerTouchWindow(QWindow *window, bool enable);
- // Set the size of the unified title and toolbar area.
- static void setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness);
-
// Set the size for a unified toolbar content border area.
// Multiple callers can register areas and the platform plugin
// will extend the "unified" area to cover them.
@@ -67,12 +58,6 @@ private:
// Returns true if the given coordinate is inside the current
// content border.
static bool testContentBorderPosition(QWindow *window, int position);
-
- // Sets a NSToolbar instance for the given QWindow. The
- // toolbar will be attached to the native NSWindow when
- // that is created;
- static void setNSToolbar(QWindow *window, void *nsToolbar);
-
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
index fd7e1c07b7..58bda2706a 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
@@ -65,49 +65,16 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInter
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerTouchWindow);
if (resource.toLower() == "setembeddedinforeignview")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setEmbeddedInForeignView);
- if (resource.toLower() == "setcontentborderthickness")
- return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderThickness);
if (resource.toLower() == "registercontentborderarea")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerContentBorderArea);
if (resource.toLower() == "setcontentborderareaenabled")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderAreaEnabled);
- if (resource.toLower() == "setnstoolbar")
- return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setNSToolbar);
if (resource.toLower() == "testcontentborderposition")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::testContentBorderPosition);
return nullptr;
}
-QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard()
-{
- // Note: starting with macOS 10.14, the KeyboardSetupAssistant app bundle no
- // longer contains the "Background.png" image. This function then returns a
- // null pixmap.
- const int ExpectedImageWidth = 242;
- const int ExpectedImageHeight = 414;
- QCFType<CFArrayRef> urls = LSCopyApplicationURLsForBundleIdentifier(
- CFSTR("com.apple.KeyboardSetupAssistant"), nullptr);
- if (urls && CFArrayGetCount(urls) > 0) {
- CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0);
- QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url);
- if (bundle) {
- url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), nullptr);
- if (url) {
- QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, nullptr);
- QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
- if (image) {
- int width = CGImageGetWidth(image);
- int height = CGImageGetHeight(image);
- if (width == ExpectedImageWidth && height == ExpectedImageHeight)
- return QPixmap::fromImage(qt_mac_toQImage(image));
- }
- }
- }
- }
- return QPixmap();
-}
-
void QCocoaNativeInterface::clearCurrentThreadCocoaEventDispatcherInterruptFlag()
{
QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
@@ -141,16 +108,6 @@ void QCocoaNativeInterface::registerTouchWindow(QWindow *window, bool enable)
cocoaWindow->registerTouch(enable);
}
-void QCocoaNativeInterface::setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness)
-{
- if (!window)
- return;
-
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- if (cocoaWindow)
- cocoaWindow->setContentBorderThickness(topThickness, bottomThickness);
-}
-
void QCocoaNativeInterface::registerContentBorderArea(QWindow *window, quintptr identifier, int upper, int lower)
{
if (!window)
@@ -171,15 +128,6 @@ void QCocoaNativeInterface::setContentBorderAreaEnabled(QWindow *window, quintpt
cocoaWindow->setContentBorderAreaEnabled(identifier, enable);
}
-void QCocoaNativeInterface::setNSToolbar(QWindow *window, void *nsToolbar)
-{
- QCocoaIntegration::instance()->setToolbar(window, static_cast<NSToolbar *>(nsToolbar));
-
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- if (cocoaWindow)
- cocoaWindow->updateNSToolbar();
-}
-
bool QCocoaNativeInterface::testContentBorderPosition(QWindow *window, int position)
{
if (!window)
diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm
index 4bc9b0b5f9..ba222a3ef4 100644
--- a/src/plugins/platforms/cocoa/qcocoansmenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm
@@ -16,6 +16,8 @@
#include <QtCore/qvarlengtharray.h>
#include <QtGui/private/qapplekeymapper_p.h>
+#include <QtCore/qpointer.h>
+
static NSString *qt_mac_removePrivateUnicode(NSString *string)
{
if (const int len = string.length) {
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h
index 435a6b95d8..7708dc1968 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.h
+++ b/src/plugins/platforms/cocoa/qcocoascreen.h
@@ -41,13 +41,14 @@ public:
QWindow *topLevelAt(const QPoint &point) const override;
QList<QPlatformScreen *> virtualSiblings() const override;
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override;
+ Qt::ScreenOrientation orientation() const override;
// ----------------------------------------------------
static NSScreen *nativeScreenForDisplayId(CGDirectDisplayID displayId);
NSScreen *nativeScreen() const;
- void requestUpdate();
+ bool requestUpdate();
void deliverUpdateRequests();
bool isRunningDisplayLink() const;
@@ -90,6 +91,7 @@ private:
QSizeF m_physicalSize;
QCocoaCursor *m_cursor;
qreal m_devicePixelRatio = 0;
+ qreal m_rotation = 0;
CVDisplayLinkRef m_displayLink = nullptr;
dispatch_source_t m_displayLinkSource = nullptr;
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm
index 10b739d6d8..be562e5455 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.mm
+++ b/src/plugins/platforms/cocoa/qcocoascreen.mm
@@ -15,6 +15,7 @@
#include <IOKit/graphics/IOGraphicsLib.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtCore/private/qcore_mac_p.h>
#include <QtCore/private/qeventdispatcher_cf_p.h>
@@ -199,7 +200,7 @@ QCocoaScreen::~QCocoaScreen()
static QString displayName(CGDirectDisplayID displayID)
{
QIOType<io_iterator_t> iterator;
- if (IOServiceGetMatchingServices(kIOMasterPortDefault,
+ if (IOServiceGetMatchingServices(kIOMainPortDefault,
IOServiceMatching("IODisplayConnect"), &iterator))
return QString();
@@ -247,6 +248,7 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
const QRect previousGeometry = m_geometry;
const QRect previousAvailableGeometry = m_availableGeometry;
const qreal previousRefreshRate = m_refreshRate;
+ const double previousRotation = m_rotation;
// The reference screen for the geometry is always the primary screen
QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
@@ -271,6 +273,7 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
m_refreshRate = refresh > 0 ? refresh : 60.0;
+ m_rotation = CGDisplayRotation(displayId);
if (@available(macOS 10.15, *))
m_name = QString::fromNSString(nsScreen.localizedName);
@@ -279,6 +282,9 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
+ if (m_rotation != previousRotation)
+ QWindowSystemInterface::handleScreenOrientationChange(screen(), orientation());
+
if (didChangeGeometry)
QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry());
if (m_refreshRate != previousRefreshRate)
@@ -289,24 +295,33 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
-void QCocoaScreen::requestUpdate()
+bool QCocoaScreen::requestUpdate()
{
Q_ASSERT(m_displayId);
if (!isOnline()) {
qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
- return;
+ return false;
}
if (!m_displayLink) {
- CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink);
+ qCDebug(lcQpaScreenUpdates) << "Creating display link for" << this;
+ if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
+ qCWarning(lcQpaScreenUpdates) << "Failed to create display link for" << this;
+ return false;
+ }
+ if (auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
+ qCWarning(lcQpaScreenUpdates) << "Unexpected display" << displayId << "for display link";
+ CVDisplayLinkRelease(m_displayLink);
+ m_displayLink = nullptr;
+ return false;
+ }
CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int {
// FIXME: It would be nice if update requests would include timing info
static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
return kCVReturnSuccess;
}, this);
- qCDebug(lcQpaScreenUpdates) << "Display link created for" << this;
// During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop
// in event-tracking mode, dequeuing only the mouse drag events needed to update the window's
@@ -361,6 +376,8 @@ void QCocoaScreen::requestUpdate()
qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this;
CVDisplayLinkStart(m_displayLink);
}
+
+ return true;
}
// Helper to allow building up debug output in multiple steps
@@ -454,6 +471,25 @@ void QCocoaScreen::deliverUpdateRequests()
if (!platformWindow->updatesWithDisplayLink())
continue;
+ // QTBUG-107198: Skip updates in a live resize for a better resize experience.
+ if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
+ const QSurface::SurfaceType surfaceType = window->surfaceType();
+ const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
+ const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
+ != NSViewLayerContentsPlacementScaleAxesIndependently;
+ if (usesMetalLayer && usesNonDefaultContentsPlacement) {
+ static bool deliverDisplayLinkUpdatesDuringLiveResize =
+ qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE");
+ if (!deliverDisplayLinkUpdatesDuringLiveResize) {
+ // Must keep the link running, we do not know what the event
+ // handlers for UpdateRequest (which is not sent now) would do,
+ // would they trigger a new requestUpdate() or not.
+ pauseUpdates = false;
+ continue;
+ }
+ }
+ }
+
platformWindow->deliverUpdateRequest();
// Another update request was triggered, keep the display link running
@@ -491,41 +527,56 @@ QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingType
return type;
}
+Qt::ScreenOrientation QCocoaScreen::orientation() const
+{
+ if (m_rotation == 0)
+ return Qt::LandscapeOrientation;
+ if (m_rotation == 90)
+ return Qt::PortraitOrientation;
+ if (m_rotation == 180)
+ return Qt::InvertedLandscapeOrientation;
+ if (m_rotation == 270)
+ return Qt::InvertedPortraitOrientation;
+ return QPlatformScreen::orientation();
+}
+
QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
{
- NSPoint screenPoint = mapToNative(point);
-
- // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint:
- // belowWindowWithWindowNumber] may return windows that are not interesting
- // to Qt. The search iterates until a suitable window or no window is found.
- NSInteger topWindowNumber = 0;
- QWindow *window = nullptr;
- do {
- // Get the top-most window, below any previously rejected window.
- topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint
- belowWindowWithWindowNumber:topWindowNumber];
-
- // Continue the search if the window does not belong to this process.
- NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber];
- if (!nsWindow)
- continue;
+ __block QWindow *window = nullptr;
+ [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
+ usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
+ if (!nsWindow)
+ return;
- // Continue the search if the window does not belong to Qt.
- if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
- continue;
+ // Continue the search if the window does not belong to Qt
+ if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return;
- QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
- if (!cocoaWindow)
- continue;
- window = cocoaWindow->window();
+ QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
+ if (!cocoaWindow)
+ return;
+
+ QWindow *w = cocoaWindow->window();
+ if (!w->isVisible())
+ return;
- // Continue the search if the window is not a top-level window.
- if (!window->isTopLevel())
- continue;
+ auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
+ if (!nativeGeometry.contains(point))
+ return;
- // Stop searching. The current window is the correct window.
- break;
- } while (topWindowNumber > 0);
+ QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w);
+ if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
+ return;
+
+ window = w;
+
+ // Continue the search if the window is not a top-level window
+ if (!window->isTopLevel())
+ return;
+
+ *stop = true;
+ }
+ ];
return window;
}
diff --git a/src/plugins/platforms/cocoa/qcocoaservices.h b/src/plugins/platforms/cocoa/qcocoaservices.h
index 20d9b67760..b6299570e8 100644
--- a/src/plugins/platforms/cocoa/qcocoaservices.h
+++ b/src/plugins/platforms/cocoa/qcocoaservices.h
@@ -4,6 +4,8 @@
#ifndef QCOCOADESKTOPSERVICES_H
#define QCOCOADESKTOPSERVICES_H
+#include <QtCore/qurl.h>
+
#include <qpa/qplatformservices.h>
QT_BEGIN_NAMESPACE
@@ -11,8 +13,16 @@ QT_BEGIN_NAMESPACE
class QCocoaServices : public QPlatformServices
{
public:
+ bool hasCapability(Capability capability) const override;
+
bool openUrl(const QUrl &url) override;
bool openDocument(const QUrl &url) override;
+ bool handleUrl(const QUrl &url);
+
+ QPlatformServiceColorPicker *colorPicker(QWindow *parent) override;
+
+private:
+ QUrl m_handlingUrl;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaservices.mm b/src/plugins/platforms/cocoa/qcocoaservices.mm
index 68daa660ef..87212c265c 100644
--- a/src/plugins/platforms/cocoa/qcocoaservices.mm
+++ b/src/plugins/platforms/cocoa/qcocoaservices.mm
@@ -4,14 +4,23 @@
#include "qcocoaservices.h"
#include <AppKit/NSWorkspace.h>
+#include <AppKit/NSColorSampler.h>
#include <Foundation/NSURL.h>
#include <QtCore/QUrl>
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <QtGui/qdesktopservices.h>
+#include <QtGui/private/qcoregraphics_p.h>
QT_BEGIN_NAMESPACE
bool QCocoaServices::openUrl(const QUrl &url)
{
+ // avoid recursing back into self
+ if (url == m_handlingUrl)
+ return false;
+
return [[NSWorkspace sharedWorkspace] openURL:url.toNSURL()];
}
@@ -20,4 +29,45 @@ bool QCocoaServices::openDocument(const QUrl &url)
return openUrl(url);
}
+/* Callback from macOS that the application should handle a URL */
+bool QCocoaServices::handleUrl(const QUrl &url)
+{
+ QScopedValueRollback<QUrl> rollback(m_handlingUrl, url);
+ // FIXME: Add platform services callback from QDesktopServices::setUrlHandler
+ // so that we can warn the user if calling setUrlHandler without also setting
+ // up the matching keys in the Info.plist file (CFBundleURLTypes and friends).
+ return QDesktopServices::openUrl(url);
+}
+
+class QCocoaColorPicker : public QPlatformServiceColorPicker
+{
+public:
+ QCocoaColorPicker() : m_colorSampler([NSColorSampler new]) {}
+ ~QCocoaColorPicker() { [m_colorSampler release]; }
+
+ void pickColor() override
+ {
+ [m_colorSampler showSamplerWithSelectionHandler:^(NSColor *selectedColor) {
+ emit colorPicked(qt_mac_toQColor(selectedColor));
+ }];
+ }
+private:
+ NSColorSampler *m_colorSampler = nullptr;
+};
+
+
+QPlatformServiceColorPicker *QCocoaServices::colorPicker(QWindow *parent)
+{
+ Q_UNUSED(parent);
+ return new QCocoaColorPicker;
+}
+
+bool QCocoaServices::hasCapability(Capability capability) const
+{
+ switch (capability) {
+ case ColorPicking: return true;
+ default: return false;
+ }
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index 2f7f73b481..cec8301cf6 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -56,6 +56,12 @@
#include "qcocoascreen.h"
#include <QtGui/private/qcoregraphics_p.h>
+#warning NSUserNotification was deprecated in macOS 11. \
+We should be using UserNotifications.framework instead. \
+See QTBUG-110998 for more information.
+#define NSUserNotificationCenter QT_IGNORE_DEPRECATIONS(NSUserNotificationCenter)
+#define NSUserNotification QT_IGNORE_DEPRECATIONS(NSUserNotification)
+
QT_BEGIN_NAMESPACE
void QCocoaSystemTrayIcon::init()
@@ -178,7 +184,18 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
{
- m_statusItem.menu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
+ auto *nsMenu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
+ if (m_statusItem.menu == nsMenu)
+ return;
+
+ if (m_statusItem.menu) {
+ [NSNotificationCenter.defaultCenter removeObserver:m_delegate
+ name:NSMenuDidBeginTrackingNotification
+ object:m_statusItem.menu
+ ];
+ }
+
+ m_statusItem.menu = nsMenu;
if (m_statusItem.menu) {
// When a menu is assigned, NSStatusBarButtonCell will intercept the mouse
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h
index d19e20e7c2..c49d83feae 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.h
+++ b/src/plugins/platforms/cocoa/qcocoatheme.h
@@ -35,9 +35,10 @@ public:
const QFont *font(Font type = SystemFont) const override;
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
QVariant themeHint(ThemeHint hint) const override;
- Qt::Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
QString standardButtonText(int button) const override;
QKeySequence standardButtonShortcut(int button) const override;
@@ -54,6 +55,9 @@ private:
QMacNotificationObserver m_systemColorObserver;
mutable QHash<QPlatformTheme::Palette, QPalette*> m_palettes;
QMacKeyValueObserver m_appearanceObserver;
+
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+ void updateColorScheme();
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm
index 69823b409b..f4fbfadbe4 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.mm
+++ b/src/plugins/platforms/cocoa/qcocoatheme.mm
@@ -22,6 +22,7 @@
#include <QtGui/qpainter.h>
#include <QtGui/qtextformat.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
+#include <QtGui/private/qappleiconengine_p.h>
#include <QtGui/private/qfontengine_coretext_p.h>
#include <QtGui/private/qabstractfileiconengine_p.h>
#include <qpa/qplatformdialoghelper.h>
@@ -94,6 +95,9 @@ static QPalette *qt_mac_createSystemPalette()
palette->setColor(QPalette::Inactive, QPalette::PlaceholderText, qc);
palette->setColor(QPalette::Disabled, QPalette::PlaceholderText, qc);
+ qc = qt_mac_toQColor([NSColor controlAccentColor]);
+ palette->setColor(QPalette::Accent, qc);
+
return palette;
}
@@ -221,6 +225,8 @@ QCocoaTheme::QCocoaTheme()
NSSystemColorsDidChangeNotification, [this] {
handleSystemThemeChange();
});
+
+ updateColorScheme();
}
QCocoaTheme::~QCocoaTheme()
@@ -239,6 +245,9 @@ void QCocoaTheme::reset()
void QCocoaTheme::handleSystemThemeChange()
{
reset();
+
+ updateColorScheme();
+
m_systemPalette = qt_mac_createSystemPalette();
m_palettes = qt_mac_createRolePalettes();
@@ -401,19 +410,8 @@ public:
QPlatformTheme::IconOptions opts) :
QAbstractFileIconEngine(info, opts) {}
- static QList<QSize> availableIconSizes()
- {
- const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
- const int sizes[] = {
- qRound(16 * devicePixelRatio), qRound(32 * devicePixelRatio),
- qRound(64 * devicePixelRatio), qRound(128 * devicePixelRatio),
- qRound(256 * devicePixelRatio)
- };
- return QAbstractFileIconEngine::toSizeList(sizes, sizes + sizeof(sizes) / sizeof(sizes[0]));
- }
-
QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
- { return QCocoaFileIconEngine::availableIconSizes(); }
+ { return QAppleIconEngine::availableIconSizes(); }
protected:
QPixmap filePixmap(const QSize &size, QIcon::Mode, QIcon::State) override
@@ -432,6 +430,11 @@ QIcon QCocoaTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptio
return QIcon(new QCocoaFileIconEngine(fileInfo, iconOptions));
}
+QIconEngine *QCocoaTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAppleIconEngine(iconName);
+}
+
QVariant QCocoaTheme::themeHint(ThemeHint hint) const
{
switch (hint) {
@@ -445,7 +448,7 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
return QVariant([[NSApplication sharedApplication] isFullKeyboardAccessEnabled] ?
int(Qt::TabFocusAllControls) : int(Qt::TabFocusTextControls | Qt::TabFocusListControls));
case IconPixmapSizes:
- return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes());
+ return QVariant::fromValue(QAppleIconEngine::availableIconSizes());
case QPlatformTheme::PasswordMaskCharacter:
return QVariant(QChar(0x2022));
case QPlatformTheme::UiEffects:
@@ -470,9 +473,21 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
return QPlatformTheme::themeHint(hint);
}
-Qt::Appearance QCocoaTheme::appearance() const
+Qt::ColorScheme QCocoaTheme::colorScheme() const
+{
+ return m_colorScheme;
+}
+
+/*
+ Update the theme's color scheme based on the current appearance.
+
+ We can only reference the appearance on the main thread, but the
+ CoreText font engine needs to know the color scheme, and might be
+ used from secondary threads, so we cache the color scheme.
+*/
+void QCocoaTheme::updateColorScheme()
{
- return qt_mac_applicationIsInDarkMode() ? Qt::Appearance::Dark : Qt::Appearance::Light;
+ m_colorScheme = qt_mac_applicationIsInDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
}
QString QCocoaTheme::standardButtonText(int button) const
@@ -490,12 +505,16 @@ QKeySequence QCocoaTheme::standardButtonShortcut(int button) const
QPlatformMenuItem *QCocoaTheme::createPlatformMenuItem() const
{
- return new QCocoaMenuItem();
+ auto *menuItem = new QCocoaMenuItem();
+ qCDebug(lcQpaMenus) << "Created" << menuItem;
+ return menuItem;
}
QPlatformMenu *QCocoaTheme::createPlatformMenu() const
{
- return new QCocoaMenu();
+ auto *menu = new QCocoaMenu();
+ qCDebug(lcQpaMenus) << "Created" << menu;
+ return menu;
}
QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const
@@ -508,7 +527,9 @@ QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const
SLOT(onAppFocusWindowChanged(QWindow*)));
}
- return new QCocoaMenuBar();
+ auto *menuBar = new QCocoaMenuBar();
+ qCDebug(lcQpaMenus) << "Created" << menuBar;
+ return menuBar;
}
#ifndef QT_NO_SHORTCUT
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h
index 0b0e6536c0..ba1bc052fb 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.h
+++ b/src/plugins/platforms/cocoa/qcocoawindow.h
@@ -158,18 +158,19 @@ public:
void setWindowCursor(NSCursor *cursor);
void registerTouch(bool enable);
- void setContentBorderThickness(int topThickness, int bottomThickness);
+
void registerContentBorderArea(quintptr identifier, int upper, int lower);
void setContentBorderAreaEnabled(quintptr identifier, bool enable);
void setContentBorderEnabled(bool enable) override;
bool testContentBorderAreaPosition(int position) const;
void applyContentBorderThickness(NSWindow *window = nullptr);
- void updateNSToolbar();
qreal devicePixelRatio() const override;
QWindow *childWindowAt(QPoint windowPoint);
bool shouldRefuseKeyWindowAndFirstResponder();
+ bool windowEvent(QEvent *event) override;
+
QPoint bottomLeftClippedByNSWindowOffset() const override;
void updateNormalGeometry();
@@ -219,32 +220,31 @@ public: // for QNSView
static void setupPopupMonitor();
static void removePopupMonitor();
- NSView *m_view;
- QCocoaNSWindow *m_nsWindow;
+ NSView *m_view = nil;
+ QCocoaNSWindow *m_nsWindow = nil;
- Qt::WindowStates m_lastReportedWindowState;
- Qt::WindowModality m_windowModality;
+ Qt::WindowStates m_lastReportedWindowState = Qt::WindowNoState;
+ Qt::WindowModality m_windowModality = Qt::NonModal;
static QPointer<QCocoaWindow> s_windowUnderMouse;
- bool m_initialized;
- bool m_inSetVisible;
- bool m_inSetGeometry;
- bool m_inSetStyleMask;
- QCocoaMenuBar *m_menubar;
+ bool m_initialized = false;
+ bool m_inSetVisible = false;
+ bool m_inSetGeometry = false;
+ bool m_inSetStyleMask = false;
+
+ QCocoaMenuBar *m_menubar = nullptr;
- bool m_frameStrutEventsEnabled;
+ bool m_frameStrutEventsEnabled = false;
QRect m_exposedRect;
QRect m_normalGeometry;
- int m_registerTouchCount;
- bool m_resizableTransientParent;
+ int m_registerTouchCount = 0;
+ bool m_resizableTransientParent = false;
static const int NoAlertRequest;
- NSInteger m_alertRequest;
+ NSInteger m_alertRequest = NoAlertRequest;
- bool m_drawContentBorderGradient;
- int m_topContentBorderThickness;
- int m_bottomContentBorderThickness;
+ bool m_drawContentBorderGradient = false;
struct BorderRange {
BorderRange(quintptr i, int u, int l) : identifier(i), upper(u), lower(l) { }
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index 7a0bf5a4cb..4a245a0f8a 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -98,24 +98,7 @@ Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks)
const int QCocoaWindow::NoAlertRequest = -1;
QPointer<QCocoaWindow> QCocoaWindow::s_windowUnderMouse;
-QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
- : QPlatformWindow(win)
- , m_view(nil)
- , m_nsWindow(nil)
- , m_lastReportedWindowState(Qt::WindowNoState)
- , m_windowModality(Qt::NonModal)
- , m_initialized(false)
- , m_inSetVisible(false)
- , m_inSetGeometry(false)
- , m_inSetStyleMask(false)
- , m_menubar(nullptr)
- , m_frameStrutEventsEnabled(false)
- , m_registerTouchCount(0)
- , m_resizableTransientParent(false)
- , m_alertRequest(NoAlertRequest)
- , m_drawContentBorderGradient(false)
- , m_topContentBorderThickness(0)
- , m_bottomContentBorderThickness(0)
+QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window();
@@ -134,16 +117,24 @@ void QCocoaWindow::initialize()
if (!m_view)
m_view = [[QNSView alloc] initWithCocoaWindow:this];
- // Compute the initial geometry based on the geometry set on the
- // QWindow. This geometry has already been reflected to the
- // QPlatformWindow in the constructor, so to ensure that the
- // resulting setGeometry call does not think the geometry has
- // already been applied, we reset the QPlatformWindow's view
- // of the geometry first.
- auto initialGeometry = QPlatformWindow::initialGeometry(window(),
- windowGeometry(), defaultWindowWidth, defaultWindowHeight);
- QPlatformWindow::d_ptr->rect = QRect();
- setGeometry(initialGeometry);
+ if (!isForeignWindow()) {
+ // Compute the initial geometry based on the geometry set on the
+ // QWindow. This geometry has already been reflected to the
+ // QPlatformWindow in the constructor, so to ensure that the
+ // resulting setGeometry call does not think the geometry has
+ // already been applied, we reset the QPlatformWindow's view
+ // of the geometry first.
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowWidth, defaultWindowHeight);
+ QPlatformWindow::d_ptr->rect = QRect();
+ setGeometry(initialGeometry);
+
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
recreateWindowIfNeeded();
@@ -159,7 +150,10 @@ QCocoaWindow::~QCocoaWindow()
QMacAutoReleasePool pool;
[m_nsWindow makeFirstResponder:nil];
[m_nsWindow setContentView:nil];
- if ([m_view superview])
+
+ // Remove from superview only if we have a Qt window parent,
+ // as we don't want to affect window container foreign windows.
+ if (QPlatformWindow::parent())
[m_view removeFromSuperview];
// Make sure to disconnect observer in all case if view is valid
@@ -183,8 +177,7 @@ QCocoaWindow::~QCocoaWindow()
object:m_view];
[m_view release];
- [m_nsWindow close];
- [m_nsWindow release];
+ [m_nsWindow closeAndRelease];
}
QSurfaceFormat QCocoaWindow::format() const
@@ -315,6 +308,31 @@ void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
+ // Our implementation of setVisible below is not idempotent, as for
+ // modal windows it calls beginSheet/endSheet or starts/ends modal
+ // sessions. However we can't simply guard for m_view.hidden already
+ // having the right state, as the behavior of this function differs
+ // based on whether the window has been initialized or not, as
+ // handleGeometryChange will bail out if the window is still
+ // initializing. Since we know we'll get a second setVisible
+ // call after creation, we can check for that case specifically,
+ // which means we can then safely guard on m_view.hidden changing.
+
+ if (!m_initialized) {
+ qCDebug(lcQpaWindow) << "Window still initializing, skipping setting visibility";
+ return; // We'll get another setVisible call after create is done
+ }
+
+ if (visible == !m_view.hidden && (!isContentView() || visible == m_view.window.visible)) {
+ qCDebug(lcQpaWindow) << "No change in visible status. Ignoring.";
+ return;
+ }
+
+ if (m_inSetVisible) {
+ qCWarning(lcQpaWindow) << "Already setting window visible!";
+ return;
+ }
+
QScopedValueRollback<bool> rollback(m_inSetVisible, true);
QMacAutoReleasePool pool;
@@ -353,6 +371,9 @@ void QCocoaWindow::setVisible(bool visible)
}
+ // Make the NSView visible first, before showing the NSWindow (in case of top level windows)
+ m_view.hidden = NO;
+
if (isContentView()) {
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
@@ -390,13 +411,6 @@ void QCocoaWindow::setVisible(bool visible)
}
}
}
-
- // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own
- // Cocoa window, and then shown again. Therefore, we test for the view being hidden even
- // if it's attached to an NSWindow.
- if ([m_view isHidden])
- [m_view setHidden:NO];
-
} else {
// Window not visible, hide it
if (isContentView()) {
@@ -425,10 +439,28 @@ void QCocoaWindow::setVisible(bool visible)
if (mainWindow && [mainWindow canBecomeKeyWindow])
[mainWindow makeKeyWindow];
}
- } else {
- [m_view setHidden:YES];
}
+ // AppKit will in some cases set up the key view loop for child views, even if we
+ // don't set autorecalculatesKeyViewLoop, nor call recalculateKeyViewLoop ourselves.
+ // When a child window is promoted to a top level, AppKit will maintain the key view
+ // loop between the views, even if these views now cross NSWindows, even after we
+ // explicitly call recalculateKeyViewLoop. When the top level is then hidden, AppKit
+ // will complain when -[NSView _setHidden:setNeedsDisplay:] tries to transfer first
+ // responder by reading the nextValidKeyView, and it turns out to live in a different
+ // window. We mitigate this by a last second reset of the first responder, which is
+ // what AppKit also falls back to. It's unclear if the original situation of views
+ // having their nextKeyView pointing to views in other windows is kosher or not.
+ if (m_view.window.firstResponder == m_view && m_view.nextValidKeyView
+ && m_view.nextValidKeyView.window != m_view.window) {
+ qCDebug(lcQpaWindow) << "Detected nextValidKeyView" << m_view.nextValidKeyView
+ << "in different window" << m_view.nextValidKeyView.window
+ << "Resetting" << m_view.window << "first responder to nil.";
+ [m_view.window makeFirstResponder:nil];
+ }
+
+ m_view.hidden = YES;
+
if (parentCocoaWindow && window()->type() == Qt::Popup) {
NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
if (m_resizableTransientParent
@@ -558,8 +590,6 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
if (!isContentView())
return;
- NSWindow *window = m_view.window;
-
static constexpr std::pair<NSWindowButton, Qt::WindowFlags> buttons[] = {
{ NSWindowCloseButton, Qt::WindowCloseButtonHint },
{ NSWindowMiniaturizeButton, Qt::WindowMinimizeButtonHint},
@@ -568,20 +598,38 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
bool hideButtons = true;
for (const auto &[button, buttonHint] : buttons) {
+ // Set up Qt defaults based on window type
bool enabled = true;
+ if (button == NSWindowMiniaturizeButton)
+ enabled = window()->type() != Qt::Dialog;
+
+ // Let users override via CustomizeWindowHint
if (windowFlags & Qt::CustomizeWindowHint)
enabled = windowFlags & buttonHint;
+ // Then do some final sanitizations
+
if (button == NSWindowZoomButton && isFixedSize())
enabled = false;
- [window standardWindowButton:button].enabled = enabled;
+ // Mimic what macOS natively does for parent windows of modal
+ // sheets, which is to disable the close button, but leave the
+ // other buttons as they were.
+ if (button == NSWindowCloseButton && enabled
+ && QWindowPrivate::get(window())->blockedByModalWindow) {
+ enabled = false;
+ // If we end up having no enabled buttons, our workaround
+ // should not be a reason for hiding all of them.
+ hideButtons = false;
+ }
+
+ [m_view.window standardWindowButton:button].enabled = enabled;
hideButtons &= !enabled;
}
// Hide buttons in case we disabled all of them
for (const auto &[button, buttonHint] : buttons)
- [window standardWindowButton:button].hidden = hideButtons;
+ [m_view.window standardWindowButton:button].hidden = hideButtons;
}
void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
@@ -1198,32 +1246,33 @@ void QCocoaWindow::windowDidEndLiveResize()
void QCocoaWindow::windowDidBecomeKey()
{
- if (!isContentView())
+ // The NSWindow we're part of become key. Check if we're the first
+ // responder, and if so, deliver focus window change to our window.
+ if (m_view.window.firstResponder != m_view)
return;
- if (isForeignWindow())
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "became key window."
+ << "Updating focus window to" << this << "with view" << m_view;
- QNSView *firstResponderView = qt_objc_cast<QNSView *>(m_view.window.firstResponder);
- if (!firstResponderView)
- return;
-
- const QCocoaWindow *focusCocoaWindow = firstResponderView.platformWindow;
- if (focusCocoaWindow->windowIsPopupType())
+ if (windowIsPopupType()) {
+ qCDebug(lcQpaWindow) << "Window is popup. Skipping focus window change.";
return;
+ }
// See also [QNSView becomeFirstResponder]
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
- focusCocoaWindow->window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
+ window(), Qt::ActiveWindowFocusReason);
}
void QCocoaWindow::windowDidResignKey()
{
- if (!isContentView())
+ // The NSWindow we're part of lost key. Check if we're the first
+ // responder, and if so, deliver window deactivation to our window.
+ if (m_view.window.firstResponder != m_view)
return;
- if (isForeignWindow())
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "resigned key window."
+ << "Clearing focus window" << this << "with view" << m_view;
// Make sure popups are closed before we deliver activation changes, which are
// otherwise ignored by QApplication.
@@ -1235,12 +1284,14 @@ void QCocoaWindow::windowDidResignKey()
NSWindow *newKeyWindow = [NSApp keyWindow];
if (newKeyWindow && newKeyWindow != m_view.window
&& [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) {
+ qCDebug(lcQpaWindow) << "New key window" << newKeyWindow
+ << "is Qt window. Deferring focus window change.";
return;
}
// Lost key window, go ahead and set the active window to zero
if (!windowIsPopupType()) {
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
nullptr, Qt::ActiveWindowFocusReason);
}
}
@@ -1277,8 +1328,14 @@ void QCocoaWindow::windowDidOrderOffScreen()
void QCocoaWindow::windowDidChangeOcclusionState()
{
+ // Note, we don't take the view's hiddenOrHasHiddenAncestor state into
+ // account here, but instead leave that up to handleExposeEvent, just
+ // like all the other signals that could potentially change the exposed
+ // state of the window.
bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
- qCDebug(lcQpaWindow) << "QCocoaWindow::windowDidChangeOcclusionState" << window() << "is now" << (visible ? "visible" : "occluded");
+ qCDebug(lcQpaWindow) << "Occlusion state of" << m_view.window << "for"
+ << window() << "changed to" << (visible ? "visible" : "occluded");
+
if (visible)
[m_view setNeedsDisplay:YES];
else
@@ -1446,14 +1503,30 @@ void QCocoaWindow::recreateWindowIfNeeded()
QMacAutoReleasePool pool;
QPlatformWindow *parentWindow = QPlatformWindow::parent();
-
- const bool isEmbeddedView = isEmbedded();
- RecreationReasons recreateReason = RecreationNotNeeded;
+ auto *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
QCocoaWindow *oldParentCocoaWindow = nullptr;
if (QNSView *qnsView = qnsview_cast(m_view.superview))
oldParentCocoaWindow = qnsView.platformWindow;
+ if (isForeignWindow()) {
+ // A foreign window is created as such, and can never move between being
+ // foreign and not, so we don't need to get rid of any existing NSWindows,
+ // nor create new ones, as a foreign window is a single simple NSView.
+ qCDebug(lcQpaWindow) << "Skipping NSWindow management for foreign window" << this;
+
+ // We do however need to manage the parent relationship
+ if (parentCocoaWindow)
+ [parentCocoaWindow->m_view addSubview:m_view];
+ else if (oldParentCocoaWindow)
+ [m_view removeFromSuperview];
+
+ return;
+ }
+
+ const bool isEmbeddedView = isEmbedded();
+ RecreationReasons recreateReason = RecreationNotNeeded;
+
if (parentWindow != oldParentCocoaWindow)
recreateReason |= ParentChanged;
@@ -1484,8 +1557,6 @@ void QCocoaWindow::recreateWindowIfNeeded()
if (recreateReason == RecreationNotNeeded)
return;
- QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
-
// Remove current window (if any)
if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) {
if (m_nsWindow) {
@@ -1515,31 +1586,10 @@ void QCocoaWindow::recreateWindowIfNeeded()
}
}
- if (isEmbeddedView) {
- // An embedded window doesn't have its own NSWindow.
- } else if (!parentWindow) {
- // QPlatformWindow subclasses must sync up with QWindow on creation:
- propagateSizeHints();
- setWindowFlags(window()->flags());
- setWindowTitle(window()->title());
- setWindowFilePath(window()->filePath()); // Also sets window icon
- setWindowState(window()->windowState());
- } else {
+ if (parentCocoaWindow) {
// Child windows have no NSWindow, re-parent to superview instead
[parentCocoaWindow->m_view addSubview:m_view];
- [m_view setHidden:!window()->isVisible()];
}
-
- const qreal opacity = qt_window_private(window())->opacity;
- if (!qFuzzyCompare(opacity, qreal(1.0)))
- setOpacity(opacity);
-
- setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
-
- // top-level QWindows may have an attached NSToolBar, call
- // update function which will attach to the NSWindow.
- if (!parentWindow && !isEmbeddedView)
- updateNSToolbar();
}
void QCocoaWindow::requestUpdate()
@@ -1548,7 +1598,10 @@ void QCocoaWindow::requestUpdate()
<< "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
if (updatesWithDisplayLink()) {
- static_cast<QCocoaScreen *>(screen())->requestUpdate();
+ if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
+ qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
+ QPlatformWindow::requestUpdate();
+ }
} else {
// Fall back to the un-throttled timer-based callback
QPlatformWindow::requestUpdate();
@@ -1742,8 +1795,9 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
// Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
- // Make popup windows show on the same desktop as the parent full-screen window
- nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
+ // Make popup windows show on the same desktop as the parent window
+ nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
+ | NSWindowCollectionBehaviorMoveToActiveSpace;
if ((type & Qt::Popup) == Qt::Popup) {
nsWindow.hasShadow = YES;
@@ -1758,11 +1812,15 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
applyContentBorderThickness(nsWindow);
- if (QColorSpace colorSpace = format().colorSpace(); colorSpace.isValid()) {
- NSData *iccData = colorSpace.iccProfile().toNSData();
- nsWindow.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease];
- qCDebug(lcQpaDrawing) << "Set" << this << "color space to" << nsWindow.colorSpace;
- }
+ // We propagate the view's color space granulary to both the IOSurfaces
+ // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
+ // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
+ // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
+ // workaround we set the NSWindow's color space, which affects GL drawing
+ // with NSOpenGLContext as well. This does not conflict with the granular
+ // modifications we do to each surface for raster or Metal.
+ if (auto *qtView = qnsview_cast(m_view))
+ nsWindow.colorSpace = qtView.colorSpace;
return nsWindow;
}
@@ -1805,26 +1863,28 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor)
view.cursor = cursor;
+ // We're not using the the legacy cursor rects API to manage our
+ // cursor, but calling this function also invalidates AppKit's
+ // view of whether or not we need a cursorUpdate callback for
+ // our tracking area.
[m_view.window invalidateCursorRectsForView:m_view];
- if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSMonterey) {
- // There's a bug in AppKit where calling invalidateCursorRectsForView when
- // there's an override cursor active (for example when hovering over the
- // window frame), will not result in a cursorUpdate: callback. To work around
- // this we synthesize a cursor update event and call the callback ourselves.
- // We only do this is if the window would normally receive cursor updates.
- auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
- auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
- bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
- auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
- bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
- if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
- qCDebug(lcQpaMouse) << "Synthesizing cursor update";
- [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
- location:locationInWindow modifierFlags:0 timestamp:0
- windowNumber:m_view.window.windowNumber context:nil
- eventNumber:0 trackingNumber:0 userData:0]];
- }
+ // We've informed AppKit that we need a cursorUpdate, but cursor
+ // updates for tracking areas are deferred in some cases, such as
+ // when the mouse is down, whereas we want a synchronous update.
+ // To ensure an updated cursor we synthesize a cursor update event
+ // now if the window is otherwise allowed to change the cursor.
+ auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
+ auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
+ bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
+ auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
+ bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
+ if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
+ qCDebug(lcQpaMouse) << "Synthesizing cursor update";
+ [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
+ location:locationInWindow modifierFlags:0 timestamp:0
+ windowNumber:m_view.window.windowNumber context:nil
+ eventNumber:0 trackingNumber:0 userData:0]];
}
}
@@ -1837,16 +1897,6 @@ void QCocoaWindow::registerTouch(bool enable)
m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
}
-void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickness)
-{
- m_topContentBorderThickness = topThickness;
- m_bottomContentBorderThickness = bottomThickness;
- bool enable = (topThickness > 0 || bottomThickness > 0);
- m_drawContentBorderGradient = enable;
-
- applyContentBorderThickness();
-}
-
void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower)
{
m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower));
@@ -1883,7 +1933,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
// Find consecutive registered border areas, starting from the top.
std::vector<BorderRange> ranges(m_contentBorderAreas.cbegin(), m_contentBorderAreas.cend());
std::sort(ranges.begin(), ranges.end());
- int effectiveTopContentBorderThickness = m_topContentBorderThickness;
+ int effectiveTopContentBorderThickness = 0;
for (BorderRange range : ranges) {
// Skip disiabled ranges (typically hidden tool bars)
if (!m_enabledContentBorderAreas.value(range.identifier, false))
@@ -1898,7 +1948,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
break;
}
- int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness;
+ int effectiveBottomContentBorderThickness = 0;
[window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground];
window.titlebarAppearsTransparent = YES;
@@ -1920,21 +1970,6 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
[[[window contentView] superview] setNeedsDisplay:YES];
}
-void QCocoaWindow::updateNSToolbar()
-{
- if (!isContentView())
- return;
-
- NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window());
- const NSWindow *window = m_view.window;
-
- if (window.toolbar == toolbar)
- return;
-
- window.toolbar = toolbar;
- window.showsToolbarButton = YES;
-}
-
bool QCocoaWindow::testContentBorderAreaPosition(int position) const
{
if (!m_drawContentBorderGradient || !isContentView())
@@ -1954,7 +1989,7 @@ qreal QCocoaWindow::devicePixelRatio() const
{
// The documented way to observe the relationship between device-independent
// and device pixels is to use one for the convertToBacking functions. Other
- // methods such as [NSWindow backingScaleFacor] might not give the correct
+ // methods such as [NSWindow backingScaleFactor] might not give the correct
// result, for example if setWantsBestResolutionOpenGLSurface is not set or
// or ignored by the OpenGL driver.
NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
@@ -1981,6 +2016,22 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
return true;
+ // For application modal windows, as well as direct parent windows
+ // of window modal windows, AppKit takes care of blocking interaction.
+ // The Qt expectation however, is that all transient parents of a
+ // window modal window is blocked, as reflected by QGuiApplication.
+ // We reflect this by returning false from this function for transient
+ // parents blocked by a modal window, but limit it to the cases not
+ // covered by AppKit to avoid potential unwanted side effects.
+ QWindow *modalWindow = nullptr;
+ if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
+ if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
+ qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
+ << "blocked by" << modalWindow;
+ return true;
+ }
+ }
+
if (m_inSetVisible) {
QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
@@ -1990,6 +2041,20 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
return false;
}
+bool QCocoaWindow::windowEvent(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::WindowBlocked:
+ case QEvent::WindowUnblocked:
+ updateTitleBarButtons(window()->flags());
+ break;
+ default:
+ break;
+ }
+
+ return QPlatformWindow::windowEvent(event);
+}
+
QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
{
if (!m_view)
diff --git a/src/plugins/platforms/cocoa/qmacclipboard.h b/src/plugins/platforms/cocoa/qmacclipboard.h
index 030732446f..95267565f2 100644
--- a/src/plugins/platforms/cocoa/qmacclipboard.h
+++ b/src/plugins/platforms/cocoa/qmacclipboard.h
@@ -7,6 +7,8 @@
#include <QtGui>
#include <QtGui/qutimimeconverter.h>
+#include <QtCore/qpointer.h>
+
#include <ApplicationServices/ApplicationServices.h>
QT_BEGIN_NAMESPACE
@@ -48,9 +50,9 @@ private:
static OSStatus promiseKeeper(PasteboardRef, PasteboardItemID, CFStringRef, void *);
void clear_helper();
public:
- QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScope::All);
+ QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All);
QMacPasteboard(QUtiMimeConverter::HandlerScope scope);
- QMacPasteboard(CFStringRef name=nullptr, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScope::All);
+ QMacPasteboard(CFStringRef name=nullptr, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All);
~QMacPasteboard();
bool hasUti(const QString &uti) const;
diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm
index 3a39fdfb0f..edafa3b6a1 100644
--- a/src/plugins/platforms/cocoa/qmacclipboard.mm
+++ b/src/plugins/platforms/cocoa/qmacclipboard.mm
@@ -111,7 +111,7 @@ QMacPasteboard::~QMacPasteboard()
Commit all promises for paste when shutting down,
unless we are the stack-allocated clipboard used by QCocoaDrag.
*/
- if (scope == QUtiMimeConverter::HandlerScope::DnD)
+ if (scope == QUtiMimeConverter::HandlerScopeFlag::DnD)
resolvingBeforeDestruction = true;
PasteboardResolvePromises(paste);
if (paste)
@@ -130,7 +130,7 @@ OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id,
const long promise_id = (long)id;
// Find the kept promise
- const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScope::All);
+ const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
const QString utiAsQString = QString::fromCFString(uti);
QMacPasteboard::Promise promise;
for (int i = 0; i < qpaste->promises.size(); i++){
diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h
index e41f5a7296..7f845a5c3b 100644
--- a/src/plugins/platforms/cocoa/qnsview.h
+++ b/src/plugins/platforms/cocoa/qnsview.h
@@ -32,6 +32,12 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QNSView, NSView
- (void)cancelComposingText;
@end
+Q_FORWARD_DECLARE_OBJC_CLASS(NSColorSpace);
+
+@interface QNSView (DrawingAPI)
+@property (nonatomic, readonly) NSColorSpace *colorSpace;
+@end
+
@interface QNSView (QtExtras)
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
@end
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index c574e2b7c8..48ffa5c1cc 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -5,6 +5,7 @@
#include <AppKit/AppKit.h>
#include <MetalKit/MetalKit.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include "qnsview.h"
#include "qcocoawindow.h"
@@ -36,13 +37,6 @@
#include "qcocoaintegration.h"
#include <QtGui/private/qmacmimeregistry_p.h>
-// Private interface
-@interface QNSView ()
-- (BOOL)isTransparentForUserInput;
-@property (assign) NSView* previousSuperview;
-@property (assign) NSWindow* previousWindow;
-@end
-
@interface QNSView (Drawing) <CALayerDelegate>
- (void)initDrawing;
@end
@@ -83,6 +77,21 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
@end
@interface QNSView (ComplexText) <NSTextInputClient>
+@property (readonly) QObject* focusObject;
+@end
+
+@interface QT_MANGLE_NAMESPACE(QNSViewMenuHelper) : NSObject
+- (instancetype)initWithView:(QNSView *)theView;
+@end
+QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper);
+
+// Private interface
+@interface QNSView ()
+- (BOOL)isTransparentForUserInput;
+@property (assign) NSView* previousSuperview;
+@property (assign) NSWindow* previousWindow;
+@property (retain) QNSViewMenuHelper* menuHelper;
+@property (nonatomic, retain) NSColorSpace *colorSpace;
@end
@implementation QNSView {
@@ -112,11 +121,20 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
NSDraggingContext m_lastSeenContext;
}
+@synthesize colorSpace = m_colorSpace;
+
- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow
{
if ((self = [super initWithFrame:NSZeroRect])) {
m_platformWindow = platformWindow;
+ // NSViews are by default visible, but QWindows are not.
+ // We should ideally pick up the actual QWindow state here,
+ // but QWindowPrivate::setVisible() expects to control the
+ // order of events tightly, so we need to wait for a call
+ // to QCocoaWindow::setVisible().
+ self.hidden = YES;
+
self.focusRingType = NSFocusRingTypeNone;
self.previousSuperview = nil;
@@ -132,6 +150,8 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
m_sendKeyEvent = false;
m_currentlyInterpretedKeyEvent = nil;
m_lastSeenContext = NSDraggingContextWithinApplication;
+
+ self.menuHelper = [[[QNSViewMenuHelper alloc] initWithView:self] autorelease];
}
return self;
}
@@ -257,15 +277,29 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
return focusWindow;
}
+/*
+ Invoked when the view is hidden, either directly,
+ or in response to an ancestor being hidden.
+*/
- (void)viewDidHide
{
+ qCDebug(lcQpaWindow) << "Did hide" << self;
+
if (!m_platformWindow->isExposed())
return;
m_platformWindow->handleExposeEvent(QRegion());
+}
+
+/*
+ Invoked when the view is unhidden, either directly,
+ or in response to an ancestor being unhidden.
+*/
+- (void)viewDidUnhide
+{
+ qCDebug(lcQpaWindow) << "Did unhide" << self;
- // Note: setNeedsDisplay is automatically called for
- // viewDidUnhide so no reason to override it here.
+ [self setNeedsDisplay:YES];
}
- (BOOL)isTransparentForUserInput
@@ -298,7 +332,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
// QWindow activation from QCocoaWindow::windowDidBecomeKey instead. The only
// exception is if the window can never become key, in which case we naturally
// cannot wait for that to happen.
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
[self topLevelWindow], Qt::ActiveWindowFocusReason);
}
diff --git a/src/plugins/platforms/cocoa/qnsview_accessibility.mm b/src/plugins/platforms/cocoa/qnsview_accessibility.mm
index 3f3898fd18..e781f21a6c 100644
--- a/src/plugins/platforms/cocoa/qnsview_accessibility.mm
+++ b/src/plugins/platforms/cocoa/qnsview_accessibility.mm
@@ -13,6 +13,15 @@
@implementation QNSView (Accessibility)
+- (void)activateQtAccessibility
+{
+ // Activate the Qt accessibility machinery for all entry points
+ // below that may be triggered by system accessibility queries,
+ // as otherwise Qt is not aware that the system needs to know
+ // about all accessibility state changes in Qt.
+ QCocoaIntegration::instance()->accessibility()->setActive(true);
+}
+
- (id)childAccessibleElement
{
QCocoaWindow *platformWindow = self.platformWindow;
@@ -32,8 +41,7 @@
- (id)accessibilityAttributeValue:(NSString *)attribute
{
- // activate accessibility updates
- QCocoaIntegration::instance()->accessibility()->setActive(true);
+ [self activateQtAccessibility];
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
return NSAccessibilityUnignoredChildrenForOnlyChild([self childAccessibleElement]);
@@ -43,11 +51,13 @@
- (id)accessibilityHitTest:(NSPoint)point
{
+ [self activateQtAccessibility];
return [[self childAccessibleElement] accessibilityHitTest:point];
}
- (id)accessibilityFocusedUIElement
{
+ [self activateQtAccessibility];
return [[self childAccessibleElement] accessibilityFocusedUIElement];
}
diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm
index cdeb7154fe..d7f8f4baf0 100644
--- a/src/plugins/platforms/cocoa/qnsview_complextext.mm
+++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm
@@ -7,6 +7,17 @@
// ------------- Text insertion -------------
+- (QObject*)focusObject
+{
+ // The text input system may still hold a reference to our QNSView,
+ // even after QCocoaWindow has been destructed, delivering text input
+ // events to us, so we need to guard for this situation explicitly.
+ if (!m_platformWindow)
+ return nullptr;
+
+ return m_platformWindow->window()->focusObject();
+}
+
/*
Inserts the given text, potentially replacing existing text.
@@ -52,8 +63,7 @@
}
}
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (queryInputMethod(focusObject)) {
+ if (queryInputMethod(self.focusObject)) {
QInputMethodEvent inputMethodEvent;
const bool isAttributedString = [text isKindOfClass:NSAttributedString.class];
@@ -75,7 +85,7 @@
inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength);
}
- QCoreApplication::sendEvent(focusObject, &inputMethodEvent);
+ QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
}
m_composingText.clear();
@@ -86,6 +96,9 @@
{
Q_UNUSED(sender);
+ if (!m_platformWindow)
+ return;
+
// Depending on the input method, pressing enter may
// result in simply dismissing the input method editor,
// without confirming the composition. In other cases
@@ -117,8 +130,8 @@
newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return;
newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode)
: QLatin1Char(kReturnCharCode);
- newlineEvent.nativeVirtualKey = isEnter ? kVK_ANSI_KeypadEnter
- : kVK_Return;
+ newlineEvent.nativeVirtualKey = isEnter ? quint32(kVK_ANSI_KeypadEnter)
+ : quint32(kVK_Return);
qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent;
newlineEvent.sendWindowSystemEvent(m_platformWindow->window());
@@ -242,7 +255,7 @@
// Update the composition, now that we've computed the replacement range
m_composingText = preeditString;
- if (QObject *focusObject = m_platformWindow->window()->focusObject()) {
+ if (QObject *focusObject = self.focusObject) {
m_composingFocusObject = focusObject;
if (queryInputMethod(focusObject)) {
QInputMethodEvent event(preeditString, preeditAttributes);
@@ -284,8 +297,7 @@
*/
- (NSRange)markedRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject, Qt::ImAbsolutePosition)) {
+ if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) {
int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
// The cursor position as reflected by Qt::ImAbsolutePosition is not
@@ -320,7 +332,7 @@
<< "for focus object" << m_composingFocusObject;
if (!m_composingText.isEmpty()) {
- QObject *focusObject = m_platformWindow->window()->focusObject();
+ QObject *focusObject = self.focusObject;
if (queryInputMethod(focusObject)) {
QInputMethodEvent e;
e.setCommitString(m_composingText);
@@ -393,8 +405,7 @@
*/
- (NSRange)selectedRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject,
+ if (auto queryResult = queryInputMethod(self.focusObject,
Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) {
// Unfortunately the Qt::InputMethodQuery values are all relative
@@ -441,8 +452,7 @@
*/
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject,
+ if (auto queryResult = queryInputMethod(self.focusObject,
Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) {
const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString();
@@ -478,8 +488,8 @@
Q_UNUSED(range);
Q_UNUSED(actualRange);
- QWindow *window = m_platformWindow->window();
- if (queryInputMethod(window->focusObject())) {
+ QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
+ if (window && queryInputMethod(window->focusObject())) {
QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect();
cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft()));
return QCocoaScreen::mapToNative(cursorRect);
@@ -495,6 +505,32 @@
return NSNotFound;
}
+/*
+ Returns the window level of the text input.
+
+ This allows the input method to place its input panel
+ above the text input.
+*/
+- (NSInteger)windowLevel
+{
+ // The default level assumed by input methods is NSFloatingWindowLevel,
+ // but our NSWindow level could be higher than that for many reasons,
+ // including being set via QWindow::setFlags() or directly on the
+ // NSWindow, or because we're embedded into a native view hierarchy.
+ // Return the actual window level to account for this.
+ auto level = m_platformWindow ? m_platformWindow->nativeWindow().level
+ : NSNormalWindowLevel;
+
+ // The logic above only covers our own window though. In some cases,
+ // such as when a completer is active, the text input has a lower
+ // window level than another window that's also visible, and we don't
+ // want the input panel to be sandwiched between these two windows.
+ // Account for this by explicitly using NSPopUpMenuWindowLevel as
+ // the minimum window level, which corresponds to the highest level
+ // one can get via QWindow::setFlags(), except for Qt::ToolTip.
+ return qMax(level, NSPopUpMenuWindowLevel);
+}
+
// ------------- Helper functions -------------
/*
diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm
index f5bb25c300..4f7d35a0d6 100644
--- a/src/plugins/platforms/cocoa/qnsview_dragging.mm
+++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm
@@ -16,8 +16,8 @@
NSPasteboardTypeRTF, NSPasteboardTypeTabularText, NSPasteboardTypeFont,
NSPasteboardTypeRuler, NSFileContentsPboardType,
NSPasteboardTypeRTFD , NSPasteboardTypeHTML,
- NSPasteboardTypeURL, NSPasteboardTypePDF, (NSString *)kUTTypeVCard,
- (NSString *)kPasteboardTypeFileURLPromise, (NSString *)kUTTypeInkText,
+ NSPasteboardTypeURL, NSPasteboardTypePDF, UTTypeVCard.identifier,
+ (NSString *)kPasteboardTypeFileURLPromise,
NSPasteboardTypeMultipleTextSelection, mimeTypeGeneric]];
// Add custom types supported by the application
diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm
index 472a5291e7..bf102e43f8 100644
--- a/src/plugins/platforms/cocoa/qnsview_drawing.mm
+++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm
@@ -13,6 +13,14 @@
<< " QT_MAC_WANTS_LAYER/_q_mac_wantsLayer has no effect.";
}
+ // Pick up and persist requested color space from surface format
+ const QSurfaceFormat surfaceFormat = m_platformWindow->format();
+ if (QColorSpace colorSpace = surfaceFormat.colorSpace(); colorSpace.isValid()) {
+ NSData *iccData = colorSpace.iccProfile().toNSData();
+ self.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease];
+ }
+
+ // Trigger creation of the layer
self.wantsLayer = YES;
}
@@ -28,6 +36,12 @@
return YES;
}
+- (NSColorSpace*)colorSpace
+{
+ // If no explicit color space was set, use the NSWindow's color space
+ return m_colorSpace ? m_colorSpace : self.window.colorSpace;
+}
+
// ----------------------- Layer setup -----------------------
- (BOOL)shouldUseMetalLayer
@@ -93,12 +107,7 @@
[super setLayer:layer];
- // When adding a view to a view hierarchy the backing properties will change
- // which results in updating the contents scale, but in case of switching the
- // layer on a view that's already in a view hierarchy we need to manually ensure
- // the scale is up to date.
- if (self.superview)
- [self updateLayerContentsScale];
+ [self propagateBackingProperties];
if (self.opaque && lcQpaDrawing().isDebugEnabled()) {
// If the view claims to be opaque we expect it to fill the entire
@@ -131,8 +140,7 @@
{
qCDebug(lcQpaDrawing) << "Backing properties changed for" << self;
- if (self.layer)
- [self updateLayerContentsScale];
+ [self propagateBackingProperties];
// Ideally we would plumb this situation through QPA in a way that lets
// clients invalidate their own caches, recreate QBackingStore, etc.
@@ -141,8 +149,11 @@
[self setNeedsDisplay:YES];
}
-- (void)updateLayerContentsScale
+- (void)propagateBackingProperties
{
+ if (!self.layer)
+ return;
+
// We expect clients to fill the layer with retina aware content,
// based on the devicePixelRatio of the QWindow, so we set the
// layer's content scale to match that. By going via devicePixelRatio
@@ -153,6 +164,12 @@
auto devicePixelRatio = m_platformWindow->devicePixelRatio();
qCDebug(lcQpaDrawing) << "Updating" << self.layer << "content scale to" << devicePixelRatio;
self.layer.contentsScale = devicePixelRatio;
+
+ if ([self.layer isKindOfClass:CAMetalLayer.class]) {
+ CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer);
+ metalLayer.colorspace = self.colorSpace.CGColorSpace;
+ qCDebug(lcQpaDrawing) << "Set" << metalLayer << "color space to" << metalLayer.colorspace;
+ }
}
/*
@@ -178,8 +195,10 @@
- (void)drawRect:(NSRect)dirtyBoundingRect
{
Q_UNUSED(dirtyBoundingRect);
- Q_ASSERT_X(!self.layer, "QNSView",
- "The drawRect code path should not be hit when we are layer backed");
+ // As we are layer backed we shouldn't really end up here, but AppKit will
+ // in some cases call this method just because we implement it.
+ // FIXME: Remove drawRect and switch from displayLayer to updateLayer
+ qCWarning(lcQpaDrawing) << "[QNSView drawRect] called for layer backed view";
}
/*
diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm
index 118678ffa5..abee622e65 100644
--- a/src/plugins/platforms/cocoa/qnsview_keys.mm
+++ b/src/plugins/platforms/cocoa/qnsview_keys.mm
@@ -30,8 +30,36 @@ static bool isSpecialKey(const QString &text)
return false;
}
+static bool sendAsShortcut(const KeyEvent &keyEvent, QWindow *window)
+{
+ KeyEvent shortcutEvent = keyEvent;
+ shortcutEvent.type = QEvent::Shortcut;
+ qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
+ << "for" << shortcutEvent;
+
+ if (shortcutEvent.sendWindowSystemEvent(window)) {
+ qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
+ return true;
+ }
+ qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
+ return false;
+}
+
@implementation QNSView (Keys)
+- (bool)performKeyEquivalent:(NSEvent *)nsevent
+{
+ // Implemented to handle shortcuts for modified Tab keys, which are
+ // handled by Cocoa and not delivered to your keyDown implementation.
+ if (nsevent.type == NSEventTypeKeyDown && m_composingText.isEmpty()) {
+ const bool ctrlDown = [nsevent modifierFlags] & NSEventModifierFlagControl;
+ const bool isTabKey = nsevent.keyCode == kVK_Tab;
+ if (ctrlDown && isTabKey && sendAsShortcut(KeyEvent(nsevent), [self topLevelWindow]))
+ return YES;
+ }
+ return NO;
+}
+
- (bool)handleKeyEvent:(NSEvent *)nsevent
{
qCDebug(lcQpaKeys) << "Handling" << nsevent;
@@ -52,17 +80,8 @@ static bool isSpecialKey(const QString &text)
if (keyEvent.type == QEvent::KeyPress) {
if (m_composingText.isEmpty()) {
- KeyEvent shortcutEvent = keyEvent;
- shortcutEvent.type = QEvent::Shortcut;
- qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
- << "for" << shortcutEvent;
-
- if (shortcutEvent.sendWindowSystemEvent(window)) {
- qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
+ if (sendAsShortcut(keyEvent, window))
return true;
- } else {
- qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
- }
}
QObject *focusObject = m_platformWindow ? m_platformWindow->window()->focusObject() : nullptr;
@@ -94,7 +113,10 @@ static bool isSpecialKey(const QString &text)
qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject;
m_currentlyInterpretedKeyEvent = nsevent;
- [self interpretKeyEvents:@[nsevent]];
+ if (![self.inputContext handleEvent:nsevent]) {
+ qCDebug(lcQpaKeys) << "Input context did not consume event";
+ m_sendKeyEvent = true;
+ }
m_currentlyInterpretedKeyEvent = 0;
didInterpretKeyEvent = true;
diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm
index 7f2b3d387c..2840936975 100644
--- a/src/plugins/platforms/cocoa/qnsview_menus.mm
+++ b/src/plugins/platforms/cocoa/qnsview_menus.mm
@@ -9,22 +9,56 @@
#include "qcocoamenu.h"
#include "qcocoamenubar.h"
-static bool selectorIsCutCopyPaste(SEL selector)
+@implementation QNSView (Menus)
+
+// Qt does not (yet) have a mechanism for propagating generic actions,
+// so we can only support actions that originate from a QCocoaNSMenuItem,
+// where we can forward the action by emitting QPlatformMenuItem::activated().
+// But waiting for forwardInvocation to check that the sender is a
+// QCocoaNSMenuItem is too late, as AppKit has at that point chosen
+// our view as the target for the action, and if we can't handle it
+// the action will not propagate up the responder chain as it should.
+// Instead, we hook in early in the process of determining the target
+// via the supplementalTargetForAction API, and if we can support the
+// action we forward it to a helper. The helper must be tied to the
+// view, as the menu validation logic depends on the view's state.
+
+- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
{
- return (selector == @selector(cut:)
- || selector == @selector(copy:)
- || selector == @selector(paste:)
- || selector == @selector(selectAll:));
+ qCDebug(lcQpaMenus) << "Resolving action target for" << action << "from" << sender << "via" << self;
+
+ if (qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
+ // The supplemental target must support the selector, but we
+ // determine so dynamically, so check here before continuing.
+ if ([self.menuHelper respondsToSelector:action])
+ return self.menuHelper;
+ } else {
+ qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create";
+ }
+
+ return [super supplementalTargetForAction:action sender:sender];
}
-@interface QNSView (Menus)
-- (void)qt_itemFired:(QCocoaNSMenuItem *)item;
@end
-@implementation QNSView (Menus)
+@interface QNSViewMenuHelper ()
+@property (assign) QNSView* view;
+@end
+
+@implementation QNSViewMenuHelper
+
+- (instancetype)initWithView:(QNSView *)theView
+{
+ if ((self = [super init]))
+ self.view = theView;
+
+ return self;
+}
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
+ qCDebug(lcQpaMenus) << "Validating" << item << "for" << self.view;
+
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
if (!nativeItem)
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
@@ -51,7 +85,7 @@ static bool selectorIsCutCopyPaste(SEL selector)
}
if ((!menuWindow || menuWindow->window() != QGuiApplication::modalWindow())
- && (!menubar || menubar->cocoaWindow() != self.platformWindow))
+ && (!menubar || menubar->cocoaWindow() != self.view.platformWindow))
return NO;
}
@@ -60,41 +94,42 @@ static bool selectorIsCutCopyPaste(SEL selector)
- (BOOL)respondsToSelector:(SEL)selector
{
- // Not exactly true. Both copy: and selectAll: can work on non key views.
- if (selectorIsCutCopyPaste(selector))
- return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self);
+ // See QCocoaMenuItem::resolveTargetAction()
+
+ if (selector == @selector(cut:)
+ || selector == @selector(copy:)
+ || selector == @selector(paste:)
+ || selector == @selector(selectAll:)) {
+ // Not exactly true. Both copy: and selectAll: can work on non key views.
+ return NSApp.keyWindow == self.view.window
+ && self.view.window.firstResponder == self.view;
+ }
- return [super respondsToSelector:selector];
-}
+ if (selector == @selector(qt_itemFired:))
+ return YES;
-- (void)qt_itemFired:(QCocoaNSMenuItem *)item
-{
- auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
- [appDelegate qt_itemFired:item];
+ return [super respondsToSelector:selector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
- if (selectorIsCutCopyPaste(selector)) {
- NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)];
- return itemFiredSign;
- }
+ // Double check, in case something has cached that we respond
+ // to the selector, but the result has changed since then.
+ if (![self respondsToSelector:selector])
+ return nil;
- return [super methodSignatureForSelector:selector];
+ auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
+ return [appDelegate methodSignatureForSelector:@selector(qt_itemFired:)];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
- if (selectorIsCutCopyPaste(invocation.selector)) {
- NSObject *sender;
- [invocation getArgument:&sender atIndex:2];
- if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
- [self qt_itemFired:nativeItem];
- return;
- }
- }
-
- [super forwardInvocation:invocation];
+ NSObject *sender;
+ [invocation getArgument:&sender atIndex:2];
+ qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender;
+ Q_ASSERT(qt_objc_cast<QCocoaNSMenuItem *>(sender));
+ invocation.selector = @selector(qt_itemFired:);
+ [invocation invokeWithTarget:[QCocoaApplicationDelegate sharedDelegate]];
}
@end
diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm
index 89125ca91f..2fd57fe68e 100644
--- a/src/plugins/platforms/cocoa/qnsview_mouse.mm
+++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm
@@ -209,20 +209,19 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
case NSEventTypeOtherMouseUp:
return QEvent::NonClientAreaMouseButtonRelease;
+ case NSEventTypeMouseMoved:
case NSEventTypeLeftMouseDragged:
case NSEventTypeRightMouseDragged:
case NSEventTypeOtherMouseDragged:
return QEvent::NonClientAreaMouseMove;
default:
- break;
+ Q_UNREACHABLE();
}
-
- return QEvent::None;
}();
qCInfo(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window;
- QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(),
+ QWindowSystemInterface::handleMouseEvent(m_platformWindow->window(),
timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType);
}
@end
@@ -484,10 +483,6 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
- (void)cursorUpdate:(NSEvent *)theEvent
{
- // Note: We do not get this callback when moving from a subview that
- // uses the legacy cursorRect API, so the cursor is reset to the arrow
- // cursor. See rdar://34183708
-
if (!NSApp.active)
return;
@@ -548,6 +543,30 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
[self handleMouseEvent: theEvent];
}
+- (BOOL)shouldPropagateMouseEnterExit
+{
+ Q_ASSERT(m_platformWindow);
+
+ // We send out enter and leave events mainly from mouse move events (mouseMovedImpl),
+ // but in some case (see mouseEnteredImpl:) we also want to propagate enter/leave
+ // events from the platform. We only do this for windows that themselves are not
+ // handled by another parent QWindow.
+
+ if (m_platformWindow->isContentView())
+ return true;
+
+ // Windows manually embedded into a native view does not have a QWindow parent
+ if (m_platformWindow->isEmbedded())
+ return true;
+
+ // Windows embedded via fromWinId do, but the parent isn't a QNSView
+ QPlatformWindow *parentWindow = m_platformWindow->QPlatformWindow::parent();
+ if (parentWindow && parentWindow->isForeignWindow())
+ return true;
+
+ return false;
+}
+
- (void)mouseEnteredImpl:(NSEvent *)theEvent
{
Q_UNUSED(theEvent);
@@ -571,8 +590,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
// in time (s_windowUnderMouse). The latter is also used to also send out enter/leave
// events when the application is activated/deactivated.
- // Root (top level or embedded) windows generate enter events for sub-windows
- if (!m_platformWindow->isContentView() && !m_platformWindow->isEmbedded())
+ if (![self shouldPropagateMouseEnterExit])
return;
QPointF windowPoint;
@@ -598,8 +616,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
if (!m_platformWindow)
return;
- // Root (top level or embedded) windows generate enter events for sub-windows
- if (!m_platformWindow->isContentView() && !m_platformWindow->isEmbedded())
+ if (![self shouldPropagateMouseEnterExit])
return;
QCocoaWindow *windowToLeave = QCocoaWindow::s_windowUnderMouse;
diff --git a/src/plugins/platforms/cocoa/qnsview_touch.mm b/src/plugins/platforms/cocoa/qnsview_touch.mm
index 6a147701fc..97ed5b7624 100644
--- a/src/plugins/platforms/cocoa/qnsview_touch.mm
+++ b/src/plugins/platforms/cocoa/qnsview_touch.mm
@@ -25,7 +25,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesMovedWithEvent:(NSEvent *)event
@@ -36,7 +39,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesEndedWithEvent:(NSEvent *)event
@@ -47,7 +53,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesCancelledWithEvent:(NSEvent *)event
@@ -58,7 +67,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
@end
diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h
index f69e809133..8f842eba85 100644
--- a/src/plugins/platforms/cocoa/qnswindow.h
+++ b/src/plugins/platforms/cocoa/qnswindow.h
@@ -36,6 +36,8 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow;
+QCocoaNSWindow *qnswindow_cast(NSWindow *window);
+
#else
class QCocoaNSWindow;
#endif // __OBJC__
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index 8d4a0617de..74ba6f65ac 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -58,6 +58,15 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
+
+NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window)
+{
+ if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return static_cast<QCocoaNSWindow *>(window);
+ else
+ return nil;
+}
+
@implementation QNSWindow
#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
#include "qnswindow.mm"
@@ -98,9 +107,10 @@ static bool isMouseEvent(NSEvent *ev)
continue;
if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
- QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
- window.level = notification.name == NSApplicationWillResignActiveNotification ?
- NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ if (QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow) {
+ window.level = notification.name == NSApplicationWillResignActiveNotification ?
+ NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ }
}
// The documentation says that "when a window enters a new level, it’s ordered
@@ -167,45 +177,6 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
-#if !defined(QT_APPLE_NO_PRIVATE_APIS)
-// When creating an NSWindow the worksWhenModal function is queried,
-// and the resulting state is used to set the corresponding window tag,
-// which the window server uses to determine whether or not the window
-// should be allowed to activate via mouse clicks in the title-bar.
-// Unfortunately, prior to macOS 10.15, this window tag was never
-// updated after the initial assignment in [NSWindow _commonAwake],
-// which meant that windows that dynamically change their worksWhenModal
-// state will behave as if they were never allowed to work when modal.
-// We work around this by manually updating the window tag when needed.
-
-typedef uint32_t CGSConnectionID;
-typedef uint32_t CGSWindowID;
-
-extern "C" {
-CGSConnectionID CGSMainConnectionID() __attribute__((weak_import));
-OSStatus CGSSetWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-}
-
-@interface QNSPanel (WorksWhenModalWindowTagWorkaround) @end
-@implementation QNSPanel (WorksWhenModalWindowTagWorkaround)
-- (void)setWorksWhenModal:(BOOL)worksWhenModal
-{
- [super setWorksWhenModal:worksWhenModal];
-
- if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSCatalina) {
- if (CGSMainConnectionID && CGSSetWindowTags && CGSClearWindowTags) {
- static int kWorksWhenModalWindowTag = 0x40;
- auto *function = worksWhenModal ? CGSSetWindowTags : CGSClearWindowTags;
- function(CGSMainConnectionID(), self.windowNumber, &kWorksWhenModalWindowTag, 64);
- } else {
- qWarning() << "Missing APIs for window tag handling, can not update worksWhenModal state";
- }
- }
-}
-@end
-#endif // QT_APPLE_NO_PRIVATE_APIS
-
#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
// The following content is mixed in to the QNSWindow and QNSPanel classes via includes
@@ -235,6 +206,29 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return m_platformWindow;
}
+- (void)setContentView:(NSView*)view
+{
+ [super setContentView:view];
+
+ if (!qnsview_cast(self.contentView))
+ return;
+
+ // Now that we're the content view, we can apply the properties of
+ // the QWindow. We do this here, instead of in init, so that we can
+ // use the same code paths for setting these properties during
+ // NSWindow initialization as we do when setting them later on.
+ const QWindow *window = m_platformWindow->window();
+ qCDebug(lcQpaWindow) << "Reflecting" << window << "state to" << self;
+
+ m_platformWindow->propagateSizeHints();
+ m_platformWindow->setWindowFlags(window->flags());
+ m_platformWindow->setWindowTitle(window->title());
+ m_platformWindow->setWindowFilePath(window->filePath()); // Also sets window icon
+ m_platformWindow->setWindowState(window->windowState());
+ m_platformWindow->setOpacity(window->opacity());
+ m_platformWindow->setVisible(window->isVisible());
+}
+
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
index 2d90fbf544..1db7772771 100644
--- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm
+++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
@@ -23,9 +23,7 @@ static inline bool isWhiteSpace(const QString &s)
static QCocoaWindow *toPlatformWindow(NSWindow *window)
{
- if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
- return static_cast<QCocoaNSWindow *>(window).platformWindow;
- return nullptr;
+ return qnswindow_cast(window).platformWindow;
}
@implementation QNSWindowDelegate
diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt
index d0ab0d1efd..54e96b09e5 100644
--- a/src/plugins/platforms/direct2d/CMakeLists.txt
+++ b/src/plugins/platforms/direct2d/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from direct2d.pro.
-
#####################################################################
## QWindowsDirect2DIntegrationPlugin Plugin:
#####################################################################
@@ -14,11 +12,11 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
../windows/qtwindowsglobal.h
../windows/qwin10helpers.cpp ../windows/qwin10helpers.h
../windows/qwindowsapplication.cpp ../windows/qwindowsapplication.h
- ../windows/qwindowscombase.h
../windows/qwindowscontext.cpp ../windows/qwindowscontext.h
../windows/qwindowscursor.cpp ../windows/qwindowscursor.h
../windows/qwindowsdialoghelpers.cpp ../windows/qwindowsdialoghelpers.h
../windows/qwindowsdropdataobject.cpp ../windows/qwindowsdropdataobject.h
+ ../windows/qwindowsiconengine.cpp ../windows/qwindowsiconengine.h
../windows/qwindowsinputcontext.cpp ../windows/qwindowsinputcontext.h
../windows/qwindowsintegration.cpp ../windows/qwindowsintegration.h
../windows/qwindowsinternalmimedata.cpp ../windows/qwindowsinternalmimedata.h
@@ -47,6 +45,8 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
qwindowsdirect2dplatformpixmap.cpp qwindowsdirect2dplatformpixmap.h
qwindowsdirect2dplatformplugin.cpp
qwindowsdirect2dwindow.cpp qwindowsdirect2dwindow.h
+ NO_UNITY_BUILD_SOURCES
+ ../windows/qwindowspointerhandler.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_FOREACH
@@ -58,16 +58,17 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
advapi32
- d2d1 # special case
+ d2d1
d3d11
dwmapi
- dwrite # special case
+ dwrite
dxgi
dxguid
gdi32
imm32
ole32
oleaut32
+ setupapi
shell32
shlwapi
user32
@@ -96,15 +97,9 @@ qt_internal_add_resource(QWindowsDirect2DIntegrationPlugin "openglblacklists"
${openglblacklists_resource_files}
)
-#### Keys ignored in scope 1:.:.:direct2d.pro:<TRUE>:
-# OTHER_FILES = "direct2d.json"
-
## Scopes:
#####################################################################
-#### Keys ignored in scope 2:.:.:direct2d.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
-
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl
LIBRARIES
opengl32
@@ -113,6 +108,8 @@ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW
LIBRARIES
uuid
+ NO_PCH_SOURCES
+ ../windows/qwindowspointerhandler.cpp
)
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl
@@ -191,6 +188,7 @@ endif()
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_accessibility
SOURCES
+ ../windows/uiautomation/qwindowsuiautomation.cpp ../windows/uiautomation/qwindowsuiautomation.h
../windows/uiautomation/qwindowsuiaaccessibility.cpp ../windows/uiautomation/qwindowsuiaaccessibility.h
../windows/uiautomation/qwindowsuiabaseprovider.cpp ../windows/uiautomation/qwindowsuiabaseprovider.h
../windows/uiautomation/qwindowsuiaexpandcollapseprovider.cpp ../windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -212,13 +210,18 @@ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE
../windows/uiautomation/qwindowsuiawindowprovider.cpp ../windows/uiautomation/qwindowsuiawindowprovider.h
)
+if(QT_FEATURE_accessibility)
+ find_library(UI_AUTOMATION_LIBRARY uiautomationcore)
+ if(UI_AUTOMATION_LIBRARY)
+ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin
+ LIBRARIES
+ ${UI_AUTOMATION_LIBRARY}
+ )
+ endif()
+endif()
+
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility
LIBRARIES
uuid
)
-# begin special case
-if (MINGW)
- set_source_files_properties(../windows/qwindowspointerhandler.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
-endif()
-# end special case
diff --git a/src/plugins/platforms/directfb/CMakeLists.txt b/src/plugins/platforms/directfb/CMakeLists.txt
index f8cfbdef30..7222168a37 100644
--- a/src/plugins/platforms/directfb/CMakeLists.txt
+++ b/src/plugins/platforms/directfb/CMakeLists.txt
@@ -1,12 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from directfb.pro.
-
-# begin special case:
qt_find_package(DirectFB)
qt_find_package(EGL)
-# end special case:
#####################################################################
## QDirectFbIntegrationPlugin Plugin:
@@ -28,16 +24,13 @@ qt_internal_add_plugin(QDirectFbIntegrationPlugin
qdirectfbwindow.cpp qdirectfbwindow.h
LIBRARIES
PkgConfig::DirectFB
- EGL::EGL # special case
+ EGL::EGL
Qt::Core
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:directfb.pro:<TRUE>:
-# OTHER_FILES = "directfb.json"
-
## Scopes:
#####################################################################
@@ -53,13 +46,7 @@ qt_internal_extend_target(QDirectFbIntegrationPlugin CONDITION NOT DIRECTFB_PLAT
DIRECTFB_PLATFORM_HOOKS
)
-#### Keys ignored in scope 3:.:.:directfb.pro:NOT DIRECTFB_PLATFORM_HOOKS_SOURCES_ISEMPTY:
-# QMAKE_LIBDIR = "$$DIRECTFB_PLATFORM_HOOKS_LIBDIR"
-
qt_internal_extend_target(QDirectFbIntegrationPlugin CONDITION DIRECTFB_PLATFORM_HOOKS_SOURCES_ISEMPTY
SOURCES
qdirectfbeglhooks_stub.cpp
)
-
-#### Keys ignored in scope 5:.:.:directfb.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/directfb/qdirectfbblitter.cpp b/src/plugins/platforms/directfb/qdirectfbblitter.cpp
index aac0e3d800..c93e48ead7 100644
--- a/src/plugins/platforms/directfb/qdirectfbblitter.cpp
+++ b/src/plugins/platforms/directfb/qdirectfbblitter.cpp
@@ -424,7 +424,7 @@ void QDirectFbBlitter::drawDebugRect(const QRect &rect, const QColor &color)
void QDirectFbTextureGlyphCache::resizeTextureData(int width, int height)
{
- m_surface.reset();;
+ m_surface.reset();
QImageTextureGlyphCache::resizeTextureData(width, height);
}
diff --git a/src/plugins/platforms/directfb/qdirectfbconvenience.h b/src/plugins/platforms/directfb/qdirectfbconvenience.h
index c013988fe2..dc657f384e 100644
--- a/src/plugins/platforms/directfb/qdirectfbconvenience.h
+++ b/src/plugins/platforms/directfb/qdirectfbconvenience.h
@@ -62,7 +62,7 @@ template <typename T>
class QDirectFBPointer : public QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >
{
public:
- QDirectFBPointer(T *t = nullptr)
+ Q_NODISCARD_CTOR QDirectFBPointer(T *t = nullptr)
: QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >(t)
{}
diff --git a/src/plugins/platforms/directfb/qdirectfbinput.cpp b/src/plugins/platforms/directfb/qdirectfbinput.cpp
index 516dc1e9d8..c5aacad6cc 100644
--- a/src/plugins/platforms/directfb/qdirectfbinput.cpp
+++ b/src/plugins/platforms/directfb/qdirectfbinput.cpp
@@ -176,7 +176,7 @@ void QDirectFbInput::handleEnterLeaveEvents(const DFBEvent &event)
void QDirectFbInput::handleGotFocusEvent(const DFBEvent &event)
{
QWindow *tlw = m_tlwMap.value(event.window.window_id);
- QWindowSystemInterface::handleWindowActivated(tlw, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(tlw, Qt::ActiveWindowFocusReason);
}
void QDirectFbInput::handleCloseEvent(const DFBEvent &event)
diff --git a/src/plugins/platforms/eglfs/CMakeLists.txt b/src/plugins/platforms/eglfs/CMakeLists.txt
index 108900518c..a0a6116a45 100644
--- a/src/plugins/platforms/eglfs/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs.pro.
-# special case begin
qt_find_package(EGL)
if(QT_FEATURE_eglfs_gbm)
@@ -18,7 +16,6 @@ elseif(QT_FEATURE_eglfs_openwfd)
endif()
set(QT_QPA_DEFAULT_EGLFS_INTEGRATION "${_device_integration}" CACHE STRING "Default EGLFS device integration plugin")
-# special case end
#####################################################################
## EglFSDeviceIntegrationPrivate Module:
@@ -37,7 +34,8 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate
DEFINES
QT_BUILD_EGL_DEVICE_LIB
QT_EGL_NO_X11
- EGLFS_PREFERRED_PLUGIN=${QT_QPA_DEFAULT_EGLFS_INTEGRATION} # special case
+ QT_NO_FOREACH
+ EGLFS_PREFERRED_PLUGIN=${QT_QPA_DEFAULT_EGLFS_INTEGRATION}
INCLUDE_DIRECTORIES
api
PUBLIC_LIBRARIES
@@ -45,14 +43,12 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate
Qt::DeviceDiscoverySupportPrivate
Qt::FbSupportPrivate
Qt::GuiPrivate
- EGL::EGL # special case
+ EGL::EGL
HEADER_SYNC_SOURCE_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}/api"
+ NO_GENERATE_CPP_EXPORTS
)
-#### Keys ignored in scope 2:.:.:eglfsdeviceintegration.pro:<TRUE>:
-# MODULE = "eglfsdeviceintegration"
-
## Scopes:
#####################################################################
@@ -74,19 +70,6 @@ qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION QT_FEATURE_ope
Qt::OpenGLPrivate
)
-# special case begin
-# comment out
-#qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION NOT EGLFS_PLATFORM_HOOKS_SOURCES_ISEMPTY
- #DEFINES
- #EGLFS_PLATFORM_HOOKS
-#)
-
-#qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION NOT EGLFS_DEVICE_INTEGRATION_ISEMPTY
- #DEFINES
- #EGLFS_PREFERRED_PLUGIN=
-#)
-# special case end
-
if(QT_FEATURE_cursor)
# Resources:
set(cursor_resource_files
@@ -109,23 +92,15 @@ endif()
qt_internal_add_plugin(QEglFSIntegrationPlugin
OUTPUT_NAME qeglfs
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES eglfs # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES eglfs
SOURCES
qeglfsmain.cpp
DEFINES
QT_EGL_NO_X11
LIBRARIES
- Qt::CorePrivate # special case
+ Qt::CorePrivate
Qt::EglFSDeviceIntegrationPrivate
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 12:.:.:eglfs-plugin.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs.json"
-
-## Scopes:
-#####################################################################
-
-#### Keys ignored in scope 13:.:.:eglfs-plugin.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(deviceintegration)
diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
index 391442de71..1e1a7d8269 100644
--- a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
@@ -368,6 +368,8 @@ struct StateSaver
f->glGetIntegerv(GL_BLEND_SRC_ALPHA, blendFunc + 1);
f->glGetIntegerv(GL_BLEND_DST_RGB, blendFunc + 2);
f->glGetIntegerv(GL_BLEND_DST_ALPHA, blendFunc + 3);
+ scissor = f->glIsEnabled(GL_SCISSOR_TEST);
+ stencil = f->glIsEnabled(GL_STENCIL_TEST);
f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBuf);
if (vaoHelper->isValid())
f->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vao);
@@ -391,17 +393,15 @@ struct StateSaver
f->glFrontFace(frontFace);
if (cull)
f->glEnable(GL_CULL_FACE);
- else
- f->glDisable(GL_CULL_FACE);
if (depthTest)
f->glEnable(GL_DEPTH_TEST);
- else
- f->glDisable(GL_DEPTH_TEST);
- if (blend)
- f->glEnable(GL_BLEND);
- else
+ if (!blend)
f->glDisable(GL_BLEND);
f->glBlendFuncSeparate(blendFunc[0], blendFunc[1], blendFunc[2], blendFunc[3]);
+ if (scissor)
+ f->glEnable(GL_SCISSOR_TEST);
+ if (stencil)
+ f->glEnable(GL_STENCIL_TEST);
f->glBindBuffer(GL_ARRAY_BUFFER, arrayBuf);
if (vaoHelper->isValid())
vaoHelper->glBindVertexArray(vao);
@@ -426,6 +426,8 @@ struct StateSaver
bool depthTest;
bool blend;
GLint blendFunc[4];
+ bool scissor;
+ bool stencil;
GLint vao;
GLint arrayBuf;
struct { GLint enabled, type, size, normalized, stride, buffer; GLvoid *pointer; } va[2];
@@ -505,6 +507,8 @@ void QEglFSCursor::draw(const QRectF &r)
f->glEnable(GL_BLEND);
f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
f->glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top
+ f->glDisable(GL_SCISSOR_TEST);
+ f->glDisable(GL_STENCIL_TEST);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
index 268b9aa093..56fda45e90 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
@@ -65,7 +65,7 @@ static int framebuffer = -1;
QByteArray QEglFSDeviceIntegration::fbDeviceName() const
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
QByteArray fbDev = qgetenv("QT_QPA_EGLFS_FB");
if (fbDev.isEmpty())
fbDev = QByteArrayLiteral("/dev/fb0");
@@ -95,7 +95,7 @@ int QEglFSDeviceIntegration::framebufferIndex() const
void QEglFSDeviceIntegration::platformInit()
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
QByteArray fbDev = fbDeviceName();
framebuffer = qt_safe_open(fbDev, O_RDONLY);
@@ -113,7 +113,7 @@ void QEglFSDeviceIntegration::platformInit()
void QEglFSDeviceIntegration::platformDestroy()
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
if (framebuffer != -1)
close(framebuffer);
#endif
diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
index 42cbdf127c..f0b64c475c 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
@@ -110,7 +110,8 @@ void QEglFSIntegration::initialize()
void QEglFSIntegration::destroy()
{
- foreach (QWindow *w, qGuiApp->topLevelWindows())
+ const auto toplevels = qGuiApp->topLevelWindows();
+ for (QWindow *w : toplevels)
w->destroy();
qt_egl_device_integration()->screenDestroy();
@@ -386,6 +387,14 @@ QFunctionPointer QEglFSIntegration::platformFunction(const QByteArray &function)
return qt_egl_device_integration()->platformFunction(function);
}
+QVariant QEglFSIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
+{
+ if (hint == QPlatformIntegration::ShowIsFullScreen)
+ return true;
+
+ return QPlatformIntegration::styleHint(hint);
+}
+
#if QT_CONFIG(evdev)
void QEglFSIntegration::loadKeymap(const QString &filename)
{
diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
index 3e4c57197e..8007167ec7 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
+++ b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
@@ -76,6 +76,8 @@ public:
QFunctionPointer platformFunction(const QByteArray &function) const override;
+ QVariant styleHint(QPlatformIntegration::StyleHint hint) const override;
+
QFbVtHandler *vtHandler() { return m_vtHandler.data(); }
QPointer<QWindow> pointerWindow() { return m_pointerWindow; }
diff --git a/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp b/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
index 72c4a47dbb..c5108be04a 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
@@ -189,7 +189,7 @@ QPixmap QEglFSScreen::grabWindow(WId wid, int x, int y, int width, int height) c
return QPixmap::fromImage(img).copy(x, y, width, height);
}
- foreach (QOpenGLCompositorWindow *w, windows) {
+ for (QOpenGLCompositorWindow *w : windows) {
const QWindow *window = w->sourceWindow();
if (window->winId() == wid) {
const QRect geom = window->geometry();
diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
index 1a31f97d7b..306d121cfb 100644
--- a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
@@ -263,7 +263,7 @@ void QEglFSWindow::requestActivateWindow()
QOpenGLCompositor::instance()->moveToTop(this);
#endif
QWindow *wnd = window();
- QWindowSystemInterface::handleWindowActivated(wnd, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(wnd, Qt::ActiveWindowFocusReason);
QWindowSystemInterface::handleExposeEvent(wnd, QRect(QPoint(0, 0), wnd->geometry().size()));
}
diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow_p.h b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
index d111042040..e51cd69f3d 100644
--- a/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
+++ b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
@@ -70,8 +70,8 @@ public:
bool isRaster() const;
#ifndef QT_NO_OPENGL
- QOpenGLCompositorBackingStore *backingStore() { return m_backingStore; }
- void setBackingStore(QOpenGLCompositorBackingStore *backingStore);
+ QOpenGLCompositorBackingStore *backingStore() const override { return m_backingStore; }
+ void setBackingStore(QOpenGLCompositorBackingStore *backingStore) override;
QWindow *sourceWindow() const override;
const QPlatformTextureList *textures() const override;
void endCompositing() override;
diff --git a/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
index 7637d373a2..bb50216f23 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from deviceintegration.pro.
-
if(QT_FEATURE_eglfs_x11)
add_subdirectory(eglfs_x11)
endif()
@@ -16,10 +14,10 @@ if(QT_FEATURE_eglfs_egldevice)
add_subdirectory(eglfs_kms_egldevice)
endif()
if(QT_FEATURE_eglfs_vsp2)
- # add_subdirectory(eglfs_kms_vsp2) # special case TODO
+ # add_subdirectory(eglfs_kms_vsp2) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_brcm)
- # add_subdirectory(eglfs_brcm) # special case TODO
+ # add_subdirectory(eglfs_brcm) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_mali)
add_subdirectory(eglfs_mali)
@@ -28,7 +26,7 @@ if(QT_FEATURE_eglfs_viv)
add_subdirectory(eglfs_viv)
endif()
if(QT_FEATURE_eglfs_rcar)
- # add_subdirectory(eglfs_rcar) # special case TODO
+ # add_subdirectory(eglfs_rcar) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_viv_wl)
add_subdirectory(eglfs_viv_wl)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
index e6eaeedd14..fa744be033 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_emu.pro.
-
#####################################################################
## QEglFSEmulatorIntegrationPlugin Plugin:
#####################################################################
@@ -25,7 +23,3 @@ qt_internal_add_plugin(QEglFSEmulatorIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_emu.pro:<TRUE>:
-# DISTFILES = "eglfs_emu.json"
-# OTHER_FILES = "$$PWD/eglfs_emu.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
index 939ed6b108..9f9d315202 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_kms.pro.
-
#####################################################################
## EglFsKmsGbmSupportPrivate Module:
#####################################################################
@@ -26,6 +24,7 @@ qt_internal_add_module(EglFsKmsGbmSupportPrivate
Qt::GuiPrivate
Qt::KmsSupportPrivate
gbm::gbm
+ NO_GENERATE_CPP_EXPORTS
)
#####################################################################
## QEglFSKmsGbmIntegrationPlugin Plugin:
@@ -48,6 +47,3 @@ qt_internal_add_plugin(QEglFSKmsGbmIntegrationPlugin
Qt::KmsSupportPrivate
gbm::gbm
)
-
-#### Keys ignored in scope 3:.:.:eglfs_kms-plugin.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_kms.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
index e6fd627823..8dcfed04db 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
@@ -73,8 +73,9 @@ QEglFSKmsGbmScreen::FrameBuffer *QEglFSKmsGbmScreen::framebufferForBufferObject(
return nullptr;
}
- gbm_bo_set_user_data(bo, fb.get(), bufferDestroyedHandler);
- return fb.release();
+ auto res = fb.get();
+ gbm_bo_set_user_data(bo, fb.release(), bufferDestroyedHandler);
+ return res;
}
QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QEglFSKmsDevice *device, const QKmsOutput &output, bool headless)
@@ -142,11 +143,12 @@ gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
}
}
+ const uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format);
+
// Fallback for older drivers, and when "format" is explicitly specified
// in the output config. (not guaranteed that the requested format works
// of course, but do what we are told to)
if (!m_gbm_surface) {
- uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format);
if (queryFromEgl)
qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x", gbmFormat);
m_gbm_surface = gbm_surface_create(gbmDevice,
@@ -155,16 +157,39 @@ gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
gbmFormat,
gbmFlags());
}
+
+ // Fallback for some drivers, its required to request with modifiers
+ if (!m_gbm_surface) {
+ uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
+
+ m_gbm_surface = gbm_surface_create_with_modifiers(gbmDevice,
+ rawGeometry().width(),
+ rawGeometry().height(),
+ gbmFormat,
+ &modifier, 1);
+ }
+ // Fail here, as it would fail with the next usage of the GBM surface, which is very unexpected
+ if (!m_gbm_surface)
+ qFatal("Could not create GBM surface!");
}
return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface()
}
void QEglFSKmsGbmScreen::resetSurface()
{
- m_flipPending = false;
+ m_flipPending = false; // not necessarily true but enough to keep bo_next
m_gbm_bo_current = nullptr;
- m_gbm_bo_next = nullptr;
m_gbm_surface = nullptr;
+
+ // Leave m_gbm_bo_next untouched. waitForFlip() should
+ // still do its work, when called. Otherwise we end up
+ // in device-is-busy errors if there is a new QWindow
+ // created afterwards. (QTBUG-122663)
+
+ // If not using atomic, will need a new drmModeSetCrtc if a new window
+ // gets created later on (and so there's a new fb).
+ if (!device()->hasAtomicSupport())
+ needsNewModeSetForNextFb = true;
}
void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones,
@@ -194,8 +219,9 @@ void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
QKmsOutput &op(output());
const int fd = device()->fd();
- if (!op.mode_set) {
+ if (!op.mode_set || needsNewModeSetForNextFb) {
op.mode_set = true;
+ needsNewModeSetForNextFb = false;
bool doModeSet = true;
drmModeCrtcPtr currentMode = drmModeGetCrtc(fd, op.crtc_id);
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
index 3660f094d2..aca34fcae2 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
@@ -83,6 +83,8 @@ protected:
bool cloneFlipPending = false;
};
QList<CloneDestination> m_cloneDests;
+
+ bool needsNewModeSetForNextFb = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
index 9fdd7d36fb..3c3e5a8a6c 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_kms_egldevice.pro.
-
#####################################################################
## QEglFSKmsEglDeviceIntegrationPlugin Plugin:
#####################################################################
@@ -27,6 +25,3 @@ qt_internal_add_plugin(QEglFSKmsEglDeviceIntegrationPlugin
Qt::GuiPrivate
Qt::KmsSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_kms_egldevice.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_kms_egldevice.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
index 9ada2de2b6..a213bc9bba 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
@@ -38,8 +38,11 @@ EGLDisplay QEglFSKmsEglDeviceIntegration::createDisplay(EGLNativeDisplayType nat
EGLDisplay display;
+ EGLint egldevice_fd = device()->fd();
+
+ const EGLint attribs[] = { EGL_DRM_MASTER_FD_EXT, egldevice_fd, EGL_NONE };
if (m_funcs->has_egl_platform_device) {
- display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, nullptr);
+ display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, attribs);
} else {
qWarning("EGL_EXT_platform_device not available, falling back to legacy path!");
display = eglGetDisplay(nativeDisplay);
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
index d4e5f2f90c..3fc46cd224 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
@@ -13,11 +13,62 @@ Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
QEglFSKmsEglDeviceScreen::QEglFSKmsEglDeviceScreen(QEglFSKmsDevice *device, const QKmsOutput &output)
: QEglFSKmsScreen(device, output)
+ , m_default_fb_handle(uint32_t(-1))
+ , m_default_fb_id(uint32_t(-1))
{
+ const int fd = device->fd();
+
+ struct drm_mode_create_dumb createRequest;
+ createRequest.width = output.size.width();
+ createRequest.height = output.size.height();
+ createRequest.bpp = 32;
+ createRequest.flags = 0;
+
+ qCDebug(qLcEglfsKmsDebug, "Creating dumb fb %dx%d", createRequest.width, createRequest.height);
+
+ int ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &createRequest);
+ if (ret < 0)
+ qFatal("Unable to create dumb buffer.\n");
+
+ m_default_fb_handle = createRequest.handle;
+
+ uint32_t handles[4] = { 0, 0, 0, 0 };
+ uint32_t pitches[4] = { 0, 0, 0, 0 };
+ uint32_t offsets[4] = { 0, 0, 0, 0 };
+
+ handles[0] = createRequest.handle;
+ pitches[0] = createRequest.pitch;
+ offsets[0] = 0;
+
+ ret = drmModeAddFB2(fd, createRequest.width, createRequest.height, DRM_FORMAT_ARGB8888, handles,
+ pitches, offsets, &m_default_fb_id, 0);
+ if (ret)
+ qFatal("Unable to add fb\n");
+
+ qCDebug(qLcEglfsKmsDebug, "Added dumb fb %dx%d handle:%u pitch:%d id:%u", createRequest.width, createRequest.height,
+ createRequest.handle, createRequest.pitch, m_default_fb_id);
}
QEglFSKmsEglDeviceScreen::~QEglFSKmsEglDeviceScreen()
{
+ int ret;
+ const int fd = device()->fd();
+
+ if (m_default_fb_id != uint32_t(-1)) {
+ ret = drmModeRmFB(fd, m_default_fb_id);
+ if (ret)
+ qErrnoWarning("drmModeRmFB failed");
+ }
+
+ if (m_default_fb_handle != uint32_t(-1)) {
+ struct drm_mode_destroy_dumb destroyRequest;
+ destroyRequest.handle = m_default_fb_handle;
+
+ ret = drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyRequest);
+ if (ret)
+ qErrnoWarning("DRM_IOCTL_MODE_DESTROY_DUMB failed");
+ }
+
const int remainingScreenCount = qGuiApp->screens().size();
qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d", remainingScreenCount);
if (!remainingScreenCount && !device()->screenConfig()->separateScreens())
@@ -53,8 +104,11 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
if (alreadySet) {
// Maybe detecting the DPMS mode could help here, but there are no properties
// exposed on the connector apparently. So rely on an env var for now.
- static bool alwaysDoSet = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ALWAYS_SET_MODE");
- if (!alwaysDoSet) {
+ // Note that typically, we need to set crtc with the default fb even if the
+ // mode did not change, unless QT_QPA_EGLFS_ALWAYS_SET_MODE is explicitly specified.
+ static bool envVarSet = false;
+ static bool alwaysDoSet = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ALWAYS_SET_MODE", &envVarSet);
+ if (envVarSet && !alwaysDoSet) {
qCDebug(qLcEglfsKmsDebug, "Mode already set");
return;
}
@@ -62,7 +116,7 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
qCDebug(qLcEglfsKmsDebug, "Setting mode");
int ret = drmModeSetCrtc(fd, op.crtc_id,
- uint32_t(-1), 0, 0,
+ m_default_fb_id, 0, 0,
&op.connector_id, 1,
&op.modes[op.mode]);
if (ret)
@@ -74,7 +128,7 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
if (op.wants_forced_plane) {
qCDebug(qLcEglfsKmsDebug, "Setting plane %u", op.forced_plane_id);
- int ret = drmModeSetPlane(fd, op.forced_plane_id, op.crtc_id, uint32_t(-1), 0,
+ int ret = drmModeSetPlane(fd, op.forced_plane_id, op.crtc_id, m_default_fb_id, 0,
0, 0, w, h,
0 << 16, 0 << 16, op.size.width() << 16, op.size.height() << 16);
if (ret == -1)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
index 884a70093a..8779499bef 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
@@ -17,6 +17,9 @@ public:
QPlatformCursor *cursor() const override;
void waitForFlip() override;
+private:
+ uint32_t m_default_fb_handle;
+ uint32_t m_default_fb_id;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
index 02c62041ff..5fcc0e1b18 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_kms_support.pro.
-
#####################################################################
## EglFsKmsSupportPrivate Module:
#####################################################################
@@ -26,4 +24,5 @@ qt_internal_add_module(EglFsKmsSupportPrivate
Qt::Gui
Qt::GuiPrivate
Qt::KmsSupportPrivate
+ NO_GENERATE_CPP_EXPORTS
)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
index 44555b85b5..5d2900097e 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
@@ -200,7 +200,7 @@ bool QLinuxMediaDevice::resetLinks()
struct media_link *QLinuxMediaDevice::parseLink(const QString &link)
{
- char *endp = nullptr;;
+ char *endp = nullptr;
struct media_link *mediaLink = media_parse_link(m_mediaDevice, link.toStdString().c_str(), &endp);
if (!mediaLink)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
index ebd20d2e7b..38981f87b9 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_mali.pro.
-
#####################################################################
## QEglFSMaliIntegrationPlugin Plugin:
#####################################################################
@@ -24,6 +22,3 @@ qt_internal_add_plugin(QEglFSMaliIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_mali.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_mali.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
index 068317d63d..7e2362e627 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from openwfd.pro.
-
#####################################################################
## QEglFSOpenWFDIntegrationPlugin Plugin:
#####################################################################
@@ -22,6 +20,3 @@ qt_internal_add_plugin(QEglFSOpenWFDIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:openwfd.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_openwfd.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
index 7d61eec257..01defc9242 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_viv.pro.
-
#####################################################################
## QEglFSVivIntegrationPlugin Plugin:
#####################################################################
@@ -25,6 +23,3 @@ qt_internal_add_plugin(QEglFSVivIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_viv.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_viv.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
index 4bd22f6851..6052e98ab8 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_viv_wl.pro.
-
#####################################################################
## QEglFSVivWaylandIntegrationPlugin Plugin:
#####################################################################
@@ -26,6 +24,3 @@ qt_internal_add_plugin(QEglFSVivWaylandIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_viv_wl.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_viv_wl.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
index c0447938bb..9dde3090c8 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs_x11.pro.
-
#####################################################################
## QEglFSX11IntegrationPlugin Plugin:
#####################################################################
@@ -26,7 +24,5 @@ qt_internal_add_plugin(QEglFSX11IntegrationPlugin
X11::X11
X11::XCB
XCB::XCB
+ NO_UNITY_BUILD # X11 define clashes
)
-
-#### Keys ignored in scope 1:.:.:eglfs_x11.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_x11.json"
diff --git a/src/plugins/platforms/haiku/qhaikuintegration.cpp b/src/plugins/platforms/haiku/qhaikuintegration.cpp
index 7a4ecbfcbf..2b0672e363 100644
--- a/src/plugins/platforms/haiku/qhaikuintegration.cpp
+++ b/src/plugins/platforms/haiku/qhaikuintegration.cpp
@@ -20,6 +20,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
static long int startApplicationThread(void *data)
{
QHaikuApplication *app = static_cast<QHaikuApplication*>(data);
@@ -32,7 +34,7 @@ QHaikuIntegration::QHaikuIntegration(const QStringList &parameters)
{
Q_UNUSED(parameters);
- const QString signature = QStringLiteral("application/x-vnd.Qt.%1").arg(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
+ const QString signature = "application/x-vnd.Qt.%1"_L1.arg(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
QHaikuApplication *app = new QHaikuApplication(signature.toLocal8Bit());
be_app = app;
diff --git a/src/plugins/platforms/haiku/qhaikuwindow.cpp b/src/plugins/platforms/haiku/qhaikuwindow.cpp
index 21bcad1749..3f2c6a889a 100644
--- a/src/plugins/platforms/haiku/qhaikuwindow.cpp
+++ b/src/plugins/platforms/haiku/qhaikuwindow.cpp
@@ -294,7 +294,7 @@ void QHaikuWindow::haikuWindowResized(const QSize &size, bool zoomInProgress)
void QHaikuWindow::haikuWindowActivated(bool activated)
{
- QWindowSystemInterface::handleWindowActivated(activated ? window() : nullptr);
+ QWindowSystemInterface::handleFocusWindowChanged(activated ? window() : nullptr);
}
void QHaikuWindow::haikuWindowMinimized(bool minimize)
diff --git a/src/plugins/platforms/ios/CMakeLists.txt b/src/plugins/platforms/ios/CMakeLists.txt
index a822005b94..4cc3efc91e 100644
--- a/src/plugins/platforms/ios/CMakeLists.txt
+++ b/src/plugins/platforms/ios/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from ios.pro.
-
#####################################################################
## QIOSIntegrationPlugin Plugin:
#####################################################################
@@ -10,13 +8,12 @@
qt_internal_add_plugin(QIOSIntegrationPlugin
OUTPUT_NAME qios
STATIC # Force static, even in shared builds
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios
PLUGIN_TYPE platforms
SOURCES
plugin.mm
qiosapplicationdelegate.h qiosapplicationdelegate.mm
qiosapplicationstate.h qiosapplicationstate.mm
- qioscontext.h qioscontext.mm
qioseventdispatcher.h qioseventdispatcher.mm
qiosglobal.h qiosglobal.mm
qiosinputcontext.h qiosinputcontext.mm
@@ -30,7 +27,13 @@ qt_internal_add_plugin(QIOSIntegrationPlugin
qioswindow.h qioswindow.mm
quiaccessibilityelement.h quiaccessibilityelement.mm
quiview.h quiview.mm
+ quiwindow.mm quiwindow.h
uistrings_p.h uistrings.cpp
+ NO_PCH_SOURCES
+ qioscontext.mm # undef QT_NO_FOREACH
+ qiosintegration.mm # undef QT_NO_FOREACH
+ qiosplatformaccessibility.mm # undef QT_NO_FOREACH
+ qiosscreen.mm # undef QT_NO_FOREACH
LIBRARIES
${FWAudioToolbox}
${FWFoundation}
@@ -42,36 +45,44 @@ qt_internal_add_plugin(QIOSIntegrationPlugin
Qt::GuiPrivate
)
-# special case begin
qt_disable_apple_app_extension_api_only(QIOSIntegrationPlugin)
-# special case end
-#### Keys ignored in scope 2:.:.:kernel.pro:<TRUE>:
-# OTHER_FILES = "quiview_textinput.mm" "quiview_accessibility.mm"
## Scopes:
#####################################################################
qt_internal_find_apple_system_framework(FWUniformTypeIdentifiers UniformTypeIdentifiers)
qt_internal_extend_target(QIOSIntegrationPlugin CONDITION QT_FEATURE_opengl
+ SOURCES
+ qioscontext.h qioscontext.mm
LIBRARIES
Qt::OpenGLPrivate
)
-qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT TVOS
+qt_internal_extend_target(QIOSIntegrationPlugin CONDITION QT_FEATURE_clipboard
SOURCES
qiosclipboard.h qiosclipboard.mm
- qiosdocumentpickercontroller.h qiosdocumentpickercontroller.mm
+)
+
+qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT TVOS
+ SOURCES
qiosfiledialog.h qiosfiledialog.mm
+ qiosdocumentpickercontroller.h qiosdocumentpickercontroller.mm
+ LIBRARIES
+ ${FWUniformTypeIdentifiers}
+ ${FWPhotos}
+)
+
+qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT TVOS
+ SOURCES
qioscolordialog.h qioscolordialog.mm
qiosfontdialog.h qiosfontdialog.mm
- qiosmenu.h qiosmenu.mm
qiosmessagedialog.h qiosmessagedialog.mm
+)
+
+qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT (TVOS OR VISIONOS)
+ SOURCES
+ qiosmenu.h qiosmenu.mm
qiostextinputoverlay.h qiostextinputoverlay.mm
- LIBRARIES
- ${FWAssetsLibrary}
- ${FWUniformTypeIdentifiers}
)
-#### Keys ignored in scope 6:.:.:kernel.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(optional)
diff --git a/src/plugins/platforms/ios/optional/CMakeLists.txt b/src/plugins/platforms/ios/optional/CMakeLists.txt
index 4820ac3b12..a01d7a6441 100644
--- a/src/plugins/platforms/ios/optional/CMakeLists.txt
+++ b/src/plugins/platforms/ios/optional/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from optional.pro.
-
if(IOS)
add_subdirectory(nsphotolibrarysupport)
endif()
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
index ab1e5b6c4a..663878bde7 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from nsphotolibrarysupport.pro.
-
#####################################################################
## QIosOptionalPlugin_NSPhotoLibrary Plugin:
#####################################################################
@@ -27,17 +25,7 @@ qt_internal_add_plugin(QIosOptionalPlugin_NSPhotoLibraryPlugin
Qt::GuiPrivate
)
-# special case begin
set_target_properties(QIosOptionalPlugin_NSPhotoLibraryPlugin
PROPERTIES
DISABLE_PRECOMPILE_HEADERS ON
)
-# special case end
-
-
-#### Keys ignored in scope 1:.:.:nsphotolibrarysupport.pro:<TRUE>:
-# OTHER_FILES = "plugin.json"
-# PLUGIN_EXTENDS = "-"
-
-## Scopes:
-#####################################################################
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
index 90b62af56d..0ad54a9e11 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
@@ -29,8 +29,8 @@ public:
void setFileName(const QString &file) override;
#ifndef QT_NO_FILESYSTEMITERATOR
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
#endif
void setError(QFile::FileError error, const QString &str) { QAbstractFileEngine::setError(error, str); }
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
index 0ca911f68b..f7e112ab81 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
@@ -11,6 +11,8 @@
#include <QtCore/qurl.h>
#include <QtCore/qset.h>
#include <QtCore/qthreadstorage.h>
+#include <QtCore/qfileselector.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -256,8 +258,8 @@ public:
QIOSAssetEnumerator *m_enumerator;
QIOSFileEngineIteratorAssetsLibrary(
- QDir::Filters filters, const QStringList &nameFilters)
- : QAbstractFileEngineIterator(filters, nameFilters)
+ const QString &path, QDir::Filters filters, const QStringList &nameFilters)
+ : QAbstractFileEngineIterator(path, filters, nameFilters)
, m_enumerator(new QIOSAssetEnumerator([[[ALAssetsLibrary alloc] init] autorelease], ALAssetsGroupAll))
{
}
@@ -268,8 +270,11 @@ public:
g_iteratorCurrentUrl.setLocalData(QString());
}
- QString next() override
+ bool advance() override
{
+ if (!m_enumerator->hasNext())
+ return false;
+
// Cache the URL that we are about to return, since QDir will immediately create a
// new file engine on the file and ask if it exists. Unless we do this, we end up
// creating a new ALAsset just to verify its existence, which will be especially
@@ -277,12 +282,7 @@ public:
ALAsset *asset = m_enumerator->next();
QString url = QUrl::fromNSURL([asset valueForProperty:ALAssetPropertyAssetURL]).toString();
g_iteratorCurrentUrl.setLocalData(url);
- return url;
- }
-
- bool hasNext() const override
- {
- return m_enumerator->hasNext();
+ return true;
}
QString currentFileName() const override
@@ -344,6 +344,17 @@ QAbstractFileEngine::FileFlags QIOSFileEngineAssetsLibrary::fileFlags(QAbstractF
{
QAbstractFileEngine::FileFlags flags;
const bool isDir = (m_assetUrl == "assets-library://"_L1);
+ if (!isDir) {
+ static const QFileSelector fileSelector;
+ static const auto selectors = fileSelector.allSelectors();
+ if (m_assetUrl.startsWith("assets-library://"_L1)) {
+ for (const auto &selector : selectors) {
+ if (m_assetUrl.endsWith(selector))
+ return flags;
+ }
+ }
+ }
+
const bool exists = isDir || m_assetUrl == g_iteratorCurrentUrl.localData() || loadAsset();
if (!exists)
@@ -427,15 +438,11 @@ void QIOSFileEngineAssetsLibrary::setFileName(const QString &file)
#ifndef QT_NO_FILESYSTEMITERATOR
-QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::beginEntryList(
- QDir::Filters filters, const QStringList &filterNames)
-{
- return new QIOSFileEngineIteratorAssetsLibrary(filters, filterNames);
-}
-
-QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::endEntryList()
+QAbstractFileEngine::IteratorUniquePtr
+QIOSFileEngineAssetsLibrary::beginEntryList(
+ const QString &path, QDir::Filters filters, const QStringList &filterNames)
{
- return 0;
+ return std::make_unique<QIOSFileEngineIteratorAssetsLibrary>(path, filters, filterNames);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
index f545a81bf2..dfffbb8990 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
@@ -12,19 +12,22 @@ QT_BEGIN_NAMESPACE
class QIOSFileEngineFactory : public QAbstractFileEngineHandler
{
+ Q_DISABLE_COPY_MOVE(QIOSFileEngineFactory)
public:
- QAbstractFileEngine* create(const QString &fileName) const
+ QIOSFileEngineFactory() = default;
+
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const
{
Q_CONSTINIT static QLatin1StringView assetsScheme("assets-library:");
#ifndef Q_OS_TVOS
if (fileName.toLower().startsWith(assetsScheme))
- return new QIOSFileEngineAssetsLibrary(fileName);
+ return std::make_unique<QIOSFileEngineAssetsLibrary>(fileName);
#else
Q_UNUSED(fileName);
#endif
- return 0;
+ return {};
}
};
diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.mm b/src/plugins/platforms/ios/qiosapplicationdelegate.mm
index a017fef457..c6e5a83874 100644
--- a/src/plugins/platforms/ios/qiosapplicationdelegate.mm
+++ b/src/plugins/platforms/ios/qiosapplicationdelegate.mm
@@ -3,15 +3,21 @@
#include "qiosapplicationdelegate.h"
+#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosservices.h"
#include "qiosviewcontroller.h"
#include "qioswindow.h"
+#include "qiosscreen.h"
+#include "quiwindow.h"
#include <qpa/qplatformintegration.h>
#include <QtCore/QtCore>
+@interface QIOSWindowSceneDelegate : NSObject<UIWindowSceneDelegate>
+@end
+
@implementation QIOSApplicationDelegate
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler
@@ -50,5 +56,44 @@
return iosServices->handleUrl(QUrl::fromNSURL(url));
}
+- (UISceneConfiguration *)application:(UIApplication *)application
+ configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
+ options:(UISceneConnectionOptions *)options
+{
+ qCDebug(lcQpaWindowScene) << "Configuring scene for" << connectingSceneSession
+ << "with options" << options;
+
+ auto *sceneConfig = connectingSceneSession.configuration;
+ sceneConfig.delegateClass = QIOSWindowSceneDelegate.class;
+ return sceneConfig;
+}
+
@end
+@implementation QIOSWindowSceneDelegate
+
+- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions
+{
+ qCDebug(lcQpaWindowScene) << "Connecting" << scene << "to" << session;
+
+ Q_ASSERT([scene isKindOfClass:UIWindowScene.class]);
+ UIWindowScene *windowScene = static_cast<UIWindowScene*>(scene);
+
+ QUIWindow *window = [[QUIWindow alloc] initWithWindowScene:windowScene];
+
+ QIOSScreen *screen = [&]{
+ for (auto *screen : qGuiApp->screens()) {
+ auto *platformScreen = static_cast<QIOSScreen*>(screen->handle());
+#if !defined(Q_OS_VISIONOS)
+ if (platformScreen->uiScreen() == windowScene.screen)
+#endif
+ return platformScreen;
+ }
+ Q_UNREACHABLE();
+ }();
+
+ window.rootViewController = [[[QIOSViewController alloc]
+ initWithWindow:window andScreen:screen] autorelease];
+}
+
+@end
diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm
index 7e1e4aa0d4..de8ab69dff 100644
--- a/src/plugins/platforms/ios/qiosclipboard.mm
+++ b/src/plugins/platforms/ios/qiosclipboard.mm
@@ -11,18 +11,6 @@
#include <QtCore/QMimeData>
#include <QtGui/QGuiApplication>
-@interface UIPasteboard (QUIPasteboard)
-+ (instancetype)pasteboardWithQClipboardMode:(QClipboard::Mode)mode;
-@end
-
-@implementation UIPasteboard (QUIPasteboard)
-+ (instancetype)pasteboardWithQClipboardMode:(QClipboard::Mode)mode
-{
- NSString *name = (mode == QClipboard::Clipboard) ? UIPasteboardNameGeneral : UIPasteboardNameFind;
- return [UIPasteboard pasteboardWithName:name create:NO];
-}
-@end
-
// --------------------------------------------------------------------
@interface QUIClipboard : NSObject
@@ -31,7 +19,6 @@
@implementation QUIClipboard {
QIOSClipboard *m_qiosClipboard;
NSInteger m_changeCountClipboard;
- NSInteger m_changeCountFindBuffer;
}
- (instancetype)initWithQIOSClipboard:(QIOSClipboard *)qiosClipboard
@@ -39,8 +26,7 @@
self = [super init];
if (self) {
m_qiosClipboard = qiosClipboard;
- m_changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
- m_changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+ m_changeCountClipboard = UIPasteboard.generalPasteboard.changeCount;
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -77,18 +63,12 @@
- (void)updatePasteboardChanged:(NSNotification *)notification
{
Q_UNUSED(notification);
- NSInteger changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
- NSInteger changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+ NSInteger changeCountClipboard = UIPasteboard.generalPasteboard.changeCount;
if (m_changeCountClipboard != changeCountClipboard) {
m_changeCountClipboard = changeCountClipboard;
m_qiosClipboard->emitChanged(QClipboard::Clipboard);
}
-
- if (m_changeCountFindBuffer != changeCountFindBuffer) {
- m_changeCountFindBuffer = changeCountFindBuffer;
- m_qiosClipboard->emitChanged(QClipboard::FindBuffer);
- }
}
@end
@@ -99,25 +79,22 @@ QT_BEGIN_NAMESPACE
class QIOSMimeData : public QMimeData {
public:
- QIOSMimeData(QClipboard::Mode mode) : QMimeData(), m_mode(mode) { }
+ QIOSMimeData() : QMimeData() { }
~QIOSMimeData() { }
QStringList formats() const override;
QVariant retrieveData(const QString &mimeType, QMetaType type) const override;
-
-private:
- const QClipboard::Mode m_mode;
};
QStringList QIOSMimeData::formats() const
{
QStringList foundMimeTypes;
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
NSArray<NSString *> *pasteboardTypes = [pb pasteboardTypes];
for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
const QString uti = QString::fromNSString([pasteboardTypes objectAtIndex:i]);
- const QString mimeType = QMacMimeRegistry::flavorToMime(QUtiMimeConverter::HandlerScope::All, uti);
+ const QString mimeType = QMacMimeRegistry::flavorToMime(QUtiMimeConverter::HandlerScopeFlag::All, uti);
if (!mimeType.isEmpty() && !foundMimeTypes.contains(mimeType))
foundMimeTypes << mimeType;
}
@@ -127,10 +104,10 @@ QStringList QIOSMimeData::formats() const
QVariant QIOSMimeData::retrieveData(const QString &mimeType, QMetaType) const
{
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
NSArray<NSString *> *pasteboardTypes = [pb pasteboardTypes];
- const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScope::All);
+ const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
for (QUtiMimeConverter *converter : converters) {
for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
NSString *availableUtiNSString = [pasteboardTypes objectAtIndex:i];
@@ -164,7 +141,7 @@ QMimeData *QIOSClipboard::mimeData(QClipboard::Mode mode)
{
Q_ASSERT(supportsMode(mode));
if (!m_mimeData.contains(mode))
- return *m_mimeData.insert(mode, new QIOSMimeData(mode));
+ return *m_mimeData.insert(mode, new QIOSMimeData);
return m_mimeData[mode];
}
@@ -172,7 +149,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
{
Q_ASSERT(supportsMode(mode));
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
if (!mimeData) {
pb.items = [NSArray<NSDictionary<NSString *, id> *> array];
return;
@@ -183,7 +160,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
const auto formats = mimeData->formats();
for (const QString &mimeType : formats) {
- const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScope::All);
+ const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
for (const QUtiMimeConverter *converter : converters) {
const QString uti = converter->utiForMime(mimeType);
if (uti.isEmpty())
@@ -193,15 +170,17 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
if (mimeData->hasImage()) {
mimeDataAsVariant = mimeData->imageData();
} else if (mimeData->hasUrls()) {
+ const auto urls = mimeData->urls();
QVariantList urlList;
- for (QUrl url : mimeData->urls())
+ urlList.reserve(urls.size());
+ for (const QUrl& url : urls)
urlList << url;
mimeDataAsVariant = QVariant(urlList);
} else {
mimeDataAsVariant = QVariant(mimeData->data(mimeType));
}
- QByteArray byteArray = converter->convertFromMime(mimeType, mimeDataAsVariant, uti).first();
+ QByteArray byteArray = converter->convertFromMime(mimeType, mimeDataAsVariant, uti).constFirst();
NSData *nsData = [NSData dataWithBytes:byteArray.constData() length:byteArray.size()];
[pbItem setValue:nsData forKey:uti.toNSString()];
break;
@@ -213,7 +192,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
bool QIOSClipboard::supportsMode(QClipboard::Mode mode) const
{
- return (mode == QClipboard::Clipboard || mode == QClipboard::FindBuffer);
+ return mode == QClipboard::Clipboard;
}
bool QIOSClipboard::ownsMode(QClipboard::Mode mode) const
diff --git a/src/plugins/platforms/ios/qioscolordialog.mm b/src/plugins/platforms/ios/qioscolordialog.mm
index 92c0f3e46c..6651b1791d 100644
--- a/src/plugins/platforms/ios/qioscolordialog.mm
+++ b/src/plugins/platforms/ios/qioscolordialog.mm
@@ -8,6 +8,7 @@
#include <QtCore/private/qcore_mac_p.h>
+#include "qiosglobal.h"
#include "qioscolordialog.h"
#include "qiosintegration.h"
@@ -117,8 +118,7 @@ bool QIOSColorDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windo
if (windowModality == Qt::ApplicationModal || windowModality == Qt::WindowModal)
m_viewController.modalInPresentation = YES;
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
- : qt_apple_sharedApplication().keyWindow;
+ UIWindow *window = presentationWindow(parent);
if (!window)
return false;
diff --git a/src/plugins/platforms/ios/qioscontext.mm b/src/plugins/platforms/ios/qioscontext.mm
index 1649089841..499adea0fe 100644
--- a/src/plugins/platforms/ios/qioscontext.mm
+++ b/src/plugins/platforms/ios/qioscontext.mm
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qioscontext.h"
#include "qiosintegration.h"
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
index af866077cd..fca0432426 100644
--- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
+++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
@@ -30,14 +30,14 @@
case QFileDialogOptions::AnyFile:
case QFileDialogOptions::ExistingFile:
case QFileDialogOptions::ExistingFiles:
- [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)kUTTypeContent]];
- [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)kUTTypeItem]];
- [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)kUTTypeData]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeContent]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeItem]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeData]];
break;
// Showing files is not supported in Directory mode in iOS
case QFileDialogOptions::Directory:
case QFileDialogOptions::DirectoryOnly:
- [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)kUTTypeFolder]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeFolder]];
break;
}
}
diff --git a/src/plugins/platforms/ios/qiosfiledialog.h b/src/plugins/platforms/ios/qiosfiledialog.h
index 12a3af7181..f00c154c03 100644
--- a/src/plugins/platforms/ios/qiosfiledialog.h
+++ b/src/plugins/platforms/ios/qiosfiledialog.h
@@ -39,6 +39,7 @@ private:
bool showImagePickerDialog(QWindow *parent);
bool showNativeDocumentPickerDialog(QWindow *parent);
+ void showImagePickerDialog_helper(QWindow *parent);
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosfiledialog.mm b/src/plugins/platforms/ios/qiosfiledialog.mm
index 3568a9e908..cf9580c17e 100644
--- a/src/plugins/platforms/ios/qiosfiledialog.mm
+++ b/src/plugins/platforms/ios/qiosfiledialog.mm
@@ -3,17 +3,22 @@
#import <UIKit/UIKit.h>
+#import <Photos/Photos.h>
+
#include <QtCore/qstandardpaths.h>
#include <QtGui/qwindow.h>
#include <QDebug>
#include <QtCore/private/qcore_mac_p.h>
+#include "qiosglobal.h"
#include "qiosfiledialog.h"
#include "qiosintegration.h"
#include "qiosoptionalplugininterface.h"
#include "qiosdocumentpickercontroller.h"
+#include <QtCore/qpointer.h>
+
using namespace Qt::StringLiterals;
QIOSFileDialog::QIOSFileDialog()
@@ -36,11 +41,15 @@ bool QIOSFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality window
Q_UNUSED(windowFlags);
Q_UNUSED(windowModality);
- bool acceptOpen = options()->acceptMode() == QFileDialogOptions::AcceptOpen;
- QString directory = options()->initialDirectory().toLocalFile();
+ const bool acceptOpen = options()->acceptMode() == QFileDialogOptions::AcceptOpen;
+ const auto initialDir = options()->initialDirectory();
+ const QString directory = initialDir.toLocalFile();
+ // We manually add assets-library:// to the list of paths,
+ // when converted to QUrl, it becames a scheme.
+ const QString scheme = initialDir.scheme();
if (acceptOpen) {
- if (directory.startsWith("assets-library:"_L1))
+ if (directory.startsWith("assets-library:"_L1) || scheme == "assets-library"_L1)
return showImagePickerDialog(parent);
else
return showNativeDocumentPickerDialog(parent);
@@ -49,6 +58,12 @@ bool QIOSFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality window
return false;
}
+void QIOSFileDialog::showImagePickerDialog_helper(QWindow *parent)
+{
+ UIWindow *window = presentationWindow(parent);
+ [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+}
+
bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
{
if (!m_viewController) {
@@ -67,9 +82,38 @@ bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
return false;
}
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
- : qt_apple_sharedApplication().keyWindow;
- [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+ // "Old style" authorization (deprecated, but we have to work with AssetsLibrary anyway).
+ //
+ // From the documentation:
+ // "The authorizationStatus and requestAuthorization: methods aren’t compatible with the
+ // limited library and return PHAuthorizationStatusAuthorized when the user authorizes your
+ // app for limited access only."
+ //
+ // This is good enough for us.
+
+ const auto authStatus = [PHPhotoLibrary authorizationStatus];
+ if (authStatus == PHAuthorizationStatusAuthorized) {
+ showImagePickerDialog_helper(parent);
+ } else if (authStatus == PHAuthorizationStatusNotDetermined) {
+ QPointer<QWindow> winGuard(parent);
+ QPointer<QIOSFileDialog> thisGuard(this);
+ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (status == PHAuthorizationStatusAuthorized) {
+ if (thisGuard && winGuard)
+ thisGuard->showImagePickerDialog_helper(winGuard);
+
+ } else if (thisGuard) {
+ emit thisGuard->reject();
+ }
+ });
+ }];
+ } else {
+ // Treat 'Limited' (we don't know how to deal with anyway) and 'Denied' as errors.
+ // FIXME: logging category?
+ qWarning() << "QIOSFileDialog: insufficient permission, cannot pick images";
+ return false;
+ }
return true;
}
@@ -79,8 +123,7 @@ bool QIOSFileDialog::showNativeDocumentPickerDialog(QWindow *parent)
#ifndef Q_OS_TVOS
m_viewController = [[QIOSDocumentPickerController alloc] initWithQIOSFileDialog:this];
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
- : qt_apple_sharedApplication().keyWindow;
+ UIWindow *window = presentationWindow(parent);
[window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
return true;
diff --git a/src/plugins/platforms/ios/qiosfontdialog.mm b/src/plugins/platforms/ios/qiosfontdialog.mm
index 4cea1cb558..25d0197195 100644
--- a/src/plugins/platforms/ios/qiosfontdialog.mm
+++ b/src/plugins/platforms/ios/qiosfontdialog.mm
@@ -11,6 +11,7 @@
#include <QtGui/private/qfont_p.h>
#include <QtGui/private/qfontengine_p.h>
+#include "qiosglobal.h"
#include "qiosfontdialog.h"
#include "qiosintegration.h"
@@ -144,8 +145,7 @@ bool QIOSFontDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality window
if (windowModality == Qt::ApplicationModal || windowModality == Qt::WindowModal)
m_viewController.modalInPresentation = YES;
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
- : qt_apple_sharedApplication().keyWindow;
+ UIWindow *window = presentationWindow(parent);
if (!window)
return false;
diff --git a/src/plugins/platforms/ios/qiosglobal.h b/src/plugins/platforms/ios/qiosglobal.h
index 022fa88c24..9428487a00 100644
--- a/src/plugins/platforms/ios/qiosglobal.h
+++ b/src/plugins/platforms/ios/qiosglobal.h
@@ -14,6 +14,7 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcQpaApplication);
Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods);
Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow);
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindowScene);
#if !defined(QT_NO_DEBUG)
#define qImDebug \
@@ -26,6 +27,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow);
class QPlatformScreen;
bool isQtApplication();
+bool isRunningOnVisionOS();
#ifndef Q_OS_TVOS
Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientation);
@@ -34,10 +36,15 @@ UIDeviceOrientation fromQtScreenOrientation(Qt::ScreenOrientation qtOrientation)
int infoPlistValue(NSString* key, int defaultValue);
+class QWindow;
+class QScreen;
+UIWindow *presentationWindow(QWindow *);
+UIView *rootViewForScreen(QScreen *);
+
QT_END_NAMESPACE
@interface UIResponder (QtFirstResponder)
-+ (id)currentFirstResponder;
++ (id)qt_currentFirstResponder;
@end
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm
index 0a6bc5bb62..25ccf2961b 100644
--- a/src/plugins/platforms/ios/qiosglobal.mm
+++ b/src/plugins/platforms/ios/qiosglobal.mm
@@ -13,6 +13,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
+Q_LOGGING_CATEGORY(lcQpaWindowScene, "qt.qpa.window.scene");
bool isQtApplication()
{
@@ -29,6 +30,15 @@ bool isQtApplication()
return isQt;
}
+bool isRunningOnVisionOS()
+{
+ static bool result = []{
+ // This class is documented to only be available on visionOS
+ return NSClassFromString(@"UIWindowSceneGeometryPreferencesVision");
+ }();
+ return result;
+}
+
#ifndef Q_OS_TVOS
Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientation)
{
@@ -85,6 +95,47 @@ int infoPlistValue(NSString* key, int defaultValue)
return value ? [value intValue] : defaultValue;
}
+UIWindow *presentationWindow(QWindow *window)
+{
+ UIWindow *uiWindow = window ? reinterpret_cast<UIView *>(window->winId()).window : nullptr;
+ if (!uiWindow) {
+ auto *scenes = [qt_apple_sharedApplication().connectedScenes allObjects];
+ if (scenes.count > 0) {
+ auto *windowScene = static_cast<UIWindowScene*>(scenes[0]);
+ uiWindow = windowScene.keyWindow;
+ if (!uiWindow && windowScene.windows.count)
+ uiWindow = windowScene.windows[0];
+ }
+ }
+ return uiWindow;
+}
+
+UIView *rootViewForScreen(QScreen *screen)
+{
+ const auto *iosScreen = static_cast<QIOSScreen *>(screen->handle());
+ for (UIScene *scene in [qt_apple_sharedApplication().connectedScenes allObjects]) {
+ if (![scene isKindOfClass:UIWindowScene.class])
+ continue;
+
+ auto *windowScene = static_cast<UIWindowScene*>(scene);
+
+#if !defined(Q_OS_VISIONOS)
+ if (windowScene.screen != iosScreen->uiScreen())
+ continue;
+#else
+ Q_UNUSED(iosScreen);
+#endif
+
+ UIWindow *uiWindow = windowScene.keyWindow;
+ if (!uiWindow && windowScene.windows.count)
+ uiWindow = windowScene.windows[0];
+
+ return uiWindow.rootViewController.view;
+ }
+
+ return nullptr;
+}
+
QT_END_NAMESPACE
// -------------------------------------------------------------------------
@@ -119,7 +170,7 @@ QT_END_NAMESPACE
@implementation UIResponder (QtFirstResponder)
-+ (id)currentFirstResponder
++ (id)qt_currentFirstResponder
{
if (qt_apple_isApplicationExtension()) {
qWarning() << "can't get first responder in application extensions!";
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 7a76760638..5716ad041e 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -18,6 +18,8 @@
#include <QGuiApplication>
#include <QtGui/private/qwindow_p.h>
+#include <QtCore/qpointer.h>
+
// -------------------------------------------------------------------------
static QUIView *focusView()
@@ -119,7 +121,7 @@ static QUIView *focusView()
{
[self keyboardWillOrDidChange:notification];
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
return;
@@ -174,7 +176,11 @@ static QUIView *focusView()
{
[super touchesBegan:touches withEvent:event];
- Q_ASSERT(m_context->isInputPanelVisible());
+ if (!m_context->isInputPanelVisible()) {
+ qImDebug("keyboard was hidden by sliding it down, disabling hide-keyboard gesture");
+ self.enabled = NO;
+ return;
+ }
if ([touches count] != 1)
self.state = UIGestureRecognizerStateFailed;
@@ -228,7 +234,7 @@ static QUIView *focusView()
if (self.state == UIGestureRecognizerStateBegan) {
qImDebug("hide keyboard gesture was triggered");
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
[firstResponder resignFirstResponder];
}
@@ -297,11 +303,7 @@ QIOSInputContext::QIOSInputContext()
, m_keyboardHideGesture([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
, m_textResponder(0)
{
- if (isQtApplication()) {
- QIOSScreen *iosScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
- [iosScreen->uiWindow() addGestureRecognizer:m_keyboardHideGesture];
- }
-
+ Q_ASSERT(!qGuiApp->focusWindow());
connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged);
}
@@ -346,7 +348,7 @@ void QIOSInputContext::clearCurrentFocusObject()
void QIOSInputContext::updateKeyboardState(NSNotification *notification)
{
-#ifdef Q_OS_TVOS
+#if defined(Q_OS_TVOS) || defined(Q_OS_VISIONOS)
Q_UNUSED(notification);
#else
static CGRect currentKeyboardRect = CGRectZero;
@@ -436,6 +438,7 @@ UIView *QIOSInputContext::scrollableRootView()
void QIOSInputContext::scrollToCursor()
{
+#if !defined(Q_OS_VISIONOS)
if (!isQtApplication())
return;
@@ -492,6 +495,7 @@ void QIOSInputContext::scrollToCursor()
} else {
scroll(0);
}
+#endif
}
void QIOSInputContext::scroll(int y)
@@ -603,12 +607,15 @@ void QIOSInputContext::setFocusObject(QObject *focusObject)
void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
{
- Q_UNUSED(focusWindow);
-
qImDebug() << "new focus window =" << focusWindow;
reset();
+ if (isQtApplication()) {
+ [m_keyboardHideGesture.view removeGestureRecognizer:m_keyboardHideGesture];
+ [focusView().window addGestureRecognizer:m_keyboardHideGesture];
+ }
+
// The keyboard rectangle depend on the focus window, so
// we need to re-evaluate the keyboard state.
updateKeyboardState();
diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h
index 2dae15efb8..2c7d33cc94 100644
--- a/src/plugins/platforms/ios/qiosintegration.h
+++ b/src/plugins/platforms/ios/qiosintegration.h
@@ -11,7 +11,8 @@
#include <QtCore/private/qfactoryloader_p.h>
#include "qiosapplicationstate.h"
-#ifndef Q_OS_TVOS
+
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
#include "qiostextinputoverlay.h"
#endif
@@ -31,15 +32,21 @@ public:
bool hasCapability(Capability cap) const override;
QPlatformWindow *createPlatformWindow(QWindow *window) const override;
+ QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const override;
QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
+#if QT_CONFIG(opengl)
QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override;
+#endif
+
QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override;
QPlatformFontDatabase *fontDatabase() const override;
-#ifndef QT_NO_CLIPBOARD
+
+#if QT_CONFIG(clipboard)
QPlatformClipboard *clipboard() const override;
#endif
+
QPlatformInputContext *inputContext() const override;
QPlatformServices *services() const override;
@@ -72,7 +79,7 @@ public:
private:
QPlatformFontDatabase *m_fontDatabase;
-#ifndef Q_OS_TVOS
+#if QT_CONFIG(clipboard)
QPlatformClipboard *m_clipboard;
#endif
QPlatformInputContext *m_inputContext;
@@ -80,7 +87,7 @@ private:
QIOSServices *m_platformServices;
mutable QPlatformAccessibility *m_accessibility;
QFactoryLoader *m_optionalPlugins;
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QIOSTextInputOverlay m_textInputOverlay;
#endif
};
diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm
index 4bf981bf11..7cd21f83f6 100644
--- a/src/plugins/platforms/ios/qiosintegration.mm
+++ b/src/plugins/platforms/ios/qiosintegration.mm
@@ -1,14 +1,15 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosintegration.h"
#include "qioseventdispatcher.h"
#include "qiosglobal.h"
#include "qioswindow.h"
#include "qiosscreen.h"
#include "qiosplatformaccessibility.h"
-#include "qioscontext.h"
-#ifndef Q_OS_TVOS
+#if QT_CONFIG(clipboard)
#include "qiosclipboard.h"
#endif
#include "qiosinputcontext.h"
@@ -29,6 +30,10 @@
#include <QDir>
#include <QOperatingSystemVersion>
+#if QT_CONFIG(opengl)
+#include "qioscontext.h"
+#endif
+
#import <AudioToolbox/AudioServices.h>
#include <QtDebug>
@@ -46,7 +51,7 @@ QIOSIntegration *QIOSIntegration::instance()
QIOSIntegration::QIOSIntegration()
: m_fontDatabase(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>)
-#if !defined(Q_OS_TVOS) && !defined(QT_NO_CLIPBOARD)
+#if QT_CONFIG(clipboard)
, m_clipboard(new QIOSClipboard)
#endif
, m_inputContext(0)
@@ -67,6 +72,10 @@ QIOSIntegration::QIOSIntegration()
void QIOSIntegration::initialize()
{
+#if defined(Q_OS_VISIONOS)
+ // Qt requires a screen, so let's give it a dummy one
+ QWindowSystemInterface::handleScreenAdded(new QIOSScreen);
+#else
UIScreen *mainScreen = [UIScreen mainScreen];
NSMutableArray<UIScreen *> *screens = [[[UIScreen screens] mutableCopy] autorelease];
if (![screens containsObject:mainScreen]) {
@@ -76,6 +85,7 @@ void QIOSIntegration::initialize()
for (UIScreen *screen in screens)
QWindowSystemInterface::handleScreenAdded(new QIOSScreen(screen));
+#endif
// Depends on a primary screen being present
m_inputContext = new QIOSInputContext;
@@ -83,8 +93,10 @@ void QIOSIntegration::initialize()
m_touchDevice = new QPointingDevice;
m_touchDevice->setType(QInputDevice::DeviceType::TouchScreen);
QPointingDevice::Capabilities touchCapabilities = QPointingDevice::Capability::Position | QPointingDevice::Capability::NormalizedPosition;
+#if !defined(Q_OS_VISIONOS)
if (mainScreen.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)
touchCapabilities |= QPointingDevice::Capability::Pressure;
+#endif
m_touchDevice->setCapabilities(touchCapabilities);
QWindowSystemInterface::registerInputDevice(m_touchDevice);
#if QT_CONFIG(tabletevent)
@@ -102,7 +114,7 @@ QIOSIntegration::~QIOSIntegration()
delete m_fontDatabase;
m_fontDatabase = 0;
-#if !defined(Q_OS_TVOS) && !defined(QT_NO_CLIPBOARD)
+#if QT_CONFIG(clipboard)
delete m_clipboard;
m_clipboard = 0;
#endif
@@ -128,11 +140,15 @@ QIOSIntegration::~QIOSIntegration()
bool QIOSIntegration::hasCapability(Capability cap) const
{
switch (cap) {
+#if QT_CONFIG(opengl)
case BufferQueueingOpenGL:
return true;
case OpenGL:
case ThreadedOpenGL:
return true;
+ case RasterGLSurface:
+ return true;
+#endif
case ThreadedPixmaps:
return true;
case MultipleWindows:
@@ -141,7 +157,7 @@ bool QIOSIntegration::hasCapability(Capability cap) const
return false;
case ApplicationState:
return true;
- case RasterGLSurface:
+ case ForeignWindows:
return true;
default:
return QPlatformIntegration::hasCapability(cap);
@@ -153,16 +169,23 @@ QPlatformWindow *QIOSIntegration::createPlatformWindow(QWindow *window) const
return new QIOSWindow(window);
}
+QPlatformWindow *QIOSIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
+{
+ return new QIOSWindow(window, nativeHandle);
+}
+
QPlatformBackingStore *QIOSIntegration::createPlatformBackingStore(QWindow *window) const
{
return new QRhiBackingStore(window);
}
+#if QT_CONFIG(opengl)
// Used when the QWindow's surface type is set by the client to QSurface::OpenGLSurface
QPlatformOpenGLContext *QIOSIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
return new QIOSContext(context);
}
+#endif
class QIOSOffscreenSurface : public QPlatformOffscreenSurface
{
@@ -192,14 +215,10 @@ QPlatformFontDatabase * QIOSIntegration::fontDatabase() const
return m_fontDatabase;
}
-#ifndef QT_NO_CLIPBOARD
+#if QT_CONFIG(clipboard)
QPlatformClipboard *QIOSIntegration::clipboard() const
{
-#ifndef Q_OS_TVOS
return m_clipboard;
-#else
- return QPlatformIntegration::clipboard();
-#endif
}
#endif
diff --git a/src/plugins/platforms/ios/qiosmenu.h b/src/plugins/platforms/ios/qiosmenu.h
index 1822c08b1a..b0c8e7e10c 100644
--- a/src/plugins/platforms/ios/qiosmenu.h
+++ b/src/plugins/platforms/ios/qiosmenu.h
@@ -11,6 +11,8 @@
#import "quiview.h"
+#include <QtCore/qpointer.h>
+
class QIOSMenu;
@class QUIMenuController;
@class QUIPickerView;
diff --git a/src/plugins/platforms/ios/qiosmenu.mm b/src/plugins/platforms/ios/qiosmenu.mm
index 5c9bbc276f..227ad2c7f5 100644
--- a/src/plugins/platforms/ios/qiosmenu.mm
+++ b/src/plugins/platforms/ios/qiosmenu.mm
@@ -491,7 +491,7 @@ QIOSMenuItemList QIOSMenu::filterFirstResponderActions(const QIOSMenuItemList &m
// In case of QIOSTextResponder, edit actions will be converted to key events that ends up
// triggering the shortcuts of the filtered menu items.
QIOSMenuItemList filteredMenuItems;
- UIResponder *responder = [UIResponder currentFirstResponder];
+ UIResponder *responder = [UIResponder qt_currentFirstResponder];
for (int i = 0; i < menuItems.count(); ++i) {
QIOSMenuItem *menuItem = menuItems.at(i);
diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm
index 1f39a94172..7fbd5d8729 100644
--- a/src/plugins/platforms/ios/qiosmessagedialog.mm
+++ b/src/plugins/platforms/ios/qiosmessagedialog.mm
@@ -30,7 +30,7 @@ inline QString QIOSMessageDialog::messageTextPlain()
{
// Concatenate text fragments, and remove HTML tags
const QSharedPointer<QMessageDialogOptions> &opt = options();
- const QString &lineShift = QStringLiteral("\n\n");
+ constexpr auto lineShift = "\n\n"_L1;
const QString &informativeText = opt->informativeText();
const QString &detailedText = opt->detailedText();
@@ -40,7 +40,7 @@ inline QString QIOSMessageDialog::messageTextPlain()
if (!detailedText.isEmpty())
text += lineShift + detailedText;
- text.replace("<p>"_L1, QStringLiteral("\n"), Qt::CaseInsensitive);
+ text.replace("<p>"_L1, "\n"_L1, Qt::CaseInsensitive);
text.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
return text;
@@ -92,7 +92,7 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win
|| windowModality == Qt::NonModal) // We can only do modal dialogs
return false;
- if (options()->supressionCheckBoxEnabled())
+ if (!options()->checkBoxLabel().isNull())
return false; // Can't support
m_alertController = [[UIAlertController
@@ -116,26 +116,18 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win
[m_alertController addAction:createAction(NoButton)];
}
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window : qt_apple_sharedApplication().keyWindow;
- if (!window) {
- qCDebug(lcQpaWindow, "Attempting to exec a dialog without any window/widget visible.");
-
- auto *primaryScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
- Q_ASSERT(primaryScreen);
-
- window = primaryScreen->uiWindow();
- if (window.hidden) {
- // With a window hidden, an attempt to present view controller
- // below fails with a warning, that a view "is not a part of
- // any view hierarchy". The UIWindow is initially hidden,
- // as unhiding it is what hides the splash screen.
- window.hidden = NO;
- }
- }
-
+ UIWindow *window = presentationWindow(parent);
if (!window)
return false;
+ if (window.hidden) {
+ // With a window hidden, an attempt to present view controller
+ // below fails with a warning, that a view "is not a part of
+ // any view hierarchy". The UIWindow is initially hidden,
+ // as unhiding it is what hides the splash screen.
+ window.hidden = NO;
+ }
+
[window.rootViewController presentViewController:m_alertController animated:YES completion:nil];
return true;
}
diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.mm b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
index d54b7db57a..eb18ee637e 100644
--- a/src/plugins/platforms/ios/qiosplatformaccessibility.mm
+++ b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
@@ -1,12 +1,15 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosplatformaccessibility.h"
#if QT_CONFIG(accessibility)
#include <QtGui/QtGui>
#include "qioswindow.h"
+#include "quiaccessibilityelement.h"
QIOSPlatformAccessibility::QIOSPlatformAccessibility()
{}
@@ -25,8 +28,6 @@ void invalidateCache(QAccessibleInterface *iface)
// This will invalidate everything regardless of what window the
// interface belonged to. We might want to revisit this strategy later.
// (Therefore this function still takes the interface as argument)
- // It is also responsible for the bug that focus gets temporary lost
- // when items get added or removed from the screen
foreach (QWindow *win, QGuiApplication::topLevelWindows()) {
if (win && win->handle()) {
QT_PREPEND_NAMESPACE(QIOSWindow) *window = static_cast<QT_PREPEND_NAMESPACE(QIOSWindow) *>(win->handle());
@@ -38,14 +39,35 @@ void invalidateCache(QAccessibleInterface *iface)
void QIOSPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
{
- if (!isActive() || !event->accessibleInterface())
+ auto *accessibleInterface = event->accessibleInterface();
+ if (!isActive() || !accessibleInterface)
return;
switch (event->type()) {
+ case QAccessible::Focus: {
+ auto *element = [QMacAccessibilityElement elementWithId:event->uniqueId()];
+ Q_ASSERT(element);
+ // There's no NSAccessibilityFocusedUIElementChangedNotification, like we have on
+ // macOS. Instead, the documentation for UIAccessibilityLayoutChangedNotification
+ // specifies that the optional argument to UIAccessibilityPostNotification is the
+ // accessibility element for VoiceOver to move to after processing the notification.
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, element);
+ break;
+ }
case QAccessible::ObjectCreated:
case QAccessible::ObjectShow:
case QAccessible::ObjectHide:
case QAccessible::ObjectDestroyed:
- invalidateCache(event->accessibleInterface());
+ invalidateCache(accessibleInterface);
+ switch (accessibleInterface->role()) {
+ case QAccessible::Window:
+ case QAccessible::Dialog:
+ // Bigger changes to the UI require a full reset of VoiceOver
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
+ break;
+ default:
+ // While smaller changes can be handled by re-reading the layout
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ }
break;
default:
break;
diff --git a/src/plugins/platforms/ios/qiosscreen.h b/src/plugins/platforms/ios/qiosscreen.h
index 21afb90c55..dd69428390 100644
--- a/src/plugins/platforms/ios/qiosscreen.h
+++ b/src/plugins/platforms/ios/qiosscreen.h
@@ -10,10 +10,6 @@
@class QIOSOrientationListener;
-@interface QUIWindow : UIWindow
-@property (nonatomic, readonly) BOOL sendingEvent;
-@end
-
QT_BEGIN_NAMESPACE
class QIOSScreen : public QObject, public QPlatformScreen
@@ -21,7 +17,11 @@ class QIOSScreen : public QObject, public QPlatformScreen
Q_OBJECT
public:
+#if !defined(Q_OS_VISIONOS)
QIOSScreen(UIScreen *screen);
+#else
+ QIOSScreen();
+#endif
~QIOSScreen();
QString name() const override;
@@ -40,8 +40,9 @@ public:
QPixmap grabWindow(WId window, int x, int y, int width, int height) const override;
+#if !defined(Q_OS_VISIONOS)
UIScreen *uiScreen() const;
- UIWindow *uiWindow() const;
+#endif
void setUpdatesPaused(bool);
@@ -50,15 +51,17 @@ public:
private:
void deliverUpdateRequests() const;
- UIScreen *m_uiScreen;
- UIWindow *m_uiWindow;
+#if !defined(Q_OS_VISIONOS)
+ UIScreen *m_uiScreen = nullptr;
+#endif
QRect m_geometry;
QRect m_availableGeometry;
int m_depth;
+#if !defined(Q_OS_VISIONOS)
uint m_physicalDpi;
+#endif
QSizeF m_physicalSize;
- QIOSOrientationListener *m_orientationListener;
- CADisplayLink *m_displayLink;
+ CADisplayLink *m_displayLink = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm
index be17972d36..7559979f33 100644
--- a/src/plugins/platforms/ios/qiosscreen.mm
+++ b/src/plugins/platforms/ios/qiosscreen.mm
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
@@ -10,6 +12,7 @@
#include "qiosviewcontroller.h"
#include "quiview.h"
#include "qiostheme.h"
+#include "quiwindow.h"
#include <QtCore/private/qcore_mac_p.h>
@@ -44,6 +47,7 @@ typedef void (^DisplayLinkBlock)(CADisplayLink *displayLink);
// -------------------------------------------------------------------------
+#if !defined(Q_OS_VISIONOS)
static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
{
foreach (QScreen *screen, QGuiApplication::screens()) {
@@ -81,6 +85,9 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
+ (void)screenDisconnected:(NSNotification*)notification
{
+ if (!QIOSIntegration::instance())
+ return;
+
QIOSScreen *screen = qtPlatformScreenFor([notification object]);
Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen disconnected that we didn't know about");
@@ -89,6 +96,9 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
+ (void)screenModeChanged:(NSNotification*)notification
{
+ if (!QIOSIntegration::instance())
+ return;
+
QIOSScreen *screen = qtPlatformScreenFor([notification object]);
Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen changed that we didn't know about");
@@ -97,104 +107,7 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
@end
-// -------------------------------------------------------------------------
-
-@interface QIOSOrientationListener : NSObject
-@end
-
-@implementation QIOSOrientationListener {
- QIOSScreen *m_screen;
-}
-
-- (instancetype)initWithQIOSScreen:(QIOSScreen *)screen
-{
- self = [super init];
- if (self) {
- m_screen = screen;
-#ifndef Q_OS_TVOS
- [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(orientationChanged:)
- name:@"UIDeviceOrientationDidChangeNotification" object:nil];
-#endif
- }
- return self;
-}
-
-- (void)dealloc
-{
-#ifndef Q_OS_TVOS
- [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:@"UIDeviceOrientationDidChangeNotification" object:nil];
-#endif
- [super dealloc];
-}
-
-- (void)orientationChanged:(NSNotification *)notification
-{
- Q_UNUSED(notification);
- m_screen->updateProperties();
-}
-
-@end
-
-@interface UIScreen (Compatibility)
-@property (nonatomic, readonly) CGRect qt_applicationFrame;
-@end
-
-@implementation UIScreen (Compatibility)
-- (CGRect)qt_applicationFrame
-{
-#ifdef Q_OS_IOS
- return self.applicationFrame;
-#else
- return self.bounds;
-#endif
-}
-@end
-
-// -------------------------------------------------------------------------
-
-@implementation QUIWindow
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- if ((self = [super initWithFrame:frame]))
- self->_sendingEvent = NO;
-
- return self;
-}
-
-- (void)sendEvent:(UIEvent *)event
-{
- QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES);
- [super sendEvent:event];
-}
-
-- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
-{
- [super traitCollectionDidChange:previousTraitCollection];
-
- Qt::Appearance appearance = self.traitCollection.userInterfaceStyle
- == UIUserInterfaceStyleDark
- ? Qt::Appearance::Dark
- : Qt::Appearance::Light;
-
- if (self.screen == UIScreen.mainScreen) {
- // Check if the current userInterfaceStyle reports a different appearance than
- // the platformTheme's appearance. We might have set that one based on the UIScreen
- if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle
- || QGuiApplicationPrivate::platformTheme()->appearance() != appearance) {
- QIOSTheme::initializeSystemPalette();
- QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
- }
- }
-}
-
-@end
+#endif // !defined(Q_OS_VISIONOS)
// -------------------------------------------------------------------------
@@ -202,6 +115,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+#if !defined(Q_OS_VISIONOS)
/*!
Returns the model identifier of the device.
*/
@@ -221,12 +135,14 @@ static QString deviceModelIdentifier()
return QString::fromLatin1(QByteArrayView(value, qsizetype(size)));
#endif
}
+#endif // !defined(Q_OS_VISIONOS)
+#if defined(Q_OS_VISIONOS)
+QIOSScreen::QIOSScreen()
+{
+#else
QIOSScreen::QIOSScreen(UIScreen *screen)
- : QPlatformScreen()
- , m_uiScreen(screen)
- , m_uiWindow(0)
- , m_orientationListener(0)
+ : m_uiScreen(screen)
{
QString deviceIdentifier = deviceModelIdentifier();
@@ -260,44 +176,30 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
m_physicalDpi = 96;
}
- if (!qt_apple_isApplicationExtension()) {
- for (UIWindow *existingWindow in qt_apple_sharedApplication().windows) {
- if (existingWindow.screen == m_uiScreen) {
- m_uiWindow = [existingWindow retain];
- break;
- }
- }
-
- if (!m_uiWindow) {
- // Create a window and associated view-controller that we can use
- m_uiWindow = [[QUIWindow alloc] initWithFrame:[m_uiScreen bounds]];
- m_uiWindow.rootViewController = [[[QIOSViewController alloc] initWithQIOSScreen:this] autorelease];
- }
- }
-
- m_orientationListener = [[QIOSOrientationListener alloc] initWithQIOSScreen:this];
-
- updateProperties();
-
m_displayLink = [m_uiScreen displayLinkWithBlock:^(CADisplayLink *) { deliverUpdateRequests(); }];
m_displayLink.paused = YES; // Enabled when clients call QWindow::requestUpdate()
[m_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
+
+#endif // !defined(Q_OS_VISIONOS))
+
+ updateProperties();
}
QIOSScreen::~QIOSScreen()
{
[m_displayLink invalidate];
-
- [m_orientationListener release];
- [m_uiWindow release];
}
QString QIOSScreen::name() const
{
+#if defined(Q_OS_VISIONOS)
+ return {};
+#else
if (m_uiScreen == [UIScreen mainScreen])
return QString::fromNSString([UIDevice currentDevice].model) + " built-in display"_L1;
else
return "External display"_L1;
+#endif
}
void QIOSScreen::updateProperties()
@@ -305,42 +207,13 @@ void QIOSScreen::updateProperties()
QRect previousGeometry = m_geometry;
QRect previousAvailableGeometry = m_availableGeometry;
+#if defined(Q_OS_VISIONOS)
+ // Based on what iPad app reports
+ m_geometry = QRect(0, 0, 1194, 834);
+ m_depth = 24;
+#else
m_geometry = QRectF::fromCGRect(m_uiScreen.bounds).toRect();
- // The application frame doesn't take safe area insets into account, and
- // the safe area insets are not available before the UIWindow is shown,
- // and do not take split-view constraints into account, so we have to
- // combine the two to get the correct available geometry.
- QRect applicationFrame = QRectF::fromCGRect(m_uiScreen.qt_applicationFrame).toRect();
- UIEdgeInsets safeAreaInsets = m_uiWindow.qt_safeAreaInsets;
- m_availableGeometry = m_geometry.adjusted(safeAreaInsets.left, safeAreaInsets.top,
- -safeAreaInsets.right, -safeAreaInsets.bottom).intersected(applicationFrame);
-
-#ifndef Q_OS_TVOS
- if (m_uiScreen == [UIScreen mainScreen]) {
- QIOSViewController *qtViewController = [m_uiWindow.rootViewController isKindOfClass:[QIOSViewController class]] ?
- static_cast<QIOSViewController *>(m_uiWindow.rootViewController) : nil;
-
- if (qtViewController.lockedOrientation) {
- Q_ASSERT(!qt_apple_isApplicationExtension());
-
- // Setting the statusbar orientation (content orientation) on will affect the screen geometry,
- // which is not what we want. We want to reflect the screen geometry based on the locked orientation,
- // and adjust the available geometry based on the repositioned status bar for the current status
- // bar orientation.
-
- Qt::ScreenOrientation statusBarOrientation = toQtScreenOrientation(
- UIDeviceOrientation(qt_apple_sharedApplication().statusBarOrientation));
-
- Qt::ScreenOrientation lockedOrientation = toQtScreenOrientation(UIDeviceOrientation(qtViewController.lockedOrientation));
- QTransform transform = transformBetween(lockedOrientation, statusBarOrientation, m_geometry).inverted();
-
- m_geometry = transform.mapRect(m_geometry);
- m_availableGeometry = transform.mapRect(m_availableGeometry);
- }
- }
-#endif
-
if (m_geometry != previousGeometry) {
// We can't use the primaryOrientation of screen(), as we haven't reported the new geometry yet
Qt::ScreenOrientation primaryOrientation = m_geometry.width() >= m_geometry.height() ?
@@ -356,6 +229,14 @@ void QIOSScreen::updateProperties()
m_physicalSize = physicalGeometry.size() / m_physicalDpi * millimetersPerInch;
}
+#endif // defined(Q_OS_VISIONOS)
+
+ // UIScreen does not provide a consistent accessor for the safe area margins
+ // of the screen, and on visionOS we won't even have a UIScreen, so we report
+ // the available geometry of the screen to be the same as the full geometry.
+ // Safe area margins and maximized state is handled in QIOSWindow::setWindowState.
+ m_availableGeometry = m_geometry;
+
// At construction time, we don't yet have an associated QScreen, but we still want
// to compute the properties above so they are ready for when the QScreen attaches.
// Also, at destruction time the QScreen has already been torn down, so notifying
@@ -440,16 +321,28 @@ QDpi QIOSScreen::logicalBaseDpi() const
qreal QIOSScreen::devicePixelRatio() const
{
+#if defined(Q_OS_VISIONOS)
+ return 2.0; // Based on what iPad app reports
+#else
return [m_uiScreen scale];
+#endif
}
qreal QIOSScreen::refreshRate() const
{
+#if defined(Q_OS_VISIONOS)
+ return 120.0; // Based on what iPad app reports
+#else
return m_uiScreen.maximumFramesPerSecond;
+#endif
}
Qt::ScreenOrientation QIOSScreen::nativeOrientation() const
{
+#if defined(Q_OS_VISIONOS)
+ // Based on iPad app reporting native bounds 1668x2388
+ return Qt::PortraitOrientation;
+#else
CGRect nativeBounds =
#if defined(Q_OS_IOS)
m_uiScreen.nativeBounds;
@@ -461,38 +354,18 @@ Qt::ScreenOrientation QIOSScreen::nativeOrientation() const
// be on the safe side we compare the width and height of the bounds.
return nativeBounds.size.width >= nativeBounds.size.height ?
Qt::LandscapeOrientation : Qt::PortraitOrientation;
+#endif
}
Qt::ScreenOrientation QIOSScreen::orientation() const
{
-#ifdef Q_OS_TVOS
- return Qt::PrimaryOrientation;
-#else
- // Auxiliary screens are always the same orientation as their primary orientation
- if (m_uiScreen != [UIScreen mainScreen])
- return Qt::PrimaryOrientation;
-
- UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
-
- // At startup, iOS will report an unknown orientation for the device, even
- // if we've asked it to begin generating device orientation notifications.
- // In this case we fall back to the status bar orientation, which reflects
- // the orientation the application was started up in (which may not match
- // the physical orientation of the device, but typically does unless the
- // application has been locked to a subset of the available orientations).
- if (deviceOrientation == UIDeviceOrientationUnknown && !qt_apple_isApplicationExtension())
- deviceOrientation = UIDeviceOrientation(qt_apple_sharedApplication().statusBarOrientation);
-
- // If the device reports face up or face down orientations, we can't map
- // them to Qt orientations, so we pretend we're in the same orientation
- // as before.
- if (deviceOrientation == UIDeviceOrientationFaceUp || deviceOrientation == UIDeviceOrientationFaceDown) {
- Q_ASSERT(screen());
- return screen()->orientation();
- }
-
- return toQtScreenOrientation(deviceOrientation);
-#endif
+ // We don't report UIDevice.currentDevice.orientation here,
+ // as that would report the actual orientation of the device,
+ // even if the orientation of the UI was locked to a subset
+ // of the possible orientations via the app's Info.plist or
+ // via [UIViewController supportedInterfaceOrientations].
+ return m_geometry.width() >= m_geometry.height() ?
+ Qt::LandscapeOrientation : Qt::PortraitOrientation;
}
QPixmap QIOSScreen::grabWindow(WId window, int x, int y, int width, int height) const
@@ -500,26 +373,27 @@ QPixmap QIOSScreen::grabWindow(WId window, int x, int y, int width, int height)
if (window && ![reinterpret_cast<id>(window) isKindOfClass:[UIView class]])
return QPixmap();
- UIView *view = window ? reinterpret_cast<UIView *>(window) : m_uiWindow;
+ UIView *view = window ? reinterpret_cast<UIView *>(window)
+ : rootViewForScreen(screen());
if (width < 0)
width = qMax(view.bounds.size.width - x, CGFloat(0));
if (height < 0)
height = qMax(view.bounds.size.height - y, CGFloat(0));
- CGRect captureRect = [m_uiWindow convertRect:CGRectMake(x, y, width, height) fromView:view];
- captureRect = CGRectIntersection(captureRect, m_uiWindow.bounds);
+ CGRect captureRect = [view.window convertRect:CGRectMake(x, y, width, height) fromView:view];
+ captureRect = CGRectIntersection(captureRect, view.window.bounds);
UIGraphicsBeginImageContextWithOptions(captureRect.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, -captureRect.origin.x, -captureRect.origin.y);
- // Draws the complete view hierarchy of m_uiWindow into the given rect, which
- // needs to be the same aspect ratio as the m_uiWindow's size. Since we've
+ // Draws the complete view hierarchy of view.window into the given rect, which
+ // needs to be the same aspect ratio as the view.window's size. Since we've
// translated the graphics context, and are potentially drawing into a smaller
// context than the full window, the resulting image will be a subsection of the
// full screen.
- [m_uiWindow drawViewHierarchyInRect:m_uiWindow.bounds afterScreenUpdates:NO];
+ [view.window drawViewHierarchyInRect:view.window.bounds afterScreenUpdates:NO];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@@ -527,15 +401,12 @@ QPixmap QIOSScreen::grabWindow(WId window, int x, int y, int width, int height)
return QPixmap::fromImage(qt_mac_toQImage(screenshot.CGImage));
}
+#if !defined(Q_OS_VISIONOS)
UIScreen *QIOSScreen::uiScreen() const
{
return m_uiScreen;
}
-
-UIWindow *QIOSScreen::uiWindow() const
-{
- return m_uiWindow;
-}
+#endif
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiostextinputoverlay.mm b/src/plugins/platforms/ios/qiostextinputoverlay.mm
index 512ab77bd2..01046334a1 100644
--- a/src/plugins/platforms/ios/qiostextinputoverlay.mm
+++ b/src/plugins/platforms/ios/qiostextinputoverlay.mm
@@ -947,7 +947,17 @@ static void executeBlockWithoutAnimation(Block block)
int cursorPosOnRelease = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPos).toInt();
if (cursorPosOnRelease == _cursorPosOnPress) {
+ // We've recognized a gesture to open the menu, but we don't know
+ // whether the user tapped a control that was overlaid our input
+ // area, since we don't do any granular hit-testing in touchesBegan.
+ // To ensure that the gesture doesn't eat touch events that should
+ // have reached another UI control we report the gesture as failed
+ // here, and then manually show the menu at the next runloop pass.
_menuShouldBeVisible = true;
+ self.state = UIGestureRecognizerStateFailed;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ QIOSTextInputOverlay::s_editMenu.visible = _menuShouldBeVisible;
+ });
} else {
// The menu is hidden, and the cursor will change position once
// Qt receive the touch release. We therefore fail so that we
@@ -996,19 +1006,26 @@ QIOSTextInputOverlay::~QIOSTextInputOverlay()
void QIOSTextInputOverlay::updateFocusObject()
{
+ // Destroy old recognizers since they were created with
+ // dependencies to the old focus object (focus view).
if (m_cursorRecognizer) {
- // Destroy old recognizers since they were created with
- // dependencies to the old focus object (focus view).
m_cursorRecognizer.enabled = NO;
- m_selectionRecognizer.enabled = NO;
- m_openMenuOnTapRecognizer.enabled = NO;
[m_cursorRecognizer release];
- [m_selectionRecognizer release];
- [m_openMenuOnTapRecognizer release];
- [s_editMenu release];
m_cursorRecognizer = nullptr;
+ }
+ if (m_selectionRecognizer) {
+ m_selectionRecognizer.enabled = NO;
+ [m_selectionRecognizer release];
m_selectionRecognizer = nullptr;
+ }
+ if (m_openMenuOnTapRecognizer) {
+ m_openMenuOnTapRecognizer.enabled = NO;
+ [m_openMenuOnTapRecognizer release];
m_openMenuOnTapRecognizer = nullptr;
+ }
+
+ if (s_editMenu) {
+ [s_editMenu release];
s_editMenu = nullptr;
}
diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm
index f8bcf7fb25..5231a3adde 100644
--- a/src/plugins/platforms/ios/qiostextresponder.mm
+++ b/src/plugins/platforms/ios/qiostextresponder.mm
@@ -164,7 +164,7 @@
{
FirstResponderCandidate firstResponderCandidate(self);
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
@@ -178,7 +178,7 @@
- (BOOL)resignFirstResponder
{
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
// Don't allow activation events of the window that we're doing text on behalf on
// to steal responder.
@@ -196,11 +196,11 @@
// a regular responder transfer to another window. In the former case, iOS
// will set the new first-responder to our next-responder, and in the latter
// case we'll have an active responder candidate.
- if (![UIResponder currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
+ if (![UIResponder qt_currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
// No first responder set anymore, sync this with Qt by clearing the
// focus object.
m_inputContext->clearCurrentFocusObject();
- } else if ([UIResponder currentFirstResponder] == [self nextResponder]) {
+ } else if ([UIResponder qt_currentFirstResponder] == [self nextResponder]) {
// We have resigned the keyboard, and transferred first responder back to the parent view
Q_ASSERT(!FirstResponderCandidate::currentCandidate());
if ([self currentImeState:Qt::ImEnabled].toBool()) {
@@ -222,8 +222,12 @@
- (UIResponder*)nextResponder
{
- return qApp->focusWindow() ?
- reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
+ // Make sure we have a handle/platform window before getting the winId().
+ // In the dtor of QIOSWindow the platform window is set to null before calling
+ // removeFromSuperview which will end up calling nextResponder. That means it's
+ // possible that we can get here while the window is being torn down.
+ return (qApp->focusWindow() && qApp->focusWindow()->handle()) ?
+ reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
}
// -------------------------------------------------------------------------
@@ -397,7 +401,7 @@
if (UIView *accessoryView = static_cast<UIView *>(platformData.value(kImePlatformDataInputAccessoryView).value<void *>()))
self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease];
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
if (platformData.value(kImePlatformDataHideShortcutsBar).toBool()) {
// According to the docs, leadingBarButtonGroups/trailingBarButtonGroups should be set to nil to hide the shortcuts bar.
// However, starting with iOS 10, the API has been surrounded with NS_ASSUME_NONNULL, which contradicts this and causes
@@ -898,7 +902,7 @@
QInputMethodEvent e(m_markedText, attrs);
[self sendEventToFocusObject:e];
}
- QRectF startRect = QPlatformInputContext::cursorRectangle();;
+ QRectF startRect = QPlatformInputContext::cursorRectangle();
attrs = QList<QInputMethodEvent::Attribute>();
attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.location + r.length, 0, 0);
@@ -906,7 +910,7 @@
QInputMethodEvent e(m_markedText, attrs);
[self sendEventToFocusObject:e];
}
- QRectF endRect = QPlatformInputContext::cursorRectangle();;
+ QRectF endRect = QPlatformInputContext::cursorRectangle();
if (cursorPos != int(r.location + r.length) || cursorPos != anchorPos) {
attrs = QList<QInputMethodEvent::Attribute>();
diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h
index 5d551663d0..f0a404a61a 100644
--- a/src/plugins/platforms/ios/qiostheme.h
+++ b/src/plugins/platforms/ios/qiostheme.h
@@ -21,15 +21,18 @@ public:
const QPalette *palette(Palette type = SystemPalette) const override;
QVariant themeHint(ThemeHint hint) const override;
- Qt::Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QPlatformMenuItem* createPlatformMenuItem() const override;
QPlatformMenu* createPlatformMenu() const override;
+#endif
bool usePlatformNativeDialog(DialogType type) const override;
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
const QFont *font(Font type = SystemFont) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
static const char *name;
diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm
index 2c40149813..3853de9cf1 100644
--- a/src/plugins/platforms/ios/qiostheme.mm
+++ b/src/plugins/platforms/ios/qiostheme.mm
@@ -11,18 +11,24 @@
#include <QtGui/private/qcoregraphics_p.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
+#include <QtGui/private/qappleiconengine_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <UIKit/UIFont.h>
#include <UIKit/UIInterface.h>
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
#include "qiosmenu.h"
+#endif
+
+#if !defined(Q_OS_TVOS)
#include "qiosfiledialog.h"
-#include "qiosmessagedialog.h"
#include "qioscolordialog.h"
#include "qiosfontdialog.h"
+#include "qiosmessagedialog.h"
+#include "qiosscreen.h"
+#include "quiwindow.h"
#endif
QT_BEGIN_NAMESPACE
@@ -68,6 +74,9 @@ void QIOSTheme::initializeSystemPalette()
s_systemPalette.setBrush(QPalette::Highlight, QColor(11, 70, 150, 60));
s_systemPalette.setBrush(QPalette::HighlightedText, qt_mac_toQBrush(UIColor.labelColor.CGColor));
+
+ if (@available(ios 15.0, *))
+ s_systemPalette.setBrush(QPalette::Accent, qt_mac_toQBrush(UIColor.tintColor.CGColor));
}
const QPalette *QIOSTheme::palette(QPlatformTheme::Palette type) const
@@ -77,23 +86,17 @@ const QPalette *QIOSTheme::palette(QPlatformTheme::Palette type) const
return 0;
}
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QPlatformMenuItem* QIOSTheme::createPlatformMenuItem() const
{
-#ifdef Q_OS_TVOS
- return 0;
-#else
- return new QIOSMenuItem();
-#endif
+ return new QIOSMenuItem;
}
QPlatformMenu* QIOSTheme::createPlatformMenu() const
{
-#ifdef Q_OS_TVOS
- return 0;
-#else
- return new QIOSMenu();
-#endif
+ return new QIOSMenu;
}
+#endif
bool QIOSTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const
{
@@ -142,19 +145,30 @@ QVariant QIOSTheme::themeHint(ThemeHint hint) const
}
}
-Qt::Appearance QIOSTheme::appearance() const
+Qt::ColorScheme QIOSTheme::colorScheme() const
{
- UIUserInterfaceStyle appearance = UIUserInterfaceStyleUnspecified;
- // Set the appearance based on the UIWindow
+#if defined(Q_OS_VISIONOS)
+ // On visionOS the concept of light or dark mode does not
+ // apply, as the UI is constantly changing based on what
+ // the lighting conditions are outside the headset, but
+ // the OS reports itself as always being in dark mode.
+ return Qt::ColorScheme::Dark;
+#else
+ // Set the appearance based on the QUIWindow
// Fallback to the UIScreen if no window is created yet
- if (UIWindow *window = qt_apple_sharedApplication().windows.lastObject) {
- appearance = window.traitCollection.userInterfaceStyle;
- } else {
- appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle;
+ UIUserInterfaceStyle appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle;
+ NSArray<UIWindow *> *windows = qt_apple_sharedApplication().windows;
+ for (UIWindow *window in windows) {
+ if ([window isKindOfClass:[QUIWindow class]]) {
+ appearance = static_cast<QUIWindow*>(window).traitCollection.userInterfaceStyle;
+ break;
+ }
}
+
return appearance == UIUserInterfaceStyleDark
- ? Qt::Appearance::Dark
- : Qt::Appearance::Light;
+ ? Qt::ColorScheme::Dark
+ : Qt::ColorScheme::Light;
+#endif
}
const QFont *QIOSTheme::font(Font type) const
@@ -164,4 +178,9 @@ const QFont *QIOSTheme::font(Font type) const
return coreTextFontDatabase->themeFont(type);
}
+QIconEngine *QIOSTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAppleIconEngine(iconName);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h
index 237cd57edf..1f8da41ba4 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.h
+++ b/src/plugins/platforms/ios/qiosviewcontroller.h
@@ -12,14 +12,12 @@ QT_END_NAMESPACE
@interface QIOSViewController : UIViewController
-- (instancetype)initWithQIOSScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen;
+- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen;
- (void)updateProperties;
- (NSArray*)keyCommands;
- (void)handleShortcut:(UIKeyCommand*)keyCommand;
#ifndef Q_OS_TVOS
-@property (nonatomic, assign) UIInterfaceOrientation lockedOrientation;
-
// UIViewController
@property (nonatomic, assign) BOOL prefersStatusBarHidden;
@property (nonatomic, assign) UIStatusBarAnimation preferredStatusBarUpdateAnimation;
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm
index c4e8968232..436d1e7bed 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.mm
+++ b/src/plugins/platforms/ios/qiosviewcontroller.mm
@@ -21,9 +21,12 @@
#include "qioswindow.h"
#include "quiview.h"
+#include <QtCore/qpointer.h>
+
// -------------------------------------------------------------------------
@interface QIOSViewController ()
+@property (nonatomic, assign) UIWindow *window;
@property (nonatomic, assign) QPointer<QT_PREPEND_NAMESPACE(QIOSScreen)> platformScreen;
@property (nonatomic, assign) BOOL changingOrientation;
@end
@@ -88,27 +91,30 @@
{
Q_UNUSED(subview);
- QT_PREPEND_NAMESPACE(QIOSScreen) *screen = self.qtViewController.platformScreen;
-
- // The 'window' property of our view is not valid until the window
- // has been shown, so we have to access it through the QIOSScreen.
- UIWindow *uiWindow = screen->uiWindow();
+ // Track UIWindow via explicit property on QIOSViewController,
+ // as the window property of our own view is not valid until
+ // the window has been shown (below).
+ UIWindow *uiWindow = self.qtViewController.window;
if (uiWindow.hidden) {
- // Associate UIWindow to screen and show it the first time a QWindow
- // is mapped to the screen. For external screens this means disabling
- // mirroring mode and presenting alternate content on the screen.
- uiWindow.screen = screen->uiScreen();
+ // Show the UIWindow the first time a QWindow is mapped to the screen.
+ // For the main screen this hides the launch screen, while for external
+ // screens this disables mirroring of the main screen, so the external
+ // screen can be used for alternate content.
uiWindow.hidden = NO;
}
}
+#if !defined(Q_OS_VISIONOS)
- (void)willRemoveSubview:(UIView *)subview
{
Q_UNUSED(subview);
- Q_ASSERT(self.window);
UIWindow *uiWindow = self.window;
+ // uiWindow can be null when closing from the ios "app manager" and the app is
+ // showing a native window like UIDocumentBrowserViewController
+ if (!uiWindow)
+ return;
if (uiWindow.screen != [UIScreen mainScreen] && self.subviews.count == 1) {
// We're about to remove the last view of an external screen, so go back
@@ -116,10 +122,10 @@
// to ensure that we don't try to layout the view that's being removed.
dispatch_async(dispatch_get_main_queue(), ^{
uiWindow.hidden = YES;
- uiWindow.screen = [UIScreen mainScreen];
});
}
}
+#endif
- (void)layoutSubviews
{
@@ -225,15 +231,14 @@
@synthesize preferredStatusBarStyle;
#endif
-- (instancetype)initWithQIOSScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen
+- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen
{
if (self = [self init]) {
+ self.window = window;
self.platformScreen = screen;
self.changingOrientation = NO;
#ifndef Q_OS_TVOS
- self.lockedOrientation = UIInterfaceOrientationUnknown;
-
// Status bar may be initially hidden at startup through Info.plist
self.prefersStatusBarHidden = infoPlistValue(@"UIStatusBarHidden", false);
self.preferredStatusBarUpdateAnimation = UIStatusBarAnimationNone;
@@ -280,7 +285,7 @@
Q_ASSERT(!qt_apple_isApplicationExtension());
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(willChangeStatusBarFrame:)
name:UIApplicationWillChangeStatusBarFrameNotification
@@ -290,6 +295,15 @@
name:UIApplicationDidChangeStatusBarOrientationNotification
object:qt_apple_sharedApplication()];
#endif
+
+ // Make sure any top level windows that have already been created
+ // for this screen are reparented into our desktop manager view.
+ for (auto *window : qGuiApp->topLevelWindows()) {
+ if (window->screen()->handle() != self.platformScreen)
+ continue;
+ if (auto *platformWindow = window->handle())
+ platformWindow->setParent(nullptr);
+ }
}
- (void)viewDidUnload
@@ -300,26 +314,6 @@
// -------------------------------------------------------------------------
-- (BOOL)shouldAutorotate
-{
-#ifndef Q_OS_TVOS
- return self.platformScreen && self.platformScreen->uiScreen() == [UIScreen mainScreen] && !self.lockedOrientation;
-#else
- return NO;
-#endif
-}
-
-- (NSUInteger)supportedInterfaceOrientations
-{
- // As documented by Apple in the iOS 6.0 release notes, setStatusBarOrientation:animated:
- // only works if the supportedInterfaceOrientations of the view controller is 0, making
- // us responsible for ensuring that the status bar orientation is consistent. We enter
- // this mode when auto-rotation is disabled due to an explicit content orientation being
- // set on the focus window. Note that this is counter to what the documentation for
- // supportedInterfaceOrientations says, which states that the method should not return 0.
- return [self shouldAutorotate] ? UIInterfaceOrientationMaskAll : 0;
-}
-
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
{
self.changingOrientation = YES;
@@ -334,6 +328,7 @@
[super didRotateFromInterfaceOrientation:orientation];
}
+#if !defined(Q_OS_VISIONOS)
- (void)willChangeStatusBarFrame:(NSNotification*)notification
{
Q_UNUSED(notification);
@@ -377,6 +372,7 @@
[self.view setNeedsLayout];
}
+#endif
- (void)viewWillLayoutSubviews
{
@@ -397,10 +393,12 @@
if (!self.platformScreen || !self.platformScreen->screen())
return;
+#if !defined(Q_OS_VISIONOS)
// For now we only care about the main screen, as both the statusbar
// visibility and orientation is only appropriate for the main screen.
if (self.platformScreen->uiScreen() != [UIScreen mainScreen])
return;
+#endif
// Prevent recursion caused by updating the status bar appearance (position
// or visibility), which in turn may cause a layout of our subviews, and
@@ -427,7 +425,7 @@
// All decisions are based on the top level window
focusWindow = qt_window_private(focusWindow)->topLevelWindow();
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
// -------------- Status bar style and visbility ---------------
@@ -447,51 +445,6 @@
[self setNeedsStatusBarAppearanceUpdate];
[self.view setNeedsLayout];
}
-
-
- // -------------- Content orientation ---------------
-
- UIApplication *uiApplication = qt_apple_sharedApplication();
-
- static BOOL kAnimateContentOrientationChanges = YES;
-
- Qt::ScreenOrientation contentOrientation = focusWindow->contentOrientation();
- if (contentOrientation != Qt::PrimaryOrientation) {
- // An explicit content orientation has been reported for the focus window,
- // so we keep the status bar in sync with content orientation. This will ensure
- // that the task bar (and associated gestures) are also rotated accordingly.
-
- if (!self.lockedOrientation) {
- // We are moving from Qt::PrimaryOrientation to an explicit orientation,
- // so we need to store the current statusbar orientation, as we need it
- // later when mapping screen coordinates for QScreen and for returning
- // to Qt::PrimaryOrientation.
- self.lockedOrientation = uiApplication.statusBarOrientation;
- }
-
- [uiApplication setStatusBarOrientation:
- UIInterfaceOrientation(fromQtScreenOrientation(contentOrientation))
- animated:kAnimateContentOrientationChanges];
-
- } else {
- // The content orientation is set to Qt::PrimaryOrientation, meaning
- // that auto-rotation should be enabled. But we may be coming out of
- // a state of locked orientation, which needs some cleanup before we
- // can enable auto-rotation again.
- if (self.lockedOrientation) {
- // First we need to restore the statusbar to what it was at the
- // time of locking the orientation, otherwise iOS will be very
- // confused when it starts doing auto-rotation again.
- [uiApplication setStatusBarOrientation:self.lockedOrientation
- animated:kAnimateContentOrientationChanges];
-
- // Then we can re-enable auto-rotation
- self.lockedOrientation = UIInterfaceOrientationUnknown;
-
- // And finally let iOS rotate the root view to match the device orientation
- [UIViewController attemptRotationToDeviceOrientation];
- }
- }
#endif
}
diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h
index 988269a855..88afee80c3 100644
--- a/src/plugins/platforms/ios/qioswindow.h
+++ b/src/plugins/platforms/ios/qioswindow.h
@@ -21,14 +21,13 @@ class QIOSWindow : public QObject, public QPlatformWindow
Q_OBJECT
public:
- explicit QIOSWindow(QWindow *window);
+ explicit QIOSWindow(QWindow *window, WId nativeHandle = 0);
~QIOSWindow();
void setGeometry(const QRect &rect) override;
void setWindowState(Qt::WindowStates state) override;
void setParent(const QPlatformWindow *window) override;
- void handleContentOrientationChange(Qt::ScreenOrientation orientation) override;
void setVisible(bool visible) override;
void setOpacity(qreal level) override;
@@ -56,13 +55,20 @@ public:
void requestUpdate() override;
+ void setMask(const QRegion &region) override;
+
+#if QT_CONFIG(opengl)
CAEAGLLayer *eaglLayer() const;
+#endif
+
+ bool isForeignWindow() const override;
+ UIView *view() const;
private:
void applicationStateChanged(Qt::ApplicationState state);
void applyGeometry(const QRect &rect);
- QUIView *m_view;
+ UIView *m_view;
QRect m_normalGeometry;
int m_windowLevel;
@@ -78,6 +84,8 @@ private:
QDebug operator<<(QDebug debug, const QIOSWindow *window);
#endif
+QT_MANGLE_NAMESPACE(QUIView) *quiview_cast(UIView *view);
+
QT_END_NAMESPACE
#endif // QIOSWINDOW_H
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index e88f5ab3d0..6a1080e238 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -4,7 +4,6 @@
#include "qioswindow.h"
#include "qiosapplicationdelegate.h"
-#include "qioscontext.h"
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
@@ -12,11 +11,17 @@
#include "quiview.h"
#include "qiosinputcontext.h"
+#include <QtCore/private/qcore_mac_p.h>
+
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qplatformintegration.h>
+#if QT_CONFIG(opengl)
#import <QuartzCore/CAEAGLLayer.h>
-#ifdef Q_OS_IOS
+#endif
+
+#if QT_CONFIG(metal)
#import <QuartzCore/CAMetalLayer.h>
#endif
@@ -24,34 +29,48 @@
QT_BEGIN_NAMESPACE
-QIOSWindow::QIOSWindow(QWindow *window)
+enum {
+ defaultWindowWidth = 160,
+ defaultWindowHeight = 160
+};
+
+QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle)
: QPlatformWindow(window)
, m_windowLevel(0)
{
-#ifdef Q_OS_IOS
- if (window->surfaceType() == QSurface::RasterSurface)
- window->setSurfaceType(QSurface::MetalSurface);
+ if (nativeHandle) {
+ m_view = reinterpret_cast<UIView *>(nativeHandle);
+ [m_view retain];
+ } else {
+#if QT_CONFIG(metal)
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::MetalSurface);
- if (window->surfaceType() == QSurface::MetalSurface)
- m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
- else
+ if (window->surfaceType() == QSurface::MetalSurface)
+ m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
+ else
#endif
- m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ }
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged);
- setParent(QPlatformWindow::parent());
+ if (QPlatformWindow::parent())
+ setParent(QPlatformWindow::parent());
- // Resolve default window geometry in case it was not set before creating the
- // platform window. This picks up eg. minimum-size if set, and defaults to
- // the "maxmized" geometry (even though we're not in that window state).
- // FIXME: Detect if we apply a maximized geometry and send a window state
- // change event in that case.
- m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
- screen()->availableGeometry().width(), screen()->availableGeometry().height());
+ if (!isForeignWindow()) {
+ // Resolve default window geometry in case it was not set before creating the
+ // platform window. This picks up eg. minimum-size if set.
+ m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
+ defaultWindowWidth, defaultWindowHeight);
- setWindowState(window->windowStates());
- setOpacity(window->opacity());
+ setWindowState(window->windowStates());
+ setOpacity(window->opacity());
+ setMask(QHighDpi::toNativeLocalRegion(window->mask(), window));
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
Qt::ScreenOrientation initialOrientation = window->contentOrientation();
if (initialOrientation != Qt::PrimaryOrientation) {
@@ -73,8 +92,15 @@ QIOSWindow::~QIOSWindow()
[m_view touchesCancelled:[NSSet set] withEvent:0];
clearAccessibleCache();
- m_view.platformWindow = 0;
- [m_view removeFromSuperview];
+
+ quiview_cast(m_view).platformWindow = nullptr;
+
+ // Remove from superview, unless we're a foreign window without a
+ // Qt window parent, in which case the foreign window is used as
+ // a window container for a Qt UI hierarchy inside a native UI.
+ if (!(isForeignWindow() && !QPlatformWindow::parent()))
+ [m_view removeFromSuperview];
+
[m_view release];
}
@@ -113,7 +139,7 @@ void QIOSWindow::setVisible(bool visible)
if (visible && shouldAutoActivateWindow()) {
if (!window()->property("_q_showWithoutActivating").toBool())
requestActivateWindow();
- } else if (!visible && [m_view isActiveWindow]) {
+ } else if (!visible && [quiview_cast(m_view) isActiveWindow]) {
// Our window was active/focus window but now hidden, so relinquish
// focus to the next possible window in the stack.
NSArray<UIView *> *subviews = m_view.viewController.view.subviews;
@@ -202,7 +228,7 @@ void QIOSWindow::applyGeometry(const QRect &rect)
QMargins QIOSWindow::safeAreaMargins() const
{
- UIEdgeInsets safeAreaInsets = m_view.qt_safeAreaInsets;
+ UIEdgeInsets safeAreaInsets = m_view.safeAreaInsets;
return QMargins(safeAreaInsets.left, safeAreaInsets.top,
safeAreaInsets.right, safeAreaInsets.bottom);
}
@@ -226,22 +252,45 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
if (state & Qt::WindowMinimized) {
applyGeometry(QRect());
} else if (state & (Qt::WindowFullScreen | Qt::WindowMaximized)) {
- // When an application is in split-view mode, the UIScreen still has the
- // same geometry, but the UIWindow is resized to the area reserved for the
- // application. We use this to constrain the geometry used when applying the
- // fullscreen or maximized window states. Note that we do not do this
- // in applyGeometry(), as we don't want to artificially limit window
- // placement "outside" of the screen bounds if that's what the user wants.
-
QRect uiWindowBounds = QRectF::fromCGRect(m_view.window.bounds).toRect();
- QRect fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
- QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
- fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
+ if (NSProcessInfo.processInfo.iOSAppOnMac) {
+ // iOS apps running as "Designed for iPad" on macOS do not match
+ // our current window management implementation where a single
+ // UIWindow is tied to a single screen. And even if we're on the
+ // right screen, the UIScreen does not account for the 77% scale
+ // of the UIUserInterfaceIdiomPad environment, so we can't use
+ // it to clamp the window geometry. Instead just use the UIWindow
+ // directly, which represents our "screen".
+ applyGeometry(uiWindowBounds);
+ } else if (isRunningOnVisionOS()) {
+ // On visionOS there is no concept of a screen, and hence no concept of
+ // screen-relative system UI that we should keep top level windows away
+ // from, so don't apply the UIWindow safe area insets to the screen.
+ applyGeometry(uiWindowBounds);
+ } else {
+ QRect fullscreenGeometry = screen()->geometry();
+ QRect maximizedGeometry = fullscreenGeometry;
+
+#if !defined(Q_OS_VISIONOS)
+ if (!(window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint)) {
+ // If the safe area margins reflect the screen's outer edges,
+ // then reduce the maximized geometry accordingly. Otherwise
+ // leave it as is, and assume the client will take the safe
+ // are margins into account explicitly.
+ UIScreen *uiScreen = m_view.window.windowScene.screen;
+ UIEdgeInsets safeAreaInsets = m_view.window.safeAreaInsets;
+ if (m_view.window.bounds.size.width == uiScreen.bounds.size.width)
+ maximizedGeometry.adjust(safeAreaInsets.left, 0, -safeAreaInsets.right, 0);
+ if (m_view.window.bounds.size.height == uiScreen.bounds.size.height)
+ maximizedGeometry.adjust(0, safeAreaInsets.top, 0, -safeAreaInsets.bottom);
+ }
+#endif
- if (state & Qt::WindowFullScreen)
- applyGeometry(fullscreenGeometry);
- else
- applyGeometry(maximizedGeometry);
+ if (state & Qt::WindowFullScreen)
+ applyGeometry(fullscreenGeometry.intersected(uiWindowBounds));
+ else
+ applyGeometry(maximizedGeometry.intersected(uiWindowBounds));
+ }
} else {
applyGeometry(m_normalGeometry);
}
@@ -249,10 +298,16 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
{
- UIView *parentView = parentWindow ? reinterpret_cast<UIView *>(parentWindow->winId())
- : isQtApplication() ? static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view : 0;
-
- [parentView addSubview:m_view];
+ UIView *superview = nullptr;
+ if (parentWindow)
+ superview = reinterpret_cast<UIView *>(parentWindow->winId());
+ else if (isQtApplication() && !isForeignWindow())
+ superview = rootViewForScreen(window()->screen());
+
+ if (superview)
+ [superview addSubview:m_view];
+ else if (quiview_cast(m_view.superview))
+ [m_view removeFromSuperview];
}
void QIOSWindow::requestActivateWindow()
@@ -263,7 +318,6 @@ void QIOSWindow::requestActivateWindow()
if (blockedByModal())
return;
- Q_ASSERT(m_view.window);
[m_view.window makeKeyWindow];
[m_view becomeFirstResponder];
@@ -273,8 +327,6 @@ void QIOSWindow::requestActivateWindow()
void QIOSWindow::raiseOrLower(bool raise)
{
- // Re-insert m_view at the correct index among its sibling views
- // (QWindows) according to their current m_windowLevel:
if (!isQtApplication())
return;
@@ -282,17 +334,27 @@ void QIOSWindow::raiseOrLower(bool raise)
if (subviews.count == 1)
return;
- for (int i = int(subviews.count) - 1; i >= 0; --i) {
- UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
- if (view.hidden || view == m_view || !view.qwindow)
- continue;
- int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
- if (m_windowLevel > level || (raise && m_windowLevel == level)) {
- [m_view.superview insertSubview:m_view aboveSubview:view];
- return;
+ if (m_view.superview == m_view.qtViewController.view) {
+ // We're a top level window, so we need to take window
+ // levels into account.
+ for (int i = int(subviews.count) - 1; i >= 0; --i) {
+ UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
+ if (view.hidden || view == m_view || !view.qwindow)
+ continue;
+ int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
+ if (m_windowLevel > level || (raise && m_windowLevel == level)) {
+ [m_view.superview insertSubview:m_view aboveSubview:view];
+ return;
+ }
}
+ [m_view.superview insertSubview:m_view atIndex:0];
+ } else {
+ // Child window, or embedded into a non-Qt view controller
+ if (raise)
+ [m_view.superview bringSubviewToFront:m_view];
+ else
+ [m_view.superview sendSubviewToBack:m_view];
}
- [m_view.superview insertSubview:m_view atIndex:0];
}
void QIOSWindow::updateWindowLevel()
@@ -321,20 +383,13 @@ void QIOSWindow::updateWindowLevel()
m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel);
}
-void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation)
-{
- // Update the QWindow representation straight away, so that
- // we can update the statusbar orientation based on the new
- // content orientation.
- qt_window_private(window())->contentOrientation = orientation;
-
- [m_view.qtViewController updateProperties];
-}
-
void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
{
+ if (isForeignWindow())
+ return;
+
if (window()->isExposed() != isExposed())
- [m_view sendUpdatedExposeEvent];
+ [quiview_cast(m_view) sendUpdatedExposeEvent];
}
qreal QIOSWindow::devicePixelRatio() const
@@ -344,7 +399,10 @@ qreal QIOSWindow::devicePixelRatio() const
void QIOSWindow::clearAccessibleCache()
{
- [m_view clearAccessibleCache];
+ if (isForeignWindow())
+ return;
+
+ [quiview_cast(m_view) clearAccessibleCache];
}
void QIOSWindow::requestUpdate()
@@ -352,11 +410,27 @@ void QIOSWindow::requestUpdate()
static_cast<QIOSScreen *>(screen())->setUpdatesPaused(false);
}
+void QIOSWindow::setMask(const QRegion &region)
+{
+ if (!region.isEmpty()) {
+ QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
+ for (const QRect &r : region)
+ CGPathAddRect(maskPath, nullptr, r.toCGRect());
+ CAShapeLayer *maskLayer = [CAShapeLayer layer];
+ maskLayer.path = maskPath;
+ m_view.layer.mask = maskLayer;
+ } else {
+ m_view.layer.mask = nil;
+ }
+}
+
+#if QT_CONFIG(opengl)
CAEAGLLayer *QIOSWindow::eaglLayer() const
{
Q_ASSERT([m_view.layer isKindOfClass:[CAEAGLLayer class]]);
return static_cast<CAEAGLLayer *>(m_view.layer);
}
+#endif
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QIOSWindow *window)
@@ -371,6 +445,37 @@ QDebug operator<<(QDebug debug, const QIOSWindow *window)
}
#endif // !QT_NO_DEBUG_STREAM
+/*!
+ Returns the view cast to a QUIview if possible.
+
+ If the view is not a QUIview, nil is returned, which is safe to
+ send messages to, effectively making [quiview_cast(view) message]
+ a no-op.
+
+ For extra verbosity and clearer code, please consider checking
+ that the platform window is not a foreign window before using
+ this cast, via QPlatformWindow::isForeignWindow().
+
+ Do not use this method solely to check for foreign windows, as
+ that will make the code harder to read for people not working
+ primarily on iOS, who do not know the difference between the
+ UIView and QUIView cases.
+*/
+QUIView *quiview_cast(UIView *view)
+{
+ return qt_objc_cast<QUIView *>(view);
+}
+
+bool QIOSWindow::isForeignWindow() const
+{
+ return ![m_view isKindOfClass:QUIView.class];
+}
+
+UIView *QIOSWindow::view() const
+{
+ return m_view;
+}
+
QT_END_NAMESPACE
#include "moc_qioswindow.cpp"
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.h b/src/plugins/platforms/ios/quiaccessibilityelement.h
index e78fef6d30..8580325436 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.h
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.h
@@ -14,7 +14,7 @@
@property (readonly) QAccessible::Id axid;
- (instancetype)initWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view;
-+ (instancetype)elementWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view;
++ (instancetype)elementWithId:(QAccessible::Id)anId;
@end
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm
index 08e366f32b..39b2cb8a50 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.mm
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm
@@ -8,6 +8,7 @@
#include "private/qaccessiblecache_p.h"
#include "private/qcore_mac_p.h"
#include "uistrings_p.h"
+#include "qioswindow.h"
QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
@@ -23,7 +24,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
return self;
}
-+ (instancetype)elementWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view
++ (instancetype)elementWithId:(QAccessible::Id)anId
{
Q_ASSERT(anId);
if (!anId)
@@ -33,9 +34,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
QMacAccessibilityElement *element = cache->elementForId(anId);
if (!element) {
- Q_ASSERT(QAccessible::accessibleInterface(anId));
- element = [[self alloc] initWithId:anId withAccessibilityContainer:view];
- cache->insertElement(anId, element);
+ auto *a11yInterface = QAccessible::accessibleInterface(anId);
+ Q_ASSERT(a11yInterface);
+ auto *window = a11yInterface->window();
+ if (window && window->handle()) {
+ auto *platformWindow = static_cast<QIOSWindow*>(window->handle());
+ element = [[self alloc] initWithId:anId withAccessibilityContainer:platformWindow->view()];
+ cache->insertElement(anId, element);
+ } else {
+ qWarning() << "Could not create a11y element for" << window
+ << "with platform window" << (window ? window->handle() : nullptr);
+ }
}
return element;
}
diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h
index 6d1d19fd3d..7899ec6e0e 100644
--- a/src/plugins/platforms/ios/quiview.h
+++ b/src/plugins/platforms/ios/quiview.h
@@ -32,10 +32,9 @@ QT_END_NAMESPACE
- (QWindow *)qwindow;
- (UIViewController *)viewController;
- (QIOSViewController*)qtViewController;
-@property (nonatomic, readonly) UIEdgeInsets qt_safeAreaInsets;
@end
-#ifdef Q_OS_IOS
+#if QT_CONFIG(metal)
@interface QUIMetalView : QUIView
@end
#endif
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index bb6d51355f..d5808db305 100644
--- a/src/plugins/platforms/ios/quiview.mm
+++ b/src/plugins/platforms/ios/quiview.mm
@@ -10,6 +10,7 @@
#include "qiosscreen.h"
#include "qioswindow.h"
#include "qiosinputcontext.h"
+#include "quiwindow.h"
#ifndef Q_OS_TVOS
#include "qiosmenu.h"
#endif
@@ -53,7 +54,6 @@ inline ulong getTimeStamp(UIEvent *event)
@implementation QUIView {
QHash<NSUInteger, QWindowSystemInterface::TouchPoint> m_activeTouches;
UITouch *m_activePencilTouch;
- int m_nextTouchId;
NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
UIPanGestureRecognizer *m_scrollGestureRecognizer;
CGPoint m_lastScrollCursorPos;
@@ -62,7 +62,7 @@ inline ulong getTimeStamp(UIEvent *event)
+ (void)load
{
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 11)) {
// iOS 11 handles this though [UIView safeAreaInsetsDidChange], but there's no signal for
// the corresponding top and bottom layout guides that we use on earlier versions. Note
@@ -82,7 +82,10 @@ inline ulong getTimeStamp(UIEvent *event)
+ (Class)layerClass
{
+#if QT_CONFIG(opengl)
return [CAEAGLLayer class];
+#endif
+ return [super layerClass];
}
- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
@@ -105,6 +108,17 @@ inline ulong getTimeStamp(UIEvent *event)
m_lastScrollDelta = CGPointZero;
m_lastScrollCursorPos = CGPointZero;
[self addGestureRecognizer:m_scrollGestureRecognizer];
+
+ if ([self.layer isKindOfClass:CAMetalLayer.class]) {
+ QWindow *window = self.platformWindow->window();
+ if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
+ QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
+ QCFType<CGColorSpaceRef> cgColorSpace = CGColorSpaceCreateWithICCData(iccData);
+ CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer);
+ metalLayer.colorspace = cgColorSpace;
+ qCDebug(lcQpaWindow) << "Set" << self << "color space to" << metalLayer.colorspace;
+ }
+ }
}
return self;
@@ -113,6 +127,7 @@ inline ulong getTimeStamp(UIEvent *event)
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
+#if QT_CONFIG(opengl)
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
// Set up EAGL layer
CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer);
@@ -122,6 +137,7 @@ inline ulong getTimeStamp(UIEvent *event)
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
};
}
+#endif
if (isQtApplication())
self.hidden = YES;
@@ -182,6 +198,7 @@ inline ulong getTimeStamp(UIEvent *event)
return description;
}
+#if !defined(Q_OS_VISIONOS)
- (void)willMoveToWindow:(UIWindow *)newWindow
{
// UIKIt will normally set the scale factor of a view to match the corresponding
@@ -191,6 +208,7 @@ inline ulong getTimeStamp(UIEvent *event)
// FIXME: Allow the scale factor to be customized through QSurfaceFormat.
}
+#endif
- (void)didAddSubview:(UIView *)subview
{
@@ -248,6 +266,9 @@ inline ulong getTimeStamp(UIEvent *event)
Q_UNUSED(layer);
Q_ASSERT(layer == self.layer);
+ if (!self.platformWindow)
+ return;
+
[self sendUpdatedExposeEvent];
}
@@ -289,7 +310,7 @@ inline ulong getTimeStamp(UIEvent *event)
// blocked by this guard.
FirstResponderCandidate firstResponderCandidate(self);
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
@@ -300,7 +321,7 @@ inline ulong getTimeStamp(UIEvent *event)
}
if (qGuiApp->focusWindow() != self.platformWindow->window())
- QWindowSystemInterface::handleWindowActivated(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
else
qImDebug() << self.platformWindow->window() << "already active, not sending window activation";
@@ -328,7 +349,7 @@ inline ulong getTimeStamp(UIEvent *event)
- (BOOL)resignFirstResponder
{
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super resignFirstResponder])
return NO;
@@ -337,7 +358,7 @@ inline ulong getTimeStamp(UIEvent *event)
UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
if ([self responderShouldTriggerWindowDeactivation:newResponder])
- QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
return YES;
}
@@ -351,7 +372,7 @@ inline ulong getTimeStamp(UIEvent *event)
if ([self isFirstResponder])
return YES;
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
if ([firstResponder isKindOfClass:[QIOSTextInputResponder class]]
&& [firstResponder nextResponder] == self)
return YES;
@@ -502,7 +523,10 @@ inline ulong getTimeStamp(UIEvent *event)
{
Q_ASSERT(!m_activeTouches.contains(touch.hash));
#endif
- m_activeTouches[touch.hash].id = m_nextTouchId++;
+ // Use window-independent touch identifiers, so that
+ // multi-touch works across windows.
+ static quint16 nextTouchId = 0;
+ m_activeTouches[touch.hash].id = nextTouchId++;
#if QT_CONFIG(tabletevent)
}
#endif
@@ -544,9 +568,6 @@ inline ulong getTimeStamp(UIEvent *event)
// tvOS only supports single touch
m_activeTouches.clear();
#endif
-
- if (m_activeTouches.isEmpty() && !m_activePencilTouch)
- m_nextTouchId = 0;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
@@ -579,7 +600,6 @@ inline ulong getTimeStamp(UIEvent *event)
qWarning("Subset of active touches cancelled by UIKit");
m_activeTouches.clear();
- m_nextTouchId = 0;
m_activePencilTouch = nil;
ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000);
@@ -683,7 +703,7 @@ inline ulong getTimeStamp(UIEvent *event)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
// Check first if QIOSMenu should handle the action before continuing up the responder chain
return [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender] != 0;
#else
@@ -696,7 +716,7 @@ inline ulong getTimeStamp(UIEvent *event)
- (id)forwardingTargetForSelector:(SEL)selector
{
Q_UNUSED(selector);
-#ifndef Q_OS_TVOS
+#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
return QIOSMenu::menuActionTarget();
#else
return nil;
@@ -812,14 +832,9 @@ inline ulong getTimeStamp(UIEvent *event)
return nil;
}
-- (UIEdgeInsets)qt_safeAreaInsets
-{
- return self.safeAreaInsets;
-}
-
@end
-#ifdef Q_OS_IOS
+#if QT_CONFIG(metal)
@implementation QUIMetalView
+ (Class)layerClass
diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm
index 366141ef81..04e1f8cfb3 100644
--- a/src/plugins/platforms/ios/quiview_accessibility.mm
+++ b/src/plugins/platforms/ios/quiview_accessibility.mm
@@ -54,7 +54,6 @@
- (void)clearAccessibleCache
{
[m_accessibleElements removeAllObjects];
- UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, @"");
}
// this is a container, returning yes here means the functions below will never be called
diff --git a/src/plugins/platforms/ios/quiwindow.h b/src/plugins/platforms/ios/quiwindow.h
new file mode 100644
index 0000000000..b5587411e4
--- /dev/null
+++ b/src/plugins/platforms/ios/quiwindow.h
@@ -0,0 +1,13 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QUIWINDOW_H
+#define QUIWINDOW_H
+
+#include <UIKit/UIWindow.h>
+
+@interface QUIWindow : UIWindow
+@property (nonatomic, readonly) BOOL sendingEvent;
+@end
+
+#endif // QUIWINDOW_H
diff --git a/src/plugins/platforms/ios/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm
new file mode 100644
index 0000000000..7c910b6d9e
--- /dev/null
+++ b/src/plugins/platforms/ios/quiwindow.mm
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "quiwindow.h"
+
+#include "qiostheme.h"
+
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+
+#include <UIKit/UIKit.h>
+
+@implementation QUIWindow
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if ((self = [super initWithFrame:frame]))
+ self->_sendingEvent = NO;
+
+ return self;
+}
+
+- (void)sendEvent:(UIEvent *)event
+{
+ QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES);
+ [super sendEvent:event];
+}
+
+#if !defined(Q_OS_VISIONOS)
+- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
+{
+ [super traitCollectionDidChange:previousTraitCollection];
+
+ if (!qGuiApp)
+ return;
+
+ Qt::ColorScheme colorScheme = self.traitCollection.userInterfaceStyle
+ == UIUserInterfaceStyleDark
+ ? Qt::ColorScheme::Dark
+ : Qt::ColorScheme::Light;
+
+ if (self.screen == UIScreen.mainScreen) {
+ // Check if the current userInterfaceStyle reports a different appearance than
+ // the platformTheme's appearance. We might have set that one based on the UIScreen
+ if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle
+ || QGuiApplicationPrivate::platformTheme()->colorScheme() != colorScheme) {
+ QIOSTheme::initializeSystemPalette();
+ QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
+ }
+ }
+}
+#endif
+
+@end
diff --git a/src/plugins/platforms/linuxfb/CMakeLists.txt b/src/plugins/platforms/linuxfb/CMakeLists.txt
index 7f950c1957..9f75f53828 100644
--- a/src/plugins/platforms/linuxfb/CMakeLists.txt
+++ b/src/plugins/platforms/linuxfb/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from linuxfb.pro.
-
#####################################################################
## QLinuxFbIntegrationPlugin Plugin:
#####################################################################
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QLinuxFbIntegrationPlugin
OUTPUT_NAME qlinuxfb
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES linuxfb # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES linuxfb
SOURCES
main.cpp
qlinuxfbintegration.cpp qlinuxfbintegration.h
@@ -25,9 +23,6 @@ qt_internal_add_plugin(QLinuxFbIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:linuxfb.pro:<TRUE>:
-# OTHER_FILES = "linuxfb.json"
-
## Scopes:
#####################################################################
@@ -42,6 +37,3 @@ qt_internal_extend_target(QLinuxFbIntegrationPlugin CONDITION TARGET Qt::KmsSupp
LIBRARIES
Qt::KmsSupportPrivate
)
-
-#### Keys ignored in scope 4:.:.:linuxfb.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/minimal/CMakeLists.txt b/src/plugins/platforms/minimal/CMakeLists.txt
index 21377fb466..f3683deccf 100644
--- a/src/plugins/platforms/minimal/CMakeLists.txt
+++ b/src/plugins/platforms/minimal/CMakeLists.txt
@@ -1,18 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from minimal.pro.
-
#####################################################################
## QMinimalIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype) # special case
+qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype)
qt_internal_add_plugin(QMinimalIntegrationPlugin
OUTPUT_NAME qminimal
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimal # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimal
SOURCES
main.cpp
qminimalbackingstore.cpp qminimalbackingstore.h
@@ -26,9 +24,6 @@ qt_internal_add_plugin(QMinimalIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:minimal.pro:<TRUE>:
-# OTHER_FILES = "minimal.json"
-
## Scopes:
#####################################################################
@@ -36,6 +31,3 @@ qt_internal_extend_target(QMinimalIntegrationPlugin CONDITION QT_FEATURE_freetyp
LIBRARIES
WrapFreetype::WrapFreetype
)
-
-#### Keys ignored in scope 3:.:.:minimal.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/minimalegl/CMakeLists.txt b/src/plugins/platforms/minimalegl/CMakeLists.txt
index 6b892e6483..a6ec8be781 100644
--- a/src/plugins/platforms/minimalegl/CMakeLists.txt
+++ b/src/plugins/platforms/minimalegl/CMakeLists.txt
@@ -1,8 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from minimalegl.pro.
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
#####################################################################
## QMinimalEglIntegrationPlugin Plugin:
@@ -11,7 +10,7 @@ qt_find_package(EGL) # special case
qt_internal_add_plugin(QMinimalEglIntegrationPlugin
OUTPUT_NAME qminimalegl
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimalegl # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimalegl
SOURCES
main.cpp
qminimaleglintegration.cpp qminimaleglintegration.h
@@ -24,12 +23,9 @@ qt_internal_add_plugin(QMinimalEglIntegrationPlugin
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 1:.:.:minimalegl.pro:<TRUE>:
-# OTHER_FILES = "minimalegl.json"
-
## Scopes:
#####################################################################
@@ -39,6 +35,3 @@ qt_internal_extend_target(QMinimalEglIntegrationPlugin CONDITION QT_FEATURE_open
LIBRARIES
Qt::OpenGL
)
-
-#### Keys ignored in scope 3:.:.:minimalegl.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/offscreen/CMakeLists.txt b/src/plugins/platforms/offscreen/CMakeLists.txt
index cb911638c0..09ad9a384d 100644
--- a/src/plugins/platforms/offscreen/CMakeLists.txt
+++ b/src/plugins/platforms/offscreen/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from offscreen.pro.
-
#####################################################################
## QOffscreenIntegrationPlugin Plugin:
#####################################################################
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QOffscreenIntegrationPlugin
OUTPUT_NAME qoffscreen
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES offscreen # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES offscreen
SOURCES
main.cpp
qoffscreencommon.cpp qoffscreencommon.h
@@ -25,9 +23,6 @@ qt_internal_add_plugin(QOffscreenIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:offscreen.pro:<TRUE>:
-# OTHER_FILES = "offscreen.json"
-
## Scopes:
#####################################################################
@@ -37,6 +32,3 @@ qt_internal_extend_target(QOffscreenIntegrationPlugin CONDITION QT_FEATURE_openg
LIBRARIES
X11::X11
)
-
-#### Keys ignored in scope 3:.:.:offscreen.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
index 20ed0ed91b..1a1471c687 100644
--- a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
+++ b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
@@ -86,7 +86,7 @@ void QOffscreenWindow::setVisible(bool visible)
if (visible) {
if (window()->type() != Qt::ToolTip)
- QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
if (m_pendingGeometryChangeOnShow) {
m_pendingGeometryChangeOnShow = false;
@@ -122,7 +122,7 @@ void QOffscreenWindow::setVisible(bool visible)
void QOffscreenWindow::requestActivateWindow()
{
if (m_visible)
- QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
}
WId QOffscreenWindow::winId() const
diff --git a/src/plugins/platforms/qnx/CMakeLists.txt b/src/plugins/platforms/qnx/CMakeLists.txt
index 2b16f86789..9fb412d8a4 100644
--- a/src/plugins/platforms/qnx/CMakeLists.txt
+++ b/src/plugins/platforms/qnx/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from qnx.pro.
-
#####################################################################
## QQnxIntegrationPlugin Plugin:
#####################################################################
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QQnxIntegrationPlugin
OUTPUT_NAME qqnx
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES qnx # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES qnx
SOURCES
main.cpp main.h
qqnxabstractcover.h
@@ -33,6 +31,12 @@ qt_internal_add_plugin(QQnxIntegrationPlugin
qqnxscreeneventthread.cpp qqnxscreeneventthread.h
qqnxservices.cpp qqnxservices.h
qqnxwindow.cpp qqnxwindow.h
+ NO_PCH_SOURCES
+ qqnxclipboard.cpp # undef QT_NO_FOREACH
+ qqnxintegration.cpp # undef QT_NO_FOREACH
+ qqnxscreen.cpp # undef QT_NO_FOREACH
+ qqnxscreeneventhandler.cpp # undef QT_NO_FOREACH
+ qqnxwindow.cpp # undef QT_NO_FOREACH
LIBRARIES
Qt::Core
Qt::CorePrivate
@@ -41,9 +45,6 @@ qt_internal_add_plugin(QQnxIntegrationPlugin
screen
)
-#### Keys ignored in scope 1:.:.:qnx.pro:<TRUE>:
-# OTHER_FILES = "qnx.json"
-
## Scopes:
#####################################################################
@@ -90,6 +91,3 @@ qt_internal_extend_target(QQnxIntegrationPlugin CONDITION lgmon
LIBRARIES
lgmon
)
-
-#### Keys ignored in scope 8:.:.:qnx.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/qnx/qqnxabstractnavigator.cpp b/src/plugins/platforms/qnx/qqnxabstractnavigator.cpp
index 05389d3ea6..9b1107b7ec 100644
--- a/src/plugins/platforms/qnx/qqnxabstractnavigator.cpp
+++ b/src/plugins/platforms/qnx/qqnxabstractnavigator.cpp
@@ -6,14 +6,10 @@
#include <QDebug>
#include <QUrl>
-#if defined(QQNXNAVIGATOR_DEBUG)
-#define qNavigatorDebug qDebug
-#else
-#define qNavigatorDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaQnxNavigator, "qt.qpa.qnx.navigator");
+
QQnxAbstractNavigator::QQnxAbstractNavigator(QObject *parent)
: QObject(parent)
{
@@ -32,7 +28,7 @@ bool QQnxAbstractNavigator::invokeUrl(const QUrl &url)
// which is not recognized by the navigator anymore
const bool result = requestInvokeUrl(url.toString().toUtf8());
- qNavigatorDebug() << "url=" << url << "result=" << result;
+ qCDebug(lcQpaQnxNavigator) << Q_FUNC_INFO << "url =" << url << "result =" << result;
return result;
}
diff --git a/src/plugins/platforms/qnx/qqnxabstractnavigator.h b/src/plugins/platforms/qnx/qqnxabstractnavigator.h
index b4e4dd9bf8..fc92592307 100644
--- a/src/plugins/platforms/qnx/qqnxabstractnavigator.h
+++ b/src/plugins/platforms/qnx/qqnxabstractnavigator.h
@@ -5,9 +5,12 @@
#define QQNXABSTRACTNAVIGATOR_H
#include <QObject>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaQnxNavigator);
+
class QUrl;
class QQnxAbstractNavigator : public QObject
diff --git a/src/plugins/platforms/qnx/qqnxbuffer.cpp b/src/plugins/platforms/qnx/qqnxbuffer.cpp
index 1ea9a8b0d3..4d3b5256b2 100644
--- a/src/plugins/platforms/qnx/qqnxbuffer.cpp
+++ b/src/plugins/platforms/qnx/qqnxbuffer.cpp
@@ -10,24 +10,20 @@
#include <errno.h>
#include <sys/mman.h>
-#if defined(QQNXBUFFER_DEBUG)
-#define qBufferDebug qDebug
-#else
-#define qBufferDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaScreenBuffer, "qt.qpa.screen.buffer");
+
QQnxBuffer::QQnxBuffer()
: m_buffer(0)
{
- qBufferDebug("empty");
+ qCDebug(lcQpaScreenBuffer) << Q_FUNC_INFO << "Empty";
}
QQnxBuffer::QQnxBuffer(screen_buffer_t buffer)
: m_buffer(buffer)
{
- qBufferDebug("normal");
+ qCDebug(lcQpaScreenBuffer) << Q_FUNC_INFO << "Normal";
// Get size of buffer
int size[2];
@@ -77,7 +73,7 @@ QQnxBuffer::QQnxBuffer(screen_buffer_t buffer)
imageFormat = QImage::Format_ARGB32_Premultiplied;
break;
default:
- qFatal("QQNX: unsupported buffer format, format=%d", screenFormat);
+ qFatal(lcQpaScreenBuffer, "QQNX: unsupported buffer format, format=%d", screenFormat);
}
// wrap buffer in an image
@@ -88,27 +84,27 @@ QQnxBuffer::QQnxBuffer(const QQnxBuffer &other)
: m_buffer(other.m_buffer),
m_image(other.m_image)
{
- qBufferDebug("copy");
+ qCDebug(lcQpaScreenBuffer) << Q_FUNC_INFO << "Copy";
}
QQnxBuffer::~QQnxBuffer()
{
- qBufferDebug();
+ qCDebug(lcQpaScreenBuffer) << Q_FUNC_INFO;
}
void QQnxBuffer::invalidateInCache()
{
- qBufferDebug();
+ qCDebug(lcQpaScreenBuffer) << Q_FUNC_INFO;
// Verify native buffer exists
if (Q_UNLIKELY(!m_buffer))
- qFatal("QQNX: can't invalidate cache for null buffer");
+ qFatal(lcQpaScreenBuffer, "QQNX: can't invalidate cache for null buffer");
// Evict buffer's data from cache
errno = 0;
int result = msync(m_image.bits(), m_image.height() * m_image.bytesPerLine(), MS_INVALIDATE | MS_CACHE_ONLY);
if (Q_UNLIKELY(result != 0))
- qFatal("QQNX: failed to invalidate cache, errno=%d", errno);
+ qFatal(lcQpaScreenBuffer, "QQNX: failed to invalidate cache, errno=%d", errno);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxbuffer.h b/src/plugins/platforms/qnx/qqnxbuffer.h
index b8f1443ad1..b456eb2a62 100644
--- a/src/plugins/platforms/qnx/qqnxbuffer.h
+++ b/src/plugins/platforms/qnx/qqnxbuffer.h
@@ -5,11 +5,14 @@
#define QQNXBUFFER_H
#include <QtGui/QImage>
+#include <QtCore/QLoggingCategory>
#include <screen/screen.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreenBuffer)
+
class QQnxBuffer
{
public:
diff --git a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
index bf66d54bba..788cddea87 100644
--- a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
+++ b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
@@ -13,16 +13,12 @@
#include <QtCore/QSocketNotifier>
#include <QtCore/private/qcore_unix_p.h>
-#if defined(QQNXBUTTON_DEBUG)
-#define qButtonDebug qDebug
-#else
-#define qButtonDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
-static const char *ppsPath = "/pps/system/buttons/status";
-static const int ppsBufferSize = 256;
+Q_LOGGING_CATEGORY(lcQpaInputHwButton, "qt.qpa.input.hwbutton");
+
+const char *QQnxButtonEventNotifier::ppsPath = "/pps/system/buttons/status";
+const size_t QQnxButtonEventNotifier::ppsBufferSize = 256;
QQnxButtonEventNotifier::QQnxButtonEventNotifier(QObject *parent)
: QObject(parent),
@@ -47,7 +43,7 @@ QQnxButtonEventNotifier::~QQnxButtonEventNotifier()
void QQnxButtonEventNotifier::start()
{
- qButtonDebug("starting hardware button event processing");
+ qCDebug(lcQpaInputHwButton) << "Starting hardware button event processing";
if (m_fd != -1)
return;
@@ -64,7 +60,7 @@ void QQnxButtonEventNotifier::start()
m_readNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Read);
QObject::connect(m_readNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(updateButtonStates()));
- qButtonDebug("successfully connected to Navigator. fd = %d", m_fd);
+ qCDebug(lcQpaInputHwButton, "successfully connected to Navigator. fd = %d", m_fd);
}
void QQnxButtonEventNotifier::updateButtonStates()
@@ -75,7 +71,8 @@ void QQnxButtonEventNotifier::updateButtonStates()
// Attempt to read pps data
errno = 0;
int bytes = qt_safe_read(m_fd, buffer, ppsBufferSize - 1);
- qButtonDebug() << "Read" << bytes << "bytes of data";
+ qCDebug(lcQpaInputHwButton) << "Read" << bytes << "bytes of data";
+
if (bytes == -1) {
qWarning("QQNX: failed to read hardware buttons pps object, errno=%d", errno);
return;
@@ -88,7 +85,7 @@ void QQnxButtonEventNotifier::updateButtonStates()
// Ensure data is null terminated
buffer[bytes] = '\0';
- qButtonDebug("received PPS message:\n%s", buffer);
+ qCDebug(lcQpaInputHwButton, "Received PPS message:\n%s", buffer);
// Process received message
QByteArray ppsData = QByteArray::fromRawData(buffer, bytes);
@@ -104,7 +101,8 @@ void QQnxButtonEventNotifier::updateButtonStates()
// If state has changed, update our state and inject a keypress event
if (m_state[buttonId] != newState) {
- qButtonDebug() << "Hardware button event: button =" << key << "state =" << fields.value(key);
+ qCDebug(lcQpaInputHwButton) << "Hardware button event: button =" << key << "state =" << fields.value(key);
+
m_state[buttonId] = newState;
// Is it a key press or key release event?
@@ -129,7 +127,7 @@ void QQnxButtonEventNotifier::updateButtonStates()
break;
default:
- qButtonDebug("Unknown hardware button");
+ qCDebug(lcQpaInputHwButton) << "Unknown hardware button";
continue;
}
@@ -170,7 +168,7 @@ bool QQnxButtonEventNotifier::parsePPS(const QByteArray &ppsData, QHash<QByteArr
// tokenize current attribute
const QByteArray &attr = lines.at(i);
- qButtonDebug() << "attr=" << attr;
+ qCDebug(lcQpaInputHwButton) << Q_FUNC_INFO << "attr =" << attr;
int doubleColon = attr.indexOf(QByteArrayLiteral("::"));
if (doubleColon == -1) {
diff --git a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
index 987055f903..476119ae27 100644
--- a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
+++ b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
@@ -5,9 +5,12 @@
#define QQNXBUTTONSEVENTNOTIFIER_H
#include <QObject>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaInputHwButton);
+
class QSocketNotifier;
class QQnxButtonEventNotifier : public QObject
@@ -45,6 +48,9 @@ private:
QSocketNotifier *m_readNotifier;
ButtonState m_state[ButtonCount];
QList<QByteArray> m_buttonKeys;
+
+ static const char *ppsPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxclipboard.cpp b/src/plugins/platforms/qnx/qqnxclipboard.cpp
index 80f9d73a3b..3f27ec8069 100644
--- a/src/plugins/platforms/qnx/qqnxclipboard.cpp
+++ b/src/plugins/platforms/qnx/qqnxclipboard.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2012 Research In Motion
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#if !defined(QT_NO_CLIPBOARD)
#include "qqnxclipboard.h"
@@ -15,14 +17,10 @@
#include <clipboard/clipboard.h>
#include <errno.h>
-#if defined(QQNXCLIPBOARD_DEBUG)
-#define qClipboardDebug qDebug
-#else
-#define qClipboardDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard");
+
// null terminated array
static const char *typeList[] = {"text/html", "text/plain", "image/png", "image/jpeg", "application/x-color", 0};
@@ -64,13 +62,13 @@ public:
void addFormatToCheck(const QString &format) {
m_formatsToCheck << format;
- qClipboardDebug() << "formats=" << m_formatsToCheck;
+ qCDebug(lcQpaClipboard) << "formats=" << m_formatsToCheck;
}
bool hasFormat(const QString &mimetype) const override
{
const bool result = is_clipboard_format_present(mimetype.toUtf8().constData()) == 0;
- qClipboardDebug() << "mimetype=" << mimetype << "result=" << result;
+ qCDebug(lcQpaClipboard) << "mimetype=" << mimetype << "result=" << result;
return result;
}
@@ -83,7 +81,7 @@ public:
result << format;
}
- qClipboardDebug() << "result=" << result;
+ qCDebug(lcQpaClipboard) << "result=" << result;
return result;
}
@@ -107,7 +105,7 @@ public:
protected:
QVariant retrieveData(const QString &mimetype, QMetaType preferredType) const override
{
- qClipboardDebug() << "mimetype=" << mimetype << "preferredType=" << preferredType.name();
+ qCDebug(lcQpaClipboard) << "mimetype=" << mimetype << "preferredType=" << preferredType.name();
if (is_clipboard_format_present(mimetype.toUtf8().constData()) != 0)
return QMimeData::retrieveData(mimetype, preferredType);
@@ -119,7 +117,7 @@ private Q_SLOTS:
void releaseOwnership()
{
if (m_userMimeData) {
- qClipboardDebug() << "user data formats=" << m_userMimeData->formats() << "system formats=" << formats();
+ qCDebug(lcQpaClipboard) << "user data formats=" << m_userMimeData->formats() << "system formats=" << formats();
delete m_userMimeData;
m_userMimeData = 0;
m_clipboard->emitChanged(QClipboard::Clipboard);
@@ -165,7 +163,7 @@ void QQnxClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
}
const QStringList formats = data->formats();
- qClipboardDebug() << "formats=" << formats;
+ qCDebug(lcQpaClipboard) << "formats=" << formats;
Q_FOREACH (const QString &format, formats) {
const QByteArray buf = data->data(format);
@@ -174,7 +172,7 @@ void QQnxClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
continue;
int ret = set_clipboard_data(format.toUtf8().data(), buf.size(), buf.data());
- qClipboardDebug() << "set " << format << "to clipboard, size=" << buf.size() << ";ret=" << ret;
+ qCDebug(lcQpaClipboard) << "set " << format << "to clipboard, size=" << buf.size() << ";ret=" << ret;
if (ret)
m_mimeData->addFormatToCheck(format);
}
diff --git a/src/plugins/platforms/qnx/qqnxclipboard.h b/src/plugins/platforms/qnx/qqnxclipboard.h
index adc70b158a..66f862f0dc 100644
--- a/src/plugins/platforms/qnx/qqnxclipboard.h
+++ b/src/plugins/platforms/qnx/qqnxclipboard.h
@@ -11,6 +11,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaClipboard);
+
class QQnxClipboard : public QPlatformClipboard
{
public:
diff --git a/src/plugins/platforms/qnx/qqnxcursor.cpp b/src/plugins/platforms/qnx/qqnxcursor.cpp
index 03c4e16401..7c55dd79b3 100644
--- a/src/plugins/platforms/qnx/qqnxcursor.cpp
+++ b/src/plugins/platforms/qnx/qqnxcursor.cpp
@@ -5,14 +5,10 @@
#include <QtCore/QDebug>
-#if defined(QQNXCURSOR_DEBUG)
-#define qCursorDebug qDebug
-#else
-#define qCursorDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaQnx, "qt.qpa.qnx");
+
QQnxCursor::QQnxCursor()
{
}
@@ -27,13 +23,13 @@ void QQnxCursor::changeCursor(QCursor *windowCursor, QWindow *window)
void QQnxCursor::setPos(const QPoint &pos)
{
- qCursorDebug() << "QQnxCursor::setPos -" << pos;
+ qCDebug(lcQpaQnx) << "QQnxCursor::setPos -" << pos;
m_pos = pos;
}
QPoint QQnxCursor::pos() const
{
- qCursorDebug() << "QQnxCursor::pos -" << m_pos;
+ qCDebug(lcQpaQnx) << "QQnxCursor::pos -" << m_pos;
return m_pos;
}
diff --git a/src/plugins/platforms/qnx/qqnxcursor.h b/src/plugins/platforms/qnx/qqnxcursor.h
index 6592e0a5ee..707bdbaaa2 100644
--- a/src/plugins/platforms/qnx/qqnxcursor.h
+++ b/src/plugins/platforms/qnx/qqnxcursor.h
@@ -5,9 +5,12 @@
#define QQNXCURSOR_H
#include <qpa/qplatformcursor.h>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+// Q_DECLARE_LOGGING_CATEGORY(lcQpaQnx);
+
class QQnxCursor : public QPlatformCursor
{
public:
diff --git a/src/plugins/platforms/qnx/qqnxeglwindow.cpp b/src/plugins/platforms/qnx/qqnxeglwindow.cpp
index ed53308216..854ad46c3d 100644
--- a/src/plugins/platforms/qnx/qqnxeglwindow.cpp
+++ b/src/plugins/platforms/qnx/qqnxeglwindow.cpp
@@ -10,14 +10,10 @@
#include <errno.h>
-#if defined(QQNXEGLWINDOW_DEBUG)
-#define qEglWindowDebug qDebug
-#else
-#define qEglWindowDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaWindowEgl, "qt.qpa.window.egl");
+
QQnxEglWindow::QQnxEglWindow(QWindow *window, screen_context_t context, bool needRootWindow) :
QQnxWindow(window, context, needRootWindow),
m_newSurfaceRequested(true),
@@ -96,8 +92,8 @@ void QQnxEglWindow::createEGLSurface(QQnxGLContext *context)
EGL_NONE
};
- qEglWindowDebug() << "Creating EGL surface from" << this << context
- << window()->surfaceType() << window()->type();
+ qCDebug(lcQpaWindowEgl) << "Creating EGL surface from" << this << context
+ << window()->surfaceType() << window()->type();
// Create EGL surface
EGLSurface eglSurface = eglCreateWindowSurface(
diff --git a/src/plugins/platforms/qnx/qqnxeglwindow.h b/src/plugins/platforms/qnx/qqnxeglwindow.h
index 909a901d3c..548d9be1ee 100644
--- a/src/plugins/platforms/qnx/qqnxeglwindow.h
+++ b/src/plugins/platforms/qnx/qqnxeglwindow.h
@@ -6,9 +6,12 @@
#include "qqnxwindow.h"
#include <QtCore/QMutex>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindowEgl);
+
class QQnxGLContext;
class QQnxEglWindow : public QQnxWindow
diff --git a/src/plugins/platforms/qnx/qqnxglcontext.cpp b/src/plugins/platforms/qnx/qqnxglcontext.cpp
index 7b5b11b2e4..3d8fecf88b 100644
--- a/src/plugins/platforms/qnx/qqnxglcontext.cpp
+++ b/src/plugins/platforms/qnx/qqnxglcontext.cpp
@@ -14,14 +14,10 @@
#include <dlfcn.h>
-#if defined(QQNXGLCONTEXT_DEBUG)
-#define qGLContextDebug qDebug
-#else
-#define qGLContextDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaGLContext, "qt.qpa.glcontext");
+
static QEGLPlatformContext::Flags makeFlags()
{
QEGLPlatformContext::Flags result = {};
@@ -51,13 +47,13 @@ EGLSurface QQnxGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface
bool QQnxGLContext::makeCurrent(QPlatformSurface *surface)
{
- qGLContextDebug();
+ qCDebug(lcQpaGLContext) << Q_FUNC_INFO;
return QEGLPlatformContext::makeCurrent(surface);
}
void QQnxGLContext::swapBuffers(QPlatformSurface *surface)
{
- qGLContextDebug();
+ qCDebug(lcQpaGLContext) << Q_FUNC_INFO;
QEGLPlatformContext::swapBuffers(surface);
diff --git a/src/plugins/platforms/qnx/qqnxglobal.cpp b/src/plugins/platforms/qnx/qqnxglobal.cpp
index 85642b44b3..9cfc328e8f 100644
--- a/src/plugins/platforms/qnx/qqnxglobal.cpp
+++ b/src/plugins/platforms/qnx/qqnxglobal.cpp
@@ -15,6 +15,8 @@ void qScreenCheckError(int rc, const char *funcInfo, const char *message, bool c
}
if (Q_UNLIKELY(rc)) {
+ qCDebug(lcQpaQnx, "%s - Screen: %s - Error: %s (%i)", funcInfo, message, strerror(errno), errno);
+
if (Q_UNLIKELY(critical))
qCritical("%s - Screen: %s - Error: %s (%i)", funcInfo, message, strerror(errno), errno);
else
diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
index a1063444d1..cd41ebb8c2 100644
--- a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
+++ b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
@@ -25,17 +25,7 @@
#include <process.h>
#include <sys/keycodes.h>
-#if defined(QQNXINPUTCONTEXT_IMF_EVENT_DEBUG)
-#define qInputContextIMFRequestDebug qDebug
-#else
-#define qInputContextIMFRequestDebug QT_NO_QDEBUG_MACRO
-#endif
-
-#if defined(QQNXINPUTCONTEXT_DEBUG)
-#define qInputContextDebug qDebug
-#else
-#define qInputContextDebug QT_NO_QDEBUG_MACRO
-#endif
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
static QQnxInputContext *sInputContextInstance;
static QColor sSelectedColor(0,0xb8,0,85);
@@ -161,7 +151,7 @@ static int32_t ic_begin_batch_edit(input_session_t *ic)
// See comment at beginning of namespace declaration for general information
static int32_t ic_commit_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfCommitText);
event.ct.text = text;
@@ -176,7 +166,7 @@ static int32_t ic_commit_text(input_session_t *ic, spannable_string_t *text, int
// See comment at beginning of namespace declaration for general information
static int32_t ic_delete_surrounding_text(input_session_t *ic, int32_t left_length, int32_t right_length)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfDeleteSurroundingText);
event.dst.left_length = left_length;
@@ -200,7 +190,7 @@ static int32_t ic_end_batch_edit(input_session_t *ic)
// See comment at beginning of namespace declaration for general information
static int32_t ic_finish_composing_text(input_session_t *ic)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfFinishComposingText);
event.fct.result = -1;
@@ -213,7 +203,7 @@ static int32_t ic_finish_composing_text(input_session_t *ic)
// See comment at beginning of namespace declaration for general information
static int32_t ic_get_cursor_position(input_session_t *ic)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfGetCursorPosition);
event.gcp.result = -1;
@@ -226,7 +216,7 @@ static int32_t ic_get_cursor_position(input_session_t *ic)
// See comment at beginning of namespace declaration for general information
static spannable_string_t *ic_get_text_after_cursor(input_session_t *ic, int32_t n, int32_t flags)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfGetTextAfterCursor);
event.gtac.n = n;
@@ -241,7 +231,7 @@ static spannable_string_t *ic_get_text_after_cursor(input_session_t *ic, int32_t
// See comment at beginning of namespace declaration for general information
static spannable_string_t *ic_get_text_before_cursor(input_session_t *ic, int32_t n, int32_t flags)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfGetTextBeforeCursor);
event.gtac.n = n;
@@ -256,7 +246,7 @@ static spannable_string_t *ic_get_text_before_cursor(input_session_t *ic, int32_
// See comment at beginning of namespace declaration for general information
static int32_t ic_send_event(input_session_t *ic, event_t *event)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest imfEvent(ic, ImfSendEvent);
imfEvent.sae.event = event;
@@ -270,7 +260,7 @@ static int32_t ic_send_event(input_session_t *ic, event_t *event)
// See comment at beginning of namespace declaration for general information
static int32_t ic_send_async_event(input_session_t *ic, event_t *event)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
// There's no difference from our point of view between ic_send_event & ic_send_async_event
QQnxImfRequest imfEvent(ic, ImfSendEvent);
@@ -285,7 +275,7 @@ static int32_t ic_send_async_event(input_session_t *ic, event_t *event)
// See comment at beginning of namespace declaration for general information
static int32_t ic_set_composing_region(input_session_t *ic, int32_t start, int32_t end)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfSetComposingRegion);
event.scr.start = start;
@@ -301,7 +291,7 @@ static int32_t ic_set_composing_region(input_session_t *ic, int32_t start, int32
// See comment at beginning of namespace declaration for general information
static int32_t ic_set_composing_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfSetComposingText);
event.sct.text = text;
@@ -316,7 +306,7 @@ static int32_t ic_set_composing_text(input_session_t *ic, spannable_string_t *te
// See comment at beginning of namespace declaration for general information
static int32_t ic_is_text_selected(input_session_t* ic, int32_t* pIsSelected)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfIsTextSelected);
event.its.pIsSelected = pIsSelected;
@@ -330,7 +320,7 @@ static int32_t ic_is_text_selected(input_session_t* ic, int32_t* pIsSelected)
// See comment at beginning of namespace declaration for general information
static int32_t ic_is_all_text_selected(input_session_t* ic, int32_t* pIsSelected)
{
- qInputContextIMFRequestDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QQnxImfRequest event(ic, ImfIsAllTextSelected);
event.its.pIsSelected = pIsSelected;
@@ -466,7 +456,7 @@ initEvent(event_t *pEvent, const input_session_t *pSession, EventType eventType,
static spannable_string_t *toSpannableString(const QString &text)
{
- qInputContextDebug() << text;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "Text:" << text;
spannable_string_t *pString = static_cast<spannable_string_t *>(malloc(sizeof(spannable_string_t)));
pString->str = static_cast<wchar_t *>(malloc(sizeof(wchar_t) * text.length() + 1));
@@ -540,7 +530,7 @@ QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVir
m_integration(integration),
m_virtualKeyboard(keyboard)
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
if (!imfAvailable())
return;
@@ -563,7 +553,7 @@ QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVir
QQnxInputContext::~QQnxInputContext()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
Q_ASSERT(sInputContextInstance == this);
sInputContextInstance = nullptr;
@@ -638,7 +628,7 @@ void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent)
bool QQnxInputContext::filterEvent( const QEvent *event )
{
- qInputContextDebug() << event;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << event;
switch (event->type()) {
case QEvent::CloseSoftwareInputPanel:
@@ -661,19 +651,19 @@ QRectF QQnxInputContext::keyboardRect() const
void QQnxInputContext::reset()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
endComposition();
}
void QQnxInputContext::commit()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
endComposition();
}
void QQnxInputContext::update(Qt::InputMethodQueries queries)
{
- qInputContextDebug() << queries;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "Queries:" << queries;
if (queries & Qt::ImCursorPosition) {
int lastCaret = m_caretPosition;
@@ -685,7 +675,8 @@ void QQnxInputContext::update(Qt::InputMethodQueries queries)
initEvent(&caretEvent.event, sInputSession, EVENT_CARET, CARET_POS_CHANGED, sizeof(caretEvent));
caretEvent.old_pos = lastCaret;
caretEvent.new_pos = m_caretPosition;
- qInputContextDebug("ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition);
+ qCDebug(lcQpaInputMethods, "ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition);
+
p_ictrl_dispatch_event(&caretEvent.event);
}
}
@@ -693,7 +684,7 @@ void QQnxInputContext::update(Qt::InputMethodQueries queries)
void QQnxInputContext::closeSession()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
if (!imfAvailable())
return;
@@ -715,7 +706,7 @@ bool QQnxInputContext::openSession()
closeSession();
sInputSession = p_ictrl_open_session(&ic_funcs);
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
return sInputSession != 0;
}
@@ -739,7 +730,7 @@ bool QQnxInputContext::hasSelectedText()
bool QQnxInputContext::dispatchRequestSoftwareInputPanel()
{
- qInputContextDebug() << "requesting keyboard" << m_inputPanelVisible;
+ qCDebug(lcQpaInputMethods) << "Requesting keyboard" << m_inputPanelVisible;
m_virtualKeyboard.showKeyboard();
return true;
@@ -747,7 +738,7 @@ bool QQnxInputContext::dispatchRequestSoftwareInputPanel()
bool QQnxInputContext::dispatchCloseSoftwareInputPanel()
{
- qInputContextDebug() << "hiding keyboard" << m_inputPanelVisible;
+ qCDebug(lcQpaInputMethods) << "Hiding keyboard" << m_inputPanelVisible;
m_virtualKeyboard.hideKeyboard();
return true;
@@ -793,7 +784,7 @@ bool QQnxInputContext::dispatchFocusGainEvent(int inputHints)
focusEvent.style |= IMF_EMAIL_TYPE;
}
- qInputContextDebug() << "ictrl_dispatch_event focus gain style:" << focusEvent.style;
+ qCDebug(lcQpaInputMethods) << "ictrl_dispatch_event focus gain style:" << focusEvent.style;
p_ictrl_dispatch_event((event_t *)&focusEvent);
@@ -803,7 +794,7 @@ bool QQnxInputContext::dispatchFocusGainEvent(int inputHints)
void QQnxInputContext::dispatchFocusLossEvent()
{
if (hasSession()) {
- qInputContextDebug("ictrl_dispatch_event focus lost");
+ qCDebug(lcQpaInputMethods) << "ictrl_dispatch_event focus lost";
focus_event_t focusEvent;
initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_LOST, sizeof(focusEvent));
@@ -878,7 +869,7 @@ bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan
navigation_event_t navEvent;
initEvent(&navEvent.event, sInputSession, EVENT_NAVIGATION, key, sizeof(navEvent));
navEvent.magnitude = 1;
- qInputContextDebug("ictrl_dispatch_even navigation %d", key);
+ qCDebug(lcQpaInputMethods, "ictrl_dispatch_even navigation %d", key);
p_ictrl_dispatch_event(&navEvent.event);
}
} else {
@@ -891,7 +882,8 @@ bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan
keyEvent.sequence_id = sequenceId;
p_ictrl_dispatch_event(&keyEvent.event);
- qInputContextDebug("ictrl_dispatch_even key %d", key);
+ qCDebug(lcQpaInputMethods, "ictrl_dispatch_even key %d", key);
+
}
return true;
@@ -907,7 +899,7 @@ void QQnxInputContext::updateCursorPosition()
QCoreApplication::sendEvent(input, &query);
m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
- qInputContextDebug("%d", m_caretPosition);
+ qCDebug(lcQpaInputMethods, "ictrl_dispatch_even key %d", key);
}
void QQnxInputContext::endComposition()
@@ -920,7 +912,7 @@ void QQnxInputContext::endComposition()
if (hasSession()) {
action_event_t actionEvent;
initEvent(&actionEvent.event, sInputSession, EVENT_ACTION, ACTION_END_COMPOSITION, sizeof(actionEvent));
- qInputContextDebug("ictrl_dispatch_even end composition");
+ qCDebug(lcQpaInputMethods, "ictrl_dispatch_even end composition");
p_ictrl_dispatch_event(&actionEvent.event);
}
}
@@ -937,7 +929,7 @@ void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_c
m_composingText = QString::fromWCharArray(text->str, text->length);
m_isComposing = true;
- qInputContextDebug() << m_composingText << new_cursor_position;
+ qCDebug(lcQpaInputMethods) << "Text =" << m_composingText << "Cursor position =" << new_cursor_position;
QList<QInputMethodEvent::Attribute> attributes;
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
@@ -967,7 +959,7 @@ void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_c
format.setFontUnderline(true);
if (highlightColor.isValid())
format.setBackground(QBrush(highlightColor));
- qInputContextDebug() << " attrib: " << underline << highlightColor << text->spans[i].start << text->spans[i].end;
+ qCDebug(lcQpaInputMethods) << "attrib: " << underline << highlightColor << text->spans[i].start << text->spans[i].end;
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, text->spans[i].start,
text->spans[i].end - text->spans[i].start + 1, QVariant(format)));
@@ -986,7 +978,7 @@ void QQnxInputContext::finishComposingText()
QObject *input = qGuiApp->focusObject();
if (input) {
- qInputContextDebug() << m_composingText;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "Text =" << m_composingText;
QInputMethodEvent event;
event.setCommitString(m_composingText);
@@ -1051,13 +1043,13 @@ int32_t QQnxInputContext::processEvent(event_t *event)
int32_t result = -1;
switch (event->event_type) {
case EVENT_SPELL_CHECK: {
- qInputContextDebug("EVENT_SPELL_CHECK");
+ qCDebug(lcQpaInputMethods) << "EVENT_SPELL_CHECK";
result = handleSpellCheck(reinterpret_cast<spell_check_event_t *>(event));
break;
}
case EVENT_NAVIGATION: {
- qInputContextDebug("EVENT_NAVIGATION");
+ qCDebug(lcQpaInputMethods) << "EVENT_NAVIGATION";
int key = event->event_id == NAVIGATE_UP ? KEYCODE_UP :
event->event_id == NAVIGATE_DOWN ? KEYCODE_DOWN :
@@ -1080,7 +1072,7 @@ int32_t QQnxInputContext::processEvent(event_t *event)
int flags = KEY_SYM_VALID | KEY_CAP_VALID;
if (event->event_id == IMF_KEY_DOWN)
flags |= KEY_DOWN;
- qInputContextDebug("EVENT_KEY %d %d", flags, keySym);
+ qCDebug(lcQpaInputMethods, "EVENT_KEY %d %d", flags, keySym);
QQnxScreenEventHandler::injectKeyboardEvent(flags, keySym, modifiers, 0, keyCap);
result = 0;
break;
@@ -1120,7 +1112,7 @@ int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cur
int32_t QQnxInputContext::onDeleteSurroundingText(int32_t left_length, int32_t right_length)
{
- qInputContextDebug("L: %d R: %d", int(left_length), int(right_length));
+ qCDebug(lcQpaInputMethods, "L: %d R: %d", int(left_length), int(right_length));
QObject *input = qGuiApp->focusObject();
if (!input)
@@ -1151,7 +1143,7 @@ int32_t QQnxInputContext::onFinishComposingText()
int32_t QQnxInputContext::onGetCursorPosition()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QObject *input = qGuiApp->focusObject();
if (!input)
@@ -1165,7 +1157,7 @@ int32_t QQnxInputContext::onGetCursorPosition()
spannable_string_t *QQnxInputContext::onGetTextAfterCursor(int32_t n, int32_t flags)
{
Q_UNUSED(flags);
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QObject *input = qGuiApp->focusObject();
if (!input)
@@ -1182,7 +1174,7 @@ spannable_string_t *QQnxInputContext::onGetTextAfterCursor(int32_t n, int32_t fl
spannable_string_t *QQnxInputContext::onGetTextBeforeCursor(int32_t n, int32_t flags)
{
Q_UNUSED(flags);
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
QObject *input = qGuiApp->focusObject();
if (!input)
@@ -1201,7 +1193,7 @@ spannable_string_t *QQnxInputContext::onGetTextBeforeCursor(int32_t n, int32_t f
int32_t QQnxInputContext::onSendEvent(event_t *event)
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
return processEvent(event);
}
@@ -1217,7 +1209,7 @@ int32_t QQnxInputContext::onSetComposingRegion(int32_t start, int32_t end)
QString text = query.value(Qt::ImSurroundingText).toString();
m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
- qInputContextDebug() << text;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "Text =" << text;
m_isUpdatingText = true;
@@ -1260,7 +1252,7 @@ int32_t QQnxInputContext::onIsTextSelected(int32_t* pIsSelected)
{
*pIsSelected = hasSelectedText();
- qInputContextDebug() << *pIsSelected;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << *pIsSelected;
return 0;
}
@@ -1276,20 +1268,20 @@ int32_t QQnxInputContext::onIsAllTextSelected(int32_t* pIsSelected)
*pIsSelected = query.value(Qt::ImSurroundingText).toString().length() == query.value(Qt::ImCurrentSelection).toString().length();
- qInputContextDebug() << *pIsSelected;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << *pIsSelected;
return 0;
}
void QQnxInputContext::showInputPanel()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
dispatchRequestSoftwareInputPanel();
}
void QQnxInputContext::hideInputPanel()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
dispatchCloseSoftwareInputPanel();
}
@@ -1305,7 +1297,7 @@ QLocale QQnxInputContext::locale() const
void QQnxInputContext::keyboardVisibilityChanged(bool visible)
{
- qInputContextDebug() << "visible=" << visible;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "visible=" << visible;
if (m_inputPanelVisible != visible) {
m_inputPanelVisible = visible;
emitInputPanelVisibleChanged();
@@ -1314,7 +1306,7 @@ void QQnxInputContext::keyboardVisibilityChanged(bool visible)
void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale)
{
- qInputContextDebug() << "locale=" << locale;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "locale=" << locale;
if (m_inputPanelLocale != locale) {
m_inputPanelLocale = locale;
emitLocaleChanged();
@@ -1323,7 +1315,7 @@ void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale)
void QQnxInputContext::setHighlightColor(int index, const QColor &color)
{
- qInputContextDebug() << "setHighlightColor" << index << color << qGuiApp->focusObject();
+ qCDebug(lcQpaInputMethods) << "setHighlightColor" << index << color << qGuiApp->focusObject();
if (!sInputContextInstance)
return;
@@ -1342,7 +1334,7 @@ void QQnxInputContext::setHighlightColor(int index, const QColor &color)
void QQnxInputContext::setFocusObject(QObject *object)
{
- qInputContextDebug() << "input item=" << object;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "input item=" << object;
// Ensure the colors are reset if we've a change in focus object
setHighlightColor(-1, QColor());
@@ -1372,7 +1364,7 @@ void QQnxInputContext::setFocusObject(QObject *object)
bool QQnxInputContext::checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList<int> &indices))
{
- qInputContextDebug() << "text" << text;
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO << "Text =" << text;
if (!imfAvailable())
return false;
diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_noimf.cpp b/src/plugins/platforms/qnx/qqnxinputcontext_noimf.cpp
index 2a4e5b509b..7789b2830a 100644
--- a/src/plugins/platforms/qnx/qqnxinputcontext_noimf.cpp
+++ b/src/plugins/platforms/qnx/qqnxinputcontext_noimf.cpp
@@ -10,14 +10,10 @@
#include <QtGui/QGuiApplication>
#include <QtGui/QInputMethodEvent>
-#if defined(QQNXINPUTCONTEXT_DEBUG)
-#define qInputContextDebug qDebug
-#else
-#define qInputContextDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
+
QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVirtualKeyboard &keyboard) :
QPlatformInputContext(),
m_inputPanelVisible(false),
@@ -58,13 +54,13 @@ bool QQnxInputContext::filterEvent( const QEvent *event )
if (event->type() == QEvent::CloseSoftwareInputPanel) {
m_virtualKeyboard.hideKeyboard();
- qInputContextDebug("hiding virtual keyboard");
+ qCDebug(lcQpaInputMethods) << "hiding virtual keyboard";
return false;
}
if (event->type() == QEvent::RequestSoftwareInputPanel) {
m_virtualKeyboard.showKeyboard();
- qInputContextDebug("requesting virtual keyboard");
+ qCDebug(lcQpaInputMethods) << "requesting virtual keyboard";
return false;
}
@@ -91,13 +87,13 @@ bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan
void QQnxInputContext::showInputPanel()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
m_virtualKeyboard.showKeyboard();
}
void QQnxInputContext::hideInputPanel()
{
- qInputContextDebug();
+ qCDebug(lcQpaInputMethods) << Q_FUNC_INFO;
m_virtualKeyboard.hideKeyboard();
}
@@ -118,7 +114,7 @@ void QQnxInputContext::keyboardHeightChanged()
void QQnxInputContext::keyboardVisibilityChanged(bool visible)
{
- qInputContextDebug() << "visible=" << visible;
+ qCDebug(lcQpaInputMethods) << "visible=" << visible;
if (m_inputPanelVisible != visible) {
m_inputPanelVisible = visible;
emitInputPanelVisibleChanged();
@@ -127,7 +123,7 @@ void QQnxInputContext::keyboardVisibilityChanged(bool visible)
void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale)
{
- qInputContextDebug() << "locale=" << locale;
+ qCDebug(lcQpaInputMethods) << "locale=" << locale;
if (m_inputPanelLocale != locale) {
m_inputPanelLocale = locale;
emitLocaleChanged();
@@ -136,7 +132,7 @@ void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale)
void QQnxInputContext::setFocusObject(QObject *object)
{
- qInputContextDebug() << "input item=" << object;
+ qCDebug(lcQpaInputMethods) << "input item=" << object;;
if (!inputMethodAccepted()) {
if (m_inputPanelVisible)
diff --git a/src/plugins/platforms/qnx/qqnxintegration.cpp b/src/plugins/platforms/qnx/qqnxintegration.cpp
index ff6ce05aaa..cc10f6a00e 100644
--- a/src/plugins/platforms/qnx/qqnxintegration.cpp
+++ b/src/plugins/platforms/qnx/qqnxintegration.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxintegration.h"
@@ -62,14 +64,10 @@
#include <QtCore/QFile>
#include <errno.h>
-#if defined(QQNXINTEGRATION_DEBUG)
-#define qIntegrationDebug qDebug
-#else
-#define qIntegrationDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+// Q_LOGGING_CATEGORY(lcQpaQnx, "qt.qpa.qnx");
+
using namespace Qt::StringLiterals;
QQnxIntegration *QQnxIntegration::ms_instance;
@@ -98,7 +96,7 @@ static inline QQnxIntegration::Options parseOptions(const QStringList &paramList
static inline int getContextCapabilities(const QStringList &paramList)
{
- QString contextCapabilitiesPrefix = QStringLiteral("screen-context-capabilities=");
+ constexpr auto contextCapabilitiesPrefix = "screen-context-capabilities="_L1;
int contextCapabilities = SCREEN_APPLICATION_CONTEXT;
for (const QString &param : paramList) {
if (param.startsWith(contextCapabilitiesPrefix)) {
@@ -142,7 +140,7 @@ QQnxIntegration::QQnxIntegration(const QStringList &paramList)
{
ms_instance = this;
m_options = parseOptions(paramList);
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Open connection to QNX composition manager
if (screen_create_context(&m_screenContext, getContextCapabilities(paramList))) {
@@ -219,7 +217,7 @@ QQnxIntegration::QQnxIntegration(const QStringList &paramList)
QQnxIntegration::~QQnxIntegration()
{
- qIntegrationDebug("platform plugin shutdown begin");
+ qCDebug(lcQpaQnx) << "Platform plugin shutdown begin";
delete m_nativeInterface;
#if QT_CONFIG(draganddrop)
@@ -276,12 +274,12 @@ QQnxIntegration::~QQnxIntegration()
ms_instance = nullptr;
- qIntegrationDebug("platform plugin shutdown end");
+ qCDebug(lcQpaQnx) << "Platform plugin shutdown end";
}
bool QQnxIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
switch (cap) {
case MultipleWindows:
case ForeignWindows:
@@ -312,7 +310,7 @@ QPlatformWindow *QQnxIntegration::createForeignWindow(QWindow *window, WId nativ
QPlatformWindow *QQnxIntegration::createPlatformWindow(QWindow *window) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
QSurface::SurfaceType surfaceType = window->surfaceType();
const bool needRootWindow = options() & RootWindow;
switch (surfaceType) {
@@ -330,14 +328,14 @@ QPlatformWindow *QQnxIntegration::createPlatformWindow(QWindow *window) const
QPlatformBackingStore *QQnxIntegration::createPlatformBackingStore(QWindow *window) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
return new QQnxRasterBackingStore(window);
}
#if !defined(QT_NO_OPENGL)
QPlatformOpenGLContext *QQnxIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Get color channel sizes from window format
QSurfaceFormat format = context->format();
@@ -396,7 +394,7 @@ QPlatformOpenGLContext *QQnxIntegration::createPlatformOpenGLContext(QOpenGLCont
#if QT_CONFIG(qqnx_pps)
QPlatformInputContext *QQnxIntegration::inputContext() const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
if (m_qpaInputContext)
return m_qpaInputContext;
return m_inputContext;
@@ -405,7 +403,7 @@ QPlatformInputContext *QQnxIntegration::inputContext() const
void QQnxIntegration::moveToScreen(QWindow *window, int screen)
{
- qIntegrationDebug() << "w =" << window << ", s =" << screen;
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO << "w =" << window << ", s =" << screen;
// get platform window used by widget
QQnxWindow *platformWindow = static_cast<QQnxWindow *>(window->handle());
@@ -419,7 +417,7 @@ void QQnxIntegration::moveToScreen(QWindow *window, int screen)
QAbstractEventDispatcher *QQnxIntegration::createEventDispatcher() const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// We transfer ownersip of the event-dispatcher to QtCoreApplication
QAbstractEventDispatcher *eventDispatcher = m_eventDispatcher;
@@ -436,7 +434,7 @@ QPlatformNativeInterface *QQnxIntegration::nativeInterface() const
#if !defined(QT_NO_CLIPBOARD)
QPlatformClipboard *QQnxIntegration::clipboard() const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
#if QT_CONFIG(qqnx_pps)
if (!m_clipboard)
@@ -455,7 +453,7 @@ QPlatformDrag *QQnxIntegration::drag() const
QVariant QQnxIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
if ((hint == ShowIsFullScreen) && (m_options & FullScreenApplication))
return true;
@@ -469,7 +467,7 @@ QPlatformServices * QQnxIntegration::services() const
QWindow *QQnxIntegration::window(screen_window_t qnxWindow) const
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
QMutexLocker locker(&m_windowMapperMutex);
Q_UNUSED(locker);
return m_windowMapper.value(qnxWindow, 0);
@@ -477,7 +475,7 @@ QWindow *QQnxIntegration::window(screen_window_t qnxWindow) const
void QQnxIntegration::addWindow(screen_window_t qnxWindow, QWindow *window)
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
QMutexLocker locker(&m_windowMapperMutex);
Q_UNUSED(locker);
m_windowMapper.insert(qnxWindow, window);
@@ -485,7 +483,7 @@ void QQnxIntegration::addWindow(screen_window_t qnxWindow, QWindow *window)
void QQnxIntegration::removeWindow(screen_window_t qnxWindow)
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
QMutexLocker locker(&m_windowMapperMutex);
Q_UNUSED(locker);
m_windowMapper.remove(qnxWindow);
@@ -594,7 +592,7 @@ QList<screen_display_t *> QQnxIntegration::sortDisplays(screen_display_t *availa
void QQnxIntegration::createDisplays()
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Query number of displays
int displayCount = 0;
int result = screen_get_context_property_iv(m_screenContext, SCREEN_PROPERTY_DISPLAY_COUNT,
@@ -624,11 +622,12 @@ void QQnxIntegration::createDisplays()
Q_SCREEN_CHECKERROR(result, "Failed to query display attachment");
if (!isAttached) {
- qIntegrationDebug("Skipping non-attached display %d", i);
+ qCDebug(lcQpaQnx) << "Skipping non-attached display " << i;
continue;
}
- qIntegrationDebug("Creating screen for display %d", i);
+ qCDebug(lcQpaQnx) << "Creating screen for display " << i;
+
createDisplay(*orderedDisplays[i], /*isPrimary=*/false);
} // of displays iteration
}
@@ -662,7 +661,8 @@ void QQnxIntegration::removeDisplay(QQnxScreen *screen)
void QQnxIntegration::destroyDisplays()
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
+
Q_FOREACH (QQnxScreen *screen, m_screens) {
QWindowSystemInterface::handleScreenRemoved(screen);
}
@@ -713,7 +713,7 @@ bool QQnxIntegration::supportsNavigatorEvents() const
#if QT_CONFIG(opengl)
void QQnxIntegration::createEglDisplay()
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Initialize connection to EGL
m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -727,7 +727,7 @@ void QQnxIntegration::createEglDisplay()
void QQnxIntegration::destroyEglDisplay()
{
- qIntegrationDebug();
+ qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Close connection to EGL
eglTerminate(m_eglDisplay);
diff --git a/src/plugins/platforms/qnx/qqnxintegration.h b/src/plugins/platforms/qnx/qqnxintegration.h
index 590b9450c2..8a78d54ceb 100644
--- a/src/plugins/platforms/qnx/qqnxintegration.h
+++ b/src/plugins/platforms/qnx/qqnxintegration.h
@@ -6,9 +6,11 @@
#include <qpa/qplatformintegration.h>
#include <private/qtguiglobal_p.h>
+#include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <screen/screen.h>
+#include <QtCore/QLoggingCategory>
#if QT_CONFIG(opengl)
#include <EGL/egl.h>
@@ -16,6 +18,9 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaQnx);
+Q_DECLARE_LOGGING_CATEGORY(lcQpaGLContext);
+
class QQnxScreenEventThread;
class QQnxFileDialogHelper;
class QQnxNativeInterface;
@@ -39,8 +44,7 @@ class QQnxButtonEventNotifier;
class QQnxClipboard;
#endif
-template<class K, class V> class QHash;
-typedef QHash<screen_window_t, QWindow *> QQnxWindowMapper;
+using QQnxWindowMapper = QHash<screen_window_t, QWindow *>;
class QQnxIntegration : public QPlatformIntegration
{
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp
index cee86a68a0..7580e560aa 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.cpp
@@ -10,11 +10,7 @@
#include <QGuiApplication>
#include <qpa/qwindowsysteminterface.h>
-#if defined(QQNXNAVIGATOREVENTHANDLER_DEBUG)
-#define qNavigatorEventHandlerDebug qDebug
-#else
-#define qNavigatorEventHandlerDebug QT_NO_QDEBUG_MACRO
-#endif
+Q_LOGGING_CATEGORY(lcQpaQnxNavigatorEvents, "qt.qpa.qnx.navigator.events");
QT_BEGIN_NAMESPACE
@@ -27,20 +23,20 @@ bool QQnxNavigatorEventHandler::handleOrientationCheck(int angle)
{
// reply to navigator that (any) orientation is acceptable
// TODO: check if top window flags prohibit orientation change
- qNavigatorEventHandlerDebug("angle=%d", angle);
+ qCDebug(lcQpaQnxNavigatorEvents, "angle=%d", angle);
return true;
}
void QQnxNavigatorEventHandler::handleOrientationChange(int angle)
{
// update screen geometry and reply to navigator that we're ready
- qNavigatorEventHandlerDebug("angle=%d", angle);
+ qCDebug(lcQpaQnxNavigatorEvents, "angle=%d", angle);
emit rotationChanged(angle);
}
void QQnxNavigatorEventHandler::handleSwipeDown()
{
- qNavigatorEventHandlerDebug();
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO;
Q_EMIT swipeDown();
}
@@ -48,25 +44,25 @@ void QQnxNavigatorEventHandler::handleSwipeDown()
void QQnxNavigatorEventHandler::handleExit()
{
// shutdown everything
- qNavigatorEventHandlerDebug();
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO;
QCoreApplication::quit();
}
void QQnxNavigatorEventHandler::handleWindowGroupActivated(const QByteArray &id)
{
- qNavigatorEventHandlerDebug() << id;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << id;
Q_EMIT windowGroupActivated(id);
}
void QQnxNavigatorEventHandler::handleWindowGroupDeactivated(const QByteArray &id)
{
- qNavigatorEventHandlerDebug() << id;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << id;
Q_EMIT windowGroupDeactivated(id);
}
void QQnxNavigatorEventHandler::handleWindowGroupStateChanged(const QByteArray &id, Qt::WindowState state)
{
- qNavigatorEventHandlerDebug() << id;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << id;
Q_EMIT windowGroupStateChanged(id, state);
}
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.h b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.h
index 342b6c3ef6..826c9a0ff9 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.h
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventhandler.h
@@ -5,9 +5,12 @@
#define QQNXNAVIGATOREVENTHANDLER_H
#include <QObject>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaQnxNavigatorEvents);
+
class QQnxNavigatorEventHandler : public QObject
{
Q_OBJECT
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
index 5d099b7e46..44c71dad52 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
@@ -17,16 +17,12 @@
#include <sys/types.h>
#include <sys/stat.h>
-#if defined(QQNXNAVIGATOREVENTNOTIFIER_DEBUG)
-#define qNavigatorEventNotifierDebug qDebug
-#else
-#define qNavigatorEventNotifierDebug QT_NO_QDEBUG_MACRO
-#endif
+QT_BEGIN_NAMESPACE
-static const char *navigatorControlPath = "/pps/services/navigator/control";
-static const int ppsBufferSize = 4096;
+// Q_LOGGING_CATEGORY(lcQpaQnxNavigatorEvents, "qt.qpa.qnx.navigator.events");
-QT_BEGIN_NAMESPACE
+const char *QQnxNavigatorEventNotifier::navigatorControlPath = "/pps/services/navigator/control";
+const size_t QQnxNavigatorEventNotifier::ppsBufferSize = 4096;
QQnxNavigatorEventNotifier::QQnxNavigatorEventNotifier(QQnxNavigatorEventHandler *eventHandler, QObject *parent)
: QObject(parent),
@@ -44,18 +40,18 @@ QQnxNavigatorEventNotifier::~QQnxNavigatorEventNotifier()
if (m_fd != -1)
close(m_fd);
- qNavigatorEventNotifierDebug("navigator event notifier stopped");
+ qCDebug(lcQpaQnxNavigatorEvents) << "Navigator event notifier stopped";
}
void QQnxNavigatorEventNotifier::start()
{
- qNavigatorEventNotifierDebug("navigator event notifier started");
+ qCDebug(lcQpaQnxNavigatorEvents) << "Navigator event notifier started";
// open connection to navigator
errno = 0;
m_fd = open(navigatorControlPath, O_RDWR);
if (m_fd == -1) {
- qNavigatorEventNotifierDebug("failed to open navigator pps: %s", strerror(errno));
+ qCDebug(lcQpaQnxNavigatorEvents, "Failed to open navigator pps: %s", strerror(errno));
return;
}
@@ -65,7 +61,7 @@ void QQnxNavigatorEventNotifier::start()
void QQnxNavigatorEventNotifier::parsePPS(const QByteArray &ppsData, QByteArray &msg, QByteArray &dat, QByteArray &id)
{
- qNavigatorEventNotifierDebug() << "data=" << ppsData;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << "data=" << ppsData;
// tokenize pps data into lines
QList<QByteArray> lines = ppsData.split('\n');
@@ -79,7 +75,7 @@ void QQnxNavigatorEventNotifier::parsePPS(const QByteArray &ppsData, QByteArray
// tokenize current attribute
const QByteArray &attr = lines.at(i);
- qNavigatorEventNotifierDebug() << "attr=" << attr;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << "attr=" << attr;
int firstColon = attr.indexOf(':');
if (firstColon == -1) {
@@ -96,8 +92,7 @@ void QQnxNavigatorEventNotifier::parsePPS(const QByteArray &ppsData, QByteArray
QByteArray key = attr.left(firstColon);
QByteArray value = attr.mid(secondColon + 1);
- qNavigatorEventNotifierDebug() << "key=" << key;
- qNavigatorEventNotifierDebug() << "val=" << value;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << "key =" << key << "value =" << value;
// save attribute value
if (key == "msg")
@@ -124,7 +119,7 @@ void QQnxNavigatorEventNotifier::replyPPS(const QByteArray &res, const QByteArra
}
ppsData += "\n";
- qNavigatorEventNotifierDebug() << "reply=" << ppsData;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << "reply=" << ppsData;
// send pps message to navigator
errno = 0;
@@ -135,7 +130,7 @@ void QQnxNavigatorEventNotifier::replyPPS(const QByteArray &res, const QByteArra
void QQnxNavigatorEventNotifier::handleMessage(const QByteArray &msg, const QByteArray &dat, const QByteArray &id)
{
- qNavigatorEventNotifierDebug() << "msg=" << msg << ", dat=" << dat << ", id=" << id;
+ qCDebug(lcQpaQnxNavigatorEvents) << Q_FUNC_INFO << "msg=" << msg << ", dat=" << dat << ", id=" << id;
// check message type
if (msg == "orientationCheck") {
@@ -159,7 +154,7 @@ void QQnxNavigatorEventNotifier::handleMessage(const QByteArray &msg, const QByt
void QQnxNavigatorEventNotifier::readData()
{
- qNavigatorEventNotifierDebug("reading navigator data");
+ qCDebug(lcQpaQnxNavigatorEvents) << "Reading navigator data";
// allocate buffer for pps data
char buffer[ppsBufferSize];
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
index 6ecf776f36..66100ece3f 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
@@ -32,6 +32,9 @@ private:
int m_fd;
QSocketNotifier *m_readNotifier;
QQnxNavigatorEventHandler *m_eventHandler;
+
+ static const char *navigatorControlPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
index 9ca402822d..3a2fee0afb 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
+++ b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
@@ -8,17 +8,11 @@
#include <QByteArray>
#include <private/qcore_unix_p.h>
-#if defined(QQNXNAVIGATOR_DEBUG)
-#define qNavigatorDebug qDebug
-#else
-#define qNavigatorDebug QT_NO_QDEBUG_MACRO
-#endif
-
-static const char *navigatorControlPath = "/pps/services/navigator/control";
-static const int ppsBufferSize = 4096;
-
QT_BEGIN_NAMESPACE
+const char *QQnxNavigatorPps::navigatorControlPath = "/pps/services/navigator/control";
+const size_t QQnxNavigatorPps::ppsBufferSize = 4096;
+
QQnxNavigatorPps::QQnxNavigatorPps(QObject *parent)
: QQnxAbstractNavigator(parent)
, m_fd(-1)
@@ -45,7 +39,7 @@ bool QQnxNavigatorPps::openPpsConnection()
return false;
}
- qNavigatorDebug("successfully connected to Navigator. fd=%d", m_fd);
+ qCDebug(lcQpaQnxNavigator) << "successfully connected to Navigator. fd=" << m_fd;
return true;
}
@@ -67,7 +61,7 @@ bool QQnxNavigatorPps::sendPpsMessage(const QByteArray &message, const QByteArra
ppsMessage += "\n";
- qNavigatorDebug() << "sending PPS message:\n" << ppsMessage;
+ qCDebug(lcQpaQnxNavigator) << "sending PPS message:\n" << ppsMessage;
// send pps message to navigator
errno = 0;
@@ -89,7 +83,7 @@ bool QQnxNavigatorPps::sendPpsMessage(const QByteArray &message, const QByteArra
// ensure data is null terminated
buffer[bytes] = '\0';
- qNavigatorDebug() << "received PPS message:\n" << buffer;
+ qCDebug(lcQpaQnxNavigator) << "received PPS message:\n" << buffer;
// process received message
QByteArray ppsData(buffer);
@@ -108,7 +102,7 @@ bool QQnxNavigatorPps::sendPpsMessage(const QByteArray &message, const QByteArra
void QQnxNavigatorPps::parsePPS(const QByteArray &ppsData, QHash<QByteArray, QByteArray> &messageFields)
{
- qNavigatorDebug() << "data=" << ppsData;
+ qCDebug(lcQpaQnxNavigator) << "data=" << ppsData;
// tokenize pps data into lines
QList<QByteArray> lines = ppsData.split('\n');
@@ -123,7 +117,7 @@ void QQnxNavigatorPps::parsePPS(const QByteArray &ppsData, QHash<QByteArray, QBy
// tokenize current attribute
const QByteArray &attr = lines.at(i);
- qNavigatorDebug() << "attr=" << attr;
+ qCDebug(lcQpaQnxNavigator) << "attr=" << attr;
int firstColon = attr.indexOf(':');
if (firstColon == -1) {
@@ -140,8 +134,7 @@ void QQnxNavigatorPps::parsePPS(const QByteArray &ppsData, QHash<QByteArray, QBy
QByteArray key = attr.left(firstColon);
QByteArray value = attr.mid(secondColon + 1);
- qNavigatorDebug() << "key=" << key;
- qNavigatorDebug() << "val=" << value;
+ qCDebug(lcQpaQnxNavigator) << "key=" << key << "value=" << value;
messageFields[key] = value;
}
}
diff --git a/src/plugins/platforms/qnx/qqnxnavigatorpps.h b/src/plugins/platforms/qnx/qqnxnavigatorpps.h
index 7f23097bc9..889b9f62eb 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatorpps.h
+++ b/src/plugins/platforms/qnx/qqnxnavigatorpps.h
@@ -5,9 +5,12 @@
#define QQNXNAVIGATORPPS_H
#include "qqnxabstractnavigator.h"
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaQnxNavigator);
+
template <typename K, typename V> class QHash;
class QQnxNavigatorPps : public QQnxAbstractNavigator
@@ -26,8 +29,9 @@ private:
bool sendPpsMessage(const QByteArray &message, const QByteArray &data);
void parsePPS(const QByteArray &ppsData, QHash<QByteArray, QByteArray> &messageFields);
-private:
int m_fd;
+ static const char *navigatorControlPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp
index cf80e44f84..b94c056a79 100644
--- a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp
+++ b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp
@@ -10,12 +10,6 @@
#include <errno.h>
-#if defined(QQNXRASTERBACKINGSTORE_DEBUG)
-#define qRasterBackingStoreDebug qDebug
-#else
-#define qRasterBackingStoreDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
QQnxRasterBackingStore::QQnxRasterBackingStore(QWindow *window)
@@ -23,14 +17,14 @@ QQnxRasterBackingStore::QQnxRasterBackingStore(QWindow *window)
m_needsPosting(false),
m_scrolled(false)
{
- qRasterBackingStoreDebug() << "w =" << window;
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window;
m_window = window;
}
QQnxRasterBackingStore::~QQnxRasterBackingStore()
{
- qRasterBackingStoreDebug() << "w =" << window();
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window();
}
QPaintDevice *QQnxRasterBackingStore::paintDevice()
@@ -45,7 +39,7 @@ void QQnxRasterBackingStore::flush(QWindow *window, const QRegion &region, const
{
Q_UNUSED(offset);
- qRasterBackingStoreDebug() << "w =" << this->window();
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << this->window();
// Sometimes this method is called even though there is nothing to be
// flushed (posted in "screen" parlance), for instance, after an expose
@@ -67,7 +61,7 @@ void QQnxRasterBackingStore::resize(const QSize &size, const QRegion &staticCont
{
Q_UNUSED(size);
Q_UNUSED(staticContents);
- qRasterBackingStoreDebug() << "w =" << window() << ", s =" << size;
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window() << ", s =" << size;
// NOTE: defer resizing window buffers until next paint as
// resize() can be called multiple times before a paint occurs
@@ -75,7 +69,7 @@ void QQnxRasterBackingStore::resize(const QSize &size, const QRegion &staticCont
bool QQnxRasterBackingStore::scroll(const QRegion &area, int dx, int dy)
{
- qRasterBackingStoreDebug() << "w =" << window();
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window();
m_needsPosting = true;
@@ -91,7 +85,7 @@ void QQnxRasterBackingStore::beginPaint(const QRegion &region)
{
Q_UNUSED(region);
- qRasterBackingStoreDebug() << "w =" << window();
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window();
m_needsPosting = true;
platformWindow()->adjustBufferSize();
@@ -119,7 +113,7 @@ void QQnxRasterBackingStore::beginPaint(const QRegion &region)
void QQnxRasterBackingStore::endPaint()
{
- qRasterBackingStoreDebug() << "w =" << window();
+ qCDebug(lcQpaBackingStore) << Q_FUNC_INFO << "w =" << window();
}
QQnxRasterWindow *QQnxRasterBackingStore::platformWindow() const
diff --git a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp
index fb7a1d3fd3..303c9e7c06 100644
--- a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp
+++ b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp
@@ -10,12 +10,6 @@
#include <errno.h>
-#if defined(QQNXRASTERWINDOW_DEBUG)
-#define qRasterWindowDebug qDebug
-#else
-#define qRasterWindowDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
QQnxRasterWindow::QQnxRasterWindow(QWindow *window, screen_context_t context, bool needRootWindow) :
@@ -61,7 +55,7 @@ void QQnxRasterWindow::post(const QRegion &dirty)
// Check if render buffer exists and something was rendered
if (m_currentBufferIndex != -1 && !dirty.isEmpty()) {
- qRasterWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window = " << window();
QQnxBuffer &currentBuffer = m_buffers[m_currentBufferIndex];
// Copy unmodified region from old render buffer to new render buffer;
@@ -94,14 +88,14 @@ void QQnxRasterWindow::post(const QRegion &dirty)
void QQnxRasterWindow::scroll(const QRegion &region, int dx, int dy, bool flush)
{
- qRasterWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window = " << window();
blitPreviousToCurrent(region, dx, dy, flush);
m_scrolled += region;
}
QQnxBuffer &QQnxRasterWindow::renderBuffer()
{
- qRasterWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window = " << window();
// Check if render buffer is invalid
if (m_currentBufferIndex == -1) {
@@ -162,7 +156,7 @@ void QQnxRasterWindow::resetBuffers()
void QQnxRasterWindow::blitPreviousToCurrent(const QRegion &region, int dx, int dy, bool flush)
{
- qRasterWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window = " << window();
// Abort if previous buffer is invalid or if nothing to copy
if (m_previousBufferIndex == -1 || region.isEmpty())
diff --git a/src/plugins/platforms/qnx/qqnxscreen.cpp b/src/plugins/platforms/qnx/qqnxscreen.cpp
index 66f8bfae01..f2c3b3847d 100644
--- a/src/plugins/platforms/qnx/qqnxscreen.cpp
+++ b/src/plugins/platforms/qnx/qqnxscreen.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxscreen.h"
@@ -13,12 +15,6 @@
#include <errno.h>
-#if defined(QQNXSCREEN_DEBUG)
-#define qScreenDebug qDebug
-#else
-#define qScreenDebug QT_NO_QDEBUG_MACRO
-#endif
-
#if defined(QQNX_PHYSICAL_SCREEN_WIDTH) && QQNX_PHYSICAL_SCREEN_WIDTH > 0 \
&& defined(QQNX_PHYSICAL_SCREEN_HEIGHT) && QQNX_PHYSICAL_SCREEN_HEIGHT > 0
#define QQNX_PHYSICAL_SCREEN_SIZE_DEFINED
@@ -32,6 +28,8 @@ static const int MAX_UNDERLAY_ZORDER = -1;
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen");
+
static QSize determineScreenSize(screen_display_t display, bool primaryScreen) {
int val[2];
@@ -44,9 +42,9 @@ static QSize determineScreenSize(screen_display_t display, bool primaryScreen) {
if (val[0] > 0 && val[1] > 0)
return QSize(val[0], val[1]);
- qScreenDebug("QQnxScreen: screen_get_display_property_iv() reported an invalid "
- "physical screen size (%dx%d). Falling back to QQNX_PHYSICAL_SCREEN_SIZE "
- "environment variable.", val[0], val[1]);
+ qCDebug(lcQpaScreen, "QQnxScreen: screen_get_display_property_iv() reported an invalid "
+ "physical screen size (%dx%d). Falling back to QQNX_PHYSICAL_SCREEN_SIZE "
+ "environment variable.", val[0], val[1]);
const QString envPhySizeStr = qgetenv("QQNX_PHYSICAL_SCREEN_SIZE");
if (!envPhySizeStr.isEmpty()) {
@@ -88,7 +86,7 @@ QQnxScreen::QQnxScreen(screen_context_t screenContext, screen_display_t display,
m_coverWindow(0),
m_cursor(new QQnxCursor())
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
// Cache initial orientation of this display
int result = screen_get_display_property_iv(m_display, SCREEN_PROPERTY_ROTATION,
&m_initialRotation);
@@ -125,7 +123,7 @@ QQnxScreen::QQnxScreen(screen_context_t screenContext, screen_display_t display,
QQnxScreen::~QQnxScreen()
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
Q_FOREACH (QQnxWindow *childWindow, m_childWindows)
childWindow->setScreen(0);
@@ -234,7 +232,7 @@ QPixmap QQnxScreen::grabWindow(WId window, int x, int y, int width, int height)
static int defaultDepth()
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
static int defaultDepth = 0;
if (defaultDepth == 0) {
// check if display depth was specified in environment variable;
@@ -248,7 +246,7 @@ static int defaultDepth()
QRect QQnxScreen::availableGeometry() const
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
// available geometry = total geometry - keyboard
return QRect(m_currentGeometry.x(), m_currentGeometry.y(),
m_currentGeometry.width(), m_currentGeometry.height() - m_keyboardHeight);
@@ -268,12 +266,12 @@ qreal QQnxScreen::refreshRate() const
qWarning("QQnxScreen: Failed to query screen mode. Using default value of 60Hz");
return 60.0;
}
- qScreenDebug("screen mode:\n"
- " width = %u\n"
- " height = %u\n"
- " refresh = %u\n"
- " interlaced = %u",
- uint(displayMode.width), uint(displayMode.height), uint(displayMode.refresh), uint(displayMode.interlaced));
+ qCDebug(lcQpaScreen, "screen mode:\n"
+ " width = %u\n"
+ " height = %u\n"
+ " refresh = %u\n"
+ " interlaced = %u",
+ uint(displayMode.width), uint(displayMode.height), uint(displayMode.refresh), uint(displayMode.interlaced));
return static_cast<qreal>(displayMode.refresh);
}
@@ -307,7 +305,7 @@ Qt::ScreenOrientation QQnxScreen::orientation() const
else
orient = Qt::InvertedLandscapeOrientation;
}
- qScreenDebug() << "orientation =" << orient;
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "Orientation =" << orient;
return orient;
}
@@ -331,7 +329,7 @@ static bool isOrthogonal(int angle1, int angle2)
void QQnxScreen::setRotation(int rotation)
{
- qScreenDebug("orientation = %d", rotation);
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "orientation =" << rotation;
// Check if rotation changed
// We only want to rotate if we are the primary screen
if (m_currentRotation != rotation && isPrimaryScreen()) {
@@ -352,7 +350,7 @@ void QQnxScreen::setRotation(int rotation)
// Resize root window if we've rotated 90 or 270 from previous orientation
if (isOrthogonal(m_currentRotation, rotation)) {
- qScreenDebug() << "resize, size =" << m_currentGeometry.size();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "resize, size =" << m_currentGeometry.size();
if (rootWindow())
rootWindow()->setGeometry(QRect(QPoint(0,0), m_currentGeometry.size()));
@@ -499,7 +497,7 @@ QQnxWindow *QQnxScreen::findWindow(screen_window_t windowHandle) const
void QQnxScreen::addWindow(QQnxWindow *window)
{
- qScreenDebug() << "window =" << window;
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "Window =" << window;
if (m_childWindows.contains(window))
return;
@@ -522,7 +520,7 @@ void QQnxScreen::addWindow(QQnxWindow *window)
void QQnxScreen::removeWindow(QQnxWindow *window)
{
- qScreenDebug() << "window =" << window;
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "Window =" << window;
if (window != m_coverWindow) {
const int numWindowsRemoved = m_childWindows.removeAll(window);
@@ -537,7 +535,7 @@ void QQnxScreen::removeWindow(QQnxWindow *window)
void QQnxScreen::raiseWindow(QQnxWindow *window)
{
- qScreenDebug() << "window =" << window;
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "Window =" << window;
if (window != m_coverWindow) {
removeWindow(window);
@@ -547,7 +545,7 @@ void QQnxScreen::raiseWindow(QQnxWindow *window)
void QQnxScreen::lowerWindow(QQnxWindow *window)
{
- qScreenDebug() << "window =" << window;
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO << "Window =" << window;
if (window != m_coverWindow) {
removeWindow(window);
@@ -557,7 +555,7 @@ void QQnxScreen::lowerWindow(QQnxWindow *window)
void QQnxScreen::updateHierarchy()
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
QList<QQnxWindow*>::const_iterator it;
int result;
@@ -707,7 +705,7 @@ void QQnxScreen::windowClosed(void *window)
void QQnxScreen::windowGroupStateChanged(const QByteArray &id, Qt::WindowState state)
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
if (!rootWindow() || id != rootWindow()->groupName())
return;
@@ -722,7 +720,7 @@ void QQnxScreen::windowGroupStateChanged(const QByteArray &id, Qt::WindowState s
void QQnxScreen::activateWindowGroup(const QByteArray &id)
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
if (!rootWindow() || id != rootWindow()->groupName())
return;
@@ -741,7 +739,7 @@ void QQnxScreen::activateWindowGroup(const QByteArray &id)
void QQnxScreen::deactivateWindowGroup(const QByteArray &id)
{
- qScreenDebug();
+ qCDebug(lcQpaScreen) << Q_FUNC_INFO;
if (!rootWindow() || id != rootWindow()->groupName())
return;
diff --git a/src/plugins/platforms/qnx/qqnxscreen.h b/src/plugins/platforms/qnx/qqnxscreen.h
index f988b42ab4..17b282bdc1 100644
--- a/src/plugins/platforms/qnx/qqnxscreen.h
+++ b/src/plugins/platforms/qnx/qqnxscreen.h
@@ -30,6 +30,10 @@ const int SCREEN_PROPERTY_SYM = SCREEN_PROPERTY_KEY_SYM;
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen);
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreenEvents);
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreenBuffer);
+
class QQnxWindow;
class QQnxScreen : public QObject, public QPlatformScreen
diff --git a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
index 525b22242c..6d923bc3a8 100644
--- a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
+++ b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxscreeneventhandler.h"
@@ -17,11 +19,7 @@
#include <errno.h>
#include <sys/keycodes.h>
-#if defined(QQNXSCREENEVENT_DEBUG)
-#define qScreenEventDebug qDebug
-#else
-#define qScreenEventDebug QT_NO_QDEBUG_MACRO
-#endif
+Q_LOGGING_CATEGORY(lcQpaScreenEvents, "qt.qpa.screen.events");
static int qtKey(int virtualKey, QChar::Category category)
{
@@ -195,7 +193,7 @@ bool QQnxScreenEventHandler::handleEvent(screen_event_t event, int qnxType)
default:
// event ignored
- qScreenEventDebug("unknown event %d", qnxType);
+ qCDebug(lcQpaScreenEvents) << Q_FUNC_INFO << "Unknown event" << qnxType;
return false;
}
@@ -233,7 +231,7 @@ void QQnxScreenEventHandler::injectKeyboardEvent(int flags, int sym, int modifie
QWindowSystemInterface::handleExtendedKeyEvent(QGuiApplication::focusWindow(), type, key, qtMod,
scan, virtualKey, modifiers, keyStr, flags & KEY_REPEAT);
- qScreenEventDebug() << "Qt key t=" << type << ", k=" << key << ", s=" << keyStr;
+ qCDebug(lcQpaScreenEvents) << "Qt key t=" << type << ", k=" << key << ", s=" << keyStr;
}
void QQnxScreenEventHandler::setScreenEventThread(QQnxScreenEventThread *eventThread)
@@ -362,12 +360,12 @@ void QQnxScreenEventHandler::handlePointerEvent(screen_event_t event)
if (wOld) {
QWindowSystemInterface::handleLeaveEvent(wOld);
- qScreenEventDebug() << "Qt leave, w=" << wOld;
+ qCDebug(lcQpaScreenEvents) << "Qt leave, w=" << wOld;
}
if (w) {
QWindowSystemInterface::handleEnterEvent(w);
- qScreenEventDebug() << "Qt enter, w=" << w;
+ qCDebug(lcQpaScreenEvents) << "Qt enter, w=" << w;
}
}
@@ -410,8 +408,8 @@ void QQnxScreenEventHandler::handlePointerEvent(screen_event_t event)
QWindowSystemInterface::handleMouseEvent(w, timestamp, m_mouseDevice, localPoint,
globalPoint, buttons, Qt::NoButton,
QEvent::MouseMove);
- qScreenEventDebug() << "Qt mouse move, w=" << w << ", (" << localPoint.x() << ","
- << localPoint.y() << "), b=" << static_cast<int>(buttons);
+ qCDebug(lcQpaScreenEvents) << "Qt mouse move, w=" << w << ", (" << localPoint.x() << ","
+ << localPoint.y() << "), b=" << static_cast<int>(buttons);
}
if (m_lastButtonState != buttons) {
@@ -426,8 +424,8 @@ void QQnxScreenEventHandler::handlePointerEvent(screen_event_t event)
QWindowSystemInterface::handleMouseEvent(w, timestamp, m_mouseDevice,
localPoint, globalPoint, buttons,
button, QEvent::MouseButtonRelease);
- qScreenEventDebug() << "Qt mouse release, w=" << w << ", (" << localPoint.x()
- << "," << localPoint.y() << "), b=" << button;
+ qCDebug(lcQpaScreenEvents) << "Qt mouse release, w=" << w << ", (" << localPoint.x()
+ << "," << localPoint.y() << "), b=" << button;
}
}
@@ -441,8 +439,8 @@ void QQnxScreenEventHandler::handlePointerEvent(screen_event_t event)
QWindowSystemInterface::handleMouseEvent(w, timestamp, m_mouseDevice,
localPoint, globalPoint, buttons,
button, QEvent::MouseButtonPress);
- qScreenEventDebug() << "Qt mouse press, w=" << w << ", (" << localPoint.x()
- << "," << localPoint.y() << "), b=" << button;
+ qCDebug(lcQpaScreenEvents) << "Qt mouse press, w=" << w << ", (" << localPoint.x()
+ << "," << localPoint.y() << "), b=" << button;
}
}
}
@@ -453,7 +451,7 @@ void QQnxScreenEventHandler::handlePointerEvent(screen_event_t event)
QPoint angleDelta(0, wheelDelta);
QWindowSystemInterface::handleWheelEvent(w, timestamp, m_mouseDevice, localPoint,
globalPoint, QPoint(), angleDelta);
- qScreenEventDebug() << "Qt wheel, w=" << w << ", (" << localPoint.x() << ","
+ qCDebug(lcQpaScreenEvents) << "Qt wheel, w=" << w << ", (" << localPoint.x() << ","
<< localPoint.y() << "), d=" << static_cast<int>(wheelDelta);
}
}
@@ -511,12 +509,12 @@ void QQnxScreenEventHandler::handleTouchEvent(screen_event_t event, int qnxType)
if (wOld) {
QWindowSystemInterface::handleLeaveEvent(wOld);
- qScreenEventDebug() << "Qt leave, w=" << wOld;
+ qCDebug(lcQpaScreenEvents) << "Qt leave, w=" << wOld;
}
if (w) {
QWindowSystemInterface::handleEnterEvent(w);
- qScreenEventDebug() << "Qt enter, w=" << w;
+ qCDebug(lcQpaScreenEvents) << "Qt enter, w=" << w;
}
}
m_lastMouseWindow = qnxWindow;
@@ -583,9 +581,9 @@ void QQnxScreenEventHandler::handleTouchEvent(screen_event_t event, int qnxType)
// inject event into Qt
QWindowSystemInterface::handleTouchEvent(w, m_touchDevice, pointList);
- qScreenEventDebug() << "Qt touch, w =" << w
- << ", p=" << m_touchPoints[touchId].area.topLeft()
- << ", t=" << type;
+ qCDebug(lcQpaScreenEvents) << "Qt touch, w =" << w
+ << ", p=" << m_touchPoints[touchId].area.topLeft()
+ << ", t=" << type;
}
}
}
@@ -629,7 +627,8 @@ void QQnxScreenEventHandler::handleDisplayEvent(screen_event_t event)
return;
}
- qScreenEventDebug() << "display attachment is now:" << isAttached;
+ qCDebug(lcQpaScreenEvents) << "display attachment is now:" << isAttached;
+
QQnxScreen *screen = m_qnxIntegration->screenForNative(nativeDisplay);
if (!screen) {
@@ -639,7 +638,7 @@ void QQnxScreenEventHandler::handleDisplayEvent(screen_event_t event)
if (val[0] == 0 && val[1] == 0) //If screen size is invalid, wait for the next event
return;
- qScreenEventDebug("creating new QQnxScreen for newly attached display");
+ qCDebug(lcQpaScreenEvents) << "Creating new QQnxScreen for newly attached display";
m_qnxIntegration->createDisplay(nativeDisplay, false /* not primary, we assume */);
}
} else if (!isAttached) {
@@ -652,7 +651,7 @@ void QQnxScreenEventHandler::handleDisplayEvent(screen_event_t event)
if (!screen->isPrimaryScreen()) {
// libscreen display is deactivated, let's remove the QQnxScreen / QScreen
- qScreenEventDebug("removing display");
+ qCDebug(lcQpaScreenEvents) << "Removing display";
m_qnxIntegration->removeDisplay(screen);
}
}
@@ -689,7 +688,7 @@ void QQnxScreenEventHandler::handlePropertyEvent(screen_event_t event)
break;
default:
// event ignored
- qScreenEventDebug() << "Ignore property event for property: " << property;
+ qCDebug(lcQpaScreenEvents) << "Ignore property event for property: " << property;
}
}
@@ -708,7 +707,7 @@ void QQnxScreenEventHandler::handleKeyboardFocusPropertyEvent(screen_window_t wi
}
if (focus && focusWindow != QGuiApplication::focusWindow())
- QWindowSystemInterface::handleWindowActivated(focusWindow, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(focusWindow, Qt::ActiveWindowFocusReason);
else if (!focus && focusWindow == QGuiApplication::focusWindow())
m_focusLostTimer = startTimer(50);
}
@@ -732,7 +731,7 @@ void QQnxScreenEventHandler::handleGeometryPropertyEvent(screen_window_t window)
QWindowSystemInterface::handleGeometryChange(qtWindow, rect);
}
- qScreenEventDebug() << qtWindow << "moved to" << rect;
+ qCDebug(lcQpaScreenEvents) << qtWindow << "moved to" << rect;
}
void QQnxScreenEventHandler::timerEvent(QTimerEvent *event)
diff --git a/src/plugins/platforms/qnx/qqnxscreeneventhandler.h b/src/plugins/platforms/qnx/qqnxscreeneventhandler.h
index d27186af9e..ba3579aaf7 100644
--- a/src/plugins/platforms/qnx/qqnxscreeneventhandler.h
+++ b/src/plugins/platforms/qnx/qqnxscreeneventhandler.h
@@ -5,11 +5,14 @@
#define QQNXSCREENEVENTHANDLER_H
#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/QLoggingCategory>
#include <screen/screen.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreenEvents);
+
class QQnxIntegration;
class QQnxScreenEventFilter;
class QQnxScreenEventThread;
diff --git a/src/plugins/platforms/qnx/qqnxscreeneventthread.cpp b/src/plugins/platforms/qnx/qqnxscreeneventthread.cpp
index 69997e4ba0..6b4ffc3962 100644
--- a/src/plugins/platforms/qnx/qqnxscreeneventthread.cpp
+++ b/src/plugins/platforms/qnx/qqnxscreeneventthread.cpp
@@ -14,12 +14,6 @@
#include <cctype>
-#if defined(QQNXSCREENEVENTTHREAD_DEBUG)
-#define qScreenEventThreadDebug qDebug
-#else
-#define qScreenEventThreadDebug QT_NO_QDEBUG_MACRO
-#endif
-
static const int c_screenCode = _PULSE_CODE_MINAVAIL + 0;
static const int c_armCode = _PULSE_CODE_MINAVAIL + 1;
static const int c_quitCode = _PULSE_CODE_MINAVAIL + 2;
@@ -74,7 +68,7 @@ QQnxScreenEventThread::~QQnxScreenEventThread()
void QQnxScreenEventThread::run()
{
- qScreenEventThreadDebug("screen event thread started");
+ qCDebug(lcQpaScreenEvents) << "Screen event thread started";
while (1) {
struct _pulse msg;
@@ -90,7 +84,7 @@ void QQnxScreenEventThread::run()
qWarning() << "MsgReceive error" << strerror(errno);
}
- qScreenEventThreadDebug("screen event thread stopped");
+ qCDebug(lcQpaScreenEvents) << "Screen event thread stopped";
}
void QQnxScreenEventThread::armEventsPending(int count)
@@ -134,10 +128,10 @@ void QQnxScreenEventThread::shutdown()
{
MsgSendPulse(m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_quitCode, 0);
- qScreenEventThreadDebug("screen event thread shutdown begin");
+ qCDebug(lcQpaScreenEvents) << "Screen event thread shutdown begin";
// block until thread terminates
wait();
- qScreenEventThreadDebug("screen event thread shutdown end");
+ qCDebug(lcQpaScreenEvents) << "Screen event thread shutdown end";
}
diff --git a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp
index b4650de315..5eceacef95 100644
--- a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp
+++ b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.cpp
@@ -18,14 +18,10 @@
#include <sys/types.h>
#include <unistd.h>
-#if defined(QQNXVIRTUALKEYBOARD_DEBUG)
-#define qVirtualKeyboardDebug qDebug
-#else
-#define qVirtualKeyboardDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaQnxVirtualKeyboard, "qt.qpa.qnx.virtualkeyboard");
+
const char *QQnxVirtualKeyboardPps::ms_PPSPath = "/pps/services/input/control";
const size_t QQnxVirtualKeyboardPps::ms_bufferSize = 2048;
@@ -45,7 +41,7 @@ QQnxVirtualKeyboardPps::~QQnxVirtualKeyboardPps()
void QQnxVirtualKeyboardPps::start()
{
- qVirtualKeyboardDebug("starting keyboard event processing");
+ qCDebug(lcQpaQnxVirtualKeyboard) << "Starting keyboard event processing";
if (!connect())
return;
}
@@ -90,8 +86,8 @@ bool QQnxVirtualKeyboardPps::connect()
m_fd = ::open(ms_PPSPath, O_RDWR);
if (m_fd == -1)
{
- qVirtualKeyboardDebug() << "Unable to open" << ms_PPSPath
- << ':' << strerror(errno);
+ qCDebug(lcQpaQnxVirtualKeyboard) << "Unable to open" << ms_PPSPath
+ << ':' << strerror(errno);
close();
return false;
}
@@ -128,7 +124,7 @@ void QQnxVirtualKeyboardPps::ppsDataReady()
{
qint64 nread = qt_safe_read(m_fd, m_buffer, ms_bufferSize - 1);
- qVirtualKeyboardDebug("keyboardMessage size: %lld", nread);
+ qCDebug(lcQpaQnxVirtualKeyboard, "keyboardMessage size: %lld", nread);
if (nread < 0){
connect(); // reconnect
return;
@@ -167,7 +163,7 @@ void QQnxVirtualKeyboardPps::ppsDataReady()
else if (strcmp(value, "info") == 0)
handleKeyboardInfoMessage();
else if (strcmp(value, "connect") == 0)
- qVirtualKeyboardDebug("Unhandled command 'connect'");
+ qCDebug(lcQpaQnxVirtualKeyboard, "Unhandled command 'connect'");
else
qCritical("QQnxVirtualKeyboard: Unexpected keyboard PPS msg value: %s", value ? value : "[null]");
} else if (pps_decoder_get_string(m_decoder, "res", &value) == PPS_DECODER_OK) {
@@ -194,12 +190,12 @@ void QQnxVirtualKeyboardPps::handleKeyboardInfoMessage()
}
setHeight(newHeight);
- qVirtualKeyboardDebug("size=%d", newHeight);
+ qCDebug(lcQpaQnxVirtualKeyboard, "size=%d", newHeight);
}
bool QQnxVirtualKeyboardPps::showKeyboard()
{
- qVirtualKeyboardDebug();
+ qCDebug(lcQpaQnxVirtualKeyboard) << Q_FUNC_INFO;
if (!prepareToSend())
return false;
@@ -221,7 +217,7 @@ bool QQnxVirtualKeyboardPps::showKeyboard()
bool QQnxVirtualKeyboardPps::hideKeyboard()
{
- qVirtualKeyboardDebug();
+ qCDebug(lcQpaQnxVirtualKeyboard) << Q_FUNC_INFO;
if (!prepareToSend())
return false;
diff --git a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.h b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.h
index 88af284db3..8f1d390760 100644
--- a/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.h
+++ b/src/plugins/platforms/qnx/qqnxvirtualkeyboardpps.h
@@ -5,11 +5,13 @@
#define VIRTUALKEYBOARDPPS_H
#include "qqnxabstractvirtualkeyboard.h"
+#include <QtCore/QLoggingCategory>
#include <sys/pps.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaQnxVirtualKeyboard);
class QSocketNotifier;
diff --git a/src/plugins/platforms/qnx/qqnxwindow.cpp b/src/plugins/platforms/qnx/qqnxwindow.cpp
index 5310ff3c65..3384932a8b 100644
--- a/src/plugins/platforms/qnx/qqnxwindow.cpp
+++ b/src/plugins/platforms/qnx/qqnxwindow.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxwindow.h"
@@ -19,14 +21,10 @@
#include <errno.h>
-#if defined(QQNXWINDOW_DEBUG)
-#define qWindowDebug qDebug
-#else
-#define qWindowDebug QT_NO_QDEBUG_MACRO
-#endif
-
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
+
#define DECLARE_DEBUG_VAR(variable) \
static bool debug_ ## variable() \
{ static bool value = qgetenv("QNX_SCREEN_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
@@ -123,7 +121,7 @@ QQnxWindow::QQnxWindow(QWindow *window, screen_context_t context, bool needRootW
m_windowState(Qt::WindowNoState),
m_firstActivateHandled(false)
{
- qWindowDebug() << "window =" << window << ", size =" << window->size();
+ qCDebug(lcQpaWindow) << "window =" << window << ", size =" << window->size();
QQnxScreen *platformScreen = static_cast<QQnxScreen *>(window->screen()->handle());
@@ -193,13 +191,13 @@ QQnxWindow::QQnxWindow(QWindow *window, screen_context_t context, bool needRootW
bool ok = false;
int pipeline = pipelineValue.toInt(&ok);
if (ok) {
- qWindowDebug() << "Set pipeline value to" << pipeline;
+ qCDebug(lcQpaWindow) << "Set pipeline value to" << pipeline;
Q_SCREEN_CHECKERROR(
screen_set_window_property_iv(m_window, SCREEN_PROPERTY_PIPELINE, &pipeline),
"Failed to set window pipeline");
} else {
- qWindowDebug() << "Invalid pipeline value:" << pipelineValue;
+ qCDebug(lcQpaWindow) << "Invalid pipeline value:" << pipelineValue;
}
}
@@ -229,7 +227,7 @@ QQnxWindow::QQnxWindow(QWindow *window, screen_context_t context, bool needRootW
if (debug > 0) {
Q_SCREEN_CHECKERROR(screen_set_window_property_iv(nativeHandle(), SCREEN_PROPERTY_DEBUG, &debug),
"Could not set SCREEN_PROPERTY_DEBUG");
- qWindowDebug() << "window SCREEN_PROPERTY_DEBUG= " << debug;
+ qCDebug(lcQpaWindow) << "window SCREEN_PROPERTY_DEBUG= " << debug;
}
}
@@ -246,7 +244,7 @@ QQnxWindow::QQnxWindow(QWindow *window, screen_context_t context, screen_window_
, m_parentGroupName(256, 0)
, m_isTopLevel(false)
{
- qWindowDebug() << "window =" << window << ", size =" << window->size();
+ qCDebug(lcQpaWindow) << "window =" << window << ", size =" << window->size();
collectWindowGroup();
@@ -267,7 +265,7 @@ QQnxWindow::QQnxWindow(QWindow *window, screen_context_t context, screen_window_
QQnxWindow::~QQnxWindow()
{
- qWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << "window =" << window();
// Qt should have already deleted the children before deleting the parent.
Q_ASSERT(m_childWindows.size() == 0);
@@ -303,9 +301,9 @@ void QQnxWindow::setGeometry(const QRect &rect)
void QQnxWindow::setGeometryHelper(const QRect &rect)
{
- qWindowDebug() << "window =" << window()
- << ", (" << rect.x() << "," << rect.y()
- << "," << rect.width() << "," << rect.height() << ")";
+ qCDebug(lcQpaWindow) << "window =" << window()
+ << ", (" << rect.x() << "," << rect.y()
+ << "," << rect.width() << "," << rect.height() << ")";
// Call base class method
QPlatformWindow::setGeometry(rect);
@@ -333,7 +331,7 @@ void QQnxWindow::setGeometryHelper(const QRect &rect)
void QQnxWindow::setVisible(bool visible)
{
- qWindowDebug() << "window =" << window() << "visible =" << visible;
+ qCDebug(lcQpaWindow) << "window =" << window() << "visible =" << visible;
if (m_visible == visible || window()->type() == Qt::Desktop)
return;
@@ -372,7 +370,7 @@ void QQnxWindow::setVisible(bool visible)
void QQnxWindow::updateVisibility(bool parentVisible)
{
- qWindowDebug() << "parentVisible =" << parentVisible << "window =" << window();
+ qCDebug(lcQpaWindow) << "parentVisible =" << parentVisible << "window =" << window();
// Set window visibility
int val = (m_visible && parentVisible) ? 1 : 0;
Q_SCREEN_CHECKERROR(screen_set_window_property_iv(m_window, SCREEN_PROPERTY_VISIBLE, &val),
@@ -384,7 +382,7 @@ void QQnxWindow::updateVisibility(bool parentVisible)
void QQnxWindow::setOpacity(qreal level)
{
- qWindowDebug() << "window =" << window() << "opacity =" << level;
+ qCDebug(lcQpaWindow) << "window =" << window() << "opacity =" << level;
// Set window global alpha
int val = (int)(level * 255);
Q_SCREEN_CHECKERROR(screen_set_window_property_iv(m_window, SCREEN_PROPERTY_GLOBAL_ALPHA, &val),
@@ -395,7 +393,7 @@ void QQnxWindow::setOpacity(qreal level)
void QQnxWindow::setExposed(bool exposed)
{
- qWindowDebug() << "window =" << window() << "expose =" << exposed;
+ qCDebug(lcQpaWindow) << "window =" << window() << "expose =" << exposed;
if (m_exposed != exposed) {
m_exposed = exposed;
@@ -410,7 +408,7 @@ bool QQnxWindow::isExposed() const
void QQnxWindow::setBufferSize(const QSize &size)
{
- qWindowDebug() << "window =" << window() << "size =" << size;
+ qCDebug(lcQpaWindow) << "window =" << window() << "size =" << size;
// libscreen fails when creating empty buffers
const QSize nonEmptySize = size.isEmpty() ? QSize(1, 1) : size;
@@ -477,7 +475,7 @@ void QQnxWindow::setBufferSize(const QSize &size)
void QQnxWindow::setScreen(QQnxScreen *platformScreen)
{
- qWindowDebug() << "window =" << window() << "platformScreen =" << platformScreen;
+ qCDebug(lcQpaWindow) << "window =" << window() << "platformScreen =" << platformScreen;
if (platformScreen == 0) { // The screen has been destroyed
m_screen = 0;
@@ -491,7 +489,7 @@ void QQnxWindow::setScreen(QQnxScreen *platformScreen)
return;
if (m_screen) {
- qWindowDebug("Moving window to different screen");
+ qCDebug(lcQpaWindow) << "Moving window to different screen";
m_screen->removeWindow(this);
if ((QQnxIntegration::instance()->options() & QQnxIntegration::RootWindow)) {
@@ -522,7 +520,7 @@ void QQnxWindow::setScreen(QQnxScreen *platformScreen)
void QQnxWindow::removeFromParent()
{
- qWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window =" << window();
// Remove from old Hierarchy position
if (m_parentWindow) {
if (Q_UNLIKELY(!m_parentWindow->m_childWindows.removeAll(this)))
@@ -536,7 +534,7 @@ void QQnxWindow::removeFromParent()
void QQnxWindow::setParent(const QPlatformWindow *window)
{
- qWindowDebug() << "window =" << this->window() << "platformWindow =" << window;
+ qCDebug(lcQpaWindow) << "window =" << this->window() << "platformWindow =" << window;
// Cast away the const, we need to modify the hierarchy.
QQnxWindow* const newParent = static_cast<QQnxWindow*>(const_cast<QPlatformWindow*>(window));
@@ -568,7 +566,7 @@ void QQnxWindow::setParent(const QPlatformWindow *window)
void QQnxWindow::raise()
{
- qWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window =" << window();
if (m_parentWindow) {
m_parentWindow->m_childWindows.removeAll(this);
@@ -582,7 +580,7 @@ void QQnxWindow::raise()
void QQnxWindow::lower()
{
- qWindowDebug() << "window =" << window();
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "window =" << window();
if (m_parentWindow) {
m_parentWindow->m_childWindows.removeAll(this);
@@ -696,7 +694,7 @@ void QQnxWindow::setFocus(screen_window_t newFocusWindow)
void QQnxWindow::setWindowState(Qt::WindowStates state)
{
- qWindowDebug() << "state =" << state;
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "state =" << state;
// Prevent two calls with Qt::WindowFullScreen from changing m_unmaximizedGeometry
if (m_windowState == state)
@@ -711,7 +709,7 @@ void QQnxWindow::setWindowState(Qt::WindowStates state)
void QQnxWindow::propagateSizeHints()
{
// nothing to do; silence base class warning
- qWindowDebug("ignored");
+ // qWindowDebug("ignored");
}
QPlatformScreen *QQnxWindow::screen() const
@@ -740,7 +738,7 @@ void QQnxWindow::minimize()
void QQnxWindow::setRotation(int rotation)
{
- qWindowDebug() << "angle =" << rotation;
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "angle =" << rotation;
Q_SCREEN_CHECKERROR(
screen_set_window_property_iv(m_window, SCREEN_PROPERTY_ROTATION, &rotation),
"Failed to set window rotation");
@@ -816,7 +814,7 @@ void QQnxWindow::joinWindowGroup(const QByteArray &groupName)
{
bool changed = false;
- qWindowDebug() << "group:" << groupName;
+ qCDebug(lcQpaWindow) << Q_FUNC_INFO << "group:" << groupName;
// screen has this annoying habit of generating a CLOSE/CREATE when the owner context of
// the parent group moves a foreign window to another group that it also owns. The
diff --git a/src/plugins/platforms/qnx/qqnxwindow.h b/src/plugins/platforms/qnx/qqnxwindow.h
index d302f22415..013ea342e4 100644
--- a/src/plugins/platforms/qnx/qqnxwindow.h
+++ b/src/plugins/platforms/qnx/qqnxwindow.h
@@ -8,6 +8,7 @@
#include "qqnxabstractcover.h"
#include <QtCore/QScopedPointer>
+#include <QtCore/QLoggingCategory>
#if !defined(QT_NO_OPENGL)
#include <EGL/egl.h>
@@ -17,6 +18,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow);
+
// all surfaces double buffered
#define MAX_BUFFER_COUNT 2
diff --git a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
index 4d58e4154e..2e8d60209e 100644
--- a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
+++ b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
@@ -143,7 +143,7 @@ bool QVkKhrDisplayVulkanInstance::chooseDisplay()
j, (void *) mode.displayMode,
mode.parameters.visibleRegion.width, mode.parameters.visibleRegion.height,
mode.parameters.refreshRate);
- if (j == wantedModeIndex) {
+ if (j == wantedModeIndex && i == wantedDisplayIndex) {
m_displayMode = mode.displayMode;
m_width = mode.parameters.visibleRegion.width;
m_height = mode.parameters.visibleRegion.height;
diff --git a/src/plugins/platforms/vnc/CMakeLists.txt b/src/plugins/platforms/vnc/CMakeLists.txt
index e64e6b9c70..25cb399bd0 100644
--- a/src/plugins/platforms/vnc/CMakeLists.txt
+++ b/src/plugins/platforms/vnc/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from vnc.pro.
-
#####################################################################
## QVncIntegrationPlugin Plugin:
#####################################################################
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QVncIntegrationPlugin
OUTPUT_NAME qvnc
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vnc # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vnc
SOURCES
main.cpp
qvnc.cpp qvnc_p.h
@@ -28,9 +26,6 @@ qt_internal_add_plugin(QVncIntegrationPlugin
Qt::Network
)
-#### Keys ignored in scope 1:.:.:vnc.pro:<TRUE>:
-# OTHER_FILES = "vnc.json"
-
## Scopes:
#####################################################################
@@ -38,6 +33,3 @@ qt_internal_extend_target(QVncIntegrationPlugin CONDITION TARGET Qt::InputSuppor
LIBRARIES
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 3:.:.:vnc.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt
index b9be181e55..185b921a4f 100644
--- a/src/plugins/platforms/wasm/CMakeLists.txt
+++ b/src/plugins/platforms/wasm/CMakeLists.txt
@@ -1,15 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from wasm.pro.
-
#####################################################################
## QWasmIntegrationPlugin Plugin:
#####################################################################
qt_internal_add_plugin(QWasmIntegrationPlugin
OUTPUT_NAME qwasm
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm
PLUGIN_TYPE platforms
STATIC
SOURCES
@@ -23,9 +21,9 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
qwasmdom.cpp qwasmdom.h
qwasmevent.cpp qwasmevent.h
qwasmeventdispatcher.cpp qwasmeventdispatcher.h
- qwasmeventtranslator.cpp qwasmeventtranslator.h
qwasmfontdatabase.cpp qwasmfontdatabase.h
qwasmintegration.cpp qwasmintegration.h
+ qwasmkeytranslator.cpp qwasmkeytranslator.h
qwasmoffscreensurface.cpp qwasmoffscreensurface.h
qwasmopenglcontext.cpp qwasmopenglcontext.h
qwasmplatform.cpp qwasmplatform.h
@@ -34,9 +32,11 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
qwasmtheme.cpp qwasmtheme.h
qwasmwindow.cpp qwasmwindow.h
qwasmwindowclientarea.cpp qwasmwindowclientarea.h
+ qwasmwindowtreenode.cpp qwasmwindowtreenode.h
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
qwasminputcontext.cpp qwasminputcontext.h
qwasmwindowstack.cpp qwasmwindowstack.h
+ qwasmdrag.cpp qwasmdrag.h
DEFINES
QT_EGL_NO_X11
QT_NO_FOREACH
@@ -49,7 +49,6 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
# Resources:
set(wasmfonts_resource_files
- "${QtBase_SOURCE_DIR}/src/3rdparty/wasm/Vera.ttf"
"${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSans.ttf"
"${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSansMono.ttf"
)
@@ -71,7 +70,6 @@ qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl
Qt::OpenGLPrivate
)
-#### Keys ignored in scope 4:.:.:wasm.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
# PLUGIN_EXTENDS = "-"
set(wasm_support_files
diff --git a/src/plugins/platforms/wasm/main.cpp b/src/plugins/platforms/wasm/main.cpp
index 1b430829ad..f32ef5aab8 100644
--- a/src/plugins/platforms/wasm/main.cpp
+++ b/src/plugins/platforms/wasm/main.cpp
@@ -6,6 +6,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
class QWasmIntegrationPlugin : public QPlatformIntegrationPlugin
{
Q_OBJECT
@@ -17,7 +19,7 @@ public:
QPlatformIntegration *QWasmIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
Q_UNUSED(paramList);
- if (!system.compare(QStringLiteral("wasm"), Qt::CaseInsensitive))
+ if (!system.compare("wasm"_L1, Qt::CaseInsensitive))
return new QWasmIntegration;
return nullptr;
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index 0419509098..8027dd8fa9 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -1,602 +1,301 @@
-// Copyright (C) 2018 The Qt Company Ltd.
+// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-// QtLoader provides javascript API for managing Qt application modules.
-//
-// QtLoader provides API on top of Emscripten which supports common lifecycle
-// tasks such as displaying placeholder content while the module downloads,
-// handing application exits, and checking for browser wasm support.
-//
-// There are two usage modes:
-// * Managed: QtLoader owns and manages the HTML display elements like
-// the loader and canvas.
-// * External: The embedding HTML page owns the display elements. QtLoader
-// provides event callbacks which the page reacts to.
-//
-// Managed mode usage:
-//
-// var config = {
-// containerElements : [$("container-id")];
-// }
-// var qtLoader = new QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// External mode usage:
-//
-// var config = {
-// canvasElements : [$("canvas-id")],
-// showLoader: function() {
-// loader.style.display = 'block'
-// canvas.style.display = 'hidden'
-// },
-// showCanvas: function() {
-// loader.style.display = 'hidden'
-// canvas.style.display = 'block'
-// return canvas;
-// }
-// }
-// var qtLoader = new QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// Config keys
-//
-// moduleConfig : {}
-// Emscripten module configuration
-// containerElements : [container-element, ...]
-// One or more HTML elements. QtLoader will display loader elements
-// on these while loading the application, and replace the loader with a
-// canvas on load complete.
-// canvasElements : [canvas-element, ...]
-// One or more canvas elements.
-// showLoader : function(status, containerElement)
-// Optional loading element constructor function. Implement to create
-// a custom loading screen. This function may be called multiple times,
-// while preparing the application binary. "status" is a string
-// containing the loading sub-status, and may be either "Downloading",
-// or "Compiling". The browser may be using streaming compilation, in
-// which case the wasm module is compiled during downloading and the
-// there is no separate compile step.
-// showCanvas : function(containerElement)
-// Optional canvas constructor function. Implement to create custom
-// canvas elements.
-// showExit : function(crashed, exitCode, containerElement)
-// Optional exited element constructor function.
-// showError : function(crashed, exitCode, containerElement)
-// Optional error element constructor function.
-// statusChanged : function(newStatus)
-// Optional callback called when the status of the app has changed
-//
-// path : <string>
-// Prefix path for wasm file, realative to the loading HMTL file.
-// restartMode : "DoNotRestart", "RestartOnExit", "RestartOnCrash"
-// Controls whether the application should be reloaded on exits. The default is "DoNotRestart"
-// restartType : "RestartModule", "ReloadPage"
-// restartLimit : <int>
-// Restart attempts limit. The default is 10.
-// stdoutEnabled : <bool>
-// stderrEnabled : <bool>
-// environment : <object>
-// key-value environment variable pairs.
-//
-// QtLoader object API
-//
-// webAssemblySupported : bool
-// webGLSupported : bool
-// canLoadQt : bool
-// Reports if WebAssembly and WebGL are supported. These are requirements for
-// running Qt applications.
-// loadEmscriptenModule(applicationName)
-// Loads the application from the given emscripten javascript module file and wasm file
-// status
-// One of "Created", "Loading", "Running", "Exited".
-// crashed
-// Set to true if there was an unclean exit.
-// exitCode
-// main()/emscripten_force_exit() return code. Valid on status change to
-// "Exited", iff crashed is false.
-// exitText
-// Abort/exit message.
-// addCanvasElement
-// Add canvas at run-time. Adds a corresponding QScreen,
-// removeCanvasElement
-// Remove canvas at run-time. Removes the corresponding QScreen.
-// resizeCanvasElement
-// Signals to the application that a canvas has been resized.
-// setFontDpi
-// Sets the logical font dpi for the application.
-// module
-// Returns the Emscripten module object, or undefined if the module
-// has not been created yet. Note that the module object becomes available
-// at the very end of the loading sequence, _after_ the transition from
-// Loading to Running occurs.
-
-
-// Forces the use of constructor on QtLoader instance.
-// This passthrough makes both the old-style:
-//
-// const loader = QtLoader(config);
-//
-// and the new-style:
-//
-// const loader = new QtLoader(config);
-//
-// instantiation types work.
-function QtLoader(config)
+/**
+ * Loads the instance of a WASM module.
+ *
+ * @param config May contain any key normally accepted by emscripten and the 'qt' extra key, with
+ * the following sub-keys:
+ * - environment: { [name:string] : string }
+ * environment variables set on the instance
+ * - onExit: (exitStatus: { text: string, code?: number, crashed: bool }) => void
+ * called when the application has exited for any reason. There are two cases:
+ * aborted: crashed is true, text contains an error message.
+ * exited: crashed is false, code contians the exit code.
+ *
+ * Note that by default Emscripten does not exit when main() returns. This behavior
+ * is controlled by the EXIT_RUNTIME linker flag; set "-s EXIT_RUNTIME=1" to make
+ * Emscripten tear down the runtime and exit when main() returns.
+ *
+ * - containerElements: HTMLDivElement[]
+ * Array of host elements for Qt screens. Each of these elements is mapped to a QScreen on
+ * launch.
+ * - fontDpi: number
+ * Specifies font DPI for the instance
+ * - onLoaded: () => void
+ * Called when the module has loaded, at the point in time where any loading placeholder
+ * should be hidden and the application window should be shown.
+ * - entryFunction: (emscriptenConfig: object) => Promise<EmscriptenModule>
+ * Qt always uses emscripten's MODULARIZE option. This is the MODULARIZE entry function.
+ * - module: Promise<WebAssembly.Module>
+ * The module to create the instance from (optional). Specifying the module allows optimizing
+ * use cases where several instances are created from a single WebAssembly source.
+ * - qtdir: string
+ * Path to Qt installation. This path will be used for loading Qt shared libraries and plugins.
+ * The path is set to 'qt' by default, and is relative to the path of the web page's html file.
+ * This property is not in use when static linking is used, since this build mode includes all
+ * libraries and plugins in the wasm file.
+ * - preload: [string]: Array of file paths to json-encoded files which specifying which files to preload.
+ * The preloaded files will be downloaded at application startup and copied to the in-memory file
+ * system provided by Emscripten.
+ *
+ * Each json file must contain an array of source, destination objects:
+ * [
+ * {
+ * "source": "path/to/source",
+ * "destination": "/path/to/destination"
+ * },
+ * ...
+ * ]
+ * The source path is relative to the html file path. The destination path must be
+ * an absolute path.
+ *
+ * $QTDIR may be used as a placeholder for the "qtdir" configuration property (see @qtdir), for instance:
+ * "source": "$QTDIR/plugins/imageformats/libqjpeg.so"
+ * - localFonts.requestPermission: bool
+ * Whether Qt should request for local fonts access permission on startup (default false).
+ * - localFonts.familiesCollection string
+ * Specifies a collection of local fonts to load. Possible values are:
+ * "NoFontFamilies" : Don't load any font families
+ * "DefaultFontFamilies" : A subset of available font families; currently the "web-safe" fonts (default).
+ * "AllFontFamilies" : All local font families (not reccomended)
+ * - localFonts.extraFamilies: [string]
+ * Adds additional font families to be loaded at startup.
+ *
+ * @return Promise<instance: EmscriptenModule>
+ * The promise is resolved when the module has been instantiated and its main function has been
+ * called.
+ *
+ * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten for
+ * EmscriptenModule
+ */
+async function qtLoad(config)
{
- return new _QtLoader(config);
-}
-
-function _QtLoader(config)
-{
- const self = this;
-
- // The Emscripten module and module configuration object. The module
- // object is created in completeLoadEmscriptenModule().
- self.module = undefined;
- self.moduleConfig = config.moduleConfig || {};
-
- // Qt properties. These are propagated to the Emscripten module after
- // it has been created.
- self.qtContainerElements = undefined;
- self.qtFontDpi = 96;
-
- function webAssemblySupported() {
- return typeof WebAssembly !== "undefined"
- }
-
- function webGLSupported() {
- // We expect that WebGL is supported if WebAssembly is; however
- // the GPU may be blacklisted.
- try {
- var canvas = document.createElement("canvas");
- return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")));
- } catch (e) {
- return false;
+ const throwIfEnvUsedButNotExported = (instance, config) =>
+ {
+ const environment = config.environment;
+ if (!environment || Object.keys(environment).length === 0)
+ return;
+ const isEnvExported = typeof instance.ENV === 'object';
+ if (!isEnvExported)
+ throw new Error('ENV must be exported if environment variables are passed');
+ };
+
+ if (typeof config !== 'object')
+ throw new Error('config is required, expected an object');
+ if (typeof config.qt !== 'object')
+ throw new Error('config.qt is required, expected an object');
+ if (typeof config.qt.entryFunction !== 'function')
+ throw new Error('config.qt.entryFunction is required, expected a function');
+
+ config.qt.qtdir ??= 'qt';
+ config.qt.preload ??= [];
+
+ config.qtContainerElements = config.qt.containerElements;
+ delete config.qt.containerElements;
+ config.qtFontDpi = config.qt.fontDpi;
+ delete config.qt.fontDpi;
+
+ // Make Emscripten not call main(); this gives us more control over
+ // the startup sequence.
+ const originalNoInitialRun = config.noInitialRun;
+ const originalArguments = config.arguments;
+ config.noInitialRun = true;
+
+ // Used for rejecting a failed load's promise where emscripten itself does not allow it,
+ // like in instantiateWasm below. This allows us to throw in case of a load error instead of
+ // hanging on a promise to entry function, which emscripten unfortunately does.
+ let circuitBreakerReject;
+ const circuitBreaker = new Promise((_, reject) => { circuitBreakerReject = reject; });
+
+ // If module async getter is present, use it so that module reuse is possible.
+ if (config.qt.module) {
+ config.instantiateWasm = async (imports, successCallback) =>
+ {
+ try {
+ const module = await config.qt.module;
+ successCallback(
+ await WebAssembly.instantiate(module, imports), module);
+ } catch (e) {
+ circuitBreakerReject(e);
+ }
}
}
-
- function canLoadQt() {
- // The current Qt implementation requires WebAssembly (asm.js is not in use),
- // and also WebGL (there is no raster fallback).
- return webAssemblySupported() && webGLSupported();
- }
-
- function removeChildren(element) {
- while (element.firstChild) element.removeChild(element.firstChild);
- }
-
- function createCanvas() {
- var canvas = document.createElement("canvas");
- canvas.className = "QtCanvas";
- canvas.style.height = "100%";
- canvas.style.width = "100%";
-
- // Set contentEditable in order to enable clipboard events; hide the resulting focus frame.
- canvas.contentEditable = true;
- canvas.style.outline = "0px solid transparent";
- canvas.style.caretColor = "transparent";
- canvas.style.cursor = "default";
-
- return canvas;
- }
-
- // Set default state handler functions and create canvases if needed
- if (config.containerElements !== undefined) {
-
- config.canvasElements = config.containerElements.map(createCanvas);
-
- config.showError = config.showError || function(errorText, container) {
- removeChildren(container);
- var errorTextElement = document.createElement("text");
- errorTextElement.className = "QtError"
- errorTextElement.innerHTML = errorText;
- return errorTextElement;
+ const fetchJsonHelper = async path => (await fetch(path)).json();
+ const filesToPreload = (await Promise.all(config.qt.preload.map(fetchJsonHelper))).flat();
+ const qtPreRun = (instance) => {
+ // Copy qt.environment to instance.ENV
+ throwIfEnvUsedButNotExported(instance, config);
+ for (const [name, value] of Object.entries(config.qt.environment ?? {}))
+ instance.ENV[name] = value;
+
+ // Preload files from qt.preload
+ const makeDirs = (FS, filePath) => {
+ const parts = filePath.split("/");
+ let path = "/";
+ for (let i = 0; i < parts.length - 1; ++i) {
+ const part = parts[i];
+ if (part == "")
+ continue;
+ path += part + "/";
+ try {
+ FS.mkdir(path);
+ } catch (error) {
+ const EEXIST = 20;
+ if (error.errno != EEXIST)
+ throw error;
+ }
+ }
}
- config.showLoader = config.showLoader || function(loadingState, container) {
- removeChildren(container);
- var loadingText = document.createElement("text");
- loadingText.className = "QtLoading"
- loadingText.innerHTML = '<p><center> ${loadingState}...</center><p>';
- return loadingText;
- };
-
- config.showCanvas = config.showCanvas || function(canvas, container) {
- removeChildren(container);
+ const extractFilenameAndDir = (path) => {
+ const parts = path.split('/');
+ const filename = parts.pop();
+ const dir = parts.join('/');
+ return {
+ filename: filename,
+ dir: dir
+ };
}
-
- config.showExit = config.showExit || function(crashed, exitCode, container) {
- if (!crashed)
- return undefined;
-
- removeChildren(container);
- var fontSize = 54;
- var crashSymbols = ["\u{1F615}", "\u{1F614}", "\u{1F644}", "\u{1F928}", "\u{1F62C}",
- "\u{1F915}", "\u{2639}", "\u{1F62E}", "\u{1F61E}", "\u{1F633}"];
- var symbolIndex = Math.floor(Math.random() * crashSymbols.length);
- var errorHtml = `<font size='${fontSize}'> ${crashSymbols[symbolIndex]} </font>`
- var errorElement = document.createElement("text");
- errorElement.className = "QtExit"
- errorElement.innerHTML = errorHtml;
- return errorElement;
+ const preloadFile = (file) => {
+ makeDirs(instance.FS, file.destination);
+ const source = file.source.replace('$QTDIR', config.qt.qtdir);
+ const filenameAndDir = extractFilenameAndDir(file.destination);
+ instance.FS.createPreloadedFile(filenameAndDir.dir, filenameAndDir.filename, source, true, true);
}
+ const isFsExported = typeof instance.FS === 'object';
+ if (!isFsExported)
+ throw new Error('FS must be exported if preload is used');
+ filesToPreload.forEach(preloadFile);
}
- config.restartMode = config.restartMode || "DoNotRestart";
- config.restartLimit = config.restartLimit || 10;
-
- if (config.stdoutEnabled === undefined) config.stdoutEnabled = true;
- if (config.stderrEnabled === undefined) config.stderrEnabled = true;
-
- // Make sure config.path is defined and ends with "/" if needed
- if (config.path === undefined)
- config.path = "";
- if (config.path.length > 0 && !config.path.endsWith("/"))
- config.path = config.path.concat("/");
-
- if (config.environment === undefined)
- config.environment = {};
-
- var publicAPI = {};
- publicAPI.webAssemblySupported = webAssemblySupported();
- publicAPI.webGLSupported = webGLSupported();
- publicAPI.canLoadQt = canLoadQt();
- publicAPI.canLoadApplication = canLoadQt();
- publicAPI.status = undefined;
- publicAPI.loadEmscriptenModule = loadEmscriptenModule;
- publicAPI.addCanvasElement = addCanvasElement;
- publicAPI.removeCanvasElement = removeCanvasElement;
- publicAPI.resizeCanvasElement = resizeCanvasElement;
- publicAPI.setFontDpi = setFontDpi;
- publicAPI.fontDpi = fontDpi;
- publicAPI.module = module;
-
- self.restartCount = 0;
-
- function handleError(error) {
- self.error = error;
- setStatus("Error");
- console.error(error);
- }
-
- function fetchResource(filePath) {
- var fullPath = config.path + filePath;
- return fetch(fullPath).then(function(response) {
- if (!response.ok) {
- let err = response.status + " " + response.statusText + " " + response.url;
- handleError(err);
- return Promise.reject(err)
- } else {
- return response;
- }
- });
- }
-
- function fetchText(filePath) {
- return fetchResource(filePath).then(function(response) {
- return response.text();
- });
- }
+ if (!config.preRun)
+ config.preRun = [];
+ config.preRun.push(qtPreRun);
- function fetchThenCompileWasm(response) {
- return response.arrayBuffer().then(function(data) {
- self.loaderSubState = "Compiling";
- setStatus("Loading") // trigger loaderSubState update
- return WebAssembly.compile(data);
- });
- }
-
- function fetchCompileWasm(filePath) {
- return fetchResource(filePath).then(function(response) {
- if (typeof WebAssembly.compileStreaming !== "undefined") {
- self.loaderSubState = "Downloading/Compiling";
- setStatus("Loading");
- return WebAssembly.compileStreaming(response).catch(function(error) {
- // compileStreaming may/will fail if the server does not set the correct
- // mime type (application/wasm) for the wasm file. Fall back to fetch,
- // then compile in this case.
- return fetchThenCompileWasm(response);
- });
- } else {
- // Fall back to fetch, then compile if compileStreaming is not supported
- return fetchThenCompileWasm(response);
- }
- });
+ const originalOnRuntimeInitialized = config.onRuntimeInitialized;
+ config.onRuntimeInitialized = () => {
+ originalOnRuntimeInitialized?.();
+ config.qt.onLoaded?.();
}
- function loadEmscriptenModule(applicationName) {
-
- // Loading in qtloader.js goes through four steps:
- // 1) Check prerequisites
- // 2) Download resources
- // 3) Configure the emscripten Module object
- // 4) Start the emcripten runtime, after which emscripten takes over
-
- // Check for Wasm & WebGL support; set error and return before downloading resources if missing
- if (!webAssemblySupported()) {
- handleError("Error: WebAssembly is not supported");
- return;
- }
- if (!webGLSupported()) {
- handleError("Error: WebGL is not supported");
- return;
- }
-
- // Continue waiting if loadEmscriptenModule() is called again
- if (publicAPI.status == "Loading")
- return;
- self.loaderSubState = "Downloading";
- setStatus("Loading");
-
- // Fetch emscripten generated javascript runtime
- var emscriptenModuleSource = undefined
- var emscriptenModuleSourcePromise = fetchText(applicationName + ".js").then(function(source) {
- emscriptenModuleSource = source
- });
-
- // Fetch and compile wasm module
- var wasmModule = undefined;
- var wasmModulePromise = fetchCompileWasm(applicationName + ".wasm").then(function (module) {
- wasmModule = module;
- });
-
- // Wait for all resources ready
- Promise.all([emscriptenModuleSourcePromise, wasmModulePromise]).then(function(){
- completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule);
- }).catch(function(error) {
- handleError(error);
- // An error here is fatal, abort
- self.moduleConfig.onAbort(error)
- });
+ const originalLocateFile = config.locateFile;
+ config.locateFile = filename => {
+ const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename;
+ if (originalLocatedFilename.startsWith('libQt6'))
+ return `${config.qt.qtdir}/lib/${originalLocatedFilename}`;
+ return originalLocatedFilename;
}
- function completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule) {
+ let onExitCalled = false;
+ const originalOnExit = config.onExit;
+ config.onExit = code => {
+ originalOnExit?.();
- // The wasm binary has been compiled into a module during resource download,
- // and is ready to be instantiated. Define the instantiateWasm callback which
- // emscripten will call to create the instance.
- self.moduleConfig.instantiateWasm = function(imports, successCallback) {
- WebAssembly.instantiate(wasmModule, imports).then(function(instance) {
- successCallback(instance, wasmModule);
- }, function(error) {
- handleError(error)
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ code,
+ crashed: false
});
- return {};
- };
-
- self.moduleConfig.locateFile = self.moduleConfig.locateFile || function(filename) {
- return config.path + filename;
- };
-
- // Attach status callbacks
- self.moduleConfig.setStatus = self.moduleConfig.setStatus || function(text) {
- // Currently the only usable status update from this function
- // is "Running..."
- if (text.startsWith("Running"))
- setStatus("Running");
- };
- self.moduleConfig.monitorRunDependencies = self.moduleConfig.monitorRunDependencies || function(left) {
- // console.log("monitorRunDependencies " + left)
- };
-
- // Attach standard out/err callbacks.
- self.moduleConfig.print = self.moduleConfig.print || function(text) {
- if (config.stdoutEnabled)
- console.log(text)
- };
- self.moduleConfig.printErr = self.moduleConfig.printErr || function(text) {
- if (config.stderrEnabled)
- console.warn(text)
- };
-
- // Error handling: set status to "Exited", update crashed and
- // exitCode according to exit type.
- // Emscripten will typically call printErr with the error text
- // as well. Note that emscripten may also throw exceptions from
- // async callbacks. These should be handled in window.onerror by user code.
- self.moduleConfig.onAbort = self.moduleConfig.onAbort || function(text) {
- publicAPI.crashed = true;
- publicAPI.exitText = text;
- setStatus("Exited");
- };
- self.moduleConfig.quit = self.moduleConfig.quit || function(code, exception) {
-
- // Emscripten (and Qt) supports exiting from main() while keeping the app
- // running. Don't transition into the "Exited" state for clean exits.
- if (code == 0)
- return;
-
- if (exception.name == "ExitStatus") {
- // Clean exit with code
- publicAPI.exitText = undefined
- publicAPI.exitCode = code;
- } else {
- publicAPI.exitText = exception.toString();
- publicAPI.crashed = true;
- // Print stack trace to console
- console.log(exception);
- }
- setStatus("Exited");
- };
-
- self.moduleConfig.preRun = self.moduleConfig.preRun || []
- self.moduleConfig.preRun.push(function(module) {
- // Set environment variables
- for (var [key, value] of Object.entries(config.environment)) {
- module.ENV[key.toUpperCase()] = value;
- }
- // Propagate Qt module properties
- module.qtContainerElements = self.qtContainerElements;
- module.qtFontDpi = self.qtFontDpi;
- });
-
- self.moduleConfig.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'});
-
- self.qtContainerElements = config.canvasElements;
-
- config.restart = function() {
-
- // Restart by reloading the page. This will wipe all state which means
- // reload loops can't be prevented.
- if (config.restartType == "ReloadPage") {
- location.reload();
- }
-
- // Restart by readling the emscripten app module.
- ++self.restartCount;
- if (self.restartCount > config.restartLimit) {
- handleError("Error: This application has crashed too many times and has been disabled. Reload the page to try again.");
- return;
- }
- loadEmscriptenModule(applicationName);
- };
-
- publicAPI.exitCode = undefined;
- publicAPI.exitText = undefined;
- publicAPI.crashed = false;
-
- // Load the Emscripten application module. This is done by eval()'ing the
- // javascript runtime generated by Emscripten, and then calling
- // createQtAppInstance(), which was added to the global scope.
- eval(emscriptenModuleSource);
- createQtAppInstance(self.moduleConfig).then(function(module) {
- self.module = module;
- });
- }
-
- function setErrorContent() {
- if (config.containerElements === undefined) {
- if (config.showError !== undefined)
- config.showError(self.error);
- return;
- }
-
- for (container of config.containerElements) {
- var errorElement = config.showError(self.error, container);
- container.appendChild(errorElement);
}
}
- function setLoaderContent() {
- if (config.containerElements === undefined) {
- if (config.showLoader !== undefined)
- config.showLoader(self.loaderSubState);
- return;
- }
-
- for (container of config.containerElements) {
- var loaderElement = config.showLoader(self.loaderSubState, container);
- container.appendChild(loaderElement);
+ const originalOnAbort = config.onAbort;
+ config.onAbort = text =>
+ {
+ originalOnAbort?.();
+
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text,
+ crashed: true
+ });
}
- }
-
- function setCanvasContent() {
- if (config.containerElements === undefined) {
- if (config.showCanvas !== undefined)
- config.showCanvas();
+ };
+
+ // Call app/emscripten module entry function. It may either come from the emscripten
+ // runtime script or be customized as needed.
+ let instance;
+ try {
+ instance = await Promise.race(
+ [circuitBreaker, config.qt.entryFunction(config)]);
+
+ // Call main after creating the instance. We've opted into manually
+ // calling main() by setting noInitialRun in the config. Thie Works around
+ // issue where Emscripten suppresses all exceptions thrown during main.
+ if (!originalNoInitialRun)
+ instance.callMain(originalArguments);
+ } catch (e) {
+ // If this is the exception thrown by app.exec() then that is a normal
+ // case and we suppress it.
+ if (e == "unwind") // not much to go on
return;
- }
- for (var i = 0; i < config.containerElements.length; ++i) {
- var container = config.containerElements[i];
- var canvas = config.canvasElements[i];
- config.showCanvas(canvas, container);
- container.appendChild(canvas);
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text: e.message,
+ crashed: true
+ });
}
+ throw e;
}
- function setExitContent() {
-
- // publicAPI.crashed = true;
-
- if (publicAPI.status != "Exited")
- return;
-
- if (config.containerElements === undefined) {
- if (config.showExit !== undefined)
- config.showExit(publicAPI.crashed, publicAPI.exitCode);
- return;
- }
-
- if (!publicAPI.crashed)
- return;
+ return instance;
+}
- for (container of config.containerElements) {
- var loaderElement = config.showExit(publicAPI.crashed, publicAPI.exitCode, container);
- if (loaderElement !== undefined)
- container.appendChild(loaderElement);
- }
+// Compatibility API. This API is deprecated,
+// and will be removed in a future version of Qt.
+function QtLoader(qtConfig) {
+
+ const warning = 'Warning: The QtLoader API is deprecated and will be removed in ' +
+ 'a future version of Qt. Please port to the new qtLoad() API.';
+ console.warn(warning);
+
+ let emscriptenConfig = qtConfig.moduleConfig || {}
+ qtConfig.moduleConfig = undefined;
+ const showLoader = qtConfig.showLoader;
+ qtConfig.showLoader = undefined;
+ const showError = qtConfig.showError;
+ qtConfig.showError = undefined;
+ const showExit = qtConfig.showExit;
+ qtConfig.showExit = undefined;
+ const showCanvas = qtConfig.showCanvas;
+ qtConfig.showCanvas = undefined;
+ if (qtConfig.canvasElements) {
+ qtConfig.containerElements = qtConfig.canvasElements
+ qtConfig.canvasElements = undefined;
+ } else {
+ qtConfig.containerElements = qtConfig.containerElements;
+ qtConfig.containerElements = undefined;
}
-
- var committedStatus = undefined;
- function handleStatusChange() {
- if (publicAPI.status != "Loading" && committedStatus == publicAPI.status)
- return;
- committedStatus = publicAPI.status;
-
- if (publicAPI.status == "Error") {
- setErrorContent();
- } else if (publicAPI.status == "Loading") {
- setLoaderContent();
- } else if (publicAPI.status == "Running") {
- setCanvasContent();
- } else if (publicAPI.status == "Exited") {
- if (config.restartMode == "RestartOnExit" ||
- config.restartMode == "RestartOnCrash" && publicAPI.crashed) {
- committedStatus = undefined;
- config.restart();
- } else {
- setExitContent();
+ emscriptenConfig.qt = qtConfig;
+
+ let qtloader = {
+ exitCode: undefined,
+ exitText: "",
+ loadEmscriptenModule: _name => {
+ try {
+ qtLoad(emscriptenConfig);
+ } catch (e) {
+ showError?.(e.message);
}
}
-
- // Send status change notification
- if (config.statusChanged)
- config.statusChanged(publicAPI.status);
}
- function setStatus(status) {
- if (status != "Loading" && publicAPI.status == status)
- return;
- publicAPI.status = status;
-
- window.setTimeout(function() { handleStatusChange(); }, 0);
- }
-
- function addCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtAddContainerElement(element);
- else
- console.log("Error: addCanvasElement can only be called in the Running state");
+ qtConfig.onLoaded = () => {
+ showCanvas?.();
}
- function removeCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtRemoveContainerElement(element);
- else
- console.log("Error: removeCanvasElement can only be called in the Running state");
+ qtConfig.onExit = exit => {
+ qtloader.exitCode = exit.code
+ qtloader.exitText = exit.text;
+ showExit?.();
}
- function resizeCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtResizeContainerElement(element);
- }
-
- function setFontDpi(dpi) {
- self.qtFontDpi = dpi;
- if (publicAPI.status == "Running")
- self.module.qtUpdateDpi();
- }
+ showLoader?.("Loading");
- function fontDpi() {
- return self.qtFontDpi;
- }
-
- function module() {
- return self.module;
- }
-
- setStatus("Created");
-
- return publicAPI;
-}
+ return qtloader;
+};
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
index e9217cbefc..4c3cb46ba3 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
@@ -5,10 +5,12 @@
#include "qwasmscreen.h"
#include "qwasmwindow.h"
#include "qwasmintegration.h"
-#include <QtGui/private/qaccessiblebridgeutils_p.h>
-
#include <QtGui/qwindow.h>
+#if QT_CONFIG(accessibility)
+
+#include <QtGui/private/qaccessiblebridgeutils_p.h>
+
Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
// Qt WebAssembly a11y backend
@@ -21,13 +23,6 @@ Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
// events. In addition or alternatively, we could also walk the accessibility tree
// from setRootObject().
-namespace {
-QWasmWindow *asWasmWindow(QWindow *window)
-{
- return static_cast<QWasmWindow*>(window->handle());
-}
-} // namespace
-
QWasmAccessibility::QWasmAccessibility()
{
@@ -65,12 +60,9 @@ void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
emscripten::val document = getDocument(container);
emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
button.set("innerText", std::string("Enable Screen Reader"));
+ button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader"));
container.call<void>("appendChild", button);
- emscripten::val style = button["style"];
- style.set("width", "100%");
- style.set("height", "100%");
-
auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback>
(button, std::string("click"), [this](emscripten::val) { enableAccessibility(); }));
m_enableButtons.insert(std::make_pair(window, std::move(enableContext)));
@@ -108,22 +100,24 @@ void QWasmAccessibility::enableAccessibility()
emscripten::val QWasmAccessibility::getContainer(QWindow *window)
{
- return window ? asWasmWindow(window)->a11yContainer() : emscripten::val::undefined();
+ return window ? static_cast<QWasmWindow *>(window->handle())->a11yContainer()
+ : emscripten::val::undefined();
}
emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface)
{
- QWindow *window = iface->window();
- if (!window) {
- //this is needed to add tabs as the window is not available
- if (iface->parent()->window()) {
- window = iface->parent()->window();
- } else {
- return emscripten::val::undefined();
- }
- }
+ if (!iface)
+ return emscripten::val::undefined();
+ return getContainer(getWindow(iface));
+}
- return getContainer(window);
+QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
+{
+ QWindow *window = iface->window();
+ // this is needed to add tabs as the window is not available
+ if (!window && iface->parent())
+ window = iface->parent()->window();
+ return window;
}
emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
@@ -246,12 +240,21 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
case QAccessible::Dialog: {
element = document.call<emscripten::val>("createElement", std::string("dialog"));
}break;
- case QAccessible::ToolBar:
- case QAccessible::ButtonMenu: {
+ case QAccessible::ToolBar:{
element = document.call<emscripten::val>("createElement", std::string("div"));
QString text = iface->text(QAccessible::Name);
- element.call<void>("setAttribute", std::string("role"), std::string("widget"));
+ element.call<void>("setAttribute", std::string("role"), std::string("toolbar"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ }break;
+ case QAccessible::MenuItem:
+ case QAccessible::ButtonMenu: {
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ QString text = iface->text(QAccessible::Name);
+
+ element.call<void>("setAttribute", std::string("role"), std::string("menuitem"));
element.call<void>("setAttribute", std::string("title"), text.toStdString());
element.call<void>("addEventListener", emscripten::val("click"),
emscripten::val::module_property("qtEventReceived"), true);
@@ -260,12 +263,14 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
case QAccessible::PopupMenu: {
element = document.call<emscripten::val>("createElement",std::string("div"));
QString text = iface->text(QAccessible::Name);
- element.call<void>("setAttribute", std::string("role"), std::string("widget"));
+ element.call<void>("setAttribute", std::string("role"), std::string("menubar"));
element.call<void>("setAttribute", std::string("title"), text.toStdString());
for (int i = 0; i < iface->childCount(); ++i) {
- ensureHtmlElement(iface->child(i));
- setHtmlElementTextName(iface->child(i));
- setHtmlElementGeometry(iface->child(i));
+ emscripten::val childElement = emscripten::val::undefined();
+ childElement= ensureHtmlElement(iface->child(i));
+ childElement.call<void>("setAttribute", std::string("aria-owns"), text.toStdString());
+ setHtmlElementTextName(iface->child(i));
+ setHtmlElementGeometry(iface->child(i));
}
}break;
case QAccessible::EditableText: {
@@ -327,7 +332,17 @@ void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, b
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
{
emscripten::val element = ensureHtmlElement(iface);
- setHtmlElementGeometry(element, iface->rect());
+
+ // QAccessibleInterface gives us the geometry in global (screen) coordinates. Translate that
+ // to window geometry in order to position elements relative to window origin.
+ QWindow *window = getWindow(iface);
+ if (!window)
+ qCWarning(lcQpaAccessibility) << "Unable to find window for" << iface << "setting null geometry";
+ QRect screenGeometry = iface->rect();
+ QPoint windowPos = window ? window->mapFromGlobal(screenGeometry.topLeft()) : QPoint();
+ QRect windowGeometry(windowPos, screenGeometry.size());
+
+ setHtmlElementGeometry(element, windowGeometry);
}
void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
@@ -359,12 +374,21 @@ void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) {
element.set("innerHTML", value.toStdString());
}
+void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) {
+ emscripten::val element = ensureHtmlElement(iface);
+ QString desc = iface->text(QAccessible::Description);
+ element.call<void>("setAttribute", std::string("aria-description"), desc.toStdString());
+}
+
void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
case QAccessible::NameChanged: {
setHtmlElementTextName(event->accessibleInterface());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
break;
@@ -383,7 +407,9 @@ void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) {
case QAccessible::TextCaretMoved: {
setHtmlElementTextNameLE(event->accessibleInterface());
} break;
-
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
break;
@@ -394,13 +420,11 @@ void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
{
QAccessibleInterface *iface = m_elements.key(event["target"]);
-
if (iface == nullptr) {
return;
} else {
QString eventType = QString::fromStdString(event["type"].as<std::string>());
const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
-
if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
@@ -418,8 +442,11 @@ void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
} else if (eventType == "input") {
- if (iface->editableTextInterface()) {
- std::string insertText = event["target"]["value"].as<std::string>();
+ // as EditableTextInterface is not implemented in qml accessibility
+ // so we need to check the role for text to update in the textbox during accessibility
+
+ if (iface->editableTextInterface() || iface->role() == QAccessible::EditableText) {
+ std::string insertText = event["target"]["value"].as<std::string>();
iface->setText(QAccessible::Value, QString::fromStdString(insertText));
}
}
@@ -444,6 +471,9 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
bool checkedString = accessible->state().checked ? true : false;
element.call<void>("setAttribute", std::string("checked"), checkedString);
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
break;
@@ -460,6 +490,9 @@ void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
emscripten::val element = ensureHtmlElement(iface);
element.call<void>("setAttribute", std::string("title"), text.toStdString());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
break;
@@ -471,6 +504,7 @@ void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
QString text = iface->text(QAccessible::Name);
QString desc = iface->text(QAccessible::Description);
switch (event->type()) {
+ case QAccessible::Focus:
case QAccessible::NameChanged:
case QAccessible::MenuStart ://"TODO: To implement later
case QAccessible::PopupMenuStart://"TODO: To implement later
@@ -478,6 +512,9 @@ void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
emscripten::val element = ensureHtmlElement(iface);
element.call<void>("setAttribute", std::string("title"), text.toStdString());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
break;
@@ -492,7 +529,9 @@ void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
case QAccessible::StateChanged: {
setHtmlElementTextName(event->accessibleInterface());
} break;
-
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
break;
@@ -510,6 +549,7 @@ void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
setHtmlElementVisibility(iface, visible);
setHtmlElementGeometry(iface);
setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
for (int i = 0; i < iface->childCount(); ++i)
populateAccessibilityTree(iface->child(i));
@@ -528,6 +568,9 @@ void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
std::string checkedString = accessible->state().checked ? "true" : "false";
element.call<void>("setAttribute", std::string("checked"), checkedString);
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
break;
@@ -547,6 +590,9 @@ void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("value"), valueString);
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
break;
@@ -566,6 +612,9 @@ void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("value"), valueString);
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
break;
@@ -585,6 +634,9 @@ void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
element.call<void>("setAttribute", std::string("aria-valuenow"), valueString);
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
break;
@@ -601,6 +653,9 @@ void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
case QAccessible::Focus: {
setHtmlElementTextName(event->accessibleInterface());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
break;
@@ -616,6 +671,9 @@ void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
case QAccessible::Focus: {
setHtmlElementTextName(event->accessibleInterface());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
break;
@@ -637,12 +695,12 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
// https://doc.qt.io/qt-5/qaccessible.html#Event-enum
switch (event->type()) {
case QAccessible::ObjectShow:
-
setHtmlElementVisibility(iface, true);
// Sync up properties on show;
setHtmlElementGeometry(iface);
setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
return;
break;
@@ -727,3 +785,5 @@ void QWasmAccessibility::onHtmlEventReceived(emscripten::val event)
EMSCRIPTEN_BINDINGS(qtButtonEvent) {
function("qtEventReceived", &QWasmAccessibility::onHtmlEventReceived);
}
+
+#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.h b/src/plugins/platforms/wasm/qwasmaccessibility.h
index 73a32d31b7..c4be7f0d72 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.h
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.h
@@ -4,6 +4,11 @@
#ifndef QWASMACCESIBILITY_H
#define QWASMACCESIBILITY_H
+#include <QtCore/qtconfigmacros.h>
+#include <QtGui/qtguiglobal.h>
+
+#if QT_CONFIG(accessibility)
+
#include <QtCore/qhash.h>
#include <private/qstdweb_p.h>
#include <qpa/qplatformaccessibility.h>
@@ -36,6 +41,7 @@ private:
static emscripten::val getContainer(QAccessibleInterface *iface);
static emscripten::val getDocument(const emscripten::val &container);
static emscripten::val getDocument(QAccessibleInterface *iface);
+ static QWindow *getWindow(QAccessibleInterface *iface);
emscripten::val createHtmlElement(QAccessibleInterface *iface);
void destroyHtmlElement(QAccessibleInterface *iface);
@@ -45,6 +51,7 @@ private:
void setHtmlElementGeometry(emscripten::val element, QRect geometry);
void setHtmlElementTextName(QAccessibleInterface *iface);
void setHtmlElementTextNameLE(QAccessibleInterface *iface);
+ void setHtmlElementDescription(QAccessibleInterface *iface);
void handleStaticTextUpdate(QAccessibleEvent *event);
void handleButtonUpdate(QAccessibleEvent *event);
@@ -80,4 +87,6 @@ private:
};
+#endif // QT_CONFIG(accessibility)
+
#endif
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
index e962592862..a3c1ae8a50 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.cpp
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
@@ -4,6 +4,7 @@
#include "qwasmbackingstore.h"
#include "qwasmwindow.h"
#include "qwasmcompositor.h"
+#include "qwasmdom.h"
#include <QtGui/qpainter.h>
#include <QtGui/qbackingstore.h>
@@ -75,37 +76,8 @@ void QWasmBackingStore::updateTexture(QWasmWindow *window)
clippedDpiScaledRegion |= r;
}
- for (const QRect &dirtyRect : clippedDpiScaledRegion) {
- constexpr int BytesPerColor = 4;
- if (dirtyRect.width() == imageRect.width()) {
- // Copy a contiguous chunk of memory
- // ...............
- // OOOOOOOOOOOOOOO
- // OOOOOOOOOOOOOOO -> image data
- // OOOOOOOOOOOOOOO
- // ...............
- auto imageMemory = emscripten::typed_memory_view(dirtyRect.width() * dirtyRect.height()
- * BytesPerColor,
- m_image.constScanLine(dirtyRect.y()));
- m_webImageDataArray["data"].call<void>("set", imageMemory);
- } else {
- // Go through the scanlines manually to set the individual lines in bulk. This is
- // marginally less performant than the above.
- // ...............
- // ...OOOOOOOOO... r = 0 -> image data
- // ...OOOOOOOOO... r = 1 -> image data
- // ...OOOOOOOOO... r = 2 -> image data
- // ...............
- for (int r = 0; r < dirtyRect.height(); ++r) {
- auto scanlineMemory = emscripten::typed_memory_view(
- dirtyRect.width() * 4,
- m_image.constScanLine(r) + BytesPerColor * dirtyRect.x());
- m_webImageDataArray["data"].call<void>("set", scanlineMemory,
- (r * dirtyRect.width() + dirtyRect.x())
- * BytesPerColor);
- }
- }
- }
+ for (const QRect &dirtyRect : clippedDpiScaledRegion)
+ dom::drawImageToWebImageDataArray(m_image, m_webImageDataArray, dirtyRect);
m_dirty = QRegion();
}
diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.h b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
index 6150ea19da..89704f2d2c 100644
--- a/src/plugins/platforms/wasm/qwasmbase64iconstore.h
+++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
@@ -4,6 +4,7 @@
#ifndef QWASMBASE64IMAGESTORE_H
#define QWASMBASE64IMAGESTORE_H
+#include <string>
#include <string_view>
#include <QtCore/qtconfigmacros.h>
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 215ff50aa0..1aa3ffa5b3 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -3,6 +3,7 @@
#include "qwasmclipboard.h"
#include "qwasmdom.h"
+#include "qwasmevent.h"
#include "qwasmwindow.h"
#include <private/qstdweb_p.h>
@@ -26,10 +27,10 @@ static void commonCopyEvent(val event)
// doing it this way seems to sanitize the text better that calling data() like down below
if (_mimes->hasText()) {
event["clipboardData"].call<void>("setData", val("text/plain"),
- _mimes->text().toJsString());
+ _mimes->text().toEcmaString());
}
if (_mimes->hasHtml()) {
- event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toJsString());
+ event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
}
for (auto mimetype : _mimes->formats()) {
@@ -37,7 +38,7 @@ static void commonCopyEvent(val event)
continue;
QByteArray ba = _mimes->data(mimetype);
if (!ba.isEmpty())
- event["clipboardData"].call<void>("setData", mimetype.toJsString(),
+ event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
val(ba.constData()));
}
@@ -49,7 +50,7 @@ static void qClipboardCutTo(val event)
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "X");
+ 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
}
commonCopyEvent(event);
@@ -69,25 +70,7 @@ static void qClipboardPasteTo(val event)
{
event.call<void>("preventDefault"); // prevent browser from handling drop event
- static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
- readDataCancellation = qstdweb::readDataTransfer(
- event["clipboardData"],
- [](QByteArray fileContent) {
- QImage image;
- image.loadFromData(fileContent, nullptr);
- return image;
- },
- [event](std::unique_ptr<QMimeData> data) {
- if (data->formats().isEmpty())
- return;
-
- // Persist clipboard data so that the app can read it when handling the CTRL+V
- QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(
- data.release(), QClipboard::Clipboard);
-
- QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
- Qt::ControlModifier, "V");
- });
+ QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
@@ -129,11 +112,9 @@ void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
writeToClipboard();
}
-QWasmClipboard::ProcessKeyboardResult
-QWasmClipboard::processKeyboard(const QWasmEventTranslator::TranslatedEvent &event,
- const QFlags<Qt::KeyboardModifier> &modifiers)
+QWasmClipboard::ProcessKeyboardResult QWasmClipboard::processKeyboard(const KeyEvent &event)
{
- if (event.type != QEvent::KeyPress || !modifiers.testFlag(Qt::ControlModifier))
+ if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
return ProcessKeyboardResult::Ignored;
if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
@@ -173,15 +154,15 @@ void QWasmClipboard::initClipboardPermissions()
})());
}
-void QWasmClipboard::installEventHandlers(const emscripten::val &screenElement)
+void QWasmClipboard::installEventHandlers(const emscripten::val &target)
{
emscripten::val cContext = val::undefined();
emscripten::val isChromium = val::global("window")["chrome"];
- if (!isChromium.isUndefined()) {
+ if (!isChromium.isUndefined()) {
cContext = val::global("document");
- } else {
- cContext = screenElement;
- }
+ } else {
+ cContext = target;
+ }
// Fallback path for browsers which do not support direct clipboard access
cContext.call<void>("addEventListener", val("cut"),
val::module_property("qtClipboardCutTo"), true);
@@ -262,12 +243,12 @@ void QWasmClipboard::writeToClipboardApi()
// we have a blob, now create a ClipboardItem
emscripten::val type = emscripten::val::array();
- type.set("type", mimetype.toJsString());
+ type.set("type", mimetype.toEcmaString());
emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
emscripten::val clipboardItemObject = emscripten::val::object();
- clipboardItemObject.set(mimetype.toJsString(), contentBlob);
+ clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
@@ -301,4 +282,23 @@ void QWasmClipboard::writeToClipboard()
val document = val::global("document");
document.call<val>("execCommand", val("copy"));
}
+
+void QWasmClipboard::sendClipboardData(emscripten::val event)
+{
+ qDebug() << "sendClipboardData";
+
+ dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
+ const auto mimeCallback = std::function([transfer](QMimeData *data) {
+
+ // Persist clipboard data so that the app can read it when handling the CTRL+V
+ QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ delete transfer;
+ });
+
+ transfer->toMimeDataWithFile(mimeCallback);
+}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.h b/src/plugins/platforms/wasm/qwasmclipboard.h
index 924c3f582f..86618dd560 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.h
+++ b/src/plugins/platforms/wasm/qwasmclipboard.h
@@ -7,15 +7,16 @@
#include <QObject>
#include <qpa/qplatformclipboard.h>
+#include <private/qstdweb_p.h>
#include <QMimeData>
#include <emscripten/bind.h>
#include <emscripten/val.h>
-#include "qwasmeventtranslator.h"
-
QT_BEGIN_NAMESPACE
+struct KeyEvent;
+
class QWasmClipboard : public QObject, public QPlatformClipboard
{
public:
@@ -34,10 +35,10 @@ public:
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
- ProcessKeyboardResult processKeyboard(const QWasmEventTranslator::TranslatedEvent &event,
- const QFlags<Qt::KeyboardModifier> &modifiers);
- void installEventHandlers(const emscripten::val &canvas);
+ ProcessKeyboardResult processKeyboard(const KeyEvent &event);
+ static void installEventHandlers(const emscripten::val &target);
bool hasClipboardApi();
+ void sendClipboardData(emscripten::val event);
private:
void initClipboardPermissions();
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp
index a66c5f5beb..ef460f666f 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp
@@ -3,45 +3,19 @@
#include "qwasmcompositor.h"
#include "qwasmwindow.h"
-#include "qwasmeventtranslator.h"
-#include "qwasmeventdispatcher.h"
-#include "qwasmclipboard.h"
-#include "qwasmevent.h"
-#include <QtGui/private/qwindow_p.h>
-
-#include <private/qguiapplication_p.h>
+#include <private/qeventdispatcher_wasm_p.h>
#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtGui/qguiapplication.h>
-
-#include <emscripten/bind.h>
-namespace {
-QWasmWindow *asWasmWindow(QWindow *window)
-{
- return static_cast<QWasmWindow*>(window->handle());
-}
-} // namespace
+#include <emscripten/html5.h>
using namespace emscripten;
-Q_GUI_EXPORT int qt_defaultDpiX();
+bool QWasmCompositor::m_requestUpdateHoldEnabled = true;
-QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
- : QObject(screen),
- m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this)),
- m_eventTranslator(std::make_unique<QWasmEventTranslator>())
+QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen)
{
- m_touchDevice = std::make_unique<QPointingDevice>(
- "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
- QPointingDevice::PointerType::Finger,
- QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
- | QPointingDevice::Capability::NormalizedPosition,
- 10, 0);
-
- QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
}
@@ -50,110 +24,35 @@ QWasmCompositor::~QWasmCompositor()
if (m_requestAnimationFrameId != -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
- destroy();
-}
-
-void QWasmCompositor::onScreenDeleting()
-{
- deregisterEventHandlers();
-}
-
-void QWasmCompositor::deregisterEventHandlers()
-{
- QByteArray screenElementSelector = screen()->eventTargetId().toUtf8();
- emscripten_set_keydown_callback(screenElementSelector.constData(), 0, 0, NULL);
- emscripten_set_keyup_callback(screenElementSelector.constData(), 0, 0, NULL);
-
- emscripten_set_touchstart_callback(screenElementSelector.constData(), 0, 0, NULL);
- emscripten_set_touchend_callback(screenElementSelector.constData(), 0, 0, NULL);
- emscripten_set_touchmove_callback(screenElementSelector.constData(), 0, 0, NULL);
- emscripten_set_touchcancel_callback(screenElementSelector.constData(), 0, 0, NULL);
-}
-
-void QWasmCompositor::destroy()
-{
// TODO(mikolaj.boc): Investigate if m_isEnabled is needed at all. It seems like a frame should
// not be generated after this instead.
m_isEnabled = false; // prevent frame() from creating a new m_context
}
-void QWasmCompositor::initEventHandlers()
-{
- constexpr EM_BOOL UseCapture = 1;
-
- const QByteArray screenElementSelector = screen()->eventTargetId().toUtf8();
- emscripten_set_keydown_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &keyboard_cb);
- emscripten_set_keyup_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &keyboard_cb);
-
- emscripten_set_touchstart_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &touchCallback);
- emscripten_set_touchend_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &touchCallback);
- emscripten_set_touchmove_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &touchCallback);
- emscripten_set_touchcancel_callback(screenElementSelector.constData(), (void *)this, UseCapture,
- &touchCallback);
-}
-
-void QWasmCompositor::addWindow(QWasmWindow *window)
+void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindow *window)
{
- m_windowStack.pushWindow(window);
- m_windowStack.topWindow()->requestActivateWindow();
-
- updateEnabledState();
+ auto allWindows = screen()->allWindows();
+ setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) {
+ return !element->context2d().isUndefined();
+ }));
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval)
+ m_requestUpdateWindows.remove(window);
}
-void QWasmCompositor::removeWindow(QWasmWindow *window)
+void QWasmCompositor::setEnabled(bool enabled)
{
- m_requestUpdateWindows.remove(window);
- m_windowStack.removeWindow(window);
- if (m_windowStack.topWindow())
- m_windowStack.topWindow()->requestActivateWindow();
-
- updateEnabledState();
+ m_isEnabled = enabled;
}
-void QWasmCompositor::updateEnabledState()
+// requestUpdate delivery is initially disabled at startup, while Qt completes
+// startup tasks such as font loading. This function enables requestUpdate delivery
+// again.
+bool QWasmCompositor::releaseRequestUpdateHold()
{
- m_isEnabled = std::any_of(m_windowStack.begin(), m_windowStack.end(), [](QWasmWindow *window) {
- return !window->context2d().isUndefined();
- });
-}
-
-void QWasmCompositor::raise(QWasmWindow *window)
-{
- m_windowStack.raise(window);
-}
-
-void QWasmCompositor::lower(QWasmWindow *window)
-{
- m_windowStack.lower(window);
-}
-
-QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding) const
-{
- const auto found = std::find_if(
- m_windowStack.begin(), m_windowStack.end(),
- [padding, &targetPointInScreenCoords](const QWasmWindow *window) {
- const QRect geometry = window->windowFrameGeometry().adjusted(-padding, -padding,
- padding, padding);
-
- return window->isVisible() && geometry.contains(targetPointInScreenCoords);
- });
- return found != m_windowStack.end() ? (*found)->window() : nullptr;
-}
-
-QWindow *QWasmCompositor::keyWindow() const
-{
- return m_windowStack.topWindow() ? m_windowStack.topWindow()->window() : nullptr;
-}
-
-void QWasmCompositor::requestUpdateAllWindows()
-{
- m_requestUpdateAllWindows = true;
- requestUpdate();
+ const bool wasEnabled = m_requestUpdateHoldEnabled;
+ m_requestUpdateHoldEnabled = false;
+ return wasEnabled;
}
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
@@ -177,6 +76,9 @@ void QWasmCompositor::requestUpdate()
if (m_requestAnimationFrameId != -1)
return;
+ if (m_requestUpdateHoldEnabled)
+ return;
+
static auto frame = [](double frameTime, void *context) -> int {
Q_UNUSED(frameTime);
@@ -197,40 +99,40 @@ void QWasmCompositor::deliverUpdateRequests()
// update set.
auto requestUpdateWindows = m_requestUpdateWindows;
m_requestUpdateWindows.clear();
- bool requestUpdateAllWindows = m_requestUpdateAllWindows;
- m_requestUpdateAllWindows = false;
// Update window content, either all windows or a spesific set of windows. Use the correct
// update type: QWindow subclasses expect that requested and delivered updateRequests matches
// exactly.
m_inDeliverUpdateRequest = true;
- if (requestUpdateAllWindows) {
- for (QWasmWindow *window : m_windowStack) {
- auto it = requestUpdateWindows.find(window);
- UpdateRequestDeliveryType updateType =
- (it == m_requestUpdateWindows.end() ? ExposeEventDelivery : it.value());
- deliverUpdateRequest(window, updateType);
- }
- } else {
- for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
- auto *window = it.key();
- UpdateRequestDeliveryType updateType = it.value();
- deliverUpdateRequest(window, updateType);
- }
+ for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
+ auto *window = it.key();
+ UpdateRequestDeliveryType updateType = it.value();
+ deliverUpdateRequest(window, updateType);
}
+
m_inDeliverUpdateRequest = false;
- frame(requestUpdateAllWindows, requestUpdateWindows.keys());
+ frame(requestUpdateWindows.keys());
}
void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
- // update by deliverUpdateRequest and expose event accordingly.
+ QWindow *qwindow = window->window();
+
+ // Make sure the DPR value for the window is up to date on expose/repaint.
+ // FIXME: listen to native DPR change events instead, if/when available.
+ QWindowSystemInterface::handleWindowDevicePixelRatioChanged(qwindow);
+
+ // Update by deliverUpdateRequest and expose event according to requested update
+ // type. If the window has not yet been exposed then we must expose it first regardless
+ // of update type. The deliverUpdateRequest must still be sent in this case in order
+ // to maintain correct window update state.
+ QRect updateRect(QPoint(0, 0), qwindow->geometry().size());
if (updateType == UpdateRequestDelivery) {
- window->QPlatformWindow::deliverUpdateRequest();
+ if (qwindow->isExposed() == false)
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
+ window->deliverUpdateRequest();
} else {
- QWindow *qwindow = window->window();
- QWindowSystemInterface::handleExposeEvent(
- qwindow, QRect(QPoint(0, 0), qwindow->geometry().size()));
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
}
}
@@ -239,165 +141,19 @@ void QWasmCompositor::handleBackingStoreFlush(QWindow *window)
// Request update to flush the updated backing store content, unless we are currently
// processing an update, in which case the new content will flushed as a part of that update.
if (!m_inDeliverUpdateRequest)
- requestUpdateWindow(asWasmWindow(window));
+ requestUpdateWindow(static_cast<QWasmWindow *>(window->handle()));
}
-int dpiScaled(qreal value)
+void QWasmCompositor::frame(const QList<QWasmWindow *> &windows)
{
- return value * (qreal(qt_defaultDpiX()) / 96.0);
-}
-
-void QWasmCompositor::frame(bool all, const QList<QWasmWindow *> &windows)
-{
- if (!m_isEnabled || m_windowStack.empty() || !screen())
+ if (!m_isEnabled || !screen())
return;
- if (all) {
- std::for_each(m_windowStack.rbegin(), m_windowStack.rend(),
- [](QWasmWindow *window) { window->paint(); });
- } else {
- std::for_each(windows.begin(), windows.end(), [](QWasmWindow *window) { window->paint(); });
- }
-}
-
-void QWasmCompositor::onTopWindowChanged()
-{
- constexpr int zOrderForElementInFrontOfScreen = 3;
- int z = zOrderForElementInFrontOfScreen;
- std::for_each(m_windowStack.rbegin(), m_windowStack.rend(),
- [&z](QWasmWindow *window) { window->setZOrder(z++); });
-
- auto it = m_windowStack.begin();
- if (it == m_windowStack.end()) {
- return;
- }
- (*it)->onActivationChanged(true);
- ++it;
- for (; it != m_windowStack.end(); ++it) {
- (*it)->onActivationChanged(false);
- }
+ for (QWasmWindow *window : windows)
+ window->paint();
}
QWasmScreen *QWasmCompositor::screen()
{
return static_cast<QWasmScreen *>(parent());
}
-
-int QWasmCompositor::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
-{
- QWasmCompositor *wasmCompositor = reinterpret_cast<QWasmCompositor *>(userData);
- return static_cast<int>(wasmCompositor->processKeyboard(eventType, keyEvent));
-}
-
-int QWasmCompositor::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
-{
- auto compositor = reinterpret_cast<QWasmCompositor*>(userData);
- return static_cast<int>(compositor->processTouch(eventType, touchEvent));
-}
-
-bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *emKeyEvent)
-{
- constexpr bool ProceedToNativeEvent = false;
- Q_ASSERT(eventType == EMSCRIPTEN_EVENT_KEYDOWN || eventType == EMSCRIPTEN_EVENT_KEYUP);
-
- auto translatedEvent = m_eventTranslator->translateKeyEvent(eventType, emKeyEvent);
-
- const QFlags<Qt::KeyboardModifier> modifiers = KeyboardModifier::getForEvent(*emKeyEvent);
-
- const auto clipboardResult = QWasmIntegration::get()->getWasmClipboard()->processKeyboard(
- translatedEvent, modifiers);
-
- using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult;
- if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded)
- return ProceedToNativeEvent;
-
- if (translatedEvent.text.isEmpty())
- translatedEvent.text = QString(emKeyEvent->key);
- if (translatedEvent.text.size() > 1)
- translatedEvent.text.clear();
- const auto result =
- QWindowSystemInterface::handleKeyEvent(
- 0, translatedEvent.type, translatedEvent.key, modifiers, translatedEvent.text);
- return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
- ? ProceedToNativeEvent
- : result;
-}
-
-bool QWasmCompositor::processTouch(int eventType, const EmscriptenTouchEvent *touchEvent)
-{
- QList<QWindowSystemInterface::TouchPoint> touchPointList;
- touchPointList.reserve(touchEvent->numTouches);
- QWindow *targetWindow = nullptr;
-
- for (int i = 0; i < touchEvent->numTouches; i++) {
-
- const EmscriptenTouchPoint *touches = &touchEvent->touches[i];
-
- QPoint targetPointInScreenCoords =
- screen()->mapFromLocal(QPoint(touches->targetX, touches->targetY));
-
- targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5);
- if (targetWindow == nullptr)
- continue;
-
- QWindowSystemInterface::TouchPoint touchPoint;
-
- touchPoint.area = QRect(0, 0, 8, 8);
- touchPoint.id = touches->identifier;
- touchPoint.pressure = 1.0;
-
- touchPoint.area.moveCenter(targetPointInScreenCoords);
-
- const auto tp = m_pressedTouchIds.constFind(touchPoint.id);
- if (tp != m_pressedTouchIds.constEnd())
- touchPoint.normalPosition = tp.value();
-
- QPointF pointInTargetWindowCoords = QPointF(targetWindow->mapFromGlobal(targetPointInScreenCoords));
- QPointF normalPosition(pointInTargetWindowCoords.x() / targetWindow->width(),
- pointInTargetWindowCoords.y() / targetWindow->height());
-
- const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
- touchPoint.normalPosition = normalPosition;
-
- switch (eventType) {
- case EMSCRIPTEN_EVENT_TOUCHSTART:
- if (tp != m_pressedTouchIds.constEnd()) {
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
- } else {
- touchPoint.state = QEventPoint::State::Pressed;
- }
- m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
-
- break;
- case EMSCRIPTEN_EVENT_TOUCHEND:
- touchPoint.state = QEventPoint::State::Released;
- m_pressedTouchIds.remove(touchPoint.id);
- break;
- case EMSCRIPTEN_EVENT_TOUCHMOVE:
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
-
- m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
- break;
- default:
- break;
- }
-
- touchPointList.append(touchPoint);
- }
-
- QFlags<Qt::KeyboardModifier> keyModifier = KeyboardModifier::getForEvent(*touchEvent);
-
- bool accepted = false;
-
- if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)
- accepted = QWindowSystemInterface::handleTouchCancelEvent(targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), keyModifier);
- else
- accepted = QWindowSystemInterface::handleTouchEvent(
- targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), touchPointList, keyModifier);
-
- return static_cast<int>(accepted);
-}
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h
index 211579fb5a..4953d65233 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.h
+++ b/src/plugins/platforms/wasm/qwasmcompositor.h
@@ -7,26 +7,15 @@
#include "qwasmwindowstack.h"
#include <qpa/qplatformwindow.h>
-#include <QMap>
-
-#include <QtGui/qinputdevice.h>
-#include <QtCore/private/qstdweb_p.h>
-
-#include <QPointer>
-#include <QPointingDevice>
-#include <emscripten/html5.h>
-#include <emscripten/emscripten.h>
-#include <emscripten/bind.h>
+#include <QMap>
QT_BEGIN_NAMESPACE
-struct PointerEvent;
class QWasmWindow;
class QWasmScreen;
-class QOpenGLContext;
-class QOpenGLTexture;
-class QWasmEventTranslator;
+
+enum class QWasmWindowTreeNodeChangeType;
class QWasmCompositor final : public QObject
{
@@ -35,76 +24,35 @@ public:
QWasmCompositor(QWasmScreen *screen);
~QWasmCompositor() final;
- void initEventHandlers();
-
- struct QWasmFrameOptions {
- QRect rect;
- int lineWidth;
- QPalette palette;
- };
-
- void addWindow(QWasmWindow *window);
- void removeWindow(QWasmWindow *window);
-
void setVisible(QWasmWindow *window, bool visible);
- void raise(QWasmWindow *window);
- void lower(QWasmWindow *window);
void onScreenDeleting();
- QWindow *windowAt(QPoint globalPoint, int padding = 0) const;
- QWindow *keyWindow() const;
-
QWasmScreen *screen();
+ void setEnabled(bool enabled);
+ static bool releaseRequestUpdateHold();
+
+ void requestUpdate();
enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
- void requestUpdateAllWindows();
void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
- void setCapture(QWasmWindow *window);
- void releaseCapture();
-
void handleBackingStoreFlush(QWindow *window);
+ void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window);
private:
- void frame(bool all, const QList<QWasmWindow *> &windows);
-
- void onTopWindowChanged();
+ void frame(const QList<QWasmWindow *> &windows);
void deregisterEventHandlers();
- void destroy();
- void requestUpdate();
void deliverUpdateRequests();
void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
- static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
-
- static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData);
-
- bool processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent);
- bool processTouch(int eventType, const EmscriptenTouchEvent *touchEvent);
-
- void enterWindow(QWindow *window, const QPoint &localPoint, const QPoint &globalPoint);
- void leaveWindow(QWindow *window);
-
- void updateEnabledState();
-
- QWasmWindowStack m_windowStack;
-
bool m_isEnabled = true;
- QSize m_targetSize;
- qreal m_targetDevicePixelRatio = 1;
QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
- bool m_requestUpdateAllWindows = false;
int m_requestAnimationFrameId = -1;
bool m_inDeliverUpdateRequest = false;
-
- std::unique_ptr<QPointingDevice> m_touchDevice;
-
- QMap <int, QPointF> m_pressedTouchIds;
-
- std::unique_ptr<QWasmEventTranslator> m_eventTranslator;
+ static bool m_requestUpdateHoldEnabled;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
index efbe4ddcc4..e0e1a99f48 100644
--- a/src/plugins/platforms/wasm/qwasmcssstyle.cpp
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
@@ -24,26 +24,47 @@ const char *Style = R"css(
width: 100%;
height: 100%;
overflow: hidden;
- outline: none;
+}
+
+.qt-screen div {
+ touch-action: none;
}
.qt-window {
- box-shadow: rgb(0 0 0 / 20%) 0px 10px 16px 0px, rgb(0 0 0 / 19%) 0px 6px 20px 0px;
position: absolute;
background-color: lightgray;
}
-.qt-window.has-title-bar {
+.qt-window-contents {
+ overflow: hidden;
+ position: relative;
+}
+
+.qt-window.transparent-for-input {
+ pointer-events: none;
+}
+
+.qt-window.has-shadow {
+ box-shadow: rgb(0 0 0 / 20%) 0px 10px 16px 0px, rgb(0 0 0 / 19%) 0px 6px 20px 0px;
+}
+
+.qt-window.has-border {
border: var(--border-width) solid lightgray;
caret-color: transparent;
}
+.qt-window.frameless {
+ background-color: transparent;
+}
+
.resize-outline {
position: absolute;
display: none;
}
-.qt-window.has-title-bar:not(.maximized) .resize-outline {
+.qt-window.no-resize > .resize-outline { display: none; }
+
+.qt-window.has-border:not(.maximized):not(.no-resize) .resize-outline {
display: block;
}
@@ -119,17 +140,23 @@ const char *Style = R"css(
padding-bottom: 4px;
}
-.qt-window.has-title-bar .title-bar {
+.qt-window.has-border > .title-bar {
display: flex;
}
.title-bar .window-name {
+ display: none;
font-family: 'Lucida Grande';
white-space: nowrap;
user-select: none;
overflow: hidden;
}
+
+.qt-window.has-title .title-bar .window-name {
+ display: block;
+}
+
.title-bar .spacer {
flex-grow: 1
}
@@ -140,6 +167,7 @@ const char *Style = R"css(
.qt-window-canvas-container {
display: flex;
+ pointer-events: none;
}
.title-bar div {
@@ -169,21 +197,6 @@ const char *Style = R"css(
background-size: 10px 10px;
}
-.title-bar .image-button img[qt-builtin-image-type=x] {
- background-image: url("data:image/svg+xml;base64,$close_icon");
-}
-
-.title-bar .image-button img[qt-builtin-image-type=qt-logo] {
- background-image: url("qtlogo.svg");
-}
-
-.title-bar .image-button img[qt-builtin-image-type=restore] {
- background-image: url("data:image/svg+xml;base64,$restore_icon");
-}
-
-.title-bar .image-button img[qt-builtin-image-type=maximize] {
- background-image: url("data:image/svg+xml;base64,$maximize_icon");
-}
.title-bar .action-button {
pointer-events: all;
}
@@ -204,26 +217,31 @@ const char *Style = R"css(
filter: invert(0.6);
}
+/* This will clip the content within 50% frame in 1x1 pixel area, preventing it
+ from being rendered on the page, but it should still be read by modern
+ screen readers */
+.hidden-visually-read-by-screen-reader {
+ visibility: visible;
+ clip: rect(1px, 1px, 1px, 1px);
+ clip-path: inset(50%);
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+}
+
)css";
-void replace(std::string &str, const std::string &from, const std::string_view &to)
-{
- str.replace(str.find(from), from.length(), to);
-}
} // namespace
emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent)
{
auto document = parent["ownerDocument"];
auto screenStyle = document.call<emscripten::val>("createElement", emscripten::val("style"));
- auto text = std::string(Style);
-
- using IconType = Base64IconStore::IconType;
- replace(text, "$close_icon", Base64IconStore::get()->getIcon(IconType::X));
- replace(text, "$restore_icon", Base64IconStore::get()->getIcon(IconType::Restore));
- replace(text, "$maximize_icon", Base64IconStore::get()->getIcon(IconType::Maximize));
- screenStyle.set("textContent", text);
+ screenStyle.set("textContent", std::string(Style));
return screenStyle;
}
diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp
index 1ffa00780d..c258befa77 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcursor.cpp
@@ -5,7 +5,9 @@
#include "qwasmscreen.h"
#include "qwasmwindow.h"
+#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
#include <QtGui/qwindow.h>
#include <emscripten/emscripten.h>
@@ -15,10 +17,9 @@ QT_BEGIN_NAMESPACE
using namespace emscripten;
namespace {
-QByteArray cursorShapeToCss(Qt::CursorShape shape)
+QByteArray cursorToCss(const QCursor *cursor)
{
- QByteArray cursorName;
-
+ auto shape = cursor->shape();
switch (shape) {
case Qt::ArrowCursor:
return "default";
@@ -64,8 +65,24 @@ QByteArray cursorShapeToCss(Qt::CursorShape shape)
return "default";
case Qt::DragLinkCursor:
return "alias";
+ case Qt::BitmapCursor: {
+ auto pixmap = cursor->pixmap();
+ QByteArray cursorAsPng;
+ QBuffer buffer(&cursorAsPng);
+ buffer.open(QBuffer::WriteOnly);
+ pixmap.save(&buffer, "PNG");
+ buffer.close();
+ auto cursorAsBase64 = cursorAsPng.toBase64();
+ auto hotSpot = cursor->hotSpot();
+ auto encodedCursor =
+ QString("url(data:image/png;base64,%1) %2 %3, auto")
+ .arg(QString::fromUtf8(cursorAsBase64),
+ QString::number(hotSpot.x()),
+ QString::number(hotSpot.y()));
+ return encodedCursor.toUtf8();
+ }
default:
- static_assert(Qt::BitmapCursor == 24 && Qt::CustomCursor == 25,
+ static_assert(Qt::CustomCursor == 25,
"New cursor type added, handle it");
qWarning() << "QWasmCursor: " << shape << " unsupported";
return "default";
@@ -75,11 +92,10 @@ QByteArray cursorShapeToCss(Qt::CursorShape shape)
void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
{
- if (!window || !window->handle())
+ if (!window)
return;
-
- static_cast<QWasmWindow *>(window->handle())
- ->setWindowCursor(cursorShapeToCss(windowCursor->shape()));
+ if (QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle()))
+ wasmWindow->setWindowCursor(windowCursor ? cursorToCss(windowCursor) : "default");
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp
index f9705f1a1c..6b2b3d0933 100644
--- a/src/plugins/platforms/wasm/qwasmdom.cpp
+++ b/src/plugins/platforms/wasm/qwasmdom.cpp
@@ -3,17 +3,239 @@
#include "qwasmdom.h"
-#include <QMimeData>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
#include <QtCore/qpoint.h>
#include <QtCore/qrect.h>
#include <QtGui/qimage.h>
#include <private/qstdweb_p.h>
+#include <QtCore/qurl.h>
#include <utility>
+#include <emscripten/wire.h>
QT_BEGIN_NAMESPACE
namespace dom {
+namespace {
+std::string dropActionToDropEffect(Qt::DropAction action)
+{
+ switch (action) {
+ case Qt::DropAction::CopyAction:
+ return "copy";
+ case Qt::DropAction::IgnoreAction:
+ return "none";
+ case Qt::DropAction::LinkAction:
+ return "link";
+ case Qt::DropAction::MoveAction:
+ case Qt::DropAction::TargetMoveAction:
+ return "move";
+ case Qt::DropAction::ActionMask:
+ Q_ASSERT(false);
+ return "";
+ }
+}
+} // namespace
+
+DataTransfer::DataTransfer(emscripten::val webDataTransfer)
+ : webDataTransfer(webDataTransfer) {
+}
+
+DataTransfer::~DataTransfer() = default;
+
+DataTransfer::DataTransfer(const DataTransfer &other) = default;
+
+DataTransfer::DataTransfer(DataTransfer &&other) = default;
+
+DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
+
+DataTransfer &DataTransfer::operator=(DataTransfer &&other) = default;
+
+void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
+{
+ webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
+ emscripten::val(hotspot.y()));
+}
+
+void DataTransfer::setData(std::string format, std::string data)
+{
+ webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
+ emscripten::val(std::move(data)));
+}
+
+void DataTransfer::setDropAction(Qt::DropAction action)
+{
+ webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
+}
+
+void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
+{
+ for (const auto &format : mimeData.formats()) {
+ auto data = mimeData.data(format);
+
+ auto encoded = format.startsWith("text/")
+ ? QString::fromLocal8Bit(data).toStdString()
+ : "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
+
+ setData(format.toStdString(), std::move(encoded));
+ }
+}
+
+// Converts a DataTransfer instance to a QMimeData instance. Invokes the
+// given callback when the conversion is complete. The callback takes ownership
+// of the QMimeData.
+void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
+{
+ enum class ItemKind {
+ File,
+ String,
+ };
+
+ class MimeContext {
+
+ public:
+ MimeContext(int itemCount, std::function<void(QMimeData *)> callback)
+ :m_remainingItemCount(itemCount), m_callback(callback)
+ {
+
+ }
+
+ void deref() {
+ if (--m_remainingItemCount > 0)
+ return;
+
+ mimeData->setUrls(fileUrls);
+
+ m_callback(mimeData);
+
+ // Delete files; we expect that the user callback reads/copies
+ // file content before returning.
+ // Fixme: tie file lifetime to lifetime of the QMimeData?
+ for (QUrl fileUrl: fileUrls)
+ QFile(fileUrl.toLocalFile()).remove();
+
+ delete this;
+ }
+
+ QMimeData *mimeData = new QMimeData();
+ QList<QUrl> fileUrls;
+
+ private:
+ int m_remainingItemCount;
+ std::function<void(QMimeData *)> m_callback;
+ };
+
+ const auto items = webDataTransfer["items"];
+ const int itemCount = items["length"].as<int>();
+ const int fileCount = webDataTransfer["files"]["length"].as<int>();
+ MimeContext *mimeContext = new MimeContext(itemCount, callback);
+
+ for (int i = 0; i < itemCount; ++i) {
+ const auto item = items[i];
+ const auto itemKind =
+ item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+
+ switch (itemKind) {
+ case ItemKind::File: {
+ qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
+
+ if (webfile.size() > 1e+9) { // limit file size to 1 GB
+ qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
+ mimeContext->deref();
+ continue;
+ }
+
+ QString mimeFormat = QString::fromStdString(webfile.type());
+ QString fileName = QString::fromStdString(webfile.name());
+
+ // there's a file, now read it
+ QByteArray fileContent(webfile.size(), Qt::Uninitialized);
+ webfile.stream(fileContent.data(), [=]() {
+
+ // If we get a single file, and that file is an image, then
+ // try to decode the image data. This handles the case where
+ // image data (i.e. not an image file) is pasted. The browsers
+ // will then create a fake "image.png" file which has the image
+ // data. As a side effect Qt will also decode the image for
+ // single-image-file drops, since there is no way to differentiate
+ // the fake "image.png" from a real one.
+ if (fileCount == 1 && mimeFormat.contains("image/")) {
+ QImage image;
+ if (image.loadFromData(fileContent))
+ mimeContext->mimeData->setImageData(image);
+ }
+
+ QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
+ qtTmpDir.mkpath(qtTmpDir.path());
+
+ QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
+ mimeContext->fileUrls.append(fileUrl);
+
+ QFile file(fileUrl.toLocalFile());
+ if (!file.open(QFile::WriteOnly)) {
+ qWarning() << "File was not opened";
+ mimeContext->deref();
+ return;
+ }
+ if (file.write(fileContent) < 0) {
+ qWarning() << "Write failed";
+ file.close();
+ }
+ mimeContext->deref();
+ });
+ break;
+ }
+ case ItemKind::String:
+ if (itemMimeType.contains("STRING", Qt::CaseSensitive)
+ || itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
+ mimeContext->deref();
+ break;
+ }
+ QString a;
+ QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
+ "getData", emscripten::val(itemMimeType.toStdString())));
+
+ if (!data.isEmpty()) {
+ if (itemMimeType == "text/html")
+ mimeContext->mimeData->setHtml(data);
+ else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
+ mimeContext->mimeData->setText(data); // the type can be empty
+ else {
+ // TODO improve encoding
+ if (data.startsWith("QB64")) {
+ data.remove(0, 4);
+ mimeContext->mimeData->setData(itemMimeType,
+ QByteArray::fromBase64(QByteArray::fromStdString(
+ data.toStdString())));
+ } else {
+ mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());
+ }
+ }
+ }
+ mimeContext->deref();
+ break;
+ }
+ } // for items
+}
+
+QMimeData *DataTransfer::toMimeDataPreview()
+{
+ auto data = new QMimeData();
+
+ QList<QUrl> uriList;
+ for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
+ const auto item = webDataTransfer["items"][i];
+ if (item["kind"].as<std::string>() == "file") {
+ uriList.append(QUrl("blob://placeholder"));
+ } else {
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+ data->setData(itemMimeType, QByteArray());
+ }
+ }
+ data->setUrls(uriList);
+ return data;
+}
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
{
@@ -25,15 +247,55 @@ void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool fl
element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
}
-QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point)
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point)
{
- auto sourceBoundingRect =
+ const auto sourceBoundingRect =
QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
- auto targetBoundingRect =
+ const auto targetBoundingRect =
QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
- auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
- return (point + offset).toPoint();
+ const auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
+ return point + offset;
+}
+
+void drawImageToWebImageDataArray(const QImage &sourceImage, emscripten::val destinationImageData,
+ const QRect &sourceRect)
+{
+ Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() == "ImageData",
+ Q_FUNC_INFO, "The destination should be an ImageData instance");
+
+ constexpr int BytesPerColor = 4;
+ if (sourceRect.width() == sourceImage.width()) {
+ // Copy a contiguous chunk of memory
+ // ...............
+ // OOOOOOOOOOOOOOO
+ // OOOOOOOOOOOOOOO -> image data
+ // OOOOOOOOOOOOOOO
+ // ...............
+ auto imageMemory = emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()
+ * BytesPerColor,
+ sourceImage.constScanLine(sourceRect.y()));
+ destinationImageData["data"].call<void>(
+ "set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);
+ } else {
+ // Go through the scanlines manually to set the individual lines in bulk. This is
+ // marginally less performant than the above.
+ // ...............
+ // ...OOOOOOOOO... r = 0 -> image data
+ // ...OOOOOOOOO... r = 1 -> image data
+ // ...OOOOOOOOO... r = 2 -> image data
+ // ...............
+ for (int row = 0; row < sourceRect.height(); ++row) {
+ auto scanlineMemory =
+ emscripten::typed_memory_view(sourceRect.width() * BytesPerColor,
+ sourceImage.constScanLine(row + sourceRect.y())
+ + BytesPerColor * sourceRect.x());
+ destinationImageData["data"].call<void>("set", scanlineMemory,
+ (sourceRect.y() + row) * sourceImage.width()
+ * BytesPerColor
+ + sourceRect.x() * BytesPerColor);
+ }
+ }
}
} // namespace dom
diff --git a/src/plugins/platforms/wasm/qwasmdom.h b/src/plugins/platforms/wasm/qwasmdom.h
index 80661fce19..0a520815a3 100644
--- a/src/plugins/platforms/wasm/qwasmdom.h
+++ b/src/plugins/platforms/wasm/qwasmdom.h
@@ -5,6 +5,9 @@
#define QWASMDOM_H
#include <QtCore/qtconfigmacros.h>
+#include <QtCore/QPointF>
+#include <private/qstdweb_p.h>
+#include <QtCore/qnamespace.h>
#include <emscripten/val.h>
@@ -12,11 +15,37 @@
#include <memory>
#include <string>
+#include <QMimeData>
QT_BEGIN_NAMESPACE
+namespace qstdweb {
+ struct CancellationFlag;
+}
+
+
class QPoint;
+class QRect;
namespace dom {
+struct DataTransfer
+{
+ explicit DataTransfer(emscripten::val webDataTransfer);
+ ~DataTransfer();
+ DataTransfer(const DataTransfer &other);
+ DataTransfer(DataTransfer &&other);
+ DataTransfer &operator=(const DataTransfer &other);
+ DataTransfer &operator=(DataTransfer &&other);
+
+ void toMimeDataWithFile(std::function<void(QMimeData *)> callback);
+ QMimeData *toMimeDataPreview();
+ void setDragImage(emscripten::val element, const QPoint &hotspot);
+ void setData(std::string format, std::string data);
+ void setDropAction(Qt::DropAction dropAction);
+ void setDataFromMimeData(const QMimeData &mimeData);
+
+ emscripten::val webDataTransfer;
+};
+
inline emscripten::val document()
{
return emscripten::val::global("document");
@@ -24,7 +53,10 @@ inline emscripten::val document()
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag);
-QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point);
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point);
+
+void drawImageToWebImageDataArray(const QImage &source, emscripten::val destinationImageData,
+ const QRect &sourceRect);
} // namespace dom
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp
new file mode 100644
index 0000000000..d07a46618f
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdrag.cpp
@@ -0,0 +1,291 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmdrag.h"
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmintegration.h"
+
+#include <qpa/qwindowsysteminterface.h>
+
+#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/qeventloop.h>
+#include <QtCore/qmimedata.h>
+#include <QtCore/qtimer.h>
+#include <QFile>
+
+#include <functional>
+#include <string>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+QWindow *windowForDrag(QDrag *drag)
+{
+ QWindow *window = qobject_cast<QWindow *>(drag->source());
+ if (window)
+ return window;
+ if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1)
+ return nullptr;
+
+ QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
+ Q_RETURN_ARG(QWindow *, window));
+ return window;
+}
+
+} // namespace
+
+struct QWasmDrag::DragState
+{
+ class DragImage
+ {
+ public:
+ DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window);
+ ~DragImage();
+
+ emscripten::val htmlElement();
+
+ private:
+ emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData);
+ emscripten::val generateDragImageFromText(const QMimeData *mimeData);
+ emscripten::val generateDefaultDragImage();
+ emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap);
+
+ emscripten::val m_imageDomElement;
+ emscripten::val m_temporaryImageElementParent;
+ };
+
+ DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure);
+ ~DragState();
+ DragState(const QWasmDrag &other) = delete;
+ DragState(QWasmDrag &&other) = delete;
+ DragState &operator=(const QWasmDrag &other) = delete;
+ DragState &operator=(QWasmDrag &&other) = delete;
+
+ QDrag *drag;
+ QWindow *window;
+ std::function<void()> quitEventLoopClosure;
+ std::unique_ptr<DragImage> dragImage;
+ Qt::DropAction dropAction = Qt::DropAction::IgnoreAction;
+};
+
+QWasmDrag::QWasmDrag() = default;
+
+QWasmDrag::~QWasmDrag() = default;
+
+QWasmDrag *QWasmDrag::instance()
+{
+ return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
+}
+
+Qt::DropAction QWasmDrag::drag(QDrag *drag)
+{
+ Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress");
+
+ QWindow *window = windowForDrag(drag);
+ if (!window)
+ return Qt::IgnoreAction;
+
+ Qt::DropAction dragResult = Qt::IgnoreAction;
+ if (qstdweb::haveJspi()) {
+ QEventLoop loop;
+ m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); });
+ loop.exec();
+ dragResult = m_dragState->dropAction;
+ m_dragState.reset();
+ }
+
+ if (dragResult == Qt::IgnoreAction)
+ dragResult = QBasicDrag::drag(drag);
+
+ return dragResult;
+}
+
+void QWasmDrag::onNativeDragStarted(DragEvent *event)
+{
+ Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
+ "The event is not a DragStart event");
+ // It is possible for a drag start event to arrive from another window.
+ if (!m_dragState || m_dragState->window != event->targetWindow) {
+ event->cancelDragStart();
+ return;
+ }
+
+ m_dragState->dragImage = std::make_unique<DragState::DragImage>(
+ m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow);
+ event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
+ m_dragState->drag->hotSpot());
+ event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData());
+}
+
+void QWasmDrag::onNativeDragOver(DragEvent *event)
+{
+ auto mimeDataPreview = event->dataTransfer.toMimeDataPreview();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+
+ const auto dragResponse = QWindowSystemInterface::handleDrag(
+ event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions,
+ event->mouseButton, event->modifiers);
+ event->acceptDragOver();
+ if (dragResponse.isAccepted()) {
+ event->dataTransfer.setDropAction(dragResponse.acceptedAction());
+ } else {
+ event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
+ }
+}
+
+void QWasmDrag::onNativeDrop(DragEvent *event)
+{
+ QWasmWindow *wasmWindow = QWasmWindow::fromWindow(event->targetWindow);
+
+ const auto screenElementPos = dom::mapPoint(
+ event->target(), wasmWindow->platformScreen()->element(), event->localPoint);
+ const auto screenPos =
+ wasmWindow->platformScreen()->mapFromLocal(screenElementPos);
+ const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+ Qt::MouseButton mouseButton = event->mouseButton;
+ QFlags<Qt::KeyboardModifier> modifiers = event->modifiers;
+
+ // Accept the native drop event: We are going to async read any dropped
+ // files, but the browser expects that accepted state is set before any
+ // async calls.
+ event->acceptDrop();
+
+ const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos,
+ actions, mouseButton, modifiers](QMimeData *mimeData) {
+
+ auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
+ *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
+ targetWindowPos, actions,
+ mouseButton, modifiers);
+
+ if (dropResponse->isAccepted())
+ m_dragState->dropAction = dropResponse->acceptedAction();
+
+ delete mimeData;
+ };
+
+ event->dataTransfer.toMimeDataWithFile(dropCallback);
+}
+
+void QWasmDrag::onNativeDragFinished(DragEvent *event)
+{
+ m_dragState->dropAction = event->dropAction;
+ m_dragState->quitEventLoopClosure();
+}
+
+QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData,
+ QWindow *window)
+ : m_temporaryImageElementParent(QWasmWindow::fromWindow(window)->containerElement())
+{
+ m_imageDomElement = generateDragImage(pixmap, mimeData);
+
+ m_imageDomElement.set("className", "hidden-drag-image");
+ m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement);
+}
+
+QWasmDrag::DragState::DragImage::~DragImage()
+{
+ m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement);
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap,
+ const QMimeData *mimeData)
+{
+ if (!pixmap.isNull())
+ return generateDragImageFromPixmap(pixmap);
+ if (mimeData->hasFormat("text/plain"))
+ return generateDragImageFromText(mimeData);
+ return generateDefaultDragImage();
+}
+
+emscripten::val
+QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData)
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("span"));
+
+ constexpr qsizetype MaxCharactersInDragImage = 100;
+
+ const auto text = QString::fromUtf8(mimeData->data("text/plain"));
+ dragImageElement.set(
+ "innerText",
+ text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString());
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage()
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("div"));
+
+ auto innerImgElement = emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("img"));
+ innerImgElement.set("src",
+ "data:image/" + std::string("svg+xml") + ";base64,"
+ + std::string(Base64IconStore::get()->getIcon(
+ Base64IconStore::IconType::QtLogo)));
+
+ constexpr char DragImageSize[] = "50px";
+
+ dragImageElement["style"].set("width", DragImageSize);
+ innerImgElement["style"].set("width", DragImageSize);
+ dragImageElement["style"].set("display", "flex");
+
+ dragImageElement.call<void>("appendChild", innerImgElement);
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap)
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("canvas"));
+ dragImageElement.set("width", pixmap.width());
+ dragImageElement.set("height", pixmap.height());
+
+ dragImageElement["style"].set(
+ "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px");
+ dragImageElement["style"].set(
+ "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px");
+
+ auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d"));
+ auto imageData = context2d.call<emscripten::val>(
+ "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height()));
+
+ dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888),
+ imageData, QRect(0, 0, pixmap.width(), pixmap.height()));
+ context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0));
+
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::htmlElement()
+{
+ return m_imageDomElement;
+}
+
+QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window,
+ std::function<void()> quitEventLoopClosure)
+ : drag(drag), window(window), quitEventLoopClosure(std::move(quitEventLoopClosure))
+{
+}
+
+QWasmDrag::DragState::~DragState() = default;
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h
new file mode 100644
index 0000000000..146a69ebe8
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdrag.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWINDOWSDRAG_H
+#define QWINDOWSDRAG_H
+
+#include <private/qstdweb_p.h>
+#include <private/qsimpledrag_p.h>
+
+#include <qpa/qplatformdrag.h>
+#include <QtGui/qdrag.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+struct DragEvent;
+
+class QWasmDrag final : public QSimpleDrag
+{
+public:
+ QWasmDrag();
+ ~QWasmDrag() override;
+ QWasmDrag(const QWasmDrag &other) = delete;
+ QWasmDrag(QWasmDrag &&other) = delete;
+ QWasmDrag &operator=(const QWasmDrag &other) = delete;
+ QWasmDrag &operator=(QWasmDrag &&other) = delete;
+
+ static QWasmDrag *instance();
+
+ void onNativeDragOver(DragEvent *event);
+ void onNativeDrop(DragEvent *event);
+ void onNativeDragStarted(DragEvent *event);
+ void onNativeDragFinished(DragEvent *event);
+
+ // QPlatformDrag:
+ Qt::DropAction drag(QDrag *drag) final;
+
+private:
+ struct DragState;
+
+ std::unique_ptr<DragState> m_dragState;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSDRAG_H
diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp
index eb2c8c145a..c1d6ce3a2a 100644
--- a/src/plugins/platforms/wasm/qwasmevent.cpp
+++ b/src/plugins/platforms/wasm/qwasmevent.cpp
@@ -3,8 +3,78 @@
#include "qwasmevent.h"
+#include "qwasmkeytranslator.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/private/qstringiterator_p.h>
+#include <QtCore/qregularexpression.h>
+
QT_BEGIN_NAMESPACE
+namespace {
+constexpr std::string_view WebDeadKeyValue = "Dead";
+
+bool isDeadKeyEvent(const char *key)
+{
+ return qstrncmp(key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
+}
+
+Qt::Key getKeyFromCode(const std::string &code)
+{
+ if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(code.c_str()))
+ return *mapping;
+
+ static QRegularExpression regex(QString(QStringLiteral(R"re((?:Key|Digit)(\w))re")));
+ const auto codeQString = QString::fromStdString(code);
+ const auto match = regex.match(codeQString);
+
+ if (!match.hasMatch())
+ return Qt::Key_unknown;
+
+ constexpr size_t CharacterIndex = 1;
+ return static_cast<Qt::Key>(match.capturedView(CharacterIndex).at(0).toLatin1());
+}
+
+Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey,
+ QFlags<Qt::KeyboardModifier> modifiers)
+{
+ if (isDeadKey) {
+ auto mapped = getKeyFromCode(code);
+ switch (mapped) {
+ case Qt::Key_U:
+ return Qt::Key_Dead_Diaeresis;
+ case Qt::Key_E:
+ return Qt::Key_Dead_Acute;
+ case Qt::Key_I:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_N:
+ return Qt::Key_Dead_Tilde;
+ case Qt::Key_QuoteLeft:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Tilde : Qt::Key_Dead_Grave;
+ case Qt::Key_6:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_Apostrophe:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Diaeresis
+ : Qt::Key_Dead_Acute;
+ case Qt::Key_AsciiTilde:
+ return Qt::Key_Dead_Tilde;
+ default:
+ return Qt::Key_unknown;
+ }
+ } else if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(key.c_str())) {
+ return *mapping;
+ }
+
+ // cast to unicode key
+ QString str = QString::fromUtf8(key.c_str()).toUpper();
+ if (str.length() > 1)
+ return Qt::Key_unknown;
+
+ QStringIterator i(str);
+ return static_cast<Qt::Key>(i.next(0));
+}
+} // namespace
+
namespace KeyboardModifier
{
template <>
@@ -16,7 +86,10 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
}
} // namespace KeyboardModifier
-Event::Event(EventType type, emscripten::val target) : type(type), target(target) { }
+Event::Event(EventType type, emscripten::val webEvent)
+ : webEvent(webEvent), type(type)
+{
+}
Event::~Event() = default;
@@ -28,7 +101,55 @@ Event &Event::operator=(const Event &other) = default;
Event &Event::operator=(Event &&other) = default;
-MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event["target"])
+KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event)
+{
+ const auto code = event["code"].as<std::string>();
+ const auto webKey = event["key"].as<std::string>();
+ deadKey = isDeadKeyEvent(webKey.c_str());
+
+ modifiers = KeyboardModifier::getForEvent(event);
+ key = webKeyToQtKey(code, webKey, deadKey, modifiers);
+
+ text = QString::fromUtf8(webKey);
+ if (text.size() > 1)
+ text.clear();
+
+ if (key == Qt::Key_Tab)
+ text = "\t";
+}
+
+KeyEvent::~KeyEvent() = default;
+
+KeyEvent::KeyEvent(const KeyEvent &other) = default;
+
+KeyEvent::KeyEvent(KeyEvent &&other) = default;
+
+KeyEvent &KeyEvent::operator=(const KeyEvent &other) = default;
+
+KeyEvent &KeyEvent::operator=(KeyEvent &&other) = default;
+
+std::optional<KeyEvent> KeyEvent::fromWebWithDeadKeyTranslation(emscripten::val event,
+ QWasmDeadKeySupport *deadKeySupport)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "keydown")
+ return EventType::KeyDown;
+ else if (eventTypeString == "keyup")
+ return EventType::KeyUp;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+
+ auto result = KeyEvent(*eventType, event);
+ deadKeySupport->applyDeadKeyTranslations(&result);
+
+ return result;
+}
+
+MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event)
{
mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
@@ -38,9 +159,9 @@ MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, even
// it up here.
if (type == EventType::PointerDown)
mouseButtons |= mouseButton;
- localPoint = QPoint(event["offsetX"].as<int>(), event["offsetY"].as<int>());
- pointInPage = QPoint(event["pageX"].as<int>(), event["pageY"].as<int>());
- pointInViewport = QPoint(event["clientX"].as<int>(), event["clientY"].as<int>());
+ localPoint = QPointF(event["offsetX"].as<qreal>(), event["offsetY"].as<qreal>());
+ pointInPage = QPointF(event["pageX"].as<qreal>(), event["pageY"].as<qreal>());
+ pointInViewport = QPointF(event["clientX"].as<qreal>(), event["clientY"].as<qreal>());
modifiers = KeyboardModifier::getForEvent(event);
}
@@ -57,8 +178,23 @@ MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default;
PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
{
pointerId = event["pointerId"].as<int>();
- pointerType = event["pointerType"].as<std::string>() == "mouse" ? PointerType::Mouse
- : PointerType::Other;
+ pointerType = ([type = event["pointerType"].as<std::string>()]() {
+ if (type == "mouse")
+ return PointerType::Mouse;
+ if (type == "touch")
+ return PointerType::Touch;
+ if (type == "pen")
+ return PointerType::Pen;
+ return PointerType::Other;
+ })();
+ width = event["width"].as<qreal>();
+ height = event["height"].as<qreal>();
+ pressure = event["pressure"].as<qreal>();
+ tiltX = event["tiltX"].as<qreal>();
+ tiltY = event["tiltY"].as<qreal>();
+ tangentialPressure = event["tangentialPressure"].as<qreal>();
+ twist = event["twist"].as<qreal>();
+ isPrimary = event["isPrimary"].as<bool>();
}
PointerEvent::~PointerEvent() = default;
@@ -94,8 +230,8 @@ std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
return PointerEvent(*eventType, event);
}
-DragEvent::DragEvent(EventType type, emscripten::val event)
- : MouseEvent(type, event), dataTransfer(event["dataTransfer"])
+DragEvent::DragEvent(EventType type, emscripten::val event, QWindow *window)
+ : MouseEvent(type, event), dataTransfer(event["dataTransfer"]), targetWindow(window)
{
dropAction = ([event]() {
const std::string effect = event["dataTransfer"]["dropEffect"].as<std::string>();
@@ -120,18 +256,42 @@ DragEvent &DragEvent::operator=(const DragEvent &other) = default;
DragEvent &DragEvent::operator=(DragEvent &&other) = default;
-std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event)
+std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event, QWindow *targetWindow)
{
const auto eventType = ([&event]() -> std::optional<EventType> {
const auto eventTypeString = event["type"].as<std::string>();
+ if (eventTypeString == "dragend")
+ return EventType::DragEnd;
+ if (eventTypeString == "dragover")
+ return EventType::DragOver;
+ if (eventTypeString == "dragstart")
+ return EventType::DragStart;
if (eventTypeString == "drop")
return EventType::Drop;
return std::nullopt;
})();
if (!eventType)
return std::nullopt;
- return DragEvent(*eventType, event);
+ return DragEvent(*eventType, event, targetWindow);
+}
+
+void DragEvent::cancelDragStart()
+{
+ Q_ASSERT_X(type == EventType::DragStart, Q_FUNC_INFO, "Only supported for DragStart");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDragOver()
+{
+ Q_ASSERT_X(type == EventType::DragOver, Q_FUNC_INFO, "Only supported for DragOver");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDrop()
+{
+ Q_ASSERT_X(type == EventType::Drop, Q_FUNC_INFO, "Only supported for Drop");
+ webEvent.call<void>("preventDefault");
}
WheelEvent::WheelEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
@@ -146,7 +306,7 @@ WheelEvent::WheelEvent(EventType type, emscripten::val event) : MouseEvent(type,
return DeltaMode::Page;
})();
- delta = QPoint(event["deltaX"].as<int>(), event["deltaY"].as<int>());
+ delta = QPointF(event["deltaX"].as<qreal>(), event["deltaY"].as<qreal>());
webkitDirectionInvertedFromDevice = event["webkitDirectionInvertedFromDevice"].as<bool>();
}
diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h
index e8aea9072e..6ada5393e3 100644
--- a/src/plugins/platforms/wasm/qwasmevent.h
+++ b/src/plugins/platforms/wasm/qwasmevent.h
@@ -5,11 +5,12 @@
#define QWASMEVENT_H
#include "qwasmplatform.h"
+#include "qwasmdom.h"
#include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h>
#include <QtGui/qevent.h>
-
+#include <private/qstdweb_p.h>
#include <QPoint>
#include <emscripten/html5.h>
@@ -17,18 +18,29 @@
QT_BEGIN_NAMESPACE
+class QWasmDeadKeySupport;
+class QWindow;
+
enum class EventType {
+ DragEnd,
+ DragOver,
+ DragStart,
Drop,
+ KeyDown,
+ KeyUp,
PointerDown,
PointerMove,
PointerUp,
PointerEnter,
PointerLeave,
+ PointerCancel,
Wheel,
};
enum class PointerType {
Mouse,
+ Touch,
+ Pen,
Other,
};
@@ -113,26 +125,38 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
struct Event
{
- EventType type;
- emscripten::val target = emscripten::val::undefined();
-
- Event(EventType type, emscripten::val target);
+ Event(EventType type, emscripten::val webEvent);
~Event();
Event(const Event &other);
Event(Event &&other);
Event &operator=(const Event &other);
Event &operator=(Event &&other);
+
+ emscripten::val webEvent;
+ EventType type;
+ emscripten::val target() const { return webEvent["target"]; }
};
-struct MouseEvent : public Event
+struct KeyEvent : public Event
{
- QPoint localPoint;
- QPoint pointInPage;
- QPoint pointInViewport;
- Qt::MouseButton mouseButton;
- Qt::MouseButtons mouseButtons;
+ static std::optional<KeyEvent>
+ fromWebWithDeadKeyTranslation(emscripten::val webEvent, QWasmDeadKeySupport *deadKeySupport);
+
+ KeyEvent(EventType type, emscripten::val webEvent);
+ ~KeyEvent();
+ KeyEvent(const KeyEvent &other);
+ KeyEvent(KeyEvent &&other);
+ KeyEvent &operator=(const KeyEvent &other);
+ KeyEvent &operator=(KeyEvent &&other);
+
+ Qt::Key key;
QFlags<Qt::KeyboardModifier> modifiers;
+ bool deadKey;
+ QString text;
+};
+struct MouseEvent : public Event
+{
MouseEvent(EventType type, emscripten::val webEvent);
~MouseEvent();
MouseEvent(const MouseEvent &other);
@@ -174,6 +198,13 @@ struct MouseEvent : public Event
return QEvent::None;
}
}
+
+ QPointF localPoint;
+ QPointF pointInPage;
+ QPointF pointInViewport;
+ Qt::MouseButton mouseButton;
+ Qt::MouseButtons mouseButtons;
+ QFlags<Qt::KeyboardModifier> modifiers;
};
struct PointerEvent : public MouseEvent
@@ -189,21 +220,34 @@ struct PointerEvent : public MouseEvent
PointerType pointerType;
int pointerId;
+ qreal pressure;
+ qreal tiltX;
+ qreal tiltY;
+ qreal tangentialPressure;
+ qreal twist;
+ qreal width;
+ qreal height;
+ bool isPrimary;
};
struct DragEvent : public MouseEvent
{
- static std::optional<DragEvent> fromWeb(emscripten::val webEvent);
+ static std::optional<DragEvent> fromWeb(emscripten::val webEvent, QWindow *targetQWindow);
- DragEvent(EventType type, emscripten::val webEvent);
+ DragEvent(EventType type, emscripten::val webEvent, QWindow *targetQWindow);
~DragEvent();
DragEvent(const DragEvent &other);
DragEvent(DragEvent &&other);
DragEvent &operator=(const DragEvent &other);
DragEvent &operator=(DragEvent &&other);
+ void cancelDragStart();
+ void acceptDragOver();
+ void acceptDrop();
+
Qt::DropAction dropAction;
- emscripten::val dataTransfer;
+ dom::DataTransfer dataTransfer;
+ QWindow *targetWindow;
};
struct WheelEvent : public MouseEvent
@@ -219,7 +263,7 @@ struct WheelEvent : public MouseEvent
DeltaMode deltaMode;
bool webkitDirectionInvertedFromDevice;
- QPoint delta;
+ QPointF delta;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
index 2fd1a30401..1f2d3095d6 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
@@ -2,16 +2,34 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmeventdispatcher.h"
+#include "qwasmintegration.h"
#include <QtGui/qpa/qwindowsysteminterface.h>
QT_BEGIN_NAMESPACE
// Note: All event dispatcher functionality is implemented in QEventDispatcherWasm
-// in QtCore, except for processWindowSystemEvents() below which uses API from QtGui.
-void QWasmEventDispatcher::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
+// in QtCore, except for processPostedEvents() below which uses API from QtGui.
+bool QWasmEventDispatcher::processPostedEvents()
{
- QWindowSystemInterface::sendWindowSystemEvents(flags);
+ QEventDispatcherWasm::processPostedEvents();
+ return QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
+}
+
+void QWasmEventDispatcher::onLoaded()
+{
+ // This function is called when the application is ready to paint
+ // the first frame. Send the qtlaoder onLoaded event first (via
+ // the base class implementation), and then enable/call requestUpdate
+ // to deliver a frame.
+ QEventDispatcherWasm::onLoaded();
+
+ // Make sure all screens have a defined size; and pick
+ // up size changes due to onLoaded event handling.
+ QWasmIntegration *wasmIntegration = QWasmIntegration::get();
+ wasmIntegration->resizeAllScreens();
+
+ wasmIntegration->releaseRequesetUpdateHold();
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.h b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
index a28fa7263b..cbf10482e3 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.h
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
@@ -11,7 +11,8 @@ QT_BEGIN_NAMESPACE
class QWasmEventDispatcher : public QEventDispatcherWasm
{
protected:
- void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) override;
+ bool processPostedEvents() override;
+ void onLoaded() override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
deleted file mode 100644
index 395c9c3ee0..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include "qwasmeventtranslator.h"
-#include "qwasmeventdispatcher.h"
-#include "qwasmcompositor.h"
-#include "qwasmintegration.h"
-#include "qwasmclipboard.h"
-#include "qwasmcursor.h"
-#include <QtGui/qevent.h>
-#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtCore/qglobal.h>
-#include <QtCore/qobject.h>
-
-#include <QtCore/qdeadlinetimer.h>
-#include <private/qmakearray_p.h>
-#include <QtCore/qnamespace.h>
-#include <QCursor>
-#include <QtCore/private/qstringiterator_p.h>
-
-#include <emscripten/bind.h>
-
-#include <iostream>
-
-QT_BEGIN_NAMESPACE
-
-using namespace emscripten;
-
-namespace {
-constexpr std::string_view WebDeadKeyValue = "Dead";
-
-struct Emkb2QtData
-{
- static constexpr char StringTerminator = '\0';
-
- const char *em;
- unsigned int qt;
-
- constexpr bool operator<=(const Emkb2QtData &that) const noexcept
- {
- return !(strcmp(that) > 0);
- }
-
- bool operator<(const Emkb2QtData &that) const noexcept { return ::strcmp(em, that.em) < 0; }
-
- constexpr bool operator==(const Emkb2QtData &that) const noexcept { return strcmp(that) == 0; }
-
- constexpr int strcmp(const Emkb2QtData &that, const int i = 0) const
- {
- return em[i] == StringTerminator && that.em[i] == StringTerminator ? 0
- : em[i] == StringTerminator ? -1
- : that.em[i] == StringTerminator ? 1
- : em[i] < that.em[i] ? -1
- : em[i] > that.em[i] ? 1
- : strcmp(that, i + 1);
- }
-};
-
-template<unsigned int Qt, char ... EmChar>
-struct Emkb2Qt
-{
- static constexpr const char storage[sizeof ... (EmChar) + 1] = {EmChar..., '\0'};
- using Type = Emkb2QtData;
- static constexpr Type data() noexcept { return Type{storage, Qt}; }
-};
-
-template<unsigned int Qt, char ... EmChar> constexpr char Emkb2Qt<Qt, EmChar...>::storage[];
-
-static constexpr const auto WebToQtKeyCodeMappings = qMakeArray(
- QSortedData<
- Emkb2Qt< Qt::Key_Escape, 'E','s','c','a','p','e' >,
- Emkb2Qt< Qt::Key_Tab, 'T','a','b' >,
- Emkb2Qt< Qt::Key_Backspace, 'B','a','c','k','s','p','a','c','e' >,
- Emkb2Qt< Qt::Key_Return, 'E','n','t','e','r' >,
- Emkb2Qt< Qt::Key_Insert, 'I','n','s','e','r','t' >,
- Emkb2Qt< Qt::Key_Delete, 'D','e','l','e','t','e' >,
- Emkb2Qt< Qt::Key_Pause, 'P','a','u','s','e' >,
- Emkb2Qt< Qt::Key_Pause, 'C','l','e','a','r' >,
- Emkb2Qt< Qt::Key_Home, 'H','o','m','e' >,
- Emkb2Qt< Qt::Key_End, 'E','n','d' >,
- Emkb2Qt< Qt::Key_Left, 'A','r','r','o','w','L','e','f','t' >,
- Emkb2Qt< Qt::Key_Up, 'A','r','r','o','w','U','p' >,
- Emkb2Qt< Qt::Key_Right, 'A','r','r','o','w','R','i','g','h','t' >,
- Emkb2Qt< Qt::Key_Down, 'A','r','r','o','w','D','o','w','n' >,
- Emkb2Qt< Qt::Key_PageUp, 'P','a','g','e','U','p' >,
- Emkb2Qt< Qt::Key_PageDown, 'P','a','g','e','D','o','w','n' >,
- Emkb2Qt< Qt::Key_Shift, 'S','h','i','f','t' >,
- Emkb2Qt< Qt::Key_Control, 'C','o','n','t','r','o','l' >,
- Emkb2Qt< Qt::Key_Meta, 'M','e','t','a'>,
- Emkb2Qt< Qt::Key_Meta, 'O','S'>,
- Emkb2Qt< Qt::Key_Alt, 'A','l','t','L','e','f','t' >,
- Emkb2Qt< Qt::Key_Alt, 'A','l','t' >,
- Emkb2Qt< Qt::Key_CapsLock, 'C','a','p','s','L','o','c','k' >,
- Emkb2Qt< Qt::Key_NumLock, 'N','u','m','L','o','c','k' >,
- Emkb2Qt< Qt::Key_ScrollLock, 'S','c','r','o','l','l','L','o','c','k' >,
- Emkb2Qt< Qt::Key_F1, 'F','1' >,
- Emkb2Qt< Qt::Key_F2, 'F','2' >,
- Emkb2Qt< Qt::Key_F3, 'F','3' >,
- Emkb2Qt< Qt::Key_F4, 'F','4' >,
- Emkb2Qt< Qt::Key_F5, 'F','5' >,
- Emkb2Qt< Qt::Key_F6, 'F','6' >,
- Emkb2Qt< Qt::Key_F7, 'F','7' >,
- Emkb2Qt< Qt::Key_F8, 'F','8' >,
- Emkb2Qt< Qt::Key_F9, 'F','9' >,
- Emkb2Qt< Qt::Key_F10, 'F','1','0' >,
- Emkb2Qt< Qt::Key_F11, 'F','1','1' >,
- Emkb2Qt< Qt::Key_F12, 'F','1','2' >,
- Emkb2Qt< Qt::Key_F13, 'F','1','3' >,
- Emkb2Qt< Qt::Key_F14, 'F','1','4' >,
- Emkb2Qt< Qt::Key_F15, 'F','1','5' >,
- Emkb2Qt< Qt::Key_F16, 'F','1','6' >,
- Emkb2Qt< Qt::Key_F17, 'F','1','7' >,
- Emkb2Qt< Qt::Key_F18, 'F','1','8' >,
- Emkb2Qt< Qt::Key_F19, 'F','1','9' >,
- Emkb2Qt< Qt::Key_F20, 'F','2','0' >,
- Emkb2Qt< Qt::Key_F21, 'F','2','1' >,
- Emkb2Qt< Qt::Key_F22, 'F','2','2' >,
- Emkb2Qt< Qt::Key_F23, 'F','2','3' >,
- Emkb2Qt< Qt::Key_Paste, 'P','a','s','t','e' >,
- Emkb2Qt< Qt::Key_AltGr, 'A','l','t','R','i','g','h','t' >,
- Emkb2Qt< Qt::Key_Help, 'H','e','l','p' >,
- Emkb2Qt< Qt::Key_yen, 'I','n','t','l','Y','e','n' >,
- Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >
- >::Data{}
- );
-
-static constexpr const auto WebToQtKeyCodeMappingsWithShift = qMakeArray(
- QSortedData<
- // shifted
- Emkb2Qt< Qt::Key_Agrave, '\xc3','\x80' >,
- Emkb2Qt< Qt::Key_Aacute, '\xc3','\x81' >,
- Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\x82' >,
- Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\x84' >,
- Emkb2Qt< Qt::Key_AE, '\xc3','\x86' >,
- Emkb2Qt< Qt::Key_Atilde, '\xc3','\x83' >,
- Emkb2Qt< Qt::Key_Aring, '\xc3','\x85' >,
- Emkb2Qt< Qt::Key_Egrave, '\xc3','\x88' >,
- Emkb2Qt< Qt::Key_Eacute, '\xc3','\x89' >,
- Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\x8a' >,
- Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\x8b' >,
- Emkb2Qt< Qt::Key_Iacute, '\xc3','\x8d' >,
- Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\x8e' >,
- Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\x8f' >,
- Emkb2Qt< Qt::Key_Igrave, '\xc3','\x8c' >,
- Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\x94' >,
- Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\x96' >,
- Emkb2Qt< Qt::Key_Ograve, '\xc3','\x92' >,
- Emkb2Qt< Qt::Key_Oacute, '\xc3','\x93' >,
- Emkb2Qt< Qt::Key_Ooblique, '\xc3','\x98' >,
- Emkb2Qt< Qt::Key_Otilde, '\xc3','\x95' >,
- Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\x9b' >,
- Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\x9c' >,
- Emkb2Qt< Qt::Key_Ugrave, '\xc3','\x99' >,
- Emkb2Qt< Qt::Key_Uacute, '\xc3','\x9a' >,
- Emkb2Qt< Qt::Key_Ntilde, '\xc3','\x91' >,
- Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\x87' >,
- Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\x8f' >,
- Emkb2Qt< Qt::Key_Yacute, '\xc3','\x9d' >
- >::Data{}
-);
-
-std::optional<Qt::Key> findMappingByBisection(const char *toFind)
-{
- const Emkb2QtData searchKey{ toFind, 0 };
- const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(),
- searchKey);
- return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast<Qt::Key>(it->qt)
- : std::optional<Qt::Key>();
-}
-
-bool isDeadKeyEvent(const EmscriptenKeyboardEvent *emKeyEvent)
-{
- return qstrncmp(emKeyEvent->key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
-}
-
-Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey)
-{
- const bool deadKeyEvent = isDeadKeyEvent(emscriptKey);
- if (deadKeyEvent) {
- if (auto mapping = findMappingByBisection(emscriptKey->code))
- return *mapping;
- }
- if (auto mapping = findMappingByBisection(emscriptKey->key))
- return *mapping;
- if (deadKeyEvent)
- return Qt::Key_unknown;
-
- // cast to unicode key
- QString str = QString::fromUtf8(emscriptKey->key).toUpper();
- QStringIterator i(str);
- return static_cast<Qt::Key>(i.next(0));
-}
-
-struct KeyMapping { Qt::Key from, to; };
-
-constexpr KeyMapping tildeKeyTable[] = { // ~
- { Qt::Key_A, Qt::Key_Atilde },
- { Qt::Key_N, Qt::Key_Ntilde },
- { Qt::Key_O, Qt::Key_Otilde },
-};
-constexpr KeyMapping graveKeyTable[] = { // `
- { Qt::Key_A, Qt::Key_Agrave },
- { Qt::Key_E, Qt::Key_Egrave },
- { Qt::Key_I, Qt::Key_Igrave },
- { Qt::Key_O, Qt::Key_Ograve },
- { Qt::Key_U, Qt::Key_Ugrave },
-};
-constexpr KeyMapping acuteKeyTable[] = { // '
- { Qt::Key_A, Qt::Key_Aacute },
- { Qt::Key_E, Qt::Key_Eacute },
- { Qt::Key_I, Qt::Key_Iacute },
- { Qt::Key_O, Qt::Key_Oacute },
- { Qt::Key_U, Qt::Key_Uacute },
- { Qt::Key_Y, Qt::Key_Yacute },
-};
-constexpr KeyMapping diaeresisKeyTable[] = { // umlaut ¨
- { Qt::Key_A, Qt::Key_Adiaeresis },
- { Qt::Key_E, Qt::Key_Ediaeresis },
- { Qt::Key_I, Qt::Key_Idiaeresis },
- { Qt::Key_O, Qt::Key_Odiaeresis },
- { Qt::Key_U, Qt::Key_Udiaeresis },
- { Qt::Key_Y, Qt::Key_ydiaeresis },
-};
-constexpr KeyMapping circumflexKeyTable[] = { // ^
- { Qt::Key_A, Qt::Key_Acircumflex },
- { Qt::Key_E, Qt::Key_Ecircumflex },
- { Qt::Key_I, Qt::Key_Icircumflex },
- { Qt::Key_O, Qt::Key_Ocircumflex },
- { Qt::Key_U, Qt::Key_Ucircumflex },
-};
-
-static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
-{
- while (first != last) {
- if (first->from == key)
- return first->to;
- ++first;
- }
- return Qt::Key_unknown;
-}
-
-template <size_t N>
-static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
-{
- return find_impl(map, map + N, key);
-}
-
-Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey)
-{
- switch (deadKey) {
- case Qt::Key_QuoteLeft: {
- // ` macOS: Key_Dead_Grave
- return platform() == Platform::MacOS ? find(graveKeyTable, accentBaseKey)
- : find(diaeresisKeyTable, accentBaseKey);
- }
- case Qt::Key_O: // ´ Key_Dead_Grave
- return find(graveKeyTable, accentBaseKey);
- case Qt::Key_E: // ´ Key_Dead_Acute
- return find(acuteKeyTable, accentBaseKey);
- case Qt::Key_AsciiTilde:
- case Qt::Key_N: // Key_Dead_Tilde
- return find(tildeKeyTable, accentBaseKey);
- case Qt::Key_U: // ¨ Key_Dead_Diaeresis
- return find(diaeresisKeyTable, accentBaseKey);
- case Qt::Key_I: // macOS Key_Dead_Circumflex
- case Qt::Key_6: // linux
- case Qt::Key_Apostrophe: // linux
- return find(circumflexKeyTable, accentBaseKey);
- default:
- return Qt::Key_unknown;
- };
-}
-
-template<class T>
-std::optional<QString> findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey)
-{
- const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(),
- [qtKey](const Emkb2QtData &data) { return data.qt == qtKey; });
- return it != mappingArray.cend() ? it->em : std::optional<QString>();
-}
-} // namespace
-
-QWasmEventTranslator::QWasmEventTranslator() = default;
-
-QWasmEventTranslator::~QWasmEventTranslator() = default;
-
-QWasmEventTranslator::TranslatedEvent
-QWasmEventTranslator::translateKeyEvent(int emEventType, const EmscriptenKeyboardEvent *keyEvent)
-{
- TranslatedEvent ret;
- switch (emEventType) {
- case EMSCRIPTEN_EVENT_KEYDOWN:
- ret.type = QEvent::KeyPress;
- break;
- case EMSCRIPTEN_EVENT_KEYUP:
- ret.type = QEvent::KeyRelease;
- break;
- default:
- // Should not be reached - do not call with this event type.
- Q_ASSERT(false);
- break;
- };
-
- ret.key = translateEmscriptKey(keyEvent);
-
- if (isDeadKeyEvent(keyEvent) || ret.key == Qt::Key_AltGr) {
- if (keyEvent->shiftKey && ret.key == Qt::Key_QuoteLeft)
- ret.key = Qt::Key_AsciiTilde;
- m_emDeadKey = ret.key;
- } else if (m_emDeadKey != Qt::Key_unknown
- && (m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
- || ret.key == m_keyModifiedByDeadKeyOnPress)) {
- const Qt::Key baseKey = ret.key;
- const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_emDeadKey);
- if (translatedKey != Qt::Key_unknown)
- ret.key = translatedKey;
-
- if (auto text = keyEvent->shiftKey
- ? findKeyTextByKeyId(WebToQtKeyCodeMappingsWithShift, ret.key)
- : findKeyTextByKeyId(WebToQtKeyCodeMappings, ret.key)) {
- if (ret.type == QEvent::KeyPress) {
- Q_ASSERT(m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown);
- m_keyModifiedByDeadKeyOnPress = baseKey;
- } else {
- Q_ASSERT(ret.type == QEvent::KeyRelease);
- Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
- m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
- m_emDeadKey = Qt::Key_unknown;
- }
- ret.text = *text;
- return ret;
- }
- }
- ret.text = QString::fromUtf8(keyEvent->key);
- return ret;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h
deleted file mode 100644
index 23299c294f..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#ifndef QWASMEVENTTRANSLATOR_H
-#define QWASMEVENTTRANSLATOR_H
-
-#include <QtCore/qobject.h>
-#include <QtCore/qrect.h>
-#include <QtCore/qpoint.h>
-#include <emscripten/html5.h>
-#include "qwasmwindow.h"
-#include <QtGui/qinputdevice.h>
-#include <QHash>
-#include <QCursor>
-#include "qwasmevent.h"
-#include "qwasmplatform.h"
-
-QT_BEGIN_NAMESPACE
-
-class QWindow;
-
-class QWasmEventTranslator : public QObject
-{
- Q_OBJECT
-
-public:
- struct TranslatedEvent
- {
- QEvent::Type type;
- Qt::Key key;
- QString text;
- };
- explicit QWasmEventTranslator();
- ~QWasmEventTranslator();
-
- TranslatedEvent translateKeyEvent(int emEventType, const EmscriptenKeyboardEvent *keyEvent);
-
-private:
- static quint64 getTimestamp();
-
- Qt::Key m_emDeadKey = Qt::Key_unknown;
- Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
-};
-
-QT_END_NAMESPACE
-#endif // QWASMEVENTTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
index 7b8265ca23..3f3dc10f71 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
@@ -6,137 +6,315 @@
#include <QtCore/qfile.h>
#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <emscripten.h>
#include <emscripten/val.h>
#include <emscripten/bind.h>
+#include <map>
+#include <array>
+
QT_BEGIN_NAMESPACE
using namespace emscripten;
using namespace Qt::StringLiterals;
-void QWasmFontDatabase::populateFontDatabase()
+
+namespace {
+
+class FontData
{
- // Load font file from resources. Currently
- // all fonts needs to be bundled with the nexe
- // as Qt resources.
+public:
+ FontData(val fontData)
+ :m_fontData(fontData) {}
- const QString fontFileNames[] = {
- QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
- QStringLiteral(":/fonts/Vera.ttf"),
- QStringLiteral(":/fonts/DejaVuSans.ttf"),
- };
- for (const QString &fontFileName : fontFileNames) {
- QFile theFont(fontFileName);
- if (!theFont.open(QIODevice::ReadOnly))
- break;
+ QString family() const
+ {
+ return QString::fromStdString(m_fontData["family"].as<std::string>());
+ }
- QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
+ QString fullName() const
+ {
+ return QString::fromStdString(m_fontData["fullName"].as<std::string>());
}
- // check if local-fonts API is available in the browser
- val window = val::global("window");
- val fonts = window["queryLocalFonts"];
+ QString postscriptName() const
+ {
+ return QString::fromStdString(m_fontData["postscriptName"].as<std::string>());
+ }
- if (fonts.isUndefined())
- return;
+ QString style() const
+ {
+ return QString::fromStdString(m_fontData["style"].as<std::string>());
+ }
+
+ val value() const
+ {
+ return m_fontData;
+ }
+
+private:
+ val m_fontData;
+};
+
+val makeObject(const char *key, const char *value)
+{
+ val obj = val::object();
+ obj.set(key, std::string(value));
+ return obj;
+}
+
+void printError(val err) {
+ qCWarning(lcQpaFonts)
+ << QString::fromStdString(err["name"].as<std::string>())
+ << QString::fromStdString(err["message"].as<std::string>());
+ QWasmFontDatabase::endAllFontFileLoading();
+}
- val permissions = val::global("navigator")["permissions"];
- if (permissions["request"].isUndefined())
+void checkFontAccessPermitted(std::function<void(bool)> callback)
+{
+ const val permissions = val::global("navigator")["permissions"];
+ if (permissions.isUndefined()) {
+ callback(false);
return;
+ }
- val requestLocalFontsPermission = val::object();
- requestLocalFontsPermission.set("name", std::string("local-fonts"));
-
- qstdweb::PromiseCallbacks permissionRequestCallbacks {
- .thenFunc = [window](val status) {
- qCDebug(lcQpaFonts) << "onFontPermissionSuccess:"
- << QString::fromStdString(status["state"].as<std::string>());
-
- // query all available local fonts and call registerFontFamily for each of them
- qstdweb::Promise::make(window, "queryLocalFonts", {
- .thenFunc = [](val status) {
- const int count = status["length"].as<int>();
- for (int i = 0; i < count; ++i) {
- val font = status.call<val>("at", i);
- const std::string family = font["family"].as<std::string>();
- QFreeTypeFontDatabase::registerFontFamily(QString::fromStdString(family));
- }
- QWasmFontDatabase::notifyFontsChanged();
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts)
- << "Error while trying to query local-fonts API";
- }
- });
+ qstdweb::Promise::make(permissions, "query", {
+ .thenFunc = [callback](val status) {
+ callback(status["state"].as<std::string>() == "granted");
},
- .catchFunc = [](val error) {
- qCWarning(lcQpaFonts)
- << "Error while requesting local-fonts API permission: "
- << QString::fromStdString(error["name"].as<std::string>());
- }
- };
+ }, makeObject("name", "local-fonts"));
+}
- // request local fonts permission (currently supported only by Chrome 103+)
- qstdweb::Promise::make(permissions, "request", std::move(permissionRequestCallbacks), std::move(requestLocalFontsPermission));
+void queryLocalFonts(std::function<void(const QList<FontData> &)> callback)
+{
+ emscripten::val window = emscripten::val::global("window");
+ qstdweb::Promise::make(window, "queryLocalFonts", {
+ .thenFunc = [callback](emscripten::val fontArray) {
+ QList<FontData> fonts;
+ const int count = fontArray["length"].as<int>();
+ fonts.reserve(count);
+ for (int i = 0; i < count; ++i)
+ fonts.append(FontData(fontArray.call<emscripten::val>("at", i)));
+ callback(fonts);
+ },
+ .catchFunc = printError
+ });
}
-void QWasmFontDatabase::populateFamily(const QString &familyName)
+void readBlob(val blob, std::function<void(const QByteArray &)> callback)
{
- val window = val::global("window");
+ qstdweb::Promise::make(blob, "arrayBuffer", {
+ .thenFunc = [callback](emscripten::val fontArrayBuffer) {
+ QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
+ callback(fontData);
+ },
+ .catchFunc = printError
+ });
+}
- auto queryFontsArgument = val::array(std::vector<val>({ val(familyName.toStdString()) }));
- val queryFont = val::object();
- queryFont.set("postscriptNames", std::move(queryFontsArgument));
+void readFont(FontData font, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(font.value(), "blob", {
+ .thenFunc = [callback](val blob) {
+ readBlob(blob, [callback](const QByteArray &data) {
+ callback(data);
+ });
+ },
+ .catchFunc = printError
+ });
+}
- qstdweb::PromiseCallbacks localFontsQueryCallback {
- .thenFunc = [](val status) {
- val font = status.call<val>("at", 0);
+emscripten::val getLocalFontsConfigProperty(const char *name) {
+ emscripten::val qt = val::module_property("qt");
+ if (qt.isUndefined())
+ return emscripten::val();
+ emscripten::val localFonts = qt["localFonts"];
+ if (localFonts.isUndefined())
+ return emscripten::val();
+ return localFonts[name];
+};
+
+bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return prop.as<bool>();
+};
+
+QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return QString::fromStdString(prop.as<std::string>());
+};
+
+QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) {
+ emscripten::val array = getLocalFontsConfigProperty(name);
+ if (array.isUndefined())
+ return defaultValue;
+
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
+ }
+ return list;
+};
- if (font.isUndefined())
- return;
+} // namespace
- qstdweb::PromiseCallbacks blobQueryCallback {
- .thenFunc = [](val status) {
- qCDebug(lcQpaFonts) << "onBlobQuerySuccess";
+QWasmFontDatabase::QWasmFontDatabase()
+:QFreeTypeFontDatabase()
+{
+ m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false;
+ if (m_localFontsApiSupported)
+ beginFontDatabaseStartupTask();
+}
- qstdweb::PromiseCallbacks arrayBufferCallback {
- .thenFunc = [](val status) {
- qCDebug(lcQpaFonts) << "onArrayBuffer" ;
+QWasmFontDatabase *QWasmFontDatabase::get()
+{
+ return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase());
+}
- QByteArray fontByteArray = QByteArray::fromUint8Array(status);
+// Populates the font database with local fonts. Will make the browser ask
+// the user for permission if needed. Does nothing if the Local Font Access API
+// is not supported.
+void QWasmFontDatabase::populateLocalfonts()
+{
+ // Decide which font families to populate based on user preferences
+ QStringList selectedLocalFontFamilies;
+ bool allFamilies = false;
+
+ switch (m_localFontFamilyLoadSet) {
+ case NoFontFamilies:
+ default:
+ // keep empty selectedLocalFontFamilies
+ break;
+ case DefaultFontFamilies: {
+ const QStringList webSafeFontFamilies =
+ {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
+ "Georgia", "Garamond", "Courier New"};
+ selectedLocalFontFamilies = webSafeFontFamilies;
+ } break;
+ case AllFontFamilies:
+ allFamilies = true;
+ break;
+ }
- QFreeTypeFontDatabase::addTTFile(fontByteArray, QByteArray());
+ selectedLocalFontFamilies += m_extraLocalFontFamilies;
- QWasmFontDatabase::notifyFontsChanged();
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onArrayBufferError";
- }
- };
+ if (selectedLocalFontFamilies.isEmpty() && !allFamilies) {
+ endAllFontFileLoading();
+ return;
+ }
- qstdweb::Promise::make(status, "arrayBuffer", std::move(arrayBufferCallback));
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onBlobQueryError";
- }
- };
+ populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies);
+}
- qstdweb::Promise::make(font, "blob", std::move(blobQueryCallback));
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onLocalFontsQueryError";
+namespace {
+ QStringList toStringList(emscripten::val array)
+ {
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
}
+ return list;
+ }
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families)
+{
+ if (!m_localFontsApiSupported)
+ return;
+ populateLocalFontFamilies(toStringList(families), false);
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies)
+{
+ queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) {
+ refFontFileLoading();
+ QList<FontData> filteredFonts;
+ std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts),
+ [fontFamilies, allFamilies](FontData fontData) {
+ return allFamilies || fontFamilies.contains(fontData.family());
+ });
+
+ for (const FontData &font: filteredFonts) {
+ refFontFileLoading();
+ readFont(font, [font](const QByteArray &fontData){
+ QFreeTypeFontDatabase::registerFontFamily(font.family());
+ QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
+ derefFontFileLoading();
+ });
+ }
+ derefFontFileLoading();
+ });
+
+}
+
+void QWasmFontDatabase::populateFontDatabase()
+{
+ // Load bundled font file from resources.
+ const QString fontFileNames[] = {
+ QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
+ QStringLiteral(":/fonts/DejaVuSans.ttf"),
};
+ for (const QString &fontFileName : fontFileNames) {
+ QFile theFont(fontFileName);
+ if (!theFont.open(QIODevice::ReadOnly))
+ break;
+
+ QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
+ }
- qstdweb::Promise::make(window, "queryLocalFonts", std::move(localFontsQueryCallback), std::move(queryFont));
+ // Get config options for controlling local fonts usage
+ m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
+ QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
+ m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
+
+ if (fontFamilyLoadSet == "NoFontFamilies") {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
+ m_localFontFamilyLoadSet = DefaultFontFamilies;
+ } else if (fontFamilyLoadSet == "AllFontFamilies") {
+ m_localFontFamilyLoadSet = AllFontFamilies;
+ } else {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
+ }
+
+ if (!m_localFontsApiSupported)
+ return;
+
+ // Populate the font database with local fonts. Either try unconditianlly
+ // if displyaing a fonts permissions dialog at startup is allowed, or else
+ // only if we already have permission.
+ if (m_queryLocalFontsPermission) {
+ populateLocalfonts();
+ } else {
+ checkFontAccessPermitted([this](bool granted) {
+ if (granted)
+ populateLocalfonts();
+ else
+ endAllFontFileLoading();
+ });
+ }
}
QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
{
- return QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ return fontEngine;
}
QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
@@ -146,9 +324,9 @@ QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::
QStringList fallbacks
= QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
- // Add the vera.ttf and DejaVuSans.ttf fonts (loaded in populateFontDatabase above) as falback fonts
+ // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
// to all other fonts (except itself).
- static const QString wasmFallbackFonts[] = { "Bitstream Vera Sans", "DejaVu Sans" };
+ static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
for (auto wasmFallbackFont : wasmFallbackFonts) {
if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
fallbacks.append(wasmFallbackFont);
@@ -164,13 +342,63 @@ void QWasmFontDatabase::releaseHandle(void *handle)
QFont QWasmFontDatabase::defaultFont() const
{
- return QFont("Bitstream Vera Sans"_L1);
+ return QFont("DejaVu Sans"_L1);
+}
+
+namespace {
+ int g_pendingFonts = 0;
+ bool g_fontStartupTaskCompleted = false;
+}
+
+// Registers font loading as a startup task, which makes Qt delay
+// sending onLoaded event until font loading has completed.
+void QWasmFontDatabase::beginFontDatabaseStartupTask()
+{
+ g_fontStartupTaskCompleted = false;
+ QEventDispatcherWasm::registerStartupTask();
+}
+
+// Ends the font loading startup task.
+void QWasmFontDatabase::endFontDatabaseStartupTask()
+{
+ if (!g_fontStartupTaskCompleted) {
+ g_fontStartupTaskCompleted = true;
+ QEventDispatcherWasm::completeStarupTask();
+ }
+}
+
+// Registers that a font file will be loaded.
+void QWasmFontDatabase::refFontFileLoading()
+{
+ g_pendingFonts += 1;
+}
+
+// Registers that one font file has been loaded, and sends notifactions
+// when all pending font files have been loaded.
+void QWasmFontDatabase::derefFontFileLoading()
+{
+ if (--g_pendingFonts <= 0) {
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ endFontDatabaseStartupTask();
+ }
}
-void QWasmFontDatabase::notifyFontsChanged()
+// Unconditionally ends local font loading, for instance if there
+// are no fonts to load or if there was an unexpected error.
+void QWasmFontDatabase::endAllFontFileLoading()
{
- QFontCache::instance()->clear();
- emit qGuiApp->fontDatabaseChanged();
+ bool hadPandingfonts = g_pendingFonts > 0;
+ if (hadPandingfonts) {
+ // The hadPandingfonts counter might no longer be correct; disable counting
+ // and send notifications unconditionally.
+ g_pendingFonts = 0;
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ }
+
+ endFontDatabaseStartupTask();
}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h
index 22c550f244..a1c8f1ff48 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.h
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h
@@ -6,13 +6,17 @@
#include <QtGui/private/qfreetypefontdatabase_p.h>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
class QWasmFontDatabase : public QFreeTypeFontDatabase
{
public:
+ QWasmFontDatabase();
+ static QWasmFontDatabase *get();
+
void populateFontDatabase() override;
- void populateFamily(const QString &familyName) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint,
@@ -20,7 +24,26 @@ public:
void releaseHandle(void *handle) override;
QFont defaultFont() const override;
- static void notifyFontsChanged();
+ void populateLocalfonts();
+ void populateLocalFontFamilies(emscripten::val families);
+ void populateLocalFontFamilies(const QStringList &famliies, bool allFamilies);
+
+ static void beginFontDatabaseStartupTask();
+ static void endFontDatabaseStartupTask();
+ static void refFontFileLoading();
+ static void derefFontFileLoading();
+ static void endAllFontFileLoading();
+
+private:
+ bool m_localFontsApiSupported = false;
+ bool m_queryLocalFontsPermission = false;
+ enum FontFamilyLoadSet {
+ NoFontFamilies,
+ DefaultFontFamilies,
+ AllFontFamilies,
+ };
+ FontFamilyLoadSet m_localFontFamilyLoadSet;
+ QStringList m_extraLocalFontFamilies;
};
QT_END_NAMESPACE
#endif
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
index 1c004b226f..ae72e7b7f9 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -5,9 +5,9 @@
#include "qwasminputcontext.h"
#include "qwasmintegration.h"
+#include "qwasmplatform.h"
#include <QRectF>
#include <qpa/qplatforminputcontext.h>
-#include "qwasmeventtranslator.h"
#include "qwasmscreen.h"
#include <qguiapplication.h>
#include <qwindow.h>
@@ -24,14 +24,13 @@ static void inputCallback(emscripten::val event)
if (length <= 0)
return;
- // use only last character
- emscripten::val _incomingCharVal = event["target"]["value"][length - 1];
+ emscripten::val _incomingCharVal = event["data"];
if (_incomingCharVal != emscripten::val::undefined() && _incomingCharVal != emscripten::val::null()) {
QString str = QString::fromStdString(_incomingCharVal.as<std::string>());
QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext*>(event["target"]["data-context"].as<quintptr>());
- wasmInput->inputStringChanged(str, wasmInput);
+ reinterpret_cast<QWasmInputContext*>(event["target"]["data-qinputcontext"].as<quintptr>());
+ wasmInput->inputStringChanged(str, EMSCRIPTEN_EVENT_KEYDOWN, wasmInput);
}
// this clears the input string, so backspaces do not send a character
// but stops suggestions
@@ -39,7 +38,7 @@ static void inputCallback(emscripten::val event)
}
EMSCRIPTEN_BINDINGS(clipboard_module) {
- function("qt_InputContextCallback", &inputCallback);
+ function("qtInputContextCallback", &inputCallback);
}
QWasmInputContext::QWasmInputContext()
@@ -50,28 +49,24 @@ QWasmInputContext::QWasmInputContext()
m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
m_inputElement.set("contenteditable","true");
- if (platform() == Platform::Android) {
- emscripten::val body = document["body"];
- body.call<void>("appendChild", m_inputElement);
-
- m_inputElement.call<void>("addEventListener", std::string("input"),
- emscripten::val::module_property("qt_InputContextCallback"),
- emscripten::val(false));
- m_inputElement.set("data-context",
- emscripten::val(quintptr(reinterpret_cast<void *>(this))));
-
- // android sends Enter through target window, let's just handle this here
- emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void *)this, 1,
- &androidKeyboardCallback);
-
- }
- if (platform() == Platform::MacOS || platform() == Platform::iPhone)
- {
+ if (platform() == Platform::MacOS || platform() == Platform::iOS) {
auto callback = [=](emscripten::val) {
m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
inputPanelIsOpen = false;
};
m_blurEventHandler.reset(new EventCallback(m_inputElement, "blur", callback));
+
+ } else {
+
+ const std::string inputType = platform() == Platform::Windows ? "textInput" : "input";
+
+ document.call<void>("addEventListener", inputType,
+ emscripten::val::module_property("qtInputContextCallback"),
+ emscripten::val(false));
+ m_inputElement.set("data-qinputcontext",
+ emscripten::val(quintptr(reinterpret_cast<void *>(this))));
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", m_inputElement);
}
QObject::connect(qGuiApp, &QGuiApplication::focusWindowChanged, this,
@@ -80,7 +75,7 @@ QWasmInputContext::QWasmInputContext()
QWasmInputContext::~QWasmInputContext()
{
- if (platform() == Platform::Android)
+ if (platform() == Platform::Android || platform() == Platform::Windows)
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
}
@@ -89,14 +84,11 @@ void QWasmInputContext::focusWindowChanged(QWindow *focusWindow)
m_focusWindow = focusWindow;
}
-emscripten::val QWasmInputContext::focusScreenElement()
+emscripten::val QWasmInputContext::inputHandlerElementForFocusedWindow()
{
if (!m_focusWindow)
return emscripten::val::undefined();
- QScreen *screen = m_focusWindow->screen();
- if (!screen)
- return emscripten::val::undefined();
- return QWasmScreen::get(screen)->element();
+ return static_cast<QWasmWindow *>(m_focusWindow->handle())->inputHandlerElement();
}
void QWasmInputContext::update(Qt::InputMethodQueries queries)
@@ -107,8 +99,10 @@ void QWasmInputContext::update(Qt::InputMethodQueries queries)
void QWasmInputContext::showInputPanel()
{
if (platform() == Platform::Windows
- && inputPanelIsOpen) // call this only once for win32
- return;
+ && !inputPanelIsOpen) { // call this only once for win32
+ m_inputElement.call<void>("focus");
+ return;
+ }
// this is called each time the keyboard is touched
// Add the input element as a child of the screen for the
@@ -119,12 +113,12 @@ void QWasmInputContext::showInputPanel()
// screen element.
if (platform() == Platform::MacOS // keep for compatibility
- || platform() == Platform::iPhone
+ || platform() == Platform::iOS
|| platform() == Platform::Windows) {
- emscripten::val screenElement = focusScreenElement();
- if (screenElement.isUndefined())
+ emscripten::val inputWrapper = inputHandlerElementForFocusedWindow();
+ if (inputWrapper.isUndefined())
return;
- screenElement.call<void>("appendChild", m_inputElement);
+ inputWrapper.call<void>("appendChild", m_inputElement);
}
m_inputElement.call<void>("focus");
@@ -139,29 +133,29 @@ void QWasmInputContext::hideInputPanel()
inputPanelIsOpen = false;
}
-void QWasmInputContext::inputStringChanged(QString &inputString, QWasmInputContext *context)
+void QWasmInputContext::inputStringChanged(QString &inputString, int eventType, QWasmInputContext *context)
{
Q_UNUSED(context)
QKeySequence keys = QKeySequence::fromString(inputString);
+ Qt::Key thisKey = keys[0].key();
+
// synthesize this keyevent as android is not normal
+ if (inputString.size() > 2 && (thisKey < Qt::Key_F35
+ || thisKey > Qt::Key_Back)) {
+ inputString.clear();
+ }
+ if (inputString == QStringLiteral("Escape")) {
+ thisKey = Qt::Key_Escape;
+ inputString.clear();
+ } else if (thisKey == Qt::Key(0)) {
+ thisKey = Qt::Key_Return;
+ }
+
QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyPress,keys[0].key(), keys[0].keyboardModifiers(), inputString);
+ 0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? QEvent::KeyPress : QEvent::KeyRelease,
+ thisKey, keys[0].keyboardModifiers(),
+ eventType == EMSCRIPTEN_EVENT_KEYDOWN ? inputString : QStringLiteral(""));
}
-int QWasmInputContext::androidKeyboardCallback(int eventType,
- const EmscriptenKeyboardEvent *keyEvent,
- void *userData)
-{
- // we get Enter, Backspace and function keys via emscripten on target window
- Q_UNUSED(eventType)
- QString strKey(keyEvent->key);
- if (strKey == "Unidentified" || strKey == "Process")
- return false;
-
- QWasmInputContext *wasmInput = reinterpret_cast<QWasmInputContext*>(userData);
- wasmInput->inputStringChanged(strKey, wasmInput);
-
- return true;
-}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
index 0886ae8d84..10dd1a0950 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.h
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -29,19 +29,17 @@ public:
bool isValid() const override { return true; }
void focusWindowChanged(QWindow *focusWindow);
- void inputStringChanged(QString &, QWasmInputContext *context);
+ void inputStringChanged(QString &, int eventType, QWasmInputContext *context);
+ emscripten::val m_inputElement = emscripten::val::null();
private:
- emscripten::val focusScreenElement();
+ emscripten::val inputHandlerElementForFocusedWindow();
bool m_inputPanelVisible = false;
QPointer<QWindow> m_focusWindow;
- emscripten::val m_inputElement = emscripten::val::null();
std::unique_ptr<qstdweb::EventCallback> m_blurEventHandler;
std::unique_ptr<qstdweb::EventCallback> m_inputEventHandler;
- static int androidKeyboardCallback(int eventType,
- const EmscriptenKeyboardEvent *keyEvent, void *userData);
bool inputPanelIsOpen = false;
};
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index b92e456668..f5cc3e2eee 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmintegration.h"
-#include "qwasmeventtranslator.h"
#include "qwasmeventdispatcher.h"
#include "qwasmcompositor.h"
#include "qwasmopenglcontext.h"
@@ -11,18 +10,18 @@
#include "qwasmaccessibility.h"
#include "qwasmservices.h"
#include "qwasmoffscreensurface.h"
-
+#include "qwasmplatform.h"
#include "qwasmwindow.h"
#include "qwasmbackingstore.h"
#include "qwasmfontdatabase.h"
-#if defined(Q_OS_UNIX)
-#include <QtGui/private/qgenericunixeventdispatcher_p.h>
-#endif
+#include "qwasmdrag.h"
+
#include <qpa/qplatformwindow.h>
#include <QtGui/qscreen.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <qpa/qplatforminputcontextfactory_p.h>
+#include <qpa/qwindowsysteminterface_p.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
@@ -33,18 +32,25 @@
QT_BEGIN_NAMESPACE
+extern void qt_set_sequence_auto_mnemonic(bool);
+
using namespace emscripten;
using namespace Qt::StringLiterals;
+static void setContainerElements(emscripten::val elementArray)
+{
+ QWasmIntegration::get()->setContainerElements(elementArray);
+}
+
static void addContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->addScreen(element);
+ QWasmIntegration::get()->addContainerElement(element);
}
static void removeContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->removeScreen(element);
+ QWasmIntegration::get()->removeContainerElement(element);
}
static void resizeContainerElement(emscripten::val element)
@@ -63,31 +69,45 @@ static void resizeAllScreens(emscripten::val event)
QWasmIntegration::get()->resizeAllScreens();
}
+static void loadLocalFontFamilies(emscripten::val event)
+{
+ QWasmIntegration::get()->loadLocalFontFamilies(event);
+}
+
EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)
{
+ function("qtSetContainerElements", &setContainerElements);
function("qtAddContainerElement", &addContainerElement);
function("qtRemoveContainerElement", &removeContainerElement);
function("qtResizeContainerElement", &resizeContainerElement);
function("qtUpdateDpi", &qtUpdateDpi);
function("qtResizeAllScreens", &resizeAllScreens);
+ function("qtLoadLocalFontFamilies", &loadLocalFontFamilies);
}
QWasmIntegration *QWasmIntegration::s_instance;
QWasmIntegration::QWasmIntegration()
- : m_fontDb(nullptr),
- m_desktopServices(nullptr),
- m_clipboard(new QWasmClipboard),
- m_accessibility(new QWasmAccessibility)
+ : m_fontDb(nullptr)
+ , m_desktopServices(nullptr)
+ , m_clipboard(new QWasmClipboard)
+#if QT_CONFIG(accessibility)
+ , m_accessibility(new QWasmAccessibility)
+#endif
{
s_instance = this;
+ if (platform() == Platform::MacOS)
+ qt_set_sequence_auto_mnemonic(false);
+
touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
+ QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
// Create screens for container elements. Each container element will ultimately become a
// div element. Qt historically supported supplying canvas for screen elements - these elements
// will be transformed into divs and warnings about deprecation will be printed. See
// QWasmScreen ctor.
+ emscripten::val filtered = emscripten::val::array();
emscripten::val qtContainerElements = val::module_property("qtContainerElements");
if (qtContainerElements.isArray()) {
for (int i = 0; i < qtContainerElements["length"].as<int>(); ++i) {
@@ -95,13 +115,14 @@ QWasmIntegration::QWasmIntegration()
if (element.isNull() || element.isUndefined())
qWarning() << "Skipping null or undefined element in qtContainerElements";
else
- addScreen(element);
+ filtered.call<void>("push", element);
}
} else {
// No screens, which may or may not be intended
qWarning() << "The qtContainerElements module property was not set or is invalid. "
"Proceeding with no screens.";
}
+ setContainerElements(filtered);
// install browser window resize handler
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE,
@@ -121,7 +142,7 @@ QWasmIntegration::QWasmIntegration()
visualViewport.call<void>("addEventListener", val("resize"),
val::module_property("qtResizeAllScreens"));
}
- m_drag = std::make_unique<QSimpleDrag>();
+ m_drag = std::make_unique<QWasmDrag>();
}
QWasmIntegration::~QWasmIntegration()
@@ -138,10 +159,12 @@ QWasmIntegration::~QWasmIntegration()
delete m_desktopServices;
if (m_platformInputContext)
delete m_platformInputContext;
+#if QT_CONFIG(accessibility)
delete m_accessibility;
+#endif
for (const auto &elementAndScreen : m_screens)
- elementAndScreen.second->deleteScreen();
+ elementAndScreen.wasmScreen->deleteScreen();
m_screens.clear();
@@ -164,8 +187,10 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
{
- QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
- return new QWasmWindow(window, compositor, m_backingStores.value(window));
+ auto *wasmScreen = QWasmScreen::get(window->screen());
+ QWasmCompositor *compositor = wasmScreen->compositor();
+ return new QWasmWindow(window, wasmScreen->deadKeySupport(), compositor,
+ m_backingStores.value(window));
}
QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
@@ -181,21 +206,31 @@ void QWasmIntegration::removeBackingStore(QWindow* window)
m_backingStores.remove(window);
}
+void QWasmIntegration::releaseRequesetUpdateHold()
+{
+ if (QWasmCompositor::releaseRequestUpdateHold())
+ {
+ for (const auto &elementAndScreen : m_screens) {
+ elementAndScreen.wasmScreen->compositor()->requestUpdate();
+ }
+ }
+}
+
#ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- return new QWasmOpenGLContext(context->format());
+ return new QWasmOpenGLContext(context);
}
#endif
void QWasmIntegration::initialize()
{
- if (qgetenv("QT_IM_MODULE").isEmpty() && touchPoints < 1)
+ auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty() && touchPoints < 1)
return;
- QString icStr = QPlatformInputContextFactory::requested();
- if (!icStr.isNull())
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ if (!icStrs.isEmpty())
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
else
m_inputContext.reset(new QWasmInputContext());
}
@@ -225,10 +260,14 @@ QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const
QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
{
- if (hint == ShowIsFullScreen)
+ switch (hint) {
+ case ShowIsFullScreen:
return true;
-
- return QPlatformIntegration::styleHint(hint);
+ case UnderlineShortcut:
+ return platform() != Platform::MacOS;
+ default:
+ return QPlatformIntegration::styleHint(hint);
+ }
}
Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const
@@ -271,38 +310,93 @@ QPlatformAccessibility *QWasmIntegration::accessibility() const
}
#endif
+void QWasmIntegration::setContainerElements(emscripten::val elementArray)
+{
+ const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ QList<ScreenMapping> newScreens;
+
+ QList<QWasmScreen *> screensToDelete;
+ std::transform(m_screens.begin(), m_screens.end(), std::back_inserter(screensToDelete),
+ [](const ScreenMapping &mapping) { return mapping.wasmScreen; });
+
+ for (int i = 0; i < elementArray["length"].as<int>(); ++i) {
+ const auto element = elementArray[i];
+ const auto it = std::find_if(
+ m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
+ QWasmScreen *screen;
+ if (it != m_screens.end()) {
+ screen = it->wasmScreen;
+ screensToDelete.erase(std::remove_if(screensToDelete.begin(), screensToDelete.end(),
+ [screen](const QWasmScreen *removedScreen) {
+ return removedScreen == screen;
+ }),
+ screensToDelete.end());
+ } else {
+ screen = new QWasmScreen(element);
+ QWindowSystemInterface::handleScreenAdded(screen);
+ }
+ newScreens.push_back({element, screen});
+ }
+
+ std::for_each(screensToDelete.begin(), screensToDelete.end(),
+ [](QWasmScreen *removed) { removed->deleteScreen(); });
+
+ m_screens = newScreens;
+ auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
+ QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
+}
-void QWasmIntegration::addScreen(const emscripten::val &element)
+void QWasmIntegration::addContainerElement(emscripten::val element)
{
+ Q_ASSERT_X(m_screens.end()
+ == std::find_if(m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) {
+ return screen.emscriptenVal == element;
+ }),
+ Q_FUNC_INFO, "Double-add of an element");
+
QWasmScreen *screen = new QWasmScreen(element);
- m_screens.append(qMakePair(element, screen));
- m_clipboard->installEventHandlers(element);
QWindowSystemInterface::handleScreenAdded(screen);
+ m_screens.push_back({element, screen});
}
-void QWasmIntegration::removeScreen(const emscripten::val &element)
+void QWasmIntegration::removeContainerElement(emscripten::val element)
{
- auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(element); });
+ const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+
+ const auto it =
+ std::find_if(m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
if (it == m_screens.end()) {
- qWarning() << "Attempting to remove non-existing screen for element"
- << QString::fromJsString(element["id"]);
+ qWarning() << "Attempt to remove a nonexistent screen.";
return;
}
- it->second->deleteScreen();
- m_screens.erase(it);
+
+ QWasmScreen *removedScreen = it->wasmScreen;
+ removedScreen->deleteScreen();
+
+ m_screens.erase(std::remove_if(m_screens.begin(), m_screens.end(),
+ [removedScreen](const ScreenMapping &mapping) {
+ return removedScreen == mapping.wasmScreen;
+ }),
+ m_screens.end());
+ auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
+ QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
}
void QWasmIntegration::resizeScreen(const emscripten::val &element)
{
auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(element); });
+ [&] (const ScreenMapping &candidate) { return candidate.emscriptenVal.equals(element); });
if (it == m_screens.end()) {
qWarning() << "Attempting to resize non-existing screen for element"
- << QString::fromJsString(element["id"]);
+ << QString::fromEcmaString(element["id"]);
return;
}
- it->second->updateQScreenAndCanvasRenderSize();
+ it->wasmScreen->updateQScreenAndCanvasRenderSize();
}
void QWasmIntegration::updateDpi()
@@ -312,13 +406,18 @@ void QWasmIntegration::updateDpi()
return;
qreal dpiValue = dpi.as<qreal>();
for (const auto &elementAndScreen : m_screens)
- QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.second->screen(), dpiValue, dpiValue);
+ QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.wasmScreen->screen(), dpiValue, dpiValue);
}
void QWasmIntegration::resizeAllScreens()
{
for (const auto &elementAndScreen : m_screens)
- elementAndScreen.second->updateQScreenAndCanvasRenderSize();
+ elementAndScreen.wasmScreen->updateQScreenAndCanvasRenderSize();
+}
+
+void QWasmIntegration::loadLocalFontFamilies(emscripten::val families)
+{
+ m_fontDb->populateLocalFontFamilies(families);
}
quint64 QWasmIntegration::getTimestamp()
diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h
index decf25009e..870bd0d16b 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.h
+++ b/src/plugins/platforms/wasm/qwasmintegration.h
@@ -14,7 +14,6 @@
#include <QtCore/qhash.h>
-#include <private/qsimpledrag_p.h>
#include <private/qstdweb_p.h>
#include <emscripten.h>
@@ -33,6 +32,7 @@ class QWasmBackingStore;
class QWasmClipboard;
class QWasmAccessibility;
class QWasmServices;
+class QWasmDrag;
class QWasmIntegration : public QObject, public QPlatformIntegration
{
@@ -70,21 +70,29 @@ public:
QWasmInputContext *getWasmInputContext() { return m_platformInputContext; }
static QWasmIntegration *get() { return s_instance; }
- void addScreen(const emscripten::val &canvas);
- void removeScreen(const emscripten::val &canvas);
+ void setContainerElements(emscripten::val elementArray);
+ void addContainerElement(emscripten::val elementArray);
+ void removeContainerElement(emscripten::val elementArray);
void resizeScreen(const emscripten::val &canvas);
- void resizeAllScreens();
void updateDpi();
+ void resizeAllScreens();
+ void loadLocalFontFamilies(emscripten::val families);
void removeBackingStore(QWindow* window);
+ void releaseRequesetUpdateHold();
static quint64 getTimestamp();
int touchPoints;
private:
+ struct ScreenMapping {
+ emscripten::val emscriptenVal;
+ QWasmScreen *wasmScreen;
+ };
+
mutable QWasmFontDatabase *m_fontDb;
mutable QWasmServices *m_desktopServices;
mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores;
- QList<QPair<emscripten::val, QWasmScreen *>> m_screens;
+ QList<ScreenMapping> m_screens;
mutable QWasmClipboard *m_clipboard;
mutable QWasmAccessibility *m_accessibility;
@@ -95,7 +103,7 @@ private:
mutable QWasmInputContext *m_platformInputContext = nullptr;
#if QT_CONFIG(draganddrop)
- std::unique_ptr<QSimpleDrag> m_drag;
+ std::unique_ptr<QWasmDrag> m_drag;
#endif
};
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
new file mode 100644
index 0000000000..8f5240d2d0
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
@@ -0,0 +1,295 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmkeytranslator.h"
+#include "qwasmevent.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+struct WebKb2QtData
+{
+ static constexpr char StringTerminator = '\0';
+
+ const char *web;
+ unsigned int qt;
+
+ constexpr bool operator<=(const WebKb2QtData &that) const noexcept
+ {
+ return !(strcmp(that) > 0);
+ }
+
+ bool operator<(const WebKb2QtData &that) const noexcept { return ::strcmp(web, that.web) < 0; }
+
+ constexpr bool operator==(const WebKb2QtData &that) const noexcept { return strcmp(that) == 0; }
+
+ constexpr int strcmp(const WebKb2QtData &that, const int i = 0) const
+ {
+ return web[i] == StringTerminator && that.web[i] == StringTerminator ? 0
+ : web[i] == StringTerminator ? -1
+ : that.web[i] == StringTerminator ? 1
+ : web[i] < that.web[i] ? -1
+ : web[i] > that.web[i] ? 1
+ : strcmp(that, i + 1);
+ }
+};
+
+template<unsigned int Qt, char... WebChar>
+struct Web2Qt
+{
+ static constexpr const char storage[sizeof...(WebChar) + 1] = { WebChar..., '\0' };
+ using Type = WebKb2QtData;
+ static constexpr Type data() noexcept { return Type{ storage, Qt }; }
+};
+
+template<unsigned int Qt, char... WebChar>
+constexpr char Web2Qt<Qt, WebChar...>::storage[];
+
+static constexpr const auto WebToQtKeyCodeMappings = qMakeArray(
+ QSortedData<Web2Qt<Qt::Key_Alt, 'A', 'l', 't', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Alt, 'A', 'l', 't'>,
+ Web2Qt<Qt::Key_AltGr, 'A', 'l', 't', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_Apostrophe, 'Q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_Backspace, 'B', 'a', 'c', 'k', 's', 'p', 'a', 'c', 'e'>,
+ Web2Qt<Qt::Key_CapsLock, 'C', 'a', 'p', 's', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Control, 'C', 'o', 'n', 't', 'r', 'o', 'l'>,
+ Web2Qt<Qt::Key_Delete, 'D', 'e', 'l', 'e', 't', 'e'>,
+ Web2Qt<Qt::Key_Down, 'A', 'r', 'r', 'o', 'w', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_Escape, 'E', 's', 'c', 'a', 'p', 'e'>,
+ Web2Qt<Qt::Key_F1, 'F', '1'>, Web2Qt<Qt::Key_F2, 'F', '2'>,
+ Web2Qt<Qt::Key_F11, 'F', '1', '1'>, Web2Qt<Qt::Key_F12, 'F', '1', '2'>,
+ Web2Qt<Qt::Key_F13, 'F', '1', '3'>, Web2Qt<Qt::Key_F14, 'F', '1', '4'>,
+ Web2Qt<Qt::Key_F15, 'F', '1', '5'>, Web2Qt<Qt::Key_F16, 'F', '1', '6'>,
+ Web2Qt<Qt::Key_F17, 'F', '1', '7'>, Web2Qt<Qt::Key_F18, 'F', '1', '8'>,
+ Web2Qt<Qt::Key_F19, 'F', '1', '9'>, Web2Qt<Qt::Key_F20, 'F', '2', '0'>,
+ Web2Qt<Qt::Key_F21, 'F', '2', '1'>, Web2Qt<Qt::Key_F22, 'F', '2', '2'>,
+ Web2Qt<Qt::Key_F23, 'F', '2', '3'>,
+ Web2Qt<Qt::Key_F3, 'F', '3'>, Web2Qt<Qt::Key_F4, 'F', '4'>,
+ Web2Qt<Qt::Key_F5, 'F', '5'>, Web2Qt<Qt::Key_F6, 'F', '6'>,
+ Web2Qt<Qt::Key_F7, 'F', '7'>, Web2Qt<Qt::Key_F8, 'F', '8'>,
+ Web2Qt<Qt::Key_F9, 'F', '9'>, Web2Qt<Qt::Key_F10, 'F', '1', '0'>,
+ Web2Qt<Qt::Key_Help, 'H', 'e', 'l', 'p'>,
+ Web2Qt<Qt::Key_Home, 'H', 'o', 'm', 'e'>, Web2Qt<Qt::Key_End, 'E', 'n', 'd'>,
+ Web2Qt<Qt::Key_Insert, 'I', 'n', 's', 'e', 'r', 't'>,
+ Web2Qt<Qt::Key_Left, 'A', 'r', 'r', 'o', 'w', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Meta, 'M', 'e', 't', 'a'>, Web2Qt<Qt::Key_Meta, 'O', 'S'>,
+ Web2Qt<Qt::Key_Menu, 'C', 'o', 'n', 't', 'e', 'x', 't', 'M', 'e', 'n', 'u'>,
+ Web2Qt<Qt::Key_NumLock, 'N', 'u', 'm', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_PageDown, 'P', 'a', 'g', 'e', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_PageUp, 'P', 'a', 'g', 'e', 'U', 'p'>,
+ Web2Qt<Qt::Key_Paste, 'P', 'a', 's', 't', 'e'>,
+ Web2Qt<Qt::Key_Pause, 'C', 'l', 'e', 'a', 'r'>,
+ Web2Qt<Qt::Key_Pause, 'P', 'a', 'u', 's', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'B', 'a', 'c', 'k', 'q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'I', 'n', 't', 'l', 'B', 'a', 'c', 'k', 's', 'l', 'a', 's', 'h'>,
+ Web2Qt<Qt::Key_Return, 'E', 'n', 't', 'e', 'r'>,
+ Web2Qt<Qt::Key_Right, 'A', 'r', 'r', 'o', 'w', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_ScrollLock, 'S', 'c', 'r', 'o', 'l', 'l', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Shift, 'S', 'h', 'i', 'f', 't'>,
+ Web2Qt<Qt::Key_Tab, 'T', 'a', 'b'>,
+ Web2Qt<Qt::Key_Up, 'A', 'r', 'r', 'o', 'w', 'U', 'p'>,
+ Web2Qt<Qt::Key_yen, 'I', 'n', 't', 'l', 'Y', 'e', 'n'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextLowercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\xa1'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\xa2'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\xa4'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\xa6'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\xa0'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\xa5'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\xa3'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\xa7'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\xa9'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\xaa'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\xab'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\xa8'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\xad'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\xae'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\xaf'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\xac'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\xb1'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\xb3'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\xb4'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\xb6'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\xb2'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\xb8'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\xb5'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\xba'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\xbb'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\xbc'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\xb9'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\xbd'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc3', '\xbf'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextUppercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\x81'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\x82'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\x84'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\x86'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\x80'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\x85'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\x83'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\x87'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\x89'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\x8a'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\x8b'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\x88'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\x8d'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\x8e'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\x8f'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\x8c'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\x91'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\x93'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\x94'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\x96'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\x92'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\x98'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\x95'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\x9a'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\x9b'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\x9c'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\x99'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\x9d'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc5', '\xb8'>>::Data{});
+
+static_assert(DiacriticalCharsKeyToTextLowercase.size()
+ == DiacriticalCharsKeyToTextUppercase.size(),
+ "Add the new key to both arrays");
+
+struct KeyMapping
+{
+ Qt::Key from, to;
+};
+
+constexpr KeyMapping tildeKeyTable[] = {
+ // ~
+ { Qt::Key_A, Qt::Key_Atilde },
+ { Qt::Key_N, Qt::Key_Ntilde },
+ { Qt::Key_O, Qt::Key_Otilde },
+};
+constexpr KeyMapping graveKeyTable[] = {
+ // `
+ { Qt::Key_A, Qt::Key_Agrave }, { Qt::Key_E, Qt::Key_Egrave }, { Qt::Key_I, Qt::Key_Igrave },
+ { Qt::Key_O, Qt::Key_Ograve }, { Qt::Key_U, Qt::Key_Ugrave },
+};
+constexpr KeyMapping acuteKeyTable[] = {
+ // '
+ { Qt::Key_A, Qt::Key_Aacute }, { Qt::Key_E, Qt::Key_Eacute }, { Qt::Key_I, Qt::Key_Iacute },
+ { Qt::Key_O, Qt::Key_Oacute }, { Qt::Key_U, Qt::Key_Uacute }, { Qt::Key_Y, Qt::Key_Yacute },
+};
+constexpr KeyMapping diaeresisKeyTable[] = {
+ // umlaut ¨
+ { Qt::Key_A, Qt::Key_Adiaeresis }, { Qt::Key_E, Qt::Key_Ediaeresis },
+ { Qt::Key_I, Qt::Key_Idiaeresis }, { Qt::Key_O, Qt::Key_Odiaeresis },
+ { Qt::Key_U, Qt::Key_Udiaeresis }, { Qt::Key_Y, Qt::Key_ydiaeresis },
+};
+constexpr KeyMapping circumflexKeyTable[] = {
+ // ^
+ { Qt::Key_A, Qt::Key_Acircumflex }, { Qt::Key_E, Qt::Key_Ecircumflex },
+ { Qt::Key_I, Qt::Key_Icircumflex }, { Qt::Key_O, Qt::Key_Ocircumflex },
+ { Qt::Key_U, Qt::Key_Ucircumflex },
+};
+
+static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
+{
+ while (first != last) {
+ if (first->from == key)
+ return first->to;
+ ++first;
+ }
+ return Qt::Key_unknown;
+}
+
+template<size_t N>
+static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
+{
+ return find_impl(map, map + N, key);
+}
+
+Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey)
+{
+ switch (deadKey) {
+ case Qt::Key_Dead_Grave:
+ return find(graveKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Acute:
+ return find(acuteKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Tilde:
+ return find(tildeKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Diaeresis:
+ return find(diaeresisKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Circumflex:
+ return find(circumflexKeyTable, accentBaseKey);
+ default:
+ return Qt::Key_unknown;
+ };
+}
+
+template<class T>
+std::optional<QString> findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey)
+{
+ const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(),
+ [qtKey](const WebKb2QtData &data) { return data.qt == qtKey; });
+ return it != mappingArray.cend() ? it->web : std::optional<QString>();
+}
+} // namespace
+
+std::optional<Qt::Key> QWasmKeyTranslator::mapWebKeyTextToQtKey(const char *toFind)
+{
+ const WebKb2QtData searchKey{ toFind, 0 };
+ const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(),
+ searchKey);
+ return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast<Qt::Key>(it->qt)
+ : std::optional<Qt::Key>();
+}
+
+QWasmDeadKeySupport::QWasmDeadKeySupport() = default;
+
+QWasmDeadKeySupport::~QWasmDeadKeySupport() = default;
+
+void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event)
+{
+ if (event->deadKey) {
+ m_activeDeadKey = event->key;
+ } else if (m_activeDeadKey != Qt::Key_unknown
+ && (((m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
+ && event->type == EventType::KeyDown))
+ || (m_keyModifiedByDeadKeyOnPress == event->key
+ && event->type == EventType::KeyUp))) {
+ const Qt::Key baseKey = event->key;
+ const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_activeDeadKey);
+ if (translatedKey != Qt::Key_unknown) {
+ event->key = translatedKey;
+
+ auto foundText = event->modifiers.testFlag(Qt::ShiftModifier)
+ ? findKeyTextByKeyId(DiacriticalCharsKeyToTextUppercase, event->key)
+ : findKeyTextByKeyId(DiacriticalCharsKeyToTextLowercase, event->key);
+ Q_ASSERT(foundText.has_value());
+ event->text = foundText->size() == 1 ? *foundText : QString();
+ }
+
+ if (!event->text.isEmpty()) {
+ if (event->type == EventType::KeyDown) {
+ // Assume the first keypress with an active dead key is treated as modified,
+ // regardless of whether it has actually been modified or not. Take into account
+ // only events that produce actual key text.
+ if (!event->text.isEmpty())
+ m_keyModifiedByDeadKeyOnPress = baseKey;
+ } else {
+ Q_ASSERT(event->type == EventType::KeyUp);
+ Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
+ m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+ m_activeDeadKey = Qt::Key_unknown;
+ }
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.h b/src/plugins/platforms/wasm/qwasmkeytranslator.h
new file mode 100644
index 0000000000..11a89e6193
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMKEYTRANSLATOR_H
+#define QWASMKEYTRANSLATOR_H
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/qtypes.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+struct KeyEvent;
+
+namespace QWasmKeyTranslator {
+std::optional<Qt::Key> mapWebKeyTextToQtKey(const char *toFind);
+}
+
+class QWasmDeadKeySupport
+{
+public:
+ explicit QWasmDeadKeySupport();
+ ~QWasmDeadKeySupport();
+
+ void applyDeadKeyTranslations(KeyEvent *event);
+
+private:
+ Qt::Key m_activeDeadKey = Qt::Key_unknown;
+ Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMKEYTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
index 31adc73cf5..dcfc4433e6 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
@@ -6,10 +6,30 @@
QT_BEGIN_NAMESPACE
QWasmOffscreenSurface::QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface)
- :QPlatformOffscreenSurface(offscreenSurface)
+ : QPlatformOffscreenSurface(offscreenSurface), m_offscreenCanvas(emscripten::val::undefined())
{
+ const auto offscreenCanvasClass = emscripten::val::global("OffscreenCanvas");
+ // The OffscreenCanvas is not supported on some browsers, most notably on Safari.
+ if (!offscreenCanvasClass)
+ return;
+
+ m_offscreenCanvas = offscreenCanvasClass.new_(offscreenSurface->size().width(),
+ offscreenSurface->size().height());
+
+ m_specialTargetId = std::string("!qtoffscreen_") + std::to_string(uintptr_t(this));
+
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(m_specialTargetId, m_offscreenCanvas);
+}
+
+QWasmOffscreenSurface::~QWasmOffscreenSurface()
+{
+ emscripten::val::module_property("specialHTMLTargets").delete_(m_specialTargetId);
}
-QWasmOffscreenSurface::~QWasmOffscreenSurface() = default;
+bool QWasmOffscreenSurface::isValid() const
+{
+ return !m_offscreenCanvas.isNull() && !m_offscreenCanvas.isUndefined();
+}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.h b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
index 88a64b775a..1c71310448 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.h
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
@@ -6,6 +6,10 @@
#include <qpa/qplatformoffscreensurface.h>
+#include <emscripten/val.h>
+
+#include <string>
+
QT_BEGIN_NAMESPACE
class QOffscreenSurface;
@@ -14,8 +18,13 @@ class QWasmOffscreenSurface final : public QPlatformOffscreenSurface
public:
explicit QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface);
~QWasmOffscreenSurface() final;
-private:
+ const std::string &id() const { return m_specialTargetId; }
+ bool isValid() const override;
+
+private:
+ std::string m_specialTargetId;
+ emscripten::val m_offscreenCanvas;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
index 3dfb201367..8a4664ec8c 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmopenglcontext.h"
+
+#include "qwasmoffscreensurface.h"
#include "qwasmintegration.h"
#include <EGL/egl.h>
#include <emscripten/bind.h>
@@ -18,35 +20,25 @@ EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
QT_BEGIN_NAMESPACE
-QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format)
- : m_requestedFormat(format)
+QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
+ : m_actualFormat(context->format()), m_qGlContext(context)
{
- m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES);
+ m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
// if we set one, we need to set the other as well since in webgl, these are tied together
- if (format.depthBufferSize() < 0 && format.stencilBufferSize() > 0)
- m_requestedFormat.setDepthBufferSize(16);
-
- if (format.stencilBufferSize() < 0 && format.depthBufferSize() > 0)
- m_requestedFormat.setStencilBufferSize(8);
+ if (m_actualFormat.depthBufferSize() < 0 && m_actualFormat.stencilBufferSize() > 0)
+ m_actualFormat.setDepthBufferSize(16);
+ if (m_actualFormat.stencilBufferSize() < 0 && m_actualFormat.depthBufferSize() > 0)
+ m_actualFormat.setStencilBufferSize(8);
}
QWasmOpenGLContext::~QWasmOpenGLContext()
{
- if (!m_context)
- return;
-
// Destroy GL context. Work around bug in emscripten_webgl_destroy_context
// which removes all event handlers on the canvas by temporarily replacing the function
// that does the removal with a function that does nothing.
- emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
- emscripten::val savedRemoveAllHandlersOnTargetFunction =
- jsEvents["removeAllHandlersOnTarget"];
- jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
- emscripten_webgl_destroy_context(m_context);
- jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
- m_context = 0;
+ destroyWebGLContext(m_ownedWebGLContext.handle);
}
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
@@ -59,20 +51,61 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
(format.majorVersion() == 3 && format.minorVersion() == 0));
}
-bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
{
- if (m_context && m_surface == surface)
- return true;
-
- // TODO(mikolajboc): Use OffscreenCanvas if available.
- if (surface->surface()->surfaceClass() == QSurface::Offscreen)
- return false;
-
- m_surface = surface;
+ if (m_ownedWebGLContext.surface == surface)
+ return m_ownedWebGLContext.handle;
+
+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
+ // Reuse the existing context for offscreen drawing, even if it happens to be a canvas
+ // context. This is because it is impossible to re-home an existing context to the
+ // new surface and works as an emulation measure.
+ if (m_ownedWebGLContext.handle)
+ return m_ownedWebGLContext.handle;
+
+ // The non-shared offscreen context is heavily limited on WASM, but we provide it
+ // anyway for potential pixel readbacks.
+ m_ownedWebGLContext =
+ QOpenGLContextData{ .surface = surface,
+ .handle = createEmscriptenContext(
+ static_cast<QWasmOffscreenSurface *>(surface)->id(),
+ m_actualFormat) };
+ } else {
+ destroyWebGLContext(m_ownedWebGLContext.handle);
+
+ // Create a full on-screen context for the window canvas.
+ m_ownedWebGLContext = QOpenGLContextData{
+ .surface = surface,
+ .handle = createEmscriptenContext(static_cast<QWasmWindow *>(surface)->canvasSelector(),
+ m_actualFormat)
+ };
+ }
+
+ EmscriptenWebGLContextAttributes actualAttributes;
+
+ EMSCRIPTEN_RESULT attributesResult = emscripten_webgl_get_context_attributes(m_ownedWebGLContext.handle, &actualAttributes);
+ if (attributesResult == EMSCRIPTEN_RESULT_SUCCESS) {
+ if (actualAttributes.majorVersion == 1) {
+ m_actualFormat.setMajorVersion(2);
+ } else if (actualAttributes.majorVersion == 2) {
+ m_actualFormat.setMajorVersion(3);
+ }
+ m_actualFormat.setMinorVersion(0);
+ }
+
+ return m_ownedWebGLContext.handle;
+}
- auto *window = static_cast<QWasmWindow *>(surface);
- m_context = createEmscriptenContext(window->canvasSelector(), m_requestedFormat);
- return true;
+void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
+{
+ if (!contextHandle)
+ return;
+ emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
+ emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
+ jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
+ emscripten_webgl_destroy_context(contextHandle);
+ jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
}
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
@@ -86,9 +119,8 @@ QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
attributes.failIfMajorPerformanceCaveat = false;
attributes.antialias = true;
attributes.enableExtensionsByDefault = true;
- attributes.majorVersion = format.majorVersion() - 1;
- attributes.minorVersion = format.minorVersion();
-
+ attributes.majorVersion = 2; // try highest supported version ES3.0 / WebGL 2.0
+ attributes.minorVersion = 0; // emscripten only supports minor version 0
// WebGL doesn't allow separate attach buffers to STENCIL_ATTACHMENT and DEPTH_ATTACHMENT
// we need both or none
const bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0);
@@ -97,13 +129,20 @@ QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
attributes.alpha = format.alphaBufferSize() > 0;
attributes.depth = useDepthStencil;
attributes.stencil = useDepthStencil;
-
- return emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
+ EMSCRIPTEN_RESULT contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
+
+ if (contextResult <= 0) {
+ // fallback to opengles2/webgl1
+ // for devices that do not support opengles3/webgl2
+ attributes.majorVersion = 1;
+ contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
+ }
+ return contextResult;
}
QSurfaceFormat QWasmOpenGLContext::format() const
{
- return m_requestedFormat;
+ return m_actualFormat;
}
GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const
@@ -113,10 +152,22 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
{
- if (!maybeCreateEmscriptenContext(surface))
+ static bool sentSharingWarning = false;
+ if (!sentSharingWarning && isSharing()) {
+ qWarning() << "The functionality for sharing OpenGL contexts is limited, see documentation";
+ sentSharingWarning = true;
+ }
+
+ if (auto *shareContext = m_qGlContext->shareContext())
+ return shareContext->makeCurrent(surface->surface());
+
+ const auto context = obtainEmscriptenContext(surface);
+ if (!context)
return false;
- return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS;
+ m_usedWebGLContextHandle = context;
+
+ return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
}
void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
@@ -132,17 +183,17 @@ void QWasmOpenGLContext::doneCurrent()
bool QWasmOpenGLContext::isSharing() const
{
- return false;
+ return m_qGlContext->shareContext();
}
bool QWasmOpenGLContext::isValid() const
{
- if (!isOpenGLVersionSupported(m_requestedFormat))
+ if (!isOpenGLVersionSupported(m_actualFormat))
return false;
// Note: we get isValid() calls before we see the surface and can
// create a native context, so no context is also a valid state.
- return !m_context || !emscripten_is_webgl_context_lost(m_context);
+ return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
}
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h
index ac456d90e4..2a8bcc5d9b 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.h
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h
@@ -1,6 +1,9 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef QWASMOPENGLCONTEXT_H
+#define QWASMOPENGLCONTEXT_H
+
#include <qpa/qplatformopenglcontext.h>
#include <emscripten.h>
@@ -8,11 +11,13 @@
QT_BEGIN_NAMESPACE
+class QOpenGLContext;
class QPlatformScreen;
+class QPlatformSurface;
class QWasmOpenGLContext : public QPlatformOpenGLContext
{
public:
- QWasmOpenGLContext(const QSurfaceFormat &format);
+ explicit QWasmOpenGLContext(QOpenGLContext *context);
~QWasmOpenGLContext();
QSurfaceFormat format() const override;
@@ -25,15 +30,25 @@ public:
QFunctionPointer getProcAddress(const char *procName) override;
private:
+ struct QOpenGLContextData
+ {
+ QPlatformSurface *surface = nullptr;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle = 0;
+ };
+
static bool isOpenGLVersionSupported(QSurfaceFormat format);
- bool maybeCreateEmscriptenContext(QPlatformSurface *surface);
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE obtainEmscriptenContext(QPlatformSurface *surface);
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);
- QSurfaceFormat m_requestedFormat;
- QPlatformSurface *m_surface = nullptr;
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0;
+ static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);
+
+ QSurfaceFormat m_actualFormat;
+ QOpenGLContext *m_qGlContext;
+ QOpenGLContextData m_ownedWebGLContext;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_usedWebGLContextHandle = 0;
};
QT_END_NAMESPACE
+#endif // QWASMOPENGLCONTEXT_H
diff --git a/src/plugins/platforms/wasm/qwasmplatform.cpp b/src/plugins/platforms/wasm/qwasmplatform.cpp
index c641e345e4..e54992be1d 100644
--- a/src/plugins/platforms/wasm/qwasmplatform.cpp
+++ b/src/plugins/platforms/wasm/qwasmplatform.cpp
@@ -13,8 +13,9 @@ Platform platform()
if (rawPlatform.call<bool>("includes", emscripten::val("Mac")))
return Platform::MacOS;
- if (rawPlatform.call<bool>("includes", emscripten::val("iPhone")))
- return Platform::iPhone;
+ if (rawPlatform.call<bool>("includes", emscripten::val("iPhone"))
+ || rawPlatform.call<bool>("includes", emscripten::val("iPad")))
+ return Platform::iOS;
if (rawPlatform.call<bool>("includes", emscripten::val("Win32")))
return Platform::Windows;
if (rawPlatform.call<bool>("includes", emscripten::val("Linux"))) {
diff --git a/src/plugins/platforms/wasm/qwasmplatform.h b/src/plugins/platforms/wasm/qwasmplatform.h
index 239efdeae9..5b32e43633 100644
--- a/src/plugins/platforms/wasm/qwasmplatform.h
+++ b/src/plugins/platforms/wasm/qwasmplatform.h
@@ -19,7 +19,7 @@ enum class Platform {
Windows,
Linux,
Android,
- iPhone,
+ iOS
};
Platform platform();
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index fd3d83b614..ddf8140c48 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -2,11 +2,12 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmscreen.h"
-#include "qwasmwindow.h"
-#include "qwasmeventtranslator.h"
+
#include "qwasmcompositor.h"
-#include "qwasmintegration.h"
#include "qwasmcssstyle.h"
+#include "qwasmintegration.h"
+#include "qwasmkeytranslator.h"
+#include "qwasmwindow.h"
#include <emscripten/bind.h>
#include <emscripten/val.h>
@@ -27,9 +28,10 @@ const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName =
QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
: m_container(containerOrCanvas),
+ m_intermediateContainer(emscripten::val::undefined()),
m_shadowContainer(emscripten::val::undefined()),
m_compositor(new QWasmCompositor(this)),
- m_eventTranslator(new QWasmEventTranslator())
+ m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
{
auto document = m_container["ownerDocument"];
// Each screen is represented by a div container. All of the windows exist therein as
@@ -42,9 +44,20 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
m_container["parentNode"].call<void>("replaceChild", container, m_container);
m_container = container;
}
+
+ // Create an intermediate container which we can remove during cleanup in ~QWasmScreen().
+ // This is required due to the attachShadow() call below; there is no corresponding
+ // "detachShadow()" API to return the container to its previous state.
+ m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
+ m_intermediateContainer.set("id", std::string("qt-shadow-container"));
+ emscripten::val intermediateContainerStyle = m_intermediateContainer["style"];
+ intermediateContainerStyle.set("width", std::string("100%"));
+ intermediateContainerStyle.set("height", std::string("100%"));
+ m_container.call<void>("appendChild", m_intermediateContainer);
+
auto shadowOptions = emscripten::val::object();
shadowOptions.set("mode", "open");
- auto shadow = m_container.call<emscripten::val>("attachShadow", shadowOptions);
+ auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions);
m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
@@ -56,22 +69,11 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
m_shadowContainer["classList"].call<void>("add", std::string("qt-screen"));
- // Set contenteditable so that the canvas gets clipboard events,
- // then hide the resulting focus frame, and reset the cursor.
- m_shadowContainer.set("contentEditable", std::string("true"));
- // set inputmode to none to stop mobile keyboard opening
- // when user clicks anywhere on the canvas.
- m_shadowContainer.set("inputmode", std::string("none"));
-
- // Hide the canvas from screen readers.
- m_shadowContainer.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
-
// Disable the default context menu; Qt applications typically
// provide custom right-click behavior.
m_onContextMenu = std::make_unique<qstdweb::EventCallback>(
m_shadowContainer, "contextmenu",
[](emscripten::val event) { event.call<void>("preventDefault"); });
-
// Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
// on its id only under some conditions, like the target being embedded in a shadow DOM or a
// subframe.
@@ -81,16 +83,33 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
emscripten::val::module_property("specialHTMLTargets")
.set(outerScreenId().toStdString(), m_container);
- // Install event handlers on the container/canvas. This must be
- // done after the canvas has been created above.
- m_compositor->initEventHandlers();
-
updateQScreenAndCanvasRenderSize();
m_shadowContainer.call<void>("focus");
+
+ m_touchDevice = std::make_unique<QPointingDevice>(
+ "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
+ QPointingDevice::PointerType::Finger,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
+ | QPointingDevice::Capability::NormalizedPosition,
+ 10, 0);
+ m_tabletDevice = std::make_unique<QPointingDevice>(
+ "stylus", 2, QInputDevice::DeviceType::Stylus,
+ QPointingDevice::PointerType::Pen,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure
+ | QPointingDevice::Capability::NormalizedPosition
+ | QInputDevice::Capability::MouseEmulation
+ | QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation
+ | QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt
+ | QInputDevice::Capability::TangentialPressure,
+ 0, 0);
+
+ QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
}
QWasmScreen::~QWasmScreen()
{
+ m_intermediateContainer.call<void>("remove");
+
emscripten::val::module_property("specialHTMLTargets")
.set(eventTargetId().toStdString(), emscripten::val::undefined());
@@ -100,7 +119,6 @@ QWasmScreen::~QWasmScreen()
void QWasmScreen::deleteScreen()
{
- m_compositor->onScreenDeleting();
// Deletes |this|!
QWindowSystemInterface::handleScreenRemoved(this);
}
@@ -122,11 +140,6 @@ QWasmCompositor *QWasmScreen::compositor()
return m_compositor.get();
}
-QWasmEventTranslator *QWasmScreen::eventTranslator()
-{
- return m_eventTranslator.get();
-}
-
emscripten::val QWasmScreen::element() const
{
return m_shadowContainer;
@@ -193,7 +206,7 @@ qreal QWasmScreen::devicePixelRatio() const
QString QWasmScreen::name() const
{
- return QString::fromJsString(m_shadowContainer["id"]);
+ return QString::fromEcmaString(m_shadowContainer["id"]);
}
QPlatformCursor *QWasmScreen::cursor() const
@@ -210,23 +223,30 @@ void QWasmScreen::resizeMaximizedWindows()
QWindow *QWasmScreen::topWindow() const
{
- return m_compositor->keyWindow();
+ return activeChild() ? activeChild()->window() : nullptr;
}
QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
{
- return m_compositor->windowAt(p);
+ const auto found =
+ std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
+ const QRect geometry = window->windowFrameGeometry();
+
+ return window->isVisible() && geometry.contains(p);
+ });
+ return found != childStack().end() ? (*found)->window() : nullptr;
}
-QPoint QWasmScreen::mapFromLocal(const QPoint &p) const
+QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
{
return geometry().topLeft() + p;
}
-QPoint QWasmScreen::clipPoint(const QPoint &p) const
+QPointF QWasmScreen::clipPoint(const QPointF &p) const
{
- return QPoint(qBound(screen()->geometry().left(), p.x(), screen()->geometry().right()),
- qBound(screen()->geometry().top(), p.y(), screen()->geometry().bottom()));
+ const auto geometryF = screen()->geometry().toRectF();
+ return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()),
+ qBound(geometryF.top(), p.y(), geometryF.bottom()));
}
void QWasmScreen::invalidateSize()
@@ -242,6 +262,18 @@ void QWasmScreen::setGeometry(const QRect &rect)
resizeMaximizedWindows();
}
+void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ Q_UNUSED(parent);
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && childStack().size() == 1) {
+ child->window()->setFlag(Qt::WindowStaysOnBottomHint);
+ }
+ QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child);
+ m_compositor->onWindowTreeChanged(changeType, child);
+}
+
void QWasmScreen::updateQScreenAndCanvasRenderSize()
{
// The HTML canvas has two sizes: the CSS size and the canvas render size.
@@ -270,7 +302,6 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize()
};
setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
- m_compositor->requestUpdateAllWindows();
}
void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val)
@@ -316,4 +347,27 @@ void QWasmScreen::installCanvasResizeObserver()
resizeObserver.call<void>("observe", m_shadowContainer);
}
+emscripten::val QWasmScreen::containerElement()
+{
+ return m_shadowContainer;
+}
+
+QWasmWindowTreeNode *QWasmScreen::parentNode()
+{
+ return nullptr;
+}
+
+QList<QWasmWindow *> QWasmScreen::allWindows()
+{
+ QList<QWasmWindow *> windows;
+ for (auto *child : childStack()) {
+ QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively);
+ std::transform(
+ list.begin(), list.end(), std::back_inserter(windows),
+ [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); });
+ windows.push_back(child);
+ }
+ return windows;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h
index a87fa9156d..da171d3f50 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.h
+++ b/src/plugins/platforms/wasm/qwasmscreen.h
@@ -6,6 +6,8 @@
#include "qwasmcursor.h"
+#include "qwasmwindowtreenode.h"
+
#include <qpa/qplatformscreen.h>
#include <QtCore/qscopedpointer.h>
@@ -20,10 +22,10 @@ class QPlatformOpenGLContext;
class QWasmWindow;
class QWasmBackingStore;
class QWasmCompositor;
-class QWasmEventTranslator;
+class QWasmDeadKeySupport;
class QOpenGLContext;
-class QWasmScreen : public QObject, public QPlatformScreen
+class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode
{
Q_OBJECT
public:
@@ -36,9 +38,13 @@ public:
emscripten::val element() const;
QString eventTargetId() const;
QString outerScreenId() const;
+ QPointingDevice *touchDevice() { return m_touchDevice.get(); }
+ QPointingDevice *tabletDevice() { return m_tabletDevice.get(); }
QWasmCompositor *compositor();
- QWasmEventTranslator *eventTranslator();
+ QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); }
+
+ QList<QWasmWindow *> allWindows();
QRect geometry() const override;
int depth() const override;
@@ -52,8 +58,12 @@ public:
QWindow *topWindow() const;
QWindow *topLevelAt(const QPoint &p) const override;
- QPoint mapFromLocal(const QPoint &p) const;
- QPoint clipPoint(const QPoint &p) const;
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
+ QPointF mapFromLocal(const QPointF &p) const;
+ QPointF clipPoint(const QPointF &p) const;
void invalidateSize();
void updateQScreenAndCanvasRenderSize();
@@ -64,10 +74,17 @@ public slots:
void setGeometry(const QRect &rect);
private:
+ // QWasmWindowTreeNode:
+ void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) final;
+
emscripten::val m_container;
+ emscripten::val m_intermediateContainer;
emscripten::val m_shadowContainer;
std::unique_ptr<QWasmCompositor> m_compositor;
- std::unique_ptr<QWasmEventTranslator> m_eventTranslator;
+ std::unique_ptr<QPointingDevice> m_touchDevice;
+ std::unique_ptr<QPointingDevice> m_tabletDevice;
+ std::unique_ptr<QWasmDeadKeySupport> m_deadKeySupport;
QRect m_geometry = QRect(0, 0, 100, 100);
int m_depth = 32;
QImage::Format m_format = QImage::Format_RGB32;
diff --git a/src/plugins/platforms/wasm/qwasmservices.cpp b/src/plugins/platforms/wasm/qwasmservices.cpp
index f5fd4e4790..e767295e41 100644
--- a/src/plugins/platforms/wasm/qwasmservices.cpp
+++ b/src/plugins/platforms/wasm/qwasmservices.cpp
@@ -12,7 +12,7 @@ QT_BEGIN_NAMESPACE
bool QWasmServices::openUrl(const QUrl &url)
{
- emscripten::val::global("window").call<void>("open", url.toString().toJsString(),
+ emscripten::val::global("window").call<void>("open", url.toString().toEcmaString(),
emscripten::val("_blank"));
return true;
}
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index be0dd74797..b8197c5113 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -5,12 +5,16 @@
#include <private/qguiapplication_p.h>
#include <QtCore/qfile.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <private/qpixmapcache_p.h>
#include <QtGui/qopenglfunctions.h>
#include <QBuffer>
#include "qwasmbase64iconstore.h"
#include "qwasmdom.h"
+#include "qwasmclipboard.h"
+#include "qwasmintegration.h"
+#include "qwasmkeytranslator.h"
#include "qwasmwindow.h"
#include "qwasmwindowclientarea.h"
#include "qwasmscreen.h"
@@ -18,23 +22,37 @@
#include "qwasmevent.h"
#include "qwasmeventdispatcher.h"
#include "qwasmaccessibility.h"
+#include "qwasmclipboard.h"
#include <iostream>
-#include <emscripten/val.h>
+#include <sstream>
-#include <GL/gl.h>
+#include <emscripten/val.h>
#include <QtCore/private/qstdweb_p.h>
QT_BEGIN_NAMESPACE
+namespace {
+QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint))
+ return QWasmWindowStack::PositionPreference::StayOnTop;
+ if (flags.testFlag(Qt::WindowStaysOnBottomHint))
+ return QWasmWindowStack::PositionPreference::StayOnBottom;
+ return QWasmWindowStack::PositionPreference::Regular;
+}
+} // namespace
+
Q_GUI_EXPORT int qt_defaultDpiX();
-QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore)
+QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
+ QWasmCompositor *compositor, QWasmBackingStore *backingStore)
: QPlatformWindow(w),
m_window(w),
m_compositor(compositor),
m_backingStore(backingStore),
+ m_deadKeySupport(deadKeySupport),
m_document(dom::document()),
m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
@@ -48,12 +66,27 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
m_nonClientArea = std::make_unique<NonClientArea>(this, m_qtWindow);
m_nonClientArea->titleBar()->setTitle(window()->title());
- m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_canvas);
+ m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_windowContents);
+ m_windowContents.set("className", "qt-window-contents");
m_qtWindow.call<void>("appendChild", m_windowContents);
m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content"));
+ // Set contenteditable so that the canvas gets clipboard events,
+ // then hide the resulting focus frame.
+ m_canvas.set("contentEditable", std::string("true"));
+ m_canvas["style"].set("outline", std::string("none"));
+
+ QWasmClipboard::installEventHandlers(m_canvas);
+
+ // set inputMode to none to stop mobile keyboard opening
+ // when user clicks anywhere on the canvas.
+ m_canvas.set("inputMode", std::string("none"));
+
+ // Hide the canvas from screen readers.
+ m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
+
m_windowContents.call<void>("appendChild", m_canvasContainer);
m_canvasContainer["classList"].call<void>("add", emscripten::val("qt-window-canvas-container"));
@@ -62,8 +95,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
m_canvasContainer.call<void>("appendChild", m_a11yContainer);
m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container"));
- compositor->screen()->element().call<void>("appendChild", m_qtWindow);
-
const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface;
if (rendersTo2dContext)
m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d"));
@@ -72,39 +103,69 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId));
emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas);
- m_compositor->addWindow(this);
+ m_flags = window()->flags();
- const auto callback = std::function([this](emscripten::val event) {
+ const auto pointerCallback = std::function([this](emscripten::val event) {
if (processPointer(*PointerEvent::fromWeb(event)))
event.call<void>("preventDefault");
});
m_pointerEnterCallback =
- std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", callback);
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback);
m_pointerLeaveCallback =
- std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", callback);
-
- m_dropCallback = std::make_unique<qstdweb::EventCallback>(
- m_qtWindow, "drop", [this](emscripten::val event) {
- if (processDrop(*DragEvent::fromWeb(event)))
- event.call<void>("preventDefault");
- });
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback);
m_wheelEventCallback = std::make_unique<qstdweb::EventCallback>(
m_qtWindow, "wheel", [this](emscripten::val event) {
if (processWheel(*WheelEvent::fromWeb(event)))
event.call<void>("preventDefault");
});
+
+ const auto keyCallback = std::function([this](emscripten::val event) {
+ if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+
+ emscripten::val keyFocusWindow;
+ if (QWasmIntegration::get()->inputContext()) {
+ QWasmInputContext *wasmContext =
+ static_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext());
+ // if there is an touchscreen input context,
+ // use that window for key input
+ keyFocusWindow = wasmContext->m_inputElement;
+ } else {
+ keyFocusWindow = m_qtWindow;
+ }
+
+ m_keyDownCallback =
+ std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback);
+ m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback);
+
+ setParent(parent());
}
QWasmWindow::~QWasmWindow()
{
emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector());
- destroy();
- m_compositor->removeWindow(this);
+ m_canvasContainer.call<void>("removeChild", m_canvas);
+ m_context2d = emscripten::val::undefined();
+ commitParent(nullptr);
if (m_requestAnimationFrameId > -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
+#if QT_CONFIG(accessibility)
QWasmAccessibility::removeAccessibilityEnableButton(window());
+#endif
+}
+
+QSurfaceFormat QWasmWindow::format() const
+{
+ return window()->requestedFormat();
+}
+
+QWasmWindow *QWasmWindow::fromWindow(QWindow *window)
+{
+ return static_cast<QWasmWindow *>(window->handle());
}
void QWasmWindow::onRestoreClicked()
@@ -130,70 +191,42 @@ void QWasmWindow::onCloseClicked()
void QWasmWindow::onNonClientAreaInteraction()
{
- if (!isActive())
- requestActivateWindow();
+ requestActivateWindow();
+ QGuiApplicationPrivate::instance()->closeAllPopups();
}
bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
{
- QPoint pointInScreen = platformScreen()->mapFromLocal(
- dom::mapPoint(event.target, platformScreen()->element(), event.localPoint));
+ QPointF pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
return QWindowSystemInterface::handleMouseEvent(
window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
- pointInScreen, event.mouseButtons, event.mouseButton, ([event]() {
- switch (event.type) {
- case EventType::PointerDown:
- return QEvent::NonClientAreaMouseButtonPress;
- case EventType::PointerUp:
- return QEvent::NonClientAreaMouseButtonRelease;
- case EventType::PointerMove:
- return QEvent::NonClientAreaMouseMove;
- default:
- Q_ASSERT(false); // notreached
- return QEvent::None;
- }
- })(),
+ pointInScreen, event.mouseButtons, event.mouseButton,
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::NonClient),
event.modifiers);
}
-void QWasmWindow::destroy()
-{
- m_qtWindow["parentElement"].call<emscripten::val>("removeChild", m_qtWindow);
-
- m_canvasContainer.call<void>("removeChild", m_canvas);
- m_context2d = emscripten::val::undefined();
-}
-
void QWasmWindow::initialize()
{
- QRect rect = windowGeometry();
-
- constexpr int minSizeBoundForDialogsAndRegularWindows = 100;
- const int windowType = window()->flags() & Qt::WindowType_Mask;
- const int systemMinSizeLowerBound = windowType == Qt::Window || windowType == Qt::Dialog
- ? minSizeBoundForDialogsAndRegularWindows
- : 0;
-
- const QSize minimumSize(std::max(windowMinimumSize().width(), systemMinSizeLowerBound),
- std::max(windowMinimumSize().height(), systemMinSizeLowerBound));
- const QSize maximumSize = windowMaximumSize();
- const QSize targetSize = !rect.isEmpty() ? rect.size() : minimumSize;
-
- rect.setWidth(qBound(minimumSize.width(), targetSize.width(), maximumSize.width()));
- rect.setHeight(qBound(minimumSize.width(), targetSize.height(), maximumSize.height()));
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowSize, defaultWindowSize);
+ m_normalGeometry = initialGeometry;
setWindowState(window()->windowStates());
setWindowFlags(window()->flags());
setWindowTitle(window()->title());
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
if (window()->isTopLevel())
setWindowIcon(window()->icon());
- m_normalGeometry = rect;
QPlatformWindow::setGeometry(m_normalGeometry);
+#if QT_CONFIG(accessibility)
// Add accessibility-enable button. The user can activate this
// button to opt-in to accessibility.
if (window()->isTopLevel())
QWasmAccessibility::addAccessibilityEnableButton(window());
+#endif
}
QWasmScreen *QWasmWindow::platformScreen() const
@@ -219,7 +252,7 @@ void QWasmWindow::setZOrder(int z)
void QWasmWindow::setWindowCursor(QByteArray cssCursorName)
{
- m_canvas["style"].set("cursor", emscripten::val(cssCursorName.constData()));
+ m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData()));
}
void QWasmWindow::setGeometry(const QRect &rect)
@@ -232,19 +265,34 @@ void QWasmWindow::setGeometry(const QRect &rect)
if (m_state.testFlag(Qt::WindowMaximized))
return platformScreen()->availableGeometry().marginsRemoved(frameMargins());
- const auto screenGeometry = screen()->geometry();
-
- QRect result(rect);
- result.moveTop(std::max(std::min(rect.y(), screenGeometry.bottom()),
- screenGeometry.y() + margins.top()));
- return result;
+ auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint());
+
+ // In viewport
+ auto containerGeometryInViewport =
+ QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"))
+ .toRect();
+
+ auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size());
+
+ QRect cappedGeometry(rectInViewport);
+ if (!parent()) {
+ // Clamp top level windows top position to the screen bounds
+ cappedGeometry.moveTop(
+ std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()),
+ containerGeometryInViewport.y() + margins.top()));
+ }
+ cappedGeometry.setSize(
+ cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));
+ return QRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()),
+ rect.size());
})();
m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());
const auto frameRect =
clientAreaRect
.adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
- .translated(-screen()->geometry().topLeft());
+ .translated(!parent() ? -screen()->geometry().topLeft() : QPoint());
m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px");
m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px");
@@ -282,6 +330,8 @@ void QWasmWindow::setVisible(bool visible)
m_compositor->requestUpdateWindow(this, QWasmCompositor::ExposeEventDelivery);
m_qtWindow["style"].set("display", visible ? "block" : "none");
+ if (window()->isActive())
+ m_canvas.call<void>("focus");
if (visible)
applyWindowState();
}
@@ -305,13 +355,15 @@ QMargins QWasmWindow::frameMargins() const
void QWasmWindow::raise()
{
- m_compositor->raise(this);
+ bringToTop();
invalidate();
+ if (QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
}
void QWasmWindow::lower()
{
- m_compositor->lower(this);
+ sendToBottom();
invalidate();
}
@@ -322,12 +374,14 @@ WId QWasmWindow::winId() const
void QWasmWindow::propagateSizeHints()
{
- QRect rect = windowGeometry();
- if (rect.size().width() < windowMinimumSize().width()
- && rect.size().height() < windowMinimumSize().height()) {
- rect.setSize(windowMinimumSize());
- setGeometry(rect);
- }
+ // setGeometry() will take care of minimum and maximum size constraints
+ setGeometry(windowGeometry());
+ m_nonClientArea->propagateSizeHints();
+}
+
+void QWasmWindow::setOpacity(qreal level)
+{
+ m_qtWindow["style"].set("opacity", qBound(0.0, level, 1.0));
}
void QWasmWindow::invalidate()
@@ -342,12 +396,29 @@ void QWasmWindow::onActivationChanged(bool active)
void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)
+ || flags.testFlag(Qt::WindowStaysOnBottomHint)
+ != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) {
+ onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
+ }
m_flags = flags;
- dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
+ dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame());
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow());
+ dom::syncCSSClassWith(m_qtWindow, "has-title", hasTitleBar());
+ dom::syncCSSClassWith(m_qtWindow, "transparent-for-input",
+ flags.testFlag(Qt::WindowTransparentForInput));
+
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+ m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint));
}
void QWasmWindow::setWindowState(Qt::WindowStates newState)
{
+ // Child windows can not have window states other than Qt::WindowActive
+ if (parent())
+ newState &= Qt::WindowActive;
+
const Qt::WindowStates oldState = m_state;
if (newState.testFlag(Qt::WindowMinimized)) {
@@ -400,26 +471,52 @@ void QWasmWindow::applyWindowState()
else
newGeom = normalGeometry();
- dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
m_nonClientArea->titleBar()->setRestoreVisible(isMaximized);
- m_nonClientArea->titleBar()->setMaximizeVisible(!isMaximized);
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
if (isVisible())
QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
setGeometry(newGeom);
}
+void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
+{
+ onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags()));
+ m_commitedParent = parent;
+}
+
+bool QWasmWindow::processKey(const KeyEvent &event)
+{
+ constexpr bool ProceedToNativeEvent = false;
+ Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp);
+
+ const auto clipboardResult =
+ QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event);
+
+ using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult;
+ if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded)
+ return ProceedToNativeEvent;
+
+ const auto result = QWindowSystemInterface::handleKeyEvent(
+ 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key,
+ event.modifiers, event.text);
+ return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
+ ? ProceedToNativeEvent
+ : result;
+}
+
bool QWasmWindow::processPointer(const PointerEvent &event)
{
- if (event.pointerType != PointerType::Mouse)
+ if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
return false;
switch (event.type) {
case EventType::PointerEnter: {
const auto pointInScreen = platformScreen()->mapFromLocal(
- dom::mapPoint(event.target, platformScreen()->element(), event.localPoint));
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
QWindowSystemInterface::handleEnterEvent(
window(), m_window->mapFromGlobal(pointInScreen), pointInScreen);
break;
@@ -434,30 +531,6 @@ bool QWasmWindow::processPointer(const PointerEvent &event)
return false;
}
-bool QWasmWindow::processDrop(const DragEvent &event)
-{
- m_dropDataReadCancellationFlag = qstdweb::readDataTransfer(
- event.dataTransfer,
- [](QByteArray fileContent) {
- QImage image;
- image.loadFromData(fileContent, nullptr);
- return image;
- },
- [this, event](std::unique_ptr<QMimeData> data) {
- QWindowSystemInterface::handleDrag(window(), data.get(), event.pointInPage,
- event.dropAction, event.mouseButton,
- event.modifiers);
-
- QWindowSystemInterface::handleDrop(window(), data.get(), event.pointInPage,
- event.dropAction, event.mouseButton,
- event.modifiers);
-
- QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction,
- {}, {});
- });
- return true;
-}
-
bool QWasmWindow::processWheel(const WheelEvent &event)
{
// Web scroll deltas are inverted from Qt deltas - negate.
@@ -473,13 +546,13 @@ bool QWasmWindow::processWheel(const WheelEvent &event)
})();
const auto pointInScreen = platformScreen()->mapFromLocal(
- dom::mapPoint(event.target, platformScreen()->element(), event.localPoint));
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
return QWindowSystemInterface::handleWheelEvent(
- window(), QWasmIntegration::getTimestamp(), mapFromGlobal(pointInScreen), pointInScreen,
- event.delta * scrollFactor, event.delta * scrollFactor, event.modifiers,
- Qt::NoScrollPhase, Qt::MouseEventNotSynthesized,
- event.webkitDirectionInvertedFromDevice);
+ window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
+ pointInScreen, (event.delta * scrollFactor).toPoint(),
+ (event.delta * scrollFactor).toPoint(), event.modifiers, Qt::NoScrollPhase,
+ Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice);
}
QRect QWasmWindow::normalGeometry() const
@@ -497,10 +570,30 @@ void QWasmWindow::requestUpdate()
m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
}
+bool QWasmWindow::hasFrame() const
+{
+ return !m_flags.testFlag(Qt::FramelessWindowHint);
+}
+
+bool QWasmWindow::hasBorder() const
+{
+ return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow)
+ && !windowIsPopupType(m_flags) && !parent();
+}
+
bool QWasmWindow::hasTitleBar() const
{
- return !m_state.testFlag(Qt::WindowFullScreen) && m_flags.testFlag(Qt::WindowTitleHint)
- && !windowIsPopupType(m_flags);
+ return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint);
+}
+
+bool QWasmWindow::hasShadow() const
+{
+ return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint);
+}
+
+bool QWasmWindow::hasMaximizeButton() const
+{
+ return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint);
}
bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const
@@ -519,8 +612,12 @@ void QWasmWindow::requestActivateWindow()
return;
}
- if (window()->isTopLevel())
- raise();
+ raise();
+ setAsActiveNode();
+
+ if (!QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
+
QPlatformWindow::requestActivateWindow();
}
@@ -544,9 +641,61 @@ bool QWasmWindow::windowEvent(QEvent *event)
}
}
+void QWasmWindow::setMask(const QRegion &region)
+{
+ if (region.isEmpty()) {
+ m_qtWindow["style"].set("clipPath", emscripten::val(""));
+ return;
+ }
+
+ std::ostringstream cssClipPath;
+ cssClipPath << "path('";
+ for (const auto &rect : region) {
+ const auto cssRect = rect.adjusted(0, 0, 1, 1);
+ cssClipPath << "M " << cssRect.left() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.bottom() << " ";
+ cssClipPath << "L " << cssRect.left() << " " << cssRect.bottom() << " z ";
+ }
+ cssClipPath << "')";
+ m_qtWindow["style"].set("clipPath", emscripten::val(cssClipPath.str()));
+}
+
+void QWasmWindow::setParent(const QPlatformWindow *)
+{
+ commitParent(parentNode());
+}
+
std::string QWasmWindow::canvasSelector() const
{
return "!qtwindow" + std::to_string(m_winId);
}
+emscripten::val QWasmWindow::containerElement()
+{
+ return m_windowContents;
+}
+
+QWasmWindowTreeNode *QWasmWindow::parentNode()
+{
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
+}
+
+QWasmWindow *QWasmWindow::asWasmWindow()
+{
+ return this;
+}
+
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (previous)
+ previous->containerElement().call<void>("removeChild", m_qtWindow);
+ if (current)
+ current->containerElement().call<void>("appendChild", m_qtWindow);
+ QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index 46bb1f81a7..ab0dc68e83 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -6,11 +6,14 @@
#include "qwasmintegration.h"
#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#include <emscripten/html5.h>
#include "qwasmbackingstore.h"
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
#include "qwasmwindownonclientarea.h"
+#include "qwasmwindowstack.h"
+#include "qwasmwindowtreenode.h"
#include <QtCore/private/qstdweb_p.h>
#include "QtGui/qopenglcontext.h"
@@ -23,25 +26,27 @@
QT_BEGIN_NAMESPACE
namespace qstdweb {
-struct CancellationFlag;
-}
-
-namespace qstdweb {
class EventCallback;
}
class ClientArea;
-struct DragEvent;
+struct KeyEvent;
struct PointerEvent;
+class QWasmDeadKeySupport;
struct WheelEvent;
-class QWasmWindow final : public QPlatformWindow
+class QWasmWindow final : public QPlatformWindow,
+ public QWasmWindowTreeNode,
+ public QNativeInterface::Private::QWasmWindow
{
public:
- QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore);
+ QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor,
+ QWasmBackingStore *backingStore);
~QWasmWindow() final;
- void destroy();
+ static QWasmWindow *fromWindow(QWindow *window);
+ QSurfaceFormat format() const override;
+
void paint();
void setZOrder(int order);
void setWindowCursor(QByteArray cssCursorName);
@@ -62,6 +67,7 @@ public:
QMargins frameMargins() const override;
WId winId() const override;
void propagateSizeHints() override;
+ void setOpacity(qreal level) override;
void raise() override;
void lower() override;
QRect normalGeometry() const override;
@@ -75,6 +81,8 @@ public:
bool setKeyboardGrabEnabled(bool) override { return false; }
bool setMouseGrabEnabled(bool grab) final;
bool windowEvent(QEvent *event) final;
+ void setMask(const QRegion &region) final;
+ void setParent(const QPlatformWindow *window) final;
QWasmScreen *platformScreen() const;
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
@@ -82,24 +90,45 @@ public:
QWindow *window() const { return m_window; }
std::string canvasSelector() const;
- emscripten::val context2d() { return m_context2d; }
- emscripten::val a11yContainer() { return m_a11yContainer; }
+ emscripten::val context2d() const { return m_context2d; }
+ emscripten::val a11yContainer() const { return m_a11yContainer; }
+ emscripten::val inputHandlerElement() const { return m_windowContents; }
+
+ // QNativeInterface::Private::QWasmWindow
+ emscripten::val document() const override { return m_document; }
+ emscripten::val clientArea() const override { return m_qtWindow; }
+
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
private:
friend class QWasmScreen;
+ static constexpr auto defaultWindowSize = 160;
+
+ // QWasmWindowTreeNode:
+ QWasmWindow *asWasmWindow() final;
+ void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference) final;
void invalidate();
+ bool hasFrame() const;
bool hasTitleBar() const;
+ bool hasBorder() const;
+ bool hasShadow() const;
+ bool hasMaximizeButton() const;
void applyWindowState();
+ void commitParent(QWasmWindowTreeNode *parent);
+ bool processKey(const KeyEvent &event);
bool processPointer(const PointerEvent &event);
- bool processDrop(const DragEvent &event);
bool processWheel(const WheelEvent &event);
QWindow *m_window = nullptr;
QWasmCompositor *m_compositor = nullptr;
QWasmBackingStore *m_backingStore = nullptr;
+ QWasmDeadKeySupport *m_deadKeySupport;
QRect m_normalGeometry {0, 0, 0 ,0};
emscripten::val m_document;
@@ -113,9 +142,13 @@ private:
std::unique_ptr<NonClientArea> m_nonClientArea;
std::unique_ptr<ClientArea> m_clientArea;
+ QWasmWindowTreeNode *m_commitedParent = nullptr;
+
+ std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
+
std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
- std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
@@ -136,8 +169,6 @@ private:
friend class QWasmCompositor;
friend class QWasmEventTranslator;
bool windowIsPopupType(Qt::WindowFlags flags) const;
-
- std::shared_ptr<qstdweb::CancellationFlag> m_dropDataReadCancellationFlag;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
index e3e17baa1e..6da3e24c05 100644
--- a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
@@ -7,9 +7,10 @@
#include "qwasmevent.h"
#include "qwasmscreen.h"
#include "qwasmwindow.h"
+#include "qwasmdrag.h"
#include <QtGui/private/qguiapplication_p.h>
-#include <qpa/qwindowsysteminterface.h>
+#include <QtGui/qpointingdevice.h>
#include <QtCore/qassert.h>
@@ -19,8 +20,9 @@ ClientArea::ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val
: m_screen(screen), m_window(window), m_element(element)
{
const auto callback = std::function([this](emscripten::val event) {
- if (processPointer(*PointerEvent::fromWeb(event)))
- event.call<void>("preventDefault");
+ processPointer(*PointerEvent::fromWeb(event));
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
});
m_pointerDownCallback =
@@ -28,36 +30,52 @@ ClientArea::ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val
m_pointerMoveCallback =
std::make_unique<qstdweb::EventCallback>(element, "pointermove", callback);
m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(element, "pointerup", callback);
+ m_pointerCancelCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointercancel", callback);
+
+ element.call<void>("setAttribute", emscripten::val("draggable"), emscripten::val("true"));
+
+ m_dragStartCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragstart", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragStarted(&event);
+ });
+ m_dragOverCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragover", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragOver(&event);
+ });
+ m_dropCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "drop", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDrop(&event);
+ });
+ m_dragEndCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragend", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragFinished(&event);
+ });
+
}
bool ClientArea::processPointer(const PointerEvent &event)
{
- if (event.pointerType != PointerType::Mouse)
- return false;
-
- const auto localScreenPoint =
- dom::mapPoint(event.target, m_screen->element(), event.localPoint);
- const auto pointInScreen = m_screen->mapFromLocal(localScreenPoint);
-
- const QPoint pointInTargetWindowCoords = m_window->mapFromGlobal(pointInScreen);
switch (event.type) {
- case EventType::PointerDown: {
+ case EventType::PointerDown:
m_element.call<void>("setPointerCapture", event.pointerId);
- m_window->window()->requestActivate();
+ if ((m_window->window()->flags() & Qt::WindowDoesNotAcceptFocus)
+ != Qt::WindowDoesNotAcceptFocus
+ && m_window->window()->isTopLevel())
+ m_window->window()->requestActivate();
break;
- }
- case EventType::PointerUp: {
+ case EventType::PointerUp:
m_element.call<void>("releasePointerCapture", event.pointerId);
break;
- }
- case EventType::PointerEnter:;
- QWindowSystemInterface::handleEnterEvent(
- m_window->window(), pointInTargetWindowCoords, pointInScreen);
- break;
- case EventType::PointerLeave:
- QWindowSystemInterface::handleLeaveEvent(m_window->window());
- break;
default:
break;
};
@@ -71,23 +89,107 @@ bool ClientArea::processPointer(const PointerEvent &event)
bool ClientArea::deliverEvent(const PointerEvent &event)
{
const auto pointInScreen = m_screen->mapFromLocal(
- dom::mapPoint(event.target, m_screen->element(), event.localPoint));
-
- const QPoint targetPointClippedToScreen(
- std::max(m_screen->geometry().left(),
- std::min(m_screen->geometry().right(), pointInScreen.x())),
- std::max(m_screen->geometry().top(),
- std::min(m_screen->geometry().bottom(), pointInScreen.y())));
-
- const QEvent::Type eventType =
- MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::Client);
-
- return eventType != QEvent::None
- && QWindowSystemInterface::handleMouseEvent(
- m_window->window(), QWasmIntegration::getTimestamp(),
- m_window->window()->mapFromGlobal(targetPointClippedToScreen),
- targetPointClippedToScreen, event.mouseButtons, event.mouseButton, eventType,
- event.modifiers);
+ dom::mapPoint(event.target(), m_screen->element(), event.localPoint));
+
+ const auto geometryF = m_screen->geometry().toRectF();
+ const QPointF targetPointClippedToScreen(
+ qBound(geometryF.left(), pointInScreen.x(), geometryF.right()),
+ qBound(geometryF.top(), pointInScreen.y(), geometryF.bottom()));
+
+ if (event.pointerType == PointerType::Mouse) {
+ const QEvent::Type eventType =
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::Client);
+
+ return eventType != QEvent::None
+ && QWindowSystemInterface::handleMouseEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, event.mouseButton,
+ eventType, event.modifiers);
+ }
+
+ if (event.pointerType == PointerType::Pen) {
+ qreal pressure;
+ switch (event.type) {
+ case EventType::PointerDown :
+ case EventType::PointerMove :
+ pressure = event.pressure;
+ break;
+ case EventType::PointerUp :
+ pressure = 0.0;
+ break;
+ default:
+ return false;
+ }
+ // Tilt in the browser is in the range +-90, but QTabletEvent only goes to +-60.
+ qreal xTilt = qBound(-60.0, event.tiltX, 60.0);
+ qreal yTilt = qBound(-60.0, event.tiltY, 60.0);
+ // Barrel rotation is reported as 0 to 359, but QTabletEvent wants a signed value.
+ qreal rotation = event.twist > 180.0 ? 360.0 - event.twist : event.twist;
+ return QWindowSystemInterface::handleTabletEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->tabletDevice(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, pressure, xTilt, yTilt,
+ event.tangentialPressure, rotation, event.modifiers);
+ }
+
+ QWindowSystemInterface::TouchPoint *touchPoint;
+
+ QPointF pointInTargetWindowCoords =
+ QPointF(m_window->window()->mapFromGlobal(targetPointClippedToScreen));
+ QPointF normalPosition(pointInTargetWindowCoords.x() / m_window->window()->width(),
+ pointInTargetWindowCoords.y() / m_window->window()->height());
+
+ const auto tp = m_pointerIdToTouchPoints.find(event.pointerId);
+ if (event.pointerType != PointerType::Pen && tp != m_pointerIdToTouchPoints.end()) {
+ touchPoint = &tp.value();
+ } else {
+ touchPoint = &m_pointerIdToTouchPoints
+ .insert(event.pointerId, QWindowSystemInterface::TouchPoint())
+ .value();
+
+ // Assign touch point id. TouchPoint::id is int, but QGuiApplicationPrivate::processTouchEvent()
+ // will not synthesize mouse events for touch points with negative id; use the absolute value for
+ // the touch point id.
+ touchPoint->id = qAbs(event.pointerId);
+
+ touchPoint->state = QEventPoint::State::Pressed;
+ }
+
+ const bool stationaryTouchPoint = (normalPosition == touchPoint->normalPosition);
+ touchPoint->normalPosition = normalPosition;
+ touchPoint->area = QRectF(targetPointClippedToScreen, QSizeF(event.width, event.height))
+ .translated(-event.width / 2, -event.height / 2);
+ touchPoint->pressure = event.pressure;
+
+ switch (event.type) {
+ case EventType::PointerUp:
+ touchPoint->state = QEventPoint::State::Released;
+ break;
+ case EventType::PointerMove:
+ touchPoint->state = (stationaryTouchPoint ? QEventPoint::State::Stationary
+ : QEventPoint::State::Updated);
+ break;
+ default:
+ break;
+ }
+
+ QList<QWindowSystemInterface::TouchPoint> touchPointList;
+ touchPointList.reserve(m_pointerIdToTouchPoints.size());
+ std::transform(m_pointerIdToTouchPoints.begin(), m_pointerIdToTouchPoints.end(),
+ std::back_inserter(touchPointList),
+ [](const QWindowSystemInterface::TouchPoint &val) { return val; });
+
+ if (event.type == EventType::PointerUp)
+ m_pointerIdToTouchPoints.remove(event.pointerId);
+
+ return event.type == EventType::PointerCancel
+ ? QWindowSystemInterface::handleTouchCancelEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ event.modifiers)
+ : QWindowSystemInterface::handleTouchEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ touchPointList, event.modifiers);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.h b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
index f2fd115e25..ba745a59a8 100644
--- a/src/plugins/platforms/wasm/qwasmwindowclientarea.h
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
@@ -5,6 +5,8 @@
#define QWASMWINDOWCLIENTAREA_H
#include <QtCore/qnamespace.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/QMap>
#include <emscripten/val.h>
@@ -32,6 +34,14 @@ private:
std::unique_ptr<qstdweb::EventCallback> m_pointerDownCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerCancelCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_dragOverCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragStartCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragEndCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
+
+ QMap<int, QWindowSystemInterface::TouchPoint> m_pointerIdToTouchPoints;
QWasmScreen *m_screen;
QWasmWindow *m_window;
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
index 304f678add..00fa8fb236 100644
--- a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
@@ -129,9 +129,6 @@ Resizer::ResizerElement::ResizerElement(ResizerElement &&other) = default;
bool Resizer::ResizerElement::onPointerDown(const PointerEvent &event)
{
- if (event.pointerType != PointerType::Mouse)
- return false;
-
m_element.call<void>("setPointerCapture", event.pointerId);
m_capturedPointerId = event.pointerId;
@@ -181,6 +178,24 @@ Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement)
Resizer::~Resizer() = default;
+ResizeConstraints Resizer::getResizeConstraints() {
+ const auto *window = m_window->window();
+ const auto minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
+ window->minimumHeight() - window->geometry().height());
+ const auto maxGrow = QPoint(window->maximumWidth() - window->geometry().width(),
+ window->maximumHeight() - window->geometry().height());
+
+ const auto frameRect =
+ QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
+ auto containerGeometry =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+
+ const int maxGrowTop = frameRect.top() - containerGeometry.top();
+
+ return ResizeConstraints{minShrink, maxGrow, maxGrowTop};
+}
+
void Resizer::onInteraction()
{
m_window->onNonClientAreaInteraction();
@@ -193,33 +208,25 @@ void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
m_currentResizeData.reset(new ResizeData{
.edges = resizeEdges,
.originInScreenCoords = dom::mapPoint(
- event.target, m_window->platformScreen()->element(), event.localPoint),
+ event.target(), m_window->platformScreen()->element(), event.localPoint),
});
- const auto *window = m_window->window();
- m_currentResizeData->minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
- window->minimumHeight() - window->geometry().height());
-
- const auto frameRect =
- QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
- const auto screenRect = QRectF::fromDOMRect(
- m_window->platformScreen()->element().call<emscripten::val>("getBoundingClientRect"));
-
- const int maxGrowTop = frameRect.top() - screenRect.top();
+ const auto resizeConstraints = getResizeConstraints();
+ m_currentResizeData->minShrink = resizeConstraints.minShrink;
m_currentResizeData->maxGrow =
- QPoint(window->maximumWidth() - window->geometry().width(),
- std::min(resizeEdges & Qt::Edge::TopEdge ? maxGrowTop : INT_MAX,
- window->maximumHeight() - window->geometry().height()));
+ QPoint(resizeConstraints.maxGrow.x(),
+ std::min(resizeEdges & Qt::Edge::TopEdge ? resizeConstraints.maxGrowTop : INT_MAX,
+ resizeConstraints.maxGrow.y()));
- m_currentResizeData->initialBounds = window->geometry();
+ m_currentResizeData->initialBounds = m_window->window()->geometry();
}
void Resizer::continueResize(const PointerEvent &event)
{
const auto pointInScreen =
- dom::mapPoint(event.target, m_window->platformScreen()->element(), event.localPoint);
- const auto amount = pointInScreen - m_currentResizeData->originInScreenCoords;
+ dom::mapPoint(event.target(), m_window->platformScreen()->element(), event.localPoint);
+ const auto amount = (pointInScreen - m_currentResizeData->originInScreenCoords).toPoint();
const QPoint cappedGrowVector(
std::min(m_currentResizeData->maxGrow.x(),
std::max(m_currentResizeData->minShrink.x(),
@@ -349,6 +356,11 @@ void TitleBar::setMaximizeVisible(bool visible)
m_maximize->setVisible(visible);
}
+void TitleBar::setCloseVisible(bool visible)
+{
+ m_close->setVisible(visible);
+}
+
void TitleBar::setIcon(std::string_view imageData, std::string_view format)
{
m_icon->setImage(imageData, format);
@@ -366,14 +378,11 @@ QRectF TitleBar::geometry() const
bool TitleBar::onPointerDown(const PointerEvent &event)
{
- if (event.pointerType != PointerType::Mouse)
- return false;
-
m_element.call<void>("setPointerCapture", event.pointerId);
m_capturedPointerId = event.pointerId;
- const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint);
- m_lastMovePoint = targetPointClippedToScreen;
+ m_moveStartWindowPosition = m_window->window()->position();
+ m_moveStartPoint = clipPointWithScreen(event.localPoint);
m_window->onNonClientEvent(event);
return true;
}
@@ -383,11 +392,9 @@ bool TitleBar::onPointerMove(const PointerEvent &event)
if (m_capturedPointerId != event.pointerId)
return false;
- const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint);
- const QPoint delta = targetPointClippedToScreen - m_lastMovePoint;
- m_lastMovePoint = targetPointClippedToScreen;
+ const QPoint delta = (clipPointWithScreen(event.localPoint) - m_moveStartPoint).toPoint();
- m_window->window()->setPosition(m_window->window()->position() + delta);
+ m_window->window()->setPosition(m_moveStartWindowPosition + delta);
m_window->onNonClientEvent(event);
return true;
}
@@ -409,17 +416,25 @@ bool TitleBar::onDoubleClick()
return true;
}
-QPoint TitleBar::clipPointWithScreen(const QPoint &pointInTitleBarCoords) const
+QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const
{
- auto *screen = m_window->platformScreen();
- return screen->clipPoint(screen->mapFromLocal(
- dom::mapPoint(m_element, screen->element(), pointInTitleBarCoords)));
+ auto containerRect =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+ const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(),
+ pointInTitleBarCoords);
+
+ auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()),
+ qBound(0., qreal(p.y()), containerRect.height()));
+ return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint();
}
NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement)
+ : m_qtWindowElement(qtWindowElement),
+ m_resizer(std::make_unique<Resizer>(window, m_qtWindowElement)),
+ m_titleBar(std::make_unique<TitleBar>(window, m_qtWindowElement))
{
- m_titleBar = std::make_unique<TitleBar>(window, qtWindowElement);
- m_resizer = std::make_unique<Resizer>(window, qtWindowElement);
+ updateResizability();
}
NonClientArea::~NonClientArea() = default;
@@ -429,4 +444,17 @@ void NonClientArea::onClientAreaWidthChange(int width)
m_titleBar->setWidth(width);
}
+void NonClientArea::propagateSizeHints()
+{
+ updateResizability();
+}
+
+void NonClientArea::updateResizability()
+{
+ const auto resizeConstraints = m_resizer->getResizeConstraints();
+ const bool nonResizable = resizeConstraints.minShrink.isNull()
+ && resizeConstraints.maxGrow.isNull() && resizeConstraints.maxGrowTop == 0;
+ dom::syncCSSClassWith(m_qtWindowElement, "no-resize", nonResizable);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.h b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
index 18d1c63f4b..78c77585a0 100644
--- a/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
@@ -4,6 +4,7 @@
#ifndef QWASMWINDOWNONCLIENTAREA_H
#define QWASMWINDOWNONCLIENTAREA_H
+#include <QtCore/qrect.h>
#include <QtCore/qtconfigmacros.h>
#include <QtCore/qnamespace.h>
@@ -33,9 +34,13 @@ public:
~NonClientArea();
void onClientAreaWidthChange(int width);
+ void propagateSizeHints();
TitleBar *titleBar() const { return m_titleBar.get(); }
private:
+ void updateResizability();
+
+ emscripten::val m_qtWindowElement;
std::unique_ptr<Resizer> m_resizer;
std::unique_ptr<TitleBar> m_titleBar;
};
@@ -86,6 +91,12 @@ private:
Callbacks m_callbacks;
};
+struct ResizeConstraints {
+ QPoint minShrink;
+ QPoint maxGrow;
+ int maxGrowTop;
+};
+
class Resizer
{
public:
@@ -146,6 +157,8 @@ public:
Resizer(QWasmWindow *window, emscripten::val parentElement);
~Resizer();
+ ResizeConstraints getResizeConstraints();
+
private:
void onInteraction();
void startResize(Qt::Edges resizeEdges, const PointerEvent &event);
@@ -155,7 +168,7 @@ private:
struct ResizeData
{
Qt::Edges edges = Qt::Edges::fromInt(0);
- QPoint originInScreenCoords;
+ QPointF originInScreenCoords;
QPoint minShrink;
QPoint maxGrow;
QRect initialBounds;
@@ -176,6 +189,7 @@ public:
void setTitle(const QString &title);
void setRestoreVisible(bool visible);
void setMaximizeVisible(bool visible);
+ void setCloseVisible(bool visible);
void setIcon(std::string_view imageData, std::string_view format);
void setWidth(int width);
@@ -187,7 +201,7 @@ private:
bool onPointerUp(const PointerEvent &event);
bool onDoubleClick();
- QPoint clipPointWithScreen(const QPoint &pointInTitleBarCoords) const;
+ QPointF clipPointWithScreen(const QPointF &pointInTitleBarCoords) const;
QWasmWindow *m_window;
@@ -200,7 +214,8 @@ private:
std::unique_ptr<WebImageButton> m_icon;
int m_capturedPointerId = -1;
- QPoint m_lastMovePoint;
+ QPointF m_moveStartPoint;
+ QPoint m_moveStartWindowPosition;
std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
std::unique_ptr<qstdweb::EventCallback> m_mouseMoveEvent;
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.cpp b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
index 098f1c1ff2..d3769c7a1b 100644
--- a/src/plugins/platforms/wasm/qwasmwindowstack.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
@@ -5,20 +5,38 @@
QT_BEGIN_NAMESPACE
-QWasmWindowStack::QWasmWindowStack(TopWindowChangedCallbackType topWindowChangedCallback)
- : m_topWindowChangedCallback(std::move(topWindowChangedCallback))
+QWasmWindowStack::QWasmWindowStack(WindowOrderChangedCallbackType windowOrderChangedCallback)
+ : m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)),
+ m_regularWindowsBegin(m_windowStack.begin()),
+ m_alwaysOnTopWindowsBegin(m_windowStack.begin())
{
}
QWasmWindowStack::~QWasmWindowStack() = default;
-void QWasmWindowStack::pushWindow(QWasmWindow *window)
+void QWasmWindowStack::pushWindow(QWasmWindow *window, PositionPreference position)
{
Q_ASSERT(m_windowStack.count(window) == 0);
- m_windowStack.push_back(window);
-
- m_topWindowChangedCallback();
+ if (position == PositionPreference::StayOnTop) {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_windowStack.push_back(window);
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else if (position == PositionPreference::Regular) {
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_alwaysOnTopWindowsBegin = m_windowStack.insert(m_alwaysOnTopWindowsBegin, window) + 1;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ m_regularWindowsBegin = m_windowStack.insert(m_regularWindowsBegin, window) + 1;
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance + 1;
+ }
+
+ m_windowOrderChangedCallback();
}
void QWasmWindowStack::removeWindow(QWasmWindow *window)
@@ -26,41 +44,105 @@ void QWasmWindowStack::removeWindow(QWasmWindow *window)
Q_ASSERT(m_windowStack.count(window) == 1);
auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
- const bool removingBottom = m_windowStack.begin() == it;
- const bool removingTop = m_windowStack.end() - 1 == it;
- if (removingBottom)
- m_firstWindowTreatment = FirstWindowTreatment::Regular;
+ const auto position = getWindowPositionPreference(it);
+ const auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
m_windowStack.erase(it);
- if (removingTop)
- m_topWindowChangedCallback();
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance
+ - (position != PositionPreference::StayOnTop ? 1 : 0);
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance
+ - (position == PositionPreference::StayOnBottom ? 1 : 0);
+
+ m_windowOrderChangedCallback();
}
void QWasmWindowStack::raise(QWasmWindow *window)
{
Q_ASSERT(m_windowStack.count(window) == 1);
- if (window == rootWindow() || window == topWindow())
+ if (window == topWindow())
return;
- auto it = std::find(regularWindowsBegin(), m_windowStack.end(), window);
- std::rotate(it, it + 1, m_windowStack.end());
- m_topWindowChangedCallback();
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itEnd = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_windowStack.end();
+ case PositionPreference::Regular:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_regularWindowsBegin;
+ }
+ })();
+ std::rotate(it, it + 1, itEnd);
+ m_windowOrderChangedCallback();
}
void QWasmWindowStack::lower(QWasmWindow *window)
{
Q_ASSERT(m_windowStack.count(window) == 1);
- if (window == rootWindow())
+ if (window == *m_windowStack.begin())
return;
- const bool loweringTopWindow = topWindow() == window;
- auto it = std::find(regularWindowsBegin(), m_windowStack.end(), window);
- std::rotate(regularWindowsBegin(), it, it + 1);
- if (loweringTopWindow && topWindow() != window)
- m_topWindowChangedCallback();
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itBegin = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::Regular:
+ return m_regularWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_windowStack.begin();
+ }
+ })();
+
+ std::rotate(itBegin, it, it + 1);
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::windowPositionPreferenceChanged(QWasmWindow *window,
+ PositionPreference position)
+{
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ const auto currentPosition = getWindowPositionPreference(it);
+
+ const auto zones = static_cast<int>(position) - static_cast<int>(currentPosition);
+ Q_ASSERT(zones != 0);
+
+ if (zones < 0) {
+ // Perform right rotation so that the window lands on top of regular windows
+ const auto begin = std::make_reverse_iterator(it + 1);
+ const auto end = position == PositionPreference::Regular
+ ? std::make_reverse_iterator(m_alwaysOnTopWindowsBegin)
+ : std::make_reverse_iterator(m_regularWindowsBegin);
+ std::rotate(begin, begin + 1, end);
+ if (zones == -2) {
+ ++m_alwaysOnTopWindowsBegin;
+ ++m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ ++m_alwaysOnTopWindowsBegin;
+ } else {
+ ++m_regularWindowsBegin;
+ }
+ } else {
+ // Perform left rotation so that the window lands at the bottom of always on top windows
+ const auto begin = it;
+ const auto end = position == PositionPreference::Regular ? m_regularWindowsBegin
+ : m_alwaysOnTopWindowsBegin;
+ std::rotate(begin, begin + 1, end);
+ if (zones == 2) {
+ --m_alwaysOnTopWindowsBegin;
+ --m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ --m_regularWindowsBegin;
+ } else {
+ --m_alwaysOnTopWindowsBegin;
+ }
+ }
+ m_windowOrderChangedCallback();
}
QWasmWindowStack::iterator QWasmWindowStack::begin()
@@ -103,21 +185,19 @@ size_t QWasmWindowStack::size() const
return m_windowStack.size();
}
-QWasmWindow *QWasmWindowStack::rootWindow() const
-{
- return m_firstWindowTreatment == FirstWindowTreatment::AlwaysAtBottom ? m_windowStack.first()
- : nullptr;
-}
-
QWasmWindow *QWasmWindowStack::topWindow() const
{
return m_windowStack.empty() ? nullptr : m_windowStack.last();
}
-QWasmWindowStack::StorageType::iterator QWasmWindowStack::regularWindowsBegin()
+QWasmWindowStack::PositionPreference
+QWasmWindowStack::getWindowPositionPreference(StorageType::iterator windowIt) const
{
- return m_windowStack.begin()
- + (m_firstWindowTreatment == FirstWindowTreatment::AlwaysAtBottom ? 1 : 0);
+ if (windowIt >= m_alwaysOnTopWindowsBegin)
+ return PositionPreference::StayOnTop;
+ if (windowIt >= m_regularWindowsBegin)
+ return PositionPreference::Regular;
+ return PositionPreference::StayOnBottom;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.h b/src/plugins/platforms/wasm/qwasmwindowstack.h
index 2eb48b0865..c75001157a 100644
--- a/src/plugins/platforms/wasm/qwasmwindowstack.h
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.h
@@ -24,7 +24,7 @@ class QWasmWindow;
class Q_AUTOTEST_EXPORT QWasmWindowStack
{
public:
- using TopWindowChangedCallbackType = std::function<void()>;
+ using WindowOrderChangedCallbackType = std::function<void()>;
using StorageType = QList<QWasmWindow *>;
@@ -32,13 +32,20 @@ public:
using const_iterator = StorageType::const_reverse_iterator;
using const_reverse_iterator = StorageType::const_iterator;
- explicit QWasmWindowStack(TopWindowChangedCallbackType topWindowChangedCallback);
+ enum class PositionPreference {
+ StayOnBottom,
+ Regular,
+ StayOnTop,
+ };
+
+ explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback);
~QWasmWindowStack();
- void pushWindow(QWasmWindow *window);
+ void pushWindow(QWasmWindow *window, PositionPreference position);
void removeWindow(QWasmWindow *window);
void raise(QWasmWindow *window);
void lower(QWasmWindow *window);
+ void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position);
// Iterates top-to-bottom
iterator begin();
@@ -55,14 +62,12 @@ public:
QWasmWindow *topWindow() const;
private:
- enum class FirstWindowTreatment { AlwaysAtBottom, Regular };
-
- QWasmWindow *rootWindow() const;
- StorageType::iterator regularWindowsBegin();
+ PositionPreference getWindowPositionPreference(StorageType::iterator windowIt) const;
- TopWindowChangedCallbackType m_topWindowChangedCallback;
+ WindowOrderChangedCallbackType m_windowOrderChangedCallback;
QList<QWasmWindow *> m_windowStack;
- FirstWindowTreatment m_firstWindowTreatment = FirstWindowTreatment::AlwaysAtBottom;
+ StorageType::iterator m_regularWindowsBegin;
+ StorageType::iterator m_alwaysOnTopWindowsBegin;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
new file mode 100644
index 0000000000..ea8d8dbcfa
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowtreenode.h"
+
+#include "qwasmwindow.h"
+
+QWasmWindowTreeNode::QWasmWindowTreeNode()
+ : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this))
+{
+}
+
+QWasmWindowTreeNode::~QWasmWindowTreeNode() = default;
+
+void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent,
+ QWasmWindowTreeNode *currentParent,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ auto *window = asWasmWindow();
+ if (previousParent) {
+ previousParent->m_childStack.removeWindow(window);
+ previousParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeRemoval, previousParent,
+ window);
+ }
+
+ if (currentParent) {
+ currentParent->m_childStack.pushWindow(window, positionPreference);
+ currentParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeInsertion, currentParent,
+ window);
+ }
+}
+
+QWasmWindow *QWasmWindowTreeNode::asWasmWindow()
+{
+ return nullptr;
+}
+
+void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && m_childStack.topWindow()
+ && m_childStack.topWindow()->window()) {
+
+ const QVariant showWithoutActivating = m_childStack.topWindow()->window()->property("_q_showWithoutActivating");
+ if (!showWithoutActivating.isValid() || !showWithoutActivating.toBool())
+ m_childStack.topWindow()->requestActivateWindow();
+ }
+
+ if (parentNode())
+ parentNode()->onSubtreeChanged(changeType, parent, child);
+}
+
+void QWasmWindowTreeNode::setWindowZOrder(QWasmWindow *window, int z)
+{
+ window->setZOrder(z);
+}
+
+void QWasmWindowTreeNode::onPositionPreferenceChanged(
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (parentNode()) {
+ parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(),
+ positionPreference);
+ }
+}
+
+void QWasmWindowTreeNode::setAsActiveNode()
+{
+ if (parentNode())
+ parentNode()->setActiveChildNode(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::bringToTop()
+{
+ if (!parentNode())
+ return;
+ parentNode()->m_childStack.raise(asWasmWindow());
+ parentNode()->bringToTop();
+}
+
+void QWasmWindowTreeNode::sendToBottom()
+{
+ if (!parentNode())
+ return;
+ m_childStack.lower(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::onTopWindowChanged()
+{
+ constexpr int zOrderForElementInFrontOfScreen = 3;
+ int z = zOrderForElementInFrontOfScreen;
+ std::for_each(m_childStack.rbegin(), m_childStack.rend(),
+ [this, &z](QWasmWindow *window) { setWindowZOrder(window, z++); });
+}
+
+void QWasmWindowTreeNode::setActiveChildNode(QWasmWindow *activeChild)
+{
+ m_activeChild = activeChild;
+
+ auto it = m_childStack.begin();
+ if (it == m_childStack.end())
+ return;
+ for (; it != m_childStack.end(); ++it)
+ (*it)->onActivationChanged(*it == m_activeChild);
+
+ setAsActiveNode();
+}
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
new file mode 100644
index 0000000000..344fdb43cb
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWTREENODE_H
+#define QWASMWINDOWTREENODE_H
+
+#include "qwasmwindowstack.h"
+
+namespace emscripten {
+class val;
+}
+
+class QWasmWindow;
+
+enum class QWasmWindowTreeNodeChangeType {
+ NodeInsertion,
+ NodeRemoval,
+};
+
+class QWasmWindowTreeNode
+{
+public:
+ QWasmWindowTreeNode();
+ virtual ~QWasmWindowTreeNode();
+
+ virtual emscripten::val containerElement() = 0;
+ virtual QWasmWindowTreeNode *parentNode() = 0;
+
+protected:
+ virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference);
+ virtual QWasmWindow *asWasmWindow();
+ virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child);
+ virtual void setWindowZOrder(QWasmWindow *window, int z);
+
+ void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference);
+ void setAsActiveNode();
+ void bringToTop();
+ void sendToBottom();
+
+ const QWasmWindowStack &childStack() const { return m_childStack; }
+ QWasmWindow *activeChild() const { return m_activeChild; }
+
+private:
+ void onTopWindowChanged();
+ void setActiveChildNode(QWasmWindow *activeChild);
+
+ QWasmWindowStack m_childStack;
+ QWasmWindow *m_activeChild = nullptr;
+};
+
+#endif // QWASMWINDOWTREENODE_H
diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html
index aaa121981d..702ea1f59d 100644
--- a/src/plugins/platforms/wasm/wasm_shell.html
+++ b/src/plugins/platforms/wasm/wasm_shell.html
@@ -27,42 +27,48 @@
</figure>
<div id="screen"></div>
- <script type='text/javascript'>
- let qtLoader = undefined;
- function init() {
- var spinner = document.querySelector('#qtspinner');
- var canvas = document.querySelector('#screen');
- var status = document.querySelector('#qtstatus')
+ <script type="text/javascript">
+ async function init()
+ {
+ const spinner = document.querySelector('#qtspinner');
+ const screen = document.querySelector('#screen');
+ const status = document.querySelector('#qtstatus');
- qtLoader = new QtLoader({
- canvasElements : [canvas],
- showLoader: function(loaderStatus) {
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- status.innerHTML = loaderStatus + "...";
- },
- showError: function(errorText) {
- status.innerHTML = errorText;
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showExit: function() {
- status.innerHTML = "Application exit";
- if (qtLoader.exitCode !== undefined)
- status.innerHTML += " with code " + qtLoader.exitCode;
- if (qtLoader.exitText !== undefined)
- status.innerHTML += " (" + qtLoader.exitText + ")";
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showCanvas: function() {
- spinner.style.display = 'none';
- canvas.style.display = 'block';
- },
- });
- qtLoader.loadEmscriptenModule("@APPNAME@");
- }
+ const showUi = (ui) => {
+ [spinner, screen].forEach(element => element.style.display = 'none');
+ if (screen === ui)
+ screen.style.position = 'default';
+ ui.style.display = 'block';
+ }
+
+ try {
+ showUi(spinner);
+ status.innerHTML = 'Loading...';
+
+ const instance = await qtLoad({
+ qt: {
+ onLoaded: () => showUi(screen),
+ onExit: exitData =>
+ {
+ status.innerHTML = 'Application exit';
+ status.innerHTML +=
+ exitData.code !== undefined ? ` with code ${exitData.code}` : '';
+ status.innerHTML +=
+ exitData.text !== undefined ? ` (${exitData.text})` : '';
+ showUi(spinner);
+ },
+ entryFunction: window.@APPEXPORTNAME@,
+ containerElements: [screen],
+ @PRELOAD@
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ console.error(e.stack);
+ }
+ }
</script>
+ <script src="@APPNAME@.js"></script>
<script type="text/javascript" src="qtloader.js"></script>
</body>
</html>
diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt
index 7884fc164e..4b92317978 100644
--- a/src/plugins/platforms/windows/CMakeLists.txt
+++ b/src/plugins/platforms/windows/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from windows.pro.
-
#####################################################################
## QWindowsIntegrationPlugin Plugin:
#####################################################################
@@ -10,20 +8,20 @@
qt_internal_add_plugin(QWindowsIntegrationPlugin
OUTPUT_NAME qwindows
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES windows # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES windows
SOURCES
main.cpp
qtwindowsglobal.h
qwin10helpers.cpp qwin10helpers.h
qwindowsapplication.cpp qwindowsapplication.h
qwindowsbackingstore.cpp qwindowsbackingstore.h
- qwindowscombase.h
qwindowscontext.cpp qwindowscontext.h
qwindowscursor.cpp qwindowscursor.h
qwindowsdialoghelpers.cpp qwindowsdialoghelpers.h
qwindowsdropdataobject.cpp qwindowsdropdataobject.h
qwindowsgdiintegration.cpp qwindowsgdiintegration.h
qwindowsgdinativeinterface.cpp qwindowsgdinativeinterface.h
+ qwindowsiconengine.cpp qwindowsiconengine.h
qwindowsinputcontext.cpp qwindowsinputcontext.h
qwindowsintegration.cpp qwindowsintegration.h
qwindowsinternalmimedata.cpp qwindowsinternalmimedata.h
@@ -40,6 +38,8 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
qwindowstheme.cpp qwindowstheme.h
qwindowsthreadpoolrunner.h
qwindowswindow.cpp qwindowswindow.h
+ NO_UNITY_BUILD_SOURCES
+ qwindowspointerhandler.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_FOREACH
@@ -56,6 +56,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
imm32
ole32
oleaut32
+ setupapi
shell32
shlwapi
user32
@@ -83,10 +84,6 @@ qt_internal_add_resource(QWindowsIntegrationPlugin "openglblacklists"
${openglblacklists_resource_files}
)
-
-#### Keys ignored in scope 1:.:.:windows.pro:<TRUE>:
-# OTHER_FILES = "windows.json"
-
## Scopes:
#####################################################################
@@ -98,9 +95,6 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl
Qt::OpenGLPrivate
)
-#### Keys ignored in scope 3:.:.:windows.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
-
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl
LIBRARIES
opengl32
@@ -109,6 +103,8 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW
LIBRARIES
uuid
+ NO_PCH_SOURCES
+ qwindowspointerhandler.cpp
)
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_systemtrayicon
@@ -179,6 +175,7 @@ endif()
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessibility
SOURCES
+ uiautomation/qwindowsuiautomation.cpp uiautomation/qwindowsuiautomation.h
uiautomation/qwindowsuiaaccessibility.cpp uiautomation/qwindowsuiaaccessibility.h
uiautomation/qwindowsuiabaseprovider.cpp uiautomation/qwindowsuiabaseprovider.h
uiautomation/qwindowsuiaexpandcollapseprovider.cpp uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -200,13 +197,17 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessi
uiautomation/qwindowsuiawindowprovider.cpp uiautomation/qwindowsuiawindowprovider.h
)
+if(QT_FEATURE_accessibility)
+ find_library(UI_AUTOMATION_LIBRARY uiautomationcore)
+ if(UI_AUTOMATION_LIBRARY)
+ qt_internal_extend_target(QWindowsIntegrationPlugin
+ LIBRARIES
+ ${UI_AUTOMATION_LIBRARY}
+ )
+ endif()
+endif()
+
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility
LIBRARIES
uuid
)
-
-# begin special case
-if (MINGW)
- set_source_files_properties(qwindowspointerhandler.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
-endif()
-# end special case
diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h
index 22787bd63e..96a72600eb 100644
--- a/src/plugins/platforms/windows/qtwindowsglobal.h
+++ b/src/plugins/platforms/windows/qtwindowsglobal.h
@@ -119,7 +119,7 @@ enum WindowsEventType // Simplify event types
NonClientPointerEvent = NonClientEventFlag + PointerEventFlag + 4,
KeyEvent = KeyEventFlag + 1,
KeyDownEvent = KeyEventFlag + KeyDownEventFlag + 1,
- KeyboardLayoutChangeEvent = KeyEventFlag + 2,
+ InputLanguageChangeEvent = KeyEventFlag + 2,
InputMethodKeyEvent = InputMethodEventFlag + KeyEventFlag + 1,
InputMethodKeyDownEvent = InputMethodEventFlag + KeyEventFlag + KeyDownEventFlag + 1,
ClipboardEvent = ClipboardEventFlag + 1,
@@ -230,7 +230,7 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
return QtWindows::InputMethodKeyDownEvent;
#ifdef WM_INPUTLANGCHANGE
case WM_INPUTLANGCHANGE:
- return QtWindows::KeyboardLayoutChangeEvent;
+ return QtWindows::InputLanguageChangeEvent;
#endif // WM_INPUTLANGCHANGE
case WM_TOUCH:
return QtWindows::TouchEvent;
diff --git a/src/plugins/platforms/windows/qwindowsapplication.cpp b/src/plugins/platforms/windows/qwindowsapplication.cpp
index 5d1b6d348b..42e34ac99f 100644
--- a/src/plugins/platforms/windows/qwindowsapplication.cpp
+++ b/src/plugins/platforms/windows/qwindowsapplication.cpp
@@ -9,25 +9,13 @@
#include "qwindowsopengltester.h"
#include "qwindowswindow.h"
#include "qwindowsintegration.h"
+#include "qwindowstheme.h"
#include <QtCore/qvariant.h>
#include <QtCore/private/qfunctions_win_p.h>
#include <QtGui/qpalette.h>
-#if QT_CONFIG(cpp_winrt)
-# include <winrt/base.h>
-# include <QtCore/private/qfactorycacheregistration_p.h>
-// Workaround for Windows SDK bug.
-// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
-namespace winrt::impl
-{
- template <typename Async>
- auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
-}
-# include <winrt/Windows.UI.ViewManagement.h>
-#endif // QT_CONFIG(cpp_winrt)
-
QT_BEGIN_NAMESPACE
void QWindowsApplication::setTouchWindowTouchType(QWindowsApplication::TouchWindowTouchTypes type)
@@ -84,11 +72,6 @@ bool QWindowsApplication::setWinTabEnabled(bool enabled)
return enabled ? ctx->initTablet() : ctx->disposeTablet();
}
-bool QWindowsApplication::isDarkMode() const
-{
- return QWindowsContext::isDarkMode();
-}
-
QWindowsApplication::DarkModeHandling QWindowsApplication::darkModeHandling() const
{
return m_darkModeHandling;
@@ -155,67 +138,9 @@ QVariant QWindowsApplication::gpuList() const
return result;
}
-static inline QColor getSysColor(int index)
-{
- COLORREF cr = GetSysColor(index);
- return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
-}
-
-#if QT_CONFIG(cpp_winrt)
-static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
-{
- return QColor(color.R, color.G, color.B, color.A);
-}
-#endif
-
-static inline QColor placeHolderColor(QColor textColor)
-{
- textColor.setAlpha(128);
- return textColor;
-}
-
-void QWindowsApplication::lightSystemPalette(QPalette &result) const
+void QWindowsApplication::populateLightSystemPalette(QPalette &result) const
{
- QColor background = getSysColor(COLOR_BTNFACE);
- QColor textColor = getSysColor(COLOR_WINDOWTEXT);
- QColor accent = getSysColor(COLOR_HIGHLIGHT);
-
-#if QT_CONFIG(cpp_winrt)
- // respect the Windows 11 accent color
- using namespace winrt::Windows::UI::ViewManagement;
- const auto settings = UISettings();
-
- accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
-#endif
-
- const QColor btnFace = background;
- const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
-
- result.setColor(QPalette::Highlight, accent);
- result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
- result.setColor(QPalette::Button, btnFace);
- result.setColor(QPalette::Light, btnHighlight);
- result.setColor(QPalette::Dark, getSysColor(COLOR_BTNSHADOW));
- result.setColor(QPalette::Mid, result.button().color().darker(150));
- result.setColor(QPalette::Text, textColor);
- result.setColor(QPalette::PlaceholderText, placeHolderColor(textColor));
- result.setColor(QPalette::BrightText, btnHighlight);
- result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW));
- result.setColor(QPalette::Window, btnFace);
- result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT));
- result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
- result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
- result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
-
- result.setColor(QPalette::Link, Qt::blue);
- result.setColor(QPalette::LinkVisited, Qt::magenta);
- result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
- result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
- result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
- result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color());
-
- if (result.midlight() == result.button())
- result.setColor(QPalette::Midlight, result.button().color().lighter(110));
+ result = QWindowsTheme::systemPalette(Qt::ColorScheme::Light);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsapplication.h b/src/plugins/platforms/windows/qwindowsapplication.h
index 8b74b47f3d..0918df91af 100644
--- a/src/plugins/platforms/windows/qwindowsapplication.h
+++ b/src/plugins/platforms/windows/qwindowsapplication.h
@@ -24,7 +24,6 @@ public:
bool isWinTabEnabled() const override;
bool setWinTabEnabled(bool enabled) override;
- bool isDarkMode() const override;
DarkModeHandling darkModeHandling() const override;
void setDarkModeHandling(DarkModeHandling handling) override;
@@ -43,7 +42,7 @@ public:
QVariant gpu() const override;
QVariant gpuList() const override;
- void lightSystemPalette(QPalette &palette) const override;
+ void populateLightSystemPalette(QPalette &palette) const override;
private:
WindowActivationBehavior m_windowActivationBehavior = DefaultActivateWindow;
diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.cpp b/src/plugins/platforms/windows/qwindowsbackingstore.cpp
index 0f9d0172d9..07918f6094 100644
--- a/src/plugins/platforms/windows/qwindowsbackingstore.cpp
+++ b/src/plugins/platforms/windows/qwindowsbackingstore.cpp
@@ -117,7 +117,7 @@ void QWindowsBackingStore::resize(const QSize &size, const QRegion &region)
if (QImage::toPixelFormat(format).alphaUsage() == QPixelFormat::UsesAlpha)
m_alphaNeedsFill = true;
else // upgrade but here we know app painting does not rely on alpha hence no need to fill
- format = qt_maybeAlphaVersionWithSameDepth(format);
+ format = qt_maybeDataCompatibleAlphaVersion(format);
QWindowsNativeImage *oldwni = m_image.data();
auto *newwni = new QWindowsNativeImage(size.width(), size.height(), format);
diff --git a/src/plugins/platforms/windows/qwindowscombase.h b/src/plugins/platforms/windows/qwindowscombase.h
deleted file mode 100644
index 04d4dc51cf..0000000000
--- a/src/plugins/platforms/windows/qwindowscombase.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWINDOWSCOMBASE_H
-#define QWINDOWSCOMBASE_H
-
-#include <QtCore/qglobal.h>
-
-#include <unknwn.h>
-
-QT_BEGIN_NAMESPACE
-
-// The __uuidof operator of MinGW does not work for all interfaces (for example,
-// IAccessible2). Specializations of this function can be provides to work
-// around this.
-template <class DesiredInterface>
-static IID qUuidOf() { return __uuidof(DesiredInterface); }
-
-// Helper for implementing IUnknown::QueryInterface.
-template <class DesiredInterface, class Derived>
-bool qWindowsComQueryInterface(Derived *d, REFIID id, LPVOID *iface)
-{
- if (id == qUuidOf<DesiredInterface>()) {
- *iface = static_cast<DesiredInterface *>(d);
- d->AddRef();
- return true;
- }
- return false;
-}
-
-// Helper for implementing IUnknown::QueryInterface for IUnknown
-// in the case of multiple inheritance via the first inherited class.
-template <class FirstInheritedInterface, class Derived>
-bool qWindowsComQueryUnknownInterfaceMulti(Derived *d, REFIID id, LPVOID *iface)
-{
- if (id == __uuidof(IUnknown)) {
- *iface = static_cast<FirstInheritedInterface *>(d);
- d->AddRef();
- return true;
- }
- return false;
-}
-
-// Helper base class to provide IUnknown methods for COM classes (single inheritance)
-template <class ComInterface> class QWindowsComBase : public ComInterface
-{
- Q_DISABLE_COPY_MOVE(QWindowsComBase)
-public:
- explicit QWindowsComBase(ULONG initialRefCount = 1) : m_ref(initialRefCount) {}
- virtual ~QWindowsComBase() = default;
-
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override
- {
- *iface = nullptr;
- return qWindowsComQueryInterface<IUnknown>(this, id, iface) || qWindowsComQueryInterface<ComInterface>(this, id, iface)
- ? S_OK : E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE AddRef() override { return ++m_ref; }
-
- ULONG STDMETHODCALLTYPE Release() override
- {
- if (!--m_ref) {
- delete this;
- return 0;
- }
- return m_ref;
- }
-
-private:
- ULONG m_ref;
-};
-
-// Clang does not consider __declspec(nothrow) as nothrow
-QT_WARNING_DISABLE_CLANG("-Wmicrosoft-exception-spec")
-QT_WARNING_DISABLE_CLANG("-Wmissing-exception-spec")
-
-QT_END_NAMESPACE
-
-#endif // QWINDOWSCOMBASE_H
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index 3fe91d2935..de65a2171d 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -45,10 +45,13 @@
#include <QtCore/qscopedpointer.h>
#include <QtCore/quuid.h>
#include <QtCore/private/qwinregistry_p.h>
-#include <QtCore/private/qfactorycacheregistration_p.h>
+#if QT_CONFIG(cpp_winrt)
+# include <QtCore/private/qfactorycacheregistration_p.h>
+#endif
#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <stdlib.h>
#include <stdio.h>
@@ -151,11 +154,9 @@ struct QWindowsContextPrivate {
bool m_asyncExpose = false;
HPOWERNOTIFY m_powerNotification = nullptr;
HWND m_powerDummyWindow = nullptr;
- static bool m_darkMode;
static bool m_v2DpiAware;
};
-bool QWindowsContextPrivate::m_darkMode = false;
bool QWindowsContextPrivate::m_v2DpiAware = false;
QWindowsContextPrivate::QWindowsContextPrivate()
@@ -169,7 +170,6 @@ QWindowsContextPrivate::QWindowsContextPrivate()
m_systemInfo |= QWindowsContext::SI_RTL_Extensions;
m_keyMapper.setUseRTLExtensions(true);
}
- m_darkMode = QWindowsTheme::queryDarkMode();
if (FAILED(m_oleInitializeResult)) {
qWarning() << "QWindowsContext: OleInitialize() failed: "
<< QSystemError::windowsComString(m_oleInitializeResult);
@@ -410,27 +410,28 @@ QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
QDebug operator<<(QDebug d, QtWindows::DpiAwareness dpiAwareness)
{
const QDebugStateSaver saver(d);
- d.nospace().noquote() << "QtWindows::DpiAwareness::";
+ QString message = u"QtWindows::DpiAwareness::"_s;
switch (dpiAwareness) {
case QtWindows::DpiAwareness::Invalid:
- d.nospace().noquote() << "Invalid";
+ message += u"Invalid"_s;
break;
case QtWindows::DpiAwareness::Unaware:
- d.nospace().noquote() << "Unaware";
+ message += u"Unaware"_s;
break;
case QtWindows::DpiAwareness::System:
- d.nospace().noquote() << "System";
+ message += u"System"_s;
break;
case QtWindows::DpiAwareness::PerMonitor:
- d.nospace().noquote() << "PerMonitor";
+ message += u"PerMonitor"_s;
break;
case QtWindows::DpiAwareness::PerMonitorVersion2:
- d.nospace().noquote() << "PerMonitorVersion2";
+ message += u"PerMonitorVersion2"_s;
break;
case QtWindows::DpiAwareness::Unaware_GdiScaled:
- d.nospace().noquote() << "Unaware_GdiScaled";
+ message += u"Unaware_GdiScaled"_s;
break;
}
+ d.nospace().noquote() << message;
return d;
}
#endif
@@ -460,11 +461,6 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes
return true;
}
-bool QWindowsContext::isDarkMode()
-{
- return QWindowsContextPrivate::m_darkMode;
-}
-
QWindowsContext *QWindowsContext::instance()
{
return m_instance;
@@ -480,9 +476,9 @@ bool QWindowsContext::useRTLExtensions() const
return d->m_keyMapper.useRTLExtensions();
}
-QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const
+QPlatformKeyMapper *QWindowsContext::keyMapper() const
{
- return d->m_keyMapper.possibleKeys(e);
+ return &d->m_keyMapper;
}
QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows()
@@ -534,6 +530,8 @@ QString QWindowsContext::classNamePrefix()
# define xstr(s) str(s)
# define str(s) #s
str << xstr(QT_NAMESPACE);
+# undef str
+# undef xstr
#endif
}
return result;
@@ -1087,21 +1085,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
// Only refresh the window theme if the user changes the personalize settings.
if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
&& (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)) {
- const bool darkMode = QWindowsTheme::queryDarkMode();
- const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode;
- QWindowsContextPrivate::m_darkMode = darkMode;
- auto integration = QWindowsIntegration::instance();
- integration->updateApplicationBadge();
- if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
- QWindowsTheme::instance()->refresh();
- QWindowSystemInterface::handleThemeChange();
- }
- if (darkModeChanged) {
- if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) {
- for (QWindowsWindow *w : d->m_windows)
- w->setDarkBorder(QWindowsContextPrivate::m_darkMode);
- }
- }
+ QWindowsTheme::handleSettingsChanged();
}
return d->m_screenManager.handleScreenChanges();
}
@@ -1118,7 +1102,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
d->m_creationContext->applyToMinMaxInfo(reinterpret_cast<MINMAXINFO *>(lParam));
return true;
case QtWindows::ResizeEvent:
- d->m_creationContext->obtainedSize = QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ d->m_creationContext->obtainedSize = QSize(LOWORD(lParam), HIWORD(lParam));
return true;
case QtWindows::MoveEvent:
d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
@@ -1161,7 +1145,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
if (wParam == DBT_DEVNODES_CHANGED)
initTouch();
break;
- case QtWindows::KeyboardLayoutChangeEvent:
+ case QtWindows::InputLanguageChangeEvent:
if (QWindowsInputContext *wic = windowsInputContext())
wic->handleInputLanguageChanged(wParam, lParam);
Q_FALLTHROUGH();
@@ -1191,7 +1175,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
platformWindow->handleMoved();
return true;
case QtWindows::ResizeEvent:
- platformWindow->handleResized(static_cast<int>(wParam));
+ platformWindow->handleResized(static_cast<int>(wParam), lParam);
return true;
case QtWindows::QuerySizeHints:
platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
@@ -1205,21 +1189,29 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
case QtWindows::ExposeEvent:
return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result);
case QtWindows::NonClientMouseEvent:
- if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled())
+ if (!platformWindow->frameStrutEventsEnabled())
+ break;
+ if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
else
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
case QtWindows::NonClientPointerEvent:
- if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled())
+ if (!platformWindow->frameStrutEventsEnabled())
+ break;
+ if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::EnterSizeMoveEvent:
platformWindow->setFlag(QWindowsWindow::ResizeMoveActive);
+ if (!IsZoomed(hwnd))
+ platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ExitSizeMoveEvent:
platformWindow->clearFlag(QWindowsWindow::ResizeMoveActive);
platformWindow->checkForScreenChanged();
handleExitSizeMove(platformWindow->window());
+ if (!IsZoomed(hwnd))
+ platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ScrollEvent:
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
@@ -1265,6 +1257,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QWindowSystemInterface::handleCloseEvent(platformWindow->window());
return true;
case QtWindows::ThemeChanged: {
+ QWindowsThemeCache::clearThemeCache(platformWindow->handle());
// Switch from Aero to Classic changes margins.
if (QWindowsTheme *theme = QWindowsTheme::instance())
theme->windowsThemeChanged(platformWindow->window());
@@ -1403,7 +1396,7 @@ void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et,
}
if (nextActiveWindow != d->m_lastActiveWindow) {
d->m_lastActiveWindow = nextActiveWindow;
- QWindowSystemInterface::handleWindowActivated(nextActiveWindow, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nextActiveWindow, Qt::ActiveWindowFocusReason);
}
}
@@ -1433,7 +1426,7 @@ bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg)
}
QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered, pos, globalPos,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper()->queryKeyboardModifiers());
return true;
}
#endif
@@ -1454,7 +1447,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons();
if (currentButtons == appButtons)
return;
- const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const Qt::KeyboardModifiers keyboardModifiers = keyMapper()->queryKeyboardModifiers();
const QPoint globalPos = QWindowsCursor::mousePosition();
const QPlatformWindow *platWin = window->handle();
const QPoint localPos = platWin->mapFromGlobal(globalPos);
@@ -1463,8 +1456,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) {
if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) {
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
- currentButtons, button, type,
- keyboardModifiers);
+ currentButtons, button, type, keyboardModifiers);
}
}
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h
index 21e841a886..0539a22afc 100644
--- a/src/plugins/platforms/windows/qwindowscontext.h
+++ b/src/plugins/platforms/windows/qwindowscontext.h
@@ -33,6 +33,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
class QWindow;
class QPlatformScreen;
class QPlatformWindow;
+class QPlatformKeyMapper;
class QWindowsMenuBar;
class QWindowsScreenManager;
class QWindowsTabletSupport;
@@ -43,7 +44,6 @@ struct QWindowsContextPrivate;
class QPoint;
class QKeyEvent;
class QPointingDevice;
-
class QWindowsContext
{
Q_DISABLE_COPY_MOVE(QWindowsContext)
@@ -116,19 +116,17 @@ public:
static void setTabletAbsoluteRange(int a);
- bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
+ static bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
static QtWindows::DpiAwareness processDpiAwareness();
static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
- static bool isDarkMode();
-
void setDetectAltGrModifier(bool a);
// Returns a combination of SystemInfoFlags
unsigned systemInfo() const;
bool useRTLExtensions() const;
- QList<int> possibleKeys(const QKeyEvent *e) const;
+ QPlatformKeyMapper *keyMapper() const;
HandleBaseWindowHash &windows();
diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp
index 5b904709ef..b416886120 100644
--- a/src/plugins/platforms/windows/qwindowscursor.cpp
+++ b/src/plugins/platforms/windows/qwindowscursor.cpp
@@ -14,6 +14,7 @@
#include <QtGui/qscreen.h>
#include <QtGui/private/qguiapplication_p.h> // getPixmapCursor()
#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtGui/private/qpixmap_win_p.h>
#include <QtCore/private/qwinregistry_p.h>
#include <QtCore/qdebug.h>
@@ -29,8 +30,7 @@ static bool initResources()
QT_BEGIN_NAMESPACE
-Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0);
-Q_GUI_EXPORT HBITMAP qt_createIconMask(const QBitmap &bitmap);
+using namespace Qt::Literals::StringLiterals;
/*!
\class QWindowsCursorCacheKey
@@ -105,14 +105,16 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
hotSpot.setX(width / 2);
if (hotSpot.y() < 0)
hotSpot.setY(height / 2);
- const int n = qMax(1, width / 8);
- QScopedArrayPointer<uchar> xBits(new uchar[height * n]);
- QScopedArrayPointer<uchar> xMask(new uchar[height * n]);
+ // a ddb is word aligned, QImage depends on bow it was created
+ const auto bplDdb = qMax(1, ((width + 15) >> 4) << 1);
+ const auto bplImg = int(bbits.bytesPerLine());
+ QScopedArrayPointer<uchar> xBits(new uchar[height * bplDdb]);
+ QScopedArrayPointer<uchar> xMask(new uchar[height * bplDdb]);
int x = 0;
for (int i = 0; i < height; ++i) {
const uchar *bits = bbits.constScanLine(i);
const uchar *mask = mbits.constScanLine(i);
- for (int j = 0; j < n; ++j) {
+ for (int j = 0; j < bplImg && j < bplDdb; ++j) {
uchar b = bits[j];
uchar m = mask[j];
if (invb)
@@ -123,6 +125,11 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
xMask[x] = b ^ m;
++x;
}
+ for (int i = bplImg; i < bplDdb; ++i) {
+ xBits[x] = 0;
+ xMask[x] = 0;
+ ++x;
+ }
}
return CreateCursor(GetModuleHandle(nullptr), hotSpot.x(), hotSpot.y(), width, height,
xBits.data(), xMask.data());
@@ -140,8 +147,8 @@ static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1)
bbits = bbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
mbits = mbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
- bbits = bbits.convertToFormat(QImage::Format_Mono);
- mbits = mbits.convertToFormat(QImage::Format_Mono);
+ bbits = std::move(bbits).convertToFormat(QImage::Format_Mono);
+ mbits = std::move(mbits).convertToFormat(QImage::Format_Mono);
const bool invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1));
const bool invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1));
return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm);
@@ -438,8 +445,8 @@ QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursor
if (!bestFit)
return PixmapCursor();
- const QPixmap rawImage(QStringLiteral(":/qt-project.org/windows/cursors/images/") +
- QString::fromLatin1(bestFit->fileName));
+ const QPixmap rawImage(":/qt-project.org/windows/cursors/images/"_L1 +
+ QLatin1StringView(bestFit->fileName));
return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY));
}
#endif // !QT_NO_IMAGEFORMAT_PNG
diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
index 7b49a424e2..0ce0b0e2a7 100644
--- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
+++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
@@ -4,7 +4,6 @@
#define QT_NO_URL_CAST_FROM_STRING 1
#include <QtCore/qt_windows.h>
-#include "qwindowscombase.h"
#include "qwindowsdialoghelpers.h"
#include "qwindowscontext.h"
@@ -31,7 +30,9 @@
#include <QtCore/qmutex.h>
#include <QtCore/quuid.h>
#include <QtCore/qtemporaryfile.h>
+#include <QtCore/private/qfunctions_win_p.h>
#include <QtCore/private/qsystemerror_p.h>
+#include <QtCore/private/qcomobject_p.h>
#include <algorithm>
#include <vector>
@@ -42,34 +43,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-#ifndef QT_NO_DEBUG_STREAM
-/* Output UID (IID, CLSID) as C++ constants.
- * The constants are contained in the Windows SDK libs, but not for MinGW. */
-static inline QString guidToString(const GUID &g)
-{
- QString rc;
- QTextStream str(&rc);
- str.setIntegerBase(16);
- str.setNumberFlags(str.numberFlags() | QTextStream::ShowBase);
- str << '{' << g.Data1 << ", " << g.Data2 << ", " << g.Data3;
- str.setFieldWidth(2);
- str.setFieldAlignment(QTextStream::AlignRight);
- str.setPadChar(u'0');
- str << ",{" << g.Data4[0] << ", " << g.Data4[1] << ", " << g.Data4[2] << ", " << g.Data4[3]
- << ", " << g.Data4[4] << ", " << g.Data4[5] << ", " << g.Data4[6] << ", " << g.Data4[7]
- << "}};";
- return rc;
-}
-
-inline QDebug operator<<(QDebug d, const GUID &g)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << guidToString(g);
- return d;
-}
-#endif // !QT_NO_DEBUG_STREAM
-
// Return an allocated wchar_t array from a QString, reserve more memory if desired.
static wchar_t *qStringToWCharArray(const QString &s, size_t reserveSize = 0)
{
@@ -265,6 +238,7 @@ private:
void QWindowsDialogThread::run()
{
qCDebug(lcQpaDialogs) << '>' << __FUNCTION__;
+ QComHelper comInit(COINIT_APARTMENTTHREADED);
m_dialog->exec(m_owner);
qCDebug(lcQpaDialogs) << '<' << __FUNCTION__;
}
@@ -449,7 +423,7 @@ inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFile
class QWindowsNativeFileDialogBase;
-class QWindowsNativeFileDialogEventHandler : public QWindowsComBase<IFileDialogEvents>
+class QWindowsNativeFileDialogEventHandler : public QComObject<IFileDialogEvents>
{
Q_DISABLE_COPY_MOVE(QWindowsNativeFileDialogEventHandler)
public:
@@ -592,7 +566,7 @@ QUrl QWindowsShellItem::url() const
if (urlV.isValid())
return urlV;
// Last resort: encode the absolute desktop parsing id as data URL
- const QString data = QStringLiteral("data:text/plain;base64,")
+ const QString data = "data:text/plain;base64,"_L1
+ QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
return QUrl(data);
}
@@ -1385,7 +1359,7 @@ QString tempFilePattern(QString name)
int lastDot = name.lastIndexOf(u'.');
if (lastDot < 0)
lastDot = name.size();
- name.insert(lastDot, QStringLiteral("_XXXXXX"));
+ name.insert(lastDot, "_XXXXXX"_L1);
for (int i = lastDot - 1; i >= 0; --i) {
if (!validFileNameCharacter(name.at(i)))
@@ -1577,7 +1551,7 @@ QWindowsNativeDialogBase *QWindowsFileDialogHelper::createNativeDialog()
if (!info.isDir())
result->selectFile(info.fileName());
} else {
- result->selectFile(url.path()); // TODO url.fileName() once it exists
+ result->selectFile(url.fileName());
}
}
// No need to select initialNameFilter if mode is Dir
@@ -1611,7 +1585,7 @@ void QWindowsFileDialogHelper::selectFile(const QUrl &fileName)
qCDebug(lcQpaDialogs) << __FUNCTION__ << fileName.toString();
if (hasNativeDialog()) // Might be invoked from the QFileDialog constructor.
- nativeFileDialog()->selectFile(fileName.toLocalFile()); // ## should use QUrl::fileName() once it exists
+ nativeFileDialog()->selectFile(fileName.fileName());
}
QList<QUrl> QWindowsFileDialogHelper::selectedFiles() const
@@ -1719,14 +1693,6 @@ static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UIN
return dialog->existingDirCallback(hwnd, uMsg, lParam);
}
-/* The correct declaration of the SHGetPathFromIDList symbol is
- * being used in mingw-w64 as of r6215, which is a v3 snapshot. */
-#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3)
-typedef ITEMIDLIST *qt_LpItemIdList;
-#else
-using qt_LpItemIdList = PIDLIST_ABSOLUTE;
-#endif
-
int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam)
{
switch (uMsg) {
@@ -1740,7 +1706,7 @@ int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM
break;
case BFFM_SELCHANGED: {
wchar_t path[MAX_PATH];
- const bool ok = SHGetPathFromIDList(reinterpret_cast<qt_LpItemIdList>(lParam), path)
+ const bool ok = SHGetPathFromIDList(reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
&& path[0];
SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
}
@@ -1762,7 +1728,7 @@ QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner)
bi.lpfn = xpFileDialogGetExistingDirCallbackProc;
bi.lParam = LPARAM(this);
QList<QUrl> selectedFiles;
- if (qt_LpItemIdList pItemIDList = SHBrowseForFolder(&bi)) {
+ if (const auto pItemIDList = SHBrowseForFolder(&bi)) {
wchar_t path[MAX_PATH];
path[0] = 0;
if (SHGetPathFromIDList(pItemIDList, path) && path[0])
diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp
index 93205259f8..c6f55c3509 100644
--- a/src/plugins/platforms/windows/qwindowsdrag.cpp
+++ b/src/plugins/platforms/windows/qwindowsdrag.cpp
@@ -28,6 +28,8 @@
#include <QtCore/qdebug.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qpoint.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/private/qcomobject_p.h>
#include <shlobj.h>
@@ -167,7 +169,7 @@ static Qt::MouseButtons lastButtons = Qt::NoButton;
\internal
*/
-class QWindowsOleDropSource : public QWindowsComBase<IDropSource>
+class QWindowsOleDropSource : public QComObject<IDropSource>
{
public:
enum Mode {
@@ -348,7 +350,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
} else {
if (buttons && !m_currentButtons) {
m_currentButtons = buttons;
- } else if (!(m_currentButtons & buttons)) { // Button changed: Complete Drop operation.
+ } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
result = DRAGDROP_S_DROP;
}
}
@@ -526,7 +528,8 @@ QWindowsOleDropTarget::DragLeave()
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
- lastModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ lastModifiers = keyMapper->queryKeyboardModifiers();
lastButtons = QWindowsMouseHandler::queryMouseButtons();
QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
@@ -611,7 +614,6 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
*/
bool QWindowsDrag::m_canceled = false;
-bool QWindowsDrag::m_dragging = false;
QWindowsDrag::QWindowsDrag() = default;
@@ -648,7 +650,9 @@ IDropTargetHelper* QWindowsDrag::dropHelper() {
// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
{
- HWND hwnd = ::GetFocus();
+ QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
+ const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture();
+ const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
bool starting = false;
for (;;) {
@@ -737,10 +741,7 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag)
const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
<< Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
- // Indicate message handlers we are in DoDragDrop() event loop.
- QWindowsDrag::m_dragging = true;
const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
- QWindowsDrag::m_dragging = false;
const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
if (r == DRAGDROP_S_DROP) {
if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h
index cf5d43fb84..a2d0e54044 100644
--- a/src/plugins/platforms/windows/qwindowsdrag.h
+++ b/src/plugins/platforms/windows/qwindowsdrag.h
@@ -4,12 +4,12 @@
#ifndef QWINDOWSDRAG_H
#define QWINDOWSDRAG_H
-#include "qwindowscombase.h"
#include "qwindowsinternalmimedata.h"
#include <qpa/qplatformdrag.h>
#include <QtGui/qpixmap.h>
#include <QtGui/qdrag.h>
+#include <QtCore/private/qcomobject_p.h>
struct IDropTargetHelper;
@@ -23,7 +23,7 @@ public:
IDataObject *retrieveDataObject() const override;
};
-class QWindowsOleDropTarget : public QWindowsComBase<IDropTarget>
+class QWindowsOleDropTarget : public QComObject<IDropTarget>
{
public:
explicit QWindowsOleDropTarget(QWindow *w);
@@ -58,7 +58,6 @@ public:
static QWindowsDrag *instance();
void cancelDrag() override { QWindowsDrag::m_canceled = true; }
static bool isCanceled() { return QWindowsDrag::m_canceled; }
- static bool isDragging() { return QWindowsDrag::m_dragging; }
IDataObject *dropDataObject() const { return m_dropDataObject; }
void setDropDataObject(IDataObject *dataObject) { m_dropDataObject = dataObject; }
@@ -69,7 +68,6 @@ public:
private:
static bool m_canceled;
- static bool m_dragging;
QWindowsDropMimeData m_dropData;
IDataObject *m_dropDataObject = nullptr;
diff --git a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
index fbb2b45e21..629291640f 100644
--- a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
+++ b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
@@ -9,6 +9,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
/*!
\class QWindowsDropDataObject
\brief QWindowsOleDataObject subclass specialized for handling Drag&Drop.
@@ -56,8 +58,8 @@ bool QWindowsDropDataObject::shouldIgnore(LPFORMATETC pformatetc) const
QString formatName = QWindowsMimeRegistry::clipboardFormatName(pformatetc->cfFormat);
if (pformatetc->cfFormat == CF_UNICODETEXT
|| pformatetc->cfFormat == CF_TEXT
- || formatName == QStringLiteral("UniformResourceLocator")
- || formatName == QStringLiteral("UniformResourceLocatorW")) {
+ || formatName == "UniformResourceLocator"_L1
+ || formatName == "UniformResourceLocatorW"_L1) {
const auto urls = dropData->urls();
return std::all_of(urls.cbegin(), urls.cend(), [] (const QUrl &u) { return u.isLocalFile(); });
}
diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp
index cfd73519fa..5ca52c2c19 100644
--- a/src/plugins/platforms/windows/qwindowsglcontext.cpp
+++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp
@@ -1262,7 +1262,6 @@ bool QWindowsGLContext::makeCurrent(QPlatformSurface *surface)
// Do we already have a DC entry for that window?
auto *window = static_cast<QWindowsWindow *>(surface);
- window->aboutToMakeCurrent();
const HWND hwnd = window->handle();
if (const QOpenGLContextData *contextData = findByHWND(m_windowContexts, hwnd)) {
// Repeated calls to wglMakeCurrent when vsync is enabled in the driver will
diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp
new file mode 100644
index 0000000000..5e5ca22ec1
--- /dev/null
+++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp
@@ -0,0 +1,394 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsiconengine.h"
+
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QString QWindowsIconEngine::glyphs() const
+{
+ if (!QFontInfo(m_iconFont).exactMatch())
+ return {};
+
+ static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
+ {"address-book-new"_L1, u"\ue780"},
+ {"application-exit"_L1, u"\ue8bb"},
+ {"appointment-new"_L1, u"\ue878"},
+ {"call-start"_L1, u"\uf715"},
+ {"call-stop"_L1, u"\uf405"},
+ {"contact-new"_L1, u"\ue8fa"},
+ {"document-new"_L1, u"\ue8a5"},
+ {"document-open"_L1, u"\ue8e5"},
+ {"document-open-recent"_L1, u"\ue823"},
+ {"document-page-setup"_L1, u"\ue7c3"},
+ {"document-print"_L1, u"\ue749"},
+ {"document-print-preview"_L1, u"\ue956"},
+ {"document-properties"_L1, u"\ue90f"},
+ {"document-revert"_L1, u"\ue7a7"}, // ?
+ {"document-save"_L1, u"\ue74e"}, // or e78c?
+ {"document-save-as"_L1, u"\ue792"},
+ {"document-send"_L1, u"\ue724"},
+ {"edit-clear"_L1, u"\ue894"},
+ {"edit-copy"_L1, u"\ue8c8"},
+ {"edit-cut"_L1, u"\ue8c6"},
+ {"edit-delete"_L1, u"\ue74d"},
+ {"edit-find"_L1, u"\ue721"},
+ //{"edit-find-replace"_L1, u"\u"},
+ {"edit-paste"_L1, u"\ue77f"},
+ {"edit-redo"_L1, u"\ue7a6"},
+ {"edit-select-all"_L1, u"\ue8b3"},
+ {"edit-undo"_L1, u"\ue7a7"},
+ {"folder-new"_L1, u"\ue8f4"},
+ //{"format-indent-less"_L1, u"\u"},
+ //{"format-indent-more"_L1, u"\u"},
+ {"format-justify-center"_L1, u"\ue8e3"},
+ //{"format-justify-fill"_L1, u"\ue235"},
+ {"format-justify-left"_L1, u"\ue8e4"},
+ {"format-justify-right"_L1, u"\ue8e2"},
+ //{"format-text-direction-ltr"_L1, u"\ue247"},
+ //{"format-text-direction-rtl"_L1, u"\ue248"},
+ {"format-text-bold"_L1, u"\ue8dd"},
+ {"format-text-italic"_L1, u"\ue8db"},
+ {"format-text-underline"_L1, u"\ue8dc"},
+ {"format-text-strikethrough"_L1, u"\uede0"},
+ //{"go-bottom"_L1,u"\ue258"},
+ {"go-down"_L1,u"\ue74b"},
+ //{"go-first"_L1, u"\ue5dc"},
+ {"go-home"_L1, u"\ue80f"},
+ // {"go-jump"_L1, u"\uf719"},
+ //{"go-last"_L1, u"\ue5dd"},
+ {"go-next"_L1, u"\ue893"},
+ {"go-previous"_L1, u"\ue892"},
+ //{"go-top"_L1, u"\ue25a"},
+ {"go-up"_L1, u"\ue74a"},
+ {"help-about"_L1, u"\ue946"},
+ //{"help-contents"_L1, u"\ue8de"},
+ {"help-faq"_L1, u"\ue897"},
+ {"insert-image"_L1, u"\ue946"},
+ {"insert-link"_L1, u"\ue71b"},
+ //{"insert-object"_L1, u"\u"},
+ //{"insert-text"_L1, u"\uf827"},
+ {"list-add"_L1, u"\ue710"},
+ {"list-remove"_L1, u"\ue738"},
+ {"mail-forward"_L1, u"\ue89c"},
+ //{"mail-mark-important"_L1, u"\ue937"},
+ //{"mail-mark-junk"_L1, u"\u"},
+ //{"mail-mark-notjunk"_L1, u"\u"},
+ {"mail-mark-read"_L1, u"\ue8c3"},
+ //{"mail-mark-unread"_L1, u"\ue9bc"},
+ {"mail-message-new"_L1, u"\ue70f"},
+ {"mail-reply-all"_L1, u"\ue8c2"},
+ {"mail-reply-sender"_L1, u"\ue8ca"},
+ {"mail-send"_L1, u"\ue724"},
+ //{"mail-send-receive"_L1, u"\u"},
+ {"media-eject"_L1, u"\uf847"},
+ {"media-playback-pause"_L1, u"\ue769"},
+ {"media-playback-start"_L1, u"\ue768"},
+ {"media-playback-stop"_L1, u"\ue71a"},
+ {"media-record"_L1, u"\ue7c8"},
+ {"media-seek-backward"_L1, u"\ueb9e"},
+ {"media-seek-forward"_L1, u"\ueb9d"},
+ {"media-skip-backward"_L1, u"\ue892"},
+ {"media-skip-forward"_L1, u"\ue893"},
+ //{"object-flip-horizontal"_L1, u"\u"},
+ //{"object-flip-vertical"_L1, u"\u"},
+ {"object-rotate-left"_L1, u"\ue80c"},
+ {"object-rotate-right"_L1, u"\ue80d"},
+ //{"process-stop"_L1, u"\ue5c9"},
+ {"system-lock-screen"_L1, u"\uee3f"},
+ {"system-log-out"_L1, u"\uf3b1"},
+ //{"system-run"_L1, u"\u"},
+ {"system-search"_L1, u"\ue721"},
+ {"system-reboot"_L1, u"\ue777"}, // unsure?
+ {"system-shutdown"_L1, u"\ue7e8"},
+ {"tools-check-spelling"_L1, u"\uf87b"},
+ {"view-fullscreen"_L1, u"\ue740"},
+ {"view-refresh"_L1, u"\ue72c"},
+ {"view-restore"_L1, u"\ue777"},
+ //{"view-sort-ascending"_L1, u"\ue25a"},
+ //{"view-sort-descending"_L1, u"\ue258"},
+ {"window-close"_L1, u"\ue8bb"},
+ {"window-new"_L1, u"\ue78b"},
+ {"zoom-fit-best"_L1, u"\ue9a6"},
+ {"zoom-in"_L1, u"\ue8a3"},
+ //{"zoom-original"_L1, u"\u"},
+ {"zoom-out"_L1, u"\ue71f"},
+
+ {"process-working"_L1, u"\ue9f3"},
+
+ {"accessories-calculator"_L1, u"\ue8ef"},
+ {"accessories-character-map"_L1, u"\uf2b7"},
+ {"accessories-dictionary"_L1, u"\ue82d"},
+ //{"accessories-text-editor"_L1, u"\ue262"},
+ {"help-browser"_L1, u"\ue897"},
+ {"multimedia-volume-control"_L1, u"\ue767"},
+ {"preferences-desktop-accessibility"_L1, u"\ue776"},
+ {"preferences-desktop-font"_L1, u"\ue8d2"},
+ {"preferences-desktop-keyboard"_L1, u"\ue765"},
+ {"preferences-desktop-locale"_L1, u"\uf2b7"},
+ //{"preferences-desktop-multimedia"_L1, u"\uea75"},
+ {"preferences-desktop-screensaver"_L1, u"\uf182"},
+ //{"preferences-desktop-theme"_L1, u"\uf560"},
+ //{"preferences-desktop-wallpaper"_L1, u"\ue1bc"},
+ {"system-file-manager"_L1, u"\uec50"},
+ //{"system-software-install"_L1, u"\ueb71"},
+ {"system-software-update"_L1, u"\uecc5"},
+ {"utilities-system-monitor"_L1, u"\ue7f4"},
+ //{"utilities-terminal"_L1, u"\ueb8e"},
+
+ //{"applications-accessories"_L1, u"\u"},
+ {"applications-development"_L1, u"\uec7a"},
+ //{"applications-engineering"_L1, u"\uea3d"},
+ {"applications-games"_L1, u"\ue7fc"},
+ //{"applications-graphics"_L1, u"\u"},
+ {"applications-internet"_L1, u"\ue774"},
+ {"applications-multimedia"_L1, u"\uea69"},
+ //{"applications-office"_L1, u"\u"},
+ //{"applications-other"_L1, u"\u"},
+ //{"applications-science"_L1, u"\uea4b"},
+ {"applications-system"_L1, u"\ue770"},
+ //{"applications-utilities"_L1, u"\u"},
+ //{"preferences-desktop"_L1, u"\ueb97"},
+ //{"preferences-desktop-peripherals"_L1, u"\u"},
+ //{"preferences-desktop-personal"_L1, u"\uf835"},
+ //{"preferences-other"_L1, u"\u"},
+ //{"preferences-system"_L1, u"\ue8b8"},
+ //{"preferences-system-network"_L1, u"\ue894"},
+ {"system-help"_L1, u"\ue946"},
+
+ {"audio-card"_L1, u"\ue8d6"},
+ {"audio-input-microphone"_L1, u"\ue720"},
+ {"battery"_L1, u"\ue83f"},
+ {"camera-photo"_L1, u"\ue722"},
+ {"camera-video"_L1, u"\ue714"},
+ {"camera-web"_L1, u"\ue8b8"},
+ {"computer"_L1, u"\ue7f8"}, // or e7fb?
+ {"drive-harddisk"_L1, u"\ueda2"},
+ {"drive-optical"_L1, u"\ue958"},
+ //{"drive-removable-media"_L1, u"\u"},
+ //{"input-gaming"_L1, u"\u"},
+ {"input-keyboard"_L1, u"\ue92e"},
+ {"input-mouse"_L1, u"\ue962"},
+ {"input-tablet"_L1, u"\ue70a"},
+ {"media-flash"_L1, u"\ue88e"},
+ //{"media-floppy"_L1, u"\u"},
+ {"media-optical"_L1, u"\ue958"},
+ {"media-tape"_L1, u"\ue96a"},
+ //{"modem"_L1, u"\u"},
+ //{"multimedia-player"_L1, u"\u"},
+ {"network-wired"_L1, u"\ue968"},
+ {"network-wireless"_L1, u"\ue701"},
+ //{"pda"_L1, u"\u"},
+ {"phone"_L1, u"\ue717"},
+ {"printer"_L1, u"\ue749"},
+ {"scanner"_L1, u"\ue8fe"},
+ //{"video-display"_L1, u"\uf06a"},
+
+ {"emblem-default"_L1, u"\uf56d"},
+ {"emblem-documents"_L1, u"\ue8a5"},
+ {"emblem-downloads"_L1, u"\ue896"},
+ {"emblem-favorite"_L1, u"\ue734"},
+ {"emblem-important"_L1, u"\ue8c9"},
+ {"emblem-mail"_L1, u"\ue715"},
+ {"emblem-photos"_L1, u"\ue91b"},
+ //{"emblem-readonly"_L1, u"\u"},
+ {"emblem-shared"_L1, u"\ue902"},
+ {"emblem-symbolic-link"_L1, u"\ue71b"},
+ {"emblem-synchronized"_L1, u"\uedab"},
+ {"emblem-system"_L1, u"\ue770"},
+ //{"emblem-unreadable"_L1, u"\u"},
+
+ {"folder"_L1, u"\ue8b7"},
+ //{"folder-remote"_L1, u"\u"},
+ //{"network-server"_L1, u"\ue875"},
+ //{"network-workgroup"_L1, u"\ue1a0"},
+ {"start-here"_L1, u"\ue8fc"}, // unsure
+ {"user-bookmarks"_L1, u"\ue8a4"},
+ //{"user-desktop"_L1, u"\ue30a"},
+ {"user-home"_L1, u"\ue80f"},
+ {"user-trash"_L1, u"\ue74d"},
+
+ //{"appointment-missed"_L1, u"\ue615"},
+ //{"appointment-soon"_L1, u"\uf540"},
+ {"audio-volume-high"_L1, u"\ue995"},
+ {"audio-volume-low"_L1, u"\ue993"},
+ {"audio-volume-medium"_L1, u"\ue994"},
+ {"audio-volume-muted"_L1, u"\ue992"},
+ //{"battery-caution"_L1, u"\ue19c"},
+ {"battery-low"_L1, u"\ue851"}, // ?
+ {"dialog-error"_L1, u"\ue783"},
+ {"dialog-information"_L1, u"\ue946"},
+ //{"dialog-password"_L1, u"\uf042"},
+ {"dialog-question"_L1, u"\uf142"}, // unsure
+ {"dialog-warning"_L1, u"\ue7ba"},
+ //{"folder-drag-accept"_L1, @u"\ue9a3"},
+ {"folder-open"_L1, u"\ue838"},
+ //{"folder-visiting"_L1, u"\ue8a7"},
+ //{"image-loading"_L1, u"\ue41a"},
+ //{"image-missing"_L1, u"\ue3ad"},
+ {"mail-attachment"_L1, u"\ue723"},
+ //{"mail-unread"_L1, u"\uf18a"},
+ //{"mail-read"_L1, u"\uf18c"},
+ {"mail-replied"_L1, u"\ue8ca"},
+ //{"mail-signed"_L1, u"\u"},
+ //{"mail-signed-verified"_L1, u"\u"},
+ {"media-playlist-repeat"_L1, u"\ue8ee"},
+ {"media-playlist-shuffle"_L1, u"\ue8b1"},
+ //{"network-error"_L1, u"\uead9"},
+ //{"network-idle"_L1, u"\ue51f"},
+ {"network-offline"_L1, u"\uf384"},
+ //{"network-receive"_L1, u"\ue2c0"},
+ //{"network-transmit"_L1, u"\ue2c3"},
+ //{"network-transmit-receive"_L1, u"\uca18"},
+ //{"printer-error"_L1, u"\uf7a0"},
+ //{"printer-printing"_L1, u"\uf7a1"},
+ //{"security-high"_L1, u"\ue32a"},
+ //{"security-medium"_L1, u"\ue9e0"},
+ //{"security-low"_L1, u"\uf012"},
+ //{"software-update-available"_L1, u"\ue923"},
+ //{"software-update-urgent"_L1, u"\uf05a"},
+ {"sync-error"_L1, u"\uea6a"},
+ {"sync-synchronizing"_L1, u"\ue895"},
+ //{"task-due"_L1, u"\u"},
+ //{"task-past-due"_L1, u"\u"},
+ {"user-available"_L1, u"\ue8cf"},
+ //{"user-away"_L1, u"\ue510"},
+ //{"user-idle"_L1, u"\u"},
+ //{"user-offline"_L1, u"\uf7b3"},
+ //{"user-trash-full"_L1, u"\ue872"}, //delete
+ //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever
+ {"weather-clear"_L1, u"\ue706"},
+ //{"weather-clear-night"_L1, u"\uf159"},
+ //{"weather-few-clouds"_L1, u"\uf172"},
+ //{"weather-few-clouds-night"_L1, u"\uf174"},
+ //{"weather-fog"_L1, u"\ue818"},
+ {"weather-overcast"_L1, u"\ue753"},
+ //{"weather-severe-alert"_L1, u"\ue002"}, //warning
+ //{"weather-showers"_L1, u"\uf176"},
+ //{"weather-showers-scattered"_L1, u"\u"},
+ //{"weather-snow"_L1, u"\ue80f"}, //snowing
+ //{"weather-storm"_L1, u"\uf070"},
+ };
+
+ const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){
+ return c.first == m_iconName;
+ });
+
+ return it != std::end(glyphMap) ? it->second.toString()
+ : (m_iconName.length() == 1 ? m_iconName : QString());
+}
+
+namespace {
+auto iconFontFamily()
+{
+ static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
+ return isWindows11 ? u"Segoe Fluent Icons"_s
+ : u"Segoe MDL2 Assets"_s;
+}
+}
+
+QWindowsIconEngine::QWindowsIconEngine(const QString &iconName)
+ : m_iconName(iconName), m_iconFont(iconFontFamily())
+ , m_glyphs(glyphs())
+{
+}
+
+QWindowsIconEngine::~QWindowsIconEngine()
+{}
+
+QIconEngine *QWindowsIconEngine::clone() const
+{
+ return new QWindowsIconEngine(m_iconName);
+}
+
+QString QWindowsIconEngine::key() const
+{
+ return u"QWindowsIconEngine"_s;
+}
+
+QString QWindowsIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QWindowsIconEngine::isNull()
+{
+ if (m_glyphs.isEmpty())
+ return true;
+
+ const QChar c0 = m_glyphs.at(0);
+ const QFontMetrics fontMetrics(m_iconFont);
+ if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1)
+ return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1)));
+ return !fontMetrics.inFont(c0);
+}
+
+QList<QSize> QWindowsIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
+}
+
+QSize QWindowsIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return QIconEngine::actualSize(size, mode, state);
+}
+
+QPixmap QWindowsIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+QPixmap QWindowsIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.fill(Qt::transparent);
+ m_pixmap.setDevicePixelRatio(scale);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(QPoint(), size), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+
+ return m_pixmap;
+}
+
+void QWindowsIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ painter->save();
+ QFont renderFont(m_iconFont);
+ renderFont.setPixelSize(rect.height());
+ painter->setFont(renderFont);
+
+ QPalette palette;
+ switch (mode) {
+ case QIcon::Active:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Normal:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Disabled:
+ painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
+ break;
+ case QIcon::Selected:
+ painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText));
+ break;
+ }
+
+ painter->drawText(rect, Qt::AlignCenter, m_glyphs);
+ painter->restore();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsiconengine.h b/src/plugins/platforms/windows/qwindowsiconengine.h
new file mode 100644
index 0000000000..3c6cbddb8b
--- /dev/null
+++ b/src/plugins/platforms/windows/qwindowsiconengine.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSICONENGINE_H
+#define QWINDOWSICONENGINE_H
+
+#include <QtCore/qt_windows.h>
+
+#include <QtGui/qfont.h>
+#include <QtGui/qiconengine.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindowsIconEngine : public QIconEngine
+{
+public:
+ QWindowsIconEngine(const QString &iconName);
+ ~QWindowsIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+
+ QString glyphs() const;
+
+ const QString m_iconName;
+ const QFont m_iconFont;
+ const QString m_glyphs;
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSICONENGINE_H
diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp
index 758490ffb7..0281025b5b 100644
--- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp
+++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp
@@ -676,7 +676,7 @@ int QWindowsInputContext::reconvertString(RECONVERTSTRING *reconv)
reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
auto *pastReconv = reinterpret_cast<ushort *>(reconv + 1);
std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
- QT_MAKE_UNCHECKED_ARRAY_ITERATOR(pastReconv));
+ pastReconv);
return memSize;
}
diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp
index 073c67effe..aa6be266da 100644
--- a/src/plugins/platforms/windows/qwindowsintegration.cpp
+++ b/src/plugins/platforms/windows/qwindowsintegration.cpp
@@ -62,15 +62,7 @@
#include "qwindowsopengltester.h"
#if QT_CONFIG(cpp_winrt)
-# include <winrt/base.h>
-# include <QtCore/private/qfactorycacheregistration_p.h>
-// Workaround for Windows SDK bug.
-// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
-namespace winrt::impl
-{
- template <typename Async>
- auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
-}
+# include <QtCore/private/qt_winrtbase_p.h>
# include <winrt/Windows.UI.Notifications.h>
# include <winrt/Windows.Data.Xml.Dom.h>
# include <winrt/Windows.Foundation.h>
@@ -88,31 +80,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-/*!
- \class QWindowsIntegration
- \brief QPlatformIntegration implementation for Windows.
- \internal
-
- \section1 Programming Considerations
-
- The platform plugin should run on Desktop Windows from Windows XP onwards
- and Windows Embedded.
-
- It should compile with:
- \list
- \li Microsoft Visual Studio 2013 or later (using the Microsoft Windows SDK,
- (\c Q_CC_MSVC).
- \li Stock \l{http://mingw.org/}{MinGW} (\c Q_CC_MINGW).
- This version ships with headers that are missing a lot of WinAPI.
- \li MinGW distributions using GCC 4.7 or higher and a recent MinGW-w64 runtime API,
- such as \l{http://tdm-gcc.tdragon.net/}{TDM-GCC}, or
- \l{http://mingwbuilds.sourceforge.net/}{MinGW-builds}
- (\c Q_CC_MINGW and \c __MINGW64_VERSION_MAJOR indicating the version).
- MinGW-w64 provides more complete headers (compared to stock MinGW from mingw.org),
- including a considerable part of the Windows SDK.
- \endlist
-*/
-
struct QWindowsIntegrationPrivate
{
Q_DISABLE_COPY_MOVE(QWindowsIntegrationPrivate)
@@ -175,8 +142,8 @@ static inline unsigned parseOptions(const QStringList &paramList,
unsigned options = 0;
for (const QString &param : paramList) {
if (param.startsWith(u"fontengine=")) {
- if (param.endsWith(u"directwrite")) {
- options |= QWindowsIntegration::FontDatabaseDirectWrite;
+ if (param.endsWith(u"gdi")) {
+ options |= QWindowsIntegration::FontDatabaseGDI;
} else if (param.endsWith(u"freetype")) {
options |= QWindowsIntegration::FontDatabaseFreeType;
} else if (param.endsWith(u"native")) {
@@ -289,9 +256,9 @@ QWindowsIntegration::~QWindowsIntegration()
void QWindowsIntegration::initialize()
{
- QString icStr = QPlatformInputContextFactory::requested();
- icStr.isNull() ? d->m_inputContext.reset(new QWindowsInputContext)
- : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ auto icStrs = QPlatformInputContextFactory::requested();
+ icStrs.isEmpty() ? d->m_inputContext.reset(new QWindowsInputContext)
+ : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
}
bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const
@@ -319,6 +286,8 @@ bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) co
return true;
case SwitchableWidgetComposition:
return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734
+ case BackingStoreStaticContents:
+ return true;
default:
return QPlatformIntegration::hasCapability(cap);
}
@@ -515,17 +484,17 @@ QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext()
QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const
{
if (!d->m_fontDatabase) {
-#if QT_CONFIG(directwrite3)
- if (d->m_options & QWindowsIntegration::FontDatabaseDirectWrite)
- d->m_fontDatabase = new QWindowsDirectWriteFontDatabase;
- else
-#endif
#ifndef QT_NO_FREETYPE
if (d->m_options & QWindowsIntegration::FontDatabaseFreeType)
d->m_fontDatabase = new QWindowsFontDatabaseFT;
else
#endif // QT_NO_FREETYPE
- d->m_fontDatabase = new QWindowsFontDatabase();
+#if QT_CONFIG(directwrite3)
+ if (!(d->m_options & (QWindowsIntegration::FontDatabaseGDI | QWindowsIntegration::DontUseDirectWriteFonts)))
+ d->m_fontDatabase = new QWindowsDirectWriteFontDatabase;
+ else
+#endif
+ d->m_fontDatabase = new QWindowsFontDatabase;
}
return d->m_fontDatabase;
}
@@ -573,14 +542,9 @@ QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) co
return QPlatformIntegration::styleHint(hint);
}
-Qt::KeyboardModifiers QWindowsIntegration::queryKeyboardModifiers() const
-{
- return QWindowsKeyMapper::queryKeyboardModifiers();
-}
-
-QList<int> QWindowsIntegration::possibleKeys(const QKeyEvent *e) const
+QPlatformKeyMapper *QWindowsIntegration::keyMapper() const
{
- return d->m_context.possibleKeys(e);
+ return d->m_context.keyMapper();
}
#if QT_CONFIG(clipboard)
@@ -661,12 +625,16 @@ void QWindowsIntegration::setApplicationBadge(qint64 number)
// We prefer the native BadgeUpdater API, that allows us to set a number directly,
// but it requires that the application has a package identity, and also doesn't
// seem to work in all cases on < Windows 11.
- if (isWindows11 && qt_win_hasPackageIdentity()) {
- using namespace winrt::Windows::UI::Notifications;
- auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber);
- badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number)));
- BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml));
- return;
+ QT_TRY {
+ if (isWindows11 && qt_win_hasPackageIdentity()) {
+ using namespace winrt::Windows::UI::Notifications;
+ auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber);
+ badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number)));
+ BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml));
+ return;
+ }
+ } QT_CATCH(...) {
+ // fall back to win32 implementation
}
#endif
@@ -678,7 +646,8 @@ void QWindowsIntegration::setApplicationBadge(qint64 number)
return;
}
- const bool isDarkMode = QWindowsContext::isDarkMode();
+ const bool isDarkMode = QWindowsTheme::instance()->colorScheme()
+ == Qt::ColorScheme::Dark;
QColor badgeColor;
QColor textColor;
@@ -793,7 +762,8 @@ void QWindowsIntegration::updateApplicationBadge()
// to a task bar button being created for the fist time or after
// Explorer had crashed and re-started. In any case, re-apply the
// badge so that everything is up to date.
- setApplicationBadge(m_applicationBadgeNumber);
+ if (m_applicationBadgeNumber)
+ setApplicationBadge(m_applicationBadgeNumber);
}
#if QT_CONFIG(vulkan)
diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h
index 1f3c71c41b..c271207741 100644
--- a/src/plugins/platforms/windows/qwindowsintegration.h
+++ b/src/plugins/platforms/windows/qwindowsintegration.h
@@ -45,7 +45,7 @@ public:
DontUseWMPointer = 0x400,
DetectAltGrModifier = 0x800,
RtlEnabled = 0x1000,
- FontDatabaseDirectWrite = 0x2000
+ FontDatabaseGDI = 0x2000
};
explicit QWindowsIntegration(const QStringList &paramList);
@@ -82,8 +82,7 @@ public:
QPlatformServices *services() const override;
QVariant styleHint(StyleHint hint) const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *e) const override;
+ QPlatformKeyMapper *keyMapper() const override;
static QWindowsIntegration *instance() { return m_instance; }
diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp
index 8f0607d23c..ba76cda40b 100644
--- a/src/plugins/platforms/windows/qwindowskeymapper.cpp
+++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp
@@ -88,9 +88,17 @@ QWindowsKeyMapper::~QWindowsKeyMapper()= default;
#define VK_OEM_3 0xC0
#endif
-// We not only need the scancode itself but also the extended bit of key messages. Thus we need
-// the additional bit when masking the scancode.
-enum { scancodeBitmask = 0x1ff };
+// Get scancode from the given message
+static constexpr quint32 getScancode(const MSG &msg)
+{
+ const auto keyFlags = HIWORD(msg.lParam);
+ quint32 scancode = LOBYTE(keyFlags);
+ // if extended-key flag is on, the scan code consists of a sequence of two bytes,
+ // where the first byte has a value of 0xe0.
+ if ((keyFlags & KF_EXTENDED) != 0)
+ scancode |= 0xE000;
+ return scancode;
+}
// Key recorder ------------------------------------------------------------------------[ start ] --
struct KeyRecord {
@@ -532,33 +540,6 @@ QDebug operator<<(QDebug d, const KeyboardLayoutItem &k)
d << ')';
return d;
}
-
-// Helpers to format a list of int as Qt key sequence
-class formatKeys
-{
-public:
- explicit formatKeys(const QList<int> &keys) : m_keys(keys) {}
-
-private:
- friend QDebug operator<<(QDebug d, const formatKeys &keys);
- const QList<int> &m_keys;
-};
-
-QDebug operator<<(QDebug d, const formatKeys &k)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << '(';
- for (int i =0, size = k.m_keys.size(); i < size; ++i) {
- if (i)
- d << ", ";
- d << QKeySequence(k.m_keys.at(i));
- }
- d << ')';
- return d;
-}
-#else // !QT_NO_DEBUG_STREAM
-static int formatKeys(const QList<int> &) { return 0; }
#endif // QT_NO_DEBUG_STREAM
/**
@@ -656,7 +637,7 @@ void QWindowsKeyMapper::updateKeyMap(const MSG &msg)
{
unsigned char kbdBuffer[256]; // Will hold the complete keyboard state
GetKeyboardState(kbdBuffer);
- const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask;
+ const quint32 scancode = getScancode(msg);
updatePossibleKeyCodes(kbdBuffer, scancode, quint32(msg.wParam));
}
@@ -942,7 +923,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
m_seenAltGr = true;
const UINT msgType = msg.message;
- const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask;
+ const quint32 scancode = getScancode(msg);
auto vk_key = quint32(msg.wParam);
quint32 nModifiers = 0;
@@ -977,7 +958,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
// A multi-character key or a Input method character
// not found by our look-ahead
if (msgType == WM_CHAR || msgType == WM_IME_CHAR) {
- sendExtendedPressRelease(receiver, msg.time, 0, Qt::KeyboardModifier(state), scancode, vk_key, nModifiers, messageKeyText(msg), false);
+ sendExtendedPressRelease(receiver, msg.time, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false);
return true;
}
@@ -1339,7 +1320,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
return result;
}
-Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers()
+Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() const
{
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
if (GetKeyState(VK_SHIFT) < 0)
@@ -1353,9 +1334,9 @@ Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers()
return modifiers;
}
-QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const
+QList<QKeyCombination> QWindowsKeyMapper::possibleKeyCombinations(const QKeyEvent *e) const
{
- QList<int> result;
+ QList<QKeyCombination> result;
const quint32 nativeVirtualKey = e->nativeVirtualKey();
@@ -1369,31 +1350,34 @@ QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const
quint32 baseKey = kbItem.qtKey[0];
Qt::KeyboardModifiers keyMods = e->modifiers();
if (baseKey == Qt::Key_Return && (e->nativeModifiers() & ExtendedKey)) {
- result << (Qt::Key_Enter | keyMods).toCombined();
+ result << (Qt::Key_Enter | keyMods);
return result;
}
- result << int(baseKey) + int(keyMods); // The base key is _always_ valid, of course
+
+ // The base key is _always_ valid, of course
+ result << QKeyCombination::fromCombined(int(baseKey) + int(keyMods));
for (size_t i = 1; i < NumMods; ++i) {
Qt::KeyboardModifiers neededMods = ModsTbl[i];
quint32 key = kbItem.qtKey[i];
if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) {
const Qt::KeyboardModifiers missingMods = keyMods & ~neededMods;
- const int matchedKey = int(key) + int(missingMods);
- const auto it =
- std::find_if(result.begin(), result.end(),
- [key] (int k) { return (k & ~Qt::KeyboardModifierMask) == key; });
+ const auto matchedKey = QKeyCombination::fromCombined(int(key) + int(missingMods));
+ const auto it = std::find_if(result.begin(), result.end(),
+ [key](auto keyCombination) {
+ return keyCombination.key() == key;
+ });
// QTBUG-67200: Use the match with the least modifiers (prefer
// Shift+9 over Alt + Shift + 9) resulting in more missing modifiers.
if (it == result.end())
result << matchedKey;
- else if (missingMods > Qt::KeyboardModifiers(*it & Qt::KeyboardModifierMask))
+ else if (missingMods > it->keyboardModifiers())
*it = matchedKey;
}
}
qCDebug(lcQpaEvents) << __FUNCTION__ << e << "nativeVirtualKey="
<< Qt::showbase << Qt::hex << e->nativeVirtualKey() << Qt::dec << Qt::noshowbase
- << e->modifiers() << kbItem << "\n returns" << formatKeys(result);
+ << e->modifiers() << kbItem << "\n returns" << result;
return result;
}
diff --git a/src/plugins/platforms/windows/qwindowskeymapper.h b/src/plugins/platforms/windows/qwindowskeymapper.h
index 6e13c47323..72b2536ad7 100644
--- a/src/plugins/platforms/windows/qwindowskeymapper.h
+++ b/src/plugins/platforms/windows/qwindowskeymapper.h
@@ -8,6 +8,8 @@
#include <QtCore/qlocale.h>
+#include <qpa/qplatformkeymapper.h>
+
QT_BEGIN_NAMESPACE
class QKeyEvent;
@@ -33,7 +35,7 @@ struct KeyboardLayoutItem {
quint32 qtKey[NumQtKeys]; // Can by any Qt::Key_<foo>, or unicode character
};
-class QWindowsKeyMapper
+class QWindowsKeyMapper : public QPlatformKeyMapper
{
Q_DISABLE_COPY_MOVE(QWindowsKeyMapper)
public:
@@ -53,8 +55,8 @@ public:
QWindow *keyGrabber() const { return m_keyGrabber; }
void setKeyGrabber(QWindow *w) { m_keyGrabber = w; }
- static Qt::KeyboardModifiers queryKeyboardModifiers();
- QList<int> possibleKeys(const QKeyEvent *e) const;
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *e) const override;
private:
bool translateKeyEventInternal(QWindow *receiver, MSG msg, bool grab, LRESULT *lResult);
diff --git a/src/plugins/platforms/windows/qwindowsmenu.cpp b/src/plugins/platforms/windows/qwindowsmenu.cpp
index 403ce52cb0..79deeeaea2 100644
--- a/src/plugins/platforms/windows/qwindowsmenu.cpp
+++ b/src/plugins/platforms/windows/qwindowsmenu.cpp
@@ -6,6 +6,7 @@
#include "qwindowswindow.h"
#include <QtGui/qwindow.h>
+#include <QtGui/private/qpixmap_win_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
#include <QtCore/qmetaobject.h>
@@ -229,8 +230,6 @@ void QWindowsMenuItem::setIcon(const QIcon &icon)
updateBitmap();
}
-Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0);
-
void QWindowsMenuItem::updateBitmap()
{
freeBitmap();
diff --git a/src/plugins/platforms/windows/qwindowsmenu.h b/src/plugins/platforms/windows/qwindowsmenu.h
index 646ab64894..6f66180d82 100644
--- a/src/plugins/platforms/windows/qwindowsmenu.h
+++ b/src/plugins/platforms/windows/qwindowsmenu.h
@@ -9,7 +9,6 @@
#include <qpa/qplatformmenu.h>
#include <QtCore/qlist.h>
-#include <QtCore/qpair.h>
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp
index 64b5dc463e..8d147e8fa0 100644
--- a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp
+++ b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp
@@ -154,7 +154,7 @@ static bool qt_write_dibv5(QDataStream &s, QImage image)
return false;
if (image.format() != QImage::Format_ARGB32)
- image = image.convertToFormat(QImage::Format_ARGB32);
+ image = std::move(image).convertToFormat(QImage::Format_ARGB32);
auto *buf = new uchar[bpl_bmp];
@@ -347,7 +347,7 @@ public:
bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
- return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText();
+ return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
}
/*
@@ -448,7 +448,8 @@ QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const
QList<FORMATETC> formatics;
if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
formatics += setCf(CF_UNICODETEXT);
- formatics += setCf(CF_TEXT);
+ if (GetACP() != CP_UTF8)
+ formatics += setCf(CF_TEXT);
}
return formatics;
}
@@ -869,7 +870,7 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD
QByteArray ba;
if (cf == CF_DIB) {
if (img.format() > QImage::Format_ARGB32)
- img = img.convertToFormat(QImage::Format_RGB32);
+ img = std::move(img).convertToFormat(QImage::Format_RGB32);
const QByteArray ba = writeDib(img);
if (!ba.isEmpty())
return setData(ba, pmedium);
@@ -882,7 +883,7 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD
} else {
QDataStream s(&ba, QIODevice::WriteOnly);
s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
- if (qt_write_dibv5(s, img))
+ if (qt_write_dibv5(s, std::move(img)))
return setData(ba, pmedium);
}
}
diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
index 8d57e75c96..9af9fba408 100644
--- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp
+++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
@@ -268,7 +268,8 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
const MouseEvent mouseEvent = eventFromMsg(msg);
Qt::MouseButtons buttons;
@@ -286,19 +287,16 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
&& (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
&& (m_lastEventButton & buttons) == 0) {
- if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton,
- QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
- } else {
- QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton,
- QEvent::MouseButtonRelease, keyModifiers, source);
- }
+ auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
+ QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton,
+ releaseType, keyModifiers, source);
}
m_lastEventType = mouseEvent.type;
m_lastEventButton = mouseEvent.button;
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, msg.time, device, clientPosition,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition,
globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
keyModifiers, source);
@@ -633,11 +631,12 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND,
if (allStates == QEventPoint::State::Released)
m_touchInputIDToTouchPointID.clear();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
QWindowSystemInterface::handleTouchEvent(window,
msg.time,
m_touchDevice.data(),
touchPoints,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper->queryKeyboardModifiers());
return true;
}
diff --git a/src/plugins/platforms/windows/qwindowsole.h b/src/plugins/platforms/windows/qwindowsole.h
index b687384684..016f9dd04c 100644
--- a/src/plugins/platforms/windows/qwindowsole.h
+++ b/src/plugins/platforms/windows/qwindowsole.h
@@ -4,12 +4,12 @@
#ifndef QWINDOWSOLE_H
#define QWINDOWSOLE_H
-#include "qwindowscombase.h"
#include <QtCore/qt_windows.h>
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
#include <QtCore/qpointer.h>
+#include <QtCore/private/qcomobject_p.h>
#include <objidl.h>
@@ -18,7 +18,7 @@ QT_BEGIN_NAMESPACE
class QMimeData;
class QWindow;
-class QWindowsOleDataObject : public QWindowsComBase<IDataObject>
+class QWindowsOleDataObject : public QComObject<IDataObject>
{
public:
explicit QWindowsOleDataObject(QMimeData *mimeData);
@@ -47,7 +47,7 @@ private:
DWORD performedEffect = DROPEFFECT_NONE;
};
-class QWindowsOleEnumFmtEtc : public QWindowsComBase<IEnumFORMATETC>
+class QWindowsOleEnumFmtEtc : public QComObject<IEnumFORMATETC>
{
public:
explicit QWindowsOleEnumFmtEtc(const QList<FORMATETC> &fmtetcs);
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
index e2bf98dc38..71c7217671 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
@@ -428,8 +428,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
return false;
if (msg.message == WM_POINTERCAPTURECHANGED) {
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(),
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper->queryKeyboardModifiers());
m_lastTouchPoints.clear();
return true;
}
@@ -441,6 +442,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (id != -1)
m_lastTouchPoints.remove(id);
}
+ // Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
+ QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
}
// Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
@@ -537,8 +540,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (allStates == QEventPoint::State::Released)
m_touchInputIDToTouchPointID.clear();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper->queryKeyboardModifiers());
return false; // Allow mouse messages to be generated.
}
@@ -671,7 +675,8 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
wumPlatformWindow->applyCursor();
}
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(),
localPos, hiResGlobalPos, mouseButtons,
@@ -737,20 +742,20 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
{
*result = 0;
- QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
- if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) {
- RECT clientArea;
- GetClientRect(hwnd, &clientArea);
- eventPos.setX(clientArea.right - eventPos.x());
- }
-
QPoint localPos;
QPoint globalPos;
+ QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
globalPos = eventPos;
localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
} else {
+ if (QWindowsBaseWindow::isRtlLayout(hwnd)) {
+ RECT clientArea;
+ GetClientRect(hwnd, &clientArea);
+ eventPos.setX(clientArea.right - eventPos.x());
+ }
+
globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
auto targetHwnd = hwnd;
if (auto *pw = window->handle())
@@ -760,7 +765,8 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
: QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
if (et == QtWindows::MouseWheelEvent)
@@ -820,19 +826,16 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
&& (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
&& (m_lastEventButton & mouseButtons) == 0) {
- if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
- QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
- } else {
- QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
- QEvent::MouseButtonRelease, keyModifiers, source);
- }
+ auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
+ QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
+ releaseType, keyModifiers, source);
}
m_lastEventType = mouseEvent.type;
m_lastEventButton = mouseEvent.button;
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
mouseEvent.button, mouseEvent.type, keyModifiers, source);
return false; // Allow further event processing
}
diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
index f15a7a870d..a50f9fd4b0 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.cpp
+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
@@ -14,12 +14,22 @@
#include <QtGui/qpixmap.h>
#include <QtGui/qguiapplication.h>
#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/private/qsystemerror_p.h>
+#include <QtGui/private/qedidparser_p.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qwindowsfontdatabasebase_p.h>
+#include <private/qpixmap_win_p.h>
+#include <private/quniquehandle_p.h>
+
#include <QtGui/qscreen.h>
#include <QtCore/qdebug.h>
+#include <memory>
+#include <type_traits>
+
+#include <cfgmgr32.h>
+#include <setupapi.h>
#include <shellscalingapi.h>
QT_BEGIN_NAMESPACE
@@ -40,7 +50,7 @@ static inline QDpi monitorDPI(HMONITOR hMonitor)
return {0, 0};
}
-static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *pathInfo)
+static std::vector<DISPLAYCONFIG_PATH_INFO> getPathInfo(const MONITORINFOEX &viewInfo)
{
// We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO.
std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos;
@@ -56,7 +66,7 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
// look up the needed buffer sizes.
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements,
&numModeInfoArrayElements) != ERROR_SUCCESS) {
- return false;
+ return {};
}
pathInfos.resize(numPathArrayElements);
modeInfos.resize(numModeInfoArrayElements);
@@ -65,24 +75,25 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result != ERROR_SUCCESS)
- return false;
-
- // Find path matching monitor name
- for (uint32_t p = 0; p < numPathArrayElements; p++) {
- DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
- deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
- deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
- deviceName.header.adapterId = pathInfos[p].sourceInfo.adapterId;
- deviceName.header.id = pathInfos[p].sourceInfo.id;
- if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
- if (wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) == 0) {
- *pathInfo = pathInfos[p];
+ return {};
+
+ // Find paths matching monitor name
+ auto discardThese =
+ std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool {
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
+ deviceName.header.adapterId = path.sourceInfo.adapterId;
+ deviceName.header.id = path.sourceInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
+ return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0;
+ }
return true;
- }
- }
- }
+ });
+
+ pathInfos.erase(discardThese, pathInfos.end());
- return false;
+ return pathInfos;
}
#if 0
@@ -106,6 +117,149 @@ static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo)
using WindowsScreenDataList = QList<QWindowsScreenData>;
+namespace {
+
+struct DiRegKeyHandleTraits
+{
+ using Type = HKEY;
+ static Type invalidValue()
+ {
+ // The setupapi.h functions return INVALID_HANDLE_VALUE when failing to open a registry key
+ return reinterpret_cast<HKEY>(INVALID_HANDLE_VALUE);
+ }
+ static bool close(Type handle) { return RegCloseKey(handle) == ERROR_SUCCESS; }
+};
+
+using DiRegKeyHandle = QUniqueHandle<DiRegKeyHandleTraits>;
+
+}
+
+static void setMonitorDataFromSetupApi(QWindowsScreenData &data,
+ const std::vector<DISPLAYCONFIG_PATH_INFO> &pathGroup)
+{
+ if (pathGroup.empty()) {
+ return;
+ }
+
+ // The only property shared among monitors in a clone group is deviceName
+ {
+ DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
+ // The first element in the clone group is the main monitor.
+ deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId;
+ deviceName.header.id = pathGroup[0].targetInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
+ data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath);
+ } else {
+ qCWarning(lcQpaScreen)
+ << u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id)
+ << QSystemError::windowsString();
+ }
+ }
+
+ // The rest must be concatenated into the resulting property
+ QStringList names;
+ QStringList manufacturers;
+ QStringList models;
+ QStringList serialNumbers;
+
+ for (const auto &path : pathGroup) {
+ DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
+ deviceName.header.adapterId = path.targetInfo.adapterId;
+ deviceName.header.id = path.targetInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) {
+ qCWarning(lcQpaScreen)
+ << u"Unable to get device information for %1:"_s.arg(path.targetInfo.id)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor
+ constexpr GUID GUID_DEVINTERFACE_MONITOR = {
+ 0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }
+ };
+ const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr,
+ DIGCF_DEVICEINTERFACE);
+
+ SP_DEVICE_INTERFACE_DATA deviceInterfaceData{};
+ deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
+
+ if (!SetupDiOpenDeviceInterfaceW(devInfo, deviceName.monitorDevicePath, DIODI_NO_ADD,
+ &deviceInterfaceData)) {
+ qCWarning(lcQpaScreen)
+ << u"Unable to open monitor interface to %1:"_s.arg(data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ DWORD requiredSize{ 0 };
+ if (SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, nullptr, 0,
+ &requiredSize, nullptr)
+ || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ continue;
+ }
+
+ const std::unique_ptr<std::byte[]> storage(new std::byte[requiredSize]);
+ auto *devicePath = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W *>(storage.get());
+ devicePath->cbSize = sizeof(std::remove_pointer_t<decltype(devicePath)>);
+ SP_DEVINFO_DATA deviceInfoData{};
+ deviceInfoData.cbSize = sizeof(deviceInfoData);
+ if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, devicePath,
+ requiredSize, nullptr, &deviceInfoData)) {
+ qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ const DiRegKeyHandle edidRegistryKey{ SetupDiOpenDevRegKey(
+ devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) };
+
+ if (!edidRegistryKey.isValid())
+ continue;
+
+ DWORD edidDataSize{ 0 };
+ if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr,
+ &edidDataSize)
+ != ERROR_SUCCESS) {
+ continue;
+ }
+
+ QByteArray edidData;
+ edidData.resize(edidDataSize);
+
+ if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr,
+ reinterpret_cast<unsigned char *>(edidData.data()), &edidDataSize)
+ != ERROR_SUCCESS) {
+ qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg(
+ data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ QEdidParser edid;
+
+ if (!edid.parse(edidData)) {
+ qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName;
+ continue;
+ }
+
+ // We skip edid.identifier because it is unreliable, and a better option
+ // is already available through DisplayConfigGetDeviceInfo (see below).
+ names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
+ manufacturers << edid.manufacturer;
+ models << edid.model;
+ serialNumbers << edid.serialNumber;
+ }
+
+ data.name = names.join(u"|"_s);
+ data.manufacturer = manufacturers.join(u"|"_s);
+ data.model = models.join(u"|"_s);
+ data.serialNumber = serialNumbers.join(u"|"_s);
+}
+
static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
{
MONITORINFOEX info;
@@ -118,16 +272,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1));
data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1));
data->deviceName = QString::fromWCharArray(info.szDevice);
- DISPLAYCONFIG_PATH_INFO pathInfo = {};
- const bool hasPathInfo = getPathInfo(info, &pathInfo);
- if (hasPathInfo) {
- DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
- deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
- deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
- deviceName.header.adapterId = pathInfo.targetInfo.adapterId;
- deviceName.header.id = pathInfo.targetInfo.id;
- if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS)
- data->name = QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
+ const auto pathGroup = getPathInfo(info);
+ if (!pathGroup.empty()) {
+ setMonitorDataFromSetupApi(*data, pathGroup);
}
if (data->name.isEmpty())
data->name = data->deviceName;
@@ -153,7 +300,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
// ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO,
// if we are going to use DISPLAYCONFIG lookups more.
- if (hasPathInfo) {
+ if (!pathGroup.empty()) {
+ // The first element in the clone group is the main monitor.
+ const auto &pathInfo = pathGroup[0];
switch (pathInfo.targetInfo.rotation) {
case DISPLAYCONFIG_ROTATION_IDENTITY:
data->orientation = Qt::LandscapeOrientation;
@@ -229,15 +378,14 @@ static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d)
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg.noquote();
- dbg << "Screen \"" << d.name << "\" "
- << d.geometry.width() << 'x' << d.geometry.height() << '+' << d.geometry.x() << '+' << d.geometry.y()
- << " avail: "
- << d.availableGeometry.width() << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+' << d.availableGeometry.y()
- << " physical: " << d.physicalSizeMM.width() << 'x' << d.physicalSizeMM.height()
- << " DPI: " << d.dpi.first << 'x' << d.dpi.second << " Depth: " << d.depth
- << " Format: " << d.format
- << " hMonitor: " << d.hMonitor
- << " device name: " << d.deviceName;
+ dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+'
+ << d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width()
+ << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+'
+ << d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x'
+ << d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second
+ << " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor
+ << " device name: " << d.deviceName << " manufacturer: " << d.manufacturer
+ << " model: " << d.model << " serial number: " << d.serialNumber;
if (d.flags & QWindowsScreenData::PrimaryScreen)
dbg << " primary";
if (d.flags & QWindowsScreenData::VirtualDesktop)
@@ -270,8 +418,6 @@ QString QWindowsScreen::name() const
: m_data.name;
}
-Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0);
-
QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
{
QSize windowSize;
@@ -390,10 +536,14 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first)
|| !qFuzzyCompare(m_data.dpi.second, newData.dpi.second);
const bool orientationChanged = m_data.orientation != newData.orientation;
+ const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen)
+ && !(m_data.flags & QWindowsScreenData::PrimaryScreen);
m_data.dpi = newData.dpi;
m_data.orientation = newData.orientation;
m_data.geometry = newData.geometry;
m_data.availableGeometry = newData.availableGeometry;
+ m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen)
+ | (newData.flags & QWindowsScreenData::PrimaryScreen);
if (dpiChanged) {
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(),
@@ -406,6 +556,8 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
QWindowSystemInterface::handleScreenGeometryChange(screen(),
newData.geometry, newData.availableGeometry);
}
+ if (primaryChanged)
+ QWindowSystemInterface::handlePrimaryScreenChanged(this);
}
HMONITOR QWindowsScreen::handle() const
@@ -517,7 +669,8 @@ extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT
if (QWindowsTheme *t = QWindowsTheme::instance())
t->displayChanged();
QWindowsWindow::displayChanged();
- QWindowsContext::instance()->screenManager().handleScreenChanges();
+ if (auto *context = QWindowsContext::instance())
+ context->screenManager().handleScreenChanges();
}
return DefWindowProc(hwnd, message, wParam, lParam);
diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
index b1c94d2204..0467ab2a0c 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.h
+++ b/src/plugins/platforms/windows/qwindowsscreen.h
@@ -7,10 +7,9 @@
#include "qtwindowsglobal.h"
#include <QtCore/qlist.h>
-#include <QtCore/qpair.h>
#include <QtCore/qscopedpointer.h>
#include <qpa/qplatformscreen.h>
-#include <qpa/qplatformscreen_p.h>
+#include <QtGui/qscreen_platform.h>
QT_BEGIN_NAMESPACE
@@ -31,15 +30,18 @@ struct QWindowsScreenData
QImage::Format format = QImage::Format_ARGB32_Premultiplied;
unsigned flags = VirtualDesktop;
QString name;
+ QString manufacturer;
+ QString model;
+ QString serialNumber;
Qt::ScreenOrientation orientation = Qt::LandscapeOrientation;
qreal refreshRateHz = 60;
HMONITOR hMonitor = nullptr;
- QString deviceName = {};
+ QString deviceName;
+ QString devicePath;
std::optional<int> deviceIndex = std::nullopt;
};
-class QWindowsScreen : public QPlatformScreen
- , public QNativeInterface::Private::QWindowsScreen
+class QWindowsScreen : public QPlatformScreen, public QNativeInterface::QWindowsScreen
{
public:
#ifndef QT_NO_CURSOR
@@ -58,6 +60,9 @@ public:
qreal devicePixelRatio() const override { return 1.0; }
qreal refreshRate() const override { return m_data.refreshRateHz; }
QString name() const override;
+ QString manufacturer() const override { return m_data.manufacturer; }
+ QString model() const override { return m_data.model; }
+ QString serialNumber() const override { return m_data.serialNumber; }
Qt::ScreenOrientation orientation() const override { return m_data.orientation; }
QList<QPlatformScreen *> virtualSiblings() const override;
QWindow *topLevelAt(const QPoint &point) const override;
diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp
index 2c84204d2b..89f93fd161 100644
--- a/src/plugins/platforms/windows/qwindowsservices.cpp
+++ b/src/plugins/platforms/windows/qwindowsservices.cpp
@@ -1,7 +1,6 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#define QT_NO_URL_CAST_FROM_STRING
#include "qwindowsservices.h"
#include <QtCore/qt_windows.h>
@@ -155,10 +154,15 @@ static inline bool launchMail(const QUrl &url)
qWarning("Cannot launch '%ls': There is no mail program installed.", qUtf16Printable(url.toString()));
return false;
}
+ // Fix mail launch if no param is expected in this command.
+ if (command.indexOf("%1"_L1) < 0) {
+ qWarning() << "The mail command lacks the '%1' parameter.";
+ return false;
+ }
//Make sure the path for the process is in quotes
const QChar doubleQuote = u'"';
if (!command.startsWith(doubleQuote)) {
- const int exeIndex = command.indexOf(QStringLiteral(".exe "), 0, Qt::CaseInsensitive);
+ const int exeIndex = command.indexOf(".exe "_L1, 0, Qt::CaseInsensitive);
if (exeIndex != -1) {
command.insert(exeIndex + 4, doubleQuote);
command.prepend(doubleQuote);
diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
index 3bad237f9e..6f0680ac23 100644
--- a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
+++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
@@ -24,6 +24,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
static const UINT q_uNOTIFYICONID = 0;
static uint MYWM_TASKBARCREATED = 0;
@@ -116,7 +118,7 @@ static inline HWND createTrayIconMessageWindow()
return nullptr;
// Register window class in the platform plugin.
const QString className =
- ctx->registerWindowClass(QWindowsContext::classNamePrefix() + QStringLiteral("TrayIconMessageWindowClass"),
+ ctx->registerWindowClass(QWindowsContext::classNamePrefix() + "TrayIconMessageWindowClass"_L1,
qWindowsTrayIconWndProc);
const wchar_t windowName[] = L"QTrayIconMessageWindow";
return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()),
@@ -162,8 +164,6 @@ void QWindowsSystemTrayIcon::cleanup()
void QWindowsSystemTrayIcon::updateIcon(const QIcon &icon)
{
qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this;
- if (icon.cacheKey() == m_icon.cacheKey())
- return;
m_icon = icon;
const HICON hIconToDestroy = createIcon(icon);
if (ensureInstalled())
@@ -184,6 +184,9 @@ void QWindowsSystemTrayIcon::updateToolTip(const QString &tooltip)
QRect QWindowsSystemTrayIcon::geometry() const
{
+ if (!isIconVisible())
+ return QRect();
+
NOTIFYICONIDENTIFIER nid;
memset(&nid, 0, sizeof(nid));
nid.cbSize = sizeof(nid);
@@ -217,25 +220,19 @@ void QWindowsSystemTrayIcon::showMessage(const QString &title, const QString &me
qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64);
tnd.uID = q_uNOTIFYICONID;
- tnd.dwInfoFlags = NIIF_USER;
-
- QSize size(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
- const QSize largeIcon(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
- const QSize more = icon.actualSize(largeIcon);
- if (more.height() > (largeIcon.height() * 3/4) || more.width() > (largeIcon.width() * 3/4)) {
- tnd.dwInfoFlags |= NIIF_LARGE_ICON;
- size = largeIcon;
- }
+
+ const auto size = icon.actualSize(QSize(256, 256));
QPixmap pm = icon.pixmap(size);
+ if (m_hMessageIcon) {
+ DestroyIcon(m_hMessageIcon);
+ m_hMessageIcon = nullptr;
+ }
if (pm.isNull()) {
tnd.dwInfoFlags = NIIF_INFO;
} else {
- if (pm.size() != size) {
- qWarning("QSystemTrayIcon::showMessage: Wrong icon size (%dx%d), please add standard one: %dx%d",
- pm.size().width(), pm.size().height(), size.width(), size.height());
- pm = pm.scaled(size, Qt::IgnoreAspectRatio);
- }
- tnd.hBalloonIcon = qt_pixmapToWinHICON(pm);
+ tnd.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
+ m_hMessageIcon = qt_pixmapToWinHICON(pm);
+ tnd.hBalloonIcon = m_hMessageIcon;
}
tnd.hWnd = m_hwnd;
tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default
@@ -293,7 +290,10 @@ void QWindowsSystemTrayIcon::ensureCleanup()
}
if (m_hIcon != nullptr)
DestroyIcon(m_hIcon);
+ if (m_hMessageIcon != nullptr)
+ DestroyIcon(m_hMessageIcon);
m_hIcon = nullptr;
+ m_hMessageIcon = nullptr;
m_menu = nullptr; // externally owned
m_toolTip.clear();
}
@@ -310,6 +310,29 @@ bool QWindowsSystemTrayIcon::setIconVisible(bool visible)
return Shell_NotifyIcon(NIM_MODIFY, &tnd) == TRUE;
}
+bool QWindowsSystemTrayIcon::isIconVisible() const
+{
+ NOTIFYICONIDENTIFIER nid;
+ memset(&nid, 0, sizeof(nid));
+ nid.cbSize = sizeof(nid);
+ nid.hWnd = m_hwnd;
+ nid.uID = q_uNOTIFYICONID;
+ RECT rect;
+ const HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect);
+ // Windows 10 returns S_FALSE if the icon is hidden
+ if (FAILED(hr) || hr == S_FALSE)
+ return false;
+
+ HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(monitor, &info);
+ // Windows 11 seems to return a geometry outside of the current monitor's geometry in case of
+ // the icon being hidden. As it's impossible to change the alignment of the task bar on Windows
+ // 11 this check should be fine.
+ return rect.bottom <= info.rcMonitor.bottom;
+}
+
bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg)
{
NOTIFYICONDATA tnd;
diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.h b/src/plugins/platforms/windows/qwindowssystemtrayicon.h
index 2545ae9101..3ad5feb125 100644
--- a/src/plugins/platforms/windows/qwindowssystemtrayicon.h
+++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.h
@@ -49,12 +49,14 @@ private:
void ensureCleanup();
bool sendTrayMessage(DWORD msg);
bool setIconVisible(bool visible);
+ bool isIconVisible() const;
HICON createIcon(const QIcon &icon);
QIcon m_icon;
QString m_toolTip;
HWND m_hwnd = nullptr;
HICON m_hIcon = nullptr;
+ HICON m_hMessageIcon = nullptr;
mutable QPointer<QWindowsPopupMenu> m_menu;
bool m_ignoreNextMouseRelease = false;
bool m_visible = false;
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
index b7cc2bf77b..ceebb483d2 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
@@ -595,7 +595,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
<< "mode=" << m_mode;
}
- const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyboardModifiers = keyMapper->queryKeyboardModifiers();
for (int i = 0; i < packetCount ; ++i) {
const PACKET &packet = localPacketBuf[i];
diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp
index 7535c7a583..e8a324aedb 100644
--- a/src/plugins/platforms/windows/qwindowstheme.cpp
+++ b/src/plugins/platforms/windows/qwindowstheme.cpp
@@ -7,26 +7,29 @@
#include "qwindowsmenu.h"
#include "qwindowsdialoghelpers.h"
#include "qwindowscontext.h"
+#include "qwindowsiconengine.h"
#include "qwindowsintegration.h"
#if QT_CONFIG(systemtrayicon)
# include "qwindowssystemtrayicon.h"
#endif
#include "qwindowsscreen.h"
+#include "qwindowswindow.h"
#include <commctrl.h>
#include <objbase.h>
-#ifndef Q_CC_MINGW
-# include <commoncontrols.h>
-#endif
+#include <commoncontrols.h>
#include <shellapi.h>
+#include <QtCore/qapplicationstatic.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qcache.h>
#include <QtCore/qthread.h>
+#include <QtCore/qqueue.h>
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
+#include <QtCore/qoperatingsystemversion.h>
#include <QtGui/qcolor.h>
#include <QtGui/qpalette.h>
#include <QtGui/qguiapplication.h>
@@ -48,10 +51,6 @@
# include <winrt/Windows.UI.ViewManagement.h>
#endif // QT_CONFIG(cpp_winrt)
-#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__)
-# define USE_IIMAGELIST
-#endif
-
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -79,11 +78,11 @@ static inline QColor mixColors(const QColor &c1, const QColor &c2)
(c1.blue() + c2.blue()) / 2};
}
-static inline QColor getSysColor(int index)
-{
- COLORREF cr = GetSysColor(index);
- return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
-}
+enum AccentColorLevel {
+ AccentColorDarkest,
+ AccentColorNormal,
+ AccentColorLightest
+};
#if QT_CONFIG(cpp_winrt)
static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
@@ -92,111 +91,146 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
}
#endif
-// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
-// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
-// behavior by running it in a thread.
+[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor(AccentColorLevel level)
+{
+#if QT_CONFIG(cpp_winrt)
+ using namespace winrt::Windows::UI::ViewManagement;
+ const auto settings = UISettings();
+ const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+ const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+ const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+#else
+ const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
+ if (!registry.isValid())
+ return {};
+ const QVariant value = registry.value(L"AccentColor");
+ if (!value.isValid())
+ return {};
+ // The retrieved value is in the #AABBGGRR format, we need to
+ // convert it to the #AARRGGBB format which Qt expects.
+ const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value));
+ if (!abgr.isValid())
+ return {};
+ const QColor accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
+ const QColor accentLight = accent.lighter(120);
+ const QColor accentDarkest = accent.darker(120 * 120 * 120);
+#endif
+ if (level == AccentColorDarkest)
+ return accentDarkest;
+ else if (level == AccentColorLightest)
+ return accentLight;
+ return accent;
+}
-struct QShGetFileInfoParams
+static inline QColor getSysColor(int index)
{
- QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r)
- : fileName(fn), attributes(a), flags(f), info(i), result(r)
- { }
-
- const QString &fileName;
- const DWORD attributes;
- const UINT flags;
- SHFILEINFO *const info;
- bool *const result;
-};
+ COLORREF cr = GetSysColor(index);
+ return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
+}
+// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
+// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
+// behavior by running it in a thread.
class QShGetFileInfoThread : public QThread
{
public:
- explicit QShGetFileInfoThread()
- : QThread(), m_params(nullptr)
+ struct Task
+ {
+ Task(const QString &fn, DWORD a, UINT f)
+ : fileName(fn), attributes(a), flags(f)
+ {}
+ Q_DISABLE_COPY(Task)
+ ~Task()
+ {
+ DestroyIcon(hIcon);
+ hIcon = 0;
+ }
+ // Request
+ const QString fileName;
+ const DWORD attributes;
+ const UINT flags;
+ // Result
+ HICON hIcon = 0;
+ int iIcon = -1;
+ bool finished = false;
+ bool resultValid() const { return hIcon != 0 && iIcon >= 0 && finished; }
+ };
+
+ QShGetFileInfoThread()
+ : QThread()
+ {
+ start();
+ }
+
+ ~QShGetFileInfoThread()
+ {
+ cancel();
+ wait();
+ }
+
+ QSharedPointer<Task> getNextTask()
{
- connect(this, &QThread::finished, this, &QObject::deleteLater);
+ QMutexLocker l(&m_waitForTaskMutex);
+ while (!isInterruptionRequested()) {
+ if (!m_taskQueue.isEmpty())
+ return m_taskQueue.dequeue();
+ m_waitForTaskCondition.wait(&m_waitForTaskMutex);
+ }
+ return nullptr;
}
void run() override
{
QComHelper comHelper(COINIT_MULTITHREADED);
- QMutexLocker readyLocker(&m_readyMutex);
- while (!m_cancelled.loadRelaxed()) {
- if (!m_params && !m_cancelled.loadRelaxed()
- && !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll)))
- continue;
-
- if (m_params) {
- const QString fileName = m_params->fileName;
+ while (!isInterruptionRequested()) {
+ auto task = getNextTask();
+ if (task) {
SHFILEINFO info;
- const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()),
- m_params->attributes, &info, sizeof(SHFILEINFO),
- m_params->flags);
- m_doneMutex.lock();
- if (!m_cancelled.loadRelaxed()) {
- *m_params->result = result;
- memcpy(m_params->info, &info, sizeof(SHFILEINFO));
+ const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(task->fileName.utf16()),
+ task->attributes, &info, sizeof(SHFILEINFO),
+ task->flags);
+ if (result) {
+ task->hIcon = info.hIcon;
+ task->iIcon = info.iIcon;
}
- m_params = nullptr;
-
+ task->finished = true;
m_doneCondition.wakeAll();
- m_doneMutex.unlock();
}
}
}
- bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs)
+ void runWithParams(const QSharedPointer<Task> &task,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds(5000))
{
- QMutexLocker doneLocker(&m_doneMutex);
-
- m_readyMutex.lock();
- m_params = params;
- m_readyCondition.wakeAll();
- m_readyMutex.unlock();
+ {
+ QMutexLocker l(&m_waitForTaskMutex);
+ m_taskQueue.enqueue(task);
+ m_waitForTaskCondition.wakeAll();
+ }
- return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs));
+ QMutexLocker doneLocker(&m_doneMutex);
+ while (!task->finished && !isInterruptionRequested()) {
+ if (!m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeout)))
+ return;
+ }
}
void cancel()
{
- QMutexLocker doneLocker(&m_doneMutex);
- m_cancelled.storeRelaxed(1);
- m_readyCondition.wakeAll();
+ requestInterruption();
+ m_doneCondition.wakeAll();
+ m_waitForTaskCondition.wakeAll();
}
private:
- QShGetFileInfoParams *m_params;
- QAtomicInt m_cancelled;
- QWaitCondition m_readyCondition;
+ QQueue<QSharedPointer<Task>> m_taskQueue;
QWaitCondition m_doneCondition;
- QMutex m_readyMutex;
+ QWaitCondition m_waitForTaskCondition;
QMutex m_doneMutex;
+ QMutex m_waitForTaskMutex;
};
-
-static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes,
- SHFILEINFO *info, UINT flags,
- qint64 timeOutMSecs = 5000)
-{
- static QShGetFileInfoThread *getFileInfoThread = nullptr;
- if (!getFileInfoThread) {
- getFileInfoThread = new QShGetFileInfoThread;
- getFileInfoThread->start();
- }
-
- bool result = false;
- QShGetFileInfoParams params(fileName, attributes, info, flags, &result);
- if (!getFileInfoThread->runWithParams(&params, timeOutMSecs)) {
- // Cancel and reset getFileInfoThread. It'll
- // be reinitialized the next time we get called.
- getFileInfoThread->cancel();
- getFileInfoThread = nullptr;
- qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName;
- return false;
- }
- return result;
-}
+Q_APPLICATION_STATIC(QShGetFileInfoThread, s_shGetFileInfoThread)
// from QStyle::standardPalette
static inline QPalette standardPalette()
@@ -225,14 +259,47 @@ static QColor placeHolderColor(QColor textColor)
This is used when the theme is light mode, and when the theme is dark but the
application doesn't support dark mode. In the latter case, we need to check.
*/
-static void populateLightSystemBasePalette(QPalette &result)
+void QWindowsTheme::populateLightSystemBasePalette(QPalette &result)
{
- using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
- if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
- nativeWindowsApp->lightSystemPalette(result);
+ const QColor background = getSysColor(COLOR_BTNFACE);
+ const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
+
+ const QColor accent = qt_accentColor(AccentColorNormal);
+ const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
+
+ const QColor linkColor = accent;
+ const QColor btnFace = background;
+ const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
+
+ result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT));
+ result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
+ result.setColor(QPalette::Button, btnFace);
+ result.setColor(QPalette::Light, btnHighlight);
+ result.setColor(QPalette::Dark, getSysColor(COLOR_BTNSHADOW));
+ result.setColor(QPalette::Mid, result.button().color().darker(150));
+ result.setColor(QPalette::Text, textColor);
+ result.setColor(QPalette::PlaceholderText, placeHolderColor(textColor));
+ result.setColor(QPalette::BrightText, btnHighlight);
+ result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW));
+ result.setColor(QPalette::Window, btnFace);
+ result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT));
+ result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
+ result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
+ result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
+ result.setColor(QPalette::Accent, accent);
+
+ result.setColor(QPalette::Link, linkColor);
+ result.setColor(QPalette::LinkVisited, accentDarkest);
+ result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
+ result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
+ result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
+ result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color());
+
+ if (result.midlight() == result.button())
+ result.setColor(QPalette::Midlight, result.button().color().lighter(110));
}
-static void populateDarkSystemBasePalette(QPalette &result)
+void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result)
{
#if QT_CONFIG(cpp_winrt)
using namespace winrt::Windows::UI::ViewManagement;
@@ -256,19 +323,18 @@ static void populateDarkSystemBasePalette(QPalette &result)
const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
- const QColor linkColor = accent;
#else
const QColor foreground = Qt::white;
const QColor background = QColor(0x1E, 0x1E, 0x1E);
- const QColor accent = QColor(0x00, 0x55, 0xff);
+ const QColor accent = qt_accentColor(AccentColorNormal);
const QColor accentDark = accent.darker(120);
const QColor accentDarker = accentDark.darker(120);
const QColor accentDarkest = accentDarker.darker(120);
const QColor accentLight = accent.lighter(120);
const QColor accentLighter = accentLight.lighter(120);
const QColor accentLightest = accentLighter.lighter(120);
- const QColor linkColor = Qt::blue;
#endif
+ const QColor linkColor = accent;
const QColor buttonColor = background.lighter(200);
result.setColor(QPalette::All, QPalette::WindowText, foreground);
@@ -294,36 +360,7 @@ static void populateDarkSystemBasePalette(QPalette &result)
result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor);
result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120));
result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground));
-}
-
-static QPalette systemPalette(bool light)
-{
- QPalette result = standardPalette();
- if (light)
- populateLightSystemBasePalette(result);
- else
- populateDarkSystemBasePalette(result);
-
- if (result.window() != result.base()) {
- result.setColor(QPalette::Inactive, QPalette::Highlight,
- result.color(QPalette::Inactive, QPalette::Window));
- result.setColor(QPalette::Inactive, QPalette::HighlightedText,
- result.color(QPalette::Inactive, QPalette::Text));
- }
-
- const QColor disabled = mixColors(result.windowText().color(), result.button().color());
-
- result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
- result.light(), result.dark(), result.mid(),
- result.text(), result.brightText(), result.base(),
- result.window());
- result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
- result.setColor(QPalette::Disabled, QPalette::Text, disabled);
- result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
- result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
- result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
- result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
- return result;
+ result.setColor(QPalette::All, QPalette::Accent, accent);
}
static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light)
@@ -415,6 +452,7 @@ QWindowsTheme *QWindowsTheme::m_instance = nullptr;
QWindowsTheme::QWindowsTheme()
{
m_instance = this;
+ s_colorScheme = QWindowsTheme::queryColorScheme();
std::fill(m_fonts, m_fonts + NFonts, nullptr);
std::fill(m_palettes, m_palettes + NPalettes, nullptr);
refresh();
@@ -436,7 +474,10 @@ static inline QStringList iconThemeSearchPaths()
static inline QStringList styleNames()
{
- return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
+ QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
+ if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11)
+ styles.prepend(QStringLiteral("Windows11"));
+ return styles;
}
static inline int uiEffects()
@@ -499,9 +540,33 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const
return QPlatformTheme::themeHint(hint);
}
-Qt::Appearance QWindowsTheme::appearance() const
+Qt::ColorScheme QWindowsTheme::colorScheme() const
{
- return QWindowsContext::isDarkMode() ? Qt::Appearance::Dark : Qt::Appearance::Light;
+ return QWindowsTheme::effectiveColorScheme();
+}
+
+Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
+{
+ if (queryHighContrast())
+ return Qt::ColorScheme::Unknown;
+ return s_colorScheme;
+}
+
+void QWindowsTheme::handleSettingsChanged()
+{
+ const auto newColorScheme = QWindowsTheme::queryColorScheme();
+ const bool colorSchemeChanged = newColorScheme != QWindowsTheme::s_colorScheme;
+ s_colorScheme = newColorScheme;
+ auto integration = QWindowsIntegration::instance();
+ integration->updateApplicationBadge();
+ if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
+ QWindowsTheme::instance()->refresh();
+ QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
+ }
+ if (colorSchemeChanged) {
+ for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows()))
+ w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark);
+ }
}
void QWindowsTheme::clearPalettes()
@@ -512,41 +577,66 @@ void QWindowsTheme::clearPalettes()
void QWindowsTheme::refreshPalettes()
{
-
if (!QGuiApplication::desktopSettingsAware())
return;
const bool light =
- !QWindowsContext::isDarkMode()
+ effectiveColorScheme() != Qt::ColorScheme::Dark
|| !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
clearPalettes();
- m_palettes[SystemPalette] = new QPalette(systemPalette(light));
+ m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme));
m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light));
m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
if (!light) {
-#if QT_CONFIG(cpp_winrt)
- using namespace winrt::Windows::UI::ViewManagement;
- const auto settings = UISettings();
- const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
- const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
- const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, accent);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, accentLight);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, accentDarkest);
-#else
- m_palettes[ButtonPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[ButtonPalette]->setColor(QPalette::Button, QColor(0x666666u));
- const QColor checkBoxBlue(0x0078d7u);
- m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Base, checkBoxBlue);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Button, checkBoxBlue);
- m_palettes[CheckBoxPalette]->setColor(QPalette::ButtonText, Qt::white);
-#endif
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLightest));
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
}
}
+QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
+{
+ QPalette result = standardPalette();
+
+ switch (colorScheme) {
+ case Qt::ColorScheme::Unknown:
+ // when a high-contrast theme is active or when we fail to read, assume light
+ Q_FALLTHROUGH();
+ case Qt::ColorScheme::Light:
+ populateLightSystemBasePalette(result);
+ break;
+ case Qt::ColorScheme::Dark:
+ populateDarkSystemBasePalette(result);
+ break;
+ }
+
+ if (result.window() != result.base()) {
+ result.setColor(QPalette::Inactive, QPalette::Highlight,
+ result.color(QPalette::Inactive, QPalette::Window));
+ result.setColor(QPalette::Inactive, QPalette::HighlightedText,
+ result.color(QPalette::Inactive, QPalette::Text));
+ result.setColor(QPalette::Inactive, QPalette::Accent,
+ result.color(QPalette::Inactive, QPalette::Window));
+ }
+
+ const QColor disabled = mixColors(result.windowText().color(), result.button().color());
+
+ result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
+ result.light(), result.dark(), result.mid(),
+ result.text(), result.brightText(), result.base(),
+ result.window());
+ result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Text, disabled);
+ result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
+ result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
+ result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
+ return result;
+}
+
void QWindowsTheme::clearFonts()
{
qDeleteAll(m_fonts, m_fonts + NFonts);
@@ -658,11 +748,7 @@ void QWindowsTheme::refreshIconPixmapSizes()
fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2;
fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
-#ifdef USE_IIMAGELIST
int *availEnd = fileIconSizes + JumboFileIcon + 1;
-#else
- int *availEnd = fileIconSizes + LargeFileIcon + 1;
-#endif // USE_IIMAGELIST
m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
}
@@ -775,15 +861,18 @@ QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSiz
}
if (stockId != SIID_INVALID) {
- QPixmap pixmap;
SHSTOCKICONINFO iconInfo;
memset(&iconInfo, 0, sizeof(iconInfo));
iconInfo.cbSize = sizeof(iconInfo);
- stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON);
- if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) {
- pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon);
- DestroyIcon(iconInfo.hIcon);
- return pixmap;
+ stockFlags |= SHGSI_ICONLOCATION;
+ if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
+ const auto iconSize = pixmapSize.width();
+ HICON icon;
+ if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
+ QPixmap pixmap = qt_pixmapFromWinHICON(icon);
+ DestroyIcon(icon);
+ return pixmap;
+ }
}
}
@@ -853,10 +942,9 @@ public:
// Shell image list helper functions.
-static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
+static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
{
QPixmap result;
-#ifdef USE_IIMAGELIST
// For MinGW:
static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
@@ -865,16 +953,12 @@ static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
if (hr != S_OK)
return result;
HICON hIcon;
- hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon);
+ hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
if (hr == S_OK) {
result = qt_pixmapFromWinHICON(hIcon);
DestroyIcon(hIcon);
}
imageList->Release();
-#else
- Q_UNUSED(iImageList);
- Q_UNUSED(info);
-#endif // USE_IIMAGELIST
return result;
}
@@ -926,13 +1010,9 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
const int width = int(size.width());
const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
const int requestedImageListSize =
-#ifdef USE_IIMAGELIST
width > fileIconSizes[ExtraLargeFileIcon]
? sHIL_JUMBO
: (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
-#else
- 0;
-#endif // !USE_IIMAGELIST
bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
@@ -948,7 +1028,6 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
}
}
- SHFILEINFO info;
unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
DWORD attributes = 0;
QString path = filePath;
@@ -960,43 +1039,43 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
flags |= SHGFI_USEFILEATTRIBUTES;
attributes |= FILE_ATTRIBUTE_NORMAL;
}
- const bool val = shGetFileInfoBackground(path, attributes, &info, flags);
-
+ auto task = QSharedPointer<QShGetFileInfoThread::Task>(
+ new QShGetFileInfoThread::Task(path, attributes, flags));
+ s_shGetFileInfoThread()->runWithParams(task);
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
- if (val && info.hIcon) {
+ if (task->resultValid()) {
QString key;
if (cacheableDirIcon) {
if (useDefaultFolderIcon && defaultFolderIIcon < 0)
- defaultFolderIIcon = info.iIcon;
+ defaultFolderIIcon = task->iIcon;
//using the unique icon index provided by windows save us from duplicate keys
- key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize);
+ key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
QPixmapCache::find(key, &pixmap);
if (!pixmap.isNull()) {
QMutexLocker locker(&mx);
- dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+ dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
}
}
if (pixmap.isNull()) {
if (requestedImageListSize) {
- pixmap = pixmapFromShellImageList(requestedImageListSize, info);
+ pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
- pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info);
+ pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
}
if (pixmap.isNull())
- pixmap = qt_pixmapFromWinHICON(info.hIcon);
+ pixmap = qt_pixmapFromWinHICON(task->hIcon);
if (!pixmap.isNull()) {
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
QPixmapCache::insert(key, pixmap);
- dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+ dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
}
} else {
qWarning("QWindowsTheme::fileIconPixmap() no icon found");
}
}
- DestroyIcon(info.hIcon);
}
return pixmap;
@@ -1007,6 +1086,11 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt
return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
}
+QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
+{
+ return new QWindowsIconEngine(iconName);
+}
+
static inline bool doUseNativeMenus()
{
const unsigned options = QWindowsIntegration::instance()->options();
@@ -1031,19 +1115,23 @@ bool QWindowsTheme::useNativeMenus()
return result;
}
-bool QWindowsTheme::queryDarkMode()
+Qt::ColorScheme QWindowsTheme::queryColorScheme()
{
- if (queryHighContrast()) {
- return false;
- }
+ if (queryHighContrast())
+ return Qt::ColorScheme::Unknown;
+
const auto setting = QWinRegistryKey(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)")
.dwordValue(L"AppsUseLightTheme");
- return setting.second && setting.first == 0;
+ return setting.second && setting.first == 0 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
}
bool QWindowsTheme::queryHighContrast()
{
- return booleanSystemParametersInfo(SPI_GETHIGHCONTRAST, false);
+ HIGHCONTRAST hcf = {};
+ hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
+ if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
+ return hcf.dwFlags & HCF_HIGHCONTRASTON;
+ return false;
}
QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const
diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h
index 661519b312..6109122944 100644
--- a/src/plugins/platforms/windows/qwindowstheme.h
+++ b/src/plugins/platforms/windows/qwindowstheme.h
@@ -31,7 +31,9 @@ public:
#endif
QVariant themeHint(ThemeHint) const override;
- Qt::Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
+
+ static void handleSettingsChanged();
const QPalette *palette(Palette type = SystemPalette) const override
{ return m_palettes[type]; }
@@ -41,6 +43,7 @@ public:
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = {}) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
void windowsThemeChanged(QWindow *window);
void displayChanged() { refreshIconPixmapSizes(); }
@@ -53,21 +56,29 @@ public:
void showPlatformMenuBar() override;
static bool useNativeMenus();
- static bool queryDarkMode();
- static bool queryHighContrast();
void refreshFonts();
void refresh();
static const char *name;
+ static QPalette systemPalette(Qt::ColorScheme);
+
private:
void clearPalettes();
void refreshPalettes();
void clearFonts();
void refreshIconPixmapSizes();
+ static void populateLightSystemBasePalette(QPalette &result);
+ static void populateDarkSystemBasePalette(QPalette &result);
+
+ static Qt::ColorScheme queryColorScheme();
+ static Qt::ColorScheme effectiveColorScheme();
+ static bool queryHighContrast();
+
static QWindowsTheme *m_instance;
+ static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown;
QPalette *m_palettes[NPalettes];
QFont *m_fonts[NFonts];
QList<QSize> m_fileIconSizes;
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index 79978b3314..5d96d40af5 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -5,6 +5,7 @@
#include "qwindowswindow.h"
#include "qwindowscontext.h"
+#include "qwindowstheme.h"
#if QT_CONFIG(draganddrop)
# include "qwindowsdrag.h"
#endif
@@ -27,11 +28,11 @@
#include <QtGui/qwindow.h>
#include <QtGui/qregion.h>
#include <QtGui/qopenglcontext.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <private/qwindow_p.h> // QWINDOWSIZE_MAX
#include <private/qguiapplication_p.h>
#include <private/qhighdpiscaling_p.h>
#include <qpa/qwindowsysteminterface.h>
-#include <qpa/qplatformtheme.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlibraryinfo.h>
@@ -430,11 +431,7 @@ static inline bool windowIsAccelerated(const QWindow *w)
{
switch (w->surfaceType()) {
case QSurface::OpenGLSurface:
- return true;
- case QSurface::RasterGLSurface:
- return qt_window_private(const_cast<QWindow *>(w))->compositing;
case QSurface::VulkanSurface:
- return true;
case QSurface::Direct3DSurface:
return true;
default:
@@ -469,14 +466,21 @@ static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags)
w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX);
}
+bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags)
+{
+ const LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ return (style & WS_CHILD) || (flags & Qt::FramelessWindowHint);
+}
+
// Set the WS_EX_LAYERED flag on a HWND if required. This is required for
// translucent backgrounds, not fully opaque windows and for
// Qt::WindowTransparentForInput (in combination with WS_EX_TRANSPARENT).
bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity)
{
const LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+ // Native children are frameless by nature, so check for that as well.
const bool needsLayered = (flags & Qt::WindowTransparentForInput)
- || (hasAlpha && (flags & Qt::FramelessWindowHint)) || opacity < 1.0;
+ || (hasAlpha && hasNoNativeFrame(hwnd, flags)) || opacity < 1.0;
const bool isLayered = (exStyle & WS_EX_LAYERED);
if (needsLayered != isLayered) {
if (needsLayered) {
@@ -492,7 +496,7 @@ static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bo
{
if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) {
const BYTE alpha = BYTE(qRound(255.0 * level));
- if (hasAlpha && !accelerated && (flags & Qt::FramelessWindowHint)) {
+ if (hasAlpha && !accelerated && QWindowsWindow::hasNoNativeFrame(hwnd, flags)) {
// Non-GL windows with alpha: Use blend function to update.
BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA};
UpdateLayeredWindow(hwnd, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blend, ULW_ALPHA);
@@ -835,6 +839,10 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
// NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window
if (flagsIn & Qt::WindowTransparentForInput)
exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+
+ // Currently only compatible with D3D surfaces, use it with care.
+ if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE"))
+ exStyle |= WS_EX_NOREDIRECTIONBITMAP;
}
}
@@ -845,11 +853,13 @@ static inline bool shouldApplyDarkFrame(const QWindow *w)
// the application has explicitly opted out of dark frames
if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames))
return false;
+
// if the application supports a dark border, and the palette is dark (window background color
// is darker than the text), then turn dark-border support on, otherwise use a light border.
- const QPalette defaultPalette;
- return defaultPalette.color(QPalette::WindowText).lightness()
- > defaultPalette.color(QPalette::Window).lightness();
+ auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w));
+ const QPalette windowPal = dWindow->windowPalette();
+ return windowPal.color(QPalette::WindowText).lightness()
+ > windowPal.color(QPalette::Window).lightness();
}
QWindowsWindowData
@@ -920,7 +930,7 @@ QWindowsWindowData
return result;
}
- if (QWindowsContext::isDarkMode() && shouldApplyDarkFrame(w))
+ if (QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark && shouldApplyDarkFrame(w))
QWindowsWindow::setDarkBorderToWindow(result.hwnd, true);
if (mirrorParentWidth != 0) {
@@ -931,6 +941,7 @@ QWindowsWindowData
QRect obtainedGeometry(context->obtainedPos, context->obtainedSize);
result.geometry = obtainedGeometry;
+ result.restoreGeometry = frameGeometry(result.hwnd, topLevel);
result.fullFrameMargins = context->margins;
result.embedded = embedded;
result.hasFrame = hasFrame;
@@ -1005,6 +1016,21 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s)
return dip;
}
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, DWORD style)
+{
+ return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, HWND hwnd)
+{
+ DWORD style = hwnd != nullptr ? DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)) : 0;
+ return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
/*!
\class QWindowsGeometryHint
\brief Stores geometry constraints and provides utility functions.
@@ -1017,7 +1043,7 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s)
QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
RECT rect = {0,0,0,0};
style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
@@ -1033,15 +1059,13 @@ QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD styl
QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, HWND hwnd)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
- return {};
return frameOnPrimaryScreen(w, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
}
QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyle, qreal dpi)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
RECT rect = {0,0,0,0};
style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
@@ -1059,7 +1083,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl
QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
if (QWindowsScreenManager::isSingleScreen())
return frameOnPrimaryScreen(w, style, exStyle);
@@ -1073,8 +1097,6 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, D
QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
- return {};
return frame(w, hwnd, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
}
@@ -1083,7 +1105,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd)
QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry,
DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
if (QWindowsScreenManager::isSingleScreen()
|| !QWindowsContext::shouldHaveNonClientDpiScaling(w)) {
@@ -1336,6 +1358,8 @@ QWindowsForeignWindow::QWindowsForeignWindow(QWindow *window, HWND hwnd)
, m_hwnd(hwnd)
, m_topLevelStyle(0)
{
+ if (QPlatformWindow::parent())
+ setParent(QPlatformWindow::parent());
}
void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow)
@@ -1511,6 +1535,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data)
QWindowsWindow::~QWindowsWindow()
{
setFlag(WithinDestroy);
+ QWindowsThemeCache::clearThemeCache(m_data.hwnd);
if (testFlag(TouchRegistered))
UnregisterTouchWindow(m_data.hwnd);
destroyWindow();
@@ -1952,6 +1977,12 @@ void QWindowsWindow::handleCompositionSettingsChanged()
}
}
+qreal QWindowsWindow::dpiRelativeScale(const UINT dpi) const
+{
+ return QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) /
+ QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi);
+}
+
void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result)
{
// We want to keep QWindow's device independent size constant across the
@@ -1959,10 +1990,9 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *
// by the change of DPI (e.g. 120 -> 144 = 1.2), also taking any scale
// factor rounding into account. The win32 window size includes the margins;
// add the margins for the new DPI to the window size.
- const int dpi = int(wParam);
- const qreal scale = QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) /
- QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi);
- const QMargins margins = QWindowsGeometryHint::frame(window(), style(), exStyle(), dpi);
+ const UINT dpi = UINT(wParam);
+ const qreal scale = dpiRelativeScale(dpi);
+ const QMargins margins = fullFrameMargins();
if (!(m_data.flags & Qt::FramelessWindowHint)) {
// We need to update the custom margins to match the current DPI, because
// we don't want our users manually hook into this message just to set a
@@ -1971,7 +2001,8 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *
// are currently doing.
m_data.customMargins *= scale;
}
- const QSize windowSize = (geometry().size() * scale).grownBy(margins + customMargins());
+
+ const QSize windowSize = (geometry().size() * scale).grownBy((margins * scale) + customMargins());
SIZE *size = reinterpret_cast<SIZE *>(lParam);
size->cx = windowSize.width();
size->cy = windowSize.height();
@@ -1981,11 +2012,17 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *
void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
const UINT dpi = HIWORD(wParam);
+ const qreal scale = dpiRelativeScale(dpi);
setSavedDpi(dpi);
+ QWindowsThemeCache::clearThemeCache(hwnd);
+
// Send screen change first, so that the new screen is set during any following resize
checkForScreenChanged(QWindowsWindow::FromDpiChange);
+ if (!IsZoomed(hwnd))
+ m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale);
+
// We get WM_DPICHANGED in one of two situations:
//
// 1. The DPI change is a "spontaneous" DPI change as a result of e.g.
@@ -2008,13 +2045,22 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
+ // If the window does not have a frame, WM_MOVE and WM_SIZE won't be
+ // called which prevents the content from being scaled appropriately
+ // after a DPI change.
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+ handleGeometryChange();
}
+
+ // Re-apply mask now that we have a new DPI, which have resulted in
+ // a new scale factor.
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
}
void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd)
{
const UINT dpi = GetDpiForWindow(hwnd);
- const qreal scale = qreal(dpi) / qreal(savedDpi());
+ const qreal scale = dpiRelativeScale(dpi);
setSavedDpi(dpi);
checkForScreenChanged(QWindowsWindow::FromDpiChange);
@@ -2136,8 +2182,41 @@ void QWindowsWindow::handleMoved()
handleGeometryChange();
}
-void QWindowsWindow::handleResized(int wParam)
+void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
{
+ /* Prevents borderless windows from covering the taskbar when maximized. */
+ if ((m_data.flags.testFlag(Qt::FramelessWindowHint)
+ || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))
+ && IsZoomed(m_data.hwnd)) {
+ const int resizedWidth = LOWORD(lParam);
+ const int resizedHeight = HIWORD(lParam);
+
+ const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTOPRIMARY);
+ MONITORINFO monitorInfo = {};
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+
+ int correctLeft = monitorInfo.rcMonitor.left;
+ int correctTop = monitorInfo.rcMonitor.top;
+ int correctWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
+ int correctHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
+
+ if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
+ const int borderWidth = invisibleMargins(m_data.hwnd).left();
+ correctLeft -= borderWidth;
+ correctTop -= borderWidth;
+ correctWidth += borderWidth * 2;
+ correctHeight += borderWidth * 2;
+ }
+
+ if (resizedWidth != correctWidth || resizedHeight != correctHeight) {
+ qCDebug(lcQpaWindow) << __FUNCTION__ << "correcting: " << resizedWidth << "x"
+ << resizedHeight << " -> " << correctWidth << "x" << correctHeight;
+ SetWindowPos(m_data.hwnd, nullptr, correctLeft, correctTop, correctWidth, correctHeight,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+
switch (wParam) {
case SIZE_MAXHIDE: // Some other window affected.
case SIZE_MAXSHOW:
@@ -2185,10 +2264,10 @@ void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode)
return;
// For screens with different DPI: postpone until WM_DPICHANGE
// Check on currentScreen as it can be 0 when resuming a session (QTBUG-80436).
- if (mode == FromGeometryChange && currentScreen != nullptr
- && !equalDpi(currentScreen->logicalDpi(), newScreen->logicalDpi())) {
+ const bool changingDpi = !equalDpi(QDpi(savedDpi(), savedDpi()), newScreen->logicalDpi());
+ if (mode == FromGeometryChange && currentScreen != nullptr && changingDpi)
return;
- }
+
qCDebug(lcQpaWindow).noquote().nospace() << __FUNCTION__
<< ' ' << window() << " \"" << (currentScreen ? currentScreen->name() : QString())
<< "\"->\"" << newScreen->name() << '"';
@@ -2200,6 +2279,7 @@ void QWindowsWindow::handleGeometryChange()
{
const QRect previousGeometry = m_data.geometry;
m_data.geometry = geometry_sys();
+ updateFullFrameMargins();
QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry);
// QTBUG-32121: OpenGL/normal windows (with exception of ANGLE
// which we no longer support in Qt 6) do not receive expose
@@ -2217,6 +2297,9 @@ void QWindowsWindow::handleGeometryChange()
if (testFlag(SynchronousGeometryChangeEvent))
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+ if (!testFlag(ResizeMoveActive))
+ updateRestoreGeometry();
+
if (!wasSync)
clearFlag(SynchronousGeometryChangeEvent);
qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry;
@@ -2305,23 +2388,9 @@ static inline bool isSoftwareGl()
}
bool QWindowsWindow::handleWmPaint(HWND hwnd, UINT message,
- WPARAM wParam, LPARAM, LRESULT *result)
+ WPARAM, LPARAM, LRESULT *result)
{
if (message == WM_ERASEBKGND) { // Backing store - ignored.
- if (!m_firstBgDraw) {
- // Get window background from the default palette; this will
- // usually be the system background color.
- const QColor bgColor = QGuiApplication::palette().color(QPalette::Window);
- HBRUSH bgBrush = CreateSolidBrush(RGB(bgColor.red(), bgColor.green(), bgColor.blue()));
- // Fill rectangle with system background color
- RECT rc;
- auto hdc = reinterpret_cast<HDC>(wParam);
- GetClientRect(hwnd, &rc);
- FillRect(hdc, &rc, bgBrush);
- DeleteObject(bgBrush);
- // Brush the window with system background color only for first time
- m_firstBgDraw = true;
- }
*result = 1;
return true;
}
@@ -2406,6 +2475,14 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
handleHidden();
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now.
} else {
+ if (state & Qt::WindowMaximized) {
+ WINDOWPLACEMENT windowPlacement{};
+ windowPlacement.length = sizeof(WINDOWPLACEMENT);
+ GetWindowPlacement(m_data.hwnd, &windowPlacement);
+ const RECT geometry = RECTfromQRect(m_data.restoreGeometry);
+ windowPlacement.rcNormalPosition = geometry;
+ SetWindowPlacement(m_data.hwnd, &windowPlacement);
+ }
// QTBUG-17548: We send expose events when receiving WM_Paint, but for
// layered windows and transient children, we won't receive any WM_Paint.
QWindow *w = window();
@@ -2429,6 +2506,11 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
}
}
+void QWindowsWindow::updateRestoreGeometry()
+{
+ m_data.restoreGeometry = normalFrameGeometry(m_data.hwnd);
+}
+
void QWindowsWindow::setWindowState(Qt::WindowStates state)
{
if (m_data.hwnd) {
@@ -2496,26 +2578,26 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
if (testFlag(HasBorderInFullScreen))
newStyle |= WS_BORDER;
setStyle(newStyle);
- // Use geometry of QWindow::screen() within creation or the virtual screen the
- // window is in (QTBUG-31166, QTBUG-30724).
- const QScreen *screen = window()->screen();
- if (!screen)
- screen = QGuiApplication::primaryScreen();
- const QRect r = screen ? QHighDpi::toNativePixels(screen->geometry(), window()) : m_savedFrameGeometry;
-
+ const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO monitorInfo = {};
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+ const QRect screenGeometry(monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top,
+ monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
+ monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
if (newState & Qt::WindowMinimized) {
- setMinimizedGeometry(m_data.hwnd, r);
+ setMinimizedGeometry(m_data.hwnd, screenGeometry);
if (stateChange & Qt::WindowMaximized)
setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
} else {
const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE;
const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
setFlag(SynchronousGeometryChangeEvent);
- SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf);
+ SetWindowPos(m_data.hwnd, HWND_TOP, screenGeometry.left(), screenGeometry.top(), screenGeometry.width(), screenGeometry.height(), swpf);
if (!wasSync)
clearFlag(SynchronousGeometryChangeEvent);
clearFlag(MaximizeToFullScreen);
- QWindowSystemInterface::handleGeometryChange(window(), r);
+ QWindowSystemInterface::handleGeometryChange(window(), screenGeometry);
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
}
} else {
@@ -2608,7 +2690,7 @@ bool QWindowsWindow::windowEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::ApplicationPaletteChange:
- setDarkBorder(QWindowsContext::isDarkMode());
+ setDarkBorder(QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark);
break;
case QEvent::WindowBlocked: // Blocked by another modal window.
setEnabled(false);
@@ -2635,10 +2717,17 @@ void QWindowsWindow::propagateSizeHints()
bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins)
{
auto *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam);
+ const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
+ windowPos->cx, windowPos->cy);
+ const QRect suggestedGeometry = suggestedFrameGeometry - margins;
// Tell Windows to discard the entire contents of the client area, as re-using
// parts of the client area would lead to jitter during resize.
- windowPos->flags |= SWP_NOCOPYBITS;
+ // Check the suggestedGeometry against the current one to only discard during
+ // resize, and not a plain move. We also look for SWP_NOSIZE since that, too,
+ // implies an identical size, and comparing QRects wouldn't work with null cx/cy
+ if (!(windowPos->flags & SWP_NOSIZE) && suggestedGeometry.size() != qWindow->geometry().size())
+ windowPos->flags |= SWP_NOCOPYBITS;
if ((windowPos->flags & SWP_NOZORDER) == 0) {
if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) {
@@ -2654,9 +2743,6 @@ bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *
return false;
if (windowPos->flags & SWP_NOSIZE)
return false;
- const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
- windowPos->cx, windowPos->cy);
- const QRect suggestedGeometry = suggestedFrameGeometry - margins;
const QRectF correctedGeometryF = QPlatformWindow::closestAcceptableGeometry(qWindow, suggestedGeometry);
if (!correctedGeometryF.isValid())
return false;
@@ -2678,7 +2764,7 @@ bool QWindowsWindow::handleGeometryChanging(MSG *message) const
void QWindowsWindow::setFullFrameMargins(const QMargins &newMargins)
{
- if (m_data.flags & Qt::FramelessWindowHint)
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
return;
if (m_data.fullFrameMargins != newMargins) {
qCDebug(lcQpaWindow) << __FUNCTION__ << window() << m_data.fullFrameMargins << "->" << newMargins;
@@ -2697,14 +2783,46 @@ void QWindowsWindow::updateFullFrameMargins()
void QWindowsWindow::calculateFullFrameMargins()
{
- if (m_data.flags & Qt::FramelessWindowHint)
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
return;
+
+ // QTBUG-113736: systemMargins depends on AdjustWindowRectExForDpi. This doesn't take into
+ // account possible external modifications to the titlebar, as with ExtendsContentIntoTitleBar()
+ // from the Windows App SDK. We can fix this by comparing the WindowRect (which includes the
+ // frame) to the ClientRect. If a 'typical' frame is detected, i.e. only the titlebar has been
+ // modified, we can safely adjust the frame by deducting the bottom margin to the total Y
+ // difference between the two rects, to get the actual size of the titlebar and prevent
+ // unwanted client area slicing.
+
+ RECT windowRect{};
+ RECT clientRect{};
+ GetWindowRect(handle(), &windowRect);
+ GetClientRect(handle(), &clientRect);
+
+ // QTBUG-117704 It is also possible that the user has manually removed the frame (for example
+ // by handling WM_NCCALCSIZE). If that is the case, i.e., the client area and the window area
+ // have identical sizes, we don't want to override the user-defined margins.
+
+ if (qrectFromRECT(windowRect).size() == qrectFromRECT(clientRect).size())
+ return;
+
// Normally obtained from WM_NCCALCSIZE. This calculation only works
// when no native menu is present.
const auto systemMargins = testFlag(DisableNonClientScaling)
? QWindowsGeometryHint::frameOnPrimaryScreen(window(), m_data.hwnd)
: frameMargins_sys();
- setFullFrameMargins(systemMargins + customMargins());
+ const QMargins actualMargins = systemMargins + customMargins();
+
+ const int yDiff = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top);
+ const bool typicalFrame = (actualMargins.left() == actualMargins.right())
+ && (actualMargins.right() == actualMargins.bottom());
+
+ const QMargins adjustedMargins = typicalFrame ?
+ QMargins(actualMargins.left(), (yDiff - actualMargins.bottom()),
+ actualMargins.right(), actualMargins.bottom())
+ : actualMargins;
+
+ setFullFrameMargins(adjustedMargins);
}
QMargins QWindowsWindow::frameMargins() const
@@ -2717,7 +2835,7 @@ QMargins QWindowsWindow::frameMargins() const
QMargins QWindowsWindow::fullFrameMargins() const
{
- if (m_data.flags & Qt::FramelessWindowHint)
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
return {};
return m_data.fullFrameMargins;
}
@@ -2785,40 +2903,75 @@ void QWindowsWindow::setMask(const QRegion &region)
void QWindowsWindow::requestActivateWindow()
{
qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
- // 'Active' state handling is based in focus since it needs to work for
- // child windows as well.
- if (m_data.hwnd) {
- const DWORD currentThread = GetCurrentThreadId();
- bool attached = false;
- DWORD foregroundThread = 0;
-
- // QTBUG-14062, QTBUG-37435: Windows normally only flashes the taskbar entry
- // when activating windows of inactive applications. Attach to the input of the
- // currently active window while setting the foreground window to always activate
- // the window when desired.
- const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
- if (QGuiApplication::applicationState() != Qt::ApplicationActive
- && activationBehavior == QWindowsApplication::AlwaysActivateWindow) {
- if (const HWND foregroundWindow = GetForegroundWindow()) {
- foregroundThread = GetWindowThreadProcessId(foregroundWindow, nullptr);
- if (foregroundThread && foregroundThread != currentThread)
- attached = AttachThreadInput(foregroundThread, currentThread, TRUE) == TRUE;
- if (attached) {
- if (!window()->flags().testFlag(Qt::WindowStaysOnBottomHint)
- && !window()->flags().testFlag(Qt::WindowStaysOnTopHint)
- && window()->type() != Qt::ToolTip) {
- const UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER;
- SetWindowPos(m_data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, swpFlags);
- SetWindowPos(m_data.hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, swpFlags);
- }
- }
- }
- }
+
+ if (!m_data.hwnd)
+ return;
+
+ const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
+ if (QGuiApplication::applicationState() == Qt::ApplicationActive
+ || activationBehavior != QWindowsApplication::AlwaysActivateWindow) {
SetForegroundWindow(m_data.hwnd);
SetFocus(m_data.hwnd);
- if (attached)
- AttachThreadInput(foregroundThread, currentThread, FALSE);
+ return;
+ }
+
+ // Force activate this window. The following code will bring the window to the
+ // foreground and activate it. If the window is hidden, it will show up. If
+ // the window is minimized, it will restore to the previous position.
+
+ // But first we need some sanity checks.
+ if (m_data.flags & Qt::WindowStaysOnBottomHint) {
+ qCWarning(lcQpaWindow) <<
+ "Windows with Qt::WindowStaysOnBottomHint can't be brought to the foreground.";
+ return;
+ }
+ if (m_data.flags & Qt::WindowStaysOnTopHint) {
+ qCWarning(lcQpaWindow) <<
+ "Windows with Qt::WindowStaysOnTopHint will always be on the foreground.";
+ return;
+ }
+ if (window()->type() == Qt::ToolTip) {
+ qCWarning(lcQpaWindow) << "ToolTip windows should not be activated.";
+ return;
+ }
+
+ // We need to show the window first, otherwise we won't be able to bring it to front.
+ if (!IsWindowVisible(m_data.hwnd))
+ ShowWindow(m_data.hwnd, SW_SHOW);
+
+ if (IsIconic(m_data.hwnd)) {
+ ShowWindow(m_data.hwnd, SW_RESTORE);
+ // When the window is restored, it will always become the foreground window.
+ // So return early here, we don't need the following code to bring it to front.
+ return;
}
+
+ // OK, our window is not minimized, so now we will try to bring it to front manually.
+ const HWND oldForegroundWindow = GetForegroundWindow();
+ if (!oldForegroundWindow) // It may be NULL, according to MS docs.
+ return;
+
+ // First try to send a message to the current foreground window to check whether
+ // it is currently hanging or not.
+ if (SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0,
+ SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, nullptr) == 0) {
+ qCWarning(lcQpaWindow) << "The foreground window hangs, can't activate current window.";
+ return;
+ }
+
+ const DWORD windowThreadProcessId = GetWindowThreadProcessId(oldForegroundWindow, nullptr);
+ const DWORD currentThreadId = GetCurrentThreadId();
+
+ AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE);
+ const auto cleanup = qScopeGuard([windowThreadProcessId, currentThreadId](){
+ AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE);
+ });
+
+ BringWindowToTop(m_data.hwnd);
+
+ // Activate the window too. This will force us to the virtual desktop this
+ // window is on, if it's on another virtual desktop.
+ SetActiveWindow(m_data.hwnd);
}
bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)
@@ -2916,36 +3069,6 @@ void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled)
void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
{
QWindowsGeometryHint::applyToMinMaxInfo(window(), fullFrameMargins(), mmi);
-
- // This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the
- // taskbar when maximized
- if (m_data.flags.testFlag(Qt::FramelessWindowHint)
- || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint))) {
- if (QPlatformScreen *currentScreen = screen()) {
- const QRect geometry = currentScreen->geometry();
- const QRect availableGeometry = currentScreen->availableGeometry();
- mmi->ptMaxSize.y = availableGeometry.height();
-
- // Width, because you can have the taskbar on the sides too.
- mmi->ptMaxSize.x = availableGeometry.width();
-
- // If you have the taskbar on top, or on the left you don't want it at (0,0):
- QPoint availablePositionDiff = availableGeometry.topLeft() - geometry.topLeft();
- mmi->ptMaxPosition.x = availablePositionDiff.x();
- mmi->ptMaxPosition.y = availablePositionDiff.y();
- if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
- const int borderWidth = invisibleMargins(m_data.hwnd).left();
- mmi->ptMaxSize.x += borderWidth * 2;
- mmi->ptMaxSize.y += borderWidth * 2;
- mmi->ptMaxTrackSize = mmi->ptMaxSize;
- mmi->ptMaxPosition.x -= borderWidth;
- mmi->ptMaxPosition.y -= borderWidth;
- }
- } else {
- qWarning("screen() returned a null screen");
- }
- }
-
qCDebug(lcQpaWindow) << __FUNCTION__ << window() << *mmi;
}
@@ -3163,17 +3286,6 @@ enum : WORD {
DwmwaUseImmersiveDarkModeBefore20h1 = 19
};
-static bool queryDarkBorder(HWND hwnd)
-{
- BOOL result = FALSE;
- const bool ok =
- SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &result, sizeof(result)))
- || SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &result, sizeof(result)));
- if (!ok)
- qCWarning(lcQpaWindow, "%s: Unable to retrieve dark window border setting.", __FUNCTION__);
- return result == TRUE;
-}
-
bool QWindowsWindow::setDarkBorderToWindow(HWND hwnd, bool d)
{
const BOOL darkBorder = d ? TRUE : FALSE;
@@ -3189,8 +3301,6 @@ void QWindowsWindow::setDarkBorder(bool d)
{
// respect explicit opt-out and incompatible palettes or styles
d = d && shouldApplyDarkFrame(window());
- if (queryDarkBorder(m_data.hwnd) == d)
- return;
setDarkBorderToWindow(m_data.hwnd, d);
}
@@ -3319,24 +3429,6 @@ void QWindowsWindow::registerTouchWindow()
qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName()));
}
-void QWindowsWindow::aboutToMakeCurrent()
-{
-#ifndef QT_NO_OPENGL
- // For RasterGLSurface windows, that become OpenGL windows dynamically, it might be
- // time to set up some GL specifics. This is particularly important for layered
- // windows (WS_EX_LAYERED due to alpha > 0).
- const bool isCompositing = qt_window_private(window())->compositing;
- if (isCompositing != testFlag(Compositing)) {
- if (isCompositing)
- setFlag(Compositing);
- else
- clearFlag(Compositing);
-
- updateGLWindowSettings(window(), m_data.hwnd, m_data.flags, m_opacity);
- }
-#endif
-}
-
void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border)
{
if (QPlatformWindow *handle = window->handle())
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
index 49e80522b4..024711e7f3 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
@@ -78,6 +78,7 @@ struct QWindowsWindowData
{
Qt::WindowFlags flags;
QRect geometry;
+ QRect restoreGeometry;
QMargins fullFrameMargins; // Do not use directly for windows, see FrameDirty.
QMargins customMargins; // User-defined, additional frame for NCCALCSIZE
HWND hwnd = nullptr;
@@ -218,6 +219,8 @@ public:
void setGeometry(const QRect &rect) override;
QRect geometry() const override { return m_data.geometry; }
QRect normalGeometry() const override;
+ QRect restoreGeometry() const { return m_data.restoreGeometry; }
+ void updateRestoreGeometry();
void setVisible(bool visible) override;
bool isVisible() const;
@@ -281,7 +284,7 @@ public:
bool handleWmPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result);
void handleMoved();
- void handleResized(int wParam);
+ void handleResized(int wParam, LPARAM lParam);
void handleHidden();
void handleCompositionSettingsChanged();
void handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result);
@@ -296,6 +299,7 @@ public:
static inline void *userDataOf(HWND hwnd);
static inline void setUserDataOf(HWND hwnd, void *ud);
+ static bool hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags);
static bool setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity);
bool isLayered() const;
@@ -320,7 +324,6 @@ public:
void *surface(void *nativeConfig, int *err);
void invalidateSurface() override;
- void aboutToMakeCurrent();
void setAlertState(bool enabled) override;
bool isAlertState() const override { return testFlag(AlertState); }
@@ -342,6 +345,7 @@ public:
void setSavedDpi(int dpi) { m_savedDpi = dpi; }
int savedDpi() const { return m_savedDpi; }
+ qreal dpiRelativeScale(const UINT dpi) const;
private:
inline void show_sys() const;
@@ -377,7 +381,6 @@ private:
HICON m_iconBig = nullptr;
void *m_surface = nullptr;
int m_savedDpi = 96;
- bool m_firstBgDraw = false;
static bool m_screenForGLInitialized;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
index 001cb8505b..1abb412ccd 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
@@ -5,6 +5,7 @@
#if QT_CONFIG(accessibility)
#include "qwindowsuiaaccessibility.h"
+#include "qwindowsuiautomation.h"
#include "qwindowsuiamainprovider.h"
#include "qwindowsuiautils.h"
@@ -14,13 +15,13 @@
#include <QtGui/private/qguiapplication_p.h>
#include <QtCore/qt_windows.h>
#include <qpa/qplatformintegration.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
#include <QtCore/private/qwinregistry_p.h>
QT_BEGIN_NAMESPACE
using namespace QWindowsUiAutomation;
+using namespace Qt::Literals::StringLiterals;
bool QWindowsUiaAccessibility::m_accessibleActive = false;
@@ -46,7 +47,7 @@ bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARA
if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) {
if (QAccessibleInterface *accessible = window->accessibleRoot()) {
QWindowsUiaMainProvider *provider = QWindowsUiaMainProvider::providerForAccessible(accessible);
- *lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider);
+ *lResult = UiaReturnRawElementProvider(hwnd, wParam, lParam, provider);
return true;
}
}
@@ -78,8 +79,8 @@ static QString alertSound(const QObject *object)
static QString soundFileName(const QString &soundName)
{
- const QString key = QStringLiteral("AppEvents\\Schemes\\Apps\\.Default\\")
- + soundName + QStringLiteral("\\.Current");
+ const QString key = "AppEvents\\Schemes\\Apps\\.Default\\"_L1
+ + soundName + "\\.Current"_L1;
return QWinRegistryKey(HKEY_CURRENT_USER, key).stringValue(L"");
}
@@ -97,11 +98,7 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
if (!event)
return;
- // Ignore events sent before the first UI Automation
- // request or while QAccessible is being activated.
- if (!m_accessibleActive)
- return;
-
+ // Always handle system sound events
switch (event->type()) {
case QAccessible::PopupMenuStart:
playSystemSound(QStringLiteral("MenuPopup"));
@@ -116,16 +113,17 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
break;
}
- QAccessibleInterface *accessible = event->accessibleInterface();
- if (!isActive() || !accessible || !accessible->isValid())
+ // Ignore events sent before the first UI Automation
+ // request or while QAccessible is being activated.
+ if (!m_accessibleActive)
return;
- // Ensures QWindowsUiaWrapper is properly initialized.
- if (!QWindowsUiaWrapper::instance()->ready())
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ if (!isActive() || !accessible || !accessible->isValid())
return;
// No need to do anything when nobody is listening.
- if (!QWindowsUiaWrapper::instance()->clientsAreListening())
+ if (!UiaClientsAreListening())
return;
switch (event->type()) {
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
index 1813bb4d89..2e8ee585da 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
@@ -7,6 +7,7 @@
#include <QtGui/qtguiglobal.h>
#if QT_CONFIG(accessibility)
+#include <QtCore/qt_windows.h>
#include "qwindowscontext.h"
#include <qpa/qplatformaccessibility.h>
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
index c899b4096e..032679ab10 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
@@ -10,8 +10,8 @@
#include <QtGui/qaccessible.h>
#include <QtCore/qpointer.h>
-#include <qwindowscombase.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
+#include "qwindowsuiautomation.h"
+#include <QtCore/private/qcomobject_p.h>
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
index 49b37ba295..b384eb521c 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Expand/Collapse control pattern provider. Used for menu items with submenus.
class QWindowsUiaExpandCollapseProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IExpandCollapseProvider>
+ public QComObject<IExpandCollapseProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaExpandCollapseProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
index b501616966..289a867869 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Grid Item control pattern provider. Used by items within a table/tree.
class QWindowsUiaGridItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IGridItemProvider>
+ public QComObject<IGridItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaGridItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
index b905133f6b..d33bbd0429 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Grid control pattern provider. Used by tables/trees.
-class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IGridProvider>
+class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider, public QComObject<IGridProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaGridProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
index eca7e73039..ec006c673e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Invoke control pattern provider.
-class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IInvokeProvider>
+class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider, public QComObject<IInvokeProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaInvokeProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
index 16f0a3f9a6..95ddbcced6 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
@@ -18,7 +18,6 @@
#include "qwindowsuiagriditemprovider.h"
#include "qwindowsuiawindowprovider.h"
#include "qwindowsuiaexpandcollapseprovider.h"
-#include "qwindowscombase.h"
#include "qwindowscontext.h"
#include "qwindowsuiautils.h"
#include "qwindowsuiaprovidercache.h"
@@ -27,6 +26,7 @@
#include <QtGui/qaccessible.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
+#include <qpa/qplatforminputcontextfactory_p.h>
#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
#include <comdef.h>
@@ -61,9 +61,8 @@ QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessi
return provider;
}
-QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount)
- : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)),
- m_ref(initialRefCount)
+QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a)
+ : QWindowsUiaBaseProvider(QAccessible::uniqueId(a))
{
}
@@ -80,7 +79,7 @@ void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event)
accessible = child;
}
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible))
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId);
}
}
@@ -97,7 +96,7 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve
if (accessible->state().checked)
toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On;
setVariantI4(toggleState, &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal);
}
}
}
@@ -106,13 +105,13 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve
// Notifies window opened/closed.
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
if (accessible->state().active) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId);
if (QAccessibleInterface *focused = accessible->focusChild()) {
if (QWindowsUiaMainProvider *focusedProvider = providerForAccessible(focused))
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId);
+ UiaRaiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId);
}
} else {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Window_WindowClosedEventId);
}
}
}
@@ -141,27 +140,11 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve
}
if (event->value().typeId() == QMetaType::QString) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
-
- // Tries to notify the change using UiaRaiseNotificationEvent(), which is only available on
- // Windows 10 version 1709 or newer. Otherwise uses UiaRaiseAutomationPropertyChangedEvent().
-
- BSTR displayString = bStrFromQString(event->value().toString());
- BSTR activityId = bStrFromQString(QString());
-
- HRESULT hr = QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider, NotificationKind_Other,
- NotificationProcessing_ImportantMostRecent,
- displayString, activityId);
-
- ::SysFreeString(displayString);
- ::SysFreeString(activityId);
-
- if (hr == static_cast<HRESULT>(UIA_E_NOTSUPPORTED)) {
- VARIANT oldVal, newVal;
- clearVariant(&oldVal);
- setVariantString(event->value().toString(), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal);
- ::SysFreeString(newVal.bstrVal);
- }
+ // Notifies changes in string values.
+ VARIANT oldVal, newVal;
+ clearVariant(&oldVal);
+ setVariantString(event->value().toString(), &newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal);
}
} else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
@@ -169,7 +152,7 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve
VARIANT oldVal, newVal;
clearVariant(&oldVal);
setVariantDouble(valueInterface->currentValue().toDouble(), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal);
}
}
}
@@ -185,7 +168,7 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
VARIANT oldVal, newVal;
clearVariant(&oldVal);
setVariantString(accessible->text(QAccessible::Name), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
::SysFreeString(newVal.bstrVal);
}
}
@@ -196,7 +179,7 @@ void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId);
}
}
}
@@ -208,13 +191,13 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event)
if (accessible->textInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
if (event->type() == QAccessible::TextSelectionChanged) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
} else if (event->type() == QAccessible::TextCaretMoved) {
if (!accessible->state().readOnly) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
}
} else {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextChangedEventId);
}
}
}
@@ -223,33 +206,26 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event)
HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface)
{
- if (!iface)
- return E_INVALIDARG;
- *iface = nullptr;
-
- QAccessibleInterface *accessible = accessibleInterface();
+ HRESULT result = QComObject::QueryInterface(iid, iface);
- const bool result = qWindowsComQueryUnknownInterfaceMulti<IRawElementProviderSimple>(this, iid, iface)
- || qWindowsComQueryInterface<IRawElementProviderSimple>(this, iid, iface)
- || qWindowsComQueryInterface<IRawElementProviderFragment>(this, iid, iface)
- || (accessible && hwndForAccessible(accessible) && qWindowsComQueryInterface<IRawElementProviderFragmentRoot>(this, iid, iface));
- return result ? S_OK : E_NOINTERFACE;
-}
+ if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) {
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (accessible && hwndForAccessible(accessible)) {
+ result = S_OK;
+ } else {
+ result = E_NOINTERFACE;
+ iface = nullptr;
+ }
+ }
-ULONG QWindowsUiaMainProvider::AddRef()
-{
- return ++m_ref;
+ return result;
}
ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release()
{
QMutexLocker locker(&m_mutex);
- if (!--m_ref) {
- delete this;
- return 0;
- }
- return m_ref;
+ return QComObject::Release();
}
HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal)
@@ -304,15 +280,18 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow
*pRetVal = new QWindowsUiaToggleProvider(id());
break;
case UIA_SelectionPatternId:
- // Lists of items.
- if (accessible->role() == QAccessible::List
+ case UIA_SelectionPattern2Id:
+ // Selections via QAccessibleSelectionInterface or lists of items.
+ if (accessible->selectionInterface()
+ || accessible->role() == QAccessible::List
|| accessible->role() == QAccessible::PageTabList) {
*pRetVal = new QWindowsUiaSelectionProvider(id());
}
break;
case UIA_SelectionItemPatternId:
- // Items within a list and radio buttons.
- if ((accessible->role() == QAccessible::RadioButton)
+ // Parent supports selection interface or items within a list and radio buttons.
+ if ((accessible->parent() && accessible->parent()->selectionInterface())
+ || (accessible->role() == QAccessible::RadioButton)
|| (accessible->role() == QAccessible::ListItem)
|| (accessible->role() == QAccessible::PageTab)) {
*pRetVal = new QWindowsUiaSelectionItemProvider(id());
@@ -369,6 +348,28 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow
return S_OK;
}
+void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible,
+ QAccessible::Relation relation, VARIANT *pRetVal)
+{
+ Q_ASSERT(accessible);
+
+ typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
+ const QList<RelationPair> relationInterfaces = accessible->relations(relation);
+ if (relationInterfaces.empty())
+ return;
+
+ SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size());
+ for (LONG i = 0; i < relationInterfaces.size(); ++i) {
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(relationInterfaces.at(i).first)) {
+ SafeArrayPutElement(elements, &i, static_cast<IRawElementProviderSimple*>(childProvider));
+ childProvider->Release();
+ }
+ }
+
+ pRetVal->vt = VT_UNKNOWN | VT_ARRAY;
+ pRetVal->parray = elements;
+}
+
HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp;
@@ -403,6 +404,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR
setVariantString(className, pRetVal);
}
break;
+ case UIA_DescribedByPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal);
+ break;
+ case UIA_FlowsFromPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal);
+ break;
+ case UIA_FlowsToPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal);
+ break;
case UIA_FrameworkIdPropertyId:
setVariantString(QStringLiteral("Qt"), pRetVal);
break;
@@ -416,7 +426,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR
// The native OSK should be disabled if the Qt OSK is in use,
// or if disabled via application attribute.
- static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE");
+ static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty();
bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
// If we want to disable the native OSK auto-showing
@@ -519,7 +529,7 @@ HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderS
// Returns a host provider only for controls associated with a native window handle. Others should return NULL.
if (QAccessibleInterface *accessible = accessibleInterface()) {
if (HWND hwnd = hwndForAccessible(accessible)) {
- return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal);
+ return UiaHostProviderFromHwnd(hwnd, pRetVal);
}
}
return S_OK;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
index fb5069f620..99db0ed318 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
@@ -20,15 +20,13 @@ QT_BEGIN_NAMESPACE
// The main UI Automation class.
class QWindowsUiaMainProvider :
public QWindowsUiaBaseProvider,
- public IRawElementProviderSimple,
- public IRawElementProviderFragment,
- public IRawElementProviderFragmentRoot
+ public QComObject<IRawElementProviderSimple, IRawElementProviderFragment, IRawElementProviderFragmentRoot>
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QWindowsUiaMainProvider)
public:
static QWindowsUiaMainProvider *providerForAccessible(QAccessibleInterface *accessible);
- explicit QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount = 1);
+ explicit QWindowsUiaMainProvider(QAccessibleInterface *a);
virtual ~QWindowsUiaMainProvider();
static void notifyFocusChange(QAccessibleEvent *event);
static void notifyStateChange(QAccessibleStateChangeEvent *event);
@@ -39,7 +37,6 @@ public:
// IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override;
- ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
// IRawElementProviderSimple methods
@@ -62,7 +59,7 @@ public:
private:
QString automationIdForAccessible(const QAccessibleInterface *accessible);
- ULONG m_ref;
+ static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal);
static QMutex m_mutex;
};
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
index 3e06961a82..ffb5ae155b 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Range Value control pattern provider.
class QWindowsUiaRangeValueProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IRangeValueProvider>
+ public QComObject<IRangeValueProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaRangeValueProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
index 7e829b24a7..95bc2f7570 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
@@ -36,6 +36,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::Select()
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ selectionInterface->clear();
+ bool ok = selectionInterface->select(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -73,6 +81,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::AddToSelection()
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool ok = selectionInterface->select(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -98,6 +113,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::RemoveFromSelection(
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool ok = selectionInterface->unselect(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -124,6 +146,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_IsSelected(BOOL
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool selected = selectionInterface->isSelected(accessible);
+ *pRetVal = selected ? TRUE : FALSE;
+ return S_OK;
+ }
+ }
+
if (accessible->role() == QAccessible::RadioButton)
*pRetVal = accessible->state().checked;
else if (accessible->role() == QAccessible::PageTab)
@@ -146,12 +176,18 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_SelectionContain
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ QAccessibleInterface *parent = accessible->parent();
+ if (parent && parent->selectionInterface()) {
+ *pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent);
+ return S_OK;
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
// Radio buttons do not require a container.
- if (QAccessibleInterface *parent = accessible->parent()) {
+ if (parent) {
if ((accessible->role() == QAccessible::ListItem && parent->role() == QAccessible::List)
|| (accessible->role() == QAccessible::PageTab && parent->role() == QAccessible::PageTabList)) {
*pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
index b510ae951c..ee34fd9edd 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Selection Item control pattern provider. Used for List items and radio buttons.
class QWindowsUiaSelectionItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ISelectionItemProvider>
+ public QComObject<ISelectionItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
index 45f3b20552..37148c655a 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
@@ -41,17 +41,23 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::GetSelection(SAFEARRAY *
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
- // First put selected items in a list, then build a safe array with the right size.
+ // First get/create list of selected items, then build a safe array with the right size.
QList<QAccessibleInterface *> selectedList;
- for (int i = 0; i < accessible->childCount(); ++i) {
- if (QAccessibleInterface *child = accessible->child(i)) {
- if (accessible->role() == QAccessible::PageTabList) {
- if (child->role() == QAccessible::PageTab && child->state().focused) {
- selectedList.append(child);
- }
- } else {
- if (child->state().selected) {
- selectedList.append(child);
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ selectedList = selectionInterface->selectedItems();
+ } else {
+ const int childCount = accessible->childCount();
+ selectedList.reserve(childCount);
+ for (int i = 0; i < childCount; ++i) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused) {
+ selectedList.append(child);
+ }
+ } else {
+ if (child->state().selected) {
+ selectedList.append(child);
+ }
}
}
}
@@ -102,11 +108,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(
// Initially returns false if none are selected. After the first selection, it may be required.
bool anySelected = false;
- for (int i = 0; i < accessible->childCount(); ++i) {
- if (QAccessibleInterface *child = accessible->child(i)) {
- if (child->state().selected) {
- anySelected = true;
- break;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ anySelected = selectionInterface->selectedItem(0) != nullptr;
+ } else {
+ for (int i = 0; i < accessible->childCount(); ++i) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (child->state().selected) {
+ anySelected = true;
+ break;
+ }
}
}
}
@@ -116,6 +126,134 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(
return S_OK;
}
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = nullptr;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ QAccessibleInterface *firstSelectedChild = nullptr;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ firstSelectedChild = selectionInterface->selectedItem(0);
+ if (!firstSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+ } else {
+ int i = 0;
+ while (!firstSelectedChild && i < accessible->childCount()) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ firstSelectedChild = child;
+ } else if (child->state().selected) {
+ firstSelectedChild = child;
+ }
+ }
+ i++;
+ }
+ }
+
+ if (!firstSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(firstSelectedChild))
+ {
+ *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = nullptr;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ QAccessibleInterface *lastSelectedChild = nullptr;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ const int selectedItemCount = selectionInterface->selectedItemCount();
+ if (selectedItemCount <= 0)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+ lastSelectedChild = selectionInterface->selectedItem(selectedItemCount - 1);
+ } else {
+ int i = accessible->childCount() - 1;
+ while (!lastSelectedChild && i >= 0) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ lastSelectedChild = child;
+ } else if (child->state().selected) {
+ lastSelectedChild = child;
+ }
+ }
+ i--;
+ }
+ }
+
+ if (!lastSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(lastSelectedChild))
+ {
+ *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+ return get_FirstSelectedItem(pRetVal);
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_ItemCount(__RPC__out int *pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = -1;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface())
+ *pRetVal = selectionInterface->selectedItemCount();
+ else {
+ int selectedCount = 0;
+ for (int i = 0; i < accessible->childCount(); i++) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ selectedCount++;
+ } else if (child->state().selected) {
+ selectedCount++;
+ }
+ }
+ }
+ *pRetVal = selectedCount;
+ }
+
+ return S_OK;
+}
+
QT_END_NAMESPACE
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
index 9a187c9413..7a899e4261 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
@@ -11,9 +11,22 @@
QT_BEGIN_NAMESPACE
+namespace QtPrivate {
+
+template <>
+struct QComObjectTraits<ISelectionProvider2>
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return QComObjectTraits<ISelectionProvider2, ISelectionProvider>::isGuidOf(riid);
+ }
+};
+
+} // namespace QtPrivate
+
// Implements the Selection control pattern provider. Used for Lists.
class QWindowsUiaSelectionProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ISelectionProvider>
+ public QComObject<ISelectionProvider2>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionProvider)
public:
@@ -24,6 +37,12 @@ public:
HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(BOOL *pRetVal) override;
HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(BOOL *pRetVal) override;
+
+ // ISelectionProvider2
+ HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *pRetVal) override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
index 8aed180671..3bb0e1027e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Table Item control pattern provider. Used by items within a table/tree.
class QWindowsUiaTableItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITableItemProvider>
+ public QComObject<ITableItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTableItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
index 6454bb9441..8beb11decf 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Table control pattern provider. Used by tables/trees.
-class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITableProvider>
+class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider, public QComObject<ITableProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTableProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
index e3b7c69b60..172836232e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
@@ -26,20 +26,6 @@ QWindowsUiaTextProvider::~QWindowsUiaTextProvider()
{
}
-HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::QueryInterface(REFIID iid, LPVOID *iface)
-{
- qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
-
- if (!iface)
- return E_INVALIDARG;
- *iface = nullptr;
-
- const bool result = qWindowsComQueryUnknownInterfaceMulti<ITextProvider>(this, iid, iface)
- || qWindowsComQueryInterface<ITextProvider>(this, iid, iface)
- || qWindowsComQueryInterface<ITextProvider2>(this, iid, iface);
- return result ? S_OK : E_NOINTERFACE;
-}
-
// Returns an array of providers for the selected text ranges.
HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetSelection(SAFEARRAY **pRetVal)
{
@@ -146,9 +132,10 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromPoint(UiaPoint point
nativeUiaPointToPoint(point, window, &pt);
int offset = textInterface->offsetAtPoint(pt);
- if ((offset >= 0) && (offset < textInterface->characterCount())) {
- *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset);
- }
+ if (offset < 0 || offset >= textInterface->characterCount())
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset);
return S_OK;
}
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
index 5e6d430f1a..8f886510b4 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
@@ -12,18 +12,27 @@
QT_BEGIN_NAMESPACE
+namespace QtPrivate {
+
+template <>
+struct QComObjectTraits<ITextProvider2>
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return QComObjectTraits<ITextProvider2, ITextProvider>::isGuidOf(riid);
+ }
+};
+
+} // namespace QtPrivate
+
// Implements the Text control pattern provider. Used for text controls.
-class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITextProvider2>
+class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider, public QComObject<ITextProvider2>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTextProvider)
public:
explicit QWindowsUiaTextProvider(QAccessible::Id id);
~QWindowsUiaTextProvider();
- // IUnknown overrides
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override;
-
// ITextProvider
HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override;
HRESULT STDMETHODCALLTYPE GetVisibleRanges(SAFEARRAY **pRetVal) override;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
index 4d02036196..a62a33cfe2 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
@@ -167,6 +167,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTAT
else
setVariantI4(CaretPosition_Unknown, pRetVal);
break;
+ case UIA_StrikethroughStyleAttributeId:
+ {
+ const QString value = valueForIA2Attribute(textInterface, QStringLiteral("text-line-through-type"));
+ if (value.isEmpty())
+ break;
+ const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value);
+ setVariantI4(uiaLineStyle, pRetVal);
+ break;
+ }
default:
break;
}
@@ -307,14 +316,14 @@ HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVa
int len = textInterface->characterCount();
- if (len < 1)
+ if (len < 1 || count == 0) // MSDN: "Zero has no effect."
return S_OK;
if (unit == TextUnit_Character) {
// Moves the start point, ensuring it lies within the bounds.
- int start = qBound(0, m_startOffset + count, len - 1);
+ int start = qBound(0, m_startOffset + count, len);
// If range was initially empty, leaves it as is; otherwise, normalizes it to one char.
- m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start;
+ m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start;
*pRetVal = start - m_startOffset; // Returns the actually moved distance.
m_startOffset = start;
} else {
@@ -385,7 +394,7 @@ HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoin
if (unit == TextUnit_Character) {
if (endpoint == TextPatternRangeEndpoint_Start) {
- int boundedValue = qBound(0, m_startOffset + count, len - 1);
+ int boundedValue = qBound(0, m_startOffset + count, len);
*pRetVal = boundedValue - m_startOffset;
m_startOffset = boundedValue;
m_endOffset = qBound(m_startOffset, m_endOffset, len);
@@ -517,6 +526,42 @@ HRESULT QWindowsUiaTextRangeProvider::unselect()
return S_OK;
}
+// helper method to retrieve the value of the given IAccessible2 text attribute,
+// or an empty string if not set
+QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface,
+ const QString &key)
+{
+ Q_ASSERT(textInterface);
+
+ int startOffset;
+ int endOffset;
+ const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset);
+ // don't report if attributes don't apply for the whole range
+ if (startOffset > m_startOffset || endOffset < m_endOffset)
+ return {};
+
+ for (auto attr : QStringTokenizer{attributes, u';'})
+ {
+ const QList<QStringView> items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
+ if (items.count() == 2 && items[0] == key)
+ return items[1].toString();
+ }
+
+ return {};
+}
+
+TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle)
+{
+ if (ia2LineStyle == QStringLiteral("none"))
+ return TextDecorationLineStyle_None;
+ if (ia2LineStyle == QStringLiteral("single"))
+ return TextDecorationLineStyle_Single;
+ if (ia2LineStyle == QStringLiteral("double"))
+ return TextDecorationLineStyle_Double;
+
+ return TextDecorationLineStyle_Other;
+}
+
QT_END_NAMESPACE
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
index f7d28a34d2..a37429a603 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Text Range control pattern provider. Used for text controls.
class QWindowsUiaTextRangeProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITextRangeProvider>
+ public QComObject<ITextRangeProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTextRangeProvider)
public:
@@ -42,6 +42,8 @@ public:
private:
HRESULT unselect();
+ QString valueForIA2Attribute(QAccessibleTextInterface *textInterface, const QString &key);
+ TextDecorationLineStyle uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle);
int m_startOffset;
int m_endOffset;
};
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
index 742e26f6d6..17150dfbe0 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Toggle control pattern provider. Used for checkboxes.
-class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IToggleProvider>
+class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider, public QComObject<IToggleProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaToggleProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
index 9287e89084..78ab3e890e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
@@ -176,6 +176,9 @@ long roleToControlTypeId(QAccessible::Role role)
{QAccessible::PageTabList, UIA_TabControlTypeId},
{QAccessible::Clock, UIA_CustomControlTypeId},
{QAccessible::Splitter, UIA_CustomControlTypeId},
+ {QAccessible::Paragraph, UIA_TextControlTypeId},
+ {QAccessible::WebDocument, UIA_DocumentControlTypeId},
+ {QAccessible::Heading, UIA_TextControlTypeId},
};
return mapping.value(role, UIA_CustomControlTypeId);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
index 8fe8b1c6d7..bf90211cec 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
@@ -12,7 +12,7 @@
#include <QtGui/qaccessible.h>
#include <QtGui/qwindow.h>
#include <QtCore/qrect.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
+#include "qwindowsuiautomation.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp
new file mode 100644
index 0000000000..1593a07202
--- /dev/null
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtGui/qtguiglobal.h>
+#if QT_CONFIG(accessibility)
+
+#include "qwindowsuiautomation.h"
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+
+template<typename T, typename... TArg>
+struct winapi_func
+{
+ using func_t = T(WINAPI*)(TArg...);
+ const func_t func;
+ const T error_value;
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+ winapi_func(const char *lib_name, const char *func_name, func_t func_proto,
+ T error_value = T(__HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND))) :
+ func(reinterpret_cast<func_t>(GetProcAddress(LoadLibraryA(lib_name), func_name))),
+ error_value(error_value)
+ {
+ std::ignore = func_proto;
+ }
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+ T invoke(TArg... arg)
+ {
+ if (!func)
+ return error_value;
+ return func(arg...);
+ }
+};
+
+#define FN(fn) #fn,fn
+
+BOOL WINAPI UiaClientsAreListening()
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaClientsAreListening), BOOL(false));
+ return func.invoke();
+}
+
+LRESULT WINAPI UiaReturnRawElementProvider(
+ HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaReturnRawElementProvider));
+ return func.invoke(hwnd, wParam, lParam, el);
+}
+
+HRESULT WINAPI UiaHostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaHostProviderFromHwnd));
+ return func.invoke(hwnd, ppProvider);
+}
+
+HRESULT WINAPI UiaRaiseAutomationPropertyChangedEvent(
+ IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationPropertyChangedEvent));
+ return func.invoke(pProvider, id, oldValue, newValue);
+}
+
+HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationEvent));
+ return func.invoke(pProvider, id);
+}
+
+#endif // defined(__MINGW32__) || defined(__MINGW64__)
+
+#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h
new file mode 100644
index 0000000000..a192b9b0fb
--- /dev/null
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSUIAUTOMATION_H
+#define QWINDOWSUIAUTOMATION_H
+
+#include <QtGui/qtguiglobal.h>
+#if QT_CONFIG(accessibility)
+
+#include <uiautomation.h>
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+
+#define UIA_SelectionPattern2Id 10034
+#define UIA_IsReadOnlyAttributeId 40015
+#define UIA_StrikethroughStyleAttributeId 40026
+#define UIA_CaretPositionAttributeId 40038
+
+enum CaretPosition {
+ CaretPosition_Unknown = 0,
+ CaretPosition_EndOfLine = 1,
+ CaretPosition_BeginningOfLine = 2
+};
+
+enum TextDecorationLineStyle {
+ TextDecorationLineStyle_None = 0,
+ TextDecorationLineStyle_Single = 1,
+ TextDecorationLineStyle_WordsOnly = 2,
+ TextDecorationLineStyle_Double = 3,
+ TextDecorationLineStyle_Dot = 4,
+ TextDecorationLineStyle_Dash = 5,
+ TextDecorationLineStyle_DashDot = 6,
+ TextDecorationLineStyle_DashDotDot = 7,
+ TextDecorationLineStyle_Wavy = 8,
+ TextDecorationLineStyle_ThickSingle = 9,
+ TextDecorationLineStyle_DoubleWavy = 11,
+ TextDecorationLineStyle_ThickWavy = 12,
+ TextDecorationLineStyle_LongDash = 13,
+ TextDecorationLineStyle_ThickDash = 14,
+ TextDecorationLineStyle_ThickDashDot = 15,
+ TextDecorationLineStyle_ThickDashDotDot = 16,
+ TextDecorationLineStyle_ThickDot = 17,
+ TextDecorationLineStyle_ThickLongDash = 18,
+ TextDecorationLineStyle_Other = -1
+};
+
+BOOL WINAPI UiaClientsAreListening();
+
+#ifndef __ISelectionProvider2_INTERFACE_DEFINED__
+#define __ISelectionProvider2_INTERFACE_DEFINED__
+DEFINE_GUID(IID_ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7);
+MIDL_INTERFACE("14f68475-ee1c-44f6-a869-d239381f0fe7")
+ISelectionProvider2 : public ISelectionProvider
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *retVal) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7)
+#endif
+#endif // __ISelectionProvider2_INTERFACE_DEFINED__
+
+#endif // defined(__MINGW32__) || defined(__MINGW64__)
+
+#endif // QT_CONFIG(accessibility)
+
+#endif // QWINDOWSUIAUTOMATION_H
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
index 2f52019d33..8c0a6b8ee7 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
@@ -13,8 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Value control pattern provider.
// Supported for all controls that can return text(QAccessible::Value).
-class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IValueProvider>
+class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider, public QComObject<IValueProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaValueProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
index e277b3f5c5..89a1655232 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
@@ -11,8 +11,7 @@
QT_BEGIN_NAMESPACE
-class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IWindowProvider>
+class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider, public QComObject<IWindowProvider>
{
Q_DISABLE_COPY(QWindowsUiaWindowProvider)
public:
diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt
index 7fe0e8177c..e8fb442dd4 100644
--- a/src/plugins/platforms/xcb/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/CMakeLists.txt
@@ -1,12 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from xcb.pro.
-
#####################################################################
## XcbQpaPrivate Module:
#####################################################################
+if(GCC)
+ # Work around GCC ABI issues
+ add_compile_options(-Wno-psabi)
+endif()
+
qt_internal_add_module(XcbQpaPrivate
CONFIG_MODULE_NAME xcb_qpa_lib
INTERNAL_MODULE
@@ -59,23 +62,16 @@ qt_internal_add_module(XcbQpaPrivate
XCB::SYNC
XCB::XCB
XCB::XFIXES
- # XCB::XINPUT # special case remove handled below
XCB::XKB
XKB::XKB
+ NO_UNITY_BUILD # X11 define clashes
)
-# special case begin
qt_disable_apple_app_extension_api_only(XcbQpaPrivate)
-# special case end
## Scopes:
#####################################################################
-qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_opengl
- PUBLIC_LIBRARIES
- Qt::OpenGLPrivate
-)
-
qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_glib
LIBRARIES
GLIB2::GLIB2
@@ -91,12 +87,7 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_xlib
qt_xlib_wrapper.c qt_xlib_wrapper.h
PUBLIC_LIBRARIES
X11::XCB
- # special case begin
- # 'QMAKE_USE += xcb_xlib' in qmake implies also += xlib (aka X11)
- # due to "use": "xcb xlib" in src/gui/configure.json.
- # That's not yet handled by the conversion scripts unfortunately.
X11::X11
- # special case end
)
qt_internal_extend_target(XcbQpaPrivate CONDITION NOT QT_FEATURE_xcb_xlib
@@ -152,7 +143,6 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_F
WrapFreetype::WrapFreetype
)
-# special case begin
if(QT_FEATURE_system_xcb_xinput)
qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT)
else()
@@ -166,7 +156,6 @@ else()
"${PROJECT_SOURCE_DIR}/src/3rdparty/xcb/include"
)
endif()
-# special case end
#####################################################################
## QXcbIntegrationPlugin Plugin:
@@ -175,7 +164,7 @@ endif()
qt_internal_add_plugin(QXcbIntegrationPlugin
OUTPUT_NAME qxcb
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES xcb # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES xcb
SOURCES
qxcbmain.cpp
DEFINES
@@ -186,16 +175,7 @@ qt_internal_add_plugin(QXcbIntegrationPlugin
Qt::XcbQpaPrivate
)
-#### Keys ignored in scope 18:.:.:xcb-plugin.pro:<TRUE>:
-# OTHER_FILES = "xcb.json" "README"
-
-## Scopes:
-#####################################################################
-
-#### Keys ignored in scope 20:.:.:xcb-plugin.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(gl_integrations)
if(OFF)
- add_subdirectory(xcb-static) # special case TODO: xcb-static sub folder
+ add_subdirectory(xcb-static)
endif()
-
diff --git a/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
index dcc5912f7c..957beb9ed4 100644
--- a/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from gl_integrations.pro.
-
if(QT_FEATURE_xcb_egl_plugin)
add_subdirectory(xcb_egl)
endif()
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
index a358ff788d..12938c159a 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
@@ -1,13 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from xcb_egl.pro.
-
#####################################################################
## QXcbEglIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
qt_internal_add_plugin(QXcbEglIntegrationPlugin
OUTPUT_NAME qxcb-egl-integration
@@ -29,5 +27,6 @@ qt_internal_add_plugin(QXcbEglIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
Qt::XcbQpaPrivate
- EGL::EGL # special case
+ EGL::EGL
+ NO_UNITY_BUILD # X11 define clashes
)
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
index 1e93ea6805..133b992cd9 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
@@ -58,6 +58,7 @@ std::optional<VisualInfo> getVisualInfo(xcb_screen_t *screen,
QXcbEglIntegration::QXcbEglIntegration()
: m_connection(nullptr)
, m_egl_display(EGL_NO_DISPLAY)
+ , m_using_platform_display(false)
{
qCDebug(lcQpaGl) << "Xcb EGL gl-integration created";
}
@@ -80,6 +81,7 @@ bool QXcbEglIntegration::initialize(QXcbConnection *connection)
m_egl_display = streamFuncs.get_platform_display(EGL_PLATFORM_X11_KHR,
m_connection->xlib_display(),
nullptr);
+ m_using_platform_display = true;
}
#if QT_CONFIG(egl_x11)
@@ -92,16 +94,19 @@ bool QXcbEglIntegration::initialize(QXcbConnection *connection)
m_egl_display = streamFuncs.get_platform_display(EGL_PLATFORM_XCB_KHR,
reinterpret_cast<void *>(connection->xcb_connection()),
nullptr);
+ m_using_platform_display = true;
}
#endif
EGLint major, minor;
bool success = eglInitialize(m_egl_display, &major, &minor);
+#if QT_CONFIG(egl_x11)
if (!success) {
m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
qCDebug(lcQpaGl) << "Xcb EGL gl-integration retrying with display" << m_egl_display;
success = eglInitialize(m_egl_display, &major, &minor);
}
+#endif
m_native_interface_handler.reset(new QXcbEglNativeInterfaceHandler(connection->nativeInterface()));
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
index ff0804678f..7caf4304bc 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
@@ -39,10 +39,13 @@ public:
EGLDisplay eglDisplay() const { return m_egl_display; }
+ bool usingPlatformDisplay() const { return m_using_platform_display; }
+
xcb_visualid_t getCompatibleVisualId(xcb_screen_t *screen, EGLConfig config) const;
private:
QXcbConnection *m_connection;
EGLDisplay m_egl_display;
+ bool m_using_platform_display;
QScopedPointer<QXcbEglNativeInterfaceHandler> m_native_interface_handler;
};
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
index 4d87b08db8..bf2ceb96f4 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
@@ -7,6 +7,10 @@
#include <QtGui/private/qeglconvenience_p.h>
+#ifndef EGL_EXT_platform_base
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list);
+#endif
+
QT_BEGIN_NAMESPACE
QXcbEglWindow::QXcbEglWindow(QWindow *window, QXcbEglIntegration *glIntegration)
@@ -42,7 +46,20 @@ void QXcbEglWindow::create()
{
QXcbWindow::create();
+ // this is always true without egl_x11
+ if (m_glIntegration->usingPlatformDisplay()) {
+ auto createPlatformWindowSurface = reinterpret_cast<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>(
+ eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"));
+ m_surface = createPlatformWindowSurface(m_glIntegration->eglDisplay(),
+ m_config,
+ reinterpret_cast<void *>(&m_window),
+ nullptr);
+ return;
+ }
+
+#if QT_CONFIG(egl_x11)
m_surface = eglCreateWindowSurface(m_glIntegration->eglDisplay(), m_config, m_window, nullptr);
+#endif
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
index 24a5e31063..f9f78ad1eb 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from xcb_glx.pro.
-
#####################################################################
## QXcbGlxIntegrationPlugin Plugin:
#####################################################################
@@ -27,6 +25,7 @@ qt_internal_add_plugin(QXcbGlxIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
Qt::XcbQpaPrivate
+ NO_UNITY_BUILD # X11 define clashes
)
## Scopes:
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
index 103bb1e22b..04eac027cd 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
@@ -395,14 +395,19 @@ QGLXContext::QGLXContext(Display *display, GLXContext context, void *visualInfo,
int numConfigs = 0;
static const int attribs[] = { GLX_FBCONFIG_ID, configId, None };
configs = glXChooseFBConfig(m_display, screenNumber, attribs, &numConfigs);
- if (!configs || numConfigs < 1) {
+ if (!configs) {
+ qWarning("QGLXContext: Failed to find config(invalid arguments for glXChooseFBConfig)");
+ return;
+ } else if (numConfigs < 1) {
qWarning("QGLXContext: Failed to find config");
+ XFree(configs);
return;
}
if (configs && numConfigs > 1) // this is suspicious so warn but let it continue
qWarning("QGLXContext: Multiple configs for FBConfig ID %d", configId);
m_config = configs[0];
+ XFree(configs);
}
Q_ASSERT(vinfo || m_config);
diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
index 96a19552a5..d523eecc5a 100644
--- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
@@ -16,10 +16,6 @@
#include <X11/Xlib.h>
#undef register
-#ifndef None
-#define None 0L
-#endif
-
QT_BEGIN_NAMESPACE
QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window)
@@ -78,11 +74,8 @@ void QXcbNativeBackingStore::flush(QWindow *window, const QRegion &region, const
XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size());
- XRenderComposite(display(), PictOpSrc, srcPic, None, dstPic,
- br.x() + offset.x(), br.y() + offset.y(),
- 0, 0,
- br.x(), br.y(),
- br.width(), br.height());
+ XRenderComposite(display(), PictOpSrc, srcPic, 0L /*None*/, dstPic, br.x() + offset.x(),
+ br.y() + offset.y(), 0, 0, br.x(), br.y(), br.width(), br.height());
XRenderFreePicture(display(), dstPic);
}
diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
index f630f538aa..743ff92654 100644
--- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
@@ -2134,7 +2134,7 @@ void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRect
XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh);
restore_clip = true;
} else if (mono_dst && !mono_src) {
- QBitmap bitmap(pixmap);
+ QBitmap bitmap = QBitmap::fromPixmap(pixmap);
XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
} else {
XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
index 186a3cf9d7..a4e820ea92 100644
--- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
@@ -1314,7 +1314,7 @@ QBitmap QX11PlatformPixmap::mask() const
#endif
if (d == 1) {
QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this);
- mask = QPixmap(that);
+ mask = QBitmap::fromPixmap(QPixmap(that));
} else {
mask = mask_to_bitmap(xinfo.screen());
}
diff --git a/src/plugins/platforms/xcb/qxcbatom.cpp b/src/plugins/platforms/xcb/qxcbatom.cpp
index fb5c97fe38..dd5596653c 100644
--- a/src/plugins/platforms/xcb/qxcbatom.cpp
+++ b/src/plugins/platforms/xcb/qxcbatom.cpp
@@ -54,6 +54,8 @@ static const char *xcb_atomnames = {
"_QT_CLOSE_CONNECTION\0"
+ "_QT_GET_TIMESTAMP\0"
+
"_MOTIF_WM_HINTS\0"
"DTWM_IS_RUNNING\0"
@@ -109,6 +111,7 @@ static const char *xcb_atomnames = {
"_NET_WM_WINDOW_TYPE_NORMAL\0"
"_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0"
+ "_KDE_NET_WM_DESKTOP_FILE\0"
"_KDE_NET_WM_FRAME_STRUT\0"
"_NET_FRAME_EXTENTS\0"
@@ -194,6 +197,7 @@ static const char *xcb_atomnames = {
"_COMPIZ_DECOR_REQUEST\0"
"_COMPIZ_DECOR_DELETE_PIXMAP\0"
"_COMPIZ_TOOLKIT_ACTION\0"
+ "_GTK_APPLICATION_ID\0"
"_GTK_LOAD_ICONTHEMES\0"
"AT_SPI_BUS\0"
"EDID\0"
@@ -213,29 +217,25 @@ void QXcbAtom::initialize(xcb_connection_t *connection)
}
void QXcbAtom::initializeAllAtoms(xcb_connection_t *connection) {
- const char *names[QXcbAtom::NAtoms];
- const char *ptr = xcb_atomnames;
-
+ const char *name = xcb_atomnames;
+ size_t name_len;
int i = 0;
- while (*ptr) {
- names[i++] = ptr;
- while (*ptr)
- ++ptr;
- ++ptr;
- }
-
- Q_ASSERT(i == QXcbAtom::NAtoms);
-
xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms];
+ while ((name_len = strlen(name)) != 0) {
+ cookies[i] = xcb_intern_atom(connection, false, name_len, name);
+ ++i;
+ name += name_len + 1; // jump over the \0
+ }
+
Q_ASSERT(i == QXcbAtom::NAtoms);
- for (i = 0; i < QXcbAtom::NAtoms; ++i)
- cookies[i] = xcb_intern_atom(connection, false, strlen(names[i]), names[i]);
for (i = 0; i < QXcbAtom::NAtoms; ++i) {
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookies[i], nullptr);
- m_allAtoms[i] = reply->atom;
- free(reply);
+ if (reply) {
+ m_allAtoms[i] = reply->atom;
+ free(reply);
+ }
}
}
diff --git a/src/plugins/platforms/xcb/qxcbatom.h b/src/plugins/platforms/xcb/qxcbatom.h
index 2171875e00..bc677eaf3e 100644
--- a/src/plugins/platforms/xcb/qxcbatom.h
+++ b/src/plugins/platforms/xcb/qxcbatom.h
@@ -10,202 +10,206 @@ class QXcbAtom
public:
enum Atom {
// window-manager <-> client protocols
- WM_PROTOCOLS,
- WM_DELETE_WINDOW,
- WM_TAKE_FOCUS,
- _NET_WM_PING,
- _NET_WM_CONTEXT_HELP,
- _NET_WM_SYNC_REQUEST,
- _NET_WM_SYNC_REQUEST_COUNTER,
- MANAGER, // System tray notification
- _NET_SYSTEM_TRAY_OPCODE, // System tray operation
+ AtomWM_PROTOCOLS,
+ AtomWM_DELETE_WINDOW,
+ AtomWM_TAKE_FOCUS,
+ Atom_NET_WM_PING,
+ Atom_NET_WM_CONTEXT_HELP,
+ Atom_NET_WM_SYNC_REQUEST,
+ Atom_NET_WM_SYNC_REQUEST_COUNTER,
+ AtomMANAGER, // System tray notification
+ Atom_NET_SYSTEM_TRAY_OPCODE, // System tray operation
// ICCCM window state
- WM_STATE,
- WM_CHANGE_STATE,
- WM_CLASS,
- WM_NAME,
+ AtomWM_STATE,
+ AtomWM_CHANGE_STATE,
+ AtomWM_CLASS,
+ AtomWM_NAME,
// Session management
- WM_CLIENT_LEADER,
- WM_WINDOW_ROLE,
- SM_CLIENT_ID,
- WM_CLIENT_MACHINE,
+ AtomWM_CLIENT_LEADER,
+ AtomWM_WINDOW_ROLE,
+ AtomSM_CLIENT_ID,
+ AtomWM_CLIENT_MACHINE,
// Clipboard
- CLIPBOARD,
- INCR,
- TARGETS,
- MULTIPLE,
- TIMESTAMP,
- SAVE_TARGETS,
- CLIP_TEMPORARY,
- _QT_SELECTION,
- _QT_CLIPBOARD_SENTINEL,
- _QT_SELECTION_SENTINEL,
- CLIPBOARD_MANAGER,
+ AtomCLIPBOARD,
+ AtomINCR,
+ AtomTARGETS,
+ AtomMULTIPLE,
+ AtomTIMESTAMP,
+ AtomSAVE_TARGETS,
+ AtomCLIP_TEMPORARY,
+ Atom_QT_SELECTION,
+ Atom_QT_CLIPBOARD_SENTINEL,
+ Atom_QT_SELECTION_SENTINEL,
+ AtomCLIPBOARD_MANAGER,
- RESOURCE_MANAGER,
+ AtomRESOURCE_MANAGER,
- _XSETROOT_ID,
+ Atom_XSETROOT_ID,
- _QT_SCROLL_DONE,
- _QT_INPUT_ENCODING,
+ Atom_QT_SCROLL_DONE,
+ Atom_QT_INPUT_ENCODING,
// Qt/XCB specific
- _QT_CLOSE_CONNECTION,
+ Atom_QT_CLOSE_CONNECTION,
- _MOTIF_WM_HINTS,
+ Atom_QT_GET_TIMESTAMP,
- DTWM_IS_RUNNING,
- ENLIGHTENMENT_DESKTOP,
- _DT_SAVE_MODE,
- _SGI_DESKS_MANAGER,
+ Atom_MOTIF_WM_HINTS,
+
+ AtomDTWM_IS_RUNNING,
+ AtomENLIGHTENMENT_DESKTOP,
+ Atom_DT_SAVE_MODE,
+ Atom_SGI_DESKS_MANAGER,
// EWMH (aka NETWM)
- _NET_SUPPORTED,
- _NET_VIRTUAL_ROOTS,
- _NET_WORKAREA,
-
- _NET_MOVERESIZE_WINDOW,
- _NET_WM_MOVERESIZE,
-
- _NET_WM_NAME,
- _NET_WM_ICON_NAME,
- _NET_WM_ICON,
-
- _NET_WM_PID,
-
- _NET_WM_WINDOW_OPACITY,
-
- _NET_WM_STATE,
- _NET_WM_STATE_ABOVE,
- _NET_WM_STATE_BELOW,
- _NET_WM_STATE_FULLSCREEN,
- _NET_WM_STATE_MAXIMIZED_HORZ,
- _NET_WM_STATE_MAXIMIZED_VERT,
- _NET_WM_STATE_MODAL,
- _NET_WM_STATE_STAYS_ON_TOP,
- _NET_WM_STATE_DEMANDS_ATTENTION,
- _NET_WM_STATE_HIDDEN,
-
- _NET_WM_USER_TIME,
- _NET_WM_USER_TIME_WINDOW,
- _NET_WM_FULL_PLACEMENT,
-
- _NET_WM_WINDOW_TYPE,
- _NET_WM_WINDOW_TYPE_DESKTOP,
- _NET_WM_WINDOW_TYPE_DOCK,
- _NET_WM_WINDOW_TYPE_TOOLBAR,
- _NET_WM_WINDOW_TYPE_MENU,
- _NET_WM_WINDOW_TYPE_UTILITY,
- _NET_WM_WINDOW_TYPE_SPLASH,
- _NET_WM_WINDOW_TYPE_DIALOG,
- _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
- _NET_WM_WINDOW_TYPE_POPUP_MENU,
- _NET_WM_WINDOW_TYPE_TOOLTIP,
- _NET_WM_WINDOW_TYPE_NOTIFICATION,
- _NET_WM_WINDOW_TYPE_COMBO,
- _NET_WM_WINDOW_TYPE_DND,
- _NET_WM_WINDOW_TYPE_NORMAL,
- _KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
-
- _KDE_NET_WM_FRAME_STRUT,
- _NET_FRAME_EXTENTS,
-
- _NET_STARTUP_INFO,
- _NET_STARTUP_INFO_BEGIN,
- _NET_STARTUP_ID,
-
- _NET_SUPPORTING_WM_CHECK,
-
- _NET_WM_CM_S0,
-
- _NET_SYSTEM_TRAY_VISUAL,
-
- _NET_ACTIVE_WINDOW,
+ Atom_NET_SUPPORTED,
+ Atom_NET_VIRTUAL_ROOTS,
+ Atom_NET_WORKAREA,
+
+ Atom_NET_MOVERESIZE_WINDOW,
+ Atom_NET_WM_MOVERESIZE,
+
+ Atom_NET_WM_NAME,
+ Atom_NET_WM_ICON_NAME,
+ Atom_NET_WM_ICON,
+
+ Atom_NET_WM_PID,
+
+ Atom_NET_WM_WINDOW_OPACITY,
+
+ Atom_NET_WM_STATE,
+ Atom_NET_WM_STATE_ABOVE,
+ Atom_NET_WM_STATE_BELOW,
+ Atom_NET_WM_STATE_FULLSCREEN,
+ Atom_NET_WM_STATE_MAXIMIZED_HORZ,
+ Atom_NET_WM_STATE_MAXIMIZED_VERT,
+ Atom_NET_WM_STATE_MODAL,
+ Atom_NET_WM_STATE_STAYS_ON_TOP,
+ Atom_NET_WM_STATE_DEMANDS_ATTENTION,
+ Atom_NET_WM_STATE_HIDDEN,
+
+ Atom_NET_WM_USER_TIME,
+ Atom_NET_WM_USER_TIME_WINDOW,
+ Atom_NET_WM_FULL_PLACEMENT,
+
+ Atom_NET_WM_WINDOW_TYPE,
+ Atom_NET_WM_WINDOW_TYPE_DESKTOP,
+ Atom_NET_WM_WINDOW_TYPE_DOCK,
+ Atom_NET_WM_WINDOW_TYPE_TOOLBAR,
+ Atom_NET_WM_WINDOW_TYPE_MENU,
+ Atom_NET_WM_WINDOW_TYPE_UTILITY,
+ Atom_NET_WM_WINDOW_TYPE_SPLASH,
+ Atom_NET_WM_WINDOW_TYPE_DIALOG,
+ Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
+ Atom_NET_WM_WINDOW_TYPE_POPUP_MENU,
+ Atom_NET_WM_WINDOW_TYPE_TOOLTIP,
+ Atom_NET_WM_WINDOW_TYPE_NOTIFICATION,
+ Atom_NET_WM_WINDOW_TYPE_COMBO,
+ Atom_NET_WM_WINDOW_TYPE_DND,
+ Atom_NET_WM_WINDOW_TYPE_NORMAL,
+ Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
+
+ Atom_KDE_NET_WM_DESKTOP_FILE,
+ Atom_KDE_NET_WM_FRAME_STRUT,
+ Atom_NET_FRAME_EXTENTS,
+
+ Atom_NET_STARTUP_INFO,
+ Atom_NET_STARTUP_INFO_BEGIN,
+ Atom_NET_STARTUP_ID,
+
+ Atom_NET_SUPPORTING_WM_CHECK,
+
+ Atom_NET_WM_CM_S0,
+
+ Atom_NET_SYSTEM_TRAY_VISUAL,
+
+ Atom_NET_ACTIVE_WINDOW,
// Property formats
- TEXT,
- UTF8_STRING,
- CARDINAL,
+ AtomTEXT,
+ AtomUTF8_STRING,
+ AtomCARDINAL,
// Xdnd
- XdndEnter,
- XdndPosition,
- XdndStatus,
- XdndLeave,
- XdndDrop,
- XdndFinished,
- XdndTypelist,
- XdndActionList,
-
- XdndSelection,
-
- XdndAware,
- XdndProxy,
-
- XdndActionCopy,
- XdndActionLink,
- XdndActionMove,
- XdndActionAsk,
- XdndActionPrivate,
+ AtomXdndEnter,
+ AtomXdndPosition,
+ AtomXdndStatus,
+ AtomXdndLeave,
+ AtomXdndDrop,
+ AtomXdndFinished,
+ AtomXdndTypelist,
+ AtomXdndActionList,
+
+ AtomXdndSelection,
+
+ AtomXdndAware,
+ AtomXdndProxy,
+
+ AtomXdndActionCopy,
+ AtomXdndActionLink,
+ AtomXdndActionMove,
+ AtomXdndActionAsk,
+ AtomXdndActionPrivate,
// Xkb
- _XKB_RULES_NAMES,
+ Atom_XKB_RULES_NAMES,
// XEMBED
- _XEMBED,
- _XEMBED_INFO,
+ Atom_XEMBED,
+ Atom_XEMBED_INFO,
// XInput2
- ButtonLeft,
- ButtonMiddle,
- ButtonRight,
- ButtonWheelUp,
- ButtonWheelDown,
- ButtonHorizWheelLeft,
- ButtonHorizWheelRight,
- AbsMTPositionX,
- AbsMTPositionY,
- AbsMTTouchMajor,
- AbsMTTouchMinor,
- AbsMTOrientation,
- AbsMTPressure,
- AbsMTTrackingID,
- MaxContacts,
- RelX,
- RelY,
+ AtomButtonLeft,
+ AtomButtonMiddle,
+ AtomButtonRight,
+ AtomButtonWheelUp,
+ AtomButtonWheelDown,
+ AtomButtonHorizWheelLeft,
+ AtomButtonHorizWheelRight,
+ AtomAbsMTPositionX,
+ AtomAbsMTPositionY,
+ AtomAbsMTTouchMajor,
+ AtomAbsMTTouchMinor,
+ AtomAbsMTOrientation,
+ AtomAbsMTPressure,
+ AtomAbsMTTrackingID,
+ AtomMaxContacts,
+ AtomRelX,
+ AtomRelY,
// XInput2 tablet
- AbsX,
- AbsY,
- AbsPressure,
- AbsTiltX,
- AbsTiltY,
- AbsWheel,
- AbsDistance,
- WacomSerialIDs,
- INTEGER,
- RelHorizWheel,
- RelVertWheel,
- RelHorizScroll,
- RelVertScroll,
-
- _XSETTINGS_SETTINGS,
-
- _COMPIZ_DECOR_PENDING,
- _COMPIZ_DECOR_REQUEST,
- _COMPIZ_DECOR_DELETE_PIXMAP,
- _COMPIZ_TOOLKIT_ACTION,
- _GTK_LOAD_ICONTHEMES,
-
- AT_SPI_BUS,
-
- EDID,
- EDID_DATA,
- XFree86_DDC_EDID1_RAWDATA,
-
- _ICC_PROFILE,
+ AtomAbsX,
+ AtomAbsY,
+ AtomAbsPressure,
+ AtomAbsTiltX,
+ AtomAbsTiltY,
+ AtomAbsWheel,
+ AtomAbsDistance,
+ AtomWacomSerialIDs,
+ AtomINTEGER,
+ AtomRelHorizWheel,
+ AtomRelVertWheel,
+ AtomRelHorizScroll,
+ AtomRelVertScroll,
+
+ Atom_XSETTINGS_SETTINGS,
+
+ Atom_COMPIZ_DECOR_PENDING,
+ Atom_COMPIZ_DECOR_REQUEST,
+ Atom_COMPIZ_DECOR_DELETE_PIXMAP,
+ Atom_COMPIZ_TOOLKIT_ACTION,
+ Atom_GTK_APPLICATION_ID,
+ Atom_GTK_LOAD_ICONTHEMES,
+
+ AtomAT_SPI_BUS,
+
+ AtomEDID,
+ AtomEDID_DATA,
+ AtomXFree86_DDC_EDID1_RAWDATA,
+
+ Atom_ICC_PROFILE,
NAtoms
};
diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
index f3905dff81..8353fac6a9 100644
--- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp
+++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
@@ -173,7 +173,7 @@ void QXcbBackingStoreImage::init(const QSize &size, uint depth, QImage::Format f
m_qimage_format = format;
m_hasAlpha = QImage::toPixelFormat(m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha;
if (!m_hasAlpha)
- m_qimage_format = qt_maybeAlphaVersionWithSameDepth(m_qimage_format);
+ m_qimage_format = qt_maybeDataCompatibleAlphaVersion(m_qimage_format);
memset(&m_shm_info, 0, sizeof m_shm_info);
@@ -877,8 +877,10 @@ QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window,
m_image->flushScrolledRegion(true);
- QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, textures, translucentBackground);
-
+ auto result = QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset,
+ textures, translucentBackground);
+ if (result != FlushSuccess)
+ return result;
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
if (platformWindow->needsSync()) {
platformWindow->updateSyncRequestCounter();
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
index 7c3a9d0741..40e2f47354 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp
@@ -31,7 +31,7 @@ public:
break;
case QClipboard::Clipboard:
- modeAtom = m_clipboard->atom(QXcbAtom::CLIPBOARD);
+ modeAtom = m_clipboard->atom(QXcbAtom::AtomCLIPBOARD);
break;
default:
@@ -61,7 +61,7 @@ protected:
// get the list of targets from the current clipboard owner - we do this
// once so that multiple calls to this function don't require multiple
// server round trips...
- that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::TARGETS));
+ that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::AtomTARGETS));
if (format_atoms.size() > 0) {
const xcb_atom_t *targets = (const xcb_atom_t *) format_atoms.data();
@@ -126,7 +126,7 @@ QXcbClipboardTransaction::QXcbClipboardTransaction(QXcbClipboard *clipboard, xcb
xcb_change_window_attributes(m_clipboard->xcb_connection(), m_window,
XCB_CW_EVENT_MASK, values);
- m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
+ m_abortTimerId = startTimer(std::chrono::milliseconds{m_clipboard->clipboardTimeout()});
}
QXcbClipboardTransaction::~QXcbClipboardTransaction()
@@ -145,7 +145,7 @@ bool QXcbClipboardTransaction::updateIncrementalProperty(const xcb_property_noti
// restart the timer
if (m_abortTimerId)
killTimer(m_abortTimerId);
- m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
+ m_abortTimerId = startTimer(std::chrono::milliseconds{m_clipboard->clipboardTimeout()});
uint bytes_left = uint(m_data.size()) - m_offset;
if (bytes_left > 0) {
@@ -203,7 +203,7 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c)
xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->qtSelectionOwner(),
XCB_ATOM_PRIMARY, mask);
xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::CLIPBOARD), mask);
+ atom(QXcbAtom::AtomCLIPBOARD), mask);
}
// xcb_change_property_request_t and xcb_get_property_request_t are the same size
@@ -218,13 +218,13 @@ QXcbClipboard::~QXcbClipboard()
m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
// First we check if there is a clipboard manager.
- if (connection()->selectionOwner(atom(QXcbAtom::CLIPBOARD_MANAGER)) != XCB_NONE) {
+ if (connection()->selectionOwner(atom(QXcbAtom::AtomCLIPBOARD_MANAGER)) != XCB_NONE) {
// we delete the property so the manager saves all TARGETS.
xcb_delete_property(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::_QT_SELECTION));
+ atom(QXcbAtom::Atom_QT_SELECTION));
xcb_convert_selection(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::CLIPBOARD_MANAGER), atom(QXcbAtom::SAVE_TARGETS),
- atom(QXcbAtom::_QT_SELECTION), connection()->time());
+ atom(QXcbAtom::AtomCLIPBOARD_MANAGER), atom(QXcbAtom::AtomSAVE_TARGETS),
+ atom(QXcbAtom::Atom_QT_SELECTION), connection()->time());
connection()->sync();
// waiting until the clipboard manager fetches the content.
@@ -259,7 +259,7 @@ bool QXcbClipboard::handlePropertyNotify(const xcb_generic_event_t *event)
xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
{
if (mode == QClipboard::Clipboard)
- return atom(QXcbAtom::CLIPBOARD);
+ return atom(QXcbAtom::AtomCLIPBOARD);
if (mode == QClipboard::Selection)
return XCB_ATOM_PRIMARY;
return XCB_NONE;
@@ -269,7 +269,7 @@ QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
{
if (a == XCB_ATOM_PRIMARY)
return QClipboard::Selection;
- if (a == atom(QXcbAtom::CLIPBOARD))
+ if (a == atom(QXcbAtom::AtomCLIPBOARD))
return QClipboard::Clipboard;
// not supported enum value, used to detect errors
return QClipboard::FindBuffer;
@@ -412,10 +412,10 @@ xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window
types.append(atoms.at(j));
}
}
- types.append(atom(QXcbAtom::TARGETS));
- types.append(atom(QXcbAtom::MULTIPLE));
- types.append(atom(QXcbAtom::TIMESTAMP));
- types.append(atom(QXcbAtom::SAVE_TARGETS));
+ types.append(atom(QXcbAtom::AtomTARGETS));
+ types.append(atom(QXcbAtom::AtomMULTIPLE));
+ types.append(atom(QXcbAtom::AtomTIMESTAMP));
+ types.append(atom(QXcbAtom::AtomSAVE_TARGETS));
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
32, types.size(), (const void *)types.constData());
@@ -439,7 +439,7 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
// don't allow INCR transfers when using MULTIPLE or to
// Motif clients (since Motif doesn't support INCR)
- static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::CLIP_TEMPORARY);
+ static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::AtomCLIP_TEMPORARY);
bool allow_incr = property != motif_clip_temporary;
// This 'bool' can be removed once there is a proper fix for QTBUG-32853
if (m_clipboard_closing)
@@ -448,7 +448,7 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
if (data.size() > m_maxPropertyRequestDataBytes && allow_incr) {
long bytes = data.size();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
- atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
+ atom(QXcbAtom::AtomINCR), 32, 1, (const void *)&bytes);
auto transaction = new QXcbClipboardTransaction(this, window, property, data, atomFormat, dataFormat);
m_transactions.insert(window, transaction);
return property;
@@ -532,9 +532,9 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
return;
}
- xcb_atom_t targetsAtom = atom(QXcbAtom::TARGETS);
- xcb_atom_t multipleAtom = atom(QXcbAtom::MULTIPLE);
- xcb_atom_t timestampAtom = atom(QXcbAtom::TIMESTAMP);
+ xcb_atom_t targetsAtom = atom(QXcbAtom::AtomTARGETS);
+ xcb_atom_t multipleAtom = atom(QXcbAtom::AtomMULTIPLE);
+ xcb_atom_t timestampAtom = atom(QXcbAtom::AtomTIMESTAMP);
struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = nullptr;
xcb_atom_t multi_type = XCB_NONE;
@@ -708,7 +708,7 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property,
// correct size, not 0-term.
if (size)
*size = buffer_offset;
- if (*type == atom(QXcbAtom::INCR))
+ if (*type == atom(QXcbAtom::AtomINCR))
m_incr_receive_time = connection()->getTimestamp();
if (deleteProperty)
xcb_delete_property(xcb_connection(), win, property);
@@ -747,12 +747,12 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
const QXcbEventNode *flushedTailNode = queue->flushedTail();
if (checkManager) {
- if (connection()->selectionOwner(atom(QXcbAtom::CLIPBOARD_MANAGER)) == XCB_NONE)
+ if (connection()->selectionOwner(atom(QXcbAtom::AtomCLIPBOARD_MANAGER)) == XCB_NONE)
return nullptr;
}
// process other clipboard events, since someone is probably requesting data from us
- auto clipboardAtom = atom(QXcbAtom::CLIPBOARD);
+ auto clipboardAtom = atom(QXcbAtom::AtomCLIPBOARD);
e = queue->peek([clipboardAtom](xcb_generic_event_t *event, int type) {
xcb_atom_t selection = XCB_ATOM_NONE;
if (type == XCB_SELECTION_REQUEST)
@@ -846,7 +846,7 @@ QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb
QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
{
- return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::_QT_SELECTION));
+ return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::Atom_QT_SELECTION));
}
QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time)
@@ -870,7 +870,7 @@ QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target,
xcb_atom_t type;
if (clipboardReadProperty(win, property, true, &buf, nullptr, &type, nullptr)) {
- if (type == atom(QXcbAtom::INCR)) {
+ if (type == atom(QXcbAtom::AtomINCR)) {
int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
buf = clipboardReadIncrementalProperty(win, property, nbytes, false);
}
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index 056075886f..1056c6408f 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
@@ -92,9 +92,9 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
const int focusInDelay = 100;
m_focusInTimer.setSingleShot(true);
m_focusInTimer.setInterval(focusInDelay);
- m_focusInTimer.callOnTimeout([]() {
+ m_focusInTimer.callOnTimeout(this, []() {
// No FocusIn events for us, proceed with FocusOut normally.
- QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
});
sync();
@@ -602,12 +602,12 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
if (clientMessage->format != 32)
return;
#if QT_CONFIG(draganddrop)
- if (clientMessage->type == atom(QXcbAtom::XdndStatus))
+ if (clientMessage->type == atom(QXcbAtom::AtomXdndStatus))
drag()->handleStatus(clientMessage);
- else if (clientMessage->type == atom(QXcbAtom::XdndFinished))
+ else if (clientMessage->type == atom(QXcbAtom::AtomXdndFinished))
drag()->handleFinished(clientMessage);
#endif
- if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::MANAGER))
+ if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::AtomMANAGER))
m_systemTrayTracker->notifyManagerClientMessageEvent(clientMessage);
HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent);
}
@@ -658,7 +658,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
setTime(selectionRequest->time);
#endif
#if QT_CONFIG(draganddrop)
- if (selectionRequest->selection == atom(QXcbAtom::XdndSelection))
+ if (selectionRequest->selection == atom(QXcbAtom::AtomXdndSelection))
m_drag->handleSelectionRequest(selectionRequest);
else
#endif
@@ -686,11 +686,11 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
if (m_clipboard->handlePropertyNotify(event))
break;
#endif
- if (propertyNotify->atom == atom(QXcbAtom::_NET_WORKAREA)) {
+ if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_WORKAREA)) {
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window);
if (virtualDesktop)
virtualDesktop->updateWorkArea();
- } else if (propertyNotify->atom == atom(QXcbAtom::_NET_SUPPORTED)) {
+ } else if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_SUPPORTED)) {
m_wmSupport->updateNetWMAtoms();
} else {
HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent);
@@ -787,13 +787,13 @@ void QXcbConnection::setStartupId(const QByteArray &nextId)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
clientLeader(),
- atom(QXcbAtom::_NET_STARTUP_ID),
- atom(QXcbAtom::UTF8_STRING),
+ atom(QXcbAtom::Atom_NET_STARTUP_ID),
+ atom(QXcbAtom::AtomUTF8_STRING),
8,
nextId.size(),
nextId.constData());
else
- xcb_delete_property(xcb_connection(), clientLeader(), atom(QXcbAtom::_NET_STARTUP_ID));
+ xcb_delete_property(xcb_connection(), clientLeader(), atom(QXcbAtom::Atom_NET_STARTUP_ID));
}
}
@@ -822,8 +822,8 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
{
// send a dummy event to myself to get the timestamp from X server.
xcb_window_t window = rootWindow();
- xcb_atom_t dummyAtom = atom(QXcbAtom::CLIP_TEMPORARY);
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_APPEND, window, dummyAtom,
+ xcb_atom_t dummyAtom = atom(QXcbAtom::Atom_QT_GET_TIMESTAMP);
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, dummyAtom,
XCB_ATOM_INTEGER, 32, 0, nullptr);
connection()->flush();
@@ -855,8 +855,6 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
xcb_timestamp_t timestamp = pn->time;
free(event);
- xcb_delete_property(xcb_connection(), window, dummyAtom);
-
return timestamp;
}
@@ -923,7 +921,7 @@ xcb_window_t QXcbConnection::clientLeader()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_clientLeader,
- atom(QXcbAtom::WM_CLIENT_LEADER),
+ atom(QXcbAtom::AtomWM_CLIENT_LEADER),
XCB_ATOM_WINDOW,
32,
1,
@@ -936,7 +934,7 @@ xcb_window_t QXcbConnection::clientLeader()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_clientLeader,
- atom(QXcbAtom::SM_CLIENT_ID),
+ atom(QXcbAtom::AtomSM_CLIENT_ID),
XCB_ATOM_STRING,
8,
session.size(),
@@ -1057,8 +1055,8 @@ bool QXcbConnection::isUserInputEvent(xcb_generic_event_t *event) const
if (eventType == XCB_CLIENT_MESSAGE) {
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
- if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::WM_PROTOCOLS))
- if (clientMessage->data.data32[0] == atom(QXcbAtom::WM_DELETE_WINDOW))
+ if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::AtomWM_PROTOCOLS))
+ if (clientMessage->data.data32[0] == atom(QXcbAtom::AtomWM_DELETE_WINDOW))
isInputEvent = true;
}
@@ -1143,13 +1141,6 @@ Qt::MouseButtons QXcbConnection::queryMouseButtons() const
return translateMouseButtons(stateMask);
}
-Qt::KeyboardModifiers QXcbConnection::queryKeyboardModifiers() const
-{
- int stateMask = 0;
- QXcbCursor::queryPointer(connection(), nullptr, nullptr, &stateMask);
- return keyboard()->translateModifiers(stateMask);
-}
-
QXcbGlIntegration *QXcbConnection::glIntegration() const
{
if (m_glIntegrationInitialized)
diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h
index 24e684866d..527744fe81 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.h
+++ b/src/plugins/platforms/xcb/qxcbconnection.h
@@ -183,7 +183,6 @@ public:
QXcbSystemTrayTracker *systemTrayTracker() const;
Qt::MouseButtons queryMouseButtons() const;
- Qt::KeyboardModifiers queryKeyboardModifiers() const;
bool isUserInputEvent(xcb_generic_event_t *event) const;
@@ -367,7 +366,7 @@ Q_DECLARE_TYPEINFO(QXcbConnection::TabletData, Q_RELOCATABLE_TYPE);
class QXcbConnectionGrabber
{
public:
- QXcbConnectionGrabber(QXcbConnection *connection);
+ Q_NODISCARD_CTOR QXcbConnectionGrabber(QXcbConnection *connection);
~QXcbConnectionGrabber();
void release();
private:
diff --git a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
index bfa22072ed..c31e9b1039 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
@@ -489,6 +489,10 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber);
m_virtualDesktops.append(virtualDesktop);
}
+
+ if (xcbScreenNumber != primaryScreenNumber())
+ return;
+
QList<QPlatformScreen*> old = virtualDesktop->m_screens;
QList<QPlatformScreen *> siblings;
@@ -521,14 +525,12 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
siblings << screen;
// similar logic with QXcbConnection::initializeScreensFromOutput()
- if (primaryScreenNumber() == xcbScreenNumber) {
- if (!(*primaryScreen) || monitor_info->primary) {
- if (*primaryScreen)
- (*primaryScreen)->setPrimary(false);
- *primaryScreen = screen;
- (*primaryScreen)->setPrimary(true);
- siblings.prepend(siblings.takeLast());
- }
+ if (!(*primaryScreen) || monitor_info->primary) {
+ if (*primaryScreen)
+ (*primaryScreen)->setPrimary(false);
+ *primaryScreen = screen;
+ (*primaryScreen)->setPrimary(true);
+ siblings.prepend(siblings.takeLast());
}
xcb_randr_monitor_info_next(&monitor_iter);
@@ -551,10 +553,8 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
qCDebug(lcQpaScreen) << "create a fake screen: " << screen;
}
- if (primaryScreenNumber() == xcbScreenNumber) {
- *primaryScreen = screen;
- (*primaryScreen)->setPrimary(true);
- }
+ *primaryScreen = screen;
+ (*primaryScreen)->setPrimary(true);
siblings << screen;
m_screens << screen;
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
index 03e758bf85..4f62a1880b 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
@@ -15,7 +15,9 @@
#include <xcb/xinput.h>
+#if QT_CONFIG(gestures)
#define QT_XCB_HAS_TOUCHPAD_GESTURES (XCB_INPUT_MINOR_VERSION >= 4)
+#endif
using namespace Qt::StringLiterals;
@@ -239,6 +241,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
const QByteArray nameRaw = QByteArray(xcb_input_xi_device_info_name(deviceInfo),
xcb_input_xi_device_info_name_length(deviceInfo));
const QString name = QString::fromUtf8(nameRaw);
+ m_xiSlavePointerIds.append(deviceInfo->deviceid);
qCDebug(lcQpaXInputDevices) << "input device " << name << "ID" << deviceInfo->deviceid;
#if QT_CONFIG(tabletevent)
TabletData tabletData;
@@ -270,9 +273,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
tabletData.valuatorInfo[valuatorAtom] = info;
}
#endif // QT_CONFIG(tabletevent)
- if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
+ if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
scrollingDevice()->lastScrollPosition.setX(fixed3232ToReal(vci->value));
- else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
+ else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
scrollingDevice()->lastScrollPosition.setY(fixed3232ToReal(vci->value));
break;
}
@@ -300,14 +303,14 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
xcb_atom_t label5 = labels[4];
// Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
// button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
- if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
- (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
+ if ((!label4 || qatom(label4) == QXcbAtom::AtomButtonWheelUp || qatom(label4) == QXcbAtom::AtomButtonWheelDown) &&
+ (!label5 || qatom(label5) == QXcbAtom::AtomButtonWheelUp || qatom(label5) == QXcbAtom::AtomButtonWheelDown))
scrollingDevice()->legacyOrientations |= Qt::Vertical;
}
if (bci->num_buttons >= 7) {
xcb_atom_t label6 = labels[5];
xcb_atom_t label7 = labels[6];
- if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
+ if ((!label6 || qatom(label6) == QXcbAtom::AtomButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::AtomButtonHorizWheelRight))
scrollingDevice()->legacyOrientations |= Qt::Horizontal;
}
buttonCount = bci->num_buttons;
@@ -331,9 +334,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
bool isTablet = false;
#if QT_CONFIG(tabletevent)
// If we have found the valuators which we expect a tablet to have, it might be a tablet.
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
- tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
- tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsX) &&
+ tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsY) &&
+ tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsPressure))
isTablet = true;
// But we need to be careful not to take the touch and tablet-button devices as tablets.
@@ -356,7 +359,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
// combined device (evdev) rather than separate pen/eraser (wacom driver)
tabletData.pointerType = QPointingDevice::PointerType::Pen;
dbgType = "pen"_L1;
- } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
+ } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::AtomKEYBOARD */) {
// some "Genius" tablets
isTablet = true;
tabletData.pointerType = QPointingDevice::PointerType::Pen;
@@ -384,9 +387,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
m_tabletData.append(tabletData);
qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
QPointingDevice::Capabilities capsOverride = QInputDevice::Capability::None;
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltX))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltX))
capsOverride.setFlag(QInputDevice::Capability::XTilt);
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltY))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltY))
capsOverride.setFlag(QInputDevice::Capability::YTilt);
// TODO can we get USB ID?
Q_ASSERT(deviceInfo->deviceid == tabletData.deviceId);
@@ -442,12 +445,15 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
}
}
+/*!
+ Find all X11 input devices at startup, or react to a device hierarchy event,
+ and create/delete the corresponding QInputDevice instances as necessary.
+ Afterwards, we expect QInputDevice::devices() to contain only the
+ Qt-relevant devices that \c {xinput list} reports. The parent of each master
+ device is this QXcbConnection object; the parent of each slave is its master.
+*/
void QXcbConnection::xi2SetupDevices()
{
-#if QT_CONFIG(tabletevent)
- m_tabletData.clear();
-#endif
- m_touchDevices.clear();
m_xiMasterPointerIds.clear();
auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL);
@@ -456,6 +462,18 @@ void QXcbConnection::xi2SetupDevices()
return;
}
+ // Start with all known devices; remove the ones that still exist.
+ // Afterwards, previousDevices will be the list of those that we should delete.
+ QList<const QInputDevice *> previousDevices = QInputDevice::devices();
+ // Return true if the device with the given systemId is new;
+ // otherwise remove it from previousDevices and return false.
+ auto newOrKeep = [&previousDevices](qint64 systemId) {
+ // if nothing is removed from previousDevices, it's a new device
+ return !previousDevices.removeIf([systemId](const QInputDevice *dev) {
+ return dev->systemId() == systemId;
+ });
+ };
+
// XInput doesn't provide a way to identify "seats"; but each device has an attachment to another device.
// So we make up a seatId: master-keyboard-id << 16 | master-pointer-id.
@@ -464,17 +482,21 @@ void QXcbConnection::xi2SetupDevices()
xcb_input_xi_device_info_t *deviceInfo = it.data;
switch (deviceInfo->type) {
case XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD: {
- auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)),
- deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard,
- QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this);
- QWindowSystemInterface::registerInputDevice(dev);
+ if (newOrKeep(deviceInfo->deviceid)) {
+ auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)),
+ deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard,
+ QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this);
+ QWindowSystemInterface::registerInputDevice(dev);
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER: {
m_xiMasterPointerIds.append(deviceInfo->deviceid);
- auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
- QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover,
- 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this);
- QWindowSystemInterface::registerInputDevice(dev);
+ if (newOrKeep(deviceInfo->deviceid)) {
+ auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
+ QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover,
+ 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this);
+ QWindowSystemInterface::registerInputDevice(dev);
+ }
continue;
} break;
default:
@@ -491,23 +513,36 @@ void QXcbConnection::xi2SetupDevices()
// already registered
break;
case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER: {
- m_xiSlavePointerIds.append(deviceInfo->deviceid);
- QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
- Q_ASSERT(master);
- xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master));
+ if (newOrKeep(deviceInfo->deviceid)) {
+ m_xiSlavePointerIds.append(deviceInfo->deviceid);
+ QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
+ Q_ASSERT(master);
+ xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master));
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_SLAVE_KEYBOARD: {
- QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
- Q_ASSERT(master);
- QWindowSystemInterface::registerInputDevice(new QInputDevice(
- QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
- QInputDevice::DeviceType::Keyboard, master->seatName(), master));
+ if (newOrKeep(deviceInfo->deviceid)) {
+ QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
+ Q_ASSERT(master);
+ QWindowSystemInterface::registerInputDevice(new QInputDevice(
+ QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
+ QInputDevice::DeviceType::Keyboard, master->seatName(), master));
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE:
break;
}
}
+ // previousDevices is now the list of those that are no longer found
+ qCDebug(lcQpaXInputDevices) << "removed" << previousDevices;
+ for (auto it = previousDevices.constBegin(); it != previousDevices.constEnd(); ++it) {
+ const auto id = (*it)->systemId();
+ m_xiSlavePointerIds.removeAll(id);
+ m_touchDevices.remove(id);
+ }
+ qDeleteAll(previousDevices);
+
if (m_xiMasterPointerIds.size() > 1)
qCDebug(lcQpaXInputDevices) << "multi-pointer X detected";
}
@@ -573,27 +608,27 @@ QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info
// Some devices (mice) report a resolution of 0; they will be excluded later,
// for now just prevent a division by zero
const int vciResolution = vci->resolution ? vci->resolution : 1;
- if (valuatorAtom == QXcbAtom::AbsMTPositionX)
+ if (valuatorAtom == QXcbAtom::AtomAbsMTPositionX)
caps |= QInputDevice::Capability::Position | QInputDevice::Capability::NormalizedPosition;
- else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTTouchMajor)
caps |= QInputDevice::Capability::Area;
- else if (valuatorAtom == QXcbAtom::AbsMTOrientation)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTOrientation)
dev.providesTouchOrientation = true;
- else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTPressure || valuatorAtom == QXcbAtom::AtomAbsPressure)
caps |= QInputDevice::Capability::Pressure;
- else if (valuatorAtom == QXcbAtom::RelX) {
+ else if (valuatorAtom == QXcbAtom::AtomRelX) {
hasRelativeCoords = true;
dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::RelY) {
+ } else if (valuatorAtom == QXcbAtom::AtomRelY) {
hasRelativeCoords = true;
dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::AbsX) {
+ } else if (valuatorAtom == QXcbAtom::AtomAbsX) {
caps |= QInputDevice::Capability::Position;
dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::AbsY) {
+ } else if (valuatorAtom == QXcbAtom::AtomAbsY) {
caps |= QInputDevice::Capability::Position;
dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::RelVertWheel || valuatorAtom == QXcbAtom::RelHorizWheel) {
+ } else if (valuatorAtom == QXcbAtom::AtomRelVertWheel || valuatorAtom == QXcbAtom::AtomRelHorizWheel) {
caps |= QInputDevice::Capability::Scroll;
}
break;
@@ -730,6 +765,8 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
if (auto device = QPointingDevicePrivate::pointingDeviceById(sourceDeviceId))
xi2HandleScrollEvent(event, device);
+ else
+ qCDebug(lcQpaXInputEvents) << "scroll event from unregistered device" << sourceDeviceId;
if (xiDeviceEvent) {
switch (xiDeviceEvent->event_type) {
@@ -776,7 +813,16 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
{
auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid);
- Q_ASSERT(dev);
+ if (!dev) {
+ qCDebug(lcQpaXInputEvents) << "didn't find the dev for given sourceid - " << xiDeviceEvent->sourceid
+ << ", try to repopulate xi2 devices";
+ xi2SetupDevices();
+ dev = touchDeviceForId(xiDeviceEvent->sourceid);
+ if (!dev) {
+ qCDebug(lcQpaXInputEvents) << "still can't find the dev for it, give up.";
+ return;
+ }
+ }
const bool firstTouch = dev->touchPoints.isEmpty();
if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
QWindowSystemInterface::TouchPoint tp;
@@ -804,27 +850,27 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
if (value < vci.min)
value = vci.min;
qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
- if (vci.label == QXcbAtom::RelX) {
+ if (vci.label == QXcbAtom::AtomRelX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::RelY) {
+ } else if (vci.label == QXcbAtom::AtomRelY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsX) {
+ } else if (vci.label == QXcbAtom::AtomAbsX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsY) {
+ } else if (vci.label == QXcbAtom::AtomAbsY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTPositionX) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPositionX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTPositionY) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPositionY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTTouchMajor) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTTouchMajor) {
const qreal sw = screen->geometry().width();
const qreal sh = screen->geometry().height();
w = valuatorNormalized * qHypot(sw, sh);
- } else if (vci.label == QXcbAtom::AbsMTTouchMinor) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTTouchMinor) {
const qreal sw = screen->geometry().width();
const qreal sh = screen->geometry().height();
h = valuatorNormalized * qHypot(sw, sh);
- } else if (vci.label == QXcbAtom::AbsMTOrientation) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTOrientation) {
// Find the closest axis.
// 0 corresponds to the Y axis, vci.max to the X axis.
// Flipping over the Y axis and rotating by 180 degrees
@@ -835,7 +881,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
value -= 2 * vci.max;
value = qAbs(value);
majorAxisIsY = value < vci.max - value;
- } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPressure || vci.label == QXcbAtom::AtomAbsPressure) {
touchPoint.pressure = valuatorNormalized;
}
@@ -967,7 +1013,7 @@ void QXcbConnection::abortSystemMoveResize(xcb_window_t window)
qCDebug(lcQpaXInputDevices) << "sending client message NET_WM_MOVERESIZE_CANCEL to window: " << window;
m_startSystemMoveResizeInfo.window = XCB_NONE;
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
xcb_client_message_event_t xev;
xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize;
@@ -1065,11 +1111,15 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
void QXcbConnection::xi2HandleHierarchyEvent(void *event)
{
auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
- // We only care about hotplugged devices
- if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)))
- return;
-
- xi2SetupDevices();
+ // We care about hotplugged devices (slaves) and master devices.
+ // We don't report anything for DEVICE_ENABLED or DEVICE_DISABLED
+ // (but often that goes with adding or removal anyway).
+ // We don't react to SLAVE_ATTACHED or SLAVE_DETACHED either.
+ if (xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED |
+ XCB_INPUT_HIERARCHY_MASK_MASTER_REMOVED |
+ XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED |
+ XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))
+ xi2SetupDevices();
}
#if QT_XCB_HAS_TOUCHPAD_GESTURES
@@ -1234,6 +1284,9 @@ void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
switch (xiEvent->reason) {
case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
+ // Don't call xi2SetupSlavePointerDevice() again for an already-known device, and never for a master.
+ if (m_xiMasterPointerIds.contains(xiEvent->deviceid) || m_xiSlavePointerIds.contains(xiEvent->deviceid))
+ return;
auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
if (!reply || reply->num_infos <= 0)
return;
@@ -1275,9 +1328,9 @@ void QXcbConnection::xi2UpdateScrollingDevice(QInputDevice *dev)
if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
const int valuatorAtom = qatom(vci->label);
- if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
+ if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
scrollingDevice->lastScrollPosition.setX(fixed3232ToReal(vci->value));
- else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
+ else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
scrollingDevice->lastScrollPosition.setY(fixed3232ToReal(vci->value));
}
}
@@ -1464,7 +1517,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
// The evdev driver doesn't do it this way.
const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
- if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
+ if (ev->property == atom(QXcbAtom::AtomWacomSerialIDs)) {
enum WacomSerialIndex {
_WACSER_USB_ID = 0,
_WACSER_LAST_TOOL_SERIAL,
@@ -1477,7 +1530,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
if (reply) {
- if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
+ if (reply->type == atom(QXcbAtom::AtomINTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get()));
quint32 tool = ptr[_WACSER_TOOL_ID];
// Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
@@ -1485,6 +1538,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
if (!tool && ptr[_WACSER_TOOL_SERIAL])
tool = ptr[_WACSER_TOOL_SERIAL];
+ QWindow *win = nullptr; // TODO QTBUG-111400 get the position somehow, then the window
// The property change event informs us which tool is in proximity or which one left proximity.
if (tool) {
const QPointingDevice *dev = tabletToolInstance(nullptr, tabletData->name,
@@ -1493,22 +1547,19 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
tabletData->inProximity = true;
tabletData->tool = dev->type();
tabletData->serialId = qint64(ptr[_WACSER_TOOL_SERIAL]);
- QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
- int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, true); // enter
} else {
tool = ptr[_WACSER_LAST_TOOL_ID];
// Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
// e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
if (!tool)
tool = ptr[_WACSER_LAST_TOOL_SERIAL];
- const QInputDevice *dev = QInputDevicePrivate::fromId(tabletData->deviceId);
+ auto *dev = qobject_cast<const QPointingDevice *>(QInputDevicePrivate::fromId(tabletData->deviceId));
Q_ASSERT(dev);
tabletData->tool = dev->type();
tabletData->inProximity = false;
tabletData->serialId = qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
- // TODO why doesn't it just take QPointingDevice*
- QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
- int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, false); // leave
}
// TODO maybe have a hash of tabletData->deviceId to device data so we can
// look up the tablet name here, and distinguish multiple tablets
@@ -1570,30 +1621,30 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD
xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
switch (valuator) {
- case QXcbAtom::AbsX:
+ case QXcbAtom::AtomAbsX:
if (Q_LIKELY(useValuators)) {
const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
global.setX(value);
local.setX(xcbWindow->mapFromGlobalF(global).x());
}
break;
- case QXcbAtom::AbsY:
+ case QXcbAtom::AtomAbsY:
if (Q_LIKELY(useValuators)) {
qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
global.setY(value);
local.setY(xcbWindow->mapFromGlobalF(global).y());
}
break;
- case QXcbAtom::AbsPressure:
+ case QXcbAtom::AtomAbsPressure:
pressure = normalizedValue;
break;
- case QXcbAtom::AbsTiltX:
+ case QXcbAtom::AtomAbsTiltX:
xTilt = classInfo.curVal;
break;
- case QXcbAtom::AbsTiltY:
+ case QXcbAtom::AtomAbsTiltY:
yTilt = classInfo.curVal;
break;
- case QXcbAtom::AbsWheel:
+ case QXcbAtom::AtomAbsWheel:
switch (tabletData->tool) {
case QInputDevice::DeviceType::Airbrush:
tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp
index 8f087edc9b..dc9ed46956 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.cpp
+++ b/src/plugins/platforms/xcb/qxcbcursor.cpp
@@ -10,6 +10,7 @@
#include <QtGui/QWindow>
#include <QtGui/QBitmap>
#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformtheme.h>
#if QT_CONFIG(xcb_xlib)
#include <X11/cursorfont.h>
@@ -288,6 +289,13 @@ QXcbCursor::~QXcbCursor()
xcb_cursor_context_free(m_cursorContext);
}
+QSize QXcbCursor::size() const
+{
+ if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
+ return theme->themeHint(QPlatformTheme::MouseCursorSize).toSize();
+ return QSize(24, 24);
+}
+
void QXcbCursor::updateContext()
{
if (m_cursorContext)
diff --git a/src/plugins/platforms/xcb/qxcbcursor.h b/src/plugins/platforms/xcb/qxcbcursor.h
index 14958f824b..bf26861e8f 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.h
+++ b/src/plugins/platforms/xcb/qxcbcursor.h
@@ -48,6 +48,8 @@ public:
QPoint pos() const override;
void setPos(const QPoint &pos) override;
+ QSize size() const override;
+
void updateContext();
static void queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDesktop, QPoint *pos, int *keybMask = nullptr);
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index 3dc52c9f06..6e5dd770b9 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -5,6 +5,7 @@
#include <xcb/xcb.h>
#include "qxcbconnection.h"
#include "qxcbclipboard.h"
+#include "qxcbkeyboard.h"
#include "qxcbmime.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
@@ -27,6 +28,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
const int xdnd_version = 5;
static inline xcb_window_t xcb_window(QPlatformWindow *w)
@@ -44,7 +47,7 @@ static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
xcb_window_t proxy = XCB_NONE;
auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
- false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
+ false, w, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
if (reply && reply->type == XCB_ATOM_WINDOW)
proxy = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -54,7 +57,7 @@ static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
// exists and is real?
reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
- false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
+ false, proxy, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
if (reply && reply->type == XCB_ATOM_WINDOW) {
xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -140,7 +143,7 @@ void QXcbDrag::startDrag()
qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->qtSelectionOwner();
xcb_set_selection_owner(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndSelection), connection()->time());
+ atom(QXcbAtom::AtomXdndSelection), connection()->time());
QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData());
for (int i = 0; i < fmts.size(); ++i) {
@@ -153,7 +156,7 @@ void QXcbDrag::startDrag()
if (drag_types.size() > 3)
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndTypelist),
+ atom(QXcbAtom::AtomXdndTypelist),
XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
setUseCompositing(current_virtual_desktop->compositingActive());
@@ -187,7 +190,7 @@ Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::Keyb
void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
{
- if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList))
+ if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::AtomXdndActionList))
return;
readActionList();
@@ -233,7 +236,7 @@ xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md
bool windowContainsMouse = !ignoreNonXdndAwareWindows;
{
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- false, w, connection()->atom(QXcbAtom::XdndAware),
+ false, w, connection()->atom(QXcbAtom::AtomXdndAware),
XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool isAware = reply && reply->type != XCB_NONE;
if (isAware) {
@@ -306,7 +309,7 @@ bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target
xcb_window_t child = translate->child;
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target,
- atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
+ atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool aware = reply && reply->type != XCB_NONE;
if (aware) {
qCDebug(lcQpaXDnd) << "found XdndAware on" << target;
@@ -379,7 +382,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
if (proxy_target) {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
false, proxy_target,
- atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
+ atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
if (!reply || reply->type == XCB_NONE) {
target = 0;
} else {
@@ -404,7 +407,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
enter.sequence = 0;
enter.window = target;
enter.format = 32;
- enter.type = atom(QXcbAtom::XdndEnter);
+ enter.type = atom(QXcbAtom::AtomXdndEnter);
enter.data.data32[0] = connection()->qtSelectionOwner();
enter.data.data32[1] = flags;
enter.data.data32[2] = drag_types.size() > 0 ? drag_types.at(0) : 0;
@@ -435,7 +438,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
move.sequence = 0;
move.window = target;
move.format = 32;
- move.type = atom(QXcbAtom::XdndPosition);
+ move.type = atom(QXcbAtom::AtomXdndPosition);
move.data.data32[0] = connection()->qtSelectionOwner();
move.data.data32[1] = 0; // flags
move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
@@ -459,7 +462,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity";
if (isUnity && xdndCollectionWindow == XCB_NONE) {
QString name = QXcbWindow::windowTitle(connection(), target);
- if (name == QStringLiteral("XdndCollectionWindowImp"))
+ if (name == "XdndCollectionWindowImp"_L1)
xdndCollectionWindow = target;
}
if (target == xdndCollectionWindow) {
@@ -481,7 +484,7 @@ void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
drop.sequence = 0;
drop.window = current_target;
drop.format = 32;
- drop.type = atom(QXcbAtom::XdndDrop);
+ drop.type = atom(QXcbAtom::AtomXdndDrop);
drop.data.data32[0] = connection()->qtSelectionOwner();
drop.data.data32[1] = 0; // flags
drop.data.data32[2] = connection()->time();
@@ -521,11 +524,11 @@ void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
{
- if (a == atom(QXcbAtom::XdndActionCopy) || a == 0)
+ if (a == atom(QXcbAtom::AtomXdndActionCopy) || a == 0)
return Qt::CopyAction;
- if (a == atom(QXcbAtom::XdndActionLink))
+ if (a == atom(QXcbAtom::AtomXdndActionLink))
return Qt::LinkAction;
- if (a == atom(QXcbAtom::XdndActionMove))
+ if (a == atom(QXcbAtom::AtomXdndActionMove))
return Qt::MoveAction;
return Qt::CopyAction;
}
@@ -534,7 +537,7 @@ Qt::DropActions QXcbDrag::toDropActions(const QList<xcb_atom_t> &atoms) const
{
Qt::DropActions actions;
for (const auto actionAtom : atoms) {
- if (actionAtom != atom(QXcbAtom::XdndActionAsk))
+ if (actionAtom != atom(QXcbAtom::AtomXdndActionAsk))
actions |= toDropAction(actionAtom);
}
return actions;
@@ -544,16 +547,16 @@ xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
{
switch (a) {
case Qt::CopyAction:
- return atom(QXcbAtom::XdndActionCopy);
+ return atom(QXcbAtom::AtomXdndActionCopy);
case Qt::LinkAction:
- return atom(QXcbAtom::XdndActionLink);
+ return atom(QXcbAtom::AtomXdndActionLink);
case Qt::MoveAction:
case Qt::TargetMoveAction:
- return atom(QXcbAtom::XdndActionMove);
+ return atom(QXcbAtom::AtomXdndActionMove);
case Qt::IgnoreAction:
return XCB_NONE;
default:
- return atom(QXcbAtom::XdndActionCopy);
+ return atom(QXcbAtom::AtomXdndActionCopy);
}
}
@@ -561,7 +564,7 @@ void QXcbDrag::readActionList()
{
drop_actions.clear();
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
- atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM,
+ atom(QXcbAtom::AtomXdndActionList), XCB_ATOM_ATOM,
0, 1024);
if (reply && reply->type != XCB_NONE && reply->format == 32) {
int length = xcb_get_property_value_length(reply.get()) / 4;
@@ -590,7 +593,7 @@ void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions sup
if (current_actions != actions) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndActionList),
+ atom(QXcbAtom::AtomXdndActionList),
XCB_ATOM_ATOM, 32, actions.size(), actions.constData());
current_actions = actions;
}
@@ -701,7 +704,7 @@ void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *
if (event->data.data32[1] & 1) {
// get the types from XdndTypeList
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
- atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM,
+ atom(QXcbAtom::AtomXdndTypelist), XCB_ATOM_ATOM,
0, xdnd_max_type);
if (reply && reply->type != XCB_NONE && reply->format == 32) {
int length = xcb_get_property_value_length(reply.get()) / 4;
@@ -761,12 +764,12 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
} else {
dropData = m_dropData;
supported_actions = toDropActions(drop_actions);
- if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk))
+ if (e->data.data32[4] != atom(QXcbAtom::AtomXdndActionAsk))
supported_actions |= Qt::DropActions(toDropAction(e->data.data32[4]));
}
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
- auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
+ auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
w->window(), dropData, p, supported_actions, buttons, modifiers);
@@ -783,7 +786,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
response.sequence = 0;
response.window = xdnd_dragsource;
response.format = 32;
- response.type = atom(QXcbAtom::XdndStatus);
+ response.type = atom(QXcbAtom::AtomXdndStatus);
response.data.data32[0] = xcb_window(w);
response.data.data32[1] = qt_response.isAccepted(); // flags
response.data.data32[2] = 0; // x, y
@@ -835,7 +838,7 @@ namespace
void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event)
{
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
- ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
+ ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndPosition));
while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
if (lastEvent != event)
free(lastEvent);
@@ -883,7 +886,7 @@ void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
xcb_generic_event_t *nextEvent;
- ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
+ ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndStatus));
while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
if (lastEvent != event)
free(lastEvent);
@@ -934,7 +937,7 @@ void QXcbDrag::send_leave()
leave.sequence = 0;
leave.window = current_target;
leave.format = 32;
- leave.type = atom(QXcbAtom::XdndLeave);
+ leave.type = atom(QXcbAtom::AtomXdndLeave);
leave.data.data32[0] = connection()->qtSelectionOwner();
leave.data.data32[1] = 0; // flags
leave.data.data32[2] = 0; // x, y
@@ -1006,7 +1009,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
return;
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
- auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
+ auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
currentWindow.data(), dropData, currentPosition, supported_drop_actions,
@@ -1024,7 +1027,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
finished.sequence = 0;
finished.window = xdnd_dragsource;
finished.format = 32;
- finished.type = atom(QXcbAtom::XdndFinished);
+ finished.type = atom(QXcbAtom::AtomXdndFinished);
finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
finished.data.data32[1] = response.isAccepted(); // flags
finished.data.data32[2] = toXdndAction(acceptedAaction);
@@ -1097,7 +1100,7 @@ void QXcbDrag::timerEvent(QTimerEvent* e)
continue;
}
QTime currentTime = QTime::currentTime();
- int delta = t.time.msecsTo(currentTime);
+ std::chrono::milliseconds delta{t.time.msecsTo(currentTime)};
if (delta > XdndDropTransactionTimeout) {
/* delete transactions which are older than XdndDropTransactionTimeout. It could mean
one of these:
@@ -1141,7 +1144,7 @@ static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window)
forever {
// check if window has XdndAware
auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window,
- c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
+ c->atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool aware = gpReply && gpReply->type != XCB_NONE;
if (aware) {
target = window;
@@ -1231,7 +1234,7 @@ void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event
bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
{
- qCDebug(lcQpaXDnd) << "dndEnable" << w << on;
+ qCDebug(lcQpaXDnd) << "dndEnable" << static_cast<QPlatformWindow *>(w) << on;
// Windows announce that they support the XDND protocol by creating a window property XdndAware.
if (on) {
QXcbWindow *window = nullptr;
@@ -1248,7 +1251,7 @@ bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
desktop_proxy = new QWindow;
window = static_cast<QXcbWindow *>(desktop_proxy->handle());
proxy_id = window->xcb_window();
- xcb_atom_t xdnd_proxy = atom(QXcbAtom::XdndProxy);
+ xcb_atom_t xdnd_proxy = atom(QXcbAtom::AtomXdndProxy);
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy,
XCB_ATOM_WINDOW, 32, 1, &proxy_id);
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy,
@@ -1262,14 +1265,14 @@ bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window();
xcb_atom_t atm = xdnd_version;
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window->xcb_window(),
- atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
+ atom(QXcbAtom::AtomXdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
return true;
} else {
return false;
}
} else {
if (w->window()->type() == Qt::Desktop) {
- xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy));
+ xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::AtomXdndProxy));
delete desktop_proxy;
desktop_proxy = nullptr;
} else {
@@ -1321,10 +1324,10 @@ QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType reques
return result;
#ifndef QT_NO_CLIPBOARD
- if (c->selectionOwner(c->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
+ if (c->selectionOwner(c->atom(QXcbAtom::AtomXdndSelection)) == XCB_NONE)
return result; // should never happen?
- xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
+ xcb_atom_t xdnd_selection = c->atom(QXcbAtom::AtomXdndSelection);
result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
#endif
diff --git a/src/plugins/platforms/xcb/qxcbdrag.h b/src/plugins/platforms/xcb/qxcbdrag.h
index a58b7e850e..ae7cc915c8 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.h
+++ b/src/plugins/platforms/xcb/qxcbdrag.h
@@ -14,7 +14,6 @@
#include <qpoint.h>
#include <qpointer.h>
#include <qrect.h>
-#include <qsharedpointer.h>
#include <qxcbobject.h>
#include <QtCore/QDebug>
@@ -128,7 +127,7 @@ private:
QXcbVirtualDesktop *current_virtual_desktop;
// 10 minute timer used to discard old XdndDrop transactions
- enum { XdndDropTransactionTimeout = 600000 };
+ static constexpr std::chrono::minutes XdndDropTransactionTimeout{10};
int cleanup_timer;
QList<xcb_atom_t> drag_types;
diff --git a/src/plugins/platforms/xcb/qxcbeventqueue.cpp b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
index 5c87cba80d..33795d63cf 100644
--- a/src/plugins/platforms/xcb/qxcbeventqueue.cpp
+++ b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
@@ -345,7 +345,7 @@ void QXcbEventQueue::sendCloseConnectionEvent() const
event.format = 32;
event.sequence = 0;
event.window = window;
- event.type = m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION);
+ event.type = m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION);
event.data.data32[0] = 0;
xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
@@ -357,7 +357,7 @@ bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
{
if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
- if (clientMessage->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
+ if (clientMessage->type == m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION))
m_closeConnectionDetected = true;
}
return m_closeConnectionDetected;
diff --git a/src/plugins/platforms/xcb/qxcbimage.h b/src/plugins/platforms/xcb/qxcbimage.h
index e550102881..c022fae639 100644
--- a/src/plugins/platforms/xcb/qxcbimage.h
+++ b/src/plugins/platforms/xcb/qxcbimage.h
@@ -5,7 +5,6 @@
#define QXCBIMAGE_H
#include "qxcbscreen.h"
-#include <QtCore/QPair>
#include <QtGui/QImage>
#include <QtGui/QPixmap>
#include <xcb/xcb_image.h>
diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp
index e32891e814..4dafae31e3 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.cpp
+++ b/src/plugins/platforms/xcb/qxcbintegration.cpp
@@ -93,10 +93,17 @@ static bool runningUnderDebugger()
#endif
}
+class QXcbUnixServices : public QGenericUnixServices
+{
+public:
+ QString portalWindowIdentifier(QWindow *window) override;
+};
+
+
QXcbIntegration *QXcbIntegration::m_instance = nullptr;
QXcbIntegration::QXcbIntegration(const QStringList &parameters, int &argc, char **argv)
- : m_services(new QGenericUnixServices)
+ : m_services(new QXcbUnixServices)
, m_instanceName(nullptr)
, m_canGrab(true)
, m_defaultVisualId(UINT_MAX)
@@ -197,7 +204,7 @@ QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelTyp
QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const
{
QXcbGlIntegration *glIntegration = nullptr;
- const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);;
+ const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
if (window->type() != Qt::Desktop && !isTrayIconWindow) {
if (window->supportsOpenGL()) {
glIntegration = connection()->glIntegration();
@@ -337,11 +344,12 @@ void QXcbIntegration::initialize()
const auto defaultInputContext = "compose"_L1;
// Perform everything that may potentially need the event dispatcher (timers, socket
// notifiers) here instead of the constructor.
- QString icStr = QPlatformInputContextFactory::requested();
- if (icStr.isNull())
- icStr = defaultInputContext;
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
- if (!m_inputContext && icStr != defaultInputContext && icStr != "none"_L1)
+ auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty())
+ icStrs = { defaultInputContext };
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
+ if (!m_inputContext && !icStrs.contains(defaultInputContext)
+ && icStrs != QStringList{"none"_L1})
m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
connection()->keyboard()->initialize();
@@ -422,14 +430,9 @@ QPlatformServices *QXcbIntegration::services() const
return m_services.data();
}
-Qt::KeyboardModifiers QXcbIntegration::queryKeyboardModifiers() const
-{
- return m_connection->queryKeyboardModifiers();
-}
-
-QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
+QPlatformKeyMapper *QXcbIntegration::keyMapper() const
{
- return m_connection->keyboard()->possibleKeys(e);
+ return m_connection->keyboard();
}
QStringList QXcbIntegration::themeNames() const
@@ -472,21 +475,23 @@ QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
case QPlatformIntegration::StartDragVelocity:
case QPlatformIntegration::UseRtlExtensions:
case QPlatformIntegration::PasswordMaskCharacter:
+ case QPlatformIntegration::FlickMaximumVelocity:
+ case QPlatformIntegration::FlickDeceleration:
// TODO using various xcb, gnome or KDE settings
break; // Not implemented, use defaults
+ case QPlatformIntegration::FlickStartDistance:
case QPlatformIntegration::StartDragDistance: {
RETURN_VALID_XSETTINGS(xsNetDndDragThreshold);
-
// The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
// on a high-resolution screen it makes sense to increase it.
- qreal dpi = 100.0;
+ qreal dpi = 100;
if (const QXcbScreen *screen = connection()->primaryScreen()) {
if (screen->logicalDpi().first > dpi)
dpi = screen->logicalDpi().first;
if (screen->logicalDpi().second > dpi)
dpi = screen->logicalDpi().second;
}
- return 10.0 * dpi / 100.0;
+ return (hint == QPlatformIntegration::FlickStartDistance ? qreal(15) : qreal(10)) * dpi / qreal(100);
}
case QPlatformIntegration::ShowIsFullScreen:
// X11 always has support for windows, but the
@@ -585,4 +590,15 @@ QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanIn
}
#endif
+void QXcbIntegration::setApplicationBadge(qint64 number)
+{
+ auto unixServices = dynamic_cast<QGenericUnixServices *>(services());
+ unixServices->setApplicationBadge(number);
+}
+
+QString QXcbUnixServices::portalWindowIdentifier(QWindow *window)
+{
+ return "x11:"_L1 + QString::number(window->winId(), 16);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h
index abdfe7112c..a1e0c3f3e1 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.h
+++ b/src/plugins/platforms/xcb/qxcbintegration.h
@@ -74,8 +74,7 @@ public:
QPlatformServices *services() const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *e) const override;
+ QPlatformKeyMapper *keyMapper() const override;
QStringList themeNames() const override;
QPlatformTheme *createPlatformTheme(const QString &name) const override;
@@ -102,6 +101,8 @@ public:
static QXcbIntegration *instance() { return m_instance; }
+ void setApplicationBadge(qint64 number) override;
+
private:
QXcbConnection *m_connection = nullptr;
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
index 0666fac735..17da54bc7a 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
@@ -3,6 +3,7 @@
#include "qxcbkeyboard.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
+#include "qxcbcursor.h"
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qplatforminputcontext.h>
@@ -377,9 +378,18 @@ void QXcbKeyboard::updateKeymap()
QXkbCommon::verifyHasLatinLayout(m_xkbKeymap.get());
}
-QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
+QList<QKeyCombination> QXcbKeyboard::possibleKeyCombinations(const QKeyEvent *event) const
{
- return QXkbCommon::possibleKeys(m_xkbState.get(), event, m_superAsMeta, m_hyperAsMeta);
+ return QXkbCommon::possibleKeyCombinations(
+ m_xkbState.get(), event, m_superAsMeta, m_hyperAsMeta);
+}
+
+Qt::KeyboardModifiers QXcbKeyboard::queryKeyboardModifiers() const
+{
+ // FIXME: Should we base this on m_xkbState?
+ int stateMask = 0;
+ QXcbCursor::queryPointer(connection(), nullptr, nullptr, &stateMask);
+ return translateModifiers(stateMask);
}
void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state)
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h
index 15b08fbead..62d9268c64 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.h
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.h
@@ -14,11 +14,13 @@
#include <QtGui/private/qxkbcommon_p.h>
#include <xkbcommon/xkbcommon-x11.h>
+#include <qpa/qplatformkeymapper.h>
+
#include <QEvent>
QT_BEGIN_NAMESPACE
-class QXcbKeyboard : public QXcbObject
+class QXcbKeyboard : public QXcbObject, public QPlatformKeyMapper
{
public:
QXcbKeyboard(QXcbConnection *connection);
@@ -34,7 +36,9 @@ public:
Qt::KeyboardModifiers translateModifiers(int s) const;
void updateKeymap(xcb_mapping_notify_event_t *event);
void updateKeymap();
- QList<int> possibleKeys(const QKeyEvent *event) const;
+
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const override;
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
void updateXKBMods();
xkb_mod_mask_t xkbModMask(quint16 state);
diff --git a/src/plugins/platforms/xcb/qxcbmime.cpp b/src/plugins/platforms/xcb/qxcbmime.cpp
index 9df9dd2bd9..860d195d13 100644
--- a/src/plugins/platforms/xcb/qxcbmime.cpp
+++ b/src/plugins/platforms/xcb/qxcbmime.cpp
@@ -27,8 +27,8 @@ QString QXcbMime::mimeAtomToString(QXcbConnection *connection, xcb_atom_t a)
// special cases for string type
if (a == XCB_ATOM_STRING
- || a == connection->atom(QXcbAtom::UTF8_STRING)
- || a == connection->atom(QXcbAtom::TEXT))
+ || a == connection->atom(QXcbAtom::AtomUTF8_STRING)
+ || a == connection->atom(QXcbAtom::AtomTEXT))
return "text/plain"_L1;
// special case for images
@@ -54,15 +54,15 @@ bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeDa
*atomFormat = a;
*dataFormat = 8;
- if ((a == connection->atom(QXcbAtom::UTF8_STRING)
+ if ((a == connection->atom(QXcbAtom::AtomUTF8_STRING)
|| a == XCB_ATOM_STRING
- || a == connection->atom(QXcbAtom::TEXT))
+ || a == connection->atom(QXcbAtom::AtomTEXT))
&& QInternalMimeData::hasFormatHelper("text/plain"_L1, mimeData)) {
- if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
+ if (a == connection->atom(QXcbAtom::AtomUTF8_STRING)) {
*data = QInternalMimeData::renderDataHelper("text/plain"_L1, mimeData);
ret = true;
} else if (a == XCB_ATOM_STRING ||
- a == connection->atom(QXcbAtom::TEXT)) {
+ a == connection->atom(QXcbAtom::AtomTEXT)) {
// ICCCM says STRING is latin1
*data = QString::fromUtf8(QInternalMimeData::renderDataHelper(
"text/plain"_L1, mimeData)).toLatin1();
@@ -79,8 +79,7 @@ bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeDa
if (atomName == "text/uri-list"_L1
&& connection->atomName(a) == "text/x-moz-url") {
const QString mozUri = QLatin1StringView(data->split('\n').constFirst()) + u'\n';
- *data = QByteArray(reinterpret_cast<const char *>(mozUri.data()),
- mozUri.size() * 2);
+ data->assign({reinterpret_cast<const char *>(mozUri.data()), mozUri.size() * 2});
} else if (atomName == "application/x-color"_L1)
*dataFormat = 16;
ret = true;
@@ -102,9 +101,9 @@ QList<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, const
// special cases for strings
if (format == "text/plain"_L1) {
- atoms.append(connection->atom(QXcbAtom::UTF8_STRING));
+ atoms.append(connection->atom(QXcbAtom::AtomUTF8_STRING));
atoms.append(XCB_ATOM_STRING);
- atoms.append(connection->atom(QXcbAtom::TEXT));
+ atoms.append(connection->atom(QXcbAtom::AtomTEXT));
}
// special cases for uris
@@ -139,11 +138,11 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
if (format == "text/plain"_L1) {
if (data.endsWith('\0'))
data.chop(1);
- if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
+ if (a == connection->atom(QXcbAtom::AtomUTF8_STRING)) {
return QString::fromUtf8(data);
}
if (a == XCB_ATOM_STRING ||
- a == connection->atom(QXcbAtom::TEXT))
+ a == connection->atom(QXcbAtom::AtomTEXT))
return QString::fromLatin1(data);
}
// If data contains UTF16 text, convert it to a string.
@@ -156,7 +155,7 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
const quint8 byte1 = data.at(1);
if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff)
|| (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) {
- const QString str = QString::fromUtf16(
+ const QStringView str(
reinterpret_cast<const char16_t *>(data.constData()), data.size() / 2);
if (!str.isNull()) {
if (format == "text/uri-list"_L1) {
@@ -175,7 +174,7 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
return list.constFirst();
return list;
} else {
- return str;
+ return str.toString();
}
}
}
@@ -227,12 +226,12 @@ xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString
// find matches for string types
if (format == "text/plain"_L1) {
- if (atoms.contains(connection->atom(QXcbAtom::UTF8_STRING)))
- return connection->atom(QXcbAtom::UTF8_STRING);
+ if (atoms.contains(connection->atom(QXcbAtom::AtomUTF8_STRING)))
+ return connection->atom(QXcbAtom::AtomUTF8_STRING);
if (atoms.contains(XCB_ATOM_STRING))
return XCB_ATOM_STRING;
- if (atoms.contains(connection->atom(QXcbAtom::TEXT)))
- return connection->atom(QXcbAtom::TEXT);
+ if (atoms.contains(connection->atom(QXcbAtom::AtomTEXT)))
+ return connection->atom(QXcbAtom::AtomTEXT);
}
// find matches for uri types
diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
index cd6ff40df9..06f5241d8c 100644
--- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
+++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
@@ -350,7 +350,7 @@ void *QXcbNativeInterface::atspiBus()
QXcbIntegration *integration = static_cast<QXcbIntegration *>(QGuiApplicationPrivate::platformIntegration());
QXcbConnection *connection = integration->connection();
if (connection) {
- auto atspiBusAtom = connection->atom(QXcbAtom::AT_SPI_BUS);
+ auto atspiBusAtom = connection->atom(QXcbAtom::AtomAT_SPI_BUS);
auto reply = Q_XCB_REPLY(xcb_get_property, connection->xcb_connection(),
false, connection->rootWindow(),
atspiBusAtom, XCB_ATOM_STRING, 0, 128);
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index 1192af1469..06f4b66edb 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -50,7 +50,7 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
false, screen->root,
- atom(QXcbAtom::_NET_SUPPORTING_WM_CHECK),
+ atom(QXcbAtom::Atom_NET_SUPPORTING_WM_CHECK),
XCB_ATOM_WINDOW, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_WINDOW) {
xcb_window_t windowManager = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -249,7 +249,7 @@ QRect QXcbVirtualDesktop::getWorkArea() const
{
QRect r;
auto workArea = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), false, screen()->root,
- atom(QXcbAtom::_NET_WORKAREA),
+ atom(QXcbAtom::Atom_NET_WORKAREA),
XCB_ATOM_CARDINAL, 0, 1024);
if (workArea && workArea->type == XCB_ATOM_CARDINAL && workArea->format == 32 && workArea->value_len >= 4) {
// If workArea->value_len > 4, the remaining ones seem to be for WM's virtual desktops
@@ -529,7 +529,7 @@ void QXcbScreen::updateColorSpaceAndEdid()
// Read colord ICC data (from GNOME settings)
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
false, screen()->root,
- connection()->atom(QXcbAtom::_ICC_PROFILE),
+ connection()->atom(QXcbAtom::Atom_ICC_PROFILE),
XCB_ATOM_CARDINAL, 0, 8192);
if (reply->format == 8 && reply->type == XCB_ATOM_CARDINAL) {
QByteArray data(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), reply->value_len);
@@ -817,7 +817,7 @@ void QXcbScreen::sendStartupMessage(const QByteArray &message) const
xcb_client_message_event_t ev;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.format = 8;
- ev.type = connection()->atom(QXcbAtom::_NET_STARTUP_INFO_BEGIN);
+ ev.type = connection()->atom(QXcbAtom::Atom_NET_STARTUP_INFO_BEGIN);
ev.sequence = 0;
ev.window = rootWindow;
int sent = 0;
@@ -825,7 +825,7 @@ void QXcbScreen::sendStartupMessage(const QByteArray &message) const
const char *data = message.constData();
do {
if (sent == 20)
- ev.type = connection()->atom(QXcbAtom::_NET_STARTUP_INFO);
+ ev.type = connection()->atom(QXcbAtom::Atom_NET_STARTUP_INFO);
const int start = sent;
const int numBytes = qMin(length - start, 20);
@@ -1095,11 +1095,11 @@ QByteArray QXcbScreen::getEdid() const
return result;
// Try a bunch of atoms
- result = getOutputProperty(atom(QXcbAtom::EDID));
+ result = getOutputProperty(atom(QXcbAtom::AtomEDID));
if (result.isEmpty())
- result = getOutputProperty(atom(QXcbAtom::EDID_DATA));
+ result = getOutputProperty(atom(QXcbAtom::AtomEDID_DATA));
if (result.isEmpty())
- result = getOutputProperty(atom(QXcbAtom::XFree86_DDC_EDID1_RAWDATA));
+ result = getOutputProperty(atom(QXcbAtom::AtomXFree86_DDC_EDID1_RAWDATA));
return result;
}
diff --git a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
index e184c07128..84d2d73f91 100644
--- a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
+++ b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
@@ -26,7 +26,7 @@ enum {
QXcbSystemTrayTracker *QXcbSystemTrayTracker::create(QXcbConnection *connection)
{
// Selection, tray atoms for GNOME, NET WM Specification
- const xcb_atom_t trayAtom = connection->atom(QXcbAtom::_NET_SYSTEM_TRAY_OPCODE);
+ const xcb_atom_t trayAtom = connection->atom(QXcbAtom::Atom_NET_SYSTEM_TRAY_OPCODE);
if (!trayAtom)
return nullptr;
const QByteArray netSysTray = QByteArrayLiteral("_NET_SYSTEM_TRAY_S") + QByteArray::number(connection->primaryScreenNumber());
@@ -113,7 +113,7 @@ xcb_visualid_t QXcbSystemTrayTracker::netSystemTrayVisual()
if (m_trayWindow == XCB_WINDOW_NONE)
return XCB_NONE;
- xcb_atom_t tray_atom = m_connection->atom(QXcbAtom::_NET_SYSTEM_TRAY_VISUAL);
+ xcb_atom_t tray_atom = m_connection->atom(QXcbAtom::Atom_NET_SYSTEM_TRAY_VISUAL);
// Get the xcb property for the _NET_SYSTEM_TRAY_VISUAL atom
auto systray_atom_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, m_connection->xcb_connection(),
diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp
index a7b3ce8005..d3e4fa9548 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.cpp
+++ b/src/plugins/platforms/xcb/qxcbwindow.cpp
@@ -6,6 +6,7 @@
#include <QtDebug>
#include <QMetaEnum>
#include <QScreen>
+#include <QtCore/QFileInfo>
#include <QtGui/QIcon>
#include <QtGui/QRegion>
#include <QtGui/private/qhighdpiscaling_p.h>
@@ -59,6 +60,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
+Q_LOGGING_CATEGORY(lcQpaXcbWindow, "qt.qpa.xcb.window");
Q_DECLARE_TYPEINFO(xcb_rectangle_t, Q_PRIMITIVE_TYPE);
@@ -94,7 +96,7 @@ const quint32 XEMBED_VERSION = 0;
QXcbScreen *QXcbWindow::parentScreen()
{
- return parent() ? static_cast<QXcbWindow*>(parent())->parentScreen() : xcbScreen();
+ return QPlatformWindow::parent() ? static_cast<QXcbWindow*>(QPlatformWindow::parent())->parentScreen() : xcbScreen();
}
QXcbScreen *QXcbWindow::initialScreen() const
@@ -133,6 +135,7 @@ void QXcbWindow::setImageFormatForVisual(const xcb_visualtype_t *visual)
case 16:
qWarning("Using RGB16 fallback, if this works your X11 server is reporting a bad screen format.");
m_imageFormat = QImage::Format_RGB16;
+ break;
default:
break;
}
@@ -223,6 +226,7 @@ enum : quint32 {
void QXcbWindow::create()
{
+ xcb_window_t old_m_window = m_window;
destroy();
m_windowState = Qt::WindowNoState;
@@ -231,8 +235,8 @@ void QXcbWindow::create()
Qt::WindowType type = window()->type();
QXcbScreen *currentScreen = xcbScreen();
- QXcbScreen *platformScreen = parent() ? parentScreen() : initialScreen();
- QRect rect = parent()
+ QXcbScreen *platformScreen = QPlatformWindow::parent() ? parentScreen() : initialScreen();
+ QRect rect = QPlatformWindow::parent()
? QHighDpi::toNativeLocalPosition(window()->geometry(), platformScreen)
: QHighDpi::toNativePixels(window()->geometry(), platformScreen);
@@ -272,11 +276,11 @@ void QXcbWindow::create()
QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen());
xcb_window_t xcb_parent_id = platformScreen->root();
- if (parent()) {
- xcb_parent_id = static_cast<QXcbWindow *>(parent())->xcb_window();
- m_embedded = parent()->isForeignWindow();
+ if (QPlatformWindow::parent()) {
+ xcb_parent_id = static_cast<QXcbWindow *>(QPlatformWindow::parent())->xcb_window();
+ m_embedded = QPlatformWindow::parent()->isForeignWindow();
- QSurfaceFormat parentFormat = parent()->window()->requestedFormat();
+ QSurfaceFormat parentFormat = QPlatformWindow::parent()->window()->requestedFormat();
if (window()->surfaceType() != QSurface::OpenGLSurface && parentFormat.hasAlpha()) {
window()->setFormat(parentFormat);
}
@@ -294,16 +298,16 @@ void QXcbWindow::create()
qWarning() << "Failed to use requested visual id.";
}
- if (parent()) {
+ if (QPlatformWindow::parent()) {
// When using a Vulkan QWindow via QWidget::createWindowContainer() we
// must make sure the visuals are compatible. Now, the parent will be
// of RasterGLSurface which typically chooses a GLX/EGL compatible
// visual which may not be what the Vulkan window would choose.
// Therefore, take the parent's visual.
if (window()->surfaceType() == QSurface::VulkanSurface
- && parent()->window()->surfaceType() != QSurface::VulkanSurface)
+ && QPlatformWindow::parent()->window()->surfaceType() != QSurface::VulkanSurface)
{
- visual = platformScreen->visualForId(static_cast<QXcbWindow *>(parent())->visualId());
+ visual = platformScreen->visualForId(static_cast<QXcbWindow *>(QPlatformWindow::parent())->visualId());
}
}
@@ -360,20 +364,20 @@ void QXcbWindow::create()
xcb_atom_t properties[5];
int propertyCount = 0;
- properties[propertyCount++] = atom(QXcbAtom::WM_DELETE_WINDOW);
- properties[propertyCount++] = atom(QXcbAtom::WM_TAKE_FOCUS);
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_PING);
+ properties[propertyCount++] = atom(QXcbAtom::AtomWM_DELETE_WINDOW);
+ properties[propertyCount++] = atom(QXcbAtom::AtomWM_TAKE_FOCUS);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_PING);
if (connection()->hasXSync())
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_SYNC_REQUEST);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST);
if (window()->flags() & Qt::WindowContextHelpButtonHint)
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_CONTEXT_HELP);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_CONTEXT_HELP);
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::WM_PROTOCOLS),
+ atom(QXcbAtom::AtomWM_PROTOCOLS),
XCB_ATOM_ATOM,
32,
propertyCount,
@@ -384,10 +388,35 @@ void QXcbWindow::create()
const QByteArray wmClass = QXcbIntegration::instance()->wmClass();
if (!wmClass.isEmpty()) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
- m_window, atom(QXcbAtom::WM_CLASS),
+ m_window, atom(QXcbAtom::AtomWM_CLASS),
XCB_ATOM_STRING, 8, wmClass.size(), wmClass.constData());
}
+ QString desktopFileName = QGuiApplication::desktopFileName();
+ if (QGuiApplication::desktopFileName().isEmpty()) {
+ QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath());
+ QStringList domainName =
+ QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
+ Qt::SkipEmptyParts);
+
+ if (domainName.isEmpty()) {
+ desktopFileName = fi.baseName();
+ } else {
+ for (int i = 0; i < domainName.size(); ++i)
+ desktopFileName.prepend(QLatin1Char('.')).prepend(domainName.at(i));
+ desktopFileName.append(fi.baseName());
+ }
+ }
+ if (!desktopFileName.isEmpty()) {
+ const QByteArray dfName = desktopFileName.toUtf8();
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
+ m_window, atom(QXcbAtom::Atom_KDE_NET_WM_DESKTOP_FILE),
+ atom(QXcbAtom::AtomUTF8_STRING), 8, dfName.size(), dfName.constData());
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
+ m_window, atom(QXcbAtom::Atom_GTK_APPLICATION_ID),
+ atom(QXcbAtom::AtomUTF8_STRING), 8, dfName.size(), dfName.constData());
+ }
+
if (connection()->hasXSync()) {
m_syncCounter = xcb_generate_id(xcb_connection());
xcb_sync_create_counter(xcb_connection(), m_syncCounter, m_syncValue);
@@ -395,7 +424,7 @@ void QXcbWindow::create()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_SYNC_REQUEST_COUNTER),
+ atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST_COUNTER),
XCB_ATOM_CARDINAL,
32,
1,
@@ -405,13 +434,13 @@ void QXcbWindow::create()
// set the PID to let the WM kill the application if unresponsive
quint32 pid = getpid();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_PID), XCB_ATOM_CARDINAL, 32,
+ atom(QXcbAtom::Atom_NET_WM_PID), XCB_ATOM_CARDINAL, 32,
1, &pid);
const QByteArray clientMachine = QSysInfo::machineHostName().toLocal8Bit();
if (!clientMachine.isEmpty()) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_CLIENT_MACHINE), XCB_ATOM_STRING, 8,
+ atom(QXcbAtom::AtomWM_CLIENT_MACHINE), XCB_ATOM_STRING, 8,
clientMachine.size(), clientMachine.constData());
}
@@ -425,14 +454,14 @@ void QXcbWindow::create()
xcb_window_t leader = connection()->clientLeader();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32,
+ atom(QXcbAtom::AtomWM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32,
1, &leader);
/* Add XEMBED info; this operation doesn't initiate the embedding. */
quint32 data[] = { XEMBED_VERSION, XEMBED_MAPPED };
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_XEMBED_INFO),
- atom(QXcbAtom::_XEMBED_INFO),
+ atom(QXcbAtom::Atom_XEMBED_INFO),
+ atom(QXcbAtom::Atom_XEMBED_INFO),
32, 2, (void *)data);
if (connection()->hasXInput2())
@@ -465,6 +494,17 @@ void QXcbWindow::create()
if (m_trayIconWindow)
m_embedded = requestSystemTrayWindowDock();
+
+ if (m_window != old_m_window) {
+ if (!m_wmTransientForChildren.isEmpty()) {
+ QList<QPointer<QXcbWindow>> transientChildren = m_wmTransientForChildren;
+ m_wmTransientForChildren.clear();
+ for (auto transientChild : transientChildren) {
+ if (transientChild)
+ transientChild->updateWmTransientFor();
+ }
+ }
+ }
}
QXcbWindow::~QXcbWindow()
@@ -472,6 +512,22 @@ QXcbWindow::~QXcbWindow()
destroy();
}
+QXcbForeignWindow::QXcbForeignWindow(QWindow *window, WId nativeHandle)
+ : QXcbWindow(window)
+{
+ m_window = nativeHandle;
+
+ // Reflect the foreign window's geometry as our own
+ if (auto geometry = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), m_window)) {
+ QRect nativeGeometry(geometry->x, geometry->y, geometry->width, geometry->height);
+ QPlatformWindow::setGeometry(nativeGeometry);
+ }
+
+ // And reparent, if we have a parent already
+ if (QPlatformWindow::parent())
+ setParent(QPlatformWindow::parent());
+}
+
QXcbForeignWindow::~QXcbForeignWindow()
{
// Clear window so that destroy() does not affect it
@@ -489,12 +545,14 @@ void QXcbWindow::destroy()
doFocusOut();
if (connection()->mouseGrabber() == this)
connection()->setMouseGrabber(nullptr);
+ if (connection()->mousePressWindow() == this)
+ connection()->setMousePressWindow(nullptr);
if (m_syncCounter && connection()->hasXSync())
xcb_sync_destroy_counter(xcb_connection(), m_syncCounter);
if (m_window) {
if (m_netWmUserTimeWindow) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
// Some window managers, like metacity, do XSelectInput on the _NET_WM_USER_TIME_WINDOW window,
// without trapping BadWindow (which crashes when the user time window is destroyed).
connection()->sync();
@@ -522,7 +580,7 @@ void QXcbWindow::setGeometry(const QRect &rect)
propagateSizeHints();
QXcbScreen *currentScreen = xcbScreen();
- QXcbScreen *newScreen = parent() ? parentScreen() : static_cast<QXcbScreen*>(screenForGeometry(rect));
+ QXcbScreen *newScreen = QPlatformWindow::parent() ? parentScreen() : static_cast<QXcbScreen*>(screenForGeometry(rect));
if (!newScreen)
newScreen = xcbScreen();
@@ -563,9 +621,9 @@ void QXcbWindow::setGeometry(const QRect &rect)
QMargins QXcbWindow::frameMargins() const
{
if (m_dirtyFrameMargins) {
- if (connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_FRAME_EXTENTS))) {
+ if (connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_FRAME_EXTENTS))) {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, m_window,
- atom(QXcbAtom::_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4);
+ atom(QXcbAtom::Atom_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4);
if (reply && reply->type == XCB_ATOM_CARDINAL && reply->format == 32 && reply->value_len == 4) {
quint32 *data = (quint32 *)xcb_get_property_value(reply.get());
// _NET_FRAME_EXTENTS format is left, right, top, bottom
@@ -641,6 +699,44 @@ void QXcbWindow::setVisible(bool visible)
hide();
}
+void QXcbWindow::updateWmTransientFor()
+{
+ xcb_window_t transientXcbParent = XCB_NONE;
+ if (isTransient(window())) {
+ QWindow *tp = window()->transientParent();
+ if (tp && tp->handle()) {
+ QXcbWindow *handle = static_cast<QXcbWindow *>(tp->handle());
+ transientXcbParent = tp->handle()->winId();
+ if (transientXcbParent) {
+ handle->registerWmTransientForChild(this);
+ qCDebug(lcQpaXcbWindow) << Q_FUNC_INFO << static_cast<QPlatformWindow *>(handle)
+ << " registerWmTransientForChild " << static_cast<QPlatformWindow *>(this);
+ }
+ }
+ // Default to client leader if there is no transient parent, else modal dialogs can
+ // be hidden by their parents.
+ if (!transientXcbParent)
+ transientXcbParent = connection()->clientLeader();
+ if (transientXcbParent) { // ICCCM 4.1.2.6
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
+ XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
+ 1, &transientXcbParent);
+ qCDebug(lcQpaXcbWindow, "0x%x added XCB_ATOM_WM_TRANSIENT_FOR 0x%x", m_window, transientXcbParent);
+ }
+ }
+ if (!transientXcbParent)
+ xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR);
+}
+
+void QXcbWindow::registerWmTransientForChild(QXcbWindow *child)
+{
+ if (!child)
+ return;
+
+ if (!m_wmTransientForChildren.contains(child))
+ m_wmTransientForChildren.append(child);
+}
+
void QXcbWindow::show()
{
if (window()->isTopLevel()) {
@@ -654,23 +750,7 @@ void QXcbWindow::show()
propagateSizeHints();
// update WM_TRANSIENT_FOR
- xcb_window_t transientXcbParent = 0;
- if (isTransient(window())) {
- const QWindow *tp = window()->transientParent();
- if (tp && tp->handle())
- transientXcbParent = tp->handle()->winId();
- // Default to client leader if there is no transient parent, else modal dialogs can
- // be hidden by their parents.
- if (!transientXcbParent)
- transientXcbParent = connection()->clientLeader();
- if (transientXcbParent) { // ICCCM 4.1.2.6
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
- 1, &transientXcbParent);
- }
- }
- if (!transientXcbParent)
- xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR);
+ updateWmTransientFor();
// update _NET_WM_STATE
setNetWmStateOnUnmappedWindow();
@@ -781,7 +861,7 @@ void QXcbWindow::doFocusIn()
return;
QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(window()))->eventReceiver();
connection()->setFocusWindow(w);
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
}
void QXcbWindow::doFocusOut()
@@ -824,29 +904,29 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates()
NetWmStates result;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_STATE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) {
const xcb_atom_t *states = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
const xcb_atom_t *statesEnd = states + reply->length;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_ABOVE)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE)))
result |= NetWmStateAbove;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_BELOW)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_BELOW)))
result |= NetWmStateBelow;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)))
result |= NetWmStateFullScreen;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ)))
result |= NetWmStateMaximizedHorz;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)))
result |= NetWmStateMaximizedVert;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MODAL)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MODAL)))
result |= NetWmStateModal;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)))
result |= NetWmStateStaysOnTop;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)))
result |= NetWmStateDemandsAttention;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_HIDDEN)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN)))
result |= NetWmStateHidden;
} else {
qCDebug(lcQpaXcb, "getting net wm state (%x), empty\n", m_window);
@@ -961,13 +1041,13 @@ void QXcbWindow::setMotifWmHints(Qt::WindowFlags flags)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_MOTIF_WM_HINTS),
- atom(QXcbAtom::_MOTIF_WM_HINTS),
+ atom(QXcbAtom::Atom_MOTIF_WM_HINTS),
+ atom(QXcbAtom::Atom_MOTIF_WM_HINTS),
32,
5,
&mwmhints);
} else {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_MOTIF_WM_HINTS));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_MOTIF_WM_HINTS));
}
}
@@ -979,7 +1059,7 @@ void QXcbWindow::setNetWmState(bool set, xcb_atom_t one, xcb_atom_t two)
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::_NET_WM_STATE);
+ event.type = atom(QXcbAtom::Atom_NET_WM_STATE);
event.data.data32[0] = set ? 1 : 0;
event.data.data32[1] = one;
event.data.data32[2] = two;
@@ -995,26 +1075,26 @@ void QXcbWindow::setNetWmState(Qt::WindowStates state)
{
if ((m_windowState ^ state) & Qt::WindowMaximized) {
setNetWmState(state & Qt::WindowMaximized,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ),
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
}
if ((m_windowState ^ state) & Qt::WindowFullScreen)
- setNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ setNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
}
void QXcbWindow::setNetWmState(Qt::WindowFlags flags)
{
setNetWmState(flags & Qt::WindowStaysOnTopHint,
- atom(QXcbAtom::_NET_WM_STATE_ABOVE),
- atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP));
- setNetWmState(flags & Qt::WindowStaysOnBottomHint, atom(QXcbAtom::_NET_WM_STATE_BELOW));
+ atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE),
+ atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP));
+ setNetWmState(flags & Qt::WindowStaysOnBottomHint, atom(QXcbAtom::Atom_NET_WM_STATE_BELOW));
}
void QXcbWindow::setNetWmStateOnUnmappedWindow()
{
if (Q_UNLIKELY(m_mapped))
- qCWarning(lcQpaXcb()) << "internal error: " << Q_FUNC_INFO << "called on mapped window";
+ qCDebug(lcQpaXcb()) << "internal info: " << Q_FUNC_INFO << "called on mapped window";
NetWmStates states;
const Qt::WindowFlags flags = window()->flags();
@@ -1049,7 +1129,7 @@ void QXcbWindow::setNetWmStateOnUnmappedWindow()
QList<xcb_atom_t> atoms;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_STATE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM && reply->value_len > 0) {
const xcb_atom_t *data = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
@@ -1057,30 +1137,30 @@ void QXcbWindow::setNetWmStateOnUnmappedWindow()
memcpy((void *)&atoms.first(), (void *)data, reply->value_len * sizeof(xcb_atom_t));
}
- if (states & NetWmStateAbove && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_ABOVE)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_ABOVE));
- if (states & NetWmStateBelow && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_BELOW)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_BELOW));
- if (states & NetWmStateHidden && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_HIDDEN)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_HIDDEN));
- if (states & NetWmStateFullScreen && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
- if (states & NetWmStateMaximizedHorz && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ));
- if (states & NetWmStateMaximizedVert && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (states & NetWmStateModal && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MODAL)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MODAL));
- if (states & NetWmStateStaysOnTop && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP));
- if (states & NetWmStateDemandsAttention && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION));
+ if (states & NetWmStateAbove && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE));
+ if (states & NetWmStateBelow && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_BELOW)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_BELOW));
+ if (states & NetWmStateHidden && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN));
+ if (states & NetWmStateFullScreen && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
+ if (states & NetWmStateMaximizedHorz && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ));
+ if (states & NetWmStateMaximizedVert && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
+ if (states & NetWmStateModal && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MODAL)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MODAL));
+ if (states & NetWmStateStaysOnTop && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP));
+ if (states & NetWmStateDemandsAttention && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION));
if (atoms.isEmpty()) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_STATE));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_STATE));
} else {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_STATE), XCB_ATOM_ATOM, 32,
+ atom(QXcbAtom::Atom_NET_WM_STATE), XCB_ATOM_ATOM, 32,
atoms.size(), atoms.constData());
}
xcb_flush(xcb_connection());
@@ -1091,18 +1171,21 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
if (state == m_windowState)
return;
+ Qt::WindowStates unsetState = m_windowState & ~state;
+ Qt::WindowStates newState = state & ~m_windowState;
+
// unset old state
- if (m_windowState & Qt::WindowMinimized)
+ if (unsetState & Qt::WindowMinimized)
xcb_map_window(xcb_connection(), m_window);
- if (m_windowState & Qt::WindowMaximized)
+ if (unsetState & Qt::WindowMaximized)
setNetWmState(false,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (m_windowState & Qt::WindowFullScreen)
- setNetWmState(false, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ),
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
+ if (unsetState & Qt::WindowFullScreen)
+ setNetWmState(false, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
// set new state
- if (state & Qt::WindowMinimized) {
+ if (newState & Qt::WindowMinimized) {
{
xcb_client_message_event_t event;
@@ -1110,7 +1193,7 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::WM_CHANGE_STATE);
+ event.type = atom(QXcbAtom::AtomWM_CHANGE_STATE);
event.data.data32[0] = XCB_ICCCM_WM_STATE_ICONIC;
event.data.data32[1] = 0;
event.data.data32[2] = 0;
@@ -1123,13 +1206,8 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
}
m_minimized = true;
}
- if (state & Qt::WindowMaximized)
- setNetWmState(true,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (state & Qt::WindowFullScreen)
- setNetWmState(true, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ // set Maximized && FullScreen state if need
setNetWmState(state);
xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(xcb_connection(), m_window);
@@ -1155,7 +1233,7 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
if (timestamp != 0)
connection()->setNetWmUserTime(timestamp);
- const bool isSupportedByWM = connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ const bool isSupportedByWM = connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
if (m_netWmUserTimeWindow || isSupportedByWM) {
if (!m_netWmUserTimeWindow) {
m_netWmUserTimeWindow = xcb_generate_id(xcb_connection());
@@ -1170,9 +1248,9 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
0, // value mask
nullptr); // value list
wid = m_netWmUserTimeWindow;
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW),
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW),
XCB_ATOM_WINDOW, 32, 1, &m_netWmUserTimeWindow);
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME));
QXcbWindow::setWindowTitle(connection(), m_netWmUserTimeWindow,
QStringLiteral("Qt NET_WM User Time Window"));
@@ -1180,14 +1258,14 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
} else if (!isSupportedByWM) {
// WM no longer supports it, then we should remove the
// _NET_WM_USER_TIME_WINDOW atom.
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
xcb_destroy_window(xcb_connection(), m_netWmUserTimeWindow);
m_netWmUserTimeWindow = XCB_NONE;
} else {
wid = m_netWmUserTimeWindow;
}
}
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, wid, atom(QXcbAtom::_NET_WM_USER_TIME),
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, wid, atom(QXcbAtom::Atom_NET_WM_USER_TIME),
XCB_ATOM_CARDINAL, 32, 1, &timestamp);
}
@@ -1262,8 +1340,8 @@ void QXcbWindow::setWindowIconText(const QString &title)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_ICON_NAME),
- atom(QXcbAtom::UTF8_STRING),
+ atom(QXcbAtom::Atom_NET_WM_ICON_NAME),
+ atom(QXcbAtom::AtomUTF8_STRING),
8,
ba.size(),
ba.constData());
@@ -1306,15 +1384,15 @@ void QXcbWindow::setWindowIcon(const QIcon &icon)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_ICON),
- atom(QXcbAtom::CARDINAL),
+ atom(QXcbAtom::Atom_NET_WM_ICON),
+ atom(QXcbAtom::AtomCARDINAL),
32,
icon_data.size(),
(unsigned char *) icon_data.data());
} else {
xcb_delete_property(xcb_connection(),
m_window,
- atom(QXcbAtom::_NET_WM_ICON));
+ atom(QXcbAtom::Atom_NET_WM_ICON));
}
}
@@ -1371,7 +1449,8 @@ void QXcbWindow::propagateSizeHints()
qMin(XCOORD_MAX, maximumSize.height()));
if (sizeIncrement.width() > 0 || sizeIncrement.height() > 0) {
- xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height());
+ if (!baseSize.isNull() && baseSize.isValid())
+ xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height());
xcb_icccm_size_hints_set_resize_inc(&hints, sizeIncrement.width(), sizeIncrement.height());
}
@@ -1389,29 +1468,37 @@ void QXcbWindow::requestActivateWindow()
return;
}
- if (!m_mapped) {
- m_deferredActivation = true;
- return;
+ {
+ QMutexLocker locker(&m_mappedMutex);
+ if (!m_mapped) {
+ m_deferredActivation = true;
+ return;
+ }
+ m_deferredActivation = false;
}
- m_deferredActivation = false;
updateNetWmUserTime(connection()->time());
QWindow *focusWindow = QGuiApplication::focusWindow();
+ xcb_window_t current = XCB_NONE;
+ if (focusWindow) {
+ if (QPlatformWindow *pw = focusWindow->handle())
+ current = pw->winId();
+ }
if (window()->isTopLevel()
&& !(window()->flags() & Qt::X11BypassWindowManagerHint)
&& (!focusWindow || !window()->isAncestorOf(focusWindow))
- && connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_ACTIVE_WINDOW))) {
+ && connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW))) {
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::_NET_ACTIVE_WINDOW);
+ event.type = atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW);
event.data.data32[0] = 1;
event.data.data32[1] = connection()->time();
- event.data.data32[2] = focusWindow ? focusWindow->winId() : XCB_NONE;
+ event.data.data32[2] = current;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
@@ -1435,7 +1522,7 @@ QXcbWindow::WindowTypes QXcbWindow::wmWindowTypes() const
WindowTypes result;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) {
const xcb_atom_t *types = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
@@ -1443,49 +1530,49 @@ QXcbWindow::WindowTypes QXcbWindow::wmWindowTypes() const
for (; types != types_end; types++) {
QXcbAtom::Atom type = connection()->qatom(*types);
switch (type) {
- case QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL:
result |= WindowType::Normal;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DESKTOP:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP:
result |= WindowType::Desktop;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DOCK:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK:
result |= WindowType::Dock;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLBAR:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR:
result |= WindowType::Toolbar;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU:
result |= WindowType::Menu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY:
result |= WindowType::Utility;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH:
result |= WindowType::Splash;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG:
result |= WindowType::Dialog;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DROPDOWN_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU:
result |= WindowType::DropDownMenu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_POPUP_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU:
result |= WindowType::PopupMenu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP:
result |= WindowType::Tooltip;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_NOTIFICATION:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION:
result |= WindowType::Notification;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_COMBO:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO:
result |= WindowType::Combo;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DND:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND:
result |= WindowType::Dnd;
break;
- case QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE:
+ case QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE:
result |= WindowType::KdeOverride;
break;
default:
@@ -1502,41 +1589,41 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
// manual selection 1 (these are never set by Qt and take precedence)
if (types & WindowType::Normal)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL));
if (types & WindowType::Desktop)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DESKTOP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP));
if (types & WindowType::Dock)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DOCK));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK));
if (types & WindowType::Notification)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NOTIFICATION));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION));
// manual selection 2 (Qt uses these during auto selection);
if (types & WindowType::Utility)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY));
if (types & WindowType::Splash)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH));
if (types & WindowType::Dialog)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG));
if (types & WindowType::Tooltip)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP));
if (types & WindowType::KdeOverride)
- atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
+ atoms.append(atom(QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
// manual selection 3 (these can be set by Qt, but don't have a
// corresponding Qt::WindowType). note that order of the *MENU
// atoms is important
if (types & WindowType::Menu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU));
if (types & WindowType::DropDownMenu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DROPDOWN_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU));
if (types & WindowType::PopupMenu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_POPUP_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU));
if (types & WindowType::Toolbar)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLBAR));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR));
if (types & WindowType::Combo)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_COMBO));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO));
if (types & WindowType::Dnd)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DND));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND));
// automatic selection
Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
@@ -1544,20 +1631,20 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
case Qt::Dialog:
case Qt::Sheet:
if (!(types & WindowType::Dialog))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG));
break;
case Qt::Tool:
case Qt::Drawer:
if (!(types & WindowType::Utility))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY));
break;
case Qt::ToolTip:
if (!(types & WindowType::Tooltip))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP));
break;
case Qt::SplashScreen:
if (!(types & WindowType::Splash))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH));
break;
default:
break;
@@ -1565,19 +1652,19 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
if ((flags & Qt::FramelessWindowHint) && !(types & WindowType::KdeOverride)) {
// override netwm type - quick and easy for KDE noborder
- atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
+ atoms.append(atom(QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
}
- if (atoms.size() == 1 && atoms.first() == atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL))
+ if (atoms.size() == 1 && atoms.first() == atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL))
atoms.clear();
else
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL));
if (atoms.isEmpty()) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE));
} else {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32,
+ atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32,
atoms.size(), atoms.constData());
}
xcb_flush(xcb_connection());
@@ -1587,7 +1674,7 @@ void QXcbWindow::setWindowRole(const QString &role)
{
QByteArray roleData = role.toLatin1();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_WINDOW_ROLE), XCB_ATOM_STRING, 8,
+ atom(QXcbAtom::AtomWM_WINDOW_ROLE), XCB_ATOM_STRING, 8,
roleData.size(), roleData.constData());
}
@@ -1646,15 +1733,15 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
if (event->format != 32)
return;
- if (event->type == atom(QXcbAtom::WM_PROTOCOLS)) {
+ if (event->type == atom(QXcbAtom::AtomWM_PROTOCOLS)) {
xcb_atom_t protocolAtom = event->data.data32[0];
- if (protocolAtom == atom(QXcbAtom::WM_DELETE_WINDOW)) {
+ if (protocolAtom == atom(QXcbAtom::AtomWM_DELETE_WINDOW)) {
QWindowSystemInterface::handleCloseEvent(window());
- } else if (protocolAtom == atom(QXcbAtom::WM_TAKE_FOCUS)) {
+ } else if (protocolAtom == atom(QXcbAtom::AtomWM_TAKE_FOCUS)) {
connection()->setTime(event->data.data32[1]);
relayFocusToModalWindow();
return;
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_PING)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_PING)) {
if (event->window == xcbScreen()->root())
return;
@@ -1667,14 +1754,14 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char *)&reply);
xcb_flush(xcb_connection());
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_SYNC_REQUEST)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST)) {
connection()->setTime(event->data.data32[1]);
m_syncValue.lo = event->data.data32[2];
m_syncValue.hi = event->data.data32[3];
if (connection()->hasXSync())
m_syncState = SyncReceived;
#ifndef QT_NO_WHATSTHIS
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_CONTEXT_HELP)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_CONTEXT_HELP)) {
QWindowSystemInterface::handleEnterWhatsThisEvent();
#endif
} else {
@@ -1682,29 +1769,29 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
connection()->atomName(protocolAtom).constData());
}
#if QT_CONFIG(draganddrop)
- } else if (event->type == atom(QXcbAtom::XdndEnter)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndEnter)) {
connection()->drag()->handleEnter(this, event);
- } else if (event->type == atom(QXcbAtom::XdndPosition)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndPosition)) {
connection()->drag()->handlePosition(this, event);
- } else if (event->type == atom(QXcbAtom::XdndLeave)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndLeave)) {
connection()->drag()->handleLeave(this, event);
- } else if (event->type == atom(QXcbAtom::XdndDrop)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndDrop)) {
connection()->drag()->handleDrop(this, event);
#endif
- } else if (event->type == atom(QXcbAtom::_XEMBED)) {
+ } else if (event->type == atom(QXcbAtom::Atom_XEMBED)) {
handleXEmbedMessage(event);
- } else if (event->type == atom(QXcbAtom::_NET_ACTIVE_WINDOW)) {
+ } else if (event->type == atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW)) {
doFocusIn();
- } else if (event->type == atom(QXcbAtom::MANAGER)
- || event->type == atom(QXcbAtom::_NET_WM_STATE)
- || event->type == atom(QXcbAtom::WM_CHANGE_STATE)) {
+ } else if (event->type == atom(QXcbAtom::AtomMANAGER)
+ || event->type == atom(QXcbAtom::Atom_NET_WM_STATE)
+ || event->type == atom(QXcbAtom::AtomWM_CHANGE_STATE)) {
// Ignore _NET_WM_STATE, MANAGER which are relate to tray icons
// and other messages.
- } else if (event->type == atom(QXcbAtom::_COMPIZ_DECOR_PENDING)
- || event->type == atom(QXcbAtom::_COMPIZ_DECOR_REQUEST)
- || event->type == atom(QXcbAtom::_COMPIZ_DECOR_DELETE_PIXMAP)
- || event->type == atom(QXcbAtom::_COMPIZ_TOOLKIT_ACTION)
- || event->type == atom(QXcbAtom::_GTK_LOAD_ICONTHEMES)) {
+ } else if (event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_PENDING)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_REQUEST)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_DELETE_PIXMAP)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_TOOLKIT_ACTION)
+ || event->type == atom(QXcbAtom::Atom_GTK_LOAD_ICONTHEMES)) {
//silence the _COMPIZ and _GTK messages for now
} else {
qCWarning(lcQpaXcb) << "Unhandled client message: " << connection()->atomName(event->type);
@@ -1715,7 +1802,7 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t *
{
bool fromSendEvent = (event->response_type & 0x80);
QPoint pos(event->x, event->y);
- if (!parent() && !fromSendEvent) {
+ if (!QPlatformWindow::parent() && !fromSendEvent) {
// Do not trust the position, query it instead.
auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
xcb_window(), xcbScreen()->root(), 0, 0);
@@ -1726,7 +1813,7 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t *
}
const QRect actualGeometry = QRect(pos, QSize(event->width, event->height));
- QPlatformScreen *newScreen = parent() ? parent()->screen() : screenForGeometry(actualGeometry);
+ QPlatformScreen *newScreen = QPlatformWindow::parent() ? QPlatformWindow::parent()->screen() : screenForGeometry(actualGeometry);
if (!newScreen)
return;
@@ -1802,8 +1889,11 @@ QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const
void QXcbWindow::handleMapNotifyEvent(const xcb_map_notify_event_t *event)
{
if (event->window == m_window) {
+ m_mappedMutex.lock();
m_mapped = true;
- if (m_deferredActivation)
+ const bool deferredActivation = m_deferredActivation;
+ m_mappedMutex.unlock();
+ if (deferredActivation)
requestActivateWindow();
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size()));
@@ -1813,7 +1903,9 @@ void QXcbWindow::handleMapNotifyEvent(const xcb_map_notify_event_t *event)
void QXcbWindow::handleUnmapNotifyEvent(const xcb_unmap_notify_event_t *event)
{
if (event->window == m_window) {
+ m_mappedMutex.lock();
m_mapped = false;
+ m_mappedMutex.unlock();
QWindowSystemInterface::handleExposeEvent(window(), QRegion());
}
}
@@ -1836,7 +1928,7 @@ void QXcbWindow::handleButtonPressEvent(int event_x, int event_y, int root_x, in
if (m_embedded && !m_trayIconWindow) {
if (window() != QGuiApplication::focusWindow()) {
- const QXcbWindow *container = static_cast<const QXcbWindow *>(parent());
+ const QXcbWindow *container = static_cast<const QXcbWindow *>(QPlatformWindow::parent());
Q_ASSERT(container != nullptr);
sendXEmbedMessage(container->xcb_window(), XEMBED_REQUEST_FOCUS);
@@ -1880,8 +1972,10 @@ void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x,
return;
}
- if (connection()->buttonState() == Qt::NoButton)
+ if (connection()->buttonState() == Qt::NoButton) {
connection()->setMousePressWindow(nullptr);
+ m_ignorePressedWindowOnMouseLeave = false;
+ }
handleMouseEvent(timestamp, local, global, modifiers, type, source);
}
@@ -1901,10 +1995,10 @@ static inline bool doCheckUnGrabAncestor(QXcbConnection *conn)
return true;
}
-static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr)
+static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn)
{
return ((doCheckUnGrabAncestor(conn)
- && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR)
+ && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR)
|| (mode == XCB_NOTIFY_MODE_UNGRAB && detail == XCB_NOTIFY_DETAIL_INFERIOR)
|| detail == XCB_NOTIFY_DETAIL_VIRTUAL
|| detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL);
@@ -1924,14 +2018,18 @@ void QXcbWindow::handleEnterNotifyEvent(int event_x, int event_y, int root_x, in
{
connection()->setTime(timestamp);
- const QPoint global = QPoint(root_x, root_y);
-
- if (ignoreEnterEvent(mode, detail, connection()) || connection()->mousePressWindow())
+ if (ignoreEnterEvent(mode, detail, connection())
+ || (connection()->mousePressWindow() && !m_ignorePressedWindowOnMouseLeave)) {
return;
+ }
// Updates scroll valuators, as user might have done some scrolling outside our X client.
connection()->xi2UpdateScrollingDevices();
+ if (mode == XCB_NOTIFY_MODE_UNGRAB && connection()->queryMouseButtons() != Qt::NoButton)
+ m_ignorePressedWindowOnMouseLeave = true;
+
+ const QPoint global = QPoint(root_x, root_y);
const QPoint local(event_x, event_y);
QWindowSystemInterface::handleEnterEvent(window(), local, global);
}
@@ -1941,8 +2039,11 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
{
connection()->setTime(timestamp);
- if (ignoreLeaveEvent(mode, detail, connection()) || connection()->mousePressWindow())
+ QXcbWindow *mousePressWindow = connection()->mousePressWindow();
+ if (ignoreLeaveEvent(mode, detail, connection())
+ || (mousePressWindow && !m_ignorePressedWindowOnMouseLeave)) {
return;
+ }
// check if enter event is buffered
auto event = connection()->eventQueue()->peek([](xcb_generic_event_t *event, int type) {
@@ -1960,6 +2061,8 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
QWindowSystemInterface::handleEnterLeaveEvent(enterWindow->window(), window(), local, global);
} else {
QWindowSystemInterface::handleLeaveEvent(window());
+ if (m_ignorePressedWindowOnMouseLeave)
+ connection()->setMousePressWindow(nullptr);
}
free(enter);
@@ -2136,17 +2239,17 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev
const bool propertyDeleted = event->state == XCB_PROPERTY_DELETE;
- if (event->atom == atom(QXcbAtom::_NET_WM_STATE) || event->atom == atom(QXcbAtom::WM_STATE)) {
+ if (event->atom == atom(QXcbAtom::Atom_NET_WM_STATE) || event->atom == atom(QXcbAtom::AtomWM_STATE)) {
if (propertyDeleted)
return;
Qt::WindowStates newState = Qt::WindowNoState;
- if (event->atom == atom(QXcbAtom::WM_STATE)) { // WM_STATE: Quick check for 'Minimize'.
+ if (event->atom == atom(QXcbAtom::AtomWM_STATE)) { // WM_STATE: Quick check for 'Minimize'.
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::WM_STATE),
+ 0, m_window, atom(QXcbAtom::AtomWM_STATE),
XCB_ATOM_ANY, 0, 1024);
- if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::WM_STATE)) {
+ if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::AtomWM_STATE)) {
const quint32 *data = (const quint32 *)xcb_get_property_value(reply.get());
if (reply->length != 0)
m_minimized = (data[0] == XCB_ICCCM_WM_STATE_ICONIC
@@ -2176,7 +2279,7 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev
connection()->setMouseGrabber(nullptr);
}
return;
- } else if (event->atom == atom(QXcbAtom::_NET_FRAME_EXTENTS)) {
+ } else if (event->atom == atom(QXcbAtom::Atom_NET_FRAME_EXTENTS)) {
m_dirtyFrameMargins = true;
}
}
@@ -2285,7 +2388,7 @@ bool QXcbWindow::windowEvent(QEvent *event)
case Qt::BacktabFocusReason:
{
const QXcbWindow *container =
- static_cast<const QXcbWindow *>(parent());
+ static_cast<const QXcbWindow *>(QPlatformWindow::parent());
sendXEmbedMessage(container->xcb_window(),
focusEvent->reason() == Qt::TabFocusReason ?
XEMBED_FOCUS_NEXT : XEMBED_FOCUS_PREV);
@@ -2315,7 +2418,7 @@ bool QXcbWindow::startSystemMove()
bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int edges)
{
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
if (!connection()->wmSupport()->isSupportedByWM(moveResize))
return false;
@@ -2363,7 +2466,7 @@ static uint qtEdgesToXcbMoveResizeDirection(Qt::Edges edges)
void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int edges)
{
qCDebug(lcQpaXInputDevices) << "triggered system move or resize via sending _NET_WM_MOVERESIZE client message";
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
xcb_client_message_event_t xev;
xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize;
@@ -2396,7 +2499,7 @@ void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message,
event.format = 32;
event.sequence = 0;
event.window = window;
- event.type = atom(QXcbAtom::_XEMBED);
+ event.type = atom(QXcbAtom::Atom_XEMBED);
event.data.data32[0] = connection()->time();
event.data.data32[1] = message;
event.data.data32[2] = detail;
@@ -2405,15 +2508,15 @@ void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message,
xcb_send_event(xcb_connection(), false, window, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
}
-static bool activeWindowChangeQueued(const QWindow *window)
+static bool focusWindowChangeQueued(const QWindow *window)
{
/* Check from window system event queue if the next queued activation
* targets a window other than @window.
*/
- QWindowSystemInterfacePrivate::ActivatedWindowEvent *systemEvent =
- static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>
- (QWindowSystemInterfacePrivate::peekWindowSystemEvent(QWindowSystemInterfacePrivate::ActivatedWindow));
- return systemEvent && systemEvent->activated != window;
+ QWindowSystemInterfacePrivate::FocusWindowEvent *systemEvent =
+ static_cast<QWindowSystemInterfacePrivate::FocusWindowEvent *>
+ (QWindowSystemInterfacePrivate::peekWindowSystemEvent(QWindowSystemInterfacePrivate::FocusWindow));
+ return systemEvent && systemEvent->focused != window;
}
void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event)
@@ -2443,13 +2546,13 @@ void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event)
break;
}
connection()->setFocusWindow(window());
- QWindowSystemInterface::handleWindowActivated(window(), reason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), reason);
break;
case XEMBED_FOCUS_OUT:
if (window() == QGuiApplication::focusWindow()
- && !activeWindowChangeQueued(window())) {
+ && !focusWindowChangeQueued(window())) {
connection()->setFocusWindow(nullptr);
- QWindowSystemInterface::handleWindowActivated(nullptr);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr);
}
break;
}
@@ -2475,7 +2578,7 @@ void QXcbWindow::setOpacity(qreal level)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_WINDOW_OPACITY),
+ atom(QXcbAtom::Atom_NET_WM_WINDOW_OPACITY),
XCB_ATOM_CARDINAL,
32,
1,
@@ -2513,7 +2616,7 @@ void QXcbWindow::setAlertState(bool enabled)
m_alertState = enabled;
- setNetWmState(enabled, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION));
+ setNetWmState(enabled, atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION));
}
uint QXcbWindow::visualId() const
@@ -2547,8 +2650,8 @@ void QXcbWindow::setWindowTitle(const QXcbConnection *conn, xcb_window_t window,
xcb_change_property(conn->xcb_connection(),
XCB_PROP_MODE_REPLACE,
window,
- conn->atom(QXcbAtom::_NET_WM_NAME),
- conn->atom(QXcbAtom::UTF8_STRING),
+ conn->atom(QXcbAtom::Atom_NET_WM_NAME),
+ conn->atom(QXcbAtom::AtomUTF8_STRING),
8,
ba.size(),
ba.constData());
@@ -2564,9 +2667,9 @@ void QXcbWindow::setWindowTitle(const QXcbConnection *conn, xcb_window_t window,
QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window)
{
- const xcb_atom_t utf8Atom = conn->atom(QXcbAtom::UTF8_STRING);
+ const xcb_atom_t utf8Atom = conn->atom(QXcbAtom::AtomUTF8_STRING);
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(),
- false, window, conn->atom(QXcbAtom::_NET_WM_NAME),
+ false, window, conn->atom(QXcbAtom::Atom_NET_WM_NAME),
utf8Atom, 0, 1024);
if (reply && reply->format == 8 && reply->type == utf8Atom) {
const char *name = reinterpret_cast<const char *>(xcb_get_property_value(reply.get()));
@@ -2574,7 +2677,7 @@ QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window)
}
reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(),
- false, window, conn->atom(QXcbAtom::WM_NAME),
+ false, window, conn->atom(QXcbAtom::AtomWM_NAME),
XCB_ATOM_STRING, 0, 1024);
if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) {
const char *name = reinterpret_cast<const char *>(xcb_get_property_value(reply.get()));
diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h
index 54a96a7a0a..0c047d569b 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.h
+++ b/src/plugins/platforms/xcb/qxcbwindow.h
@@ -6,6 +6,8 @@
#include <qpa/qplatformwindow.h>
#include <qpa/qplatformwindow_p.h>
+#include <QtCore/QObject>
+#include <QtCore/QPointer>
#include <QtGui/QSurfaceFormat>
#include <QtGui/QImage>
@@ -20,9 +22,10 @@ class QXcbScreen;
class QXcbSyncWindowRequest;
class QIcon;
-class Q_XCB_EXPORT QXcbWindow : public QXcbObject, public QXcbWindowEventListener, public QPlatformWindow
+class Q_XCB_EXPORT QXcbWindow : public QObject, public QXcbObject, public QXcbWindowEventListener, public QPlatformWindow
, public QNativeInterface::Private::QXcbWindow
{
+ Q_OBJECT
public:
enum NetWmState {
NetWmStateAbove = 0x1,
@@ -118,6 +121,8 @@ public:
Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source);
void updateNetWmUserTime(xcb_timestamp_t timestamp);
+ void updateWmTransientFor();
+ void registerWmTransientForChild(QXcbWindow *);
WindowTypes wmWindowTypes() const;
void setWmWindowType(WindowTypes types, Qt::WindowFlags flags);
@@ -217,6 +222,7 @@ protected:
Qt::WindowStates m_windowState = Qt::WindowNoState;
+ QMutex m_mappedMutex;
bool m_mapped = false;
bool m_transparent = false;
bool m_deferredActivation = false;
@@ -224,6 +230,7 @@ protected:
bool m_alertState = false;
bool m_minimized = false;
bool m_trayIconWindow = false;
+ bool m_ignorePressedWindowOnMouseLeave = false;
xcb_window_t m_netWmUserTimeWindow = XCB_NONE;
QSurfaceFormat m_format;
@@ -253,13 +260,14 @@ protected:
qreal m_sizeHintsScaleFactor = 1.0;
RecreationReasons m_recreationReasons = RecreationNotNeeded;
+
+ QList<QPointer<QXcbWindow>> m_wmTransientForChildren;
};
class QXcbForeignWindow : public QXcbWindow
{
public:
- QXcbForeignWindow(QWindow *window, WId nativeHandle)
- : QXcbWindow(window) { m_window = nativeHandle; }
+ QXcbForeignWindow(QWindow *window, WId nativeHandle);
~QXcbForeignWindow();
bool isForeignWindow() const override { return true; }
diff --git a/src/plugins/platforms/xcb/qxcbwmsupport.cpp b/src/plugins/platforms/xcb/qxcbwmsupport.cpp
index 50e85c0aa5..0e3c470c89 100644
--- a/src/plugins/platforms/xcb/qxcbwmsupport.cpp
+++ b/src/plugins/platforms/xcb/qxcbwmsupport.cpp
@@ -30,7 +30,7 @@ void QXcbWMSupport::updateNetWMAtoms()
int offset = 0;
int remaining = 0;
do {
- auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, root, atom(QXcbAtom::_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024);
+ auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, root, atom(QXcbAtom::Atom_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024);
if (!reply)
break;
@@ -54,7 +54,7 @@ void QXcbWMSupport::updateVirtualRoots()
{
net_virtual_roots.clear();
- if (!isSupportedByWM(atom(QXcbAtom::_NET_VIRTUAL_ROOTS)))
+ if (!isSupportedByWM(atom(QXcbAtom::Atom_NET_VIRTUAL_ROOTS)))
return;
xcb_window_t root = connection()->primaryScreen()->root();
@@ -62,7 +62,7 @@ void QXcbWMSupport::updateVirtualRoots()
int remaining = 0;
do {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- false, root, atom(QXcbAtom::_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024);
+ false, root, atom(QXcbAtom::Atom_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024);
if (!reply)
break;
diff --git a/src/plugins/platforms/xcb/qxcbxsettings.cpp b/src/plugins/platforms/xcb/qxcbxsettings.cpp
index 21828922c6..6b62864add 100644
--- a/src/plugins/platforms/xcb/qxcbxsettings.cpp
+++ b/src/plugins/platforms/xcb/qxcbxsettings.cpp
@@ -68,7 +68,7 @@ public:
int offset = 0;
QByteArray settings;
- xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::_XSETTINGS_SETTINGS);
+ xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::Atom_XSETTINGS_SETTINGS);
while (1) {
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property,
screen->xcb_connection(),