summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/SCsub26
-rw-r--r--platform/android/SCsub76
-rw-r--r--platform/android/android_keys_utils.cpp (renamed from platform/haiku/key_mapping_haiku.h)21
-rw-r--r--platform/android/android_keys_utils.h283
-rw-r--r--platform/android/api/api.cpp16
-rw-r--r--platform/android/api/api.h5
-rw-r--r--platform/android/api/java_class_wrapper.h68
-rw-r--r--platform/android/api/jni_singleton.h223
-rw-r--r--platform/android/audio_driver_jandroid.cpp47
-rw-r--r--platform/android/audio_driver_jandroid.h3
-rw-r--r--platform/android/audio_driver_opensl.cpp47
-rw-r--r--platform/android/audio_driver_opensl.h3
-rw-r--r--platform/android/detect.py256
-rw-r--r--platform/android/dir_access_jandroid.cpp34
-rw-r--r--platform/android/dir_access_jandroid.h3
-rw-r--r--platform/android/display_server_android.cpp697
-rw-r--r--platform/android/display_server_android.h182
-rw-r--r--platform/android/export/export.cpp1564
-rw-r--r--platform/android/export/export.h5
-rw-r--r--platform/android/export/gradle_export_util.h245
-rw-r--r--platform/android/file_access_android.cpp24
-rw-r--r--platform/android/file_access_android.h1
-rw-r--r--platform/android/file_access_jandroid.cpp19
-rw-r--r--platform/android/file_access_jandroid.h1
-rw-r--r--platform/android/java/app/AndroidManifest.xml29
-rw-r--r--platform/android/java/app/build.gradle123
-rw-r--r--platform/android/java/app/config.gradle143
-rw-r--r--platform/android/java/app/res/values-ar/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ar/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-bg/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-bg/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-ca/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ca/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-cs/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-cs/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-da/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-da/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-de/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-de/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-el/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-el/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-en/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-en/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-es-rES/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-es-rES/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-es/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-es/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-fa/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-fi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-fi/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-fr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-fr/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-hi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hi/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-hr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hr/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-hu/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hu/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-in/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-it/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-it/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-iw/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-ja/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ja/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-ko/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-lt/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-lt/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-lv/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-lv/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-nb/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-nb/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-nl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-nl/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-pl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-pl/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-pt/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-pt/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-ro/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ro/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-ru/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ru/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-sk/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sk/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-sl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sl/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-sr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sr/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-sv/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sv/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-th/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-th/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-tl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-tl/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-tr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-tr/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-uk/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-uk/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-vi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-vi/strings.xml)3
-rw-r--r--platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rHK/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rTW/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-zh/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rCN/strings.xml)1
-rw-r--r--platform/android/java/app/res/values/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/settings.gradle2
-rw-r--r--platform/android/java/app/src/com/godot/game/GodotApp.java4
-rw-r--r--platform/android/java/build.gradle94
-rw-r--r--platform/android/java/gradle.properties3
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--platform/android/java/lib/AndroidManifest.xml2
-rw-r--r--platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl281
-rw-r--r--platform/android/java/lib/build.gradle9
-rw-r--r--platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.pngbin1116 -> 0 bytes
-rw-r--r--platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.pngbin388 -> 0 bytes
-rw-r--r--platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.pngbin462 -> 0 bytes
-rw-r--r--platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.pngbin1556 -> 0 bytes
-rw-r--r--platform/android/java/lib/res/layout/downloading_expansion.xml2
-rw-r--r--platform/android/java/lib/res/layout/godot_app_layout.xml5
-rw-r--r--platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml2
-rw-r--r--platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml2
-rw-r--r--platform/android/java/lib/res/values-fa/strings.xml1
-rw-r--r--platform/android/java/lib/res/values-in/strings.xml4
-rw-r--r--platform/android/java/lib/res/values-iw/strings.xml4
-rw-r--r--platform/android/java/lib/res/values-ko/strings.xml3
-rw-r--r--platform/android/java/lib/res/values/dimens.xml4
-rw-r--r--platform/android/java/lib/res/values/strings.xml2
-rw-r--r--platform/android/java/lib/res/values/styles.xml2
-rw-r--r--platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java2
-rw-r--r--platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Dictionary.java6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java (renamed from platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java)80
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java597
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java (renamed from platform/android/java/lib/src/org/godotengine/godot/GodotView.java)74
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java90
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java52
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java230
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java (renamed from platform/iphone/platform_refcount.h)28
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java29
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java148
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java117
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java16
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java58
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java58
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java116
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java93
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java419
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java118
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java141
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java142
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java370
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java208
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java (renamed from platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java)82
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java3
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java227
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java28
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt (renamed from platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java)95
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt136
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt230
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java5
-rw-r--r--platform/android/java_class_wrapper.cpp138
-rw-r--r--platform/android/java_godot_io_wrapper.cpp47
-rw-r--r--platform/android/java_godot_io_wrapper.h12
-rw-r--r--platform/android/java_godot_lib_jni.cpp1155
-rw-r--r--platform/android/java_godot_lib_jni.h58
-rw-r--r--platform/android/java_godot_wrapper.cpp98
-rw-r--r--platform/android/java_godot_wrapper.h18
-rw-r--r--platform/android/jni_utils.cpp400
-rw-r--r--platform/android/jni_utils.h55
-rw-r--r--platform/android/logo.pngbin1459 -> 951 bytes
-rw-r--r--platform/android/net_socket_android.cpp6
-rw-r--r--platform/android/net_socket_android.h7
-rw-r--r--platform/android/os_android.cpp570
-rw-r--r--platform/android/os_android.h119
-rw-r--r--platform/android/plugin/godot_plugin_config.h268
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp158
-rw-r--r--platform/android/plugin/godot_plugin_jni.h45
-rw-r--r--platform/android/power_android.cpp255
-rw-r--r--platform/android/power_android.h80
-rw-r--r--platform/android/string_android.h4
-rw-r--r--platform/android/thread_jandroid.cpp27
-rw-r--r--platform/android/thread_jandroid.h1
-rw-r--r--platform/android/vulkan/vulkan_context_android.cpp (renamed from platform/haiku/context_gl_haiku.cpp)65
-rw-r--r--platform/android/vulkan/vulkan_context_android.h (renamed from platform/iphone/power_iphone.h)27
-rw-r--r--platform/haiku/SCsub28
-rw-r--r--platform/haiku/audio_driver_media_kit.cpp142
-rw-r--r--platform/haiku/detect.py154
-rw-r--r--platform/haiku/godot.rdef60
-rw-r--r--platform/haiku/haiku_application.cpp35
-rw-r--r--platform/haiku/haiku_direct_window.cpp361
-rw-r--r--platform/haiku/haiku_direct_window.h89
-rw-r--r--platform/haiku/key_mapping_haiku.cpp249
-rw-r--r--platform/haiku/logo.pngbin1265 -> 0 bytes
-rw-r--r--platform/haiku/os_haiku.cpp378
-rw-r--r--platform/haiku/os_haiku.h127
-rw-r--r--platform/haiku/platform_config.h37
-rw-r--r--platform/iphone/SCsub44
-rw-r--r--platform/iphone/app_delegate.h20
-rw-r--r--platform/iphone/app_delegate.mm658
-rw-r--r--platform/iphone/detect.py270
-rw-r--r--platform/iphone/display_layer.h58
-rw-r--r--platform/iphone/display_layer.mm186
-rw-r--r--platform/iphone/display_server_iphone.h202
-rw-r--r--platform/iphone/display_server_iphone.mm766
-rw-r--r--platform/iphone/export/export.cpp652
-rw-r--r--platform/iphone/export/export.h5
-rw-r--r--platform/iphone/game_center.h7
-rw-r--r--platform/iphone/game_center.mm90
-rw-r--r--platform/iphone/gl_view.h123
-rw-r--r--platform/iphone/gl_view.mm733
-rw-r--r--platform/iphone/godot_iphone.mm (renamed from platform/iphone/godot_iphone.cpp)64
-rw-r--r--platform/iphone/godot_view.h56
-rw-r--r--platform/iphone/godot_view.mm499
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.h (renamed from platform/haiku/haiku_application.h)21
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.m171
-rw-r--r--platform/iphone/godot_view_renderer.h (renamed from platform/haiku/godot_haiku.cpp)25
-rw-r--r--platform/iphone/godot_view_renderer.mm117
-rw-r--r--platform/iphone/icloud.h7
-rw-r--r--platform/iphone/icloud.mm52
-rw-r--r--platform/iphone/in_app_store.h5
-rw-r--r--platform/iphone/in_app_store.mm75
-rw-r--r--platform/iphone/ios.h1
-rw-r--r--platform/iphone/ios.mm23
-rw-r--r--platform/iphone/joypad_iphone.h (renamed from platform/haiku/haiku_gl_view.h)27
-rw-r--r--platform/iphone/joypad_iphone.mm380
-rw-r--r--platform/iphone/logo.pngbin1489 -> 1297 bytes
-rw-r--r--platform/iphone/main.m15
-rw-r--r--platform/iphone/os_iphone.cpp679
-rw-r--r--platform/iphone/os_iphone.h158
-rw-r--r--platform/iphone/os_iphone.mm365
-rw-r--r--platform/iphone/platform_config.h3
-rw-r--r--platform/iphone/semaphore_iphone.cpp112
-rw-r--r--platform/iphone/view_controller.h20
-rw-r--r--platform/iphone/view_controller.mm354
-rw-r--r--platform/iphone/vulkan_context_iphone.h (renamed from platform/iphone/semaphore_iphone.h)33
-rw-r--r--platform/iphone/vulkan_context_iphone.mm (renamed from platform/haiku/haiku_gl_view.cpp)36
-rw-r--r--platform/javascript/SCsub88
-rw-r--r--platform/javascript/api/api.cpp10
-rw-r--r--platform/javascript/api/api.h5
-rw-r--r--platform/javascript/audio_driver_javascript.cpp177
-rw-r--r--platform/javascript/audio_driver_javascript.h29
-rw-r--r--platform/javascript/detect.py205
-rw-r--r--platform/javascript/display_server_javascript.cpp1201
-rw-r--r--platform/javascript/display_server_javascript.h208
-rw-r--r--platform/javascript/dom_keys.inc538
-rw-r--r--platform/javascript/emscripten_helpers.py38
-rw-r--r--platform/javascript/engine.js413
-rw-r--r--platform/javascript/engine/engine.js253
-rw-r--r--platform/javascript/engine/externs.js3
-rw-r--r--platform/javascript/engine/preloader.js139
-rw-r--r--platform/javascript/engine/utils.js51
-rw-r--r--platform/javascript/export/export.cpp204
-rw-r--r--platform/javascript/http_client.h.inc18
-rw-r--r--platform/javascript/http_client_javascript.cpp69
-rw-r--r--platform/javascript/http_request.h2
-rw-r--r--platform/javascript/javascript_eval.cpp24
-rw-r--r--platform/javascript/javascript_main.cpp139
-rw-r--r--platform/javascript/native/http_request.js (renamed from platform/javascript/http_request.js)0
-rw-r--r--platform/javascript/native/id_handler.js (renamed from platform/javascript/id_handler.js)2
-rw-r--r--platform/javascript/native/utils.js277
-rw-r--r--platform/javascript/os_javascript.cpp1273
-rw-r--r--platform/javascript/os_javascript.h137
-rw-r--r--platform/javascript/pre.js5
-rw-r--r--platform/linuxbsd/SCsub22
-rw-r--r--platform/linuxbsd/context_gl_x11.cpp (renamed from platform/x11/context_gl_x11.cpp)40
-rw-r--r--platform/linuxbsd/context_gl_x11.h (renamed from platform/x11/context_gl_x11.h)4
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp (renamed from platform/x11/crash_handler_x11.cpp)29
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.h (renamed from platform/x11/crash_handler_x11.h)3
-rw-r--r--platform/linuxbsd/detect.py385
-rw-r--r--platform/linuxbsd/detect_prime_x11.cpp (renamed from platform/x11/detect_prime.cpp)16
-rw-r--r--platform/linuxbsd/detect_prime_x11.h (renamed from platform/x11/detect_prime.h)2
-rw-r--r--platform/linuxbsd/display_server_x11.cpp3991
-rw-r--r--platform/linuxbsd/display_server_x11.h371
-rw-r--r--platform/linuxbsd/export/export.cpp (renamed from platform/x11/export/export.cpp)9
-rw-r--r--platform/linuxbsd/export/export.h (renamed from platform/x11/export/export.h)7
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp (renamed from platform/x11/godot_x11.cpp)13
-rw-r--r--platform/linuxbsd/joypad_linux.cpp (renamed from platform/x11/joypad_linux.cpp)127
-rw-r--r--platform/linuxbsd/joypad_linux.h (renamed from platform/x11/joypad_linux.h)12
-rw-r--r--platform/linuxbsd/key_mapping_x11.cpp (renamed from platform/x11/key_mapping_x11.cpp)186
-rw-r--r--platform/linuxbsd/key_mapping_x11.h (renamed from platform/x11/key_mapping_x11.h)3
-rw-r--r--platform/linuxbsd/logo.png (renamed from platform/x11/logo.png)bin1679 -> 1679 bytes
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp362
-rw-r--r--platform/linuxbsd/os_linuxbsd.h (renamed from platform/haiku/audio_driver_media_kit.h)91
-rw-r--r--platform/linuxbsd/pck_embed.ld (renamed from platform/x11/pck_embed.ld)0
-rw-r--r--platform/linuxbsd/pck_embed.legacy.ld (renamed from platform/x11/pck_embed.legacy.ld)0
-rw-r--r--platform/linuxbsd/platform_config.h (renamed from platform/x11/platform_config.h)15
-rw-r--r--platform/linuxbsd/platform_linuxbsd_builders.py (renamed from platform/x11/platform_x11_builders.py)10
-rw-r--r--platform/linuxbsd/vulkan_context_x11.cpp56
-rw-r--r--platform/linuxbsd/vulkan_context_x11.h (renamed from platform/osx/semaphore_osx.h)34
-rw-r--r--platform/osx/SCsub20
-rw-r--r--platform/osx/context_gl_osx.h (renamed from platform/haiku/context_gl_haiku.h)40
-rw-r--r--platform/osx/context_gl_osx.mm161
-rw-r--r--platform/osx/crash_handler_osx.h1
-rw-r--r--platform/osx/crash_handler_osx.mm4
-rw-r--r--platform/osx/detect.py213
-rw-r--r--platform/osx/dir_access_osx.mm1
-rw-r--r--platform/osx/display_server_osx.h325
-rw-r--r--platform/osx/display_server_osx.mm3875
-rw-r--r--platform/osx/export/export.cpp536
-rw-r--r--platform/osx/export/export.h5
-rw-r--r--platform/osx/godot_main_osx.mm8
-rw-r--r--platform/osx/joypad_osx.cpp101
-rw-r--r--platform/osx/joypad_osx.h10
-rw-r--r--platform/osx/logo.pngbin1751 -> 7195 bytes
-rw-r--r--platform/osx/os_osx.h237
-rw-r--r--platform/osx/os_osx.mm2914
-rw-r--r--platform/osx/platform_config.h2
-rw-r--r--platform/osx/platform_osx_builders.py10
-rw-r--r--platform/osx/power_osx.cpp252
-rw-r--r--platform/osx/power_osx.h59
-rw-r--r--platform/osx/semaphore_osx.cpp107
-rw-r--r--platform/osx/vulkan_context_osx.h (renamed from platform/uwp/power_uwp.h)31
-rw-r--r--platform/osx/vulkan_context_osx.mm (renamed from platform/iphone/power_iphone.cpp)49
-rw-r--r--platform/server/SCsub15
-rw-r--r--platform/server/detect.py263
-rw-r--r--platform/server/godot_server.cpp4
-rw-r--r--platform/server/os_server.cpp76
-rw-r--r--platform/server/os_server.h22
-rw-r--r--platform/server/platform_config.h13
-rw-r--r--platform/uwp/SCsub21
-rw-r--r--platform/uwp/app.cpp54
-rw-r--r--platform/uwp/app.h17
-rw-r--r--platform/uwp/context_egl_uwp.cpp12
-rw-r--r--platform/uwp/context_egl_uwp.h3
-rw-r--r--platform/uwp/detect.py183
-rw-r--r--platform/uwp/export/export.cpp278
-rw-r--r--platform/uwp/export/export.h5
-rw-r--r--platform/uwp/joypad_uwp.cpp28
-rw-r--r--platform/uwp/joypad_uwp.h4
-rw-r--r--platform/uwp/os_uwp.cpp233
-rw-r--r--platform/uwp/os_uwp.h27
-rw-r--r--platform/uwp/power_uwp.cpp77
-rw-r--r--platform/uwp/thread_uwp.cpp12
-rw-r--r--platform/uwp/thread_uwp.h9
-rw-r--r--platform/windows/SCsub15
-rw-r--r--platform/windows/context_gl_windows.cpp19
-rw-r--r--platform/windows/context_gl_windows.h1
-rw-r--r--platform/windows/crash_handler_windows.cpp12
-rw-r--r--platform/windows/crash_handler_windows.h1
-rw-r--r--platform/windows/detect.py444
-rw-r--r--platform/windows/display_server_windows.cpp3266
-rw-r--r--platform/windows/display_server_windows.h562
-rw-r--r--platform/windows/export/export.cpp18
-rw-r--r--platform/windows/godot.natvis6
-rw-r--r--platform/windows/godot_windows.cpp23
-rw-r--r--platform/windows/joypad_windows.cpp171
-rw-r--r--platform/windows/joypad_windows.h13
-rw-r--r--platform/windows/key_mapping_windows.cpp181
-rw-r--r--platform/windows/key_mapping_windows.h5
-rw-r--r--[-rwxr-xr-x]platform/windows/os_windows.cpp2919
-rw-r--r--platform/windows/os_windows.h321
-rw-r--r--platform/windows/platform_config.h5
-rw-r--r--platform/windows/platform_windows_builders.py10
-rw-r--r--platform/windows/power_windows.cpp131
-rw-r--r--platform/windows/vulkan_context_win.cpp56
-rw-r--r--platform/windows/vulkan_context_win.h (renamed from platform/windows/power_windows.h)31
-rw-r--r--platform/windows/windows_terminal_logger.cpp103
-rw-r--r--platform/x11/SCsub21
-rw-r--r--platform/x11/detect.py357
-rw-r--r--platform/x11/os_x11.cpp3510
-rw-r--r--platform/x11/os_x11.h334
-rw-r--r--platform/x11/power_x11.cpp577
-rw-r--r--platform/x11/power_x11.h66
339 files changed, 29405 insertions, 26538 deletions
diff --git a/platform/SCsub b/platform/SCsub
index 38bab59d74..5194a19518 100644
--- a/platform/SCsub
+++ b/platform/SCsub
@@ -1,32 +1,30 @@
#!/usr/bin/env python
-from compat import open_utf8
-
-Import('env')
+Import("env")
env.platform_sources = []
# Register platform-exclusive APIs
reg_apis_inc = '#include "register_platform_apis.h"\n'
-reg_apis = 'void register_platform_apis() {\n'
-unreg_apis = 'void unregister_platform_apis() {\n'
+reg_apis = "void register_platform_apis() {\n"
+unreg_apis = "void unregister_platform_apis() {\n"
for platform in env.platform_apis:
platform_dir = env.Dir(platform)
- env.add_source_files(env.platform_sources, platform + '/api/api.cpp')
- reg_apis += '\tregister_' + platform + '_api();\n'
- unreg_apis += '\tunregister_' + platform + '_api();\n'
+ env.add_source_files(env.platform_sources, platform + "/api/api.cpp")
+ reg_apis += "\tregister_" + platform + "_api();\n"
+ unreg_apis += "\tunregister_" + platform + "_api();\n"
reg_apis_inc += '#include "' + platform + '/api/api.h"\n'
-reg_apis_inc += '\n'
-reg_apis += '}\n\n'
-unreg_apis += '}\n'
+reg_apis_inc += "\n"
+reg_apis += "}\n\n"
+unreg_apis += "}\n"
# NOTE: It is safe to generate this file here, since this is still execute serially
-with open_utf8('register_platform_apis.gen.cpp', 'w') as f:
+with open("register_platform_apis.gen.cpp", "w", encoding="utf-8") as f:
f.write(reg_apis_inc)
f.write(reg_apis)
f.write(unreg_apis)
-env.add_source_files(env.platform_sources, 'register_platform_apis.gen.cpp')
+env.add_source_files(env.platform_sources, "register_platform_apis.gen.cpp")
-lib = env.add_library('platform', env.platform_sources)
+lib = env.add_library("platform", env.platform_sources)
env.Prepend(LIBS=[lib])
diff --git a/platform/android/SCsub b/platform/android/SCsub
index 3ff5b8278a..ec42bc42b5 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -1,24 +1,24 @@
#!/usr/bin/env python
-from detect import get_ndk_version
-from distutils.version import LooseVersion
-
-Import('env')
+Import("env")
android_files = [
- 'os_android.cpp',
- 'file_access_android.cpp',
- 'audio_driver_opensl.cpp',
- 'file_access_jandroid.cpp',
- 'dir_access_jandroid.cpp',
- 'thread_jandroid.cpp',
- 'net_socket_android.cpp',
- 'audio_driver_jandroid.cpp',
- 'java_godot_lib_jni.cpp',
- 'java_class_wrapper.cpp',
- 'java_godot_wrapper.cpp',
- 'java_godot_io_wrapper.cpp',
- #'power_android.cpp'
+ "os_android.cpp",
+ "file_access_android.cpp",
+ "audio_driver_opensl.cpp",
+ "file_access_jandroid.cpp",
+ "dir_access_jandroid.cpp",
+ "thread_jandroid.cpp",
+ "net_socket_android.cpp",
+ "audio_driver_jandroid.cpp",
+ "java_godot_lib_jni.cpp",
+ "java_class_wrapper.cpp",
+ "java_godot_wrapper.cpp",
+ "java_godot_io_wrapper.cpp",
+ "jni_utils.cpp",
+ "android_keys_utils.cpp",
+ "display_server_android.cpp",
+ "vulkan/vulkan_context_android.cpp",
]
env_android = env.Clone()
@@ -29,30 +29,34 @@ for x in android_files:
env_thirdparty = env_android.Clone()
env_thirdparty.disable_warnings()
-android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc'))
+android_objects.append(env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc"))
lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"])
-lib_arch_dir = ''
-if env['android_arch'] == 'armv7':
- lib_arch_dir = 'armeabi-v7a'
-elif env['android_arch'] == 'arm64v8':
- lib_arch_dir = 'arm64-v8a'
-elif env['android_arch'] == 'x86':
- lib_arch_dir = 'x86'
-elif env['android_arch'] == 'x86_64':
- lib_arch_dir = 'x86_64'
+lib_arch_dir = ""
+if env["android_arch"] == "armv7":
+ lib_arch_dir = "armeabi-v7a"
+elif env["android_arch"] == "arm64v8":
+ lib_arch_dir = "arm64-v8a"
+elif env["android_arch"] == "x86":
+ lib_arch_dir = "x86"
+elif env["android_arch"] == "x86_64":
+ lib_arch_dir = "x86_64"
else:
- print('WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin')
+ print("WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin")
-if lib_arch_dir != '':
- if env['target'] == 'release':
- lib_type_dir = 'release'
+if lib_arch_dir != "":
+ if env["target"] == "release":
+ lib_type_dir = "release"
else: # release_debug, debug
- lib_type_dir = 'debug'
+ lib_type_dir = "debug"
- out_dir = '#platform/android/java/lib/libs/' + lib_type_dir + '/' + lib_arch_dir
- env_android.Command(out_dir + '/libgodot_android.so', '#bin/libgodot' + env['SHLIBSUFFIX'], Move("$TARGET", "$SOURCE"))
+ out_dir = "#platform/android/java/lib/libs/" + lib_type_dir + "/" + lib_arch_dir
+ env_android.Command(
+ out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE")
+ )
- stl_lib_path = str(env['ANDROID_NDK_ROOT']) + '/sources/cxx-stl/llvm-libc++/libs/' + lib_arch_dir + '/libc++_shared.so'
- env_android.Command(out_dir + '/libc++_shared.so', stl_lib_path, Copy("$TARGET", "$SOURCE"))
+ stl_lib_path = (
+ str(env["ANDROID_NDK_ROOT"]) + "/sources/cxx-stl/llvm-libc++/libs/" + lib_arch_dir + "/libc++_shared.so"
+ )
+ env_android.Command(out_dir + "/libc++_shared.so", stl_lib_path, Copy("$TARGET", "$SOURCE"))
diff --git a/platform/haiku/key_mapping_haiku.h b/platform/android/android_keys_utils.cpp
index a0e85e3390..b5b4fb9a4b 100644
--- a/platform/haiku/key_mapping_haiku.h
+++ b/platform/android/android_keys_utils.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* key_mapping_haiku.h */
+/* android_keys_utils.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,14 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef KEY_MAPPING_HAIKU_H
-#define KEY_MAPPING_HAIKU_H
+#include "android_keys_utils.h"
-class KeyMappingHaiku {
- KeyMappingHaiku(){};
+unsigned int android_get_keysym(unsigned int p_code) {
+ for (int i = 0; _ak_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+ if (_ak_to_keycode[i].keycode == p_code) {
+ return _ak_to_keycode[i].keysym;
+ }
+ }
-public:
- static unsigned int get_keysym(int32 raw_char, int32 key);
- static unsigned int get_modifier_keysym(int32 key);
-};
-
-#endif
+ return KEY_UNKNOWN;
+}
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
new file mode 100644
index 0000000000..857bef02d1
--- /dev/null
+++ b/platform/android/android_keys_utils.h
@@ -0,0 +1,283 @@
+/*************************************************************************/
+/* android_keys_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANDROID_KEYS_UTILS_H
+#define ANDROID_KEYS_UTILS_H
+
+#include <core/os/keyboard.h>
+
+/*
+ * Android Key codes.
+ */
+enum {
+ AKEYCODE_UNKNOWN = 0,
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_HOME = 3,
+ AKEYCODE_BACK = 4,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_0 = 7,
+ AKEYCODE_1 = 8,
+ AKEYCODE_2 = 9,
+ AKEYCODE_3 = 10,
+ AKEYCODE_4 = 11,
+ AKEYCODE_5 = 12,
+ AKEYCODE_6 = 13,
+ AKEYCODE_7 = 14,
+ AKEYCODE_8 = 15,
+ AKEYCODE_9 = 16,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_DPAD_UP = 19,
+ AKEYCODE_DPAD_DOWN = 20,
+ AKEYCODE_DPAD_LEFT = 21,
+ AKEYCODE_DPAD_RIGHT = 22,
+ AKEYCODE_DPAD_CENTER = 23,
+ AKEYCODE_VOLUME_UP = 24,
+ AKEYCODE_VOLUME_DOWN = 25,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_A = 29,
+ AKEYCODE_B = 30,
+ AKEYCODE_C = 31,
+ AKEYCODE_D = 32,
+ AKEYCODE_E = 33,
+ AKEYCODE_F = 34,
+ AKEYCODE_G = 35,
+ AKEYCODE_H = 36,
+ AKEYCODE_I = 37,
+ AKEYCODE_J = 38,
+ AKEYCODE_K = 39,
+ AKEYCODE_L = 40,
+ AKEYCODE_M = 41,
+ AKEYCODE_N = 42,
+ AKEYCODE_O = 43,
+ AKEYCODE_P = 44,
+ AKEYCODE_Q = 45,
+ AKEYCODE_R = 46,
+ AKEYCODE_S = 47,
+ AKEYCODE_T = 48,
+ AKEYCODE_U = 49,
+ AKEYCODE_V = 50,
+ AKEYCODE_W = 51,
+ AKEYCODE_X = 52,
+ AKEYCODE_Y = 53,
+ AKEYCODE_Z = 54,
+ AKEYCODE_COMMA = 55,
+ AKEYCODE_PERIOD = 56,
+ AKEYCODE_ALT_LEFT = 57,
+ AKEYCODE_ALT_RIGHT = 58,
+ AKEYCODE_SHIFT_LEFT = 59,
+ AKEYCODE_SHIFT_RIGHT = 60,
+ AKEYCODE_TAB = 61,
+ AKEYCODE_SPACE = 62,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_EXPLORER = 64,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_ENTER = 66,
+ AKEYCODE_DEL = 67,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_MINUS = 69,
+ AKEYCODE_EQUALS = 70,
+ AKEYCODE_LEFT_BRACKET = 71,
+ AKEYCODE_RIGHT_BRACKET = 72,
+ AKEYCODE_BACKSLASH = 73,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_SLASH = 76,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_PLUS = 81,
+ AKEYCODE_MENU = 82,
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_MEDIA_PLAY_PAUSE = 85,
+ AKEYCODE_MEDIA_STOP = 86,
+ AKEYCODE_MEDIA_NEXT = 87,
+ AKEYCODE_MEDIA_PREVIOUS = 88,
+ AKEYCODE_MEDIA_REWIND = 89,
+ AKEYCODE_MEDIA_FAST_FORWARD = 90,
+ AKEYCODE_MUTE = 91,
+ AKEYCODE_PAGE_UP = 92,
+ AKEYCODE_PAGE_DOWN = 93,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+ AKEYCODE_BUTTON_A = 96,
+ AKEYCODE_BUTTON_B = 97,
+ AKEYCODE_BUTTON_C = 98,
+ AKEYCODE_BUTTON_X = 99,
+ AKEYCODE_BUTTON_Y = 100,
+ AKEYCODE_BUTTON_Z = 101,
+ AKEYCODE_BUTTON_L1 = 102,
+ AKEYCODE_BUTTON_R1 = 103,
+ AKEYCODE_BUTTON_L2 = 104,
+ AKEYCODE_BUTTON_R2 = 105,
+ AKEYCODE_BUTTON_THUMBL = 106,
+ AKEYCODE_BUTTON_THUMBR = 107,
+ AKEYCODE_BUTTON_START = 108,
+ AKEYCODE_BUTTON_SELECT = 109,
+ AKEYCODE_BUTTON_MODE = 110,
+ AKEYCODE_CONTROL_LEFT = 113,
+ AKEYCODE_CONTROL_RIGHT = 114,
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+};
+
+struct _WinTranslatePair {
+ unsigned int keysym;
+ unsigned int keycode;
+};
+
+static _WinTranslatePair _ak_to_keycode[] = {
+ { KEY_TAB, AKEYCODE_TAB },
+ { KEY_ENTER, AKEYCODE_ENTER },
+ { KEY_SHIFT, AKEYCODE_SHIFT_LEFT },
+ { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT },
+ { KEY_ALT, AKEYCODE_ALT_LEFT },
+ { KEY_ALT, AKEYCODE_ALT_RIGHT },
+ { KEY_MENU, AKEYCODE_MENU },
+ { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
+ { KEY_ESCAPE, AKEYCODE_BACK },
+ { KEY_SPACE, AKEYCODE_SPACE },
+ { KEY_PAGEUP, AKEYCODE_PAGE_UP },
+ { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN },
+ { KEY_HOME, AKEYCODE_HOME }, //(0x24)
+ { KEY_LEFT, AKEYCODE_DPAD_LEFT },
+ { KEY_UP, AKEYCODE_DPAD_UP },
+ { KEY_RIGHT, AKEYCODE_DPAD_RIGHT },
+ { KEY_DOWN, AKEYCODE_DPAD_DOWN },
+ { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER },
+ { KEY_BACKSPACE, AKEYCODE_DEL },
+ { KEY_0, AKEYCODE_0 }, ////0 key
+ { KEY_1, AKEYCODE_1 }, ////1 key
+ { KEY_2, AKEYCODE_2 }, ////2 key
+ { KEY_3, AKEYCODE_3 }, ////3 key
+ { KEY_4, AKEYCODE_4 }, ////4 key
+ { KEY_5, AKEYCODE_5 }, ////5 key
+ { KEY_6, AKEYCODE_6 }, ////6 key
+ { KEY_7, AKEYCODE_7 }, ////7 key
+ { KEY_8, AKEYCODE_8 }, ////8 key
+ { KEY_9, AKEYCODE_9 }, ////9 key
+ { KEY_A, AKEYCODE_A }, ////A key
+ { KEY_B, AKEYCODE_B }, ////B key
+ { KEY_C, AKEYCODE_C }, ////C key
+ { KEY_D, AKEYCODE_D }, ////D key
+ { KEY_E, AKEYCODE_E }, ////E key
+ { KEY_F, AKEYCODE_F }, ////F key
+ { KEY_G, AKEYCODE_G }, ////G key
+ { KEY_H, AKEYCODE_H }, ////H key
+ { KEY_I, AKEYCODE_I }, ////I key
+ { KEY_J, AKEYCODE_J }, ////J key
+ { KEY_K, AKEYCODE_K }, ////K key
+ { KEY_L, AKEYCODE_L }, ////L key
+ { KEY_M, AKEYCODE_M }, ////M key
+ { KEY_N, AKEYCODE_N }, ////N key
+ { KEY_O, AKEYCODE_O }, ////O key
+ { KEY_P, AKEYCODE_P }, ////P key
+ { KEY_Q, AKEYCODE_Q }, ////Q key
+ { KEY_R, AKEYCODE_R }, ////R key
+ { KEY_S, AKEYCODE_S }, ////S key
+ { KEY_T, AKEYCODE_T }, ////T key
+ { KEY_U, AKEYCODE_U }, ////U key
+ { KEY_V, AKEYCODE_V }, ////V key
+ { KEY_W, AKEYCODE_W }, ////W key
+ { KEY_X, AKEYCODE_X }, ////X key
+ { KEY_Y, AKEYCODE_Y }, ////Y key
+ { KEY_Z, AKEYCODE_Z }, ////Z key
+ { KEY_HOMEPAGE, AKEYCODE_EXPLORER },
+ { KEY_LAUNCH0, AKEYCODE_BUTTON_A },
+ { KEY_LAUNCH1, AKEYCODE_BUTTON_B },
+ { KEY_LAUNCH2, AKEYCODE_BUTTON_C },
+ { KEY_LAUNCH3, AKEYCODE_BUTTON_X },
+ { KEY_LAUNCH4, AKEYCODE_BUTTON_Y },
+ { KEY_LAUNCH5, AKEYCODE_BUTTON_Z },
+ { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 },
+ { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 },
+ { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 },
+ { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 },
+ { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL },
+ { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR },
+ { KEY_LAUNCHC, AKEYCODE_BUTTON_START },
+ { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT },
+ { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE },
+ { KEY_VOLUMEMUTE, AKEYCODE_MUTE },
+ { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
+ { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP },
+ { KEY_BACK, AKEYCODE_MEDIA_REWIND },
+ { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
+ { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT },
+ { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
+ { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP },
+ { KEY_PLUS, AKEYCODE_PLUS },
+ { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key
+ { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key
+ { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key
+ { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key
+ { KEY_BACKSLASH, AKEYCODE_BACKSLASH },
+ { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
+ { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
+ { KEY_CONTROL, AKEYCODE_CONTROL_LEFT },
+ { KEY_CONTROL, AKEYCODE_CONTROL_RIGHT },
+ { KEY_UNKNOWN, 0 }
+};
+/*
+TODO: map these android key:
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+*/
+
+unsigned int android_get_keysym(unsigned int p_code);
+
+#endif // ANDROID_KEYS_UTILS_H
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 2146c5409b..1f140f7119 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -32,15 +32,19 @@
#include "core/engine.h"
#include "java_class_wrapper.h"
+#include "jni_singleton.h"
#if !defined(ANDROID_ENABLED)
-static JavaClassWrapper *java_class_wrapper = NULL;
+static JavaClassWrapper *java_class_wrapper = nullptr;
#endif
void register_android_api() {
-
#if !defined(ANDROID_ENABLED)
+ // On Android platforms, the `java_class_wrapper` instantiation and the
+ // `JNISingleton` registration occurs in
+ // `platform/android/java_godot_lib_jni.cpp#Java_org_godotengine_godot_GodotLib_setup`
java_class_wrapper = memnew(JavaClassWrapper); // Dummy
+ ClassDB::register_class<JNISingleton>();
#endif
ClassDB::register_class<JavaClass>();
@@ -49,31 +53,29 @@ void register_android_api() {
}
void unregister_android_api() {
-
#if !defined(ANDROID_ENABLED)
memdelete(java_class_wrapper);
#endif
}
void JavaClassWrapper::_bind_methods() {
-
ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap);
}
#if !defined(ANDROID_ENABLED)
-Variant JavaClass::call(const StringName &, const Variant **, int, Variant::CallError &) {
+Variant JavaClass::call(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
JavaClass::JavaClass() {
}
-Variant JavaObject::call(const StringName &, const Variant **, int, Variant::CallError &) {
+Variant JavaObject::call(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
-JavaClassWrapper *JavaClassWrapper::singleton = NULL;
+JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
Ref<JavaClass> JavaClassWrapper::wrap(const String &) {
return Ref<JavaClass>();
diff --git a/platform/android/api/api.h b/platform/android/api/api.h
index c7296d92a7..5e951b9c88 100644
--- a/platform/android/api/api.h
+++ b/platform/android/api/api.h
@@ -28,5 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef ANDROID_API_H
+#define ANDROID_API_H
+
void register_android_api();
void unregister_android_api();
+
+#endif // ANDROID_API_H
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index 6c06d57ac1..1fa2726784 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -43,7 +43,6 @@ class JavaObject;
#endif
class JavaClass : public Reference {
-
GDCLASS(JavaClass, Reference);
#ifdef ANDROID_ENABLED
@@ -68,7 +67,6 @@ class JavaClass : public Reference {
Map<StringName, Variant> constant_map;
struct MethodInfo {
-
bool _static;
Vector<uint32_t> param_types;
Vector<StringName> param_sigs;
@@ -77,15 +75,17 @@ class JavaClass : public Reference {
};
_FORCE_INLINE_ static void _convert_to_variant_type(int p_sig, Variant::Type &r_type, float &likelihood) {
-
likelihood = 1.0;
r_type = Variant::NIL;
switch (p_sig) {
-
- case ARG_TYPE_VOID: r_type = Variant::NIL; break;
+ case ARG_TYPE_VOID:
+ r_type = Variant::NIL;
+ break;
case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT:
- case ARG_TYPE_BOOLEAN: r_type = Variant::BOOL; break;
+ case ARG_TYPE_BOOLEAN:
+ r_type = Variant::BOOL;
+ break;
case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT:
case ARG_TYPE_BYTE:
r_type = Variant::INT;
@@ -113,68 +113,79 @@ class JavaClass : public Reference {
break;
case ARG_TYPE_FLOAT | ARG_NUMBER_CLASS_BIT:
case ARG_TYPE_FLOAT:
- r_type = Variant::REAL;
+ r_type = Variant::FLOAT;
likelihood = 1.0;
break;
case ARG_TYPE_DOUBLE | ARG_NUMBER_CLASS_BIT:
case ARG_TYPE_DOUBLE:
- r_type = Variant::REAL;
+ r_type = Variant::FLOAT;
likelihood = 0.5;
break;
- case ARG_TYPE_STRING: r_type = Variant::STRING; break;
- case ARG_TYPE_CLASS: r_type = Variant::OBJECT; break;
- case ARG_ARRAY_BIT | ARG_TYPE_VOID: r_type = Variant::NIL; break;
- case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: r_type = Variant::ARRAY; break;
+ case ARG_TYPE_STRING:
+ r_type = Variant::STRING;
+ break;
+ case ARG_TYPE_CLASS:
+ r_type = Variant::OBJECT;
+ break;
+ case ARG_ARRAY_BIT | ARG_TYPE_VOID:
+ r_type = Variant::NIL;
+ break;
+ case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN:
+ r_type = Variant::ARRAY;
+ break;
case ARG_ARRAY_BIT | ARG_TYPE_BYTE:
- r_type = Variant::POOL_BYTE_ARRAY;
+ r_type = Variant::PACKED_BYTE_ARRAY;
likelihood = 1.0;
break;
case ARG_ARRAY_BIT | ARG_TYPE_CHAR:
- r_type = Variant::POOL_BYTE_ARRAY;
+ r_type = Variant::PACKED_BYTE_ARRAY;
likelihood = 0.5;
break;
case ARG_ARRAY_BIT | ARG_TYPE_SHORT:
- r_type = Variant::POOL_INT_ARRAY;
+ r_type = Variant::PACKED_INT32_ARRAY;
likelihood = 0.3;
break;
case ARG_ARRAY_BIT | ARG_TYPE_INT:
- r_type = Variant::POOL_INT_ARRAY;
+ r_type = Variant::PACKED_INT32_ARRAY;
likelihood = 1.0;
break;
case ARG_ARRAY_BIT | ARG_TYPE_LONG:
- r_type = Variant::POOL_INT_ARRAY;
+ r_type = Variant::PACKED_INT32_ARRAY;
likelihood = 0.5;
break;
case ARG_ARRAY_BIT | ARG_TYPE_FLOAT:
- r_type = Variant::POOL_REAL_ARRAY;
+ r_type = Variant::PACKED_FLOAT32_ARRAY;
likelihood = 1.0;
break;
case ARG_ARRAY_BIT | ARG_TYPE_DOUBLE:
- r_type = Variant::POOL_REAL_ARRAY;
+ r_type = Variant::PACKED_FLOAT32_ARRAY;
likelihood = 0.5;
break;
- case ARG_ARRAY_BIT | ARG_TYPE_STRING: r_type = Variant::POOL_STRING_ARRAY; break;
- case ARG_ARRAY_BIT | ARG_TYPE_CLASS: r_type = Variant::ARRAY; break;
+ case ARG_ARRAY_BIT | ARG_TYPE_STRING:
+ r_type = Variant::PACKED_STRING_ARRAY;
+ break;
+ case ARG_ARRAY_BIT | ARG_TYPE_CLASS:
+ r_type = Variant::ARRAY;
+ break;
}
}
_FORCE_INLINE_ static bool _convert_object_to_variant(JNIEnv *env, jobject obj, Variant &var, uint32_t p_sig);
- bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error, Variant &ret);
+ bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret);
friend class JavaClassWrapper;
- Map<StringName, List<MethodInfo> > methods;
+ Map<StringName, List<MethodInfo>> methods;
jclass _class;
#endif
public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
+ virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
JavaClass();
};
class JavaObject : public Reference {
-
GDCLASS(JavaObject, Reference);
#ifdef ANDROID_ENABLED
@@ -185,7 +196,7 @@ class JavaObject : public Reference {
#endif
public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
+ virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
#ifdef ANDROID_ENABLED
JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance);
@@ -194,11 +205,10 @@ public:
};
class JavaClassWrapper : public Object {
-
GDCLASS(JavaClassWrapper, Object);
#ifdef ANDROID_ENABLED
- Map<String, Ref<JavaClass> > class_cache;
+ Map<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
jclass activityClass;
jmethodID findClass;
@@ -236,7 +246,7 @@ public:
Ref<JavaClass> wrap(const String &p_class);
#ifdef ANDROID_ENABLED
- JavaClassWrapper(jobject p_activity = NULL);
+ JavaClassWrapper(jobject p_activity = nullptr);
#else
JavaClassWrapper();
#endif
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
new file mode 100644
index 0000000000..5e63f20d6c
--- /dev/null
+++ b/platform/android/api/jni_singleton.h
@@ -0,0 +1,223 @@
+/*************************************************************************/
+/* jni_singleton.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef JNI_SINGLETON_H
+#define JNI_SINGLETON_H
+
+#include <core/engine.h>
+#include <core/variant.h>
+#ifdef ANDROID_ENABLED
+#include <platform/android/jni_utils.h>
+#endif
+
+class JNISingleton : public Object {
+ GDCLASS(JNISingleton, Object);
+
+#ifdef ANDROID_ENABLED
+ struct MethodData {
+ jmethodID method;
+ Variant::Type ret_type;
+ Vector<Variant::Type> argtypes;
+ };
+
+ jobject instance;
+ Map<StringName, MethodData> method_map;
+#endif
+
+public:
+ virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
+#ifdef ANDROID_ENABLED
+ Map<StringName, MethodData>::Element *E = method_map.find(p_method);
+
+ // Check the method we're looking for is in the JNISingleton map and that
+ // the arguments match.
+ bool call_error = !E || E->get().argtypes.size() != p_argcount;
+ if (!call_error) {
+ for (int i = 0; i < p_argcount; i++) {
+ if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
+ call_error = true;
+ break;
+ }
+ }
+ }
+
+ if (call_error) {
+ // The method is not in this map, defaulting to the regular instance calls.
+ return Object::call(p_method, p_args, p_argcount, r_error);
+ }
+
+ ERR_FAIL_COND_V(!instance, Variant());
+
+ r_error.error = Callable::CallError::CALL_OK;
+
+ jvalue *v = nullptr;
+
+ if (p_argcount) {
+ v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
+ }
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ int res = env->PushLocalFrame(16);
+
+ ERR_FAIL_COND_V(res != 0, Variant());
+
+ List<jobject> to_erase;
+ for (int i = 0; i < p_argcount; i++) {
+ jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
+ v[i] = vr.val;
+ if (vr.obj)
+ to_erase.push_back(vr.obj);
+ }
+
+ Variant ret;
+
+ switch (E->get().ret_type) {
+ case Variant::NIL: {
+ env->CallVoidMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::BOOL: {
+ ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE;
+ } break;
+ case Variant::INT: {
+ ret = env->CallIntMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::FLOAT: {
+ ret = env->CallFloatMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::STRING: {
+ jobject o = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = jstring_to_string((jstring)o, env);
+ env->DeleteLocalRef(o);
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+ jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ ret = _jobject_to_variant(env, arr);
+
+ env->DeleteLocalRef(arr);
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ int fCount = env->GetArrayLength(arr);
+ Vector<int> sarr;
+ sarr.resize(fCount);
+
+ int *w = sarr.ptrw();
+ env->GetIntArrayRegion(arr, 0, fCount, w);
+ ret = sarr;
+ env->DeleteLocalRef(arr);
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ int fCount = env->GetArrayLength(arr);
+ Vector<float> sarr;
+ sarr.resize(fCount);
+
+ float *w = sarr.ptrw();
+ env->GetFloatArrayRegion(arr, 0, fCount, w);
+ ret = sarr;
+ env->DeleteLocalRef(arr);
+ } break;
+
+#ifndef _MSC_VER
+#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
+#endif
+ case Variant::DICTIONARY: {
+ jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = _jobject_to_variant(env, obj);
+ env->DeleteLocalRef(obj);
+
+ } break;
+ default: {
+ env->PopLocalFrame(nullptr);
+ ERR_FAIL_V(Variant());
+ } break;
+ }
+
+ while (to_erase.size()) {
+ env->DeleteLocalRef(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+
+ env->PopLocalFrame(nullptr);
+
+ return ret;
+#else // ANDROID_ENABLED
+
+ // Defaulting to the regular instance calls.
+ return Object::call(p_method, p_args, p_argcount, r_error);
+#endif
+ }
+
+#ifdef ANDROID_ENABLED
+ jobject get_instance() const {
+ return instance;
+ }
+
+ void set_instance(jobject p_instance) {
+ instance = p_instance;
+ }
+
+ void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
+ MethodData md;
+ md.method = p_method;
+ md.argtypes = p_args;
+ md.ret_type = p_ret_type;
+ method_map[p_name] = md;
+ }
+
+ void add_signal(const StringName &p_name, const Vector<Variant::Type> &p_args) {
+ if (p_args.size() == 0)
+ ADD_SIGNAL(MethodInfo(p_name));
+ else if (p_args.size() == 1)
+ ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1")));
+ else if (p_args.size() == 2)
+ ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2")));
+ else if (p_args.size() == 3)
+ ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3")));
+ else if (p_args.size() == 4)
+ ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4")));
+ else if (p_args.size() == 5)
+ ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"), PropertyInfo(p_args[4], "arg5")));
+ }
+
+#endif
+
+ JNISingleton() {
+#ifdef ANDROID_ENABLED
+ instance = nullptr;
+#endif
+ }
+};
+
+#endif // JNI_SINGLETON_H
diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp
index 5a8e3b94da..09c981b3fa 100644
--- a/platform/android/audio_driver_jandroid.cpp
+++ b/platform/android/audio_driver_jandroid.cpp
@@ -34,7 +34,7 @@
#include "core/project_settings.h"
#include "thread_jandroid.h"
-AudioDriverAndroid *AudioDriverAndroid::s_ad = NULL;
+AudioDriverAndroid *AudioDriverAndroid::s_ad = nullptr;
jobject AudioDriverAndroid::io;
jmethodID AudioDriverAndroid::_init_audio;
@@ -46,19 +46,16 @@ jclass AudioDriverAndroid::cls;
int AudioDriverAndroid::audioBufferFrames = 0;
int AudioDriverAndroid::mix_rate = 44100;
bool AudioDriverAndroid::quit = false;
-jobject AudioDriverAndroid::audioBuffer = NULL;
-void *AudioDriverAndroid::audioBufferPinned = NULL;
-Mutex *AudioDriverAndroid::mutex = NULL;
-int32_t *AudioDriverAndroid::audioBuffer32 = NULL;
+jobject AudioDriverAndroid::audioBuffer = nullptr;
+void *AudioDriverAndroid::audioBufferPinned = nullptr;
+Mutex AudioDriverAndroid::mutex;
+int32_t *AudioDriverAndroid::audioBuffer32 = nullptr;
const char *AudioDriverAndroid::get_name() const {
-
return "Android";
}
Error AudioDriverAndroid::init() {
-
- mutex = Mutex::create();
/*
// TODO: pass in/return a (Java) device ID, also whether we're opening for input or output
this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
@@ -76,15 +73,15 @@ Error AudioDriverAndroid::init() {
// __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
JNIEnv *env = ThreadAndroid::get_env();
- int mix_rate = GLOBAL_DEF_RST("audio/mix_rate", 44100);
+ int mix_rate = GLOBAL_GET("audio/mix_rate");
- int latency = GLOBAL_DEF_RST("audio/output_latency", 25);
+ int latency = GLOBAL_GET("audio/output_latency");
unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000);
print_verbose("Audio buffer size: " + itos(buffer_size));
audioBuffer = env->CallObjectMethod(io, _init_audio, mix_rate, buffer_size);
- ERR_FAIL_COND_V(audioBuffer == NULL, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(audioBuffer == nullptr, ERR_INVALID_PARAMETER);
audioBuffer = env->NewGlobalRef(audioBuffer);
@@ -101,7 +98,6 @@ void AudioDriverAndroid::start() {
}
void AudioDriverAndroid::setup(jobject p_io) {
-
JNIEnv *env = ThreadAndroid::get_env();
io = p_io;
@@ -115,10 +111,8 @@ void AudioDriverAndroid::setup(jobject p_io) {
}
void AudioDriverAndroid::thread_func(JNIEnv *env) {
-
jclass cls = env->FindClass("org/godotengine/godot/Godot");
if (cls) {
-
cls = (jclass)env->NewGlobalRef(cls);
}
jfieldID fid = env->GetStaticFieldID(cls, "io", "Lorg/godotengine/godot/GodotIO;");
@@ -129,24 +123,20 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) {
_write_buffer = env->GetMethodID(lcls, "audioWriteShortBuffer", "([S)V");
while (!quit) {
-
int16_t *ptr = (int16_t *)audioBufferPinned;
int fc = audioBufferFrames;
- if (!s_ad->active || mutex->try_lock() != OK) {
-
+ if (!s_ad->active || mutex.try_lock() != OK) {
for (int i = 0; i < fc; i++) {
ptr[i] = 0;
}
} else {
-
s_ad->audio_server_process(fc / 2, audioBuffer32);
- mutex->unlock();
+ mutex.unlock();
for (int i = 0; i < fc; i++) {
-
ptr[i] = audioBuffer32[i] >> 16;
}
}
@@ -156,49 +146,40 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) {
}
int AudioDriverAndroid::get_mix_rate() const {
-
return mix_rate;
}
AudioDriver::SpeakerMode AudioDriverAndroid::get_speaker_mode() const {
-
return SPEAKER_MODE_STEREO;
}
void AudioDriverAndroid::lock() {
-
- if (mutex)
- mutex->lock();
+ mutex.lock();
}
void AudioDriverAndroid::unlock() {
-
- if (mutex)
- mutex->unlock();
+ mutex.unlock();
}
void AudioDriverAndroid::finish() {
-
JNIEnv *env = ThreadAndroid::get_env();
env->CallVoidMethod(io, _quit);
if (audioBuffer) {
env->DeleteGlobalRef(audioBuffer);
- audioBuffer = NULL;
- audioBufferPinned = NULL;
+ audioBuffer = nullptr;
+ audioBufferPinned = nullptr;
}
active = false;
}
void AudioDriverAndroid::set_pause(bool p_pause) {
-
JNIEnv *env = ThreadAndroid::get_env();
env->CallVoidMethod(io, _pause, p_pause);
}
AudioDriverAndroid::AudioDriverAndroid() {
-
s_ad = this;
active = false;
}
diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h
index d3d1641c20..953ade9311 100644
--- a/platform/android/audio_driver_jandroid.h
+++ b/platform/android/audio_driver_jandroid.h
@@ -36,8 +36,7 @@
#include "java_godot_lib_jni.h"
class AudioDriverAndroid : public AudioDriver {
-
- static Mutex *mutex;
+ static Mutex mutex;
static AudioDriverAndroid *s_ad;
static jobject io;
static jmethodID _init_audio;
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 6e9864c803..740e9a3132 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -39,27 +39,25 @@
void AudioDriverOpenSL::_buffer_callback(
SLAndroidSimpleBufferQueueItf queueItf) {
-
bool mix = true;
if (pause) {
mix = false;
- } else if (mutex) {
- mix = mutex->try_lock() == OK;
+ } else {
+ mix = mutex.try_lock() == OK;
}
if (mix) {
audio_server_process(buffer_size, mixdown_buffer);
} else {
-
int32_t *src_buff = mixdown_buffer;
for (unsigned int i = 0; i < buffer_size * 2; i++) {
src_buff[i] = 0;
}
}
- if (mutex && mix)
- mutex->unlock();
+ if (mix)
+ mutex.unlock();
const int32_t *src_buff = mixdown_buffer;
@@ -67,7 +65,6 @@ void AudioDriverOpenSL::_buffer_callback(
last_free = (last_free + 1) % BUFFER_COUNT;
for (unsigned int i = 0; i < buffer_size * 2; i++) {
-
ptr[i] = src_buff[i] >> 16;
}
@@ -77,26 +74,23 @@ void AudioDriverOpenSL::_buffer_callback(
void AudioDriverOpenSL::_buffer_callbacks(
SLAndroidSimpleBufferQueueItf queueItf,
void *pContext) {
-
AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext;
ad->_buffer_callback(queueItf);
}
-AudioDriverOpenSL *AudioDriverOpenSL::s_ad = NULL;
+AudioDriverOpenSL *AudioDriverOpenSL::s_ad = nullptr;
const char *AudioDriverOpenSL::get_name() const {
-
return "Android";
}
Error AudioDriverOpenSL::init() {
-
SLresult res;
SLEngineOption EngineOption[] = {
{ (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE }
};
- res = slCreateEngine(&sl, 1, EngineOption, 0, NULL, NULL);
+ res = slCreateEngine(&sl, 1, EngineOption, 0, nullptr, nullptr);
ERR_FAIL_COND_V_MSG(res != SL_RESULT_SUCCESS, ERR_INVALID_PARAMETER, "Could not initialize OpenSL.");
res = (*sl)->Realize(sl, SL_BOOLEAN_FALSE);
@@ -106,8 +100,6 @@ Error AudioDriverOpenSL::init() {
}
void AudioDriverOpenSL::start() {
-
- mutex = Mutex::create();
active = false;
SLresult res;
@@ -115,7 +107,6 @@ void AudioDriverOpenSL::start() {
buffer_size = 1024;
for (int i = 0; i < BUFFER_COUNT; i++) {
-
buffers[i] = memnew_arr(int16_t, buffer_size * 2);
memset(buffers[i], 0, buffer_size * 4);
}
@@ -162,7 +153,7 @@ void AudioDriverOpenSL::start() {
locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
locator_outputmix.outputMix = OutputMix;
audioSink.pLocator = (void *)&locator_outputmix;
- audioSink.pFormat = NULL;
+ audioSink.pFormat = nullptr;
/* Initialize the context for Buffer queue callbacks */
//cntxt.pDataBase = (void*)&pcmData;
//cntxt.pData = cntxt.pDataBase;
@@ -205,7 +196,6 @@ void AudioDriverOpenSL::start() {
}
void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf queueItf) {
-
for (int i = 0; i < rec_buffer.size(); i++) {
int32_t sample = rec_buffer[i] << 16;
input_buffer_write(sample);
@@ -217,21 +207,19 @@ void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf qu
}
void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf queueItf, void *pContext) {
-
AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext;
ad->_record_buffer_callback(queueItf);
}
Error AudioDriverOpenSL::capture_init_device() {
-
SLDataLocator_IODevice loc_dev = {
SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT,
- NULL
+ nullptr
};
- SLDataSource recSource = { &loc_dev, NULL };
+ SLDataSource recSource = { &loc_dev, nullptr };
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
@@ -292,7 +280,6 @@ Error AudioDriverOpenSL::capture_init_device() {
}
Error AudioDriverOpenSL::capture_start() {
-
if (OS::get_singleton()->request_permission("RECORD_AUDIO")) {
return capture_init_device();
}
@@ -301,7 +288,6 @@ Error AudioDriverOpenSL::capture_start() {
}
Error AudioDriverOpenSL::capture_stop() {
-
SLuint32 state;
SLresult res = (*recordItf)->GetRecordState(recordItf, &state);
ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
@@ -318,34 +304,28 @@ Error AudioDriverOpenSL::capture_stop() {
}
int AudioDriverOpenSL::get_mix_rate() const {
-
return 44100; // hardcoded for Android, as selected by SL_SAMPLINGRATE_44_1
}
AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const {
-
return SPEAKER_MODE_STEREO;
}
void AudioDriverOpenSL::lock() {
-
- if (active && mutex)
- mutex->lock();
+ if (active)
+ mutex.lock();
}
void AudioDriverOpenSL::unlock() {
-
- if (active && mutex)
- mutex->unlock();
+ if (active)
+ mutex.unlock();
}
void AudioDriverOpenSL::finish() {
-
(*sl)->Destroy(sl);
}
void AudioDriverOpenSL::set_pause(bool p_pause) {
-
pause = p_pause;
if (active) {
@@ -359,7 +339,6 @@ void AudioDriverOpenSL::set_pause(bool p_pause) {
AudioDriverOpenSL::AudioDriverOpenSL() {
s_ad = this;
- mutex = Mutex::create(); //NULL;
pause = false;
active = false;
}
diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h
index 57d9b30af6..9858a40822 100644
--- a/platform/android/audio_driver_opensl.h
+++ b/platform/android/audio_driver_opensl.h
@@ -38,9 +38,8 @@
#include <SLES/OpenSLES_Android.h>
class AudioDriverOpenSL : public AudioDriver {
-
bool active;
- Mutex *mutex;
+ Mutex mutex;
enum {
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 8b62360888..0accacb679 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -13,7 +13,7 @@ def get_name():
def can_build():
- return ("ANDROID_NDK_ROOT" in os.environ)
+ return "ANDROID_NDK_ROOT" in os.environ
def get_platform(platform):
@@ -24,33 +24,33 @@ def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
return [
- ('ANDROID_NDK_ROOT', 'Path to the Android NDK', os.environ.get("ANDROID_NDK_ROOT", 0)),
- ('ndk_platform', 'Target platform (android-<api>, e.g. "android-18")', "android-18"),
- EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'arm64v8', 'x86', 'x86_64')),
- BoolVariable('android_neon', 'Enable NEON support (armv7 only)', True),
+ ("ANDROID_NDK_ROOT", "Path to the Android NDK", os.environ.get("ANDROID_NDK_ROOT", 0)),
+ ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"),
+ EnumVariable("android_arch", "Target architecture", "armv7", ("armv7", "arm64v8", "x86", "x86_64")),
+ BoolVariable("android_neon", "Enable NEON support (armv7 only)", True),
]
def get_flags():
return [
- ('tools', False),
+ ("tools", False),
]
def create(env):
- tools = env['TOOLS']
+ tools = env["TOOLS"]
if "mingw" in tools:
- tools.remove('mingw')
+ tools.remove("mingw")
if "applelink" in tools:
tools.remove("applelink")
- env.Tool('gcc')
+ env.Tool("gcc")
return env.Clone(tools=tools)
def configure(env):
# Workaround for MinGW. See:
# http://www.scons.org/wiki/LongCmdLinesOnWin32
- if (os.name == "nt"):
+ if os.name == "nt":
import subprocess
@@ -58,8 +58,15 @@ def configure(env):
# print("SPAWNED : " + cmdline)
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, startupinfo=startupinfo, shell=False, env=env)
+ proc = subprocess.Popen(
+ cmdline,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ startupinfo=startupinfo,
+ shell=False,
+ env=env,
+ )
data, err = proc.communicate()
rv = proc.wait()
if rv:
@@ -70,7 +77,7 @@ def configure(env):
def mySpawn(sh, escape, cmd, args, env):
- newargs = ' '.join(args[1:])
+ newargs = " ".join(args[1:])
cmdline = cmd + " " + newargs
rv = 0
@@ -85,50 +92,56 @@ def configure(env):
return rv
- env['SPAWN'] = mySpawn
+ env["SPAWN"] = mySpawn
# Architecture
- if env['android_arch'] not in ['armv7', 'arm64v8', 'x86', 'x86_64']:
- env['android_arch'] = 'armv7'
+ if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]:
+ env["android_arch"] = "armv7"
neon_text = ""
- if env["android_arch"] == "armv7" and env['android_neon']:
+ if env["android_arch"] == "armv7" and env["android_neon"]:
neon_text = " (with NEON)"
- print("Building for Android (" + env['android_arch'] + ")" + neon_text)
+ print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")" + neon_text)
can_vectorize = True
- if env['android_arch'] == 'x86':
- env['ARCH'] = 'arch-x86'
+ if env["android_arch"] == "x86":
+ env["ARCH"] = "arch-x86"
env.extra_suffix = ".x86" + env.extra_suffix
target_subpath = "x86-4.9"
abi_subpath = "i686-linux-android"
arch_subpath = "x86"
env["x86_libtheora_opt_gcc"] = True
- if env['android_arch'] == 'x86_64':
+ if env["android_arch"] == "x86_64":
if get_platform(env["ndk_platform"]) < 21:
- print("WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21")
+ print(
+ "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
+ )
env["ndk_platform"] = "android-21"
- env['ARCH'] = 'arch-x86_64'
+ env["ARCH"] = "arch-x86_64"
env.extra_suffix = ".x86_64" + env.extra_suffix
target_subpath = "x86_64-4.9"
abi_subpath = "x86_64-linux-android"
arch_subpath = "x86_64"
env["x86_libtheora_opt_gcc"] = True
elif env["android_arch"] == "armv7":
- env['ARCH'] = 'arch-arm'
+ env["ARCH"] = "arch-arm"
target_subpath = "arm-linux-androideabi-4.9"
abi_subpath = "arm-linux-androideabi"
arch_subpath = "armeabi-v7a"
- if env['android_neon']:
+ if env["android_neon"]:
env.extra_suffix = ".armv7.neon" + env.extra_suffix
else:
env.extra_suffix = ".armv7" + env.extra_suffix
elif env["android_arch"] == "arm64v8":
if get_platform(env["ndk_platform"]) < 21:
- print("WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21")
+ print(
+ "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
+ )
env["ndk_platform"] = "android-21"
- env['ARCH'] = 'arch-arm64'
+ env["ARCH"] = "arch-arm64"
target_subpath = "aarch64-linux-android-4.9"
abi_subpath = "aarch64-linux-android"
arch_subpath = "arm64-v8a"
@@ -136,86 +149,80 @@ def configure(env):
# Build type
- if (env["target"].startswith("release")):
- if (env["optimize"] == "speed"): # optimize for speed (default)
- env.Append(LINKFLAGS=['-O2'])
- env.Append(CCFLAGS=['-O2', '-fomit-frame-pointer'])
- env.Append(CPPDEFINES=['NDEBUG'])
+ if env["target"].startswith("release"):
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(LINKFLAGS=["-O2"])
+ env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"])
+ env.Append(CPPDEFINES=["NDEBUG"])
else: # optimize for size
- env.Append(CCFLAGS=['-Os'])
- env.Append(CPPDEFINES=['NDEBUG'])
- env.Append(LINKFLAGS=['-Os'])
-
- if (can_vectorize):
- env.Append(CCFLAGS=['-ftree-vectorize'])
- if (env["target"] == "release_debug"):
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- elif (env["target"] == "debug"):
- env.Append(LINKFLAGS=['-O0'])
- env.Append(CCFLAGS=['-O0', '-g', '-fno-limit-debug-info'])
- env.Append(CPPDEFINES=['_DEBUG', 'DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
- env.Append(CPPFLAGS=['-UNDEBUG'])
+ env.Append(CCFLAGS=["-Os"])
+ env.Append(CPPDEFINES=["NDEBUG"])
+ env.Append(LINKFLAGS=["-Os"])
+
+ if can_vectorize:
+ env.Append(CCFLAGS=["-ftree-vectorize"])
+ if env["target"] == "release_debug":
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ elif env["target"] == "debug":
+ env.Append(LINKFLAGS=["-O0"])
+ env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"])
+ env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"])
+ env.Append(CPPFLAGS=["-UNDEBUG"])
# Compiler configuration
- env['SHLIBSUFFIX'] = '.so'
+ env["SHLIBSUFFIX"] = ".so"
- if env['PLATFORM'] == 'win32':
- env.Tool('gcc')
+ if env["PLATFORM"] == "win32":
+ env.Tool("gcc")
env.use_windows_spawn_fix()
- mt_link = True
- if (sys.platform.startswith("linux")):
+ if sys.platform.startswith("linux"):
host_subpath = "linux-x86_64"
- elif (sys.platform.startswith("darwin")):
+ elif sys.platform.startswith("darwin"):
host_subpath = "darwin-x86_64"
- elif (sys.platform.startswith('win')):
- if (platform.machine().endswith('64')):
+ elif sys.platform.startswith("win"):
+ if platform.machine().endswith("64"):
host_subpath = "windows-x86_64"
else:
- mt_link = False
host_subpath = "windows"
- if env["android_arch"] == "arm64v8":
- mt_link = False
-
compiler_path = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/" + host_subpath + "/bin"
gcc_toolchain_path = env["ANDROID_NDK_ROOT"] + "/toolchains/" + target_subpath + "/prebuilt/" + host_subpath
tools_path = gcc_toolchain_path + "/" + abi_subpath + "/bin"
# For Clang to find NDK tools in preference of those system-wide
- env.PrependENVPath('PATH', tools_path)
+ env.PrependENVPath("PATH", tools_path)
ccache_path = os.environ.get("CCACHE")
if ccache_path is None:
- env['CC'] = compiler_path + '/clang'
- env['CXX'] = compiler_path + '/clang++'
+ env["CC"] = compiler_path + "/clang"
+ env["CXX"] = compiler_path + "/clang++"
else:
# there aren't any ccache wrappers available for Android,
# to enable caching we need to prepend the path to the ccache binary
- env['CC'] = ccache_path + ' ' + compiler_path + '/clang'
- env['CXX'] = ccache_path + ' ' + compiler_path + '/clang++'
- env['AR'] = tools_path + "/ar"
- env['RANLIB'] = tools_path + "/ranlib"
- env['AS'] = tools_path + "/as"
+ env["CC"] = ccache_path + " " + compiler_path + "/clang"
+ env["CXX"] = ccache_path + " " + compiler_path + "/clang++"
+ env["AR"] = tools_path + "/ar"
+ env["RANLIB"] = tools_path + "/ranlib"
+ env["AS"] = tools_path + "/as"
- common_opts = ['-fno-integrated-as', '-gcc-toolchain', gcc_toolchain_path]
+ common_opts = ["-fno-integrated-as", "-gcc-toolchain", gcc_toolchain_path]
# Compile flags
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"])
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"])
- env.Append(CXXFLAGS=["-std=gnu++14"])
# Disable exceptions and rtti on non-tools (template) builds
- if env['tools']:
- env.Append(CXXFLAGS=['-frtti'])
+ if env["tools"]:
+ env.Append(CXXFLAGS=["-frtti"])
else:
- env.Append(CXXFLAGS=['-fno-rtti', '-fno-exceptions'])
+ env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"])
# Don't use dynamic_cast, necessary with no-rtti.
- env.Append(CPPDEFINES=['NO_SAFE_CAST'])
+ env.Append(CPPDEFINES=["NO_SAFE_CAST"])
- lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH']
+ lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env["ndk_platform"] + "/" + env["ARCH"]
# Using NDK unified headers (NDK r15+)
sysroot = env["ANDROID_NDK_ROOT"] + "/sysroot"
@@ -223,35 +230,40 @@ def configure(env):
env.Append(CPPFLAGS=["-isystem", sysroot + "/usr/include/" + abi_subpath])
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/android/support/include"])
# For unified headers this define has to be set manually
- env.Append(CPPDEFINES=[('__ANDROID_API__', str(get_platform(env['ndk_platform'])))])
-
- env.Append(CCFLAGS='-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing'.split())
- env.Append(CPPDEFINES=['NO_STATVFS', 'GLES_ENABLED'])
-
- env['neon_enabled'] = False
- if env['android_arch'] == 'x86':
- target_opts = ['-target', 'i686-none-linux-android']
+ env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))])
+
+ env.Append(
+ CCFLAGS=(
+ "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden"
+ " -fno-strict-aliasing".split()
+ )
+ )
+ env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"])
+
+ env["neon_enabled"] = False
+ if env["android_arch"] == "x86":
+ target_opts = ["-target", "i686-none-linux-android"]
# The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least
- env.Append(CCFLAGS=['-mstackrealign'])
+ env.Append(CCFLAGS=["-mstackrealign"])
- elif env['android_arch'] == 'x86_64':
- target_opts = ['-target', 'x86_64-none-linux-android']
+ elif env["android_arch"] == "x86_64":
+ target_opts = ["-target", "x86_64-none-linux-android"]
elif env["android_arch"] == "armv7":
- target_opts = ['-target', 'armv7-none-linux-androideabi']
- env.Append(CCFLAGS='-march=armv7-a -mfloat-abi=softfp'.split())
- env.Append(CPPDEFINES=['__ARM_ARCH_7__', '__ARM_ARCH_7A__'])
- if env['android_neon']:
- env['neon_enabled'] = True
- env.Append(CCFLAGS=['-mfpu=neon'])
- env.Append(CPPDEFINES=['__ARM_NEON__'])
+ target_opts = ["-target", "armv7-none-linux-androideabi"]
+ env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split())
+ env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
+ if env["android_neon"]:
+ env["neon_enabled"] = True
+ env.Append(CCFLAGS=["-mfpu=neon"])
+ env.Append(CPPDEFINES=["__ARM_NEON__"])
else:
- env.Append(CCFLAGS=['-mfpu=vfpv3-d16'])
+ env.Append(CCFLAGS=["-mfpu=vfpv3-d16"])
elif env["android_arch"] == "arm64v8":
- target_opts = ['-target', 'aarch64-none-linux-android']
- env.Append(CCFLAGS=['-mfix-cortex-a53-835769'])
- env.Append(CPPDEFINES=['__ARM_ARCH_8A__'])
+ target_opts = ["-target", "aarch64-none-linux-android"]
+ env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
+ env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
env.Append(CCFLAGS=target_opts)
env.Append(CCFLAGS=common_opts)
@@ -260,29 +272,55 @@ def configure(env):
ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"])
if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"):
- env.Append(LINKFLAGS=['-Wl,--exclude-libs,libgcc.a', '-Wl,--exclude-libs,libatomic.a', '-nostdlib++'])
+ env.Append(LINKFLAGS=["-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"])
else:
- env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a"])
- env.Append(LINKFLAGS=['-shared', '--sysroot=' + lib_sysroot, '-Wl,--warn-shared-textrel'])
+ env.Append(
+ LINKFLAGS=[
+ env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a"
+ ]
+ )
+ env.Append(LINKFLAGS=["-shared", "--sysroot=" + lib_sysroot, "-Wl,--warn-shared-textrel"])
env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/"])
- env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"])
+ env.Append(
+ LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"]
+ )
if env["android_arch"] == "armv7":
- env.Append(LINKFLAGS='-Wl,--fix-cortex-a8'.split())
- env.Append(LINKFLAGS='-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now'.split())
- env.Append(LINKFLAGS='-Wl,-soname,libgodot_android.so -Wl,--gc-sections'.split())
+ env.Append(LINKFLAGS="-Wl,--fix-cortex-a8".split())
+ env.Append(LINKFLAGS="-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now".split())
+ env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so -Wl,--gc-sections".split())
env.Append(LINKFLAGS=target_opts)
env.Append(LINKFLAGS=common_opts)
- env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + '/toolchains/' + target_subpath + '/prebuilt/' +
- host_subpath + '/lib/gcc/' + abi_subpath + '/4.9.x'])
- env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] +
- '/toolchains/' + target_subpath + '/prebuilt/' + host_subpath + '/' + abi_subpath + '/lib'])
-
- env.Prepend(CPPPATH=['#platform/android'])
- env.Append(CPPDEFINES=['ANDROID_ENABLED', 'UNIX_ENABLED', 'NO_FCNTL'])
- env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'GLESv2', 'android', 'log', 'z', 'dl'])
+ env.Append(
+ LIBPATH=[
+ env["ANDROID_NDK_ROOT"]
+ + "/toolchains/"
+ + target_subpath
+ + "/prebuilt/"
+ + host_subpath
+ + "/lib/gcc/"
+ + abi_subpath
+ + "/4.9.x"
+ ]
+ )
+ env.Append(
+ LIBPATH=[
+ env["ANDROID_NDK_ROOT"]
+ + "/toolchains/"
+ + target_subpath
+ + "/prebuilt/"
+ + host_subpath
+ + "/"
+ + abi_subpath
+ + "/lib"
+ ]
+ )
+
+ env.Prepend(CPPPATH=["#platform/android"])
+ env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "VULKAN_ENABLED", "NO_FCNTL"])
+ env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "vulkan", "android", "log", "z", "dl"])
# Return NDK version string in source.properties (adapted from the Chromium project).
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index f52b511522..ca312b427f 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -34,20 +34,18 @@
#include "string_android.h"
#include "thread_jandroid.h"
-jobject DirAccessJAndroid::io = NULL;
-jclass DirAccessJAndroid::cls = NULL;
-jmethodID DirAccessJAndroid::_dir_open = NULL;
-jmethodID DirAccessJAndroid::_dir_next = NULL;
-jmethodID DirAccessJAndroid::_dir_close = NULL;
-jmethodID DirAccessJAndroid::_dir_is_dir = NULL;
+jobject DirAccessJAndroid::io = nullptr;
+jclass DirAccessJAndroid::cls = nullptr;
+jmethodID DirAccessJAndroid::_dir_open = nullptr;
+jmethodID DirAccessJAndroid::_dir_next = nullptr;
+jmethodID DirAccessJAndroid::_dir_close = nullptr;
+jmethodID DirAccessJAndroid::_dir_is_dir = nullptr;
DirAccess *DirAccessJAndroid::create_fs() {
-
return memnew(DirAccessJAndroid);
}
Error DirAccessJAndroid::list_dir_begin() {
-
list_dir_end();
JNIEnv *env = ThreadAndroid::get_env();
@@ -62,7 +60,6 @@ Error DirAccessJAndroid::list_dir_begin() {
}
String DirAccessJAndroid::get_next() {
-
ERR_FAIL_COND_V(id == 0, "");
JNIEnv *env = ThreadAndroid::get_env();
@@ -76,19 +73,16 @@ String DirAccessJAndroid::get_next() {
}
bool DirAccessJAndroid::current_is_dir() const {
-
JNIEnv *env = ThreadAndroid::get_env();
return env->CallBooleanMethod(io, _dir_is_dir, id);
}
bool DirAccessJAndroid::current_is_hidden() const {
-
return current != "." && current != ".." && current.begins_with(".");
}
void DirAccessJAndroid::list_dir_end() {
-
if (id == 0)
return;
@@ -98,17 +92,14 @@ void DirAccessJAndroid::list_dir_end() {
}
int DirAccessJAndroid::get_drive_count() {
-
return 0;
}
String DirAccessJAndroid::get_drive(int p_drive) {
-
return "";
}
Error DirAccessJAndroid::change_dir(String p_dir) {
-
JNIEnv *env = ThreadAndroid::get_env();
if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == ""))
@@ -144,13 +135,11 @@ Error DirAccessJAndroid::change_dir(String p_dir) {
return OK;
}
-String DirAccessJAndroid::get_current_dir() {
-
+String DirAccessJAndroid::get_current_dir(bool p_include_drive) {
return "res://" + current_dir;
}
bool DirAccessJAndroid::file_exists(String p_file) {
-
String sd;
if (current_dir == "")
sd = p_file;
@@ -165,7 +154,6 @@ bool DirAccessJAndroid::file_exists(String p_file) {
}
bool DirAccessJAndroid::dir_exists(String p_dir) {
-
JNIEnv *env = ThreadAndroid::get_env();
String sd;
@@ -198,33 +186,27 @@ bool DirAccessJAndroid::dir_exists(String p_dir) {
}
Error DirAccessJAndroid::make_dir(String p_dir) {
-
ERR_FAIL_V(ERR_UNAVAILABLE);
}
Error DirAccessJAndroid::rename(String p_from, String p_to) {
-
ERR_FAIL_V(ERR_UNAVAILABLE);
}
Error DirAccessJAndroid::remove(String p_name) {
-
ERR_FAIL_V(ERR_UNAVAILABLE);
}
String DirAccessJAndroid::get_filesystem_type() const {
-
return "APK";
}
//FileType get_file_type() const;
size_t DirAccessJAndroid::get_space_left() {
-
return 0;
}
void DirAccessJAndroid::setup(jobject p_io) {
-
JNIEnv *env = ThreadAndroid::get_env();
io = p_io;
@@ -240,11 +222,9 @@ void DirAccessJAndroid::setup(jobject p_io) {
}
DirAccessJAndroid::DirAccessJAndroid() {
-
id = 0;
}
DirAccessJAndroid::~DirAccessJAndroid() {
-
list_dir_end();
}
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index caeb4b58b9..7d0def137a 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -36,7 +36,6 @@
#include <stdio.h>
class DirAccessJAndroid : public DirAccess {
-
//AAssetDir* aad;
static jobject io;
@@ -65,7 +64,7 @@ public:
virtual String get_drive(int p_drive);
virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success
- virtual String get_current_dir(); ///< return current dir location
+ virtual String get_current_dir(bool p_include_drive = true); ///< return current dir location
virtual bool file_exists(String p_file);
virtual bool dir_exists(String p_dir);
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
new file mode 100644
index 0000000000..3aa2fb5451
--- /dev/null
+++ b/platform/android/display_server_android.cpp
@@ -0,0 +1,697 @@
+/*************************************************************************/
+/* display_server_android.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_android.h"
+
+#include "android_keys_utils.h"
+#include "core/project_settings.h"
+#include "java_godot_io_wrapper.h"
+#include "java_godot_wrapper.h"
+#include "os_android.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/android/vulkan/vulkan_context_android.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#endif
+
+DisplayServerAndroid *DisplayServerAndroid::get_singleton() {
+ return (DisplayServerAndroid *)DisplayServer::get_singleton();
+}
+
+bool DisplayServerAndroid::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ //case FEATURE_CONSOLE_WINDOW:
+ //case FEATURE_CURSOR_SHAPE:
+ //case FEATURE_CUSTOM_CURSOR_SHAPE:
+ //case FEATURE_GLOBAL_MENU:
+ //case FEATURE_HIDPI:
+ //case FEATURE_ICON:
+ //case FEATURE_IME:
+ //case FEATURE_MOUSE:
+ //case FEATURE_MOUSE_WARP:
+ //case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_ICON:
+ //case FEATURE_NATIVE_VIDEO:
+ //case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerAndroid::get_name() const {
+ return "Android";
+}
+
+void DisplayServerAndroid::clipboard_set(const String &p_text) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND(!godot_java);
+
+ if (godot_java->has_set_clipboard()) {
+ godot_java->set_clipboard(p_text);
+ } else {
+ DisplayServer::clipboard_set(p_text);
+ }
+}
+
+String DisplayServerAndroid::clipboard_get() const {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND_V(!godot_java, String());
+
+ if (godot_java->has_get_clipboard()) {
+ return godot_java->get_clipboard();
+ } else {
+ return DisplayServer::clipboard_get();
+ }
+}
+
+void DisplayServerAndroid::screen_set_keep_on(bool p_enable) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND(!godot_java);
+
+ godot_java->set_keep_screen_on(p_enable);
+ keep_screen_on = p_enable;
+}
+
+bool DisplayServerAndroid::screen_is_kept_on() const {
+ return keep_screen_on;
+}
+
+void DisplayServerAndroid::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND(!godot_io_java);
+
+ godot_io_java->set_screen_orientation(p_orientation);
+}
+
+DisplayServer::ScreenOrientation DisplayServerAndroid::screen_get_orientation(int p_screen) const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND_V(!godot_io_java, SCREEN_LANDSCAPE);
+
+ return (ScreenOrientation)godot_io_java->get_screen_orientation();
+}
+
+int DisplayServerAndroid::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerAndroid::screen_get_position(int p_screen) const {
+ return Point2i(0, 0);
+}
+
+Size2i DisplayServerAndroid::screen_get_size(int p_screen) const {
+ return OS_Android::get_singleton()->get_display_size();
+}
+
+Rect2i DisplayServerAndroid::screen_get_usable_rect(int p_screen) const {
+ Size2i display_size = OS_Android::get_singleton()->get_display_size();
+ return Rect2i(0, 0, display_size.width, display_size.height);
+}
+
+int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND_V(!godot_io_java, 0);
+
+ return godot_io_java->get_screen_dpi();
+}
+
+bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
+ return true;
+}
+
+void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND(!godot_io_java);
+
+ if (godot_io_java->has_vk()) {
+ godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end);
+ } else {
+ ERR_PRINT("Virtual keyboard not available");
+ }
+}
+
+void DisplayServerAndroid::virtual_keyboard_hide() {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND(!godot_io_java);
+
+ if (godot_io_java->has_vk()) {
+ godot_io_java->hide_vk();
+ } else {
+ ERR_PRINT("Virtual keyboard not available");
+ }
+}
+
+int DisplayServerAndroid::virtual_keyboard_get_height() const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND_V(!godot_io_java, 0);
+
+ return godot_io_java->get_vk_height();
+}
+
+void DisplayServerAndroid::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
+ window_event_callback = p_callable;
+}
+
+void DisplayServerAndroid::window_set_input_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerAndroid::window_set_input_text_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
+ input_text_callback = p_callable;
+}
+
+void DisplayServerAndroid::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+ if (!p_callable.is_null()) {
+ const Variant *argp = &p_arg;
+ Variant ret;
+ Callable::CallError ce;
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ }
+}
+
+void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event) const {
+ _window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerAndroid::send_input_event(const Ref<InputEvent> &p_event) const {
+ _window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerAndroid::send_input_text(const String &p_text) const {
+ _window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerAndroid::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ DisplayServerAndroid::get_singleton()->send_input_event(p_event);
+}
+
+Vector<DisplayServer::WindowID> DisplayServerAndroid::get_window_list() const {
+ Vector<WindowID> ret;
+ ret.push_back(MAIN_WINDOW_ID);
+ return ret;
+}
+
+DisplayServer::WindowID DisplayServerAndroid::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerAndroid::window_attach_instance_id(ObjectID p_instance, DisplayServer::WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerAndroid::window_get_attached_instance_id(DisplayServer::WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerAndroid::window_set_title(const String &p_title, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+int DisplayServerAndroid::window_get_current_screen(DisplayServer::WindowID p_window) const {
+ return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerAndroid::window_set_current_screen(int p_screen, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+Point2i DisplayServerAndroid::window_get_position(DisplayServer::WindowID p_window) const {
+ return Point2i();
+}
+
+void DisplayServerAndroid::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+void DisplayServerAndroid::window_set_transient(DisplayServer::WindowID p_window, DisplayServer::WindowID p_parent) {
+ // Not supported on Android.
+}
+
+void DisplayServerAndroid::window_set_max_size(const Size2i p_size, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+Size2i DisplayServerAndroid::window_get_max_size(DisplayServer::WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerAndroid::window_set_min_size(const Size2i p_size, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+Size2i DisplayServerAndroid::window_get_min_size(DisplayServer::WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerAndroid::window_set_size(const Size2i p_size, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+Size2i DisplayServerAndroid::window_get_size(DisplayServer::WindowID p_window) const {
+ return OS_Android::get_singleton()->get_display_size();
+}
+
+Size2i DisplayServerAndroid::window_get_real_size(DisplayServer::WindowID p_window) const {
+ return OS_Android::get_singleton()->get_display_size();
+}
+
+void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const {
+ return WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerAndroid::window_set_flag(DisplayServer::WindowFlags p_flag, bool p_enabled, DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+bool DisplayServerAndroid::window_get_flag(DisplayServer::WindowFlags p_flag, DisplayServer::WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerAndroid::window_request_attention(DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+void DisplayServerAndroid::window_move_to_foreground(DisplayServer::WindowID p_window) {
+ // Not supported on Android.
+}
+
+bool DisplayServerAndroid::window_can_draw(DisplayServer::WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerAndroid::can_any_window_draw() const {
+ return true;
+}
+
+void DisplayServerAndroid::alert(const String &p_alert, const String &p_title) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND(!godot_java);
+
+ godot_java->alert(p_alert, p_title);
+}
+
+void DisplayServerAndroid::process_events() {
+ // Nothing to do
+}
+
+Vector<String> DisplayServerAndroid::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#ifdef OPENGL_ENABLED
+ drivers.push_back("opengl");
+#endif
+#ifdef VULKAN_ENABLED
+ drivers.push_back("vulkan");
+#endif
+
+ return drivers;
+}
+
+DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ds->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver");
+ }
+ return ds;
+}
+
+void DisplayServerAndroid::register_android_driver() {
+ register_create_function("android", create_func, get_rendering_drivers_func);
+}
+
+void DisplayServerAndroid::reset_window() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window();
+ ERR_FAIL_COND(!native_window);
+
+ ERR_FAIL_COND(!context_vulkan);
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+
+ Size2i display_size = OS_Android::get_singleton()->get_display_size();
+ if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to reset Vulkan window.");
+ }
+ }
+#endif
+}
+
+DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ rendering_driver = p_rendering_driver;
+
+ // TODO: rendering_driver is broken, change when different drivers are supported again
+ rendering_driver = "vulkan";
+
+ keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on");
+
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl") {
+ bool gl_initialization_error = false;
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n"
+ "Please try updating your Android version.",
+ "Unable to initialize video driver");
+ return;
+ }
+ }
+#endif
+
+#if defined(VULKAN_ENABLED)
+ context_vulkan = nullptr;
+ rendering_device_vulkan = nullptr;
+
+ if (rendering_driver == "vulkan") {
+ ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window();
+ ERR_FAIL_COND(!native_window);
+
+ context_vulkan = memnew(VulkanContextAndroid);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to initialize Vulkan context");
+ }
+
+ Size2i display_size = OS_Android::get_singleton()->get_display_size();
+ if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to create Vulkan window.");
+ }
+
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+}
+
+DisplayServerAndroid::~DisplayServerAndroid() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ }
+
+ if (context_vulkan) {
+ memdelete(context_vulkan);
+ }
+ }
+#endif
+}
+
+void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p_event) {
+ switch (p_event.type) {
+ case JOY_EVENT_BUTTON:
+ Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed);
+ break;
+ case JOY_EVENT_AXIS:
+ Input::JoyAxis value;
+ value.min = -1;
+ value.value = p_event.value;
+ Input::get_singleton()->joy_axis(p_event.device, p_event.index, value);
+ break;
+ case JOY_EVENT_HAT:
+ Input::get_singleton()->joy_hat(p_event.device, p_event.hat);
+ break;
+ default:
+ return;
+ }
+}
+
+void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) {
+ ev->set_shift(shift_mem);
+ ev->set_alt(alt_mem);
+ ev->set_metakey(meta_mem);
+ ev->set_control(control_mem);
+}
+
+void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ int val = p_unicode_char;
+ int keycode = android_get_keysym(p_keycode);
+ int phy_keycode = android_get_keysym(p_scancode);
+
+ if (keycode == KEY_SHIFT) {
+ shift_mem = p_pressed;
+ }
+ if (keycode == KEY_ALT) {
+ alt_mem = p_pressed;
+ }
+ if (keycode == KEY_CONTROL) {
+ control_mem = p_pressed;
+ }
+ if (keycode == KEY_META) {
+ meta_mem = p_pressed;
+ }
+
+ ev->set_keycode(keycode);
+ ev->set_physical_keycode(phy_keycode);
+ ev->set_unicode(val);
+ ev->set_pressed(p_pressed);
+
+ _set_key_modifier_state(ev);
+
+ if (val == '\n') {
+ ev->set_keycode(KEY_ENTER);
+ } else if (val == 61448) {
+ ev->set_keycode(KEY_BACKSPACE);
+ ev->set_unicode(KEY_BACKSPACE);
+ } else if (val == 61453) {
+ ev->set_keycode(KEY_ENTER);
+ ev->set_unicode(KEY_ENTER);
+ } else if (p_keycode == 4) {
+ OS_Android::get_singleton()->main_loop_request_go_back();
+ }
+
+ Input::get_singleton()->parse_input_event(ev);
+}
+
+void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) {
+ switch (p_what) {
+ case 0: { //gesture begin
+ if (touch.size()) {
+ //end all if exist
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ }
+
+ touch.resize(p_points.size());
+ for (int i = 0; i < p_points.size(); i++) {
+ touch.write[i].id = p_points[i].id;
+ touch.write[i].pos = p_points[i].pos;
+ }
+
+ //send touch
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(true);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+
+ } break;
+ case 1: { //motion
+ ERR_FAIL_COND(touch.size() != p_points.size());
+
+ for (int i = 0; i < touch.size(); i++) {
+ int idx = -1;
+ for (int j = 0; j < p_points.size(); j++) {
+ if (touch[i].id == p_points[j].id) {
+ idx = j;
+ break;
+ }
+ }
+
+ ERR_CONTINUE(idx == -1);
+
+ if (touch[i].pos == p_points[idx].pos)
+ continue; //no move unncesearily
+
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ ev->set_index(touch[i].id);
+ ev->set_position(p_points[idx].pos);
+ ev->set_relative(p_points[idx].pos - touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ touch.write[i].pos = p_points[idx].pos;
+ }
+
+ } break;
+ case 2: { //release
+ if (touch.size()) {
+ //end all if exist
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ touch.clear();
+ }
+ } break;
+ case 3: { // add touch
+ for (int i = 0; i < p_points.size(); i++) {
+ if (p_points[i].id == p_pointer) {
+ TouchPos tp = p_points[i];
+ touch.push_back(tp);
+
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+
+ ev->set_index(tp.id);
+ ev->set_pressed(true);
+ ev->set_position(tp.pos);
+ Input::get_singleton()->parse_input_event(ev);
+
+ break;
+ }
+ }
+ } break;
+ case 4: { // remove touch
+ for (int i = 0; i < touch.size(); i++) {
+ if (touch[i].id == p_pointer) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ touch.remove(i);
+
+ break;
+ }
+ }
+ } break;
+ }
+}
+
+void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) {
+ // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER
+ switch (p_type) {
+ case 7: // hover move
+ case 9: // hover enter
+ case 10: { // hover exit
+ Ref<InputEventMouseMotion> ev;
+ ev.instance();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_global_position(p_pos);
+ ev->set_relative(p_pos - hover_prev_pos);
+ Input::get_singleton()->parse_input_event(ev);
+ hover_prev_pos = p_pos;
+ } break;
+ }
+}
+
+void DisplayServerAndroid::process_double_tap(Point2 p_pos) {
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_global_position(p_pos);
+ ev->set_pressed(false);
+ ev->set_doubleclick(true);
+ Input::get_singleton()->parse_input_event(ev);
+}
+
+void DisplayServerAndroid::process_scroll(Point2 p_pos) {
+ Ref<InputEventPanGesture> ev;
+ ev.instance();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_delta(p_pos - scroll_prev_pos);
+ Input::get_singleton()->parse_input_event(ev);
+ scroll_prev_pos = p_pos;
+}
+
+void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) {
+ Input::get_singleton()->set_accelerometer(p_accelerometer);
+}
+
+void DisplayServerAndroid::process_gravity(const Vector3 &p_gravity) {
+ Input::get_singleton()->set_gravity(p_gravity);
+}
+
+void DisplayServerAndroid::process_magnetometer(const Vector3 &p_magnetometer) {
+ Input::get_singleton()->set_magnetometer(p_magnetometer);
+}
+
+void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) {
+ Input::get_singleton()->set_gyroscope(p_gyroscope);
+}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
new file mode 100644
index 0000000000..5cdc69ee83
--- /dev/null
+++ b/platform/android/display_server_android.h
@@ -0,0 +1,182 @@
+/*************************************************************************/
+/* display_server_android.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_ANDROID_H
+#define DISPLAY_SERVER_ANDROID_H
+
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+class VulkanContextAndroid;
+class RenderingDeviceVulkan;
+#endif
+
+class DisplayServerAndroid : public DisplayServer {
+public:
+ struct TouchPos {
+ int id;
+ Point2 pos;
+ };
+
+ enum {
+ JOY_EVENT_BUTTON = 0,
+ JOY_EVENT_AXIS = 1,
+ JOY_EVENT_HAT = 2
+ };
+
+ struct JoypadEvent {
+ int device;
+ int type;
+ int index;
+ bool pressed;
+ float value;
+ int hat;
+ };
+
+private:
+ String rendering_driver;
+
+ bool alt_mem = false;
+ bool shift_mem = false;
+ bool control_mem = false;
+ bool meta_mem = false;
+
+ bool keep_screen_on;
+
+ Vector<TouchPos> touch;
+ Point2 hover_prev_pos; // needed to calculate the relative position on hover events
+ Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextAndroid *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ ObjectID window_attached_instance_id;
+
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+
+ void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+
+ void _set_key_modifier_state(Ref<InputEventWithModifiers> ev);
+
+public:
+ static DisplayServerAndroid *get_singleton();
+
+ virtual bool has_feature(Feature p_feature) const;
+ virtual String get_name() const;
+
+ virtual void clipboard_set(const String &p_text);
+ virtual String clipboard_get() const;
+
+ virtual void screen_set_keep_on(bool p_enable);
+ virtual bool screen_is_kept_on() const;
+
+ virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW);
+ virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+ virtual int get_screen_count() const;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void virtual_keyboard_hide();
+ virtual int virtual_keyboard_get_height() const;
+
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+ void send_window_event(WindowEvent p_event) const;
+ void send_input_event(const Ref<InputEvent> &p_event) const;
+ void send_input_text(const String &p_text) const;
+
+ virtual Vector<WindowID> get_window_list() const;
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent);
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual bool can_any_window_draw() const;
+
+ virtual void alert(const String &p_alert, const String &p_title);
+
+ virtual void process_events();
+
+ void process_accelerometer(const Vector3 &p_accelerometer);
+ void process_gravity(const Vector3 &p_gravity);
+ void process_magnetometer(const Vector3 &p_magnetometer);
+ void process_gyroscope(const Vector3 &p_gyroscope);
+ void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points);
+ void process_hover(int p_type, Point2 p_pos);
+ void process_double_tap(Point2 p_pos);
+ void process_scroll(Point2 p_pos);
+ void process_joy_event(JoypadEvent p_event);
+ void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed);
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+ static void register_android_driver();
+
+ void reset_window();
+
+ DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerAndroid();
+};
+
+#endif // DISPLAY_SERVER_ANDROID_H
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index b9968c08aa..9a144c0a78 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "export.h"
+#include "gradle_export_util.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
@@ -43,7 +44,9 @@
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "platform/android/export/gradle_export_util.h"
#include "platform/android/logo.gen.h"
+#include "platform/android/plugin/godot_plugin_config.h"
#include "platform/android/run_icon.gen.h"
#include <string.h>
@@ -194,7 +197,7 @@ static const char *android_perms[] = {
"WRITE_SOCIAL_STREAM",
"WRITE_SYNC_SETTINGS",
"WRITE_USER_DICTIONARY",
- NULL
+ nullptr
};
struct LauncherIcon {
@@ -235,14 +238,12 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun
};
class EditorExportPlatformAndroid : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform);
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
struct Device {
-
String id;
String name;
String description;
@@ -250,55 +251,79 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
};
struct APKExportData {
-
zipFile apk;
EditorProgress *ep;
};
+ Vector<PluginConfig> plugins;
+ String last_plugin_names;
+ uint64_t last_custom_build_time = 0;
+ volatile bool plugins_changed;
+ Mutex plugins_lock;
Vector<Device> devices;
volatile bool devices_changed;
- Mutex *device_lock;
- Thread *device_thread;
+ Mutex device_lock;
+ Thread *check_for_changes_thread;
volatile bool quit_request;
- static void _device_poll_thread(void *ud) {
-
+ static void _check_for_changes_poll_thread(void *ud) {
EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
while (!ea->quit_request) {
+ // Check for plugins updates
+ {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed) {
+ Vector<PluginConfig> loaded_plugins = get_plugins();
+
+ MutexLock lock(ea->plugins_lock);
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed = true;
+ } else {
+ for (int i = 0; i < ea->plugins.size(); i++) {
+ if (ea->plugins[i].name != loaded_plugins[i].name) {
+ ea->plugins_changed = true;
+ break;
+ }
+ }
+ }
+
+ if (ea->plugins_changed) {
+ ea->plugins = loaded_plugins;
+ }
+ }
+ }
+ // Check for devices updates
String adb = EditorSettings::get_singleton()->get("export/android/adb");
if (FileAccess::exists(adb)) {
-
String devices;
List<String> args;
args.push_back("devices");
int ec;
- OS::get_singleton()->execute(adb, args, true, NULL, &devices, &ec);
+ OS::get_singleton()->execute(adb, args, true, nullptr, &devices, &ec);
Vector<String> ds = devices.split("\n");
Vector<String> ldevices;
for (int i = 1; i < ds.size(); i++) {
-
String d = ds[i];
int dpos = d.find("device");
- if (dpos == -1)
+ if (dpos == -1) {
continue;
+ }
d = d.substr(0, dpos).strip_edges();
ldevices.push_back(d);
}
- ea->device_lock->lock();
+ MutexLock lock(ea->device_lock);
bool different = false;
if (ea->devices.size() != ldevices.size()) {
-
different = true;
} else {
-
for (int i = 0; i < ea->devices.size(); i++) {
-
if (ea->devices[i].id != ldevices[i]) {
different = true;
break;
@@ -307,11 +332,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
if (different) {
-
Vector<Device> ndevices;
for (int i = 0; i < ldevices.size(); i++) {
-
Device d;
d.id = ldevices[i];
for (int j = 0; j < ea->devices.size(); j++) {
@@ -332,15 +355,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int ec2;
String dp;
- OS::get_singleton()->execute(adb, args, true, NULL, &dp, &ec2);
+ OS::get_singleton()->execute(adb, args, true, nullptr, &dp, &ec2);
Vector<String> props = dp.split("\n");
String vendor;
String device;
- d.description + "Device ID: " + d.id + "\n";
+ d.description = "Device ID: " + d.id + "\n";
d.api_level = 0;
for (int j = 0; j < props.size(); j++) {
-
// got information by `shell cat /system/build.prop` before and its format is "property=value"
// it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above
// its format is "[property]: [value]" so changed it as like build.prop
@@ -372,7 +394,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
d.name = vendor + " " + device;
- if (device == String()) continue;
+ if (device == String()) {
+ continue;
+ }
}
ndevices.push_back(d);
@@ -381,17 +405,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ea->devices = ndevices;
ea->devices_changed = true;
}
-
- ea->device_lock->unlock();
}
- uint64_t sleep = OS::get_singleton()->get_power_state() == OS::POWERSTATE_ON_BATTERY ? 1000 : 100;
+ uint64_t sleep = 200;
uint64_t wait = 3000000;
uint64_t time = OS::get_singleton()->get_ticks_usec();
while (OS::get_singleton()->get_ticks_usec() - time < wait) {
OS::get_singleton()->delay_usec(1000 * sleep);
- if (ea->quit_request)
+ if (ea->quit_request) {
break;
+ }
}
}
@@ -408,7 +431,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
String get_project_name(const String &p_name) const {
-
String aname;
if (p_name != "") {
aname = p_name;
@@ -424,7 +446,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
String get_package_name(const String &p_package) const {
-
String pname = p_package;
String basename = ProjectSettings::get_singleton()->get("application/config/name");
basename = basename.to_lower();
@@ -432,7 +453,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String name;
bool first = true;
for (int i = 0; i < basename.length(); i++) {
- CharType c = basename[i];
+ char32_t c = basename[i];
if (c >= '0' && c <= '9' && first) {
continue;
}
@@ -441,16 +462,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
first = false;
}
}
- if (name == "")
+ if (name == "") {
name = "noname";
+ }
pname = pname.replace("$genname", name);
return pname;
}
- bool is_package_name_valid(const String &p_package, String *r_error = NULL) const {
-
+ bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
String pname = p_package;
if (pname.length() == 0) {
@@ -463,7 +484,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int segments = 0;
bool first = true;
for (int i = 0; i < pname.length(); i++) {
- CharType c = pname[i];
+ char32_t c = pname[i];
if (first && c == '.') {
if (r_error) {
*r_error = TTR("Package segments must be of non-zero length.");
@@ -514,7 +535,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
-
/*
* By not compressing files with little or not benefit in doing so,
* a performance gain is expected attime. Moreover, if the APK is
@@ -539,7 +559,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
".scn", // Binary scenes are usually already compressed
".stex", // Streamable textures are usually already compressed
// Trailer for easier processing
- NULL
+ nullptr
};
for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
@@ -561,7 +581,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
static zip_fileinfo get_zip_fileinfo() {
-
OS::Time time = OS::get_singleton()->get_time();
OS::Date date = OS::get_singleton()->get_date();
@@ -588,16 +607,83 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return abis;
}
+ /// List the gdap files in the directory specified by the p_path parameter.
+ static Vector<String> list_gdap_files(const String &p_path) {
+ Vector<String> dir_files;
+ DirAccessRef da = DirAccess::open(p_path);
+ if (da) {
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+
+ if (da->current_is_dir() || da->current_is_hidden()) {
+ continue;
+ }
+
+ if (file.ends_with(PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+ }
+
+ static Vector<PluginConfig> get_plugins() {
+ Vector<PluginConfig> loaded_plugins;
+
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
+
+ // Add the prebuilt plugins
+ loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir));
+
+ if (DirAccess::exists(plugins_dir)) {
+ Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
+
+ if (!plugins_filenames.empty()) {
+ Ref<ConfigFile> config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+ if (config.valid_config) {
+ loaded_plugins.push_back(config);
+ } else {
+ print_error("Invalid plugin config file " + plugins_filenames[i]);
+ }
+ }
+ }
+ }
+
+ return loaded_plugins;
+ }
+
+ static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+ Vector<PluginConfig> enabled_plugins;
+ Vector<PluginConfig> all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfig plugin = all_plugins[i];
+ bool enabled = p_presets->get("plugins/" + plugin.name);
+ if (enabled) {
+ enabled_plugins.push_back(plugin);
+ }
+ }
+
+ return enabled_plugins;
+ }
+
static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) {
zip_fileinfo zipfi = get_zip_fileinfo();
zipOpenNewFileInZip(ed->apk,
p_path.utf8().get_data(),
&zipfi,
- NULL,
+ nullptr,
0,
- NULL,
+ nullptr,
0,
- NULL,
+ nullptr,
compression_method,
Z_DEFAULT_COMPRESSION);
@@ -610,7 +696,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
static Error save_apk_so(void *p_userdata, const SharedObject &p_so) {
if (!p_so.path.get_file().begins_with("lib")) {
String err = "Android .so file names must start with \"lib\", but got: " + p_so.path;
- ERR_PRINTS(err);
+ ERR_PRINT(err);
return FAILED;
}
APKExportData *ed = (APKExportData *)p_userdata;
@@ -631,29 +717,82 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
if (!exported) {
String abis_string = String(" ").join(abis);
String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string;
- ERR_PRINTS(err);
+ ERR_PRINT(err);
return FAILED;
}
return OK;
}
- static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+ static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
APKExportData *ed = (APKExportData *)p_userdata;
String dst_path = p_path.replace_first("res://", "assets/");
store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0);
- if (ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total)) {
- return ERR_SKIP;
- }
return OK;
}
- static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+ static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
return OK;
}
- void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
+ void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
+ const char **aperms = android_perms;
+ while (*aperms) {
+ bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower());
+ if (enabled) {
+ r_permissions.push_back("android.permission." + String(*aperms));
+ }
+ aperms++;
+ }
+ PackedStringArray user_perms = p_preset->get("permissions/custom_permissions");
+ for (int i = 0; i < user_perms.size(); i++) {
+ String user_perm = user_perms[i].strip_edges();
+ if (!user_perm.empty()) {
+ r_permissions.push_back(user_perm);
+ }
+ }
+ if (p_give_internet) {
+ if (r_permissions.find("android.permission.INTERNET") == -1) {
+ r_permissions.push_back("android.permission.INTERNET");
+ }
+ }
+
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ if (xr_mode_index == 1 /* XRMode.OVR */) {
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index > 0) {
+ if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) {
+ r_permissions.push_back("com.oculus.permission.HAND_TRACKING");
+ }
+ }
+ }
+ }
+
+ void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
+ String manifest_text =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\">\n";
+
+ manifest_text += _get_screen_sizes_tag(p_preset);
+ manifest_text += _get_gles_tag();
+ Vector<String> perms;
+ _get_permissions(p_preset, p_give_internet, perms);
+ for (int i = 0; i < perms.size(); i++) {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ }
+
+ manifest_text += _get_xr_features_tag(p_preset);
+ manifest_text += _get_instrumentation_tag(p_preset);
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
+ manifest_text += _get_application_tag(p_preset, plugins_names);
+ manifest_text += "</manifest>\n";
+ String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
+ store_string_at_path(manifest_path, manifest_text);
+ }
+
+ void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
// Leaving the unused types commented because looking these constants up
// again later would be annoying
// const int CHUNK_AXML_FILE = 0x00080003;
@@ -686,49 +825,26 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int orientation = p_preset->get("screen/orientation");
- bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
- !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
bool screen_support_small = p_preset->get("screen/support_small");
bool screen_support_normal = p_preset->get("screen/support_normal");
bool screen_support_large = p_preset->get("screen/support_large");
bool screen_support_xlarge = p_preset->get("screen/support_xlarge");
int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ bool focus_awareness = p_preset->get("xr_features/focus_awareness");
- Vector<String> perms;
-
- const char **aperms = android_perms;
- while (*aperms) {
-
- bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower());
- if (enabled)
- perms.push_back("android.permission." + String(*aperms));
- aperms++;
- }
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
- PoolStringArray user_perms = p_preset->get("permissions/custom_permissions");
-
- for (int i = 0; i < user_perms.size(); i++) {
- String user_perm = user_perms[i].strip_edges();
- if (!user_perm.empty()) {
- perms.push_back(user_perm);
- }
- }
-
- if (p_give_internet) {
- if (perms.find("android.permission.INTERNET") == -1)
- perms.push_back("android.permission.INTERNET");
- }
+ Vector<String> perms;
+ // Write permissions into the perms variable.
+ _get_permissions(p_preset, p_give_internet, perms);
while (ofs < (uint32_t)p_manifest.size()) {
-
uint32_t chunk = decode_uint32(&p_manifest[ofs]);
uint32_t size = decode_uint32(&p_manifest[ofs + 4]);
switch (chunk) {
-
case CHUNK_STRINGS: {
-
int iofs = ofs + 8;
string_count = decode_uint32(&p_manifest[iofs]);
@@ -749,17 +865,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
string_table_begins = st_offset;
for (uint32_t i = 0; i < string_count; i++) {
-
uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]);
string_at += st_offset + string_count * 4;
ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table.");
if (string_flags & UTF8_FLAG) {
-
} else {
uint32_t len = decode_uint16(&p_manifest[string_at]);
- Vector<CharType> ucstring;
+ Vector<char32_t> ucstring;
ucstring.resize(len + 1);
for (uint32_t j = 0; j < len; j++) {
uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]);
@@ -779,13 +893,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
} break;
case CHUNK_XML_START_TAG: {
-
int iofs = ofs + 8;
uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
String tname = string_table[name];
uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]);
iofs += 28;
+ bool is_focus_aware_metadata = false;
for (uint32_t i = 0; i < attrcount; i++) {
uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]);
@@ -809,8 +923,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
if (tname == "manifest" && attrname == "versionName") {
if (attr_value == 0xFFFFFFFF) {
WARN_PRINT("Version name in a resource, should be plain text");
- } else
+ } else {
string_table.write[attr_value] = version_name;
+ }
}
if (tname == "instrumentation" && attrname == "targetPackage") {
@@ -818,35 +933,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
if (tname == "activity" && attrname == "screenOrientation") {
-
encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]);
}
if (tname == "supports-screens") {
-
if (attrname == "smallScreens") {
-
encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
} else if (attrname == "normalScreens") {
-
encode_uint32(screen_support_normal ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
} else if (attrname == "largeScreens") {
-
encode_uint32(screen_support_large ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
} else if (attrname == "xlargeScreens") {
-
encode_uint32(screen_support_xlarge ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
}
}
- if (tname == "uses-feature" && attrname == "glEsVersion") {
-
- encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]);
- }
-
// FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553,
// but the issue should be debugged further and properly addressed.
if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") {
@@ -863,6 +967,17 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
+ if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) {
+ // Update the focus awareness meta-data value
+ encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) {
+ // Update the meta-data 'android:value' attribute with the list of enabled plugins.
+ string_table.write[attr_value] = plugins_names;
+ }
+
+ is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware";
iofs += 20;
}
@@ -893,10 +1008,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
feature_names.push_back("oculus.software.handtracking");
feature_required_list.push_back(hand_tracking_index == 2);
feature_versions.push_back(-1); // no version attribute should be added.
-
- if (perms.find("oculus.permission.handtracking") == -1) {
- perms.push_back("oculus.permission.handtracking");
- }
}
}
@@ -1042,7 +1153,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
if (tname == "manifest") {
-
// save manifest ending so we can restore it
Vector<uint8_t> manifest_end;
uint32_t manifest_cur_size = p_manifest.size();
@@ -1128,13 +1238,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ret.resize(string_table_begins + string_table.size() * 4);
for (uint32_t i = 0; i < string_table_begins; i++) {
-
ret.write[i] = p_manifest[i];
}
ofs = 0;
for (int i = 0; i < string_table.size(); i++) {
-
encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
ofs += string_table[i].length() * 2 + 2 + 2;
}
@@ -1143,7 +1251,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
string_data_offset = ret.size() - ofs;
uint8_t *chars = &ret.write[string_data_offset];
for (int i = 0; i < string_table.size(); i++) {
-
String s = string_table[i];
encode_uint16(s.length(), chars);
chars += 2;
@@ -1160,18 +1267,21 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
//pad
- while (ret.size() % 4)
+ while (ret.size() % 4) {
ret.push_back(0);
+ }
uint32_t new_stable_end = ret.size();
uint32_t extra = (p_manifest.size() - string_table_ends);
ret.resize(new_stable_end + extra);
- for (uint32_t i = 0; i < extra; i++)
+ for (uint32_t i = 0; i < extra; i++) {
ret.write[new_stable_end + i] = p_manifest[string_table_ends + i];
+ }
- while (ret.size() % 4)
+ while (ret.size() % 4) {
ret.push_back(0);
+ }
encode_uint32(ret.size(), &ret.write[4]); //update new file size
encode_uint32(new_stable_end - 8, &ret.write[12]); //update new string table size
@@ -1182,19 +1292,36 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
static String _parse_string(const uint8_t *p_bytes, bool p_utf8) {
-
uint32_t offset = 0;
- uint32_t len = decode_uint16(&p_bytes[offset]);
+ uint32_t len = 0;
if (p_utf8) {
- //don't know how to read extended utf8, this will have to be for now
- len >>= 8;
+ uint8_t byte = p_bytes[offset];
+ if (byte & 0x80) {
+ offset += 2;
+ } else {
+ offset += 1;
+ }
+ byte = p_bytes[offset];
+ offset++;
+ if (byte & 0x80) {
+ len = byte & 0x7F;
+ len = (len << 8) + p_bytes[offset];
+ offset++;
+ } else {
+ len = byte;
+ }
+ } else {
+ len = decode_uint16(&p_bytes[offset]);
+ offset += 2;
+ if (len & 0x8000) {
+ len &= 0x7FFF;
+ len = (len << 16) + decode_uint16(&p_bytes[offset]);
+ offset += 2;
+ }
}
- offset += 2;
- //printf("len %i, unicode: %i\n",len,int(p_utf8));
if (p_utf8) {
-
Vector<uint8_t> str8;
str8.resize(len + 1);
for (uint32_t i = 0; i < len; i++) {
@@ -1205,24 +1332,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
str.parse_utf8((const char *)str8.ptr());
return str;
} else {
-
String str;
for (uint32_t i = 0; i < len; i++) {
- CharType c = decode_uint16(&p_bytes[offset + i * 2]);
- if (c == 0)
+ char32_t c = decode_uint16(&p_bytes[offset + i * 2]);
+ if (c == 0) {
break;
+ }
str += String::chr(c);
}
return str;
}
}
- void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest) {
+ void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest) {
const int UTF8_FLAG = 0x00000100;
- uint32_t string_block_len = decode_uint32(&p_manifest[16]);
- uint32_t string_count = decode_uint32(&p_manifest[20]);
- uint32_t string_flags = decode_uint32(&p_manifest[28]);
+ uint32_t string_block_len = decode_uint32(&r_manifest[16]);
+ uint32_t string_count = decode_uint32(&r_manifest[20]);
+ uint32_t string_flags = decode_uint32(&r_manifest[28]);
const uint32_t string_table_begins = 40;
Vector<String> string_table;
@@ -1230,21 +1357,18 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String package_name = p_preset->get("package/name");
for (uint32_t i = 0; i < string_count; i++) {
-
- uint32_t offset = decode_uint32(&p_manifest[string_table_begins + i * 4]);
+ uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
offset += string_table_begins + string_count * 4;
- String str = _parse_string(&p_manifest[offset], string_flags & UTF8_FLAG);
+ String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG);
if (str.begins_with("godot-project-name")) {
-
if (str == "godot-project-name") {
//project name
str = get_project_name(package_name);
} else {
-
- String lang = str.substr(str.find_last("-") + 1, str.length()).replace("-", "_");
+ String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_");
String prop = "application/config/name_" + lang;
if (ProjectSettings::get_singleton()->has_setting(prop)) {
str = ProjectSettings::get_singleton()->get(prop);
@@ -1262,13 +1386,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ret.resize(string_table_begins + string_table.size() * 4);
for (uint32_t i = 0; i < string_table_begins; i++) {
-
- ret.write[i] = p_manifest[i];
+ ret.write[i] = r_manifest[i];
}
int ofs = 0;
for (int i = 0; i < string_table.size(); i++) {
-
encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
ofs += string_table[i].length() * 2 + 2 + 2;
}
@@ -1276,7 +1398,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ret.resize(ret.size() + ofs);
uint8_t *chars = &ret.write[ret.size() - ofs];
for (int i = 0; i < string_table.size(); i++) {
-
String s = string_table[i];
encode_uint16(s.length(), chars);
chars += 2;
@@ -1289,8 +1410,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
//pad
- while (ret.size() % 4)
+ while (ret.size() % 4) {
ret.push_back(0);
+ }
//change flags to not use utf8
encode_uint32(string_flags & ~0x100, &ret.write[28]);
@@ -1299,35 +1421,93 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
//append the rest...
int rest_from = 12 + string_block_len;
int rest_to = ret.size();
- int rest_len = (p_manifest.size() - rest_from);
- ret.resize(ret.size() + (p_manifest.size() - rest_from));
+ int rest_len = (r_manifest.size() - rest_from);
+ ret.resize(ret.size() + (r_manifest.size() - rest_from));
for (int i = 0; i < rest_len; i++) {
- ret.write[rest_to + i] = p_manifest[rest_from + i];
+ ret.write[rest_to + i] = r_manifest[rest_from + i];
}
//finally update the size
encode_uint32(ret.size(), &ret.write[4]);
- p_manifest = ret;
+ r_manifest = ret;
//printf("end\n");
}
- void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) {
- if (p_processing_file_name == p_icon.export_path) {
- Ref<Image> working_image = p_source_image;
+ void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) {
+ Ref<Image> working_image = p_source_image;
+
+ if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) {
+ working_image = p_source_image->duplicate();
+ working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS);
+ }
+
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png.";
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+ }
+
+ void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
+ String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+
+ icon.instance();
+ foreground.instance();
+ background.instance();
- if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) {
- working_image = p_source_image->duplicate();
- working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS);
+ // Regular icon: user selection -> project icon -> default.
+ String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, icon) != OK) {
+ ImageLoader::load_image(project_icon_path, icon);
+ }
+
+ // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, foreground) != OK) {
+ foreground = icon;
+ }
+
+ // Adaptive background: user selection -> default.
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
+ if (!path.empty()) {
+ ImageLoader::load_image(path, background);
+ }
+ }
+
+ void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) {
+ String img_path = launcher_icon.export_path;
+ img_path = img_path.insert(0, "res://android/build/");
+ store_file_at_path(img_path, data);
+ }
+
+ void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &main_image,
+ const Ref<Image> &foreground, const Ref<Image> &background) {
+ // Prepare images to be resized for the icons. If some image ends up being uninitialized,
+ // the default image from the export template will be used.
+
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data);
+ store_image(launcher_icons[i], data);
}
- PoolVector<uint8_t> png_buffer;
- Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
- if (err == OK) {
- p_data.resize(png_buffer.size());
- memcpy(p_data.ptrw(), png_buffer.read().ptr(), p_data.size());
- } else {
- String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png.";
- WARN_PRINT(err_str.utf8().get_data());
+ if (foreground.is_valid() && !foreground->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground,
+ launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_foregrounds[i], data);
+ }
+
+ if (background.is_valid() && !background->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background,
+ launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_backgrounds[i], data);
}
}
}
@@ -1345,19 +1525,17 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
public:
- typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+ typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
public:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
-
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override {
String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
if (driver == "GLES2") {
r_features->push_back("etc");
- } else if (driver == "GLES3") {
+ }
+ // FIXME: Review what texture formats are used for Vulkan.
+ if (driver == "Vulkan") {
r_features->push_back("etc2");
- if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) {
- r_features->push_back("etc");
- }
}
Vector<String> abis = get_enabled_abis(p_preset);
@@ -1366,16 +1544,25 @@ public:
}
}
- virtual void get_export_options(List<ExportOption> *r_options) {
-
+ virtual void get_export_options(List<ExportOption> *r_options) override {
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "xr_features/focus_awareness"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0));
+
+ Vector<PluginConfig> plugins_configs = get_plugins();
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ print_verbose("Found Android plugin " + plugins_configs[i].name);
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false));
+ }
+ plugins_changed = false;
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
@@ -1409,30 +1596,37 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default));
}
- r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "permissions/custom_permissions"), PoolStringArray()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray()));
const char **perms = android_perms;
while (*perms) {
-
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false));
perms++;
}
}
- virtual String get_name() const {
+ virtual String get_name() const override {
return "Android";
}
- virtual String get_os_name() const {
+ virtual String get_os_name() const override {
return "Android";
}
- virtual Ref<Texture> get_logo() const {
+ virtual Ref<Texture2D> get_logo() const override {
return logo;
}
- virtual bool poll_export() {
+ virtual bool should_update_export_options() override {
+ bool export_options_changed = plugins_changed;
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed = false;
+ }
+ return export_options_changed;
+ }
+ virtual bool poll_export() override {
bool dc = devices_changed;
if (dc) {
// don't clear unless we're reporting true, to avoid race
@@ -1441,33 +1635,24 @@ public:
return dc;
}
- virtual int get_options_count() const {
-
- device_lock->lock();
- int dc = devices.size();
- device_lock->unlock();
-
- return dc;
+ virtual int get_options_count() const override {
+ MutexLock lock(device_lock);
+ return devices.size();
}
- virtual String get_options_tooltip() const {
-
+ virtual String get_options_tooltip() const override {
return TTR("Select device from the list");
}
- virtual String get_option_label(int p_index) const {
-
+ virtual String get_option_label(int p_index) const override {
ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- device_lock->lock();
- String s = devices[p_index].name;
- device_lock->unlock();
- return s;
+ MutexLock lock(device_lock);
+ return devices[p_index].name;
}
- virtual String get_option_tooltip(int p_index) const {
-
+ virtual String get_option_tooltip(int p_index) const override {
ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- device_lock->lock();
+ MutexLock lock(device_lock);
String s = devices[p_index].description;
if (devices.size() == 1) {
// Tooltip will be:
@@ -1475,12 +1660,10 @@ public:
// Description
s = devices[p_index].name + "\n\n" + s;
}
- device_lock->unlock();
return s;
}
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
-
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override {
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
String can_export_error;
@@ -1490,30 +1673,29 @@ public:
return ERR_UNCONFIGURED;
}
- device_lock->lock();
+ MutexLock lock(device_lock);
EditorProgress ep("run", "Running on " + devices[p_device].name, 3);
String adb = EditorSettings::get_singleton()->get("export/android/adb");
// Export_temp APK.
- if (ep.step("Exporting APK", 0)) {
- device_lock->unlock();
+ if (ep.step("Exporting APK...", 0)) {
return ERR_SKIP;
}
const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
const bool use_reverse = devices[p_device].api_level >= 21;
- if (use_reverse)
+ if (use_reverse) {
p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
+ }
String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk");
#define CLEANUP_AND_RETURN(m_err) \
{ \
DirAccess::remove_file_or_error(tmp_export_path); \
- device_lock->unlock(); \
return m_err; \
}
@@ -1543,11 +1725,11 @@ public:
args.push_back("uninstall");
args.push_back(get_package_name(package_name));
- err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
}
print_line("Installing to device (please wait...): " + devices[p_device].name);
- if (ep.step("Installing to device (please wait...)", 2)) {
+ if (ep.step("Installing to device, please wait...", 2)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
@@ -1558,7 +1740,7 @@ public:
args.push_back("-r");
args.push_back(tmp_export_path);
- err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
if (err || rv != 0) {
EditorNode::add_io_error("Could not install to device.");
CLEANUP_AND_RETURN(ERR_CANT_CREATE);
@@ -1566,7 +1748,6 @@ public:
if (use_remote) {
if (use_reverse) {
-
static const char *const msg = "--- Device API >= 21; debugging over USB ---";
EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
print_line(String(msg).to_upper());
@@ -1576,10 +1757,9 @@ public:
args.push_back(devices[p_device].id);
args.push_back("reverse");
args.push_back("--remove-all");
- OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
-
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
args.clear();
args.push_back("-s");
@@ -1588,12 +1768,11 @@ public:
args.push_back("tcp:" + itos(dbg_port));
args.push_back("tcp:" + itos(dbg_port));
- OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
print_line("Reverse result: " + itos(rv));
}
if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
-
int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port");
args.clear();
@@ -1603,18 +1782,17 @@ public:
args.push_back("tcp:" + itos(fs_port));
args.push_back("tcp:" + itos(fs_port));
- err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
print_line("Reverse result2: " + itos(rv));
}
} else {
-
static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---";
EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
print_line(String(msg).to_upper());
}
}
- if (ep.step("Running on Device...", 3)) {
+ if (ep.step("Running on device...", 3)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
args.clear();
@@ -1632,7 +1810,7 @@ public:
args.push_back("-n");
args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp");
- err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
+ err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
if (err || rv != 0) {
EditorNode::add_io_error("Could not execute on device.");
CLEANUP_AND_RETURN(ERR_CANT_CREATE);
@@ -1642,35 +1820,43 @@ public:
#undef CLEANUP_AND_RETURN
}
- virtual Ref<Texture> get_run_icon() const {
+ virtual Ref<Texture2D> get_run_icon() const override {
return run_icon;
}
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
-
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override {
String err;
bool valid = false;
// Look for export templates (first official, and if defined custom templates).
if (!bool(p_preset->get("custom_template/use_custom_build"))) {
- bool dvalid = exists_export_template("android_debug.apk", &err);
- bool rvalid = exists_export_template("android_release.apk", &err);
+ String template_err;
+ bool dvalid = false;
+ bool rvalid = false;
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
if (!dvalid) {
- err += TTR("Custom debug template not found.") + "\n";
+ template_err += TTR("Custom debug template not found.") + "\n";
}
+ } else {
+ dvalid = exists_export_template("android_debug.apk", &template_err);
}
+
if (p_preset->get("custom_template/release") != "") {
rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
if (!rvalid) {
- err += TTR("Custom release template not found.") + "\n";
+ template_err += TTR("Custom release template not found.") + "\n";
}
+ } else {
+ rvalid = exists_export_template("android_release.apk", &template_err);
}
valid = dvalid || rvalid;
+ if (!valid) {
+ err += template_err;
+ }
} else {
valid = exists_export_template("android_source.zip", &err);
}
@@ -1681,7 +1867,6 @@ public:
String adb = EditorSettings::get_singleton()->get("export/android/adb");
if (!FileAccess::exists(adb)) {
-
valid = false;
err += TTR("ADB executable not configured in the Editor Settings.") + "\n";
}
@@ -1689,7 +1874,6 @@ public:
String js = EditorSettings::get_singleton()->get("export/android/jarsigner");
if (!FileAccess::exists(js)) {
-
valid = false;
err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n";
}
@@ -1697,7 +1881,6 @@ public:
String dk = p_preset->get("keystore/debug");
if (!FileAccess::exists(dk)) {
-
dk = EditorSettings::get_singleton()->get("export/android/debug_keystore");
if (!FileAccess::exists(dk)) {
valid = false;
@@ -1705,6 +1888,13 @@ public:
}
}
+ String rk = p_preset->get("keystore/release");
+
+ if (!rk.empty() && !FileAccess::exists(rk)) {
+ valid = false;
+ err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
+ }
+
if (bool(p_preset->get("custom_template/use_custom_build"))) {
String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path");
if (sdk_path == "") {
@@ -1712,7 +1902,7 @@ public:
valid = false;
} else {
Error errn;
- DirAccessRef da = DirAccess::open(sdk_path.plus_file("tools"), &errn);
+ DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n";
valid = false;
@@ -1720,7 +1910,6 @@ public:
}
if (!FileAccess::exists("res://android/build/build.gradle")) {
-
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
valid = false;
}
@@ -1729,7 +1918,6 @@ public:
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
-
String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
if (apk_expansion_pkey == "") {
@@ -1743,7 +1931,6 @@ public:
String pn_err;
if (!is_package_name_valid(get_package_name(pn), &pn_err)) {
-
valid = false;
err += TTR("Invalid package name:") + " " + pn_err + "\n";
}
@@ -1754,303 +1941,341 @@ public:
err += etc_error;
}
+ // Ensure that `Use Custom Build` is enabled if a plugin is selected.
+ String enabled_plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
+ bool custom_build_enabled = p_preset->get("custom_template/use_custom_build");
+ if (!enabled_plugins_names.empty() && !custom_build_enabled) {
+ valid = false;
+ err += TTR("\"Use Custom Build\" must be enabled to use the plugins.");
+ err += "\n";
+ }
+
+ // Validate the Xr features are properly populated
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ int degrees_of_freedom = p_preset->get("xr_features/degrees_of_freedom");
+ int hand_tracking = p_preset->get("xr_features/hand_tracking");
+ bool focus_awareness = p_preset->get("xr_features/focus_awareness");
+ if (xr_mode_index != /* XRMode.OVR*/ 1) {
+ if (degrees_of_freedom > 0) {
+ valid = false;
+ err += TTR("\"Degrees Of Freedom\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
+ err += "\n";
+ }
+
+ if (hand_tracking > 0) {
+ valid = false;
+ err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
+ err += "\n";
+ }
+
+ if (focus_awareness) {
+ valid = false;
+ err += TTR("\"Focus Awareness\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
+ err += "\n";
+ }
+ }
+
+ if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/
+ !bool(p_preset->get("custom_template/use_custom_build"))) {
+ valid = false;
+ err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled.");
+ err += "\n";
+ }
+
r_error = err;
return valid;
}
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
list.push_back("apk");
+ list.push_back("aab");
return list;
}
- void _update_custom_build_project() {
-
- DirAccessRef da = DirAccess::open("res://android");
-
- ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'.");
- Map<String, List<String> > directory_paths;
- Map<String, List<String> > manifest_sections;
- Map<String, List<String> > gradle_sections;
- da->list_dir_begin();
- String d = da->get_next();
- while (d != String()) {
-
- if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir
- //add directories found
- DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d));
- if (ds) {
- ds->list_dir_begin();
- String sd = ds->get_next();
- while (sd != String()) {
-
- if (!sd.begins_with(".") && ds->current_is_dir()) {
- String key = sd.to_upper();
- if (!directory_paths.has(key)) {
- directory_paths[key] = List<String>();
- }
- String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd);
- directory_paths[key].push_back(path);
- print_line("Add: " + sd + ":" + path);
- }
-
- sd = ds->get_next();
- }
- ds->list_dir_end();
- }
- //parse manifest
- {
- FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ);
- if (f) {
-
- String section;
- while (!f->eof_reached()) {
- String l = f->get_line();
- String k = l.strip_edges();
- if (k.begins_with("[")) {
- section = k.substr(1, k.length() - 2).strip_edges().to_upper();
- print_line("Section: " + section);
- } else if (k != String()) {
- if (!manifest_sections.has(section)) {
- manifest_sections[section] = List<String>();
- }
- manifest_sections[section].push_back(l);
- }
- }
-
- f->close();
- }
- }
- //parse gradle
- {
- FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ);
- if (f) {
-
- String section;
- while (!f->eof_reached()) {
- String l = f->get_line().strip_edges();
- String k = l.strip_edges();
- if (k.begins_with("[")) {
- section = k.substr(1, k.length() - 2).strip_edges().to_upper();
- print_line("Section: " + section);
- } else if (k != String()) {
- if (!gradle_sections.has(section)) {
- gradle_sections[section] = List<String>();
- }
- gradle_sections[section].push_back(l);
- }
- }
+ inline bool is_clean_build_required(Vector<PluginConfig> enabled_plugins) {
+ String plugin_names = get_plugins_names(enabled_plugins);
+ bool first_build = last_custom_build_time == 0;
+ bool have_plugins_changed = false;
+
+ if (!first_build) {
+ have_plugins_changed = plugin_names != last_plugin_names;
+ if (!have_plugins_changed) {
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ if (enabled_plugins.get(i).last_updated > last_custom_build_time) {
+ have_plugins_changed = true;
+ break;
}
}
}
- d = da->get_next();
}
- da->list_dir_end();
- { //fix gradle build
+ last_custom_build_time = OS::get_singleton()->get_unix_time();
+ last_plugin_names = plugin_names;
- String new_file;
- {
- FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ);
- if (f) {
-
- while (!f->eof_reached()) {
- String l = f->get_line();
-
- if (l.begins_with("//CHUNK_")) {
- String text = l.replace_first("//CHUNK_", "");
- int begin_pos = text.find("_BEGIN");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "//CHUNK_" + text + "_END";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
+ return have_plugins_changed || first_build;
+ }
- new_file += "//CHUNK_" + text + "_BEGIN\n";
+ String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ int version_code = p_preset->get("version/code");
+ String package_name = p_preset->get("package/unique_name");
+ String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
+ String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
+ return fullpath;
+ }
- if (!found) {
- ERR_PRINTS("No end marker found in build.gradle for chunk: " + text);
- f->seek(pos);
- } else {
+ Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
+ Error err = save_pack(p_preset, fullpath);
+ return err;
+ }
- //add chunk lines
- if (gradle_sections.has(text)) {
- for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) {
- new_file += E->get() + "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
- } else if (l.begins_with("//DIR_")) {
- String text = l.replace_first("//DIR_", "");
- int begin_pos = text.find("_BEGIN");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "//DIR_" + text + "_END";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+ String cmdline = p_preset->get("command_line/extra_args");
+ Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
+ for (int i = 0; i < command_line_strings.size(); i++) {
+ if (command_line_strings[i].strip_edges().length() == 0) {
+ command_line_strings.remove(i);
+ i--;
+ }
+ }
- new_file += "//DIR_" + text + "_BEGIN\n";
+ gen_export_flags(command_line_strings, p_flags);
- if (!found) {
- ERR_PRINTS("No end marker found in build.gradle for dir: " + text);
- f->seek(pos);
- } else {
- //add chunk lines
- if (directory_paths.has(text)) {
- for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) {
- new_file += ",'" + E->get().replace("'", "\'") + "'";
- new_file += "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ if (apk_expansion) {
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
+ String apk_expansion_public_key = p_preset->get("apk_expansion/public_key");
- } else {
- new_file += l + "\n";
- }
- }
- }
+ command_line_strings.push_back("--use_apk_expansion");
+ command_line_strings.push_back("--apk_expansion_md5");
+ command_line_strings.push_back(FileAccess::get_md5(fullpath));
+ command_line_strings.push_back("--apk_expansion_key");
+ command_line_strings.push_back(apk_expansion_public_key.strip_edges());
+ }
+
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ if (xr_mode_index == 1) {
+ command_line_strings.push_back("--xr_mode_ovr");
+ } else { // XRMode.REGULAR is the default.
+ command_line_strings.push_back("--xr_mode_regular");
+ }
+
+ bool use_32_bit_framebuffer = p_preset->get("graphics/32_bits_framebuffer");
+ if (use_32_bit_framebuffer) {
+ command_line_strings.push_back("--use_depth_32");
+ }
+
+ bool immersive = p_preset->get("screen/immersive_mode");
+ if (immersive) {
+ command_line_strings.push_back("--use_immersive");
+ }
+
+ bool debug_opengl = p_preset->get("screen/opengl_debug");
+ if (debug_opengl) {
+ command_line_strings.push_back("--debug_opengl");
+ }
+
+ if (command_line_strings.size()) {
+ r_command_line_flags.resize(4);
+ encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]);
+ for (int i = 0; i < command_line_strings.size(); i++) {
+ print_line(itos(i) + " param: " + command_line_strings[i]);
+ CharString command_line_argument = command_line_strings[i].utf8();
+ int base = r_command_line_flags.size();
+ int length = command_line_argument.length();
+ if (length == 0)
+ continue;
+ r_command_line_flags.resize(base + 4 + length);
+ encode_uint32(length, &r_command_line_flags.write[base]);
+ copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);
}
+ }
+ }
- FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE);
- f->store_string(new_file);
- f->close();
+ Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) {
+ String release_keystore = p_preset->get("keystore/release");
+ String release_username = p_preset->get("keystore/release_user");
+ String release_password = p_preset->get("keystore/release_password");
+
+ String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
+ if (!FileAccess::exists(jarsigner)) {
+ EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
+ return OK;
}
- { //fix manifest
+ String keystore;
+ String password;
+ String user;
+ if (p_debug) {
+ keystore = p_preset->get("keystore/debug");
+ password = p_preset->get("keystore/debug_password");
+ user = p_preset->get("keystore/debug_user");
- String new_file;
- {
- FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ);
- if (f) {
-
- while (!f->eof_reached()) {
- String l = f->get_line();
-
- if (l.begins_with("<!--CHUNK_")) {
- String text = l.replace_first("<!--CHUNK_", "");
- int begin_pos = text.find("_BEGIN-->");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "<!--CHUNK_" + text + "_END-->";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
+ if (keystore.empty()) {
+ keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
- new_file += "<!--CHUNK_" + text + "_BEGIN-->\n";
+ if (ep.step("Signing debug APK...", 103)) {
+ return ERR_SKIP;
+ }
- if (!found) {
- ERR_PRINTS("No end marker found in AndroidManifest.xml for chunk: " + text);
- f->seek(pos);
- } else {
- //add chunk lines
- if (manifest_sections.has(text)) {
- for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) {
- new_file += E->get() + "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
+ } else {
+ keystore = release_keystore;
+ password = release_password;
+ user = release_username;
- } else if (l.strip_edges().begins_with("<application")) {
- String last_tag = "android:icon=\"@mipmap/icon\"";
- int last_tag_pos = l.find(last_tag);
- if (last_tag_pos == -1) {
- ERR_PRINTS("Not adding application attributes as the expected tag was not found in '<application': " + last_tag);
- new_file += l + "\n";
- } else {
- String base = l.substr(0, last_tag_pos + last_tag.length());
- if (manifest_sections.has("application_attribs")) {
- for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) {
- String to_add = E->get().strip_edges();
- base += " " + to_add + " ";
- }
- }
- base += ">\n";
- new_file += base;
- }
- } else {
- new_file += l + "\n";
- }
- }
- }
+ if (ep.step("Signing release APK...", 103)) {
+ return ERR_SKIP;
}
+ }
- FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE);
- f->store_string(new_file);
- f->close();
+ if (!FileAccess::exists(keystore)) {
+ EditorNode::add_io_error("Could not find keystore, unable to export.");
+ return ERR_FILE_CANT_OPEN;
}
- }
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
+ List<String> args;
+ args.push_back("-digestalg");
+ args.push_back("SHA-256");
+ args.push_back("-sigalg");
+ args.push_back("SHA256withRSA");
+ String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
+ if (tsa_url != "") {
+ args.push_back("-tsa");
+ args.push_back(tsa_url);
+ }
+ args.push_back("-verbose");
+ args.push_back("-keystore");
+ args.push_back(keystore);
+ args.push_back("-storepass");
+ args.push_back(password);
+ args.push_back(apk_path);
+ args.push_back(user);
+ int retval;
+ OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
+ if (retval) {
+ EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
+ return ERR_CANT_CREATE;
+ }
+
+ if (ep.step("Verifying APK...", 104)) {
+ return ERR_SKIP;
+ }
+
+ args.clear();
+ args.push_back("-verify");
+ args.push_back("-keystore");
+ args.push_back(keystore);
+ args.push_back(apk_path);
+ args.push_back("-verbose");
+
+ OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
+ if (retval) {
+ EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
+ return ERR_CANT_CREATE;
+ }
+ return OK;
+ }
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
String src_apk;
+ Error err;
EditorProgress ep("export", "Exporting for Android", 105, true);
- if (bool(p_preset->get("custom_template/use_custom_build"))) { //custom build
- //re-generate build.gradle and AndroidManifest.xml
+ bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
+ int export_format = int(p_preset->get("custom_template/export_format"));
+ bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool _signed = p_preset->get("package/signed");
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ Vector<String> enabled_abis = get_enabled_abis(p_preset);
+
+ Ref<Image> main_image;
+ Ref<Image> foreground;
+ Ref<Image> background;
- { //test that installed build version is alright
- FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
- if (!f) {
- EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
- return ERR_UNCONFIGURED;
- }
- String version = f->get_line().strip_edges();
- if (version != VERSION_FULL_CONFIG) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
- return ERR_UNCONFIGURED;
- }
+ load_icon_refs(p_preset, main_image, foreground, background);
+
+ Vector<uint8_t> command_line_flags;
+ // Write command line flags into the command_line_flags variable.
+ get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
+
+ if (export_format == 1) {
+ if (!p_path.ends_with(".aab")) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
+ return ERR_UNCONFIGURED;
}
- //build project if custom build is enabled
- String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
+ if (apk_expansion) {
+ EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
+ return ERR_UNCONFIGURED;
+ }
+ }
+ if (export_format == 0 && !p_path.ends_with(".apk")) {
+ EditorNode::get_singleton()->show_warning(
+ TTR("Invalid filename! Android APK requires the *.apk extension."));
+ return ERR_UNCONFIGURED;
+ }
+ if (export_format > 1 || export_format < 0) {
+ EditorNode::add_io_error("Unsupported export format!\n");
+ return ERR_UNCONFIGURED; //TODO: is this the right error?
+ }
+ if (use_custom_build) {
+ //test that installed build version is alright
+ FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
+ if (!f) {
+ EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
+ return ERR_UNCONFIGURED;
+ }
+ String version = f->get_line().strip_edges();
+ if (version != VERSION_FULL_CONFIG) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
+ return ERR_UNCONFIGURED;
+ }
+ String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
- _update_custom_build_project();
+ // TODO: should we use "package/name" or "application/config/name"?
+ String project_name = get_project_name(p_preset->get("package/name"));
+ err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
+ if (err != OK) {
+ EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
+ }
+ // Copies the project icon files into the appropriate Gradle project directory.
+ _copy_icons_to_gradle_project(p_preset, main_image, foreground, background);
+ // Write an AndroidManifest.xml file into the Gradle project directory.
+ _write_tmp_manifest(p_preset, p_give_internet, p_debug);
+
+ //stores all the project files inside the Gradle project directory. Also includes all ABIs
+ if (!apk_expansion) {
+ DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (da_res->dir_exists("res://android/build/assets")) {
+ DirAccess *da_assets = DirAccess::open("res://android/build/assets");
+ da_assets->erase_contents_recursive();
+ da_res->remove("res://android/build/assets");
+ }
+ err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file);
+ if (err != OK) {
+ EditorNode::add_io_error("Could not export project files to gradle project\n");
+ return err;
+ }
+ } else {
+ err = save_apk_expansion_file(p_preset, p_path);
+ if (err != OK) {
+ EditorNode::add_io_error("Could not write expansion package file!");
+ return err;
+ }
+ }
+ store_file_at_path("res://android/build/assets/_cl_", command_line_flags);
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
-
String build_command;
+
#ifdef WINDOWS_ENABLED
build_command = "gradlew.bat";
#else
@@ -2058,20 +2283,46 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
-
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String version_code = itos(p_preset->get("version/code"));
+ String version_name = p_preset->get("version/name");
+ String enabled_abi_string = String("|").join(enabled_abis);
+
+ Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
+ String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
+ String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins);
+ String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins);
+ bool clean_build_required = is_clean_build_required(enabled_plugins);
List<String> cmdline;
- cmdline.push_back("build");
+ if (clean_build_required) {
+ cmdline.push_back("clean");
+ }
+
+ String build_type = p_debug ? "Debug" : "Release";
+ if (export_format == 1) {
+ String bundle_build_command = vformat("bundle%s", build_type);
+ cmdline.push_back(bundle_build_command);
+ } else if (export_format == 0) {
+ String apk_build_command = vformat("assemble%s", build_type);
+ cmdline.push_back(apk_build_command);
+ }
+
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
+ cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
+ cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.
+ cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
+ cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
+ cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory.
/*{ used for debug
int ec;
String pipe;
- OS::get_singleton()->execute(build_command, cmdline, true, NULL, NULL, &ec);
+ OS::get_singleton()->execute(build_command, cmdline, true, nullptr, nullptr, &ec);
print_line("exit code: " + itos(ec));
}
*/
@@ -2080,52 +2331,70 @@ public:
EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
return ERR_CANT_CREATE;
}
- if (p_debug) {
- src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk");
- } else {
- src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk");
- }
- if (!FileAccess::exists(src_apk)) {
- EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk);
- return ERR_CANT_CREATE;
+ List<String> copy_args;
+ String copy_command;
+ if (export_format == 1) {
+ copy_command = vformat("copyAndRename%sAab", build_type);
+ } else if (export_format == 0) {
+ copy_command = vformat("copyAndRename%sApk", build_type);
}
- } else {
+ copy_args.push_back(copy_command);
- if (p_debug)
- src_apk = p_preset->get("custom_template/debug");
- else
- src_apk = p_preset->get("custom_template/release");
+ copy_args.push_back("-p"); // argument to specify the start directory.
+ copy_args.push_back(build_path); // start directory.
- src_apk = src_apk.strip_edges();
- if (src_apk == "") {
- if (p_debug) {
- src_apk = find_export_template("android_debug.apk");
- } else {
- src_apk = find_export_template("android_release.apk");
- }
- if (src_apk == "") {
- EditorNode::add_io_error("Package not found: " + src_apk);
- return ERR_FILE_NOT_FOUND;
+ String export_filename = p_path.get_file();
+ String export_path = p_path.get_base_dir();
+
+ copy_args.push_back("-Pexport_path=file:" + export_path);
+ copy_args.push_back("-Pexport_filename=" + export_filename);
+
+ int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
+ if (copy_result != 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
+ return ERR_CANT_CREATE;
+ }
+ if (_signed) {
+ err = sign_apk(p_preset, p_debug, p_path, ep);
+ if (err != OK) {
+ return err;
}
}
+ return OK;
+ }
+ // This is the start of the Legacy build system
+ if (p_debug)
+ src_apk = p_preset->get("custom_template/debug");
+ else
+ src_apk = p_preset->get("custom_template/release");
+ src_apk = src_apk.strip_edges();
+ if (src_apk == "") {
+ if (p_debug) {
+ src_apk = find_export_template("android_debug.apk");
+ } else {
+ src_apk = find_export_template("android_release.apk");
+ }
+ if (src_apk == "") {
+ EditorNode::add_io_error("Package not found: " + src_apk);
+ return ERR_FILE_NOT_FOUND;
+ }
}
if (!DirAccess::exists(p_path.get_base_dir())) {
return ERR_FILE_BAD_PATH;
}
- FileAccess *src_f = NULL;
+ FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- if (ep.step("Creating APK", 0)) {
+ if (ep.step("Creating APK...", 0)) {
return ERR_SKIP;
}
unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
if (!pkg) {
-
EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk);
return ERR_FILE_NOT_FOUND;
}
@@ -2133,7 +2402,7 @@ public:
int ret = unzGoToFirstFile(pkg);
zlib_filefunc_def io2 = io;
- FileAccess *dst_f = NULL;
+ FileAccess *dst_f = nullptr;
io2.opaque = &dst_f;
String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk");
@@ -2144,65 +2413,21 @@ public:
return m_err; \
}
- zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
-
- bool use_32_fb = p_preset->get("graphics/32_bits_framebuffer");
- bool immersive = p_preset->get("screen/immersive_mode");
- bool debug_opengl = p_preset->get("screen/opengl_debug");
-
- bool _signed = p_preset->get("package/signed");
-
- bool apk_expansion = p_preset->get("apk_expansion/enable");
+ zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
String cmdline = p_preset->get("command_line/extra_args");
- int version_code = p_preset->get("version/code");
String version_name = p_preset->get("version/name");
String package_name = p_preset->get("package/unique_name");
String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
-
- Vector<String> enabled_abis = get_enabled_abis(p_preset);
-
- String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
-
- // Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used.
- Ref<Image> launcher_icon_image;
- Ref<Image> launcher_adaptive_icon_foreground_image;
- Ref<Image> launcher_adaptive_icon_background_image;
-
- launcher_icon_image.instance();
- launcher_adaptive_icon_foreground_image.instance();
- launcher_adaptive_icon_background_image.instance();
-
- // Regular icon: user selection -> project icon -> default.
- String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) {
- ImageLoader::load_image(project_icon_path, launcher_icon_image);
- }
-
- // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) {
- launcher_adaptive_icon_foreground_image = launcher_icon_image;
- }
-
- // Adaptive background: user selection -> default.
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
- if (!path.empty()) {
- ImageLoader::load_image(path, launcher_adaptive_icon_background_image);
- }
-
+ Vector<String> invalid_abis(enabled_abis);
while (ret == UNZ_OK) {
-
//get filename
unz_file_info info;
char fname[16384];
- ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
+ ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
bool skip = false;
@@ -2217,24 +2442,27 @@ public:
unzCloseCurrentFile(pkg);
//write
-
if (file == "AndroidManifest.xml") {
- _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG));
+ _fix_manifest(p_preset, data, p_give_internet);
}
-
if (file == "resources.arsc") {
_fix_resources(p_preset, data);
}
-
for (int i = 0; i < icon_densities_count; ++i) {
- if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) {
- _process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data);
+ if (main_image.is_valid() && !main_image->empty()) {
+ if (file == launcher_icons[i].export_path) {
+ _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
+ }
}
- if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data);
+ if (foreground.is_valid() && !foreground->empty()) {
+ if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
+ _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ }
}
- if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data);
+ if (background.is_valid() && !background->empty()) {
+ if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
+ _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ }
}
}
@@ -2242,6 +2470,7 @@ public:
bool enabled = false;
for (int i = 0; i < enabled_abis.size(); ++i) {
if (file.begins_with("lib/" + enabled_abis[i] + "/")) {
+ invalid_abis.erase(enabled_abis[i]);
enabled = true;
break;
}
@@ -2266,11 +2495,11 @@ public:
zipOpenNewFileInZip(unaligned_apk,
file.utf8().get_data(),
&zipfi,
- NULL,
+ nullptr,
0,
- NULL,
+ nullptr,
0,
- NULL,
+ nullptr,
uncompressed ? 0 : Z_DEFLATED,
Z_DEFAULT_COMPRESSION);
@@ -2281,111 +2510,58 @@ public:
ret = unzGoToNextFile(pkg);
}
- if (ep.step("Adding Files...", 1)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
- Error err = OK;
- Vector<String> cl = cmdline.strip_edges().split(" ");
- for (int i = 0; i < cl.size(); i++) {
- if (cl[i].strip_edges().length() == 0) {
- cl.remove(i);
- i--;
- }
+ if (!invalid_abis.empty()) {
+ String unsupported_arch = String(", ").join(invalid_abis);
+ EditorNode::add_io_error("Missing libraries in the export template for the selected architectures: " + unsupported_arch + ".\n" +
+ "Please build a template with all required libraries, or uncheck the missing architectures in the export preset.");
+ CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
}
- gen_export_flags(cl, p_flags);
+ if (ep.step("Adding files...", 1)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+ err = OK;
if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
-
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
} else {
- //all files
-
if (apk_expansion) {
-
- String apkfname = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
- String fullpath = p_path.get_base_dir().plus_file(apkfname);
- err = save_pack(p_preset, fullpath);
-
+ err = save_apk_expansion_file(p_preset, p_path);
if (err != OK) {
- unzClose(pkg);
- EditorNode::add_io_error("Could not write expansion package file: " + apkfname);
-
- CLEANUP_AND_RETURN(ERR_SKIP);
+ EditorNode::add_io_error("Could not write expansion package file!");
+ return err;
}
-
- cl.push_back("--use_apk_expansion");
- cl.push_back("--apk_expansion_md5");
- cl.push_back(FileAccess::get_md5(fullpath));
- cl.push_back("--apk_expansion_key");
- cl.push_back(apk_expansion_pkey.strip_edges());
-
} else {
-
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
-
err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
}
}
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- if (xr_mode_index == 1 /* XRMode.OVR */) {
- cl.push_back("--xr_mode_ovr");
- } else {
- // XRMode.REGULAR is the default.
- cl.push_back("--xr_mode_regular");
- }
-
- if (use_32_fb)
- cl.push_back("--use_depth_32");
-
- if (immersive)
- cl.push_back("--use_immersive");
-
- if (debug_opengl)
- cl.push_back("--debug_opengl");
-
- if (cl.size()) {
- //add comandline
- Vector<uint8_t> clf;
- clf.resize(4);
- encode_uint32(cl.size(), &clf.write[0]);
- for (int i = 0; i < cl.size(); i++) {
-
- print_line(itos(i) + " param: " + cl[i]);
- CharString txt = cl[i].utf8();
- int base = clf.size();
- int length = txt.length();
- if (!length)
- continue;
- clf.resize(base + 4 + length);
- encode_uint32(length, &clf.write[base]);
- copymem(&clf.write[base + 4], txt.ptr(), length);
- }
-
- zip_fileinfo zipfi = get_zip_fileinfo();
-
- zipOpenNewFileInZip(unaligned_apk,
- "assets/_cl_",
- &zipfi,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- 0, // No compress (little size gain and potentially slower startup)
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(unaligned_apk, clf.ptr(), clf.size());
- zipCloseFileInZip(unaligned_apk);
+ if (err != OK) {
+ unzClose(pkg);
+ EditorNode::add_io_error("Could not export project files");
+ CLEANUP_AND_RETURN(ERR_SKIP);
}
- zipClose(unaligned_apk, NULL);
+ zip_fileinfo zipfi = get_zip_fileinfo();
+ zipOpenNewFileInZip(unaligned_apk,
+ "assets/_cl_",
+ &zipfi,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ 0, // No compress (little size gain and potentially slower startup)
+ Z_DEFAULT_COMPRESSION);
+ zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size());
+ zipCloseFileInZip(unaligned_apk);
+ zipClose(unaligned_apk, nullptr);
unzClose(pkg);
if (err != OK) {
@@ -2393,87 +2569,9 @@ public:
}
if (_signed) {
-
- String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
- if (!FileAccess::exists(jarsigner)) {
- EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
- CLEANUP_AND_RETURN(OK);
- }
-
- String keystore;
- String password;
- String user;
- if (p_debug) {
-
- keystore = p_preset->get("keystore/debug");
- password = p_preset->get("keystore/debug_password");
- user = p_preset->get("keystore/debug_user");
-
- if (keystore.empty()) {
-
- keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
- password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
- user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
- }
-
- if (ep.step("Signing debug APK...", 103)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- } else {
- keystore = release_keystore;
- password = release_password;
- user = release_username;
-
- if (ep.step("Signing release APK...", 103)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
- }
-
- if (!FileAccess::exists(keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
- CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN);
- }
-
- List<String> args;
- args.push_back("-digestalg");
- args.push_back("SHA-256");
- args.push_back("-sigalg");
- args.push_back("SHA256withRSA");
- String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
- if (tsa_url != "") {
- args.push_back("-tsa");
- args.push_back(tsa_url);
- }
- args.push_back("-verbose");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back("-storepass");
- args.push_back(password);
- args.push_back(tmp_unaligned_path);
- args.push_back(user);
- int retval;
- OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
- }
-
- if (ep.step("Verifying APK...", 104)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- args.clear();
- args.push_back("-verify");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back(tmp_unaligned_path);
- args.push_back("-verbose");
-
- OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
+ err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep);
+ if (err != OK) {
+ CLEANUP_AND_RETURN(err);
}
}
@@ -2487,7 +2585,6 @@ public:
unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io);
if (!tmp_unaligned) {
-
EditorNode::add_io_error("Could not unzip temporary unaligned APK.");
CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
}
@@ -2495,22 +2592,21 @@ public:
ret = unzGoToFirstFile(tmp_unaligned);
io2 = io;
- dst_f = NULL;
+ dst_f = nullptr;
io2.opaque = &dst_f;
- zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
+ zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
// Take files from the unaligned APK and write them out to the aligned one
// in raw mode, i.e. not uncompressing and recompressing, aligning them as needed,
// following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp
int bias = 0;
while (ret == UNZ_OK) {
-
unz_file_info info;
memset(&info, 0, sizeof(info));
char fname[16384];
char extra[16384];
- ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, NULL, 0);
+ ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, nullptr, 0);
String file = fname;
@@ -2534,17 +2630,15 @@ public:
memset(extra + info.size_file_extra, 0, padding);
- // write
- zip_fileinfo zipfi = get_zip_fileinfo();
-
+ zip_fileinfo fileinfo = get_zip_fileinfo();
zipOpenNewFileInZip2(final_apk,
file.utf8().get_data(),
- &zipfi,
+ &fileinfo,
extra,
info.size_file_extra + padding,
- NULL,
+ nullptr,
0,
- NULL,
+ nullptr,
method,
level,
1); // raw write
@@ -2556,23 +2650,21 @@ public:
ret = unzGoToNextFile(tmp_unaligned);
}
- zipClose(final_apk, NULL);
+ zipClose(final_apk, nullptr);
unzClose(tmp_unaligned);
CLEANUP_AND_RETURN(OK);
}
- virtual void get_platform_features(List<String> *r_features) {
-
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("mobile");
r_features->push_back("Android");
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
EditorExportPlatformAndroid() {
-
Ref<Image> img = memnew(Image(_android_logo));
logo.instance();
logo->create_from_image(img);
@@ -2581,22 +2673,20 @@ public:
run_icon.instance();
run_icon->create_from_image(img);
- device_lock = Mutex::create();
devices_changed = true;
+ plugins_changed = true;
quit_request = false;
- device_thread = Thread::create(_device_poll_thread, this);
+ check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
}
~EditorExportPlatformAndroid() {
quit_request = true;
- Thread::wait_to_finish(device_thread);
- memdelete(device_lock);
- memdelete(device_thread);
+ Thread::wait_to_finish(check_for_changes_thread);
+ memdelete(check_for_changes_thread);
}
};
void register_android_exporter() {
-
String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = "*.exe";
diff --git a/platform/android/export/export.h b/platform/android/export/export.h
index ce786cc8b6..d11ab9f49e 100644
--- a/platform/android/export/export.h
+++ b/platform/android/export/export.h
@@ -28,4 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef ANDROID_EXPORT_H
+#define ANDROID_EXPORT_H
+
void register_android_exporter();
+
+#endif // ANDROID_EXPORT_H
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
new file mode 100644
index 0000000000..95f870bc35
--- /dev/null
+++ b/platform/android/export/gradle_export_util.h
@@ -0,0 +1,245 @@
+/*************************************************************************/
+/* gradle_export_util.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_GRADLE_EXPORT_UTIL_H
+#define GODOT_GRADLE_EXPORT_UTIL_H
+
+#include "core/io/zip_io.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/os/os.h"
+#include "editor/editor_export.h"
+
+const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="utf-8"?>
+<!--WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">%s</string>
+</resources>
+)";
+
+// Utility method used to create a directory.
+Error create_directory(const String &p_dir) {
+ if (!DirAccess::exists(p_dir)) {
+ DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
+ Error err = filesystem_da->make_dir_recursive(p_dir);
+ ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
+ memdelete(filesystem_da);
+ }
+ return OK;
+}
+
+// Implementation of EditorExportSaveSharedObject.
+// This method will only be called as an input to export_project_files.
+// This method lets the .so files for all ABIs to be copied
+// into the gradle project from the .AAR file
+Error ignore_so_file(void *p_userdata, const SharedObject &p_so) {
+ return OK;
+}
+
+// Writes p_data into a file at p_path, creating directories if necessary.
+// Note: this will overwrite the file at p_path if it already exists.
+Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) {
+ String dir = p_path.get_base_dir();
+ Error err = create_directory(dir);
+ if (err != OK) {
+ return err;
+ }
+ FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ fa->store_buffer(p_data.ptr(), p_data.size());
+ memdelete(fa);
+ return OK;
+}
+
+// Writes string p_data into a file at p_path, creating directories if necessary.
+// Note: this will overwrite the file at p_path if it already exists.
+Error store_string_at_path(const String &p_path, const String &p_data) {
+ String dir = p_path.get_base_dir();
+ Error err = create_directory(dir);
+ if (err != OK) {
+ return err;
+ }
+ FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ fa->store_string(p_data);
+ memdelete(fa);
+ return OK;
+}
+
+// Implementation of EditorExportSaveFunction.
+// This method will only be called as an input to export_project_files.
+// It is used by the export_project_files method to save all the asset files into the gradle project.
+// It's functionality mirrors that of the method save_apk_file.
+// This method will be called ONLY when custom build is enabled.
+Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ String dst_path = p_path.replace_first("res://", "res://android/build/assets/");
+ Error err = store_file_at_path(dst_path, p_data);
+ return err;
+}
+
+// Creates strings.xml files inside the gradle project for different locales.
+Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) {
+ // Stores the string into the default values directory.
+ String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true));
+ store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string);
+
+ // Searches the Gradle project res/ directory to find all supported locales
+ DirAccessRef da = DirAccess::open("res://android/build/res");
+ if (!da) {
+ return ERR_CANT_OPEN;
+ }
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+ if (!file.begins_with("values-")) {
+ // NOTE: This assumes all directories that start with "values-" are for localization.
+ continue;
+ }
+ String locale = file.replace("values-", "").replace("-r", "_");
+ String property_name = "application/config/name_" + locale;
+ String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml";
+ if (ProjectSettings::get_singleton()->has_setting(property_name)) {
+ String locale_project_name = ProjectSettings::get_singleton()->get(property_name);
+ String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true));
+ store_string_at_path(locale_directory, processed_xml_string);
+ } else {
+ // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch
+ store_string_at_path(locale_directory, processed_default_xml_string);
+ }
+ }
+ da->list_dir_end();
+ return OK;
+}
+
+String bool_to_string(bool v) {
+ return v ? "true" : "false";
+}
+
+String _get_gles_tag() {
+ bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
+ !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
+ return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
+}
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
+ String sizes[] = { "small", "normal", "large", "xlarge" };
+ size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
+ for (size_t i = 0; i < num_sizes; i++) {
+ String feature_name = vformat("screen/support_%s", sizes[i]);
+ String feature_support = bool_to_string(p_preset->get(feature_name));
+ String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
+ manifest_screen_sizes += xml_entry;
+ }
+ manifest_screen_sizes += " />\n";
+ return manifest_screen_sizes;
+}
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_xr_features;
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ if (uses_xr) {
+ int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
+ if (dof_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n";
+ } else if (dof_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+ }
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
+ } else if (hand_tracking_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
+ }
+ }
+ return manifest_xr_features;
+}
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
+ String package_name = p_preset->get("package/unique_name");
+ String manifest_instrumentation_text = vformat(
+ " <instrumentation\n"
+ " tools:node=\"replace\"\n"
+ " android:name=\".GodotInstrumentation\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:label=\"@string/godot_project_name_string\"\n"
+ " android:targetPackage=\"%s\" />\n",
+ package_name);
+ return manifest_instrumentation_text;
+}
+
+String _get_plugins_tag(const String &plugins_names) {
+ if (!plugins_names.empty()) {
+ return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
+ } else {
+ return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
+ }
+}
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
+ String manifest_activity_text = vformat(
+ " <activity android:name=\"com.godot.game.GodotApp\" "
+ "tools:replace=\"android:screenOrientation\" "
+ "android:screenOrientation=\"%s\">\n",
+ orientation);
+ if (uses_xr) {
+ String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness"));
+ manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness);
+ } else {
+ manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
+ }
+ manifest_activity_text += " </activity>\n";
+ return manifest_activity_text;
+}
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String manifest_application_text =
+ " <application android:label=\"@string/godot_project_name_string\"\n"
+ " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n"
+ " android:icon=\"@mipmap/icon\">)\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
+
+ manifest_application_text += _get_plugins_tag(plugins_names);
+ if (uses_xr) {
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
+ }
+ manifest_application_text += _get_activity_tag(p_preset);
+ manifest_application_text += " </application>\n";
+ return manifest_application_text;
+}
+
+#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index 965342b364..05d5fb576d 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -31,7 +31,7 @@
#include "file_access_android.h"
#include "core/print_string.h"
-AAssetManager *FileAccessAndroid::asset_manager = NULL;
+AAssetManager *FileAccessAndroid::asset_manager = nullptr;
/*void FileAccessAndroid::make_default() {
@@ -39,12 +39,10 @@ AAssetManager *FileAccessAndroid::asset_manager = NULL;
}*/
FileAccess *FileAccessAndroid::create_android() {
-
return memnew(FileAccessAndroid);
}
Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
-
String path = fix_path(p_path).simplify_path();
if (path.begins_with("/"))
path = path.substr(1, path.length());
@@ -64,20 +62,17 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
}
void FileAccessAndroid::close() {
-
if (!a)
return;
AAsset_close(a);
- a = NULL;
+ a = nullptr;
}
bool FileAccessAndroid::is_open() const {
-
- return a != NULL;
+ return a != nullptr;
}
void FileAccessAndroid::seek(size_t p_position) {
-
ERR_FAIL_COND(!a);
AAsset_seek(a, p_position, SEEK_SET);
pos = p_position;
@@ -90,29 +85,24 @@ void FileAccessAndroid::seek(size_t p_position) {
}
void FileAccessAndroid::seek_end(int64_t p_position) {
-
ERR_FAIL_COND(!a);
AAsset_seek(a, p_position, SEEK_END);
pos = len + p_position;
}
size_t FileAccessAndroid::get_position() const {
-
return pos;
}
size_t FileAccessAndroid::get_len() const {
-
return len;
}
bool FileAccessAndroid::eof_reached() const {
-
return eof;
}
uint8_t FileAccessAndroid::get_8() const {
-
if (pos >= len) {
eof = true;
return 0;
@@ -125,7 +115,6 @@ uint8_t FileAccessAndroid::get_8() const {
}
int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
-
off_t r = AAsset_read(a, p_dst, p_length);
if (pos + p_length > len) {
@@ -133,7 +122,6 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
}
if (r >= 0) {
-
pos += r;
if (pos > len) {
pos = len;
@@ -143,22 +131,18 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
}
Error FileAccessAndroid::get_error() const {
-
return eof ? ERR_FILE_EOF : OK; //not sure what else it may happen
}
void FileAccessAndroid::flush() {
-
ERR_FAIL();
}
void FileAccessAndroid::store_8(uint8_t p_dest) {
-
ERR_FAIL();
}
bool FileAccessAndroid::file_exists(const String &p_path) {
-
String path = fix_path(p_path).simplify_path();
if (path.begins_with("/"))
path = path.substr(1, path.length());
@@ -175,7 +159,7 @@ bool FileAccessAndroid::file_exists(const String &p_path) {
}
FileAccessAndroid::FileAccessAndroid() {
- a = NULL;
+ a = nullptr;
eof = false;
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index 6b5ec541fd..a347c63ffb 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -38,7 +38,6 @@
//#include <android_native_app_glue.h>
class FileAccessAndroid : public FileAccess {
-
static FileAccess *create_android();
mutable AAsset *a;
mutable size_t len;
diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp
index db3aa4255e..df8b57fd3a 100644
--- a/platform/android/file_access_jandroid.cpp
+++ b/platform/android/file_access_jandroid.cpp
@@ -33,7 +33,7 @@
#include "thread_jandroid.h"
#include <unistd.h>
-jobject FileAccessJAndroid::io = NULL;
+jobject FileAccessJAndroid::io = nullptr;
jclass FileAccessJAndroid::cls;
jmethodID FileAccessJAndroid::_file_open = 0;
jmethodID FileAccessJAndroid::_file_get_size = 0;
@@ -44,12 +44,10 @@ jmethodID FileAccessJAndroid::_file_eof = 0;
jmethodID FileAccessJAndroid::_file_close = 0;
FileAccess *FileAccessJAndroid::create_jandroid() {
-
return memnew(FileAccessJAndroid);
}
Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) {
-
if (is_open())
close();
@@ -75,7 +73,6 @@ Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) {
}
void FileAccessJAndroid::close() {
-
if (!is_open())
return;
@@ -86,12 +83,10 @@ void FileAccessJAndroid::close() {
}
bool FileAccessJAndroid::is_open() const {
-
return id != 0;
}
void FileAccessJAndroid::seek(size_t p_position) {
-
JNIEnv *env = ThreadAndroid::get_env();
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
@@ -99,42 +94,37 @@ void FileAccessJAndroid::seek(size_t p_position) {
}
void FileAccessJAndroid::seek_end(int64_t p_position) {
-
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
seek(get_len());
}
size_t FileAccessJAndroid::get_position() const {
-
JNIEnv *env = ThreadAndroid::get_env();
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
return env->CallIntMethod(io, _file_tell, id);
}
size_t FileAccessJAndroid::get_len() const {
-
JNIEnv *env = ThreadAndroid::get_env();
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
return env->CallIntMethod(io, _file_get_size, id);
}
bool FileAccessJAndroid::eof_reached() const {
-
JNIEnv *env = ThreadAndroid::get_env();
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
return env->CallIntMethod(io, _file_eof, id);
}
uint8_t FileAccessJAndroid::get_8() const {
-
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
uint8_t byte;
get_buffer(&byte, 1);
return byte;
}
-int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
+int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
if (p_length == 0)
return 0;
@@ -150,7 +140,6 @@ int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
}
Error FileAccessJAndroid::get_error() const {
-
if (eof_reached())
return ERR_FILE_EOF;
return OK;
@@ -163,7 +152,6 @@ void FileAccessJAndroid::store_8(uint8_t p_dest) {
}
bool FileAccessJAndroid::file_exists(const String &p_path) {
-
JNIEnv *env = ThreadAndroid::get_env();
String path = fix_path(p_path).simplify_path();
@@ -184,7 +172,6 @@ bool FileAccessJAndroid::file_exists(const String &p_path) {
}
void FileAccessJAndroid::setup(jobject p_io) {
-
io = p_io;
JNIEnv *env = ThreadAndroid::get_env();
@@ -201,12 +188,10 @@ void FileAccessJAndroid::setup(jobject p_io) {
}
FileAccessJAndroid::FileAccessJAndroid() {
-
id = 0;
}
FileAccessJAndroid::~FileAccessJAndroid() {
-
if (is_open())
close();
}
diff --git a/platform/android/file_access_jandroid.h b/platform/android/file_access_jandroid.h
index b361c64922..e252a4d3ac 100644
--- a/platform/android/file_access_jandroid.h
+++ b/platform/android/file_access_jandroid.h
@@ -34,7 +34,6 @@
#include "core/os/file_access.h"
#include "java_godot_lib_jni.h"
class FileAccessJAndroid : public FileAccess {
-
static jobject io;
static jclass cls;
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 7e9ce70d80..48c09552c1 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -6,28 +6,21 @@
android:versionName="1.0"
android:installLocation="auto" >
- <!-- Adding custom text to the manifest is fine, but do it outside the custom USER and APPLICATION BEGIN/END comments, -->
- <!-- as that gets rewritten. -->
-
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true" />
- <!-- glEsVersion is modified by the exporter, changing this value here has no effect. -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
-<!-- Custom user permissions XML added by add-ons. It's recommended to add them from the export preset, though. -->
-<!--CHUNK_USER_PERMISSIONS_BEGIN-->
-<!--CHUNK_USER_PERMISSIONS_END-->
-
- <!-- Any tag in this line after android:icon will be erased when doing custom builds. -->
- <!-- If you want to add tags manually, do before it. -->
- <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. -->
- <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@mipmap/icon" >
+ <application
+ android:label="@string/godot_project_name_string"
+ android:allowBackup="false"
+ tools:ignore="GoogleAppIndexingWarning"
+ android:icon="@mipmap/icon" >
<!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. -->
<!-- Do these changes in the export preset. Adding new ones is fine. -->
@@ -37,6 +30,11 @@
android:name="xr_mode_metadata_name"
android:value="xr_mode_metadata_value" />
+ <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
+ <meta-data
+ android:name="plugins"
+ android:value="plugins_value"/>
+
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
@@ -47,16 +45,15 @@
android:resizeableActivity="false"
tools:ignore="UnusedAttribute" >
+ <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. -->
+ <meta-data android:name="com.oculus.vr.focusaware" android:value="false" />
+
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-<!-- Custom application XML added by add-ons. -->
-<!--CHUNK_APPLICATION_BEGIN-->
-<!--CHUNK_APPLICATION_END-->
-
</application>
</manifest>
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 2e4f2ffab0..ceacfec9e1 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -1,7 +1,4 @@
// Gradle build config for Godot Engine's Android port.
-//
-// Do not remove/modify comments ending with BEGIN/END, they are used to inject
-// addon-specific configuration.
apply from: 'config.gradle'
buildscript {
@@ -10,13 +7,10 @@ buildscript {
repositories {
google()
jcenter()
-//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN
-//CHUNK_BUILDSCRIPT_REPOSITORIES_END
}
dependencies {
classpath libraries.androidGradlePlugin
-//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN
-//CHUNK_BUILDSCRIPT_DEPENDENCIES_END
+ classpath libraries.kotlinGradlePlugin
}
}
@@ -27,37 +21,76 @@ allprojects {
mavenCentral()
google()
jcenter()
-//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN
-//CHUNK_ALLPROJECTS_REPOSITORIES_END
+
+ // Godot user plugins custom maven repos
+ String[] mavenRepos = getGodotPluginsMavenRepos()
+ if (mavenRepos != null && mavenRepos.size() > 0) {
+ for (String repoUrl : mavenRepos) {
+ maven {
+ url repoUrl
+ }
+ }
+ }
}
}
dependencies {
implementation libraries.supportCoreUtils
+ implementation libraries.kotlinStdLib
+ implementation libraries.v4Support
if (rootProject.findProject(":lib")) {
implementation project(":lib")
+ } else if (rootProject.findProject(":godot:lib")) {
+ implementation project(":godot:lib")
} else {
// Custom build mode. In this scenario this project is the only one around and the Godot
// library is available through the pre-generated godot-lib.*.aar android archive files.
debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar'])
releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
}
-//CHUNK_DEPENDENCIES_BEGIN
-//CHUNK_DEPENDENCIES_END
+
+ // Godot user plugins remote dependencies
+ String[] remoteDeps = getGodotPluginsRemoteBinaries()
+ if (remoteDeps != null && remoteDeps.size() > 0) {
+ for (String dep : remoteDeps) {
+ implementation dep
+ }
+ }
+
+ // Godot user plugins local dependencies
+ String[] pluginsBinaries = getGodotPluginsLocalBinaries()
+ if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
+ implementation files(pluginsBinaries)
+ }
}
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+
defaultConfig {
+ // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
+ aaptOptions {
+ ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ }
+
+ ndk {
+ String[] export_abi_list = getExportEnabledABIs()
+ abiFilters export_abi_list
+ }
+
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
+ versionCode getExportVersionCode()
+ versionName getExportVersionName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
-//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
-//CHUNK_ANDROID_DEFAULTCONFIG_END
}
lintOptions {
@@ -68,6 +101,9 @@ android {
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
+
+ // Should be uncommented for development purpose within Android Studio
+ // doNotStrip '**/*.so'
}
// Both signing and zip-aligning will be done at export time
@@ -79,37 +115,13 @@ android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = [
- 'src'
-//DIR_SRC_BEGIN
-//DIR_SRC_END
- ]
- res.srcDirs = [
- 'res'
-//DIR_RES_BEGIN
-//DIR_RES_END
- ]
- aidl.srcDirs = [
- 'aidl'
-//DIR_AIDL_BEGIN
-//DIR_AIDL_END
- ]
- assets.srcDirs = [
- 'assets'
-//DIR_ASSETS_BEGIN
-//DIR_ASSETS_END
- ]
+ java.srcDirs = ['src']
+ res.srcDirs = ['res']
+ aidl.srcDirs = ['aidl']
+ assets.srcDirs = ['assets']
}
- debug.jniLibs.srcDirs = [
- 'libs/debug'
-//DIR_JNI_DEBUG_BEGIN
-//DIR_JNI_DEBUG_END
- ]
- release.jniLibs.srcDirs = [
- 'libs/release'
-//DIR_JNI_RELEASE_BEGIN
-//DIR_JNI_RELEASE_END
- ]
+ debug.jniLibs.srcDirs = ['libs/debug']
+ release.jniLibs.srcDirs = ['libs/release']
}
applicationVariants.all { variant ->
@@ -119,5 +131,26 @@ android {
}
}
-//CHUNK_GLOBAL_BEGIN
-//CHUNK_GLOBAL_END
+task copyAndRenameDebugApk(type: Copy) {
+ from "$buildDir/outputs/apk/debug/android_debug.apk"
+ into getExportPath()
+ rename "android_debug.apk", getExportFilename()
+}
+
+task copyAndRenameReleaseApk(type: Copy) {
+ from "$buildDir/outputs/apk/release/android_release.apk"
+ into getExportPath()
+ rename "android_release.apk", getExportFilename()
+}
+
+task copyAndRenameDebugAab(type: Copy) {
+ from "$buildDir/outputs/bundle/debug/build-debug.aab"
+ into getExportPath()
+ rename "build-debug.aab", getExportFilename()
+}
+
+task copyAndRenameReleaseAab(type: Copy) {
+ from "$buildDir/outputs/bundle/release/build-release.aab"
+ into getExportPath()
+ rename "build-release.aab", getExportFilename()
+}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 5550d3099d..d1176e6196 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,24 +1,137 @@
ext.versions = [
- androidGradlePlugin : '3.4.2',
- compileSdk : 28,
- minSdk : 18,
- targetSdk : 28,
- buildTools : '28.0.3',
- supportCoreUtils : '28.0.0'
+ androidGradlePlugin: '3.5.3',
+ compileSdk : 29,
+ minSdk : 18,
+ targetSdk : 29,
+ buildTools : '29.0.3',
+ supportCoreUtils : '1.0.0',
+ kotlinVersion : '1.3.61',
+ v4Support : '1.0.0'
]
ext.libraries = [
- androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils"
+ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
+ supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils",
+ kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
+ kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion",
+ v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support"
]
ext.getExportPackageName = { ->
- // Retrieve the app id from the project property set by the Godot build command.
- String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
- // Check if the app id is valid, otherwise use the default.
- if (appId == null || appId.isEmpty()) {
- appId = "com.godot.game"
- }
- return appId
+ // Retrieve the app id from the project property set by the Godot build command.
+ String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
+ // Check if the app id is valid, otherwise use the default.
+ if (appId == null || appId.isEmpty()) {
+ appId = "com.godot.game"
+ }
+ return appId
+}
+
+ext.getExportVersionCode = { ->
+ String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : ""
+ if (versionCode == null || versionCode.isEmpty()) {
+ versionCode = "1"
+ }
+ return Integer.parseInt(versionCode)
+}
+
+ext.getExportVersionName = { ->
+ String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : ""
+ if (versionName == null || versionName.isEmpty()) {
+ versionName = "1.0"
+ }
+ return versionName
+}
+
+final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+
+// get the list of ABIs the project should be exported to
+ext.getExportEnabledABIs = { ->
+ String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "";
+ if (enabledABIs == null || enabledABIs.isEmpty()) {
+ enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
+ }
+ Set<String> exportAbiFilter = [];
+ for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ if (!abi_name.trim().isEmpty()){
+ exportAbiFilter.add(abi_name);
+ }
+ }
+ return exportAbiFilter;
+}
+
+ext.getExportPath = {
+ String exportPath = project.hasProperty("export_path") ? project.property("export_path") : ""
+ if (exportPath == null || exportPath.isEmpty()) {
+ exportPath = "."
+ }
+ return exportPath
+}
+
+ext.getExportFilename = {
+ String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : ""
+ if (exportFilename == null || exportFilename.isEmpty()) {
+ exportFilename = "godot_android"
+ }
+ return exportFilename
+}
+
+/**
+ * Parse the project properties for the 'plugins_maven_repos' property and return the list
+ * of maven repos.
+ */
+ext.getGodotPluginsMavenRepos = { ->
+ Set<String> mavenRepos = []
+
+ // Retrieve the list of maven repos.
+ if (project.hasProperty("plugins_maven_repos")) {
+ String mavenReposProperty = project.property("plugins_maven_repos")
+ if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
+ for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ mavenRepos += mavenRepoUrl.trim()
+ }
+ }
+ }
+
+ return mavenRepos
+}
+
+/**
+ * Parse the project properties for the 'plugins_remote_binaries' property and return
+ * it for inclusion in the build dependencies.
+ */
+ext.getGodotPluginsRemoteBinaries = { ->
+ Set<String> remoteDeps = []
+
+ // Retrieve the list of remote plugins binaries.
+ if (project.hasProperty("plugins_remote_binaries")) {
+ String remoteDepsList = project.property("plugins_remote_binaries")
+ if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
+ for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ remoteDeps += dep.trim()
+ }
+ }
+ }
+ return remoteDeps
+}
+
+/**
+ * Parse the project properties for the 'plugins_local_binaries' property and return
+ * their binaries for inclusion in the build dependencies.
+ */
+ext.getGodotPluginsLocalBinaries = { ->
+ Set<String> binDeps = []
+
+ // Retrieve the list of local plugins binaries.
+ if (project.hasProperty("plugins_local_binaries")) {
+ String pluginsList = project.property("plugins_local_binaries")
+ if (pluginsList != null && !pluginsList.trim().isEmpty()) {
+ for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ binDeps += plugin.trim()
+ }
+ }
+ }
+
+ return binDeps
}
diff --git a/platform/android/java/lib/res/values-ar/strings.xml b/platform/android/java/app/res/values-ar/godot_project_name_string.xml
index 9f3dc6d6ac..23aa5cf3e1 100644
--- a/platform/android/java/lib/res/values-ar/strings.xml
+++ b/platform/android/java/app/res/values-ar/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ar</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-bg/strings.xml b/platform/android/java/app/res/values-bg/godot_project_name_string.xml
index bd8109277e..dbb7e04ae5 100644
--- a/platform/android/java/lib/res/values-bg/strings.xml
+++ b/platform/android/java/app/res/values-bg/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-bg</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-ca/strings.xml b/platform/android/java/app/res/values-ca/godot_project_name_string.xml
index 494cb88468..709d0961e6 100644
--- a/platform/android/java/lib/res/values-ca/strings.xml
+++ b/platform/android/java/app/res/values-ca/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ca</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-cs/strings.xml b/platform/android/java/app/res/values-cs/godot_project_name_string.xml
index 30ce00f895..ab248a8032 100644
--- a/platform/android/java/lib/res/values-cs/strings.xml
+++ b/platform/android/java/app/res/values-cs/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-cs</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-da/strings.xml b/platform/android/java/app/res/values-da/godot_project_name_string.xml
index 4c2a1cf0f4..906bf44f57 100644
--- a/platform/android/java/lib/res/values-da/strings.xml
+++ b/platform/android/java/app/res/values-da/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-da</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-de/strings.xml b/platform/android/java/app/res/values-de/godot_project_name_string.xml
index 52946d4cce..0cacb0175f 100644
--- a/platform/android/java/lib/res/values-de/strings.xml
+++ b/platform/android/java/app/res/values-de/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-de</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-el/strings.xml b/platform/android/java/app/res/values-el/godot_project_name_string.xml
index 181dc51762..047de616a5 100644
--- a/platform/android/java/lib/res/values-el/strings.xml
+++ b/platform/android/java/app/res/values-el/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-el</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-en/strings.xml b/platform/android/java/app/res/values-en/godot_project_name_string.xml
index 976a565013..bb3a5dbef3 100644
--- a/platform/android/java/lib/res/values-en/strings.xml
+++ b/platform/android/java/app/res/values-en/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-en</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-es-rES/strings.xml b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml
index 73f63a08f8..d4537f3496 100644
--- a/platform/android/java/lib/res/values-es-rES/strings.xml
+++ b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es_ES</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-es/strings.xml b/platform/android/java/app/res/values-es/godot_project_name_string.xml
index 07b718a641..d63a16022e 100644
--- a/platform/android/java/lib/res/values-es/strings.xml
+++ b/platform/android/java/app/res/values-es/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/app/res/values-fa/godot_project_name_string.xml b/platform/android/java/app/res/values-fa/godot_project_name_string.xml
new file mode 100644
index 0000000000..c303f13d5f
--- /dev/null
+++ b/platform/android/java/app/res/values-fa/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-fa</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-fi/strings.xml b/platform/android/java/app/res/values-fi/godot_project_name_string.xml
index 323d82aff1..bd6005574a 100644
--- a/platform/android/java/lib/res/values-fi/strings.xml
+++ b/platform/android/java/app/res/values-fi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fi</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-fr/strings.xml b/platform/android/java/app/res/values-fr/godot_project_name_string.xml
index 32bead2661..2e94b65a20 100644
--- a/platform/android/java/lib/res/values-fr/strings.xml
+++ b/platform/android/java/app/res/values-fr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fr</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-hi/strings.xml b/platform/android/java/app/res/values-hi/godot_project_name_string.xml
index 8aab2a8c63..0bf75dcd56 100644
--- a/platform/android/java/lib/res/values-hi/strings.xml
+++ b/platform/android/java/app/res/values-hi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hi</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-hr/strings.xml b/platform/android/java/app/res/values-hr/godot_project_name_string.xml
index caf55e2241..d3f75910f9 100644
--- a/platform/android/java/lib/res/values-hr/strings.xml
+++ b/platform/android/java/app/res/values-hr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hr</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-hu/strings.xml b/platform/android/java/app/res/values-hu/godot_project_name_string.xml
index e7f9e51226..012b613af3 100644
--- a/platform/android/java/lib/res/values-hu/strings.xml
+++ b/platform/android/java/app/res/values-hu/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hu</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/app/res/values-in/godot_project_name_string.xml b/platform/android/java/app/res/values-in/godot_project_name_string.xml
new file mode 100644
index 0000000000..eedecff7a1
--- /dev/null
+++ b/platform/android/java/app/res/values-in/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-in</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-it/strings.xml b/platform/android/java/app/res/values-it/godot_project_name_string.xml
index 1f5e5a049e..7e734047c4 100644
--- a/platform/android/java/lib/res/values-it/strings.xml
+++ b/platform/android/java/app/res/values-it/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-it</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/app/res/values-iw/godot_project_name_string.xml b/platform/android/java/app/res/values-iw/godot_project_name_string.xml
new file mode 100644
index 0000000000..03893f0cbb
--- /dev/null
+++ b/platform/android/java/app/res/values-iw/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-iw</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-ja/strings.xml b/platform/android/java/app/res/values-ja/godot_project_name_string.xml
index 7f85f57df7..f9dd4fab0d 100644
--- a/platform/android/java/lib/res/values-ja/strings.xml
+++ b/platform/android/java/app/res/values-ja/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ja</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/app/res/values-ko/godot_project_name_string.xml b/platform/android/java/app/res/values-ko/godot_project_name_string.xml
new file mode 100644
index 0000000000..26f5dac176
--- /dev/null
+++ b/platform/android/java/app/res/values-ko/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-ko</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-lt/strings.xml b/platform/android/java/app/res/values-lt/godot_project_name_string.xml
index 6e3677fde7..1c2e976cc5 100644
--- a/platform/android/java/lib/res/values-lt/strings.xml
+++ b/platform/android/java/app/res/values-lt/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lt</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-lv/strings.xml b/platform/android/java/app/res/values-lv/godot_project_name_string.xml
index 701fc271ac..b5e638ed73 100644
--- a/platform/android/java/lib/res/values-lv/strings.xml
+++ b/platform/android/java/app/res/values-lv/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lv</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-nb/strings.xml b/platform/android/java/app/res/values-nb/godot_project_name_string.xml
index 73147ca1af..e6d89d6a3f 100644
--- a/platform/android/java/lib/res/values-nb/strings.xml
+++ b/platform/android/java/app/res/values-nb/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nb</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-nl/strings.xml b/platform/android/java/app/res/values-nl/godot_project_name_string.xml
index e501928a35..93cb3a3878 100644
--- a/platform/android/java/lib/res/values-nl/strings.xml
+++ b/platform/android/java/app/res/values-nl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nl</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-pl/strings.xml b/platform/android/java/app/res/values-pl/godot_project_name_string.xml
index ea5da73b6f..e5d6ac74fb 100644
--- a/platform/android/java/lib/res/values-pl/strings.xml
+++ b/platform/android/java/app/res/values-pl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pl</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-pt/strings.xml b/platform/android/java/app/res/values-pt/godot_project_name_string.xml
index bdda7cd2c7..a4624655c5 100644
--- a/platform/android/java/lib/res/values-pt/strings.xml
+++ b/platform/android/java/app/res/values-pt/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pt</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-ro/strings.xml b/platform/android/java/app/res/values-ro/godot_project_name_string.xml
index 3686da4c19..19e026637e 100644
--- a/platform/android/java/lib/res/values-ro/strings.xml
+++ b/platform/android/java/app/res/values-ro/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ro</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-ru/strings.xml b/platform/android/java/app/res/values-ru/godot_project_name_string.xml
index 954067658b..284845241f 100644
--- a/platform/android/java/lib/res/values-ru/strings.xml
+++ b/platform/android/java/app/res/values-ru/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ru</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-sk/strings.xml b/platform/android/java/app/res/values-sk/godot_project_name_string.xml
index 37d1283124..f8ab4a5b59 100644
--- a/platform/android/java/lib/res/values-sk/strings.xml
+++ b/platform/android/java/app/res/values-sk/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sk</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-sl/strings.xml b/platform/android/java/app/res/values-sl/godot_project_name_string.xml
index 0bb249c375..98bd53e8d2 100644
--- a/platform/android/java/lib/res/values-sl/strings.xml
+++ b/platform/android/java/app/res/values-sl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sl</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-sr/strings.xml b/platform/android/java/app/res/values-sr/godot_project_name_string.xml
index 0e83cab1a1..3f400f2a4d 100644
--- a/platform/android/java/lib/res/values-sr/strings.xml
+++ b/platform/android/java/app/res/values-sr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sr</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-sv/strings.xml b/platform/android/java/app/res/values-sv/godot_project_name_string.xml
index e3a04ac2ec..8670b7c9aa 100644
--- a/platform/android/java/lib/res/values-sv/strings.xml
+++ b/platform/android/java/app/res/values-sv/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sv</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-th/strings.xml b/platform/android/java/app/res/values-th/godot_project_name_string.xml
index 0aa893b8bf..a1cc1bcd49 100644
--- a/platform/android/java/lib/res/values-th/strings.xml
+++ b/platform/android/java/app/res/values-th/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-th</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-tl/strings.xml b/platform/android/java/app/res/values-tl/godot_project_name_string.xml
index e7e2af4909..6d66d114cf 100644
--- a/platform/android/java/lib/res/values-tl/strings.xml
+++ b/platform/android/java/app/res/values-tl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tl</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-tr/strings.xml b/platform/android/java/app/res/values-tr/godot_project_name_string.xml
index 97af1243a6..ba3bd7de36 100644
--- a/platform/android/java/lib/res/values-tr/strings.xml
+++ b/platform/android/java/app/res/values-tr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tr</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-uk/strings.xml b/platform/android/java/app/res/values-uk/godot_project_name_string.xml
index 3dea6908a9..5f14ab25a0 100644
--- a/platform/android/java/lib/res/values-uk/strings.xml
+++ b/platform/android/java/app/res/values-uk/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-uk</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-vi/strings.xml b/platform/android/java/app/res/values-vi/godot_project_name_string.xml
index a6552130b0..295378e111 100644
--- a/platform/android/java/lib/res/values-vi/strings.xml
+++ b/platform/android/java/app/res/values-vi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-vi</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values-zh-rHK/strings.xml b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml
index 8a6269da0f..40ab0f285a 100644
--- a/platform/android/java/lib/res/values-zh-rHK/strings.xml
+++ b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_HK</string>
</resources>
diff --git a/platform/android/java/lib/res/values-zh-rTW/strings.xml b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml
index b1bb39d5d6..095bd564e2 100644
--- a/platform/android/java/lib/res/values-zh-rTW/strings.xml
+++ b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_TW</string>
</resources>
diff --git a/platform/android/java/lib/res/values-zh-rCN/strings.xml b/platform/android/java/app/res/values-zh/godot_project_name_string.xml
index 6668c56bd9..31aa8c273a 100644
--- a/platform/android/java/lib/res/values-zh-rCN/strings.xml
+++ b/platform/android/java/app/res/values-zh/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh</string>
</resources>
diff --git a/platform/android/java/app/res/values/godot_project_name_string.xml b/platform/android/java/app/res/values/godot_project_name_string.xml
new file mode 100644
index 0000000000..7ec2738896
--- /dev/null
+++ b/platform/android/java/app/res/values/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name</string>
+</resources>
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
new file mode 100644
index 0000000000..33b863c7bf
--- /dev/null
+++ b/platform/android/java/app/settings.gradle
@@ -0,0 +1,2 @@
+// Empty settings.gradle file to denote this directory as being the root project
+// of the Godot custom build.
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index eb884404cd..1af5950cbe 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -30,11 +30,11 @@
package com.godot.game;
-import org.godotengine.godot.Godot;
+import org.godotengine.godot.FullScreenGodotApp;
/**
* Template activity for Godot Android custom builds.
* Feel free to extend and modify this class for your custom logic.
*/
-public class GodotApp extends Godot {
+public class GodotApp extends FullScreenGodotApp {
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 2052017888..821a4dc584 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -9,6 +9,7 @@ buildscript {
}
dependencies {
classpath libraries.androidGradlePlugin
+ classpath libraries.kotlinGradlePlugin
}
}
@@ -24,7 +25,7 @@ ext {
sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : ""
supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
- supportedTargets = ['release':"release", 'debug':"release_debug"]
+ supportedTargets = ["release", "debug"]
// Used by gradle to specify which architecture to build for by default when running `./gradlew build`.
// This command is usually used by Android Studio.
@@ -64,10 +65,10 @@ task copyReleaseBinaryToBin(type: Copy) {
}
/**
- * Copy the Godot android library archive debug file into the app debug libs directory.
+ * Copy the Godot android library archive debug file into the app module debug libs directory.
* Depends on the library build task to ensure the AAR file is generated prior to copying.
*/
-task copyDebugAAR(type: Copy) {
+task copyDebugAARToAppModule(type: Copy) {
dependsOn ':lib:assembleDebug'
from('lib/build/outputs/aar')
into('app/libs/debug')
@@ -75,10 +76,21 @@ task copyDebugAAR(type: Copy) {
}
/**
- * Copy the Godot android library archive release file into the app release libs directory.
+ * Copy the Godot android library archive debug file into the root bin directory.
* Depends on the library build task to ensure the AAR file is generated prior to copying.
*/
-task copyReleaseAAR(type: Copy) {
+task copyDebugAARToBin(type: Copy) {
+ dependsOn ':lib:assembleDebug'
+ from('lib/build/outputs/aar')
+ into(binDir)
+ include('godot-lib.debug.aar')
+}
+
+/**
+ * Copy the Godot android library archive release file into the app module release libs directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyReleaseAARToAppModule(type: Copy) {
dependsOn ':lib:assembleRelease'
from('lib/build/outputs/aar')
into('app/libs/release')
@@ -86,6 +98,17 @@ task copyReleaseAAR(type: Copy) {
}
/**
+ * Copy the Godot android library archive release file into the root bin directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyReleaseAARToBin(type: Copy) {
+ dependsOn ':lib:assembleRelease'
+ from('lib/build/outputs/aar')
+ into(binDir)
+ include('godot-lib.release.aar')
+}
+
+/**
* Generate Godot custom build template by zipping the source files from the app directory, as well
* as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'.
* The zip file also includes some gradle tools to allow building of the custom build.
@@ -95,7 +118,7 @@ task zipCustomBuild(type: Zip) {
doFirst {
logger.lifecycle("Generating Godot custom build template")
}
- from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties','gradlew', 'gradlew.bat', 'gradle/**']))
+ from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties', 'gradlew', 'gradlew.bat', 'gradle/**']))
include '**/*'
archiveName 'android_source.zip'
destinationDir(file(binDir))
@@ -106,23 +129,28 @@ task zipCustomBuild(type: Zip) {
*/
task generateGodotTemplates(type: GradleBuild) {
// We exclude these gradle tasks so we can run the scons command manually.
- for (String buildType : supportedTargets.keySet()) {
+ for (String buildType : supportedTargets) {
startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
}
tasks = []
// Only build the apks and aar files for which we have native shared libraries.
- for (String target : supportedTargets.keySet()) {
+ for (String target : supportedTargets) {
File targetLibs = new File("lib/libs/" + target)
- if (targetLibs != null && targetLibs.isDirectory()) {
- File[] targetLibsContents = targetLibs.listFiles()
- if (targetLibsContents != null && targetLibsContents.length > 0) {
- // Copy the generated aar library files to the custom build directory.
- tasks += "copy" + target.capitalize() + "AAR"
- // Copy the prebuilt binary templates to the bin directory.
- tasks += "copy" + target.capitalize() + "BinaryToBin"
- }
+ if (targetLibs != null
+ && targetLibs.isDirectory()
+ && targetLibs.listFiles() != null
+ && targetLibs.listFiles().length > 0) {
+ String capitalizedTarget = target.capitalize()
+ // Copy the generated aar library files to the custom build directory.
+ tasks += "copy" + capitalizedTarget + "AARToAppModule"
+ // Copy the generated aar library files to the bin directory.
+ tasks += "copy" + capitalizedTarget + "AARToBin"
+ // Copy the prebuilt binary templates to the bin directory.
+ tasks += "copy" + capitalizedTarget + "BinaryToBin"
+ } else {
+ logger.lifecycle("No native shared libs for target $target. Skipping build.")
}
}
@@ -133,20 +161,30 @@ task generateGodotTemplates(type: GradleBuild) {
* Clean the generated artifacts.
*/
task cleanGodotTemplates(type: Delete) {
- // Delete the generated native libs
- delete("lib/libs")
+ // Delete the generated native libs
+ delete("lib/libs")
+
+ // Delete the library generated AAR files
+ delete("lib/build/outputs/aar")
+
+ // Delete the godotpayment libs directory contents
+ delete("plugins/godotpayment/libs")
+
+ // Delete the generated godotpayment aar
+ delete("plugins/godotpayment/build/outputs/aar")
- // Delete the library generated AAR files
- delete("lib/build/outputs/aar")
+ // Delete the app libs directory contents
+ delete("app/libs")
- // Delete the app libs directory contents
- delete("app/libs")
+ // Delete the generated binary apks
+ delete("app/build/outputs/apk")
- // Delete the generated binary apks
- delete("app/build/outputs/apk")
+ // Delete the Godot templates in the Godot bin directory
+ delete("$binDir/android_debug.apk")
+ delete("$binDir/android_release.apk")
+ delete("$binDir/android_source.zip")
+ delete("$binDir/godot-lib.debug.aar")
+ delete("$binDir/godot-lib.release.aar")
- // Delete the Godot templates in the Godot bin directory
- delete("$binDir/android_debug.apk")
- delete("$binDir/android_release.apk")
- delete("$binDir/android_source.zip")
+ finalizedBy getTasksByName("clean", true)
}
diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties
index aac7c9b461..e14cd5ba5c 100644
--- a/platform/android/java/gradle.properties
+++ b/platform/android/java/gradle.properties
@@ -7,6 +7,9 @@
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
+android.enableJetifier=true
+android.useAndroidX=true
+
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index bf50265715..f56b0f6a5e 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml
index b133585f99..fa39bc0f1d 100644
--- a/platform/android/java/lib/AndroidManifest.xml
+++ b/platform/android/java/lib/AndroidManifest.xml
@@ -13,7 +13,7 @@
<instrumentation
android:icon="@mipmap/icon"
android:label="@string/godot_project_name_string"
- android:name=".GodotInstrumentation"
+ android:name="org.godotengine.godot.GodotInstrumentation"
android:targetPackage="org.godotengine.godot" />
</manifest>
diff --git a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl
deleted file mode 100644
index 0f2bcae338..0000000000
--- a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.vending.billing;
-
-import android.os.Bundle;
-
-/**
- * InAppBillingService is the service that provides in-app billing version 3 and beyond.
- * This service provides the following features:
- * 1. Provides a new API to get details of in-app items published for the app including
- * price, type, title and description.
- * 2. The purchase flow is synchronous and purchase information is available immediately
- * after it completes.
- * 3. Purchase information of in-app purchases is maintained within the Google Play system
- * till the purchase is consumed.
- * 4. An API to consume a purchase of an inapp item. All purchases of one-time
- * in-app items are consumable and thereafter can be purchased again.
- * 5. An API to get current purchases of the user immediately. This will not contain any
- * consumed purchases.
- *
- * All calls will give a response code with the following possible values
- * RESULT_OK = 0 - success
- * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog
- * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down
- * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested
- * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase
- * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API
- * RESULT_ERROR = 6 - Fatal error during the API action
- * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
- * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
- */
-interface IInAppBillingService {
- /**
- * Checks support for the requested billing API version, package and in-app type.
- * Minimum API version supported by this interface is 3.
- * @param apiVersion billing API version that the app is using
- * @param packageName the package name of the calling app
- * @param type type of the in-app item being purchased ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @return RESULT_OK(0) on success and appropriate response code on failures.
- */
- int isBillingSupported(int apiVersion, String packageName, String type);
-
- /**
- * Provides details of a list of SKUs
- * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
- * with a list JSON strings containing the productId, price, title and description.
- * This API can be called with a maximum of 20 SKUs.
- * @param apiVersion billing API version that the app is using
- * @param packageName the package name of the calling app
- * @param type of the in-app items ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
- * @return Bundle containing the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
- * on failures.
- * "DETAILS_LIST" with a StringArrayList containing purchase information
- * in JSON format similar to:
- * '{ "productId" : "exampleSku",
- * "type" : "inapp",
- * "price" : "$5.00",
- * "price_currency": "USD",
- * "price_amount_micros": 5000000,
- * "title : "Example Title",
- * "description" : "This is an example description" }'
- */
- Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
-
- /**
- * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
- * the type, a unique purchase token and an optional developer payload.
- * @param apiVersion billing API version that the app is using
- * @param packageName package name of the calling app
- * @param sku the SKU of the in-app item as published in the developer console
- * @param type of the in-app item being purchased ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @param developerPayload optional argument to be sent back with the purchase information
- * @return Bundle containing the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
- * on failures.
- * "BUY_INTENT" - PendingIntent to start the purchase flow
- *
- * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
- * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
- * If the purchase is successful, the result data will contain the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
- * codes on failures.
- * "INAPP_PURCHASE_DATA" - String in JSON format similar to
- * '{"orderId":"12999763169054705758.1371079406387615",
- * "packageName":"com.example.app",
- * "productId":"exampleSku",
- * "purchaseTime":1345678900000,
- * "purchaseToken" : "122333444455555",
- * "developerPayload":"example developer payload" }'
- * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
- * was signed with the private key of the developer
- */
- Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
- String developerPayload);
-
- /**
- * Returns the current SKUs owned by the user of the type and package name specified along with
- * purchase information and a signature of the data to be validated.
- * This will return all SKUs that have been purchased in V3 and managed items purchased using
- * V1 and V2 that have not been consumed.
- * @param apiVersion billing API version that the app is using
- * @param packageName package name of the calling app
- * @param type of the in-app items being requested ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @param continuationToken to be set as null for the first call, if the number of owned
- * skus are too many, a continuationToken is returned in the response bundle.
- * This method can be called again with the continuation token to get the next set of
- * owned skus.
- * @return Bundle containing the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
- on failures.
- * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
- * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
- * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
- * of the purchase information
- * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
- * next set of in-app purchases. Only set if the
- * user has more owned skus than the current list.
- */
- Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
-
- /**
- * Consume the last purchase of the given SKU. This will result in this item being removed
- * from all subsequent responses to getPurchases() and allow re-purchase of this item.
- * @param apiVersion billing API version that the app is using
- * @param packageName package name of the calling app
- * @param purchaseToken token in the purchase information JSON that identifies the purchase
- * to be consumed
- * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures.
- */
- int consumePurchase(int apiVersion, String packageName, String purchaseToken);
-
- /**
- * This API is currently under development.
- */
- int stub(int apiVersion, String packageName, String type);
-
- /**
- * Returns a pending intent to launch the purchase flow for upgrading or downgrading a
- * subscription. The existing owned SKU(s) should be provided along with the new SKU that
- * the user is upgrading or downgrading to.
- * @param apiVersion billing API version that the app is using, must be 5 or later
- * @param packageName package name of the calling app
- * @param oldSkus the SKU(s) that the user is upgrading or downgrading from,
- * if null or empty this method will behave like {@link #getBuyIntent}
- * @param newSku the SKU that the user is upgrading or downgrading to
- * @param type of the item being purchased, currently must be "subs"
- * @param developerPayload optional argument to be sent back with the purchase information
- * @return Bundle containing the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
- * on failures.
- * "BUY_INTENT" - PendingIntent to start the purchase flow
- *
- * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
- * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
- * If the purchase is successful, the result data will contain the following key-value pairs
- * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
- * codes on failures.
- * "INAPP_PURCHASE_DATA" - String in JSON format similar to
- * '{"orderId":"12999763169054705758.1371079406387615",
- * "packageName":"com.example.app",
- * "productId":"exampleSku",
- * "purchaseTime":1345678900000,
- * "purchaseToken" : "122333444455555",
- * "developerPayload":"example developer payload" }'
- * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
- * was signed with the private key of the developer
- */
- Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName,
- in List<String> oldSkus, String newSku, String type, String developerPayload);
-
- /**
- * Returns a pending intent to launch the purchase flow for an in-app item. This method is
- * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams}
- * parameter. This parameter is a Bundle of optional keys and values that affect the
- * operation of the method.
- * @param apiVersion billing API version that the app is using, must be 6 or later
- * @param packageName package name of the calling app
- * @param sku the SKU of the in-app item as published in the developer console
- * @param type of the in-app item being purchased ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @param developerPayload optional argument to be sent back with the purchase information
- * @extraParams a Bundle with the following optional keys:
- * "skusToReplace" - List<String> - an optional list of SKUs that the user is
- * upgrading or downgrading from.
- * Pass this field if the purchase is upgrading or downgrading
- * existing subscriptions.
- * The specified SKUs are replaced with the SKUs that the user is
- * purchasing. Google Play replaces the specified SKUs at the start of
- * the next billing cycle.
- * "replaceSkusProration" - Boolean - whether the user should be credited for any unused
- * subscription time on the SKUs they are upgrading or downgrading.
- * If you set this field to true, Google Play swaps out the old SKUs
- * and credits the user with the unused value of their subscription
- * time on a pro-rated basis.
- * Google Play applies this credit to the new subscription, and does
- * not begin billing the user for the new subscription until after
- * the credit is used up.
- * If you set this field to false, the user does not receive credit for
- * any unused subscription time and the recurrence date does not
- * change.
- * Default value is true. Ignored if you do not pass skusToReplace.
- * "accountId" - String - an optional obfuscated string that is uniquely
- * associated with the user's account in your app.
- * If you pass this value, Google Play can use it to detect irregular
- * activity, such as many devices making purchases on the same
- * account in a short period of time.
- * Do not use the developer ID or the user's Google ID for this field.
- * In addition, this field should not contain the user's ID in
- * cleartext.
- * We recommend that you use a one-way hash to generate a string from
- * the user's ID, and store the hashed string in this field.
- * "vr" - Boolean - an optional flag indicating whether the returned intent
- * should start a VR purchase flow. The apiVersion must also be 7 or
- * later to use this flag.
- */
- Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku,
- String type, String developerPayload, in Bundle extraParams);
-
- /**
- * Returns the most recent purchase made by the user for each SKU, even if that purchase is
- * expired, canceled, or consumed.
- * @param apiVersion billing API version that the app is using, must be 6 or later
- * @param packageName package name of the calling app
- * @param type of the in-app items being requested ("inapp" for one-time purchases
- * and "subs" for subscriptions)
- * @param continuationToken to be set as null for the first call, if the number of owned
- * skus is too large, a continuationToken is returned in the response bundle.
- * This method can be called again with the continuation token to get the next set of
- * owned skus.
- * @param extraParams a Bundle with extra params that would be appended into http request
- * query string. Not used at this moment. Reserved for future functionality.
- * @return Bundle containing the following key-value pairs
- * "RESPONSE_CODE" with int value: RESULT_OK(0) if success,
- * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures.
- *
- * "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs
- * "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
- * "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures
- * of the purchase information
- * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
- * next set of in-app purchases. Only set if the
- * user has more owned skus than the current list.
- */
- Bundle getPurchaseHistory(int apiVersion, String packageName, String type,
- String continuationToken, in Bundle extraParams);
-
- /**
- * This method is a variant of {@link #isBillingSupported}} that takes an additional
- * {@code extraParams} parameter.
- * @param apiVersion billing API version that the app is using, must be 7 or later
- * @param packageName package name of the calling app
- * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs"
- * for subscriptions)
- * @param extraParams a Bundle with the following optional keys:
- * "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams}
- * supports returning a VR purchase flow.
- * @return RESULT_OK(0) on success and appropriate response code on failures.
- */
- int isBillingSupportedExtraParams(int apiVersion, String packageName, String type,
- in Bundle extraParams);
-}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index eb97484b9c..19eee5a315 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -1,7 +1,10 @@
apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
dependencies {
implementation libraries.supportCoreUtils
+ implementation libraries.kotlinStdLib
+ implementation libraries.v4Support
}
def pathToRootDir = "../../../../"
@@ -9,7 +12,6 @@ def pathToRootDir = "../../../../"
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
- useLibrary 'org.apache.http.legacy'
defaultConfig {
minSdkVersion versions.minSdk
@@ -24,6 +26,9 @@ android {
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
+
+ // Should be uncommented for development purpose within Android Studio
+ // doNotStrip '**/*.so'
}
sourceSets {
@@ -54,7 +59,7 @@ android {
// files is only setup for editing support.
gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType
- def releaseTarget = supportedTargets[buildType.toLowerCase()]
+ def releaseTarget = buildType.toLowerCase()
if (releaseTarget == null || releaseTarget == "") {
throw new GradleException("Invalid build type: " + buildType)
}
diff --git a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png
deleted file mode 100644
index f849d8e90d..0000000000
--- a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png
deleted file mode 100644
index 1dfb28b33a..0000000000
--- a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png
deleted file mode 100644
index 372b763ec5..0000000000
--- a/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png
deleted file mode 100644
index 302a972049..0000000000
--- a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/lib/res/layout/downloading_expansion.xml b/platform/android/java/lib/res/layout/downloading_expansion.xml
index 4a9700965f..34c2757598 100644
--- a/platform/android/java/lib/res/layout/downloading_expansion.xml
+++ b/platform/android/java/lib/res/layout/downloading_expansion.xml
@@ -162,4 +162,4 @@
</LinearLayout>
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/platform/android/java/lib/res/layout/godot_app_layout.xml b/platform/android/java/lib/res/layout/godot_app_layout.xml
new file mode 100644
index 0000000000..386ded1c5d
--- /dev/null
+++ b/platform/android/java/lib/res/layout/godot_app_layout.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/godot_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml
index fae1faeb60..426e1bd841 100644
--- a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml
+++ b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml
@@ -105,4 +105,4 @@
</RelativeLayout>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml
index 1ed4037035..cfdcca2ab5 100644
--- a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml
+++ b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml
@@ -2,4 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/icon_background"/>
<foreground android:drawable="@mipmap/icon_foreground"/>
-</adaptive-icon> \ No newline at end of file
+</adaptive-icon>
diff --git a/platform/android/java/lib/res/values-fa/strings.xml b/platform/android/java/lib/res/values-fa/strings.xml
index f1e29013c4..60b01accf1 100644
--- a/platform/android/java/lib/res/values-fa/strings.xml
+++ b/platform/android/java/lib/res/values-fa/strings.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="godot_project_name_string">godot-project-name-fa</string>
<string name="text_paused_cellular">آیا می خواهید بر روی اتصال داده همراه دانلود را شروع کنید؟ بر اساس نوع سطح داده شما این ممکن است برای شما هزینه مالی داشته باشد.</string>
<string name="text_paused_cellular_2">اگر نمی خواهید بر روی اتصال داده همراه دانلود را شروع کنید ، دانلود به صورت خودکار در زمان دسترسی به وای-فای شروع می شود.</string>
<string name="text_button_resume_cellular">ادامه دانلود</string>
diff --git a/platform/android/java/lib/res/values-in/strings.xml b/platform/android/java/lib/res/values-in/strings.xml
deleted file mode 100644
index 9e9a8b0c03..0000000000
--- a/platform/android/java/lib/res/values-in/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_project_name_string">godot-project-name-id</string>
-</resources> \ No newline at end of file
diff --git a/platform/android/java/lib/res/values-iw/strings.xml b/platform/android/java/lib/res/values-iw/strings.xml
deleted file mode 100644
index f52ede2085..0000000000
--- a/platform/android/java/lib/res/values-iw/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_project_name_string">godot-project-name-he</string>
-</resources> \ No newline at end of file
diff --git a/platform/android/java/lib/res/values-ko/strings.xml b/platform/android/java/lib/res/values-ko/strings.xml
index fab0bdd753..7b62345977 100644
--- a/platform/android/java/lib/res/values-ko/strings.xml
+++ b/platform/android/java/lib/res/values-ko/strings.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="godot_project_name_string">godot-project-name-ko</string>
<string name="text_paused_cellular">모바일 네트워크를 사용하여 다운로드 하시겠습니까? 남은 데이터 사용량에 따라, 요금이 부과될 수 있습니다.</string>
<string name="text_paused_cellular_2">모바일 네트워크를 사용하여 다운로드 하지 않을 경우, 와이파이 연결이 가능할 때 자동적으로 다운로드가 이루어집니다.</string>
<string name="text_button_resume_cellular">다운로드 계속하기</string>
@@ -52,4 +51,4 @@
<string name="kilobytes_per_second">%1$s KB/s</string>
<string name="time_remaining">남은 시간: %1$s</string>
<string name="time_remaining_notification">%1$s 남음</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml
new file mode 100644
index 0000000000..9034dbbcc1
--- /dev/null
+++ b/platform/android/java/lib/res/values/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="text_edit_height">48dp</dimen>
+</resources>
diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml
index a1b81a6186..590b066d8a 100644
--- a/platform/android/java/lib/res/values/strings.xml
+++ b/platform/android/java/lib/res/values/strings.xml
@@ -52,4 +52,4 @@
<string name="kilobytes_per_second">%1$s KB/s</string>
<string name="time_remaining">Time remaining: %1$s</string>
<string name="time_remaining_notification">%1$s left</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/res/values/styles.xml b/platform/android/java/lib/res/values/styles.xml
index a442f61e7e..b798373bc6 100644
--- a/platform/android/java/lib/res/values/styles.xml
+++ b/platform/android/java/lib/res/values/styles.xml
@@ -22,4 +22,4 @@
<item name="android:background">@android:color/background_dark</item>
</style>
-</resources> \ No newline at end of file
+</resources>
diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java
index 1dcc370d83..0700b78a28 100644
--- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java
+++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java
@@ -233,4 +233,4 @@ public class Constants {
*/
public static final long ACTIVE_THREAD_WATCHDOG = 5*1000;
-} \ No newline at end of file
+}
diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
index 0abaf2e052..d481c22204 100644
--- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
+++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
@@ -29,9 +29,9 @@ import com.google.android.vending.expansion.downloader.IDownloaderClient;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
-import android.os.Build;
import android.os.Messenger;
-import android.support.v4.app.NotificationCompat;
+
+import androidx.core.app.NotificationCompat;
/**
* This class handles displaying the notification associated with the download
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
index 594cae774b..8b7a9c6c74 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
@@ -34,16 +34,13 @@ import java.util.HashMap;
import java.util.Set;
public class Dictionary extends HashMap<String, Object> {
-
protected String[] keys_cache;
public String[] get_keys() {
-
String[] ret = new String[size()];
int i = 0;
Set<String> keys = keySet();
for (String key : keys) {
-
ret[i] = key;
i++;
};
@@ -52,12 +49,10 @@ public class Dictionary extends HashMap<String, Object> {
};
public Object[] get_values() {
-
Object[] ret = new Object[size()];
int i = 0;
Set<String> keys = keySet();
for (String key : keys) {
-
ret[i] = get(key);
i++;
};
@@ -70,7 +65,6 @@ public class Dictionary extends HashMap<String, Object> {
};
public void set_values(Object[] vals) {
-
int i = 0;
for (String key : keys_cache) {
put(key, vals[i]);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index c78e8c1c66..5aa48d87da 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* CustomSSLSocketFactory.java */
+/* FullScreenGodotApp.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,42 +28,72 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-package org.godotengine.godot.utils;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
-import org.apache.http.conn.ssl.SSLSocketFactory;
+package org.godotengine.godot;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
/**
+ * Base activity for Android apps intending to use Godot as the primary and only screen.
*
- * @author Luis Linietsky <luis.linietsky@gmail.com>
+ * It's also a reference implementation for how to setup and use the {@link Godot} fragment
+ * within an Android app.
*/
-public class CustomSSLSocketFactory extends SSLSocketFactory {
- SSLContext sslContext = SSLContext.getInstance("TLS");
+public abstract class FullScreenGodotApp extends FragmentActivity {
+ @Nullable
+ private Godot godotFragment;
- public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- super(truststore);
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.godot_app_layout);
+ godotFragment = initGodotInstance();
+ if (godotFragment == null) {
+ throw new IllegalStateException("Godot instance must be non-null.");
+ }
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
- tmf.init(truststore);
+ getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
+ }
- sslContext.init(null, tmf.getTrustManagers(), null);
+ @Override
+ public void onNewIntent(Intent intent) {
+ if (godotFragment != null) {
+ godotFragment.onNewIntent(intent);
+ }
}
@Override
- public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
- return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+ public void onBackPressed() {
+ if (godotFragment != null) {
+ godotFragment.onBackPressed();
+ } else {
+ super.onBackPressed();
+ }
}
@Override
- public Socket createSocket() throws IOException {
- return sslContext.getSocketFactory().createSocket();
+ public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) {
+ if (godotFragment != null && godotFragment.onKeyMultiple(inKeyCode, repeatCount, event)) {
+ return true;
+ }
+ return super.onKeyMultiple(inKeyCode, repeatCount, event);
+ }
+
+ /**
+ * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}.
+ */
+ @NonNull
+ protected Godot initGodotInstance() {
+ return new Godot();
+ }
+
+ @Nullable
+ protected final Godot getGodotFragment() {
+ return godotFragment;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 2f6a93fbb1..524f32bf5e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -30,6 +30,16 @@
package org.godotengine.godot;
+import static android.content.Context.MODE_PRIVATE;
+import static android.content.Context.WINDOW_SERVICE;
+
+import org.godotengine.godot.input.GodotEditText;
+import org.godotengine.godot.plugin.GodotPlugin;
+import org.godotengine.godot.plugin.GodotPluginRegistry;
+import org.godotengine.godot.utils.GodotNetUtils;
+import org.godotengine.godot.utils.PermissionsUtil;
+import org.godotengine.godot.xr.XRMode;
+
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
@@ -55,16 +65,13 @@ import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings.Secure;
-import android.support.annotation.Keep;
-import android.support.annotation.Nullable;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
@@ -77,6 +84,12 @@ import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
@@ -84,25 +97,16 @@ import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
-import java.lang.reflect.Method;
import java.security.MessageDigest;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import javax.microedition.khronos.opengles.GL10;
-import org.godotengine.godot.input.GodotEditText;
-import org.godotengine.godot.payments.PaymentsManager;
-import org.godotengine.godot.utils.GodotNetUtils;
-import org.godotengine.godot.utils.PermissionsUtil;
-import org.godotengine.godot.xr.XRMode;
-
-public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient {
- static final int MAX_SINGLETONS = 64;
+public class Godot extends Fragment implements SensorEventListener, IDownloaderClient {
private IStub mDownloaderClientStub;
private TextView mStatusText;
private TextView mProgressFraction;
@@ -126,12 +130,10 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
private boolean activityResumed;
private int mState;
- // Used to dispatch events to the main thread.
- private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+ private GodotPluginRegistry pluginRegistry;
static private Intent mCurrentIntent;
- @Override
public void onNewIntent(Intent intent) {
mCurrentIntent = intent;
}
@@ -149,92 +151,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
- int stringResourceID = paused ? R.string.text_button_resume :
- R.string.text_button_pause;
+ int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
- static public class SingletonBase {
-
- protected void registerClass(String p_name, String[] p_methods) {
-
- GodotLib.singleton(p_name, this);
-
- Class clazz = getClass();
- Method[] methods = clazz.getDeclaredMethods();
- for (Method method : methods) {
- boolean found = false;
-
- for (String s : p_methods) {
- if (s.equals(method.getName())) {
- found = true;
- break;
- }
- }
- if (!found)
- continue;
-
- List<String> ptr = new ArrayList<String>();
-
- Class[] paramTypes = method.getParameterTypes();
- for (Class c : paramTypes) {
- ptr.add(c.getName());
- }
-
- String[] pt = new String[ptr.size()];
- ptr.toArray(pt);
-
- GodotLib.method(p_name, method.getName(), method.getReturnType().getName(), pt);
- }
-
- Godot.singletons[Godot.singleton_count++] = this;
- }
-
- /**
- * Invoked once during the Godot Android initialization process after creation of the
- * {@link GodotView} view.
- * <p>
- * This method should be overridden by descendants of this class that would like to add
- * their view/layout to the Godot view hierarchy.
- *
- * @return the view to be included; null if no views should be included.
- */
- @Nullable
- protected View onMainCreateView(Activity activity) {
- return null;
- }
-
- protected void onMainActivityResult(int requestCode, int resultCode, Intent data) {
- }
-
- protected void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- }
-
- protected void onMainPause() {}
- protected void onMainResume() {}
- protected void onMainDestroy() {}
- protected boolean onMainBackPressed() { return false; }
-
- protected void onGLDrawFrame(GL10 gl) {}
- protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call
- //protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step()
-
- public void registerMethods() {}
- }
-
- /*
- protected List<SingletonBase> singletons = new ArrayList<SingletonBase>();
- protected void instanceSingleton(SingletonBase s) {
-
- s.registerMethods();
- singletons.add(s);
- }
- */
-
private String[] command_line;
private boolean use_apk_expansion;
- public GodotView mView;
+ private ViewGroup containerLayout;
+ public GodotRenderView mRenderView;
private boolean godot_initialized = false;
private SensorManager mSensorManager;
@@ -246,35 +171,27 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
public static GodotIO io;
public static GodotNetUtils netUtils;
- static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS];
- static int singleton_count = 0;
-
public interface ResultCallback {
public void callback(int requestCode, int resultCode, Intent data);
}
public ResultCallback result_callback;
- private PaymentsManager mPaymentsManager = null;
-
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) {
- mPaymentsManager.processPurchaseResponse(resultCode, data);
- } else if (result_callback != null) {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (result_callback != null) {
result_callback.callback(requestCode, resultCode, data);
result_callback = null;
- };
-
- for (int i = 0; i < singleton_count; i++) {
+ }
- singletons[i].onMainActivityResult(requestCode, resultCode, data);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainActivityResult(requestCode, resultCode, data);
}
- };
+ }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults);
}
for (int i = 0; i < permissions.length; i++) {
@@ -283,63 +200,78 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
};
/**
+ * Invoked on the render thread when the Godot main loop has started.
+ */
+ @CallSuper
+ protected void onGodotMainLoopStarted() {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGodotMainLoopStarted();
+ }
+ }
+
+ /**
* Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer.
*/
@Keep
private void onVideoInit() {
- boolean use_gl3 = getGLESVersionCode() >= 0x00030000;
-
- final FrameLayout layout = new FrameLayout(this);
- layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- setContentView(layout);
+ final Activity activity = getActivity();
+ containerLayout = new FrameLayout(activity);
+ containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
// GodotEditText layout
- GodotEditText edittext = new GodotEditText(this);
- edittext.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ GodotEditText editText = new GodotEditText(activity);
+ editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ (int)getResources().getDimension(R.dimen.text_edit_height)));
// ...add to FrameLayout
- layout.addView(edittext);
+ containerLayout.addView(editText);
+
+ GodotLib.setup(command_line);
+
+ final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name");
+ if (videoDriver.equals("Vulkan")) {
+ mRenderView = new GodotVulkanRenderView(activity, this);
+ } else {
+ mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits,
+ use_debug_opengl);
+ }
- mView = new GodotView(this, xrMode, use_gl3, use_32_bits, use_debug_opengl);
- layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- edittext.setView(mView);
- io.setEdit(edittext);
+ View view = mRenderView.getView();
+ containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ editText.setView(mRenderView);
+ io.setEdit(editText);
- final Godot godot = this;
- mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Point fullSize = new Point();
- godot.getWindowManager().getDefaultDisplay().getSize(fullSize);
+ activity.getWindowManager().getDefaultDisplay().getSize(fullSize);
Rect gameSize = new Rect();
- godot.mView.getWindowVisibleDisplayFrame(gameSize);
+ mRenderView.getView().getWindowVisibleDisplayFrame(gameSize);
final int keyboardHeight = fullSize.y - gameSize.bottom;
GodotLib.setVirtualKeyboardHeight(keyboardHeight);
}
});
- final String[] current_command_line = command_line;
- mView.queueEvent(new Runnable() {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
- GodotLib.setup(current_command_line);
- setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
+ // Must occur after GodotLib.setup has completed.
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onRegisterPluginWithGodotNative();
+ }
- // The Godot Android plugins are setup on completion of GodotLib.setup
- mainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- // Include the non-null views returned in the Godot view hierarchy.
- for (int i = 0; i < singleton_count; i++) {
- View view = singletons[i].onMainCreateView(Godot.this);
- if (view != null) {
- layout.addView(view);
- }
- }
- }
- });
+ setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
}
});
+
+ // Include the returned non-null views in the Godot view hierarchy.
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ View pluginView = plugin.onMainCreate(activity);
+ if (pluginView != null) {
+ containerLayout.addView(pluginView);
+ }
+ }
}
public void setKeepScreenOn(final boolean p_enabled) {
@@ -347,9 +279,9 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
@Override
public void run() {
if (p_enabled) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
});
@@ -363,7 +295,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
@Keep
private void vibrate(int durationMs) {
if (requestPermission("VIBRATE")) {
- Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
+ Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE));
@@ -387,13 +319,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
// Using instrumentation is a way of making the whole app process restart, because Android
// will kill any process of the same package which was already running.
//
- Bundle args = new Bundle();
- args.putParcelable("intent", mCurrentIntent);
- startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args);
+ final Activity activity = getActivity();
+ if (activity != null) {
+ Bundle args = new Bundle();
+ args.putParcelable("intent", mCurrentIntent);
+ activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args);
+ }
}
public void alert(final String message, final String title) {
- final Activity activity = this;
+ final Activity activity = getActivity();
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -413,15 +348,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
public int getGLESVersionCode() {
- ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo();
return deviceInfo.reqGlEsVersion;
}
- private String[] getCommandLine() {
+ @CallSuper
+ protected String[] getCommandLine() {
InputStream is;
try {
- is = getAssets().open("_cl_");
+ is = getActivity().getAssets().open("_cl_");
byte[] len = new byte[4];
int r = is.read(len);
if (r < 4) {
@@ -433,7 +369,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
for (int i = 0; i < argc; i++) {
r = is.read(len);
if (r < 4) {
-
return new String[0];
}
int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF));
@@ -466,7 +401,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
*/
@Keep
private Surface getSurface() {
- return mView.getHolder().getSurface();
+ return mRenderView.getView().getHolder().getSurface();
}
/**
@@ -481,9 +416,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
String expansion_pack_path;
private void initializeGodot() {
-
if (expansion_pack_path != null) {
-
String[] new_cmdline;
int cll = 0;
if (command_line != null) {
@@ -501,11 +434,12 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
command_line = new_cmdline;
}
- io = new GodotIO(this);
- io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
+ final Activity activity = getActivity();
+ io = new GodotIO(activity);
+ io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
- netUtils = new GodotNetUtils(this);
- mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
+ netUtils = new GodotNetUtils(activity);
+ mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
@@ -515,12 +449,10 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
- GodotLib.initialize(this, getAssets(), use_apk_expansion);
+ GodotLib.initialize(activity, this, activity.getAssets(), use_apk_expansion);
result_callback = null;
- mPaymentsManager = PaymentsManager.createManager(this).initService();
-
godot_initialized = true;
}
@@ -531,157 +463,152 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
@Override
- protected void onCreate(Bundle icicle) {
-
- super.onCreate(icicle);
- Window window = getWindow();
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
+ final Activity activity = getActivity();
+ Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
+ mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
//check for apk expansion API
- if (true) {
- boolean md5mismatch = false;
- command_line = getCommandLine();
- String main_pack_md5 = null;
- String main_pack_key = null;
-
- List<String> new_args = new LinkedList<String>();
-
- for (int i = 0; i < command_line.length; i++) {
-
- boolean has_extra = i < command_line.length - 1;
- if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) {
- xrMode = XRMode.REGULAR;
- } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) {
- xrMode = XRMode.OVR;
- } else if (command_line[i].equals("--use_depth_32")) {
- use_32_bits = true;
- } else if (command_line[i].equals("--debug_opengl")) {
- use_debug_opengl = true;
- } else if (command_line[i].equals("--use_immersive")) {
- use_immersive = true;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
- window.getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
- UiChangeListener();
- }
- } else if (command_line[i].equals("--use_apk_expansion")) {
- use_apk_expansion = true;
- } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
- main_pack_md5 = command_line[i + 1];
- i++;
- } else if (has_extra && command_line[i].equals("--apk_expansion_key")) {
- main_pack_key = command_line[i + 1];
- SharedPreferences prefs = getSharedPreferences("app_data_keys", MODE_PRIVATE);
- Editor editor = prefs.edit();
- editor.putString("store_public_key", main_pack_key);
-
- editor.apply();
- i++;
- } else if (command_line[i].trim().length() != 0) {
- new_args.add(command_line[i]);
+ boolean md5mismatch = false;
+ command_line = getCommandLine();
+ String main_pack_md5 = null;
+ String main_pack_key = null;
+
+ List<String> new_args = new LinkedList<String>();
+
+ for (int i = 0; i < command_line.length; i++) {
+ boolean has_extra = i < command_line.length - 1;
+ if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) {
+ xrMode = XRMode.REGULAR;
+ } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) {
+ xrMode = XRMode.OVR;
+ } else if (command_line[i].equals("--use_depth_32")) {
+ use_32_bits = true;
+ } else if (command_line[i].equals("--debug_opengl")) {
+ use_debug_opengl = true;
+ } else if (command_line[i].equals("--use_immersive")) {
+ use_immersive = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
+ window.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
+ View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ UiChangeListener();
}
+ } else if (command_line[i].equals("--use_apk_expansion")) {
+ use_apk_expansion = true;
+ } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
+ main_pack_md5 = command_line[i + 1];
+ i++;
+ } else if (has_extra && command_line[i].equals("--apk_expansion_key")) {
+ main_pack_key = command_line[i + 1];
+ SharedPreferences prefs = activity.getSharedPreferences("app_data_keys",
+ MODE_PRIVATE);
+ Editor editor = prefs.edit();
+ editor.putString("store_public_key", main_pack_key);
+
+ editor.apply();
+ i++;
+ } else if (command_line[i].trim().length() != 0) {
+ new_args.add(command_line[i]);
}
+ }
- if (new_args.isEmpty()) {
- command_line = null;
- } else {
-
- command_line = new_args.toArray(new String[new_args.size()]);
+ if (new_args.isEmpty()) {
+ command_line = null;
+ } else {
+ command_line = new_args.toArray(new String[new_args.size()]);
+ }
+ if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
+ //check that environment is ok!
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ //show popup and die
}
- if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
- //check that environment is ok!
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- //show popup and die
- }
-
- // Build the full path to the app's expansion files
- try {
- expansion_pack_path = Helpers.getSaveFilePath(getApplicationContext());
- expansion_pack_path += "/main." + getPackageManager().getPackageInfo(getPackageName(), 0).versionCode + "." + this.getPackageName() + ".obb";
- } catch (Exception e) {
- e.printStackTrace();
- }
- File f = new File(expansion_pack_path);
+ // Build the full path to the app's expansion files
+ try {
+ expansion_pack_path = Helpers.getSaveFilePath(getContext());
+ expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb";
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
- boolean pack_valid = true;
+ File f = new File(expansion_pack_path);
- if (!f.exists()) {
+ boolean pack_valid = true;
- pack_valid = false;
+ if (!f.exists()) {
+ pack_valid = false;
- } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) {
- pack_valid = false;
- try {
- f.delete();
- } catch (Exception e) {
- }
+ } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) {
+ pack_valid = false;
+ try {
+ f.delete();
+ } catch (Exception e) {
}
+ }
- if (!pack_valid) {
-
- Intent notifierIntent = new Intent(this, this.getClass());
- notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (!pack_valid) {
+ Intent notifierIntent = new Intent(activity, activity.getClass());
+ notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0,
+ notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- int startResult;
- try {
- startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
- getApplicationContext(),
- pendingIntent,
+ int startResult;
+ try {
+ startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
+ getContext(),
+ pendingIntent,
+ GodotDownloaderService.class);
+
+ if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
+ // This is where you do set up to display the download
+ // progress (next step)
+ mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
GodotDownloaderService.class);
- if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
- // This is where you do set up to display the download
- // progress (next step)
- mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
- GodotDownloaderService.class);
-
- setContentView(R.layout.downloading_expansion);
- mPB = (ProgressBar)findViewById(R.id.progressBar);
- mStatusText = (TextView)findViewById(R.id.statusText);
- mProgressFraction = (TextView)findViewById(R.id.progressAsFraction);
- mProgressPercent = (TextView)findViewById(R.id.progressAsPercentage);
- mAverageSpeed = (TextView)findViewById(R.id.progressAverageSpeed);
- mTimeRemaining = (TextView)findViewById(R.id.progressTimeRemaining);
- mDashboard = findViewById(R.id.downloaderDashboard);
- mCellMessage = findViewById(R.id.approveCellular);
- mPauseButton = (Button)findViewById(R.id.pauseButton);
- mWiFiSettingsButton = (Button)findViewById(R.id.wifiSettingsButton);
-
- return;
- }
- } catch (NameNotFoundException e) {
- // TODO Auto-generated catch block
+ View downloadingExpansionView =
+ inflater.inflate(R.layout.downloading_expansion, container, false);
+ mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
+ mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
+ mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
+ mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
+ mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
+ mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
+ mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
+ mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
+ mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
+ mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
+
+ return downloadingExpansionView;
}
+ } catch (NameNotFoundException e) {
+ // TODO Auto-generated catch block
}
}
}
- mCurrentIntent = getIntent();
+ mCurrentIntent = activity.getIntent();
initializeGodot();
+ return containerLayout;
}
@Override
- protected void onDestroy() {
-
- if (mPaymentsManager != null) mPaymentsManager.destroy();
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainDestroy();
+ public void onDestroy() {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainDestroy();
}
- GodotLib.ondestroy(this);
+ GodotLib.ondestroy();
super.onDestroy();
@@ -691,27 +618,26 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
@Override
- protected void onPause() {
+ public void onPause() {
super.onPause();
activityResumed = false;
if (!godot_initialized) {
if (null != mDownloaderClientStub) {
- mDownloaderClientStub.disconnect(this);
+ mDownloaderClientStub.disconnect(getActivity());
}
return;
}
- mView.onPause();
+ mRenderView.onActivityPaused();
mSensorManager.unregisterListener(this);
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainPause();
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainPause();
}
}
public String getClipboard() {
-
String copiedText = "";
if (mClipboard.getPrimaryClip() != null) {
@@ -723,23 +649,22 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
public void setClipboard(String p_text) {
-
ClipData clip = ClipData.newPlainText("myLabel", p_text);
mClipboard.setPrimaryClip(clip);
}
@Override
- protected void onResume() {
+ public void onResume() {
super.onResume();
activityResumed = true;
if (!godot_initialized) {
if (null != mDownloaderClientStub) {
- mDownloaderClientStub.connect(this);
+ mDownloaderClientStub.connect(getActivity());
}
return;
}
- mView.onResume();
+ mRenderView.onActivityResumed();
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME);
@@ -747,7 +672,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
- Window window = getWindow();
+ Window window = getActivity().getWindow();
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
@@ -757,14 +682,13 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
- for (int i = 0; i < singleton_count; i++) {
-
- singletons[i].onMainResume();
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainResume();
}
}
public void UiChangeListener() {
- final View decorView = getWindow().getDecorView();
+ final View decorView = getActivity().getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
@@ -785,7 +709,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
@Override
public void onSensorChanged(SensorEvent event) {
- Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
+ Display display =
+ ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
int displayRotation = display.getRotation();
float[] adjustedValues = new float[3];
@@ -806,8 +731,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
final float z = adjustedValues[2];
final int typeOfSensor = event.sensor.getType();
- if (mView != null) {
- mView.queueEvent(new Runnable() {
+ if (mRenderView != null) {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
if (typeOfSensor == Sensor.TYPE_ACCELEROMETER) {
@@ -848,18 +773,17 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
*/
- @Override
public void onBackPressed() {
boolean shouldQuit = true;
- for (int i = 0; i < singleton_count; i++) {
- if (singletons[i].onMainBackPressed()) {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ if (plugin.onMainBackPressed()) {
shouldQuit = false;
}
}
- if (shouldQuit && mView != null) {
- mView.queueEvent(new Runnable() {
+ if (shouldQuit && mRenderView != null) {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
GodotLib.back();
@@ -868,14 +792,29 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
}
+ /**
+ * Queue a runnable to be run on the render thread.
+ * <p>
+ * This must be called after the render thread has started.
+ */
+ public final void runOnRenderThread(@NonNull Runnable action) {
+ if (mRenderView != null) {
+ mRenderView.queueOnRenderThread(action);
+ }
+ }
+
+ public final void runOnUiThread(@NonNull Runnable action) {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(action);
+ }
+ }
+
private void forceQuit() {
System.exit(0);
}
private boolean obbIsCorrupted(String f, String main_pack_md5) {
-
try {
-
InputStream fis = new FileInputStream(f);
// Create MD5 Hash
@@ -916,16 +855,14 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
public boolean gotTouchEvent(final MotionEvent event) {
-
final int evcount = event.getPointerCount();
if (evcount == 0)
return true;
- if (mView != null) {
+ if (mRenderView != null) {
final int[] arr = new int[event.getPointerCount() * 3];
for (int i = 0; i < event.getPointerCount(); i++) {
-
arr[i * 3 + 0] = (int)event.getPointerId(i);
arr[i * 3 + 1] = (int)event.getX(i);
arr[i * 3 + 2] = (int)event.getY(i);
@@ -934,7 +871,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
//System.out.printf("gaction: %d\n",event.getAction());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
- mView.queueEvent(new Runnable() {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
switch (action) {
@@ -974,26 +911,26 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
return true;
}
- @Override
public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) {
String s = event.getCharacters();
if (s == null || s.length() == 0)
- return super.onKeyMultiple(inKeyCode, repeatCount, event);
+ return false;
final char[] cc = s.toCharArray();
int cnt = 0;
for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0)
;
- if (cnt == 0) return super.onKeyMultiple(inKeyCode, repeatCount, event);
- mView.queueEvent(new Runnable() {
+ if (cnt == 0)
+ return false;
+ mRenderView.queueOnRenderThread(new Runnable() {
// This method will be called on the rendering thread:
public void run() {
for (int i = 0, n = cc.length; i < n; i++) {
int keyCode;
if ((keyCode = cc[i]) != 0) {
// Simulate key down and up...
- GodotLib.key(0, keyCode, true);
- GodotLib.key(0, keyCode, false);
+ GodotLib.key(0, 0, keyCode, true);
+ GodotLib.key(0, 0, keyCode, false);
}
}
}
@@ -1001,20 +938,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
return true;
}
- public PaymentsManager getPaymentsManager() {
- return mPaymentsManager;
- }
-
public boolean requestPermission(String p_name) {
- return PermissionsUtil.requestPermission(p_name, this);
+ return PermissionsUtil.requestPermission(p_name, getActivity());
}
public boolean requestPermissions() {
- return PermissionsUtil.requestManifestPermissions(this);
+ return PermissionsUtil.requestManifestPermissions(getActivity());
}
public String[] getGrantedPermissions() {
- return PermissionsUtil.getGrantedPermissions(this);
+ return PermissionsUtil.getGrantedPermissions(getActivity());
}
/**
@@ -1111,6 +1044,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
progress.mOverallTotal));
}
public void initInputDevices() {
- mView.initInputDevices();
+ mRenderView.initInputDevices();
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
index 1fb242d0bc..a3dae15980 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
@@ -35,6 +35,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
+
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
/**
@@ -45,7 +46,6 @@ import com.google.android.vending.expansion.downloader.DownloaderClientMarshalle
* <receiver android:name=".GodotDownloaderAlarmReceiver"/>
*/
public class GodotDownloaderAlarmReceiver extends BroadcastReceiver {
-
@Override
public void onReceive(Context context, Intent intent) {
Log.d("GODOT", "Alarma recivida");
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
index 7e74e8a80d..434da95bc0 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
@@ -33,6 +33,7 @@ package org.godotengine.godot;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
+
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index f938583082..d169f46599 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* GodotView.java */
+/* GodotGLRenderView.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -29,12 +29,7 @@
/*************************************************************************/
package org.godotengine.godot;
-import android.annotation.SuppressLint;
-import android.graphics.PixelFormat;
-import android.opengl.GLSurfaceView;
-import android.view.GestureDetector;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
+
import org.godotengine.godot.input.GodotGestureHandler;
import org.godotengine.godot.input.GodotInputHandler;
import org.godotengine.godot.utils.GLUtils;
@@ -46,6 +41,15 @@ import org.godotengine.godot.xr.regular.RegularConfigChooser;
import org.godotengine.godot.xr.regular.RegularContextFactory;
import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+
/**
* A simple GLSurfaceView sub-class that demonstrate how to perform
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
@@ -64,38 +68,66 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
* that matches it exactly (with regards to red/green/blue/alpha channels
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
-public class GodotView extends GLSurfaceView {
-
- private static String TAG = GodotView.class.getSimpleName();
-
- private final Godot activity;
+public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
+ private final Godot godot;
private final GodotInputHandler inputHandler;
private final GestureDetector detector;
private final GodotRenderer godotRenderer;
- public GodotView(Godot activity, XRMode xrMode, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl) {
- super(activity);
- GLUtils.use_gl3 = p_use_gl3;
+ public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits,
+ boolean p_use_debug_opengl) {
+ super(context);
GLUtils.use_32 = p_use_32_bits;
GLUtils.use_debug_opengl = p_use_debug_opengl;
- this.activity = activity;
+ this.godot = godot;
this.inputHandler = new GodotInputHandler(this);
- this.detector = new GestureDetector(activity, new GodotGestureHandler(this));
+ this.detector = new GestureDetector(context, new GodotGestureHandler(this));
this.godotRenderer = new GodotRenderer();
init(xrMode, false, 16, 0);
}
+ @Override
+ public SurfaceView getView() {
+ return this;
+ }
+
+ @Override
public void initInputDevices() {
this.inputHandler.initInputDevices();
}
+ @Override
+ public void queueOnRenderThread(Runnable event) {
+ queueEvent(event);
+ }
+
+ @Override
+ public void onActivityPaused() {
+ onPause();
+ }
+
+ @Override
+ public void onActivityResumed() {
+ onResume();
+ }
+
+ @Override
+ public void onBackPressed() {
+ godot.onBackPressed();
+ }
+
+ @Override
+ public GodotInputHandler getInputHandler() {
+ return inputHandler;
+ }
+
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
this.detector.onTouchEvent(event);
- return activity.gotTouchEvent(event);
+ return godot.gotTouchEvent(event);
}
@Override
@@ -114,11 +146,9 @@ public class GodotView extends GLSurfaceView {
}
private void init(XRMode xrMode, boolean translucent, int depth, int stencil) {
-
setPreserveEGLContextOnPause(true);
setFocusableInTouchMode(true);
switch (xrMode) {
-
case OVR:
// Replace the default egl config chooser.
setEGLConfigChooser(new OvrConfigChooser());
@@ -171,10 +201,6 @@ public class GodotView extends GLSurfaceView {
setRenderer(godotRenderer);
}
- public void onBackPressed() {
- activity.onBackPressed();
- }
-
@Override
public void onResume() {
super.onResume();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 68ce40ba10..c2f3c88416 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -29,6 +29,10 @@
/*************************************************************************/
package org.godotengine.godot;
+
+import org.godotengine.godot.input.*;
+
+import android.app.Activity;
import android.content.*;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -39,22 +43,18 @@ import android.os.*;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
-import org.godotengine.godot.input.*;
-//android.os.Build
// Wrapper for native library
public class GodotIO {
-
AssetManager am;
- Godot activity;
+ final Activity activity;
GodotEditText edit;
- MediaPlayer mediaPlayer;
-
final int SCREEN_LANDSCAPE = 0;
final int SCREEN_PORTRAIT = 1;
final int SCREEN_REVERSE_LANDSCAPE = 2;
@@ -70,7 +70,6 @@ public class GodotIO {
public int last_file_id = 1;
class AssetData {
-
public boolean eof = false;
public String path;
public InputStream is;
@@ -81,7 +80,6 @@ public class GodotIO {
SparseArray<AssetData> streams;
public int file_open(String path, boolean write) {
-
//System.out.printf("file_open: Attempt to Open %s\n",path);
//Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
@@ -94,7 +92,6 @@ public class GodotIO {
ad.is = am.open(path);
} catch (Exception e) {
-
//System.out.printf("Exception on file_open: %s\n",path);
return -1;
}
@@ -102,7 +99,6 @@ public class GodotIO {
try {
ad.len = ad.is.available();
} catch (Exception e) {
-
System.out.printf("Exception availabling on file_open: %s\n", path);
return -1;
}
@@ -115,7 +111,6 @@ public class GodotIO {
return last_file_id;
}
public int file_get_size(int id) {
-
if (streams.get(id) == null) {
System.out.printf("file_get_size: Invalid file id: %d\n", id);
return -1;
@@ -124,7 +119,6 @@ public class GodotIO {
return streams.get(id).len;
}
public void file_seek(int id, int bytes) {
-
if (streams.get(id) == null) {
System.out.printf("file_get_size: Invalid file id: %d\n", id);
return;
@@ -137,7 +131,6 @@ public class GodotIO {
bytes = 0;
try {
-
if (bytes > (int)ad.pos) {
int todo = bytes - (int)ad.pos;
while (todo > 0) {
@@ -145,7 +138,6 @@ public class GodotIO {
}
ad.pos = bytes;
} else if (bytes < (int)ad.pos) {
-
ad.is = am.open(ad.path);
ad.pos = bytes;
@@ -157,14 +149,12 @@ public class GodotIO {
ad.eof = false;
} catch (IOException e) {
-
System.out.printf("Exception on file_seek: %s\n", e);
return;
}
}
public int file_tell(int id) {
-
if (streams.get(id) == null) {
System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id);
return 0;
@@ -174,7 +164,6 @@ public class GodotIO {
return ad.pos;
}
public boolean file_eof(int id) {
-
if (streams.get(id) == null) {
System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id);
return false;
@@ -185,7 +174,6 @@ public class GodotIO {
}
public byte[] file_read(int id, int bytes) {
-
if (streams.get(id) == null) {
System.out.printf("file_read: Can't read invalid file id: %d\n", id);
return new byte[0];
@@ -194,13 +182,11 @@ public class GodotIO {
AssetData ad = streams.get(id);
if (ad.pos + bytes > ad.len) {
-
bytes = ad.len - ad.pos;
ad.eof = true;
}
if (bytes == 0) {
-
return new byte[0];
}
@@ -209,7 +195,6 @@ public class GodotIO {
try {
r = ad.is.read(buf1);
} catch (IOException e) {
-
System.out.printf("Exception on file_read: %s\n", e);
return new byte[bytes];
}
@@ -221,19 +206,16 @@ public class GodotIO {
ad.pos += r;
if (r < bytes) {
-
byte[] buf2 = new byte[r];
for (int i = 0; i < r; i++)
buf2[i] = buf1[i];
return buf2;
} else {
-
return buf1;
}
}
public void file_close(int id) {
-
if (streams.get(id) == null) {
System.out.printf("file_close: Can't close invalid file id: %d\n", id);
return;
@@ -247,7 +229,6 @@ public class GodotIO {
/////////////////////////
class AssetDir {
-
public String[] files;
public int current;
public String path;
@@ -258,7 +239,6 @@ public class GodotIO {
SparseArray<AssetDir> dirs;
public int dir_open(String path) {
-
AssetDir ad = new AssetDir();
ad.current = 0;
ad.path = path;
@@ -271,7 +251,6 @@ public class GodotIO {
return -1;
}
} catch (IOException e) {
-
System.out.printf("Exception on dir_open: %s\n", e);
return -1;
}
@@ -310,7 +289,6 @@ public class GodotIO {
}
public String dir_next(int id) {
-
if (dirs.get(id) == null) {
System.out.printf("dir_next: invalid dir id: %d\n", id);
return "";
@@ -329,7 +307,6 @@ public class GodotIO {
}
public void dir_close(int id) {
-
if (dirs.get(id) == null) {
System.out.printf("dir_close: invalid dir id: %d\n", id);
return;
@@ -338,8 +315,7 @@ public class GodotIO {
dirs.remove(id);
}
- GodotIO(Godot p_activity) {
-
+ GodotIO(Activity p_activity) {
am = p_activity.getAssets();
activity = p_activity;
//streams = new HashMap<Integer, AssetData>();
@@ -430,7 +406,6 @@ public class GodotIO {
}
public void audioPause(boolean p_pause) {
-
if (p_pause)
mAudioTrack.pause();
else
@@ -442,7 +417,6 @@ public class GodotIO {
/////////////////////////
public int openURI(String p_uri) {
-
try {
Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
String path = p_uri;
@@ -451,7 +425,6 @@ public class GodotIO {
//absolute path to filesystem, prepend file://
path = "file://" + path;
if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) {
-
type = "image/*";
}
}
@@ -467,18 +440,15 @@ public class GodotIO {
activity.startActivity(intent);
return 0;
} catch (ActivityNotFoundException e) {
-
return 1;
}
}
public String getDataDir() {
-
return activity.getFilesDir().getAbsolutePath();
}
public String getLocale() {
-
return Locale.getDefault().toString();
}
@@ -491,9 +461,9 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
- public void showKeyboard(String p_existing_text, int p_max_input_length) {
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
- edit.showKeyboard(p_existing_text, p_max_input_length);
+ edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
@@ -505,9 +475,7 @@ public class GodotIO {
};
public void setScreenOrientation(int p_orientation) {
-
switch (p_orientation) {
-
case SCREEN_LANDSCAPE: {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} break;
@@ -530,44 +498,14 @@ public class GodotIO {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
} break;
}
- };
-
- public void setEdit(GodotEditText _edit) {
- edit = _edit;
}
- public void playVideo(String p_path) {
- Uri filePath = Uri.parse(p_path);
- mediaPlayer = new MediaPlayer();
-
- try {
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setDataSource(activity.getApplicationContext(), filePath);
- mediaPlayer.prepare();
- mediaPlayer.start();
- } catch (IOException e) {
- System.out.println("IOError while playing video");
- }
- }
-
- public boolean isVideoPlaying() {
- if (mediaPlayer != null) {
- return mediaPlayer.isPlaying();
- }
- return false;
- }
-
- public void pauseVideo() {
- if (mediaPlayer != null) {
- mediaPlayer.pause();
- }
+ public int getScreenOrientation() {
+ return activity.getRequestedOrientation();
}
- public void stopVideo() {
- if (mediaPlayer != null) {
- mediaPlayer.release();
- mediaPlayer = null;
- }
+ public void setEdit(GodotEditText _edit) {
+ edit = _edit;
}
public static final int SYSTEM_DIR_DESKTOP = 0;
@@ -580,7 +518,6 @@ public class GodotIO {
public static final int SYSTEM_DIR_RINGTONES = 7;
public String getSystemDir(int idx) {
-
String what = "";
switch (idx) {
case SYSTEM_DIR_DESKTOP: {
@@ -625,7 +562,6 @@ public class GodotIO {
public static String unique_id = "";
public String getUniqueID() {
-
return unique_id;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index e0b46673ba..318e2816ff 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -32,6 +32,8 @@ package org.godotengine.godot;
import android.app.Activity;
import android.hardware.SensorEvent;
+import android.view.Surface;
+
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
@@ -39,7 +41,6 @@ import javax.microedition.khronos.opengles.GL10;
* Wrapper for native library
*/
public class GodotLib {
-
public static GodotIO io;
static {
@@ -49,13 +50,13 @@ public class GodotLib {
/**
* Invoked on the main thread to initialize Godot native layer.
*/
- public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion);
+ public static native void initialize(Activity activity, Godot p_instance, Object p_asset_manager, boolean use_apk_expansion);
/**
* Invoked on the main thread to clean up Godot native layer.
- * @see Activity#onDestroy()
+ * @see androidx.fragment.app.Fragment#onDestroy()
*/
- public static native void ondestroy(Godot p_instance);
+ public static native void ondestroy();
/**
* Invoked on the GL thread to complete setup for the Godot native layer logic.
@@ -65,18 +66,19 @@ public class GodotLib {
/**
* Invoked on the GL thread when the underlying Android surface has changed size.
- * @param width
- * @param height
+ * @param p_surface
+ * @param p_width
+ * @param p_height
* @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int)
*/
- public static native void resize(int width, int height);
+ public static native void resize(Surface p_surface, int p_width, int p_height);
/**
- * Invoked on the GL thread when the underlying Android surface is created or recreated.
+ * Invoked on the render thread when the underlying Android surface is created or recreated.
+ * @param p_surface
* @param p_32_bits
- * @see android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)
*/
- public static native void newcontext(boolean p_32_bits);
+ public static native void newcontext(Surface p_surface, boolean p_32_bits);
/**
* Forward {@link Activity#onBackPressed()} event from the main thread to the GL thread.
@@ -136,7 +138,7 @@ public class GodotLib {
/**
* Forward regular key events from the main thread to the GL thread.
*/
- public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed);
+ public static native void key(int p_keycode, int p_scancode, int p_unicode_char, boolean p_pressed);
/**
* Forward game device's key events from the main thread to the GL thread.
@@ -159,14 +161,14 @@ public class GodotLib {
public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name);
/**
- * Invoked when the Android activity resumes.
- * @see Activity#onResume()
+ * Invoked when the Android app resumes.
+ * @see androidx.fragment.app.Fragment#onResume()
*/
public static native void focusin();
/**
- * Invoked when the Android activity pauses.
- * @see Activity#onPause()
+ * Invoked when the Android app pauses.
+ * @see androidx.fragment.app.Fragment#onPause()
*/
public static native void focusout();
@@ -176,22 +178,6 @@ public class GodotLib {
public static native void audio();
/**
- * Used to setup a {@link org.godotengine.godot.Godot.SingletonBase} instance.
- * @param p_name Name of the instance.
- * @param p_object Reference to the singleton instance.
- */
- public static native void singleton(String p_name, Object p_object);
-
- /**
- * Used to complete registration of the {@link org.godotengine.godot.Godot.SingletonBase} instance's methods.
- * @param p_sname Name of the instance
- * @param p_name Name of the method to register
- * @param p_ret Return type of the registered method
- * @param p_params Method parameters types
- */
- public static native void method(String p_sname, String p_name, String p_ret, String[] p_params);
-
- /**
* Used to access Godot global properties.
* @param p_key Property key
* @return String value of the property
@@ -204,7 +190,7 @@ public class GodotLib {
* @param p_method Name of the method to invoke
* @param p_params Parameters to use for method invocation
*/
- public static native void callobject(int p_id, String p_method, Object[] p_params);
+ public static native void callobject(long p_id, String p_method, Object[] p_params);
/**
* Invoke method |p_method| on the Godot object specified by |p_id| during idle time.
@@ -212,7 +198,7 @@ public class GodotLib {
* @param p_method Name of the method to invoke
* @param p_params Parameters to use for method invocation
*/
- public static native void calldeferred(int p_id, String p_method, Object[] p_params);
+ public static native void calldeferred(long p_id, String p_method, Object[] p_params);
/**
* Forward the results from a permission request.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java
deleted file mode 100644
index 93265d509f..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*************************************************************************/
-/* GodotPaymentV3.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot;
-
-import android.app.Activity;
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import org.godotengine.godot.payments.PaymentsManager;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class GodotPaymentV3 extends Godot.SingletonBase {
-
- private Godot activity;
- private Integer purchaseCallbackId = 0;
- private String accessToken;
- private String purchaseValidationUrlPrefix;
- private String transactionId;
- private PaymentsManager mPaymentManager;
- private Dictionary mSkuDetails = new Dictionary();
-
- public void purchase(final String sku, final String transactionId) {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mPaymentManager.requestPurchase(sku, transactionId);
- }
- });
- }
-
- static public Godot.SingletonBase initialize(Activity p_activity) {
-
- return new GodotPaymentV3(p_activity);
- }
-
- public GodotPaymentV3(Activity p_activity) {
-
- registerClass("GodotPayments", new String[] { "purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected" });
- activity = (Godot)p_activity;
- mPaymentManager = activity.getPaymentsManager();
- mPaymentManager.setBaseSingleton(this);
- }
-
- public void consumeUnconsumedPurchases() {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mPaymentManager.consumeUnconsumedPurchases();
- }
- });
- }
-
- private String signature;
-
- public String getSignature() {
- return this.signature;
- }
-
- public void callbackSuccess(String ticket, String signature, String sku) {
- GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku });
- }
-
- public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
- Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku);
- GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku });
- }
-
- public void callbackSuccessNoUnconsumedPurchases() {
- GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {});
- }
-
- public void callbackFailConsume(String message) {
- GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message });
- }
-
- public void callbackFail(String message) {
- GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message });
- }
-
- public void callbackCancel() {
- GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {});
- }
-
- public void callbackAlreadyOwned(String sku) {
- GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku });
- }
-
- public int getPurchaseCallbackId() {
- return purchaseCallbackId;
- }
-
- public void setPurchaseCallbackId(int purchaseCallbackId) {
- this.purchaseCallbackId = purchaseCallbackId;
- }
-
- public String getPurchaseValidationUrlPrefix() {
- return this.purchaseValidationUrlPrefix;
- }
-
- public void setPurchaseValidationUrlPrefix(String url) {
- this.purchaseValidationUrlPrefix = url;
- }
-
- public String getAccessToken() {
- return accessToken;
- }
-
- public void setAccessToken(String accessToken) {
- this.accessToken = accessToken;
- }
-
- public void setTransactionId(String transactionId) {
- this.transactionId = transactionId;
- }
-
- public String getTransactionId() {
- return this.transactionId;
- }
-
- // request purchased items are not consumed
- public void requestPurchased() {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mPaymentManager.requestPurchased();
- }
- });
- }
-
- // callback for requestPurchased()
- public void callbackPurchased(String receipt, String signature, String sku) {
- GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku });
- }
-
- public void callbackDisconnected() {
- GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {});
- }
-
- public void callbackConnected() {
- GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {});
- }
-
- // true if connected, false otherwise
- public boolean isConnected() {
- return mPaymentManager.isConnected();
- }
-
- // consume item automatically after purchase. default is true.
- public void setAutoConsume(boolean autoConsume) {
- mPaymentManager.setAutoConsume(autoConsume);
- }
-
- // consume a specific item
- public void consume(String sku) {
- mPaymentManager.consume(sku);
- }
-
- // query in app item detail info
- public void querySkuDetails(String[] list) {
- List<String> nKeys = Arrays.asList(list);
- List<String> cKeys = Arrays.asList(mSkuDetails.get_keys());
- ArrayList<String> fKeys = new ArrayList<String>();
- for (String key : nKeys) {
- if (!cKeys.contains(key)) {
- fKeys.add(key);
- }
- }
- if (fKeys.size() > 0) {
- mPaymentManager.querySkuDetails(fKeys.toArray(new String[0]));
- } else {
- completeSkuDetail();
- }
- }
-
- public void addSkuDetail(String itemJson) {
- JSONObject o = null;
- try {
- o = new JSONObject(itemJson);
- Dictionary item = new Dictionary();
- item.put("type", o.optString("type"));
- item.put("product_id", o.optString("productId"));
- item.put("title", o.optString("title"));
- item.put("description", o.optString("description"));
- item.put("price", o.optString("price"));
- item.put("price_currency_code", o.optString("price_currency_code"));
- item.put("price_amount", 0.000001d * o.optLong("price_amount_micros"));
- mSkuDetails.put(item.get("product_id").toString(), item);
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
-
- public void completeSkuDetail() {
- GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails });
- }
-
- public void errorSkuDetail(String errorMessage) {
- GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage });
- }
-}
diff --git a/platform/iphone/platform_refcount.h b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 9029418462..68b8a16641 100644
--- a/platform/iphone/platform_refcount.h
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* platform_refcount.h */
+/* GodotRenderView.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,21 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/safe_refcount.h"
+package org.godotengine.godot;
-#ifdef IPHONE_ENABLED
+import org.godotengine.godot.input.GodotInputHandler;
-#define REFCOUNT_T int
-#define REFCOUNT_GET_T int const volatile &
+import android.view.SurfaceView;
-#include <libkern/OSAtomic.h>
+public interface GodotRenderView {
+ abstract public SurfaceView getView();
-inline int atomic_conditional_increment(volatile int *v) {
- return (*v == 0) ? 0 : OSAtomicIncrement32(v);
-}
+ abstract public void initInputDevices();
-inline int atomic_decrement(volatile int *v) {
- return OSAtomicDecrement32(v);
-}
+ abstract public void queueOnRenderThread(Runnable event);
+
+ abstract public void onActivityPaused();
+ abstract public void onActivityResumed();
-#endif
+ abstract public void onBackPressed();
+
+ abstract public GodotInputHandler getInputHandler();
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
index 26fa033f12..64395f7d1e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
@@ -30,18 +30,27 @@
package org.godotengine.godot;
+import org.godotengine.godot.plugin.GodotPlugin;
+import org.godotengine.godot.plugin.GodotPluginRegistry;
+import org.godotengine.godot.utils.GLUtils;
+
+import android.content.Context;
import android.opengl.GLSurfaceView;
+
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
-import org.godotengine.godot.utils.GLUtils;
/**
* Godot's renderer implementation.
*/
class GodotRenderer implements GLSurfaceView.Renderer {
-
+ private final GodotPluginRegistry pluginRegistry;
private boolean activityJustResumed = false;
+ GodotRenderer() {
+ this.pluginRegistry = GodotPluginRegistry.getPluginRegistry();
+ }
+
public void onDrawFrame(GL10 gl) {
if (activityJustResumed) {
GodotLib.onRendererResumed();
@@ -49,21 +58,23 @@ class GodotRenderer implements GLSurfaceView.Renderer {
}
GodotLib.step();
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLDrawFrame(gl);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLDrawFrame(gl);
}
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
-
- GodotLib.resize(width, height);
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLSurfaceChanged(gl, width, height);
+ GodotLib.resize(null, width, height);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLSurfaceChanged(gl, width, height);
}
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- GodotLib.newcontext(GLUtils.use_32);
+ GodotLib.newcontext(null, GLUtils.use_32);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLSurfaceCreated(gl, config);
+ }
}
void onActivityResumed() {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
new file mode 100644
index 0000000000..65708389c3
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -0,0 +1,148 @@
+/*************************************************************************/
+/* GodotVulkanRenderView.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot;
+
+import org.godotengine.godot.input.GodotGestureHandler;
+import org.godotengine.godot.input.GodotInputHandler;
+import org.godotengine.godot.vulkan.VkRenderer;
+import org.godotengine.godot.vulkan.VkSurfaceView;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+
+public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
+ private final Godot godot;
+ private final GodotInputHandler mInputHandler;
+ private final GestureDetector mGestureDetector;
+ private final VkRenderer mRenderer;
+
+ public GodotVulkanRenderView(Context context, Godot godot) {
+ super(context);
+
+ this.godot = godot;
+ mInputHandler = new GodotInputHandler(this);
+ mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this));
+ mRenderer = new VkRenderer();
+
+ setFocusableInTouchMode(true);
+ startRenderer(mRenderer);
+ }
+
+ @Override
+ public SurfaceView getView() {
+ return this;
+ }
+
+ @Override
+ public void initInputDevices() {
+ mInputHandler.initInputDevices();
+ }
+
+ @Override
+ public void queueOnRenderThread(Runnable event) {
+ queueOnVkThread(event);
+ }
+
+ @Override
+ public void onActivityPaused() {
+ onPause();
+ }
+
+ @Override
+ public void onActivityResumed() {
+ onResume();
+ }
+
+ @Override
+ public void onBackPressed() {
+ godot.onBackPressed();
+ }
+
+ @Override
+ public GodotInputHandler getInputHandler() {
+ return mInputHandler;
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ mGestureDetector.onTouchEvent(event);
+ return godot.gotTouchEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(final int keyCode, KeyEvent event) {
+ return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyDown(final int keyCode, KeyEvent event) {
+ return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ queueOnVkThread(new Runnable() {
+ @Override
+ public void run() {
+ // Resume the renderer
+ mRenderer.onVkResume();
+ GodotLib.focusin();
+ }
+ });
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ queueOnVkThread(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.focusout();
+ // Pause the renderer
+ mRenderer.onVkPause();
+ }
+ });
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index e901b4b36d..c95339c583 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -29,17 +29,21 @@
/*************************************************************************/
package org.godotengine.godot.input;
+
+import org.godotengine.godot.*;
+
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.InputFilter;
+import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+
import java.lang.ref.WeakReference;
-import org.godotengine.godot.*;
public class GodotEditText extends EditText {
// ===========================================================
@@ -51,10 +55,12 @@ public class GodotEditText extends EditText {
// ===========================================================
// Fields
// ===========================================================
- private GodotView mView;
+ private GodotRenderView mRenderView;
private GodotTextInputWrapper mInputWrapper;
private EditHandler sHandler = new EditHandler(this);
private String mOriginText;
+ private int mMaxInputLength = Integer.MAX_VALUE;
+ private boolean mMultiline = false;
private static class EditHandler extends Handler {
private final WeakReference<GodotEditText> mEdit;
@@ -76,22 +82,26 @@ public class GodotEditText extends EditText {
// ===========================================================
public GodotEditText(final Context context) {
super(context);
- this.initView();
+ initView();
}
public GodotEditText(final Context context, final AttributeSet attrs) {
super(context, attrs);
- this.initView();
+ initView();
}
public GodotEditText(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- this.initView();
+ initView();
}
protected void initView() {
- this.setPadding(0, 0, 0, 0);
- this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ setPadding(0, 0, 0, 0);
+ setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
+ }
+
+ public boolean isMultiline() {
+ return mMultiline;
}
private void handleMessage(final Message msg) {
@@ -101,12 +111,25 @@ public class GodotEditText extends EditText {
String text = edit.mOriginText;
if (edit.requestFocus()) {
edit.removeTextChangedListener(edit.mInputWrapper);
+ setMaxInputLength(edit);
edit.setText("");
edit.append(text);
+ if (msg.arg2 != -1) {
+ edit.setSelection(msg.arg1, msg.arg2);
+ edit.mInputWrapper.setSelection(true);
+ } else {
+ edit.mInputWrapper.setSelection(false);
+ }
+
+ int inputType = InputType.TYPE_CLASS_TEXT;
+ if (edit.isMultiline()) {
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ edit.setInputType(inputType);
+
edit.mInputWrapper.setOriginText(text);
edit.addTextChangedListener(edit.mInputWrapper);
- setMaxInputLength(edit, msg.arg1);
- final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(edit, 0);
}
} break;
@@ -115,32 +138,28 @@ public class GodotEditText extends EditText {
GodotEditText edit = (GodotEditText)msg.obj;
edit.removeTextChangedListener(mInputWrapper);
- final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edit.getWindowToken(), 0);
- edit.mView.requestFocus();
+ edit.mRenderView.getView().requestFocus();
} break;
}
}
- private void setMaxInputLength(EditText p_edit_text, int p_max_input_length) {
- if (p_max_input_length > 0) {
- InputFilter[] filters = new InputFilter[1];
- filters[0] = new InputFilter.LengthFilter(p_max_input_length);
- p_edit_text.setFilters(filters);
- } else {
- p_edit_text.setFilters(new InputFilter[] {});
- }
+ private void setMaxInputLength(EditText p_edit_text) {
+ InputFilter[] filters = new InputFilter[1];
+ filters[0] = new InputFilter.LengthFilter(this.mMaxInputLength);
+ p_edit_text.setFilters(filters);
}
// ===========================================================
// Getter & Setter
// ===========================================================
- public void setView(final GodotView view) {
- this.mView = view;
+ public void setView(final GodotRenderView view) {
+ mRenderView = view;
if (mInputWrapper == null)
- mInputWrapper = new GodotTextInputWrapper(mView, this);
- this.setOnEditorActionListener(mInputWrapper);
- view.requestFocus();
+ mInputWrapper = new GodotTextInputWrapper(mRenderView, this);
+ setOnEditorActionListener(mInputWrapper);
+ view.getView().requestFocus();
}
// ===========================================================
@@ -148,26 +167,60 @@ public class GodotEditText extends EditText {
// ===========================================================
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
- super.onKeyDown(keyCode, keyEvent);
-
- /* Let GlSurfaceView get focus if back key is input. */
+ /* Let SurfaceView get focus if back key is input. */
if (keyCode == KeyEvent.KEYCODE_BACK) {
- this.mView.requestFocus();
+ mRenderView.getView().requestFocus();
}
- return true;
+ // pass event to godot in special cases
+ if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) {
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, keyEvent);
+ }
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
+ if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) {
+ return true;
+ } else {
+ return super.onKeyUp(keyCode, keyEvent);
+ }
+ }
+
+ private boolean needHandlingInGodot(int keyCode, KeyEvent keyEvent) {
+ boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
+ keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
+ boolean isModifiedKey = keyEvent.isAltPressed() || keyEvent.isCtrlPressed() || keyEvent.isSymPressed() ||
+ keyEvent.isFunctionPressed() || keyEvent.isMetaPressed();
+ return isArrowKey || keyCode == KeyEvent.KEYCODE_TAB || KeyEvent.isModifierKey(keyCode) ||
+ isModifiedKey;
}
// ===========================================================
// Methods
// ===========================================================
- public void showKeyboard(String p_existing_text, int p_max_input_length) {
- this.mOriginText = p_existing_text;
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
+ if (p_cursor_start == -1) { // cursor position not given
+ this.mOriginText = p_existing_text;
+ this.mMaxInputLength = maxInputLength;
+ } else if (p_cursor_end == -1) { // not text selection
+ this.mOriginText = p_existing_text.substring(0, p_cursor_start);
+ this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_start);
+ } else {
+ this.mOriginText = p_existing_text.substring(0, p_cursor_end);
+ this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
+ }
+
+ this.mMultiline = p_multiline;
final Message msg = new Message();
msg.what = HANDLER_OPEN_IME_KEYBOARD;
msg.obj = this;
- msg.arg1 = p_max_input_length;
+ msg.arg1 = p_cursor_start;
+ msg.arg2 = p_cursor_end;
sHandler.sendMessage(msg);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
index 1a38a9c3d2..1c9a683bbd 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
@@ -30,26 +30,26 @@
package org.godotengine.godot.input;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.GodotRenderView;
+
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import org.godotengine.godot.GodotLib;
-import org.godotengine.godot.GodotView;
/**
- * Handles gesture input related events for the {@link GodotView} view.
+ * Handles gesture input related events for the {@link GodotRenderView} view.
* https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener
*/
public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener {
+ private final GodotRenderView mRenderView;
- private final GodotView godotView;
-
- public GodotGestureHandler(GodotView godotView) {
- this.godotView = godotView;
+ public GodotGestureHandler(GodotRenderView godotView) {
+ mRenderView = godotView;
}
private void queueEvent(Runnable task) {
- godotView.queueEvent(task);
+ mRenderView.queueOnRenderThread(task);
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index b2b88088e8..9abd65cc67 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -32,37 +32,38 @@ package org.godotengine.godot.input;
import static org.godotengine.godot.utils.GLUtils.DEBUG;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.GodotRenderView;
+import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
+
import android.util.Log;
import android.view.InputDevice;
import android.view.InputDevice.MotionRange;
import android.view.KeyEvent;
import android.view.MotionEvent;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import org.godotengine.godot.GodotLib;
-import org.godotengine.godot.GodotView;
-import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
/**
- * Handles input related events for the {@link GodotView} view.
+ * Handles input related events for the {@link GodotRenderView} view.
*/
public class GodotInputHandler implements InputDeviceListener {
+ private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>();
- private final ArrayList<Joystick> joysticksDevices = new ArrayList<Joystick>();
-
- private final GodotView godotView;
- private final InputManagerCompat inputManager;
+ private final GodotRenderView mRenderView;
+ private final InputManagerCompat mInputManager;
- public GodotInputHandler(GodotView godotView) {
- this.godotView = godotView;
- this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext());
- this.inputManager.registerInputDeviceListener(this, null);
+ public GodotInputHandler(GodotRenderView godotView) {
+ mRenderView = godotView;
+ mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
+ mInputManager.registerInputDeviceListener(this, null);
}
private void queueEvent(Runnable task) {
- godotView.queueEvent(task);
+ mRenderView.queueOnRenderThread(task);
}
private boolean isKeyEvent_GameDevice(int source) {
@@ -84,7 +85,6 @@ public class GodotInputHandler implements InputDeviceListener {
int source = event.getSource();
if (isKeyEvent_GameDevice(source)) {
-
final int button = getGodotButton(keyCode);
final int device_id = findJoystickDevice(event.getDeviceId());
@@ -98,11 +98,12 @@ public class GodotInputHandler implements InputDeviceListener {
});
}
} else {
+ final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
- GodotLib.key(keyCode, chr, false);
+ GodotLib.key(keyCode, scanCode, chr, false);
}
});
};
@@ -112,7 +113,7 @@ public class GodotInputHandler implements InputDeviceListener {
public boolean onKeyDown(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- godotView.onBackPressed();
+ mRenderView.onBackPressed();
// press 'back' button should not terminate program
//normal handle 'back' event in game logic
return true;
@@ -126,7 +127,6 @@ public class GodotInputHandler implements InputDeviceListener {
//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
if (isKeyEvent_GameDevice(source)) {
-
if (event.getRepeatCount() > 0) // ignore key echo
return true;
@@ -143,11 +143,12 @@ public class GodotInputHandler implements InputDeviceListener {
});
}
} else {
+ final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
- GodotLib.key(keyCode, chr, true);
+ GodotLib.key(keyCode, scanCode, chr, true);
}
});
};
@@ -157,12 +158,11 @@ public class GodotInputHandler implements InputDeviceListener {
public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
-
final int device_id = findJoystickDevice(event.getDeviceId());
// Check if the device exists
if (device_id > -1) {
- Joystick joy = joysticksDevices.get(device_id);
+ Joystick joy = mJoysticksDevices.get(device_id);
for (int i = 0; i < joy.axes.size(); i++) {
InputDevice.MotionRange range = joy.axes.get(i);
@@ -206,11 +206,11 @@ public class GodotInputHandler implements InputDeviceListener {
public void initInputDevices() {
/* initially add input devices*/
- int[] deviceIds = inputManager.getInputDeviceIds();
+ int[] deviceIds = mInputManager.getInputDeviceIds();
for (int deviceId : deviceIds) {
- InputDevice device = inputManager.getInputDevice(deviceId);
+ InputDevice device = mInputManager.getInputDevice(deviceId);
if (DEBUG) {
- Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
+ Log.v("GodotInputHandler", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
}
onInputDeviceAdded(deviceId);
}
@@ -222,13 +222,13 @@ public class GodotInputHandler implements InputDeviceListener {
// Check if the device has not been already added
if (id < 0) {
- InputDevice device = inputManager.getInputDevice(deviceId);
+ InputDevice device = mInputManager.getInputDevice(deviceId);
//device can be null if deviceId is not found
if (device != null) {
int sources = device.getSources();
if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
- id = joysticksDevices.size();
+ id = mJoysticksDevices.size();
Joystick joy = new Joystick();
joy.device_id = deviceId;
@@ -247,7 +247,7 @@ public class GodotInputHandler implements InputDeviceListener {
}
}
- joysticksDevices.add(joy);
+ mJoysticksDevices.add(joy);
final int device_id = id;
final String name = joy.name;
@@ -268,7 +268,7 @@ public class GodotInputHandler implements InputDeviceListener {
// Check if the evice has not been already removed
if (device_id > -1) {
- joysticksDevices.remove(device_id);
+ mJoysticksDevices.remove(device_id);
queueEvent(new Runnable() {
@Override
@@ -358,8 +358,8 @@ public class GodotInputHandler implements InputDeviceListener {
}
private int findJoystickDevice(int device_id) {
- for (int i = 0; i < joysticksDevices.size(); i++) {
- if (joysticksDevices.get(i).device_id == device_id) {
+ for (int i = 0; i < mJoysticksDevices.size(); i++) {
+ if (mJoysticksDevices.get(i).device_id == device_id) {
return i;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 3a154f1bf3..4dd1054738 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -29,6 +29,9 @@
/*************************************************************************/
package org.godotengine.godot.input;
+
+import org.godotengine.godot.*;
+
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
@@ -37,7 +40,6 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
-import org.godotengine.godot.*;
public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListener {
// ===========================================================
@@ -48,17 +50,18 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
// ===========================================================
// Fields
// ===========================================================
- private final GodotView mView;
+ private final GodotRenderView mRenderView;
private final GodotEditText mEdit;
private String mOriginText;
+ private boolean mHasSelection;
// ===========================================================
// Constructors
// ===========================================================
- public GodotTextInputWrapper(final GodotView view, final GodotEditText edit) {
- this.mView = view;
- this.mEdit = edit;
+ public GodotTextInputWrapper(final GodotRenderView view, final GodotEditText edit) {
+ mRenderView = view;
+ mEdit = edit;
}
// ===========================================================
@@ -66,13 +69,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
// ===========================================================
private boolean isFullScreenEdit() {
- final TextView textField = this.mEdit;
+ final TextView textField = mEdit;
final InputMethodManager imm = (InputMethodManager)textField.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
return imm.isFullscreenMode();
}
public void setOriginText(final String originText) {
- this.mOriginText = originText;
+ mOriginText = originText;
+ }
+
+ public void setSelection(boolean selection) {
+ mHasSelection = selection;
}
// ===========================================================
@@ -87,12 +94,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) {
//Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after);
- mView.queueEvent(new Runnable() {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; ++i) {
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false);
+
+ if (mHasSelection) {
+ mHasSelection = false;
+ break;
+ }
}
}
});
@@ -106,12 +118,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
for (int i = start; i < start + count; ++i) {
newChars[i - start] = pCharSequence.charAt(i);
}
- mView.queueEvent(new Runnable() {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; ++i) {
- GodotLib.key(0, newChars[i], true);
- GodotLib.key(0, newChars[i], false);
+ int key = newChars[i];
+ if ((key == '\n') && !mEdit.isMultiline()) {
+ // Return keys are handled through action events
+ continue;
+ }
+ GodotLib.key(0, 0, key, true);
+ GodotLib.key(0, 0, key, false);
}
}
});
@@ -119,23 +136,28 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
- if (this.mEdit == pTextView && this.isFullScreenEdit()) {
+ if (mEdit == pTextView && isFullScreenEdit()) {
final String characters = pKeyEvent.getCharacters();
- mView.queueEvent(new Runnable() {
+ mRenderView.queueOnRenderThread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < characters.length(); i++) {
final int ch = characters.codePointAt(i);
- GodotLib.key(0, ch, true);
- GodotLib.key(0, ch, false);
+ GodotLib.key(0, 0, ch, true);
+ GodotLib.key(0, 0, ch, false);
}
}
});
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
- this.mView.requestFocus();
+ // Enter key has been pressed
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false);
+
+ mRenderView.getView().requestFocus();
+ return true;
}
return false;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
index 4042c42e9d..62810ad3a4 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
@@ -120,7 +120,6 @@ public interface InputManagerCompat {
* Use this to construct a compatible InputManager.
*/
public static class Factory {
-
/**
* Constructs and returns a compatible InputManger
*
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
index e4bafa7ff9..61828dccae 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
@@ -23,12 +23,12 @@ import android.os.Build;
import android.os.Handler;
import android.view.InputDevice;
import android.view.MotionEvent;
+
import java.util.HashMap;
import java.util.Map;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class InputManagerV16 implements InputManagerCompat {
-
private final InputManager mInputManager;
private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
index 0c1bdb32aa..1f3fe1e527 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
@@ -31,6 +31,7 @@
package org.godotengine.godot.input;
import android.view.InputDevice.MotionRange;
+
import java.util.ArrayList;
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java
deleted file mode 100644
index 95cc48f536..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*************************************************************************/
-/* ConsumeTask.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import com.android.vending.billing.IInAppBillingService;
-import java.lang.ref.WeakReference;
-
-abstract public class ConsumeTask {
-
- private Context context;
- private IInAppBillingService mService;
-
- private String mSku;
- private String mToken;
-
- private static class ConsumeAsyncTask extends AsyncTask<String, String, String> {
-
- private WeakReference<ConsumeTask> mTask;
-
- ConsumeAsyncTask(ConsumeTask consume) {
- mTask = new WeakReference<>(consume);
- }
-
- @Override
- protected String doInBackground(String... strings) {
- ConsumeTask consume = mTask.get();
- if (consume != null) {
- return consume.doInBackground(strings);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(String param) {
- ConsumeTask consume = mTask.get();
- if (consume != null) {
- consume.onPostExecute(param);
- }
- }
- }
-
- public ConsumeTask(IInAppBillingService mService, Context context) {
- this.context = context;
- this.mService = mService;
- }
-
- public void consume(final String sku) {
- mSku = sku;
- PaymentsCache pc = new PaymentsCache(context);
- Boolean isBlocked = pc.getConsumableFlag("block", sku);
- mToken = pc.getConsumableValue("token", sku);
- if (!isBlocked && mToken == null) {
- // Consuming task is processing
- } else if (!isBlocked) {
- return;
- } else if (mToken == null) {
- this.error("No token for sku:" + sku);
- return;
- }
- new ConsumeAsyncTask(this).execute();
- }
-
- private String doInBackground(String... params) {
- try {
- int response = mService.consumePurchase(3, context.getPackageName(), mToken);
- if (response == 0 || response == 8) {
- return null;
- }
- } catch (RemoteException e) {
- return e.getMessage();
- }
- return "Some error";
- }
-
- private void onPostExecute(String param) {
- if (param == null) {
- success(new PaymentsCache(context).getConsumableValue("ticket", mSku));
- } else {
- error(param);
- }
- }
-
- abstract protected void success(String ticket);
- abstract protected void error(String message);
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java
deleted file mode 100644
index 23d693cc8c..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*************************************************************************/
-/* HandlePurchaseTask.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.app.Activity;
-import android.content.Intent;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-abstract public class HandlePurchaseTask {
-
- private Activity context;
-
- public HandlePurchaseTask(Activity context) {
- this.context = context;
- }
-
- public void handlePurchaseRequest(int resultCode, Intent data) {
- //Log.d("XXX", "Handling purchase response");
- if (resultCode == Activity.RESULT_OK) {
- try {
- //int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
- PaymentsCache pc = new PaymentsCache(context);
-
- String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
- //Log.d("XXX", "Purchase data:" + purchaseData);
- String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
- //Log.d("XXX", "Purchase signature:" + dataSignature);
- //Log.d("SARLANGA", purchaseData);
-
- JSONObject jo = new JSONObject(purchaseData);
- //String sku = jo.getString("productId");
- //alert("You have bought the " + sku + ". Excellent choice, aventurer!");
- //String orderId = jo.getString("orderId");
- //String packageName = jo.getString("packageName");
- String productId = jo.getString("productId");
- //Long purchaseTime = jo.getLong("purchaseTime");
- //Integer state = jo.getInt("purchaseState");
- String developerPayload = jo.getString("developerPayload");
- String purchaseToken = jo.getString("purchaseToken");
-
- if (!pc.getConsumableValue("validation_hash", productId).equals(developerPayload)) {
- error("Untrusted callback");
- return;
- }
- //Log.d("XXX", "Este es el product ID:" + productId);
- pc.setConsumableValue("ticket_signautre", productId, dataSignature);
- pc.setConsumableValue("ticket", productId, purchaseData);
- pc.setConsumableFlag("block", productId, true);
- pc.setConsumableValue("token", productId, purchaseToken);
-
- success(productId, dataSignature, purchaseData);
- return;
- } catch (JSONException e) {
- error(e.getMessage());
- }
- } else if (resultCode == Activity.RESULT_CANCELED) {
- canceled();
- }
- }
-
- abstract protected void success(String sku, String signature, String ticket);
- abstract protected void error(String message);
- abstract protected void canceled();
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java
deleted file mode 100644
index 90b958266b..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*************************************************************************/
-/* PaymentsManager.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.vending.billing.IInAppBillingService;
-import java.util.ArrayList;
-import java.util.Arrays;
-import org.godotengine.godot.GodotPaymentV3;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class PaymentsManager {
-
- public static final int BILLING_RESPONSE_RESULT_OK = 0;
- public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
- private static boolean auto_consume = true;
-
- private Activity activity;
- IInAppBillingService mService;
-
- public void setActivity(Activity activity) {
- this.activity = activity;
- }
-
- public static PaymentsManager createManager(Activity activity) {
- PaymentsManager manager = new PaymentsManager(activity);
- return manager;
- }
-
- private PaymentsManager(Activity activity) {
- this.activity = activity;
- }
-
- public PaymentsManager initService() {
- Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
- intent.setPackage("com.android.vending");
- activity.bindService(
- intent,
- mServiceConn,
- Context.BIND_AUTO_CREATE);
- return this;
- }
-
- public void destroy() {
- if (mService != null) {
- activity.unbindService(mServiceConn);
- }
- }
-
- ServiceConnection mServiceConn = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
-
- // At this stage, godotPaymentV3 might not have been initialized yet.
- if (godotPaymentV3 != null) {
- godotPaymentV3.callbackDisconnected();
- }
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IInAppBillingService.Stub.asInterface(service);
-
- // At this stage, godotPaymentV3 might not have been initialized yet.
- if (godotPaymentV3 != null) {
- godotPaymentV3.callbackConnected();
- }
- }
- };
-
- public void requestPurchase(final String sku, String transactionId) {
- new PurchaseTask(mService, activity) {
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFail(message);
- }
-
- @Override
- protected void canceled() {
- godotPaymentV3.callbackCancel();
- }
-
- @Override
- protected void alreadyOwned() {
- godotPaymentV3.callbackAlreadyOwned(sku);
- }
- }
- .purchase(sku, transactionId);
- }
-
- public boolean isConnected() {
- return mService != null;
- }
-
- public void consumeUnconsumedPurchases() {
- new ReleaseAllConsumablesTask(mService, activity) {
- @Override
- protected void success(String sku, String receipt, String signature, String token) {
- godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
- }
-
- @Override
- protected void error(String message) {
- Log.d("godot", "consumeUnconsumedPurchases :" + message);
- godotPaymentV3.callbackFailConsume(message);
- }
-
- @Override
- protected void notRequired() {
- Log.d("godot", "callbackSuccessNoUnconsumedPurchases :");
- godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
- }
- }
- .consumeItAll();
- }
-
- public void requestPurchased() {
- try {
- PaymentsCache pc = new PaymentsCache(activity);
-
- String continueToken = null;
-
- do {
- Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken);
-
- if (bundle.getInt("RESPONSE_CODE") == 0) {
-
- final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
- final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
-
- if (myPurchases == null || myPurchases.size() == 0) {
- godotPaymentV3.callbackPurchased("", "", "");
- return;
- }
-
- for (int i = 0; i < myPurchases.size(); i++) {
-
- try {
- String receipt = myPurchases.get(i);
- JSONObject inappPurchaseData = new JSONObject(receipt);
- String sku = inappPurchaseData.getString("productId");
- String token = inappPurchaseData.getString("purchaseToken");
- String signature = mySignatures.get(i);
-
- pc.setConsumableValue("ticket_signautre", sku, signature);
- pc.setConsumableValue("ticket", sku, receipt);
- pc.setConsumableFlag("block", sku, true);
- pc.setConsumableValue("token", sku, token);
-
- godotPaymentV3.callbackPurchased(receipt, signature, sku);
- } catch (JSONException e) {
- }
- }
- }
- continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN");
- Log.d("godot", "continue token = " + continueToken);
- } while (!TextUtils.isEmpty(continueToken));
- } catch (Exception e) {
- Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage());
- }
- }
-
- public void processPurchaseResponse(int resultCode, Intent data) {
- new HandlePurchaseTask(activity) {
- @Override
- protected void success(final String sku, final String signature, final String ticket) {
- godotPaymentV3.callbackSuccess(ticket, signature, sku);
-
- if (auto_consume) {
- new ConsumeTask(mService, activity) {
- @Override
- protected void success(String ticket) {
- }
-
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFail(message);
- }
- }
- .consume(sku);
- }
- }
-
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFail(message);
- }
-
- @Override
- protected void canceled() {
- godotPaymentV3.callbackCancel();
- }
- }
- .handlePurchaseRequest(resultCode, data);
- }
-
- public void validatePurchase(String purchaseToken, final String sku) {
-
- new ValidateTask(activity, godotPaymentV3) {
- @Override
- protected void success() {
-
- new ConsumeTask(mService, activity) {
- @Override
- protected void success(String ticket) {
- godotPaymentV3.callbackSuccess(ticket, null, sku);
- }
-
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFail(message);
- }
- }
- .consume(sku);
- }
-
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFail(message);
- }
-
- @Override
- protected void canceled() {
- godotPaymentV3.callbackCancel();
- }
- }
- .validatePurchase(sku);
- }
-
- public void setAutoConsume(boolean autoConsume) {
- auto_consume = autoConsume;
- }
-
- public void consume(final String sku) {
- new ConsumeTask(mService, activity) {
- @Override
- protected void success(String ticket) {
- godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
- }
-
- @Override
- protected void error(String message) {
- godotPaymentV3.callbackFailConsume(message);
- }
- }
- .consume(sku);
- }
-
- // Workaround to bug where sometimes response codes come as Long instead of Integer
- int getResponseCodeFromBundle(Bundle b) {
- Object o = b.get("RESPONSE_CODE");
- if (o == null) {
- //logDebug("Bundle with null response code, assuming OK (known issue)");
- return BILLING_RESPONSE_RESULT_OK;
- } else if (o instanceof Integer)
- return ((Integer)o).intValue();
- else if (o instanceof Long)
- return (int)((Long)o).longValue();
- else {
- //logError("Unexpected type for bundle response code.");
- //logError(o.getClass().getName());
- throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
- }
- }
-
- /**
- * Returns a human-readable description for the given response code.
- *
- * @param code The response code
- * @return A human-readable string explaining the result code.
- * It also includes the result code numerically.
- */
- public static String getResponseDesc(int code) {
- String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/"
- +
- "3:Billing Unavailable/4:Item unavailable/"
- +
- "5:Developer Error/6:Error/7:Item Already Owned/"
- +
- "8:Item not owned")
- .split("/");
- String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/"
- +
- "-1002:Bad response received/"
- +
- "-1003:Purchase signature verification failed/"
- +
- "-1004:Send intent failed/"
- +
- "-1005:User cancelled/"
- +
- "-1006:Unknown purchase response/"
- +
- "-1007:Missing token/"
- +
- "-1008:Unknown error/"
- +
- "-1009:Subscriptions not available/"
- +
- "-1010:Invalid consumption attempt")
- .split("/");
-
- if (code <= -1000) {
- int index = -1000 - code;
- if (index >= 0 && index < iabhelper_msgs.length)
- return iabhelper_msgs[index];
- else
- return String.valueOf(code) + ":Unknown IAB Helper Error";
- } else if (code < 0 || code >= iab_msgs.length)
- return String.valueOf(code) + ":Unknown";
- else
- return iab_msgs[code];
- }
-
- public void querySkuDetails(final String[] list) {
- (new Thread(new Runnable() {
- @Override
- public void run() {
- ArrayList<String> skuList = new ArrayList<String>(Arrays.asList(list));
- if (skuList.size() == 0) {
- return;
- }
- // Split the sku list in blocks of no more than 20 elements.
- ArrayList<ArrayList<String>> packs = new ArrayList<ArrayList<String>>();
- ArrayList<String> tempList;
- int n = skuList.size() / 20;
- int mod = skuList.size() % 20;
- for (int i = 0; i < n; i++) {
- tempList = new ArrayList<String>();
- for (String s : skuList.subList(i * 20, i * 20 + 20)) {
- tempList.add(s);
- }
- packs.add(tempList);
- }
- if (mod != 0) {
- tempList = new ArrayList<String>();
- for (String s : skuList.subList(n * 20, n * 20 + mod)) {
- tempList.add(s);
- }
- packs.add(tempList);
- }
- for (ArrayList<String> skuPartList : packs) {
- Bundle querySkus = new Bundle();
- querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList);
- Bundle skuDetails = null;
- try {
- skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus);
- if (!skuDetails.containsKey("DETAILS_LIST")) {
- int response = getResponseCodeFromBundle(skuDetails);
- if (response != BILLING_RESPONSE_RESULT_OK) {
- godotPaymentV3.errorSkuDetail(getResponseDesc(response));
- } else {
- godotPaymentV3.errorSkuDetail("No error but no detail list.");
- }
- return;
- }
-
- ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
-
- for (String thisResponse : responseList) {
- Log.d("godot", "response = " + thisResponse);
- godotPaymentV3.addSkuDetail(thisResponse);
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- godotPaymentV3.errorSkuDetail("RemoteException error!");
- }
- }
- godotPaymentV3.completeSkuDetail();
- }
- }))
- .start();
- }
-
- private GodotPaymentV3 godotPaymentV3;
-
- public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
- this.godotPaymentV3 = godotPaymentV3;
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java
deleted file mode 100644
index 09c9349124..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*************************************************************************/
-/* PurchaseTask.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.content.IntentSender.SendIntentException;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-import com.android.vending.billing.IInAppBillingService;
-
-abstract public class PurchaseTask {
-
- private Activity context;
-
- private IInAppBillingService mService;
- public PurchaseTask(IInAppBillingService mService, Activity context) {
- this.context = context;
- this.mService = mService;
- }
-
- private boolean isLooping = false;
-
- public void purchase(final String sku, final String transactionId) {
- Log.d("XXX", "Starting purchase for: " + sku);
- PaymentsCache pc = new PaymentsCache(context);
- Boolean isBlocked = pc.getConsumableFlag("block", sku);
- /*
- if(isBlocked){
- Log.d("XXX", "Is awaiting payment confirmation");
- error("Awaiting payment confirmation");
- return;
- }
- */
- final String hash = transactionId;
-
- Bundle buyIntentBundle;
- try {
- buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash);
- } catch (RemoteException e) {
- //Log.d("XXX", "Error: " + e.getMessage());
- error(e.getMessage());
- return;
- }
- Object rc = buyIntentBundle.get("RESPONSE_CODE");
- int responseCode = 0;
- if (rc == null) {
- responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
- } else if (rc instanceof Integer) {
- responseCode = ((Integer)rc).intValue();
- } else if (rc instanceof Long) {
- responseCode = (int)((Long)rc).longValue();
- }
- //Log.d("XXX", "Buy intent response code: " + responseCode);
- if (responseCode == 1 || responseCode == 3 || responseCode == 4) {
- canceled();
- return;
- }
- if (responseCode == 7) {
- alreadyOwned();
- return;
- }
-
- PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
- pc.setConsumableValue("validation_hash", sku, hash);
- try {
- if (context == null) {
- //Log.d("XXX", "No context!");
- }
- if (pendingIntent == null) {
- //Log.d("XXX", "No pending intent");
- }
- //Log.d("XXX", "Starting activity for purchase!");
- context.startIntentSenderForResult(
- pendingIntent.getIntentSender(),
- PaymentsManager.REQUEST_CODE_FOR_PURCHASE,
- new Intent(),
- Integer.valueOf(0), Integer.valueOf(0),
- Integer.valueOf(0));
- } catch (SendIntentException e) {
- error(e.getMessage());
- }
- }
-
- abstract protected void error(String message);
- abstract protected void canceled();
- abstract protected void alreadyOwned();
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java
deleted file mode 100644
index a101780511..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*************************************************************************/
-/* ReleaseAllConsumablesTask.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.Log;
-import com.android.vending.billing.IInAppBillingService;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-abstract public class ReleaseAllConsumablesTask {
-
- private Context context;
- private IInAppBillingService mService;
-
- private static class ReleaseAllConsumablesAsyncTask extends AsyncTask<String, String, String> {
-
- private WeakReference<ReleaseAllConsumablesTask> mTask;
- private String mSku;
- private String mReceipt;
- private String mSignature;
- private String mToken;
-
- ReleaseAllConsumablesAsyncTask(ReleaseAllConsumablesTask task, String sku, String receipt, String signature, String token) {
- mTask = new WeakReference<ReleaseAllConsumablesTask>(task);
-
- mSku = sku;
- mReceipt = receipt;
- mSignature = signature;
- mToken = token;
- }
-
- @Override
- protected String doInBackground(String... params) {
- ReleaseAllConsumablesTask consume = mTask.get();
- if (consume != null) {
- return consume.doInBackground(mToken);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(String param) {
- ReleaseAllConsumablesTask consume = mTask.get();
- if (consume != null) {
- consume.success(mSku, mReceipt, mSignature, mToken);
- }
- }
- }
-
- public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context) {
- this.context = context;
- this.mService = mService;
- }
-
- public void consumeItAll() {
- try {
- //Log.d("godot", "consumeItall for " + context.getPackageName());
- Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null);
-
- if (bundle.getInt("RESPONSE_CODE") == 0) {
-
- final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
- final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
-
- if (myPurchases == null || myPurchases.size() == 0) {
- //Log.d("godot", "No purchases!");
- notRequired();
- return;
- }
-
- //Log.d("godot", "# products to be consumed:" + myPurchases.size());
- for (int i = 0; i < myPurchases.size(); i++) {
-
- try {
- String receipt = myPurchases.get(i);
- JSONObject inappPurchaseData = new JSONObject(receipt);
- String sku = inappPurchaseData.getString("productId");
- String token = inappPurchaseData.getString("purchaseToken");
- String signature = mySignatures.get(i);
- //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt);
- new ReleaseAllConsumablesAsyncTask(this, sku, receipt, signature, token).execute();
- } catch (JSONException e) {
- }
- }
- }
- } catch (Exception e) {
- Log.d("godot", "Error releasing products:" + e.getClass().getName() + ":" + e.getMessage());
- }
- }
-
- private String doInBackground(String token) {
- try {
- //Log.d("godot", "Requesting to consume an item with token ." + token);
- int response = mService.consumePurchase(3, context.getPackageName(), token);
- //Log.d("godot", "consumePurchase response: " + response);
- if (response == 0 || response == 8) {
- return null;
- }
- } catch (Exception e) {
- Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage());
- }
- return null;
- }
-
- abstract protected void success(String sku, String receipt, String signature, String token);
- abstract protected void error(String message);
- abstract protected void notRequired();
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java
deleted file mode 100644
index dbb6b8a783..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*************************************************************************/
-/* ValidateTask.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.payments;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.os.AsyncTask;
-import java.lang.ref.WeakReference;
-import org.godotengine.godot.GodotPaymentV3;
-import org.godotengine.godot.utils.HttpRequester;
-import org.godotengine.godot.utils.RequestParams;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-abstract public class ValidateTask {
-
- private Activity context;
- private GodotPaymentV3 godotPaymentsV3;
- private ProgressDialog dialog;
- private String mSku;
-
- private static class ValidateAsyncTask extends AsyncTask<String, String, String> {
- private WeakReference<ValidateTask> mTask;
-
- ValidateAsyncTask(ValidateTask task) {
- mTask = new WeakReference<>(task);
- }
-
- @Override
- protected void onPreExecute() {
- ValidateTask task = mTask.get();
- if (task != null) {
- task.onPreExecute();
- }
- }
-
- @Override
- protected String doInBackground(String... params) {
- ValidateTask task = mTask.get();
- if (task != null) {
- return task.doInBackground(params);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(String response) {
- ValidateTask task = mTask.get();
- if (task != null) {
- task.onPostExecute(response);
- }
- }
- }
-
- public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) {
- this.context = context;
- this.godotPaymentsV3 = godotPaymentsV3;
- }
-
- public void validatePurchase(final String sku) {
- mSku = sku;
- new ValidateAsyncTask(this).execute();
- }
-
- private void onPreExecute() {
- dialog = ProgressDialog.show(context, null, "Please wait...");
- }
-
- private String doInBackground(String... params) {
- PaymentsCache pc = new PaymentsCache(context);
- String url = godotPaymentsV3.getPurchaseValidationUrlPrefix();
- RequestParams param = new RequestParams();
- param.setUrl(url);
- param.put("ticket", pc.getConsumableValue("ticket", mSku));
- param.put("purchaseToken", pc.getConsumableValue("token", mSku));
- param.put("sku", mSku);
- //Log.d("XXX", "Haciendo request a " + url);
- //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku));
- //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku));
- //Log.d("XXX", "sku: " + sku);
- param.put("package", context.getApplicationContext().getPackageName());
- HttpRequester requester = new HttpRequester();
- String jsonResponse = requester.post(param);
- //Log.d("XXX", "Validation response:\n"+jsonResponse);
- return jsonResponse;
- }
-
- private void onPostExecute(String response) {
- if (dialog != null) {
- dialog.dismiss();
- dialog = null;
- }
- JSONObject j;
- try {
- j = new JSONObject(response);
- if (j.getString("status").equals("OK")) {
- success();
- return;
- } else if (j.getString("status") != null) {
- error(j.getString("message"));
- } else {
- error("Connection error");
- }
- } catch (JSONException e) {
- error(e.getMessage());
- } catch (Exception e) {
- error(e.getMessage());
- }
- }
-
- abstract protected void success();
- abstract protected void error(String message);
- abstract protected void canceled();
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
new file mode 100644
index 0000000000..93c204935c
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -0,0 +1,370 @@
+/*************************************************************************/
+/* GodotPlugin.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import org.godotengine.godot.BuildConfig;
+import org.godotengine.godot.Godot;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Base class for the Godot Android plugins.
+ * <p>
+ * A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats:
+ * <p>
+ * - The library must have a dependency on the Godot Android library (godot-lib.aar).
+ * A stable version is available for each release.
+ * <p>
+ * - The library must include a <meta-data> tag in its manifest file setup as follow:
+ * <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" />
+ * Where:
+ * - 'PluginName' is the name of the plugin.
+ * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class
+ * extending {@link GodotPlugin}.
+ *
+ * A plugin can also define and provide c/c++ gdnative libraries and nativescripts for the target
+ * app/game to leverage.
+ * The shared library for the gdnative library will be automatically bundled by the aar build
+ * system.
+ * Godot '*.gdnlib' and '*.gdns' resource files must however be manually defined in the project
+ * 'assets' directory. The recommended path for these resources in the 'assets' directory should be:
+ * 'godot/plugin/v1/[PluginName]/'
+ */
+public abstract class GodotPlugin {
+ private static final String TAG = GodotPlugin.class.getSimpleName();
+
+ private final Godot godot;
+ private final ConcurrentHashMap<String, SignalInfo> registeredSignals = new ConcurrentHashMap<>();
+
+ public GodotPlugin(Godot godot) {
+ this.godot = godot;
+ }
+
+ /**
+ * Provides access to the Godot engine.
+ */
+ protected Godot getGodot() {
+ return godot;
+ }
+
+ /**
+ * Provides access to the underlying {@link Activity}.
+ */
+ @Nullable
+ protected Activity getActivity() {
+ return godot.getActivity();
+ }
+
+ /**
+ * Register the plugin with Godot native code.
+ *
+ * This method is invoked on the render thread.
+ */
+ public final void onRegisterPluginWithGodotNative() {
+ nativeRegisterSingleton(getPluginName());
+
+ Class clazz = getClass();
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ boolean found = false;
+
+ for (String s : getPluginMethods()) {
+ if (s.equals(method.getName())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ continue;
+
+ List<String> ptr = new ArrayList<String>();
+
+ Class[] paramTypes = method.getParameterTypes();
+ for (Class c : paramTypes) {
+ ptr.add(c.getName());
+ }
+
+ String[] pt = new String[ptr.size()];
+ ptr.toArray(pt);
+
+ nativeRegisterMethod(getPluginName(), method.getName(), method.getReturnType().getName(), pt);
+ }
+
+ // Register the signals for this plugin.
+ for (SignalInfo signalInfo : getPluginSignals()) {
+ String signalName = signalInfo.getName();
+ nativeRegisterSignal(getPluginName(), signalName, signalInfo.getParamTypesNames());
+ registeredSignals.put(signalName, signalInfo);
+ }
+
+ // Get the list of gdnative libraries to register.
+ Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths();
+ if (!gdnativeLibrariesPaths.isEmpty()) {
+ nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0]));
+ }
+ }
+
+ /**
+ * Invoked once during the Godot Android initialization process after creation of the
+ * {@link org.godotengine.godot.GodotView} view.
+ * <p>
+ * The plugin can return a non-null {@link View} layout in order to add it to the Godot view
+ * hierarchy.
+ *
+ * @see Activity#onCreate(Bundle)
+ * @return the plugin's view to be included; null if no views should be included.
+ */
+ @Nullable
+ public View onMainCreate(Activity activity) {
+ return null;
+ }
+
+ /**
+ * @see Activity#onActivityResult(int, int, Intent)
+ */
+ public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
+ }
+
+ /**
+ * @see Activity#onRequestPermissionsResult(int, String[], int[])
+ */
+ public void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ }
+
+ /**
+ * @see Activity#onPause()
+ */
+ public void onMainPause() {}
+
+ /**
+ * @see Activity#onResume()
+ */
+ public void onMainResume() {}
+
+ /**
+ * @see Activity#onDestroy()
+ */
+ public void onMainDestroy() {}
+
+ /**
+ * @see Activity#onBackPressed()
+ */
+ public boolean onMainBackPressed() { return false; }
+
+ /**
+ * Invoked on the render thread when the Godot main loop has started.
+ */
+ public void onGodotMainLoopStarted() {}
+
+ /**
+ * Invoked once per frame on the GL thread after the frame is drawn.
+ */
+ public void onGLDrawFrame(GL10 gl) {}
+
+ /**
+ * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size
+ * changes.
+ */
+ public void onGLSurfaceChanged(GL10 gl, int width, int height) {}
+
+ /**
+ * Called on the GL thread when the surface is created or recreated.
+ */
+ public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {}
+
+ /**
+ * Invoked once per frame on the Vulkan thread after the frame is drawn.
+ */
+ public void onVkDrawFrame() {}
+
+ /**
+ * Called on the Vulkan thread after the surface is created and whenever the surface size
+ * changes.
+ */
+ public void onVkSurfaceChanged(Surface surface, int width, int height) {}
+
+ /**
+ * Called on the Vulkan thread when the surface is created or recreated.
+ */
+ public void onVkSurfaceCreated(Surface surface) {}
+
+ /**
+ * Returns the name of the plugin.
+ * <p>
+ * This value must match the one listed in the plugin '<meta-data>' manifest entry.
+ */
+ @NonNull
+ public abstract String getPluginName();
+
+ /**
+ * Returns the list of methods to be exposed to Godot.
+ */
+ @NonNull
+ public List<String> getPluginMethods() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns the list of signals to be exposed to Godot.
+ */
+ @NonNull
+ public Set<SignalInfo> getPluginSignals() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Returns the paths for the plugin's gdnative libraries.
+ *
+ * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file.
+ */
+ @NonNull
+ protected Set<String> getPluginGDNativeLibrariesPaths() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Runs the specified action on the UI thread. If the current thread is the UI
+ * thread, then the action is executed immediately. If the current thread is
+ * not the UI thread, the action is posted to the event queue of the UI thread.
+ *
+ * @param action the action to run on the UI thread
+ */
+ protected void runOnUiThread(Runnable action) {
+ godot.runOnUiThread(action);
+ }
+
+ /**
+ * Queue the specified action to be run on the render thread.
+ *
+ * @param action the action to run on the render thread
+ */
+ protected void runOnRenderThread(Runnable action) {
+ godot.runOnRenderThread(action);
+ }
+
+ /**
+ * Emit a registered Godot signal.
+ * @param signalName
+ * @param signalArgs
+ */
+ protected void emitSignal(final String signalName, final Object... signalArgs) {
+ try {
+ // Check that the given signal is among the registered set.
+ SignalInfo signalInfo = registeredSignals.get(signalName);
+ if (signalInfo == null) {
+ throw new IllegalArgumentException(
+ "Signal " + signalName + " is not registered for this plugin.");
+ }
+
+ // Validate the arguments count.
+ Class<?>[] signalParamTypes = signalInfo.getParamTypes();
+ if (signalArgs.length != signalParamTypes.length) {
+ throw new IllegalArgumentException(
+ "Invalid arguments count. Should be " + signalParamTypes.length + " but is " + signalArgs.length);
+ }
+
+ // Validate the argument's types.
+ for (int i = 0; i < signalParamTypes.length; i++) {
+ if (!signalParamTypes[i].isInstance(signalArgs[i])) {
+ throw new IllegalArgumentException(
+ "Invalid type for argument #" + i + ". Should be of type " + signalParamTypes[i].getName());
+ }
+ }
+
+ runOnRenderThread(new Runnable() {
+ @Override
+ public void run() {
+ nativeEmitSignal(getPluginName(), signalName, signalArgs);
+ }
+ });
+ } catch (IllegalArgumentException exception) {
+ Log.w(TAG, exception.getMessage());
+ if (BuildConfig.DEBUG) {
+ throw exception;
+ }
+ }
+ }
+
+ /**
+ * Used to setup a {@link GodotPlugin} instance.
+ * @param p_name Name of the instance.
+ */
+ private native void nativeRegisterSingleton(String p_name);
+
+ /**
+ * Used to complete registration of the {@link GodotPlugin} instance's methods.
+ * @param p_sname Name of the instance
+ * @param p_name Name of the method to register
+ * @param p_ret Return type of the registered method
+ * @param p_params Method parameters types
+ */
+ private native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
+
+ /**
+ * Used to register gdnative libraries bundled by the plugin.
+ * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory.
+ */
+ private native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths);
+
+ /**
+ * Used to complete registration of the {@link GodotPlugin} instance's methods.
+ * @param pluginName Name of the plugin
+ * @param signalName Name of the signal to register
+ * @param signalParamTypes Signal parameters types
+ */
+ private native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes);
+
+ /**
+ * Used to emit signal by {@link GodotPlugin} instance.
+ * @param pluginName Name of the plugin
+ * @param signalName Name of the signal to emit
+ * @param signalParams Signal parameters
+ */
+ private native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams);
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
new file mode 100644
index 0000000000..1c2d1a6563
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -0,0 +1,208 @@
+/*************************************************************************/
+/* GodotPluginRegistry.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import org.godotengine.godot.Godot;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Registry used to load and access the registered Godot Android plugins.
+ */
+public final class GodotPluginRegistry {
+ private static final String TAG = GodotPluginRegistry.class.getSimpleName();
+
+ private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
+
+ /**
+ * Name for the metadata containing the list of Godot plugins to enable.
+ */
+ private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins";
+
+ private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|";
+
+ private static GodotPluginRegistry instance;
+ private final ConcurrentHashMap<String, GodotPlugin> registry;
+
+ private GodotPluginRegistry(Godot godot) {
+ registry = new ConcurrentHashMap<>();
+ loadPlugins(godot);
+ }
+
+ /**
+ * Retrieve the plugin tied to the given plugin name.
+ * @param pluginName Name of the plugin
+ * @return {@link GodotPlugin} handle if it exists, null otherwise.
+ */
+ @Nullable
+ public GodotPlugin getPlugin(String pluginName) {
+ return registry.get(pluginName);
+ }
+
+ /**
+ * Retrieve the full set of loaded plugins.
+ */
+ public Collection<GodotPlugin> getAllPlugins() {
+ return registry.values();
+ }
+
+ /**
+ * Parse the manifest file and load all included Godot Android plugins.
+ * <p>
+ * A plugin manifest entry is a '<meta-data>' tag setup as described in the {@link GodotPlugin}
+ * documentation.
+ *
+ * @param godot Godot instance
+ * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance
+ * of each Godot Android plugins is available at runtime.
+ */
+ public static GodotPluginRegistry initializePluginRegistry(Godot godot) {
+ if (instance == null) {
+ instance = new GodotPluginRegistry(godot);
+ }
+
+ return instance;
+ }
+
+ /**
+ * Return the plugin registry if it's initialized.
+ * Throws a {@link IllegalStateException} exception if not.
+ *
+ * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method.
+ */
+ public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException {
+ if (instance == null) {
+ throw new IllegalStateException("Plugin registry hasn't been initialized.");
+ }
+
+ return instance;
+ }
+
+ private void loadPlugins(Godot godot) {
+ try {
+ final Activity activity = godot.getActivity();
+ ApplicationInfo appInfo = activity
+ .getPackageManager()
+ .getApplicationInfo(activity.getPackageName(),
+ PackageManager.GET_META_DATA);
+ Bundle metaData = appInfo.metaData;
+ if (metaData == null || metaData.isEmpty()) {
+ return;
+ }
+
+ // When using the Godot editor for building and exporting the apk, this is used to check
+ // which plugins to enable.
+ // When using a custom process to generate the apk, the metadata is not needed since
+ // it's assumed that the developer is aware of the dependencies included in the apk.
+ final Set<String> enabledPluginsSet;
+ if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
+ String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
+ String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX);
+ if (enabledPluginsList.length == 0) {
+ // No plugins to enable. Aborting early.
+ return;
+ }
+
+ enabledPluginsSet = new HashSet<>();
+ for (String enabledPlugin : enabledPluginsList) {
+ enabledPluginsSet.add(enabledPlugin.trim());
+ }
+ } else {
+ enabledPluginsSet = null;
+ }
+
+ int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length();
+ for (String metaDataName : metaData.keySet()) {
+ // Parse the meta-data looking for entry with the Godot plugin name prefix.
+ if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
+ String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim();
+ if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) {
+ Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled.");
+ continue;
+ }
+
+ Log.i(TAG, "Initializing Godot plugin " + pluginName);
+
+ // Retrieve the plugin class full name.
+ String pluginHandleClassFullName = metaData.getString(metaDataName);
+ if (!TextUtils.isEmpty(pluginHandleClassFullName)) {
+ try {
+ // Attempt to create the plugin init class via reflection.
+ @SuppressWarnings("unchecked")
+ Class<GodotPlugin> pluginClass = (Class<GodotPlugin>)Class
+ .forName(pluginHandleClassFullName);
+ Constructor<GodotPlugin> pluginConstructor = pluginClass
+ .getConstructor(Godot.class);
+ GodotPlugin pluginHandle = pluginConstructor.newInstance(godot);
+
+ // Load the plugin initializer into the registry using the plugin name
+ // as key.
+ if (!pluginName.equals(pluginHandle.getPluginName())) {
+ Log.w(TAG,
+ "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
+ }
+ registry.put(pluginName, pluginHandle);
+ Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName());
+ } catch (ClassNotFoundException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (InstantiationException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (InvocationTargetException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ }
+ } else {
+ Log.w(TAG, "Invalid plugin loader class for " + pluginName);
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e);
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java
index 25fa10647d..f82c4d3fa0 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* RequestParams.java */
+/* SignalInfo.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,58 +28,72 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-package org.godotengine.godot.utils;
+package org.godotengine.godot.plugin;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import org.apache.http.NameValuePair;
-import org.apache.http.message.BasicNameValuePair;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
/**
- *
- * @author Luis Linietsky <luis.linietsky@gmail.com>
+ * Store information about a {@link GodotPlugin}'s signal.
*/
-public class RequestParams {
+public final class SignalInfo {
+ private final String name;
+ private final Class<?>[] paramTypes;
+ private final String[] paramTypesNames;
- private HashMap<String, String> params;
- private String url;
+ public SignalInfo(@NonNull String signalName, Class<?>... paramTypes) {
+ if (TextUtils.isEmpty(signalName)) {
+ throw new IllegalArgumentException("Invalid signal name: " + signalName);
+ }
- public RequestParams() {
- params = new HashMap<String, String>();
+ this.name = signalName;
+ this.paramTypes = paramTypes == null ? new Class<?>[ 0 ] : paramTypes;
+ this.paramTypesNames = new String[this.paramTypes.length];
+ for (int i = 0; i < this.paramTypes.length; i++) {
+ this.paramTypesNames[i] = this.paramTypes[i].getName();
+ }
}
- public void put(String key, String value) {
- params.put(key, value);
+ public String getName() {
+ return name;
}
- public String get(String key) {
- return params.get(key);
+ Class<?>[] getParamTypes() {
+ return paramTypes;
}
- public void remove(Object key) {
- params.remove(key);
+ String[] getParamTypesNames() {
+ return paramTypesNames;
}
- public boolean has(String key) {
- return params.containsKey(key);
+ @Override
+ public String toString() {
+ return "SignalInfo{"
+ +
+ "name='" + name + '\'' +
+ ", paramsTypes=" + Arrays.toString(paramTypes) +
+ '}';
}
- public List<NameValuePair> toPairsList() {
- List<NameValuePair> fields = new ArrayList<NameValuePair>();
-
- for (String key : params.keySet()) {
- fields.add(new BasicNameValuePair(key, this.get(key)));
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
}
- return fields;
- }
+ if (!(o instanceof SignalInfo)) {
+ return false;
+ }
+
+ SignalInfo that = (SignalInfo)o;
- public String getUrl() {
- return url;
+ return name.equals(that.name);
}
- public void setUrl(String url) {
- this.url = url;
+ @Override
+ public int hashCode() {
+ return name.hashCode();
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
index bc0e565774..acc9c4981b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
@@ -34,7 +34,6 @@ import java.security.MessageDigest;
import java.util.Random;
public class Crypt {
-
public static String md5(String input) {
try {
// Create MD5 Hash
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
index bbf876ea1f..82420eda79 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
@@ -31,6 +31,7 @@
package org.godotengine.godot.utils;
import android.util.Log;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
@@ -39,12 +40,10 @@ import javax.microedition.khronos.egl.EGLDisplay;
* Contains GL utilities methods.
*/
public class GLUtils {
-
private static final String TAG = GLUtils.class.getSimpleName();
public static final boolean DEBUG = false;
- public static boolean use_gl3 = false;
public static boolean use_32 = false;
public static boolean use_debug_opengl = false;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
index 011d426c7e..c89118ad55 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
@@ -30,10 +30,10 @@
package org.godotengine.godot.utils;
+import android.app.Activity;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.util.Log;
-import org.godotengine.godot.Godot;
/**
* This class handles Android-specific networking functions.
@@ -41,11 +41,10 @@ import org.godotengine.godot.Godot;
* to receive broadcast and multicast packets.
*/
public class GodotNetUtils {
-
/* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */
private WifiManager.MulticastLock multicastLock;
- public GodotNetUtils(Godot p_activity) {
+ public GodotNetUtils(Activity p_activity) {
if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) {
WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
multicastLock = wifi.createMulticastLock("GodotMulticastLock");
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java
deleted file mode 100644
index 68f9a83597..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*************************************************************************/
-/* HttpRequester.java */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-package org.godotengine.godot.utils;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.security.KeyStore;
-import java.util.Date;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.util.EntityUtils;
-
-/**
- *
- * @author Luis Linietsky <luis.linietsky@gmail.com>
- */
-public class HttpRequester {
-
- private Context context;
- private static final int TTL = 600000; // 10 minutos
- private long cttl = 0;
-
- public HttpRequester() {
- //Log.d("XXX", "Creando http request sin contexto");
- }
-
- public HttpRequester(Context context) {
- this.context = context;
- //Log.d("XXX", "Creando http request con contexto");
- }
-
- public String post(RequestParams params) {
- HttpPost httppost = new HttpPost(params.getUrl());
- try {
- httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList()));
- return request(httppost);
- } catch (UnsupportedEncodingException e) {
- return null;
- }
- }
-
- public String get(RequestParams params) {
- String response = getResponseFromCache(params.getUrl());
- if (response == null) {
- //Log.d("XXX", "Cache miss!");
- HttpGet httpget = new HttpGet(params.getUrl());
- long timeInit = new Date().getTime();
- response = request(httpget);
- long delay = new Date().getTime() - timeInit;
- Log.d("HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds");
- if (response == null || response.length() == 0) {
- response = "";
- } else {
- saveResponseIntoCache(params.getUrl(), response);
- }
- }
- Log.d("XXX", "Req: " + params.getUrl());
- Log.d("XXX", "Resp: " + response);
- return response;
- }
-
- private String request(HttpUriRequest request) {
- //Log.d("XXX", "Haciendo request a: " + request.getURI() );
- Log.d("PPP", "Haciendo request a: " + request.getURI());
- long init = new Date().getTime();
- HttpClient httpclient = getNewHttpClient();
- HttpParams httpParameters = httpclient.getParams();
- HttpConnectionParams.setConnectionTimeout(httpParameters, 0);
- HttpConnectionParams.setSoTimeout(httpParameters, 0);
- HttpConnectionParams.setTcpNoDelay(httpParameters, true);
- try {
- HttpResponse response = httpclient.execute(request);
- Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI());
- //Log.d("XXX1", "Status:" + response.getStatusLine().toString());
- if (response.getStatusLine().getStatusCode() == 200) {
- String strResponse = EntityUtils.toString(response.getEntity());
- //Log.d("XXX2", strResponse);
- return strResponse;
- } else {
- Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity()));
- return null;
- }
-
- } catch (ClientProtocolException e) {
- Log.d("XXX3", e.getMessage());
- } catch (IOException e) {
- Log.d("XXX4", e.getMessage());
- }
- return null;
- }
-
- private HttpClient getNewHttpClient() {
- try {
- KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
- trustStore.load(null, null);
-
- SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
- sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
-
- HttpParams params = new BasicHttpParams();
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
-
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
- registry.register(new Scheme("https", sf, 443));
-
- ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
-
- return new DefaultHttpClient(ccm, params);
- } catch (Exception e) {
- return new DefaultHttpClient();
- }
- }
-
- private static String convertStreamToString(InputStream is) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- StringBuilder sb = new StringBuilder();
- String line = null;
- try {
- while ((line = reader.readLine()) != null) {
- sb.append((line + "\n"));
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return sb.toString();
- }
-
- public void saveResponseIntoCache(String request, String response) {
- if (context == null) {
- //Log.d("XXX", "No context, cache failed!");
- return;
- }
- SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPref.edit();
- editor.putString("request_" + Crypt.md5(request), response);
- editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl());
- editor.apply();
- }
-
- public String getResponseFromCache(String request) {
- if (context == null) {
- Log.d("XXX", "No context, cache miss");
- return null;
- }
- SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE);
- long ttl = getResponseTtl(request);
- if (ttl == 0l || (new Date().getTime() - ttl) > 0l) {
- Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime());
- return null;
- }
- return sharedPref.getString("request_" + Crypt.md5(request), null);
- }
-
- public long getResponseTtl(String request) {
- SharedPreferences sharedPref = context.getSharedPreferences(
- "http_get_cache", Context.MODE_PRIVATE);
- return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l);
- }
-
- public long getTtl() {
- return cttl > 0 ? cttl : TTL;
- }
-
- public void setTtl(long ttl) {
- this.cttl = (ttl * 1000) + new Date().getTime();
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 7cf32b00fe..7104baf86e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -31,20 +31,24 @@
package org.godotengine.godot.utils;
import android.Manifest;
+import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Build;
-import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+import androidx.core.content.ContextCompat;
+
import java.util.ArrayList;
import java.util.List;
-import org.godotengine.godot.Godot;
/**
* This class includes utility functions for Android permissions related operations.
* @author Cagdas Caglak <cagdascaglak@gmail.com>
*/
public final class PermissionsUtil {
+ private static final String TAG = PermissionsUtil.class.getSimpleName();
static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
static final int REQUEST_CAMERA_PERMISSION = 2;
@@ -60,7 +64,7 @@ public final class PermissionsUtil {
* @param activity the caller activity for this method.
* @return true/false. "true" if permission was granted otherwise returns "false".
*/
- public static boolean requestPermission(String name, Godot activity) {
+ public static boolean requestPermission(String name, Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Not necessary, asked on install already
return true;
@@ -88,7 +92,7 @@ public final class PermissionsUtil {
* @param activity the caller activity for this method.
* @return true/false. "true" if all permissions were granted otherwise returns "false".
*/
- public static boolean requestManifestPermissions(Godot activity) {
+ public static boolean requestManifestPermissions(Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
@@ -113,8 +117,8 @@ public final class PermissionsUtil {
dangerousPermissions.add(manifestPermission);
}
} catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return false;
+ // Skip this permission and continue.
+ Log.w(TAG, "Unable to identify permission " + manifestPermission, e);
}
}
@@ -133,7 +137,7 @@ public final class PermissionsUtil {
* @param activity the caller activity for this method.
* @return granted permissions list
*/
- public static String[] getGrantedPermissions(Godot activity) {
+ public static String[] getGrantedPermissions(Activity activity) {
String[] manifestPermissions;
try {
manifestPermissions = getManifestPermissions(activity);
@@ -153,8 +157,8 @@ public final class PermissionsUtil {
dangerousPermissions.add(manifestPermission);
}
} catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return new String[0];
+ // Skip this permission and continue.
+ Log.w(TAG, "Unable to identify permission " + manifestPermission, e);
}
}
@@ -167,7 +171,7 @@ public final class PermissionsUtil {
* @param permission the permession to look for in the manifest file.
* @return "true" if the permission is in the manifest file of the activity, "false" otherwise.
*/
- public static boolean hasManifestPermission(Godot activity, String permission) {
+ public static boolean hasManifestPermission(Activity activity, String permission) {
try {
for (String p : getManifestPermissions(activity)) {
if (permission.equals(p))
@@ -185,7 +189,7 @@ public final class PermissionsUtil {
* @return manifest permissions list
* @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
*/
- private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException {
+ private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException {
PackageManager packageManager = activity.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
if (packageInfo.requestedPermissions == null)
@@ -200,7 +204,7 @@ public final class PermissionsUtil {
* @return permission info object
* @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
*/
- private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException {
+ private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException {
PackageManager packageManager = activity.getPackageManager();
return packageManager.getPermissionInfo(permission, 0);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
index 84a7eda6e0..7fa8e3b4e5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* PaymentsCache.java */
+/* VkRenderer.kt */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,45 +28,82 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-package org.godotengine.godot.payments;
+@file:JvmName("VkRenderer")
+package org.godotengine.godot.vulkan
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
+import android.view.Surface
-public class PaymentsCache {
+import org.godotengine.godot.Godot
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.plugin.GodotPlugin
+import org.godotengine.godot.plugin.GodotPluginRegistry
- public Context context;
+/**
+ * Responsible to setting up and driving the Vulkan rendering logic.
+ *
+ * <h3>Threading</h3>
+ * The renderer will be called on a separate thread, so that rendering
+ * performance is decoupled from the UI thread. Clients typically need to
+ * communicate with the renderer from the UI thread, because that's where
+ * input events are received. Clients can communicate using any of the
+ * standard Java techniques for cross-thread communication, or they can
+ * use the [VkSurfaceView.queueOnVkThread] convenience method.
+ *
+ * @see [VkSurfaceView.startRenderer]
+ */
+internal class VkRenderer {
- public PaymentsCache(Context context) {
- this.context = context;
+ private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry()
+
+ /**
+ * Called when the surface is created and signals the beginning of rendering.
+ */
+ fun onVkSurfaceCreated(surface: Surface) {
+ GodotLib.newcontext(surface, false)
+
+ for (plugin in pluginRegistry.getAllPlugins()) {
+ plugin.onVkSurfaceCreated(surface)
+ }
+ }
+
+ /**
+ * Called after the surface is created and whenever its size changes.
+ */
+ fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) {
+ GodotLib.resize(surface, width, height)
+
+ for (plugin in pluginRegistry.getAllPlugins()) {
+ plugin.onVkSurfaceChanged(surface, width, height)
+ }
}
- public void setConsumableFlag(String set, String sku, Boolean flag) {
- SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPref.edit();
- editor.putBoolean(sku, flag);
- editor.apply();
+ /**
+ * Called to draw the current frame.
+ */
+ fun onVkDrawFrame() {
+ GodotLib.step()
+ for (plugin in pluginRegistry.getAllPlugins()) {
+ plugin.onVkDrawFrame()
+ }
}
- public boolean getConsumableFlag(String set, String sku) {
- SharedPreferences sharedPref = context.getSharedPreferences(
- "consumables_" + set, Context.MODE_PRIVATE);
- return sharedPref.getBoolean(sku, false);
+ /**
+ * Called when the rendering thread is resumed.
+ */
+ fun onVkResume() {
+ GodotLib.onRendererResumed()
}
- public void setConsumableValue(String set, String sku, String value) {
- SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPref.edit();
- editor.putString(sku, value);
- //Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku);
- editor.apply();
+ /**
+ * Called when the rendering thread is paused.
+ */
+ fun onVkPause() {
+ GodotLib.onRendererPaused()
}
- public String getConsumableValue(String set, String sku) {
- SharedPreferences sharedPref = context.getSharedPreferences(
- "consumables_" + set, Context.MODE_PRIVATE);
- //Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku);
- return sharedPref.getString(sku, null);
+ /**
+ * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic.
+ */
+ fun onVkDestroy() {
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
new file mode 100644
index 0000000000..6b0e12b21a
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
@@ -0,0 +1,136 @@
+/*************************************************************************/
+/* VkSurfaceView.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+@file:JvmName("VkSurfaceView")
+package org.godotengine.godot.vulkan
+
+import android.content.Context
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying Vulkan rendering.
+ * <p>
+ * A [VkSurfaceView] provides the following features:
+ * <p>
+ * <ul>
+ * <li>Manages a surface, which is a special piece of memory that can be
+ * composited into the Android view system.
+ * <li>Accepts a user-provided [VkRenderer] object that does the actual rendering.
+ * <li>Renders on a dedicated [VkThread] thread to decouple rendering performance from the
+ * UI thread.
+ * </ul>
+ */
+open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
+
+ companion object {
+ fun checkState(expression: Boolean, errorMessage: Any) {
+ check(expression) { errorMessage.toString() }
+ }
+ }
+
+ /**
+ * Thread used to drive the vulkan logic.
+ */
+ private val vkThread: VkThread by lazy {
+ VkThread(this, renderer)
+ }
+
+ /**
+ * Performs the actual rendering.
+ */
+ private lateinit var renderer: VkRenderer
+
+ init {
+ isClickable = true
+ holder.addCallback(this)
+ }
+
+ /**
+ * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan
+ * rendering.
+ *
+ * This method should be called once and only once in the life-cycle of [VkSurfaceView].
+ */
+ fun startRenderer(renderer: VkRenderer) {
+ checkState(!this::renderer.isInitialized, "startRenderer must only be invoked once")
+ this.renderer = renderer
+ vkThread.start()
+ }
+
+ /**
+ * Queues a runnable to be run on the Vulkan rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun queueOnVkThread(runnable: Runnable) {
+ vkThread.queueEvent(runnable)
+ }
+
+ /**
+ * Resumes the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ open fun onResume() {
+ vkThread.onResume()
+ }
+
+ /**
+ * Pauses the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ open fun onPause() {
+ vkThread.onPause()
+ }
+
+ /**
+ * Tear down the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun onDestroy() {
+ vkThread.blockingExit()
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ vkThread.onSurfaceChanged(width, height)
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ vkThread.onSurfaceDestroyed()
+ }
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ vkThread.onSurfaceCreated()
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
new file mode 100644
index 0000000000..7557c8aa22
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
@@ -0,0 +1,230 @@
+/*************************************************************************/
+/* VkThread.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+@file:JvmName("VkThread")
+package org.godotengine.godot.vulkan
+
+import android.util.Log
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * Thread implementation for the [VkSurfaceView] onto which the vulkan logic is ran.
+ *
+ * The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread.
+ */
+internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vkRenderer: VkRenderer) : Thread(TAG) {
+
+ companion object {
+ private val TAG = VkThread::class.java.simpleName
+ }
+
+ /**
+ * Used to run events scheduled on the thread.
+ */
+ private val eventQueue = ArrayList<Runnable>()
+
+ /**
+ * Used to synchronize interaction with other threads (e.g: main thread).
+ */
+ private val lock = ReentrantLock()
+ private val lockCondition = lock.newCondition()
+
+ private var shouldExit = false
+ private var exited = false
+ private var rendererInitialized = false
+ private var rendererResumed = false
+ private var resumed = false
+ private var hasSurface = false
+ private var width = 0
+ private var height = 0
+
+ /**
+ * Determine when drawing can occur on the thread. This usually occurs after the
+ * [android.view.Surface] is available, the app is in a resumed state.
+ */
+ private val readyToDraw
+ get() = hasSurface && resumed
+
+ private fun threadExiting() {
+ lock.withLock {
+ exited = true
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Queue an event on the [VkThread].
+ */
+ fun queueEvent(event: Runnable) {
+ lock.withLock {
+ eventQueue.add(event)
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Request the thread to exit and block until it's done.
+ */
+ fun blockingExit() {
+ lock.withLock {
+ shouldExit = true
+ lockCondition.signalAll()
+ while (!exited) {
+ try {
+ Log.i(TAG, "Waiting on exit for $name")
+ lockCondition.await()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when the app resumes.
+ */
+ fun onResume() {
+ lock.withLock {
+ resumed = true
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the app pauses.
+ */
+ fun onPause() {
+ lock.withLock {
+ resumed = false
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the [android.view.Surface] has been created.
+ */
+ fun onSurfaceCreated() {
+ // This is a no op because surface creation will always be followed by surfaceChanged()
+ // which provide all the needed information.
+ }
+
+ /**
+ * Invoked following structural updates to [android.view.Surface].
+ */
+ fun onSurfaceChanged(width: Int, height: Int) {
+ lock.withLock {
+ hasSurface = true
+ this.width = width
+ this.height = height
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the [android.view.Surface] is no longer available.
+ */
+ fun onSurfaceDestroyed() {
+ lock.withLock {
+ hasSurface = false
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Thread loop modeled after [android.opengl.GLSurfaceView]'s GLThread.
+ */
+ override fun run() {
+ try {
+ while (true) {
+ var event: Runnable? = null
+ lock.withLock {
+ while (true) {
+ // Code path for exiting the thread loop.
+ if (shouldExit) {
+ vkRenderer.onVkDestroy()
+ return
+ }
+
+ // Check for events and execute them outside of the loop if found to avoid
+ // blocking the thread lifecycle by holding onto the lock.
+ if (eventQueue.isNotEmpty()) {
+ event = eventQueue.removeAt(0)
+ break;
+ }
+
+ if (readyToDraw) {
+ if (!rendererResumed) {
+ rendererResumed = true
+ vkRenderer.onVkResume()
+
+ if (!rendererInitialized) {
+ rendererInitialized = true
+ vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface)
+ }
+
+ vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height)
+ }
+
+ // Break out of the loop so drawing can occur without holding onto the lock.
+ break;
+ } else if (rendererResumed) {
+ // If we aren't ready to draw but are resumed, that means we either lost a surface
+ // or the app was paused.
+ rendererResumed = false
+ vkRenderer.onVkPause()
+ }
+ // We only reach this state if we are not ready to draw and have no queued events, so
+ // we wait.
+ // On state change, the thread will be awoken using the [lock] and [lockCondition], and
+ // we will resume execution.
+ lockCondition.await()
+ }
+ }
+
+ // Run queued event.
+ if (event != null) {
+ event?.run()
+ continue
+ }
+
+ // Draw only when there no more queued events.
+ vkRenderer.onVkDrawFrame()
+ }
+ } catch (ex: InterruptedException) {
+ Log.i(TAG, "InterruptedException", ex)
+ } catch (ex: IllegalStateException) {
+ Log.i(TAG, "IllegalStateException", ex)
+ } finally {
+ threadExiting()
+ }
+ }
+
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
index 9209d6ccf2..819bcccdf1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
@@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr;
import android.opengl.EGLExt;
import android.opengl.GLSurfaceView;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
@@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLDisplay;
* EGL config chooser for the Oculus Mobile VR SDK.
*/
public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser {
-
private static final int[] CONFIG_ATTRIBS = {
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
index 36f4416df2..2d9b921466 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
@@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr;
import android.opengl.EGL14;
import android.opengl.GLSurfaceView;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
@@ -41,7 +42,6 @@ import javax.microedition.khronos.egl.EGLDisplay;
* EGL Context factory for the Oculus mobile VR SDK.
*/
public class OvrContextFactory implements GLSurfaceView.EGLContextFactory {
-
private static final int[] CONTEXT_ATTRIBS = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE
};
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
index b2aa130f37..43c7f0f966 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
@@ -31,6 +31,7 @@
package org.godotengine.godot.xr.ovr;
import android.opengl.GLSurfaceView;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
@@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLSurface;
* EGL window surface factory for the Oculus mobile VR SDK.
*/
public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory {
-
private final static int[] SURFACE_ATTRIBS = {
EGL10.EGL_WIDTH, 16,
EGL10.EGL_HEIGHT, 16,
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
index ce4defd7a7..54672db282 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
@@ -30,21 +30,24 @@
package org.godotengine.godot.xr.regular;
+import org.godotengine.godot.utils.GLUtils;
+
import android.opengl.GLSurfaceView;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
-import org.godotengine.godot.utils.GLUtils;
/**
* Used to select the egl config for pancake games.
*/
public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {
-
private static final String TAG = RegularConfigChooser.class.getSimpleName();
private int[] mValue = new int[1];
+ // FIXME: Add support for Vulkan.
+
/* This EGL config specification is used to specify 2.0 rendering.
* We use a minimum size of 4 bits for red/green/blue, but will
* perform actual matching in chooseConfig() below.
@@ -59,15 +62,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
- private static int[] s_configAttribs3 = {
- EGL10.EGL_RED_SIZE, 4,
- EGL10.EGL_GREEN_SIZE, 4,
- EGL10.EGL_BLUE_SIZE, 4,
- // EGL10.EGL_DEPTH_SIZE, 16,
- // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT
- EGL10.EGL_NONE
- };
public RegularConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
mRedSize = r;
@@ -79,11 +73,10 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {
}
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
-
/* Get the number of minimally matching EGL configurations
*/
int[] num_config = new int[1];
- egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config);
+ egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
int numConfigs = num_config[0];
@@ -94,7 +87,7 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {
/* Allocate then read the array of minimally matching EGL configs
*/
EGLConfig[] configs = new EGLConfig[numConfigs];
- egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config);
+ egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
if (GLUtils.DEBUG) {
GLUtils.printConfigs(egl, display, configs);
@@ -134,7 +127,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
-
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
index 22bd4ced87..126f3ad5f5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
@@ -30,14 +30,16 @@
package org.godotengine.godot.xr.regular;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.utils.GLUtils;
+
import android.opengl.GLSurfaceView;
import android.util.Log;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
-import org.godotengine.godot.GodotLib;
-import org.godotengine.godot.utils.GLUtils;
/**
* Factory used to setup the opengl context for pancake games.
@@ -51,25 +53,17 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory {
private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
- String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name");
- if (GLUtils.use_gl3 && !driver_name.equals("GLES3")) {
- GLUtils.use_gl3 = false;
- }
- if (GLUtils.use_gl3)
- Log.w(TAG, "creating OpenGL ES 3.0 context :");
- else
- Log.w(TAG, "creating OpenGL ES 2.0 context :");
+ // FIXME: Add support for Vulkan.
+ Log.w(TAG, "creating OpenGL ES 2.0 context :");
GLUtils.checkEglError(TAG, "Before eglCreateContext", egl);
EGLContext context;
if (GLUtils.use_debug_opengl) {
int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
- int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
- context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2);
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2);
} else {
int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
- int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE };
- context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2);
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2);
}
GLUtils.checkEglError(TAG, "After eglCreateContext", egl);
return context;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
index 71fcf06020..c83c47bed7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
@@ -30,15 +30,16 @@
package org.godotengine.godot.xr.regular;
+import org.godotengine.godot.utils.GLUtils;
+
import android.util.Log;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
-import org.godotengine.godot.utils.GLUtils;
/* Fallback if 32bit View is not supported*/
public class RegularFallbackConfigChooser extends RegularConfigChooser {
-
private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName();
private RegularConfigChooser fallback;
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index fe2fd89710..9b44ac4b41 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -32,32 +32,28 @@
#include "string_android.h"
#include "thread_jandroid.h"
-bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error, Variant &ret) {
-
- Map<StringName, List<MethodInfo> >::Element *M = methods.find(p_method);
+bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) {
+ Map<StringName, List<MethodInfo>>::Element *M = methods.find(p_method);
if (!M)
return false;
JNIEnv *env = ThreadAndroid::get_env();
- MethodInfo *method = NULL;
+ MethodInfo *method = nullptr;
for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) {
-
if (!p_instance && !E->get()._static) {
- r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
continue;
}
int pc = E->get().param_types.size();
if (pc > p_argcount) {
-
- r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = pc;
continue;
}
if (pc < p_argcount) {
-
- r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.argument = pc;
continue;
}
@@ -65,10 +61,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
bool valid = true;
for (int i = 0; i < pc; i++) {
-
Variant::Type arg_expected = Variant::NIL;
switch (ptypes[i]) {
-
case ARG_TYPE_VOID: {
//bug?
} break;
@@ -86,7 +80,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
case ARG_TYPE_SHORT:
case ARG_TYPE_INT:
case ARG_TYPE_LONG: {
-
if (!p_args[i]->is_num())
arg_expected = Variant::INT;
@@ -95,32 +88,26 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
case ARG_NUMBER_CLASS_BIT | ARG_TYPE_DOUBLE:
case ARG_TYPE_FLOAT:
case ARG_TYPE_DOUBLE: {
-
if (!p_args[i]->is_num())
- arg_expected = Variant::REAL;
+ arg_expected = Variant::FLOAT;
} break;
case ARG_TYPE_STRING: {
-
if (p_args[i]->get_type() != Variant::STRING)
arg_expected = Variant::STRING;
} break;
case ARG_TYPE_CLASS: {
-
if (p_args[i]->get_type() != Variant::OBJECT)
arg_expected = Variant::OBJECT;
else {
-
Ref<Reference> ref = *p_args[i];
if (!ref.is_null()) {
if (Object::cast_to<JavaObject>(ref.ptr())) {
-
Ref<JavaObject> jo = ref;
//could be faster
jclass c = env->FindClass(E->get().param_sigs[i].operator String().utf8().get_data());
if (!c || !env->IsInstanceOf(jo->instance, c)) {
-
arg_expected = Variant::OBJECT;
} else {
//ok
@@ -133,7 +120,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
default: {
-
if (p_args[i]->get_type() != Variant::ARRAY)
arg_expected = Variant::ARRAY;
@@ -141,7 +127,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
if (arg_expected != Variant::NIL) {
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = i;
r_error.expected = arg_expected;
valid = false;
@@ -158,22 +144,20 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
if (!method)
return true; //no version convinces
- r_error.error = Variant::CallError::CALL_OK;
+ r_error.error = Callable::CallError::CALL_OK;
- jvalue *argv = NULL;
+ jvalue *argv = nullptr;
if (method->param_types.size()) {
-
argv = (jvalue *)alloca(sizeof(jvalue) * method->param_types.size());
}
List<jobject> to_free;
for (int i = 0; i < method->param_types.size(); i++) {
-
switch (method->param_types[i]) {
case ARG_TYPE_VOID: {
//can't happen
- argv[i].l = NULL; //I hope this works
+ argv[i].l = nullptr; //I hope this works
} break;
case ARG_TYPE_BOOLEAN: {
@@ -279,18 +263,15 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
to_free.push_back(jStr);
} break;
case ARG_TYPE_CLASS: {
-
Ref<JavaObject> jo = *p_args[i];
if (jo.is_valid()) {
-
argv[i].l = jo->instance;
} else {
- argv[i].l = NULL; //I hope this works
+ argv[i].l = nullptr; //I hope this works
}
} break;
case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: {
-
Array arr = *p_args[i];
jbooleanArray a = env->NewBooleanArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -302,7 +283,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_BYTE: {
-
Array arr = *p_args[i];
jbyteArray a = env->NewByteArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -314,7 +294,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_CHAR: {
-
Array arr = *p_args[i];
jcharArray a = env->NewCharArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -326,7 +305,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_SHORT: {
-
Array arr = *p_args[i];
jshortArray a = env->NewShortArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -338,7 +316,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_INT: {
-
Array arr = *p_args[i];
jintArray a = env->NewIntArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -360,7 +337,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_FLOAT: {
-
Array arr = *p_args[i];
jfloatArray a = env->NewFloatArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -372,7 +348,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_DOUBLE: {
-
Array arr = *p_args[i];
jdoubleArray a = env->NewDoubleArray(arr.size());
for (int j = 0; j < arr.size(); j++) {
@@ -384,11 +359,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_ARRAY_BIT | ARG_TYPE_STRING: {
-
Array arr = *p_args[i];
- jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), NULL);
+ jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), nullptr);
for (int j = 0; j < arr.size(); j++) {
-
String s = arr[j];
jstring jStr = env->NewStringUTF(s.utf8().get_data());
env->SetObjectArrayElement(a, j, jStr);
@@ -399,17 +372,15 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
to_free.push_back(a);
} break;
case ARG_ARRAY_BIT | ARG_TYPE_CLASS: {
-
- argv[i].l = NULL;
+ argv[i].l = nullptr;
} break;
}
}
- r_error.error = Variant::CallError::CALL_OK;
+ r_error.error = Callable::CallError::CALL_OK;
bool success = true;
switch (method->return_type) {
-
case ARG_TYPE_VOID: {
if (method->_static) {
env->CallStaticVoidMethodA(_class, method->method, argv);
@@ -434,7 +405,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
} break;
case ARG_TYPE_CHAR: {
-
if (method->_static) {
ret = env->CallStaticCharMethodA(_class, method->method, argv);
} else {
@@ -442,7 +412,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
} break;
case ARG_TYPE_SHORT: {
-
if (method->_static) {
ret = env->CallStaticShortMethodA(_class, method->method, argv);
} else {
@@ -451,7 +420,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_TYPE_INT: {
-
if (method->_static) {
ret = env->CallStaticIntMethodA(_class, method->method, argv);
} else {
@@ -460,7 +428,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_TYPE_LONG: {
-
if (method->_static) {
ret = (int64_t)env->CallStaticLongMethodA(_class, method->method, argv);
} else {
@@ -469,7 +436,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_TYPE_FLOAT: {
-
if (method->_static) {
ret = env->CallStaticFloatMethodA(_class, method->method, argv);
} else {
@@ -478,7 +444,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
case ARG_TYPE_DOUBLE: {
-
if (method->_static) {
ret = env->CallStaticDoubleMethodA(_class, method->method, argv);
} else {
@@ -487,7 +452,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
default: {
-
jobject obj;
if (method->_static) {
obj = env->CallStaticObjectMethodA(_class, method->method, argv);
@@ -498,10 +462,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
if (!obj) {
ret = Variant();
} else {
-
if (!_convert_object_to_variant(env, obj, ret, method->return_type)) {
ret = Variant();
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
success = false;
}
env->DeleteLocalRef(obj);
@@ -517,10 +480,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
return success;
}
-Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
+Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Variant ret;
- bool found = _call_method(NULL, p_method, p_args, p_argcount, r_error, ret);
+ bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret);
if (found) {
return ret;
}
@@ -533,8 +495,7 @@ JavaClass::JavaClass() {
/////////////////////
-Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
+Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
return Variant();
}
@@ -547,14 +508,12 @@ JavaObject::~JavaObject() {
////////////////////
bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig) {
-
jstring name2 = (jstring)env->CallObjectMethod(obj, Class_getName);
String str_type = jstring_to_string(name2, env);
env->DeleteLocalRef(name2);
uint32_t t = 0;
if (str_type.begins_with("[")) {
-
t = JavaClass::ARG_ARRAY_BIT;
strsig = "[";
str_type = str_type.substr(1, str_type.length() - 1);
@@ -633,87 +592,71 @@ bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, St
}
bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &var, uint32_t p_sig) {
-
if (!obj) {
var = Variant(); //seems null is just null...
return true;
}
switch (p_sig) {
-
case ARG_TYPE_VOID: {
-
return Variant();
} break;
case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallBooleanMethod(obj, JavaClassWrapper::singleton->Boolean_booleanValue);
return true;
} break;
case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallByteMethod(obj, JavaClassWrapper::singleton->Byte_byteValue);
return true;
} break;
case ARG_TYPE_CHAR | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallCharMethod(obj, JavaClassWrapper::singleton->Character_characterValue);
return true;
} break;
case ARG_TYPE_SHORT | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallShortMethod(obj, JavaClassWrapper::singleton->Short_shortValue);
return true;
} break;
case ARG_TYPE_INT | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallIntMethod(obj, JavaClassWrapper::singleton->Integer_integerValue);
return true;
} break;
case ARG_TYPE_LONG | ARG_NUMBER_CLASS_BIT: {
-
var = (int64_t)env->CallLongMethod(obj, JavaClassWrapper::singleton->Long_longValue);
return true;
} break;
case ARG_TYPE_FLOAT | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallFloatMethod(obj, JavaClassWrapper::singleton->Float_floatValue);
return true;
} break;
case ARG_TYPE_DOUBLE | ARG_NUMBER_CLASS_BIT: {
-
var = env->CallDoubleMethod(obj, JavaClassWrapper::singleton->Double_doubleValue);
return true;
} break;
case ARG_TYPE_STRING: {
-
var = jstring_to_string((jstring)obj, env);
return true;
} break;
case ARG_TYPE_CLASS: {
-
return false;
} break;
case ARG_ARRAY_BIT | ARG_TYPE_VOID: {
-
var = Array(); // ?
return true;
} break;
case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jboolean val;
env->GetBooleanArrayRegion((jbooleanArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -724,14 +667,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
} break;
case ARG_ARRAY_BIT | ARG_TYPE_BYTE: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jbyte val;
env->GetByteArrayRegion((jbyteArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -747,7 +688,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jchar val;
env->GetCharArrayRegion((jcharArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -763,7 +703,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jshort val;
env->GetShortArrayRegion((jshortArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -779,7 +718,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jint val;
env->GetIntArrayRegion((jintArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -795,7 +733,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jlong val;
env->GetLongArrayRegion((jlongArray)arr, 0, 1, &val);
ret.push_back((int64_t)val);
@@ -811,7 +748,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jfloat val;
env->GetFloatArrayRegion((jfloatArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -827,7 +763,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jdouble val;
env->GetDoubleArrayRegion((jdoubleArray)arr, 0, 1, &val);
ret.push_back(val);
@@ -837,14 +772,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -860,14 +793,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BYTE: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -882,14 +813,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_CHAR: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -904,14 +833,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_SHORT: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -926,14 +853,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_INT: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -948,14 +873,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_LONG: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -970,14 +893,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_FLOAT: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -998,7 +919,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -1014,14 +934,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
} break;
case ARG_ARRAY_BIT | ARG_TYPE_STRING: {
-
Array ret;
jobjectArray arr = (jobjectArray)obj;
int count = env->GetArrayLength(arr);
for (int i = 0; i < count; i++) {
-
jobject o = env->GetObjectArrayElement(arr, i);
if (!o)
ret.push_back(Variant());
@@ -1036,7 +954,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_ARRAY_BIT | ARG_TYPE_CLASS: {
-
} break;
}
@@ -1044,7 +961,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
}
Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
-
if (class_cache.has(p_class))
return class_cache[p_class];
@@ -1066,7 +982,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
int count = env->GetArrayLength(methods);
for (int i = 0; i < count; i++) {
-
jobject obj = env->GetObjectArrayElement(methods, i);
ERR_CONTINUE(!obj);
@@ -1096,7 +1011,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
String signature = "(";
for (int j = 0; j < count2; j++) {
-
jobject obj2 = env->GetObjectArrayElement(param_types, j);
String strsig;
uint32_t sig = 0;
@@ -1138,7 +1052,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
bool discard = false;
for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) {
-
float new_likeliness = 0;
float existing_likeliness = 0;
@@ -1146,7 +1059,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
continue;
bool valid = true;
for (int j = 0; j < E->get().param_types.size(); j++) {
-
Variant::Type _new;
float new_l;
Variant::Type existing;
@@ -1195,7 +1107,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
count = env->GetArrayLength(fields);
for (int i = 0; i < count; i++) {
-
jobject obj = env->GetObjectArrayElement(fields, i);
ERR_CONTINUE(!obj);
@@ -1205,19 +1116,15 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
int mods = env->CallIntMethod(obj, Field_getModifiers);
if ((mods & 0x8) && (mods & 0x10) && (mods & 0x1)) { //static final public!
- jobject objc = env->CallObjectMethod(obj, Field_get, NULL);
+ jobject objc = env->CallObjectMethod(obj, Field_get, nullptr);
if (objc) {
-
uint32_t sig;
String strsig;
jclass cl = env->GetObjectClass(objc);
if (JavaClassWrapper::_get_type_sig(env, cl, sig, strsig)) {
-
if ((sig & JavaClass::ARG_TYPE_MASK) <= JavaClass::ARG_TYPE_STRING) {
-
Variant value;
if (JavaClass::_convert_object_to_variant(env, objc, value, sig)) {
-
java_class->constant_map[str_field] = value;
}
}
@@ -1236,15 +1143,14 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
return Ref<JavaClass>();
}
-JavaClassWrapper *JavaClassWrapper::singleton = NULL;
+JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
-
singleton = this;
JNIEnv *env = ThreadAndroid::get_env();
- jclass activityClass = env->FindClass("org/godotengine/godot/Godot");
+ jclass activityClass = env->FindClass("android/app/Activity");
jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
classLoader = env->CallObjectMethod(p_activity, getClassLoader);
classLoader = (jclass)env->NewGlobalRef(classLoader);
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 8d075f8e97..4ccbc6b97e 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -53,14 +53,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
- _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;I)V");
+ _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
+ _get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
- _play_video = p_env->GetMethodID(cls, "playVideo", "(Ljava/lang/String;)V");
- _is_video_playing = p_env->GetMethodID(cls, "isVideoPlaying", "()Z");
- _pause_video = p_env->GetMethodID(cls, "pauseVideo", "()V");
- _stop_video = p_env->GetMethodID(cls, "stopVideo", "()V");
}
}
@@ -135,11 +132,11 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != 0) && (_hide_keyboard != 0);
}
-void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length) {
+void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = ThreadAndroid::get_env();
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
- env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length);
+ env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
}
}
@@ -157,40 +154,22 @@ void GodotIOJavaWrapper::set_screen_orientation(int p_orient) {
}
}
-String GodotIOJavaWrapper::get_system_dir(int p_dir) {
- if (_get_system_dir) {
+int GodotIOJavaWrapper::get_screen_orientation() {
+ if (_get_screen_orientation) {
JNIEnv *env = ThreadAndroid::get_env();
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
- return jstring_to_string(s, env);
+ return env->CallIntMethod(godot_io_instance, _get_screen_orientation);
} else {
- return String(".");
+ return 0;
}
}
-void GodotIOJavaWrapper::play_video(const String &p_path) {
- // Why is this not here?!?!
-}
-
-bool GodotIOJavaWrapper::is_video_playing() {
- if (_is_video_playing) {
+String GodotIOJavaWrapper::get_system_dir(int p_dir) {
+ if (_get_system_dir) {
JNIEnv *env = ThreadAndroid::get_env();
- return env->CallBooleanMethod(godot_io_instance, _is_video_playing);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
+ return jstring_to_string(s, env);
} else {
- return false;
- }
-}
-
-void GodotIOJavaWrapper::pause_video() {
- if (_pause_video) {
- JNIEnv *env = ThreadAndroid::get_env();
- env->CallVoidMethod(godot_io_instance, _pause_video);
- }
-}
-
-void GodotIOJavaWrapper::stop_video() {
- if (_stop_video) {
- JNIEnv *env = ThreadAndroid::get_env();
- env->CallVoidMethod(godot_io_instance, _stop_video);
+ return String(".");
}
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 7dfed52187..6465ded985 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -54,11 +54,8 @@ private:
jmethodID _show_keyboard = 0;
jmethodID _hide_keyboard = 0;
jmethodID _set_screen_orientation = 0;
+ jmethodID _get_screen_orientation = 0;
jmethodID _get_system_dir = 0;
- jmethodID _play_video = 0;
- jmethodID _is_video_playing = 0;
- jmethodID _pause_video = 0;
- jmethodID _stop_video = 0;
public:
GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance);
@@ -73,16 +70,13 @@ public:
int get_screen_dpi();
String get_unique_id();
bool has_vk();
- void show_vk(const String &p_existing, int p_max_input_length);
+ void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk();
int get_vk_height();
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
+ int get_screen_orientation();
String get_system_dir(int p_dir);
- void play_video(const String &p_path);
- bool is_video_playing();
- void pause_video();
- void stop_video();
};
#endif /* !JAVA_GODOT_IO_WRAPPER_H */
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index dedb2ee114..4610b94363 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -29,568 +29,36 @@
/*************************************************************************/
#include "java_godot_lib_jni.h"
+
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
#include "android/asset_manager_jni.h"
#include "api/java_class_wrapper.h"
+#include "api/jni_singleton.h"
#include "audio_driver_jandroid.h"
#include "core/engine.h"
-#include "core/os/keyboard.h"
+#include "core/input/input.h"
#include "core/project_settings.h"
#include "dir_access_jandroid.h"
+#include "display_server_android.h"
#include "file_access_android.h"
#include "file_access_jandroid.h"
-#include "main/input_default.h"
+#include "jni_utils.h"
#include "main/main.h"
#include "net_socket_android.h"
#include "os_android.h"
#include "string_android.h"
#include "thread_jandroid.h"
-#include <unistd.h>
-
-static JavaClassWrapper *java_class_wrapper = NULL;
-static OS_Android *os_android = NULL;
-static GodotJavaWrapper *godot_java = NULL;
-static GodotIOJavaWrapper *godot_io_java = NULL;
-
-struct jvalret {
-
- jobject obj;
- jvalue val;
- jvalret() { obj = NULL; }
-};
-
-jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false) {
-
- jvalret v;
-
- switch (p_type) {
-
- case Variant::BOOL: {
-
- if (force_jobject) {
- jclass bclass = env->FindClass("java/lang/Boolean");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V");
- jvalue val;
- val.z = (bool)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
- } else {
- v.val.z = *p_arg;
- };
- } break;
- case Variant::INT: {
-
- if (force_jobject) {
-
- jclass bclass = env->FindClass("java/lang/Integer");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V");
- jvalue val;
- val.i = (int)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
-
- } else {
- v.val.i = *p_arg;
- };
- } break;
- case Variant::REAL: {
-
- if (force_jobject) {
-
- jclass bclass = env->FindClass("java/lang/Double");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V");
- jvalue val;
- val.d = (double)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
-
- } else {
- v.val.f = *p_arg;
- };
- } break;
- case Variant::STRING: {
-
- String s = *p_arg;
- jstring jStr = env->NewStringUTF(s.utf8().get_data());
- v.val.l = jStr;
- v.obj = jStr;
- } break;
- case Variant::POOL_STRING_ARRAY: {
-
- PoolVector<String> sarray = *p_arg;
- jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
-
- for (int j = 0; j < sarray.size(); j++) {
-
- jstring str = env->NewStringUTF(sarray[j].utf8().get_data());
- env->SetObjectArrayElement(arr, j, str);
- env->DeleteLocalRef(str);
- }
- v.val.l = arr;
- v.obj = arr;
-
- } break;
-
- case Variant::DICTIONARY: {
-
- Dictionary dict = *p_arg;
- jclass dclass = env->FindClass("org/godotengine/godot/Dictionary");
- jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V");
- jobject jdict = env->NewObject(dclass, ctor);
-
- Array keys = dict.keys();
-
- jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
- for (int j = 0; j < keys.size(); j++) {
- jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data());
- env->SetObjectArrayElement(jkeys, j, str);
- env->DeleteLocalRef(str);
- };
-
- jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V");
- jvalue val;
- val.l = jkeys;
- env->CallVoidMethodA(jdict, set_keys, &val);
- env->DeleteLocalRef(jkeys);
-
- jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), NULL);
-
- for (int j = 0; j < keys.size(); j++) {
- Variant var = dict[keys[j]];
- jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true);
- env->SetObjectArrayElement(jvalues, j, v.val.l);
- if (v.obj) {
- env->DeleteLocalRef(v.obj);
- }
- };
-
- jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V");
- val.l = jvalues;
- env->CallVoidMethodA(jdict, set_values, &val);
- env->DeleteLocalRef(jvalues);
- env->DeleteLocalRef(dclass);
-
- v.val.l = jdict;
- v.obj = jdict;
- } break;
-
- case Variant::POOL_INT_ARRAY: {
-
- PoolVector<int> array = *p_arg;
- jintArray arr = env->NewIntArray(array.size());
- PoolVector<int>::Read r = array.read();
- env->SetIntArrayRegion(arr, 0, array.size(), r.ptr());
- v.val.l = arr;
- v.obj = arr;
-
- } break;
- case Variant::POOL_BYTE_ARRAY: {
- PoolVector<uint8_t> array = *p_arg;
- jbyteArray arr = env->NewByteArray(array.size());
- PoolVector<uint8_t>::Read r = array.read();
- env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r.ptr()));
- v.val.l = arr;
- v.obj = arr;
-
- } break;
- case Variant::POOL_REAL_ARRAY: {
-
- PoolVector<float> array = *p_arg;
- jfloatArray arr = env->NewFloatArray(array.size());
- PoolVector<float>::Read r = array.read();
- env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr());
- v.val.l = arr;
- v.obj = arr;
-
- } break;
- default: {
-
- v.val.i = 0;
- } break;
- }
- return v;
-}
-
-String _get_class_name(JNIEnv *env, jclass cls, bool *array) {
-
- jclass cclass = env->FindClass("java/lang/Class");
- jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;");
- jstring clsName = (jstring)env->CallObjectMethod(cls, getName);
-
- if (array) {
- jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z");
- jboolean isarr = env->CallBooleanMethod(cls, isArray);
- (*array) = isarr ? true : false;
- }
- String name = jstring_to_string(clsName, env);
- env->DeleteLocalRef(clsName);
-
- return name;
-}
-
-Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
-
- if (obj == NULL) {
- return Variant();
- }
-
- jclass c = env->GetObjectClass(obj);
- bool array;
- String name = _get_class_name(env, c, &array);
-
- if (name == "java.lang.String") {
-
- return jstring_to_string((jstring)obj, env);
- };
-
- if (name == "[Ljava.lang.String;") {
-
- jobjectArray arr = (jobjectArray)obj;
- int stringCount = env->GetArrayLength(arr);
- PoolVector<String> sarr;
-
- for (int i = 0; i < stringCount; i++) {
- jstring string = (jstring)env->GetObjectArrayElement(arr, i);
- sarr.push_back(jstring_to_string(string, env));
- env->DeleteLocalRef(string);
- }
-
- return sarr;
- };
-
- if (name == "java.lang.Boolean") {
-
- jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z");
- bool ret = env->CallBooleanMethod(obj, boolValue);
- return ret;
- };
-
- if (name == "java.lang.Integer" || name == "java.lang.Long") {
-
- jclass nclass = env->FindClass("java/lang/Number");
- jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J");
- jlong ret = env->CallLongMethod(obj, longValue);
- return ret;
- };
-
- if (name == "[I") {
-
- jintArray arr = (jintArray)obj;
- int fCount = env->GetArrayLength(arr);
- PoolVector<int> sarr;
- sarr.resize(fCount);
-
- PoolVector<int>::Write w = sarr.write();
- env->GetIntArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- return sarr;
- };
-
- if (name == "[B") {
-
- jbyteArray arr = (jbyteArray)obj;
- int fCount = env->GetArrayLength(arr);
- PoolVector<uint8_t> sarr;
- sarr.resize(fCount);
-
- PoolVector<uint8_t>::Write w = sarr.write();
- env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w.ptr()));
- w.release();
- return sarr;
- };
-
- if (name == "java.lang.Float" || name == "java.lang.Double") {
-
- jclass nclass = env->FindClass("java/lang/Number");
- jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D");
- double ret = env->CallDoubleMethod(obj, doubleValue);
- return ret;
- };
-
- if (name == "[D") {
-
- jdoubleArray arr = (jdoubleArray)obj;
- int fCount = env->GetArrayLength(arr);
- PoolRealArray sarr;
- sarr.resize(fCount);
-
- PoolRealArray::Write w = sarr.write();
-
- for (int i = 0; i < fCount; i++) {
-
- double n;
- env->GetDoubleArrayRegion(arr, i, 1, &n);
- w.ptr()[i] = n;
- };
- return sarr;
- };
-
- if (name == "[F") {
-
- jfloatArray arr = (jfloatArray)obj;
- int fCount = env->GetArrayLength(arr);
- PoolRealArray sarr;
- sarr.resize(fCount);
-
- PoolRealArray::Write w = sarr.write();
-
- for (int i = 0; i < fCount; i++) {
-
- float n;
- env->GetFloatArrayRegion(arr, i, 1, &n);
- w.ptr()[i] = n;
- };
- return sarr;
- };
-
- if (name == "[Ljava.lang.Object;") {
-
- jobjectArray arr = (jobjectArray)obj;
- int objCount = env->GetArrayLength(arr);
- Array varr;
-
- for (int i = 0; i < objCount; i++) {
- jobject jobj = env->GetObjectArrayElement(arr, i);
- Variant v = _jobject_to_variant(env, jobj);
- varr.push_back(v);
- env->DeleteLocalRef(jobj);
- }
-
- return varr;
- };
-
- if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") {
-
- Dictionary ret;
- jclass oclass = c;
- jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;");
- jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys);
-
- PoolStringArray keys = _jobject_to_variant(env, arr);
- env->DeleteLocalRef(arr);
-
- jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;");
- arr = (jobjectArray)env->CallObjectMethod(obj, get_values);
-
- Array vals = _jobject_to_variant(env, arr);
- env->DeleteLocalRef(arr);
-
- for (int i = 0; i < keys.size(); i++) {
-
- ret[keys[i]] = vals[i];
- };
-
- return ret;
- };
-
- env->DeleteLocalRef(c);
-
- return Variant();
-}
-
-class JNISingleton : public Object {
-
- GDCLASS(JNISingleton, Object);
-
- struct MethodData {
-
- jmethodID method;
- Variant::Type ret_type;
- Vector<Variant::Type> argtypes;
- };
-
- jobject instance;
- Map<StringName, MethodData> method_map;
-
-public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
- ERR_FAIL_COND_V(!instance, Variant());
-
- r_error.error = Variant::CallError::CALL_OK;
-
- Map<StringName, MethodData>::Element *E = method_map.find(p_method);
- if (!E) {
-
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
- return Variant();
- }
-
- int ac = E->get().argtypes.size();
- if (ac < p_argcount) {
-
- r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = ac;
- return Variant();
- }
-
- if (ac > p_argcount) {
-
- r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = ac;
- return Variant();
- }
-
- for (int i = 0; i < p_argcount; i++) {
-
- if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
-
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = i;
- r_error.expected = E->get().argtypes[i];
- }
- }
-
- jvalue *v = NULL;
-
- if (p_argcount) {
-
- v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
- }
-
- JNIEnv *env = ThreadAndroid::get_env();
-
- int res = env->PushLocalFrame(16);
-
- ERR_FAIL_COND_V(res != 0, Variant());
-
- List<jobject> to_erase;
- for (int i = 0; i < p_argcount; i++) {
-
- jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
- v[i] = vr.val;
- if (vr.obj)
- to_erase.push_back(vr.obj);
- }
-
- Variant ret;
-
- switch (E->get().ret_type) {
-
- case Variant::NIL: {
-
- env->CallVoidMethodA(instance, E->get().method, v);
- } break;
- case Variant::BOOL: {
-
- ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE;
- } break;
- case Variant::INT: {
-
- ret = env->CallIntMethodA(instance, E->get().method, v);
- } break;
- case Variant::REAL: {
-
- ret = env->CallFloatMethodA(instance, E->get().method, v);
- } break;
- case Variant::STRING: {
-
- jobject o = env->CallObjectMethodA(instance, E->get().method, v);
- ret = jstring_to_string((jstring)o, env);
- env->DeleteLocalRef(o);
- } break;
- case Variant::POOL_STRING_ARRAY: {
-
- jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- ret = _jobject_to_variant(env, arr);
- env->DeleteLocalRef(arr);
- } break;
- case Variant::POOL_INT_ARRAY: {
-
- jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- PoolVector<int> sarr;
- sarr.resize(fCount);
-
- PoolVector<int>::Write w = sarr.write();
- env->GetIntArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::POOL_REAL_ARRAY: {
-
- jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- PoolVector<float> sarr;
- sarr.resize(fCount);
-
- PoolVector<float>::Write w = sarr.write();
- env->GetFloatArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
-
- case Variant::DICTIONARY: {
-
- jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
- ret = _jobject_to_variant(env, obj);
- env->DeleteLocalRef(obj);
-
- } break;
- default: {
-
- env->PopLocalFrame(NULL);
- ERR_FAIL_V(Variant());
- } break;
- }
-
- while (to_erase.size()) {
- env->DeleteLocalRef(to_erase.front()->get());
- to_erase.pop_front();
- }
-
- env->PopLocalFrame(NULL);
-
- return ret;
- }
-
- jobject get_instance() const {
-
- return instance;
- }
- void set_instance(jobject p_instance) {
-
- instance = p_instance;
- }
-
- void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
-
- MethodData md;
- md.method = p_method;
- md.argtypes = p_args;
- md.ret_type = p_ret_type;
- method_map[p_name] = md;
- }
-
- JNISingleton() {
- instance = NULL;
- }
-};
-
-struct TST {
+#include <unistd.h>
- int a;
- TST() {
+#include <android/native_window_jni.h>
- a = 5;
- }
-};
-
-TST tst;
+static JavaClassWrapper *java_class_wrapper = nullptr;
+static OS_Android *os_android = nullptr;
+static GodotJavaWrapper *godot_java = nullptr;
+static GodotIOJavaWrapper *godot_io_java = nullptr;
static bool initialized = false;
static int step = 0;
@@ -600,28 +68,23 @@ static Vector3 accelerometer;
static Vector3 gravity;
static Vector3 magnetometer;
static Vector3 gyroscope;
-static HashMap<String, JNISingleton *> jni_singletons;
-// virtual Error native_video_play(String p_path);
-// virtual bool native_video_is_playing();
-// virtual void native_video_pause();
-// virtual void native_video_stop();
+extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height) {
if (godot_io_java) {
godot_io_java->set_vk_height(p_height);
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) {
-
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion) {
initialized = true;
JavaVM *jvm;
env->GetJavaVM(&jvm);
// create our wrapper classes
- godot_java = new GodotJavaWrapper(env, activity); // our activity is our godot instance is our activity..
+ godot_java = new GodotJavaWrapper(env, activity, godot_instance);
godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env));
ThreadAndroid::make_default(jvm);
@@ -646,7 +109,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
godot_java->on_video_init(env);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) {
// lets cleanup
if (godot_io_java) {
delete godot_io_java;
@@ -659,61 +122,20 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env
}
}
-static void _initialize_java_modules() {
-
- if (!ProjectSettings::get_singleton()->has_setting("android/modules")) {
- return;
- }
-
- String modules = ProjectSettings::get_singleton()->get("android/modules");
- modules = modules.strip_edges();
- if (modules == String()) {
- return;
- }
- Vector<String> mods = modules.split(",", false);
-
- if (mods.size()) {
- jobject cls = godot_java->get_class_loader();
-
- // TODO create wrapper for class loader
-
- JNIEnv *env = ThreadAndroid::get_env();
- jclass classLoader = env->FindClass("java/lang/ClassLoader");
- jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
-
- for (int i = 0; i < mods.size(); i++) {
-
- String m = mods[i];
-
- print_line("Loading Android module: " + m);
- jstring strClassName = env->NewStringUTF(m.utf8().get_data());
- jclass singletonClass = (jclass)env->CallObjectMethod(cls, findClass, strClassName);
- ERR_CONTINUE_MSG(!singletonClass, "Couldn't find singleton for class: " + m + ".");
-
- jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;");
- ERR_CONTINUE_MSG(!initialize, "Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m + ".");
-
- jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity());
- env->NewGlobalRef(obj);
- }
- }
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
ThreadAndroid::setup_thread();
- const char **cmdline = NULL;
- jstring *j_cmdline = NULL;
+ const char **cmdline = nullptr;
+ jstring *j_cmdline = nullptr;
int cmdlen = 0;
if (p_cmdline) {
cmdlen = env->GetArrayLength(p_cmdline);
if (cmdlen) {
cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *));
- cmdline[cmdlen] = NULL;
+ cmdline[cmdlen] = nullptr;
j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring));
for (int i = 0; i < cmdlen; i++) {
-
jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i);
const char *rawString = env->GetStringUTFChars(string, 0);
@@ -739,23 +161,36 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
- _initialize_java_modules();
+ ClassDB::register_class<JNISingleton>();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height) {
+ if (os_android) {
+ os_android->set_display_size(Size2i(p_width, p_height));
- if (os_android)
- os_android->set_display_size(Size2(width, height));
-}
+ // No need to reset the surface during startup
+ if (step > 0) {
+ if (p_surface) {
+ ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
+ os_android->set_native_window(native_window);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits) {
+ DisplayServerAndroid::get_singleton()->reset_window();
+ }
+ }
+ }
+}
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits) {
if (os_android) {
if (step == 0) {
// During startup
os_android->set_context_is_16_bits(!p_32_bits);
+ if (p_surface) {
+ ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
+ os_android->set_native_window(native_window);
+ }
} else {
- // GL context recreated because it was lost; restart app to let it reload everything
+ // Rendering context recreated because it was lost; restart app to let it reload everything
os_android->main_loop_end();
godot_java->restart(env);
step = -1; // Ensure no further steps are attempted
@@ -763,19 +198,18 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
os_android->main_loop_request_go_back();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
if (step == -1)
return;
if (step == 0) {
-
// Since Godot is initialized on the UI thread, _main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
Main::setup2(Thread::get_caller_id());
@@ -789,37 +223,35 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job
}
os_android->main_loop_begin();
+ godot_java->on_godot_main_loop_started(env);
++step;
}
- os_android->process_accelerometer(accelerometer);
- os_android->process_gravity(gravity);
- os_android->process_magnetometer(magnetometer);
- os_android->process_gyroscope(gyroscope);
+ DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer);
+ DisplayServerAndroid::get_singleton()->process_gravity(gravity);
+ DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer);
+ DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope);
if (os_android->main_loop_iterate()) {
-
godot_java->force_quit(env);
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions) {
-
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions) {
if (step == 0)
return;
- Vector<OS_Android::TouchPos> points;
+ Vector<DisplayServerAndroid::TouchPos> points;
for (int i = 0; i < count; i++) {
-
jint p[3];
env->GetIntArrayRegion(positions, i * 3, 3, p);
- OS_Android::TouchPos tp;
+ DisplayServerAndroid::TouchPos tp;
tp.pos = Point2(p[1], p[2]);
tp.id = p[0];
points.push_back(tp);
}
- os_android->process_touch(ev, pointer, points);
+ DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points);
/*
if (os_android)
@@ -827,527 +259,135 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo
*/
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y) {
if (step == 0)
return;
- os_android->process_hover(p_type, Point2(p_x, p_y));
+ DisplayServerAndroid::get_singleton()->process_hover(p_type, Point2(p_x, p_y));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
if (step == 0)
return;
- os_android->process_double_tap(Point2(p_x, p_y));
+ DisplayServerAndroid::get_singleton()->process_double_tap(Point2(p_x, p_y));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
if (step == 0)
return;
- os_android->process_scroll(Point2(p_x, p_y));
+ DisplayServerAndroid::get_singleton()->process_scroll(Point2(p_x, p_y));
}
-/*
- * Android Key codes.
- */
-enum {
- AKEYCODE_UNKNOWN = 0,
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_HOME = 3,
- AKEYCODE_BACK = 4,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_0 = 7,
- AKEYCODE_1 = 8,
- AKEYCODE_2 = 9,
- AKEYCODE_3 = 10,
- AKEYCODE_4 = 11,
- AKEYCODE_5 = 12,
- AKEYCODE_6 = 13,
- AKEYCODE_7 = 14,
- AKEYCODE_8 = 15,
- AKEYCODE_9 = 16,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_DPAD_UP = 19,
- AKEYCODE_DPAD_DOWN = 20,
- AKEYCODE_DPAD_LEFT = 21,
- AKEYCODE_DPAD_RIGHT = 22,
- AKEYCODE_DPAD_CENTER = 23,
- AKEYCODE_VOLUME_UP = 24,
- AKEYCODE_VOLUME_DOWN = 25,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_A = 29,
- AKEYCODE_B = 30,
- AKEYCODE_C = 31,
- AKEYCODE_D = 32,
- AKEYCODE_E = 33,
- AKEYCODE_F = 34,
- AKEYCODE_G = 35,
- AKEYCODE_H = 36,
- AKEYCODE_I = 37,
- AKEYCODE_J = 38,
- AKEYCODE_K = 39,
- AKEYCODE_L = 40,
- AKEYCODE_M = 41,
- AKEYCODE_N = 42,
- AKEYCODE_O = 43,
- AKEYCODE_P = 44,
- AKEYCODE_Q = 45,
- AKEYCODE_R = 46,
- AKEYCODE_S = 47,
- AKEYCODE_T = 48,
- AKEYCODE_U = 49,
- AKEYCODE_V = 50,
- AKEYCODE_W = 51,
- AKEYCODE_X = 52,
- AKEYCODE_Y = 53,
- AKEYCODE_Z = 54,
- AKEYCODE_COMMA = 55,
- AKEYCODE_PERIOD = 56,
- AKEYCODE_ALT_LEFT = 57,
- AKEYCODE_ALT_RIGHT = 58,
- AKEYCODE_SHIFT_LEFT = 59,
- AKEYCODE_SHIFT_RIGHT = 60,
- AKEYCODE_TAB = 61,
- AKEYCODE_SPACE = 62,
- AKEYCODE_SYM = 63,
- AKEYCODE_EXPLORER = 64,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_ENTER = 66,
- AKEYCODE_DEL = 67,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_MINUS = 69,
- AKEYCODE_EQUALS = 70,
- AKEYCODE_LEFT_BRACKET = 71,
- AKEYCODE_RIGHT_BRACKET = 72,
- AKEYCODE_BACKSLASH = 73,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_SLASH = 76,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_PLUS = 81,
- AKEYCODE_MENU = 82,
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_MEDIA_PLAY_PAUSE = 85,
- AKEYCODE_MEDIA_STOP = 86,
- AKEYCODE_MEDIA_NEXT = 87,
- AKEYCODE_MEDIA_PREVIOUS = 88,
- AKEYCODE_MEDIA_REWIND = 89,
- AKEYCODE_MEDIA_FAST_FORWARD = 90,
- AKEYCODE_MUTE = 91,
- AKEYCODE_PAGE_UP = 92,
- AKEYCODE_PAGE_DOWN = 93,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
- AKEYCODE_BUTTON_A = 96,
- AKEYCODE_BUTTON_B = 97,
- AKEYCODE_BUTTON_C = 98,
- AKEYCODE_BUTTON_X = 99,
- AKEYCODE_BUTTON_Y = 100,
- AKEYCODE_BUTTON_Z = 101,
- AKEYCODE_BUTTON_L1 = 102,
- AKEYCODE_BUTTON_R1 = 103,
- AKEYCODE_BUTTON_L2 = 104,
- AKEYCODE_BUTTON_R2 = 105,
- AKEYCODE_BUTTON_THUMBL = 106,
- AKEYCODE_BUTTON_THUMBR = 107,
- AKEYCODE_BUTTON_START = 108,
- AKEYCODE_BUTTON_SELECT = 109,
- AKEYCODE_BUTTON_MODE = 110,
-
- // NOTE: If you add a new keycode here you must also add it to several other files.
- // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
-};
-
-struct _WinTranslatePair {
-
- unsigned int keysym;
- unsigned int keycode;
-};
-
-static _WinTranslatePair _ak_to_keycode[] = {
- { KEY_TAB, AKEYCODE_TAB },
- { KEY_ENTER, AKEYCODE_ENTER },
- { KEY_SHIFT, AKEYCODE_SHIFT_LEFT },
- { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT },
- { KEY_ALT, AKEYCODE_ALT_LEFT },
- { KEY_ALT, AKEYCODE_ALT_RIGHT },
- { KEY_MENU, AKEYCODE_MENU },
- { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
- { KEY_ESCAPE, AKEYCODE_BACK },
- { KEY_SPACE, AKEYCODE_SPACE },
- { KEY_PAGEUP, AKEYCODE_PAGE_UP },
- { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN },
- { KEY_HOME, AKEYCODE_HOME }, //(0x24)
- { KEY_LEFT, AKEYCODE_DPAD_LEFT },
- { KEY_UP, AKEYCODE_DPAD_UP },
- { KEY_RIGHT, AKEYCODE_DPAD_RIGHT },
- { KEY_DOWN, AKEYCODE_DPAD_DOWN },
- { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER },
- { KEY_BACKSPACE, AKEYCODE_DEL },
- { KEY_0, AKEYCODE_0 }, ////0 key
- { KEY_1, AKEYCODE_1 }, ////1 key
- { KEY_2, AKEYCODE_2 }, ////2 key
- { KEY_3, AKEYCODE_3 }, ////3 key
- { KEY_4, AKEYCODE_4 }, ////4 key
- { KEY_5, AKEYCODE_5 }, ////5 key
- { KEY_6, AKEYCODE_6 }, ////6 key
- { KEY_7, AKEYCODE_7 }, ////7 key
- { KEY_8, AKEYCODE_8 }, ////8 key
- { KEY_9, AKEYCODE_9 }, ////9 key
- { KEY_A, AKEYCODE_A }, ////A key
- { KEY_B, AKEYCODE_B }, ////B key
- { KEY_C, AKEYCODE_C }, ////C key
- { KEY_D, AKEYCODE_D }, ////D key
- { KEY_E, AKEYCODE_E }, ////E key
- { KEY_F, AKEYCODE_F }, ////F key
- { KEY_G, AKEYCODE_G }, ////G key
- { KEY_H, AKEYCODE_H }, ////H key
- { KEY_I, AKEYCODE_I }, ////I key
- { KEY_J, AKEYCODE_J }, ////J key
- { KEY_K, AKEYCODE_K }, ////K key
- { KEY_L, AKEYCODE_L }, ////L key
- { KEY_M, AKEYCODE_M }, ////M key
- { KEY_N, AKEYCODE_N }, ////N key
- { KEY_O, AKEYCODE_O }, ////O key
- { KEY_P, AKEYCODE_P }, ////P key
- { KEY_Q, AKEYCODE_Q }, ////Q key
- { KEY_R, AKEYCODE_R }, ////R key
- { KEY_S, AKEYCODE_S }, ////S key
- { KEY_T, AKEYCODE_T }, ////T key
- { KEY_U, AKEYCODE_U }, ////U key
- { KEY_V, AKEYCODE_V }, ////V key
- { KEY_W, AKEYCODE_W }, ////W key
- { KEY_X, AKEYCODE_X }, ////X key
- { KEY_Y, AKEYCODE_Y }, ////Y key
- { KEY_Z, AKEYCODE_Z }, ////Z key
- { KEY_HOMEPAGE, AKEYCODE_EXPLORER },
- { KEY_LAUNCH0, AKEYCODE_BUTTON_A },
- { KEY_LAUNCH1, AKEYCODE_BUTTON_B },
- { KEY_LAUNCH2, AKEYCODE_BUTTON_C },
- { KEY_LAUNCH3, AKEYCODE_BUTTON_X },
- { KEY_LAUNCH4, AKEYCODE_BUTTON_Y },
- { KEY_LAUNCH5, AKEYCODE_BUTTON_Z },
- { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 },
- { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 },
- { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 },
- { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 },
- { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL },
- { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR },
- { KEY_LAUNCHC, AKEYCODE_BUTTON_START },
- { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT },
- { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE },
- { KEY_VOLUMEMUTE, AKEYCODE_MUTE },
- { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
- { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP },
- { KEY_BACK, AKEYCODE_MEDIA_REWIND },
- { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
- { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT },
- { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
- { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP },
- { KEY_PLUS, AKEYCODE_PLUS },
- { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key
- { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key
- { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key
- { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key
- { KEY_BACKSLASH, AKEYCODE_BACKSLASH },
- { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
- { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
- { KEY_UNKNOWN, 0 }
-};
-/*
-TODO: map these android key:
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_SYM = 63,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
-*/
-
-static unsigned int android_get_keysym(unsigned int p_code) {
- for (int i = 0; _ak_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
-
- if (_ak_to_keycode[i].keycode == p_code) {
-
- return _ak_to_keycode[i].keysym;
- }
- }
-
- return KEY_UNKNOWN;
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {
if (step == 0)
return;
- OS_Android::JoypadEvent jevent;
+ DisplayServerAndroid::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = OS_Android::JOY_EVENT_BUTTON;
+ jevent.type = DisplayServerAndroid::JOY_EVENT_BUTTON;
jevent.index = p_button;
jevent.pressed = p_pressed;
- os_android->process_joy_event(jevent);
+ DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) {
if (step == 0)
return;
- OS_Android::JoypadEvent jevent;
+ DisplayServerAndroid::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = OS_Android::JOY_EVENT_AXIS;
+ jevent.type = DisplayServerAndroid::JOY_EVENT_AXIS;
jevent.index = p_axis;
jevent.value = p_value;
- os_android->process_joy_event(jevent);
+ DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) {
if (step == 0)
return;
- OS_Android::JoypadEvent jevent;
+ DisplayServerAndroid::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = OS_Android::JOY_EVENT_HAT;
+ jevent.type = DisplayServerAndroid::JOY_EVENT_HAT;
int hat = 0;
if (p_hat_x != 0) {
if (p_hat_x < 0)
- hat |= InputDefault::HAT_MASK_LEFT;
+ hat |= Input::HAT_MASK_LEFT;
else
- hat |= InputDefault::HAT_MASK_RIGHT;
+ hat |= Input::HAT_MASK_RIGHT;
}
if (p_hat_y != 0) {
if (p_hat_y < 0)
- hat |= InputDefault::HAT_MASK_UP;
+ hat |= Input::HAT_MASK_UP;
else
- hat |= InputDefault::HAT_MASK_DOWN;
+ hat |= Input::HAT_MASK_DOWN;
}
jevent.hat = hat;
- os_android->process_joy_event(jevent);
+ DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name) {
if (os_android) {
String name = jstring_to_string(p_name, env);
- os_android->joy_connection_changed(p_device, p_connected, name);
+ Input::get_singleton()->joy_connection_changed(p_device, p_connected, name);
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
if (step == 0)
return;
- Ref<InputEventKey> ievent;
- ievent.instance();
- int val = p_unicode_char;
- int scancode = android_get_keysym(p_scancode);
- ievent->set_scancode(scancode);
- ievent->set_unicode(val);
- ievent->set_pressed(p_pressed);
-
- if (val == '\n') {
- ievent->set_scancode(KEY_ENTER);
- } else if (val == 61448) {
- ievent->set_scancode(KEY_BACKSPACE);
- ievent->set_unicode(KEY_BACKSPACE);
- } else if (val == 61453) {
- ievent->set_scancode(KEY_ENTER);
- ievent->set_unicode(KEY_ENTER);
- } else if (p_scancode == 4) {
-
- os_android->main_loop_request_go_back();
- }
-
- os_android->process_event(ievent);
+ DisplayServerAndroid::get_singleton()->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
accelerometer = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
gravity = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
magnetometer = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
gyroscope = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj) {
-
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
os_android->main_loop_focusin();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj) {
-
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
os_android->main_loop_focusout();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj) {
-
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) {
ThreadAndroid::setup_thread();
AudioDriverAndroid::thread_func(env);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) {
-
- String singname = jstring_to_string(name, env);
- JNISingleton *s = memnew(JNISingleton);
- s->set_instance(env->NewGlobalRef(p_object));
- jni_singletons[singname] = s;
-
- Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
- ProjectSettings::get_singleton()->set(singname, s);
-}
-
-static Variant::Type get_jni_type(const String &p_type) {
-
- static struct {
- const char *name;
- Variant::Type type;
- } _type_to_vtype[] = {
- { "void", Variant::NIL },
- { "boolean", Variant::BOOL },
- { "int", Variant::INT },
- { "float", Variant::REAL },
- { "double", Variant::REAL },
- { "java.lang.String", Variant::STRING },
- { "[I", Variant::POOL_INT_ARRAY },
- { "[B", Variant::POOL_BYTE_ARRAY },
- { "[F", Variant::POOL_REAL_ARRAY },
- { "[Ljava.lang.String;", Variant::POOL_STRING_ARRAY },
- { "org.godotengine.godot.Dictionary", Variant::DICTIONARY },
- { NULL, Variant::NIL }
- };
-
- int idx = 0;
-
- while (_type_to_vtype[idx].name) {
-
- if (p_type == _type_to_vtype[idx].name)
- return _type_to_vtype[idx].type;
-
- idx++;
- }
-
- return Variant::NIL;
-}
-
-static const char *get_jni_sig(const String &p_type) {
-
- static struct {
- const char *name;
- const char *sig;
- } _type_to_vtype[] = {
- { "void", "V" },
- { "boolean", "Z" },
- { "int", "I" },
- { "float", "F" },
- { "double", "D" },
- { "java.lang.String", "Ljava/lang/String;" },
- { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" },
- { "[I", "[I" },
- { "[B", "[B" },
- { "[F", "[F" },
- { "[Ljava.lang.String;", "[Ljava/lang/String;" },
- { NULL, "V" }
- };
-
- int idx = 0;
-
- while (_type_to_vtype[idx].name) {
-
- if (p_type == _type_to_vtype[idx].name)
- return _type_to_vtype[idx].sig;
-
- idx++;
- }
-
- return "Ljava/lang/Object;";
-}
-
-JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path) {
-
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data());
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
-
- String singname = jstring_to_string(sname, env);
-
- ERR_FAIL_COND(!jni_singletons.has(singname));
-
- JNISingleton *s = jni_singletons.get(singname);
-
- String mname = jstring_to_string(name, env);
- String retval = jstring_to_string(ret, env);
- Vector<Variant::Type> types;
- String cs = "(";
-
- int stringCount = env->GetArrayLength(args);
-
- for (int i = 0; i < stringCount; i++) {
-
- jstring string = (jstring)env->GetObjectArrayElement(args, i);
- const String rawString = jstring_to_string(string, env);
- types.push_back(get_jni_type(rawString));
- cs += get_jni_sig(rawString);
- }
-
- cs += ")";
- cs += get_jni_sig(retval);
- jclass cls = env->GetObjectClass(s->get_instance());
- jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
- if (!mid) {
-
- print_line("Failed getting method ID " + mname);
- }
-
- s->add_method(mname, mid, types, get_jni_type(retval));
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) {
-
- Object *obj = ObjectDB::get_instance(ID);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
+ Object *obj = ObjectDB::get_instance(ObjectID(ID));
ERR_FAIL_COND(!obj);
int res = env->PushLocalFrame(16);
@@ -1359,7 +399,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en
Variant *vlist = (Variant *)alloca(sizeof(Variant) * count);
Variant **vptr = (Variant **)alloca(sizeof(Variant *) * count);
for (int i = 0; i < count; i++) {
-
jobject obj = env->GetObjectArrayElement(params, i);
Variant v;
if (obj)
@@ -1370,16 +409,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en
env->DeleteLocalRef(obj);
};
- Variant::CallError err;
+ Callable::CallError err;
obj->call(str_method, (const Variant **)vptr, count, err);
// something
- env->PopLocalFrame(NULL);
+ env->PopLocalFrame(nullptr);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) {
-
- Object *obj = ObjectDB::get_instance(ID);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
+ Object *obj = ObjectDB::get_instance(ObjectID(ID));
ERR_FAIL_COND(!obj);
int res = env->PushLocalFrame(16);
@@ -1391,19 +429,19 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
Variant args[VARIANT_ARG_MAX];
for (int i = 0; i < MIN(count, VARIANT_ARG_MAX); i++) {
-
jobject obj = env->GetObjectArrayElement(params, i);
if (obj)
args[i] = _jobject_to_variant(env, obj);
env->DeleteLocalRef(obj);
};
+ static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5");
obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4]);
// something
- env->PopLocalFrame(NULL);
+ env->PopLocalFrame(nullptr);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
String permission = jstring_to_string(p_permission, env);
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
AudioDriver::get_singleton()->capture_start();
@@ -1419,7 +457,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI
return;
if (os_android->get_main_loop()) {
- os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_RESUMED);
+ os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
}
}
@@ -1428,6 +466,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE
return;
if (os_android->get_main_loop()) {
- os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED);
+ os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
}
+}
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 71d4547f65..07584518e5 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -37,36 +37,34 @@
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
-JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz);
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 893b786c0b..cff591d903 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -37,35 +37,47 @@
// TODO we could probably create a base class for this...
-GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
+GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance) {
godot_instance = p_env->NewGlobalRef(p_godot_instance);
+ activity = p_env->NewGlobalRef(p_activity);
// get info about our Godot class so we can get pointers and stuff...
- cls = p_env->FindClass("org/godotengine/godot/Godot");
- if (cls) {
- cls = (jclass)p_env->NewGlobalRef(cls);
+ godot_class = p_env->FindClass("org/godotengine/godot/Godot");
+ if (godot_class) {
+ godot_class = (jclass)p_env->NewGlobalRef(godot_class);
+ } else {
+ // this is a pretty serious fail.. bail... pointers will stay 0
+ return;
+ }
+ activity_class = p_env->FindClass("android/app/Activity");
+ if (activity_class) {
+ activity_class = (jclass)p_env->NewGlobalRef(activity_class);
} else {
// this is a pretty serious fail.. bail... pointers will stay 0
return;
}
- // get some method pointers...
- _on_video_init = p_env->GetMethodID(cls, "onVideoInit", "()V");
- _restart = p_env->GetMethodID(cls, "restart", "()V");
- _finish = p_env->GetMethodID(cls, "forceQuit", "()V");
- _set_keep_screen_on = p_env->GetMethodID(cls, "setKeepScreenOn", "(Z)V");
- _alert = p_env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
- _get_GLES_version_code = p_env->GetMethodID(cls, "getGLESVersionCode", "()I");
- _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;");
- _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V");
- _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z");
- _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z");
- _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;");
- _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V");
- _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;");
- _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z");
- _vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V");
- _get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;");
+ // get some Godot method pointers...
+ _on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()V");
+ _restart = p_env->GetMethodID(godot_class, "restart", "()V");
+ _finish = p_env->GetMethodID(godot_class, "forceQuit", "()V");
+ _set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V");
+ _alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
+ _get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I");
+ _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
+ _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
+ _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
+ _request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
+ _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
+ _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V");
+ _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;");
+ _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
+ _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V");
+ _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
+ _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
+
+ // get some Activity method pointers...
+ _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -73,50 +85,50 @@ GodotJavaWrapper::~GodotJavaWrapper() {
}
jobject GodotJavaWrapper::get_activity() {
- // our godot instance is our activity
- return godot_instance;
+ return activity;
}
jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) {
- if (cls) {
- if (p_env == NULL)
+ if (godot_class) {
+ if (p_env == nullptr)
p_env = ThreadAndroid::get_env();
- jfieldID fid = p_env->GetStaticFieldID(cls, p_name, p_class);
- return p_env->GetStaticObjectField(cls, fid);
+ jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class);
+ return p_env->GetStaticObjectField(godot_class, fid);
} else {
- return NULL;
+ return nullptr;
}
}
jobject GodotJavaWrapper::get_class_loader() {
- if (cls) {
+ if (_get_class_loader) {
JNIEnv *env = ThreadAndroid::get_env();
- jmethodID getClassLoader = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
- return env->CallObjectMethod(godot_instance, getClassLoader);
+ return env->CallObjectMethod(godot_instance, _get_class_loader);
} else {
- return NULL;
+ return nullptr;
}
}
-void GodotJavaWrapper::gfx_init(bool gl2) {
- // beats me what this once did, there was no code,
- // but we're getting false if our GLES3 driver is initialised
- // and true for our GLES2 driver
- // Maybe we're supposed to communicate this back or store it?
-}
-
void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
if (_on_video_init)
- if (p_env == NULL)
+ if (p_env == nullptr)
p_env = ThreadAndroid::get_env();
p_env->CallVoidMethod(godot_instance, _on_video_init);
}
+void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
+ if (_on_godot_main_loop_started) {
+ if (p_env == nullptr) {
+ p_env = ThreadAndroid::get_env();
+ }
+ }
+ p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
+}
+
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart)
- if (p_env == NULL)
+ if (p_env == nullptr)
p_env = ThreadAndroid::get_env();
p_env->CallVoidMethod(godot_instance, _restart);
@@ -124,7 +136,7 @@ void GodotJavaWrapper::restart(JNIEnv *p_env) {
void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
if (_finish)
- if (p_env == NULL)
+ if (p_env == nullptr)
p_env = ThreadAndroid::get_env();
p_env->CallVoidMethod(godot_instance, _finish);
@@ -241,7 +253,7 @@ jobject GodotJavaWrapper::get_surface() {
JNIEnv *env = ThreadAndroid::get_env();
return env->CallObjectMethod(godot_instance, _get_surface);
} else {
- return NULL;
+ return nullptr;
}
}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 655f5170bf..e0c3809a64 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -43,7 +43,9 @@
class GodotJavaWrapper {
private:
jobject godot_instance;
- jclass cls;
+ jobject activity;
+ jclass godot_class;
+ jclass activity_class;
jmethodID _on_video_init = 0;
jmethodID _restart = 0;
@@ -61,20 +63,22 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
+ jmethodID _on_godot_main_loop_started = 0;
+ jmethodID _get_class_loader = 0;
public:
- GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
+ GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
~GodotJavaWrapper();
jobject get_activity();
- jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL);
+ jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr);
jobject get_class_loader();
- void gfx_init(bool gl2);
- void on_video_init(JNIEnv *p_env = NULL);
- void restart(JNIEnv *p_env = NULL);
- void force_quit(JNIEnv *p_env = NULL);
+ void on_video_init(JNIEnv *p_env = nullptr);
+ void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
+ void restart(JNIEnv *p_env = nullptr);
+ void force_quit(JNIEnv *p_env = nullptr);
void set_keep_screen_on(bool p_enabled);
void alert(const String &p_message, const String &p_title);
int get_gles_version_code();
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
new file mode 100644
index 0000000000..8e1ae53b78
--- /dev/null
+++ b/platform/android/jni_utils.cpp
@@ -0,0 +1,400 @@
+/*************************************************************************/
+/* jni_utils.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "jni_utils.h"
+
+jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) {
+ jvalret v;
+
+ switch (p_type) {
+ case Variant::BOOL: {
+ if (force_jobject) {
+ jclass bclass = env->FindClass("java/lang/Boolean");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V");
+ jvalue val;
+ val.z = (bool)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+ } else {
+ v.val.z = *p_arg;
+ };
+ } break;
+ case Variant::INT: {
+ if (force_jobject) {
+ jclass bclass = env->FindClass("java/lang/Integer");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V");
+ jvalue val;
+ val.i = (int)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+
+ } else {
+ v.val.i = *p_arg;
+ };
+ } break;
+ case Variant::FLOAT: {
+ if (force_jobject) {
+ jclass bclass = env->FindClass("java/lang/Double");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V");
+ jvalue val;
+ val.d = (double)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+
+ } else {
+ v.val.f = *p_arg;
+ };
+ } break;
+ case Variant::STRING: {
+ String s = *p_arg;
+ jstring jStr = env->NewStringUTF(s.utf8().get_data());
+ v.val.l = jStr;
+ v.obj = jStr;
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+ Vector<String> sarray = *p_arg;
+ jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
+
+ for (int j = 0; j < sarray.size(); j++) {
+ jstring str = env->NewStringUTF(sarray[j].utf8().get_data());
+ env->SetObjectArrayElement(arr, j, str);
+ env->DeleteLocalRef(str);
+ }
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+
+ case Variant::DICTIONARY: {
+ Dictionary dict = *p_arg;
+ jclass dclass = env->FindClass("org/godotengine/godot/Dictionary");
+ jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V");
+ jobject jdict = env->NewObject(dclass, ctor);
+
+ Array keys = dict.keys();
+
+ jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
+ for (int j = 0; j < keys.size(); j++) {
+ jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data());
+ env->SetObjectArrayElement(jkeys, j, str);
+ env->DeleteLocalRef(str);
+ };
+
+ jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V");
+ jvalue val;
+ val.l = jkeys;
+ env->CallVoidMethodA(jdict, set_keys, &val);
+ env->DeleteLocalRef(jkeys);
+
+ jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), nullptr);
+
+ for (int j = 0; j < keys.size(); j++) {
+ Variant var = dict[keys[j]];
+ jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true);
+ env->SetObjectArrayElement(jvalues, j, v.val.l);
+ if (v.obj) {
+ env->DeleteLocalRef(v.obj);
+ }
+ };
+
+ jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V");
+ val.l = jvalues;
+ env->CallVoidMethodA(jdict, set_values, &val);
+ env->DeleteLocalRef(jvalues);
+ env->DeleteLocalRef(dclass);
+
+ v.val.l = jdict;
+ v.obj = jdict;
+ } break;
+
+ case Variant::PACKED_INT32_ARRAY: {
+ Vector<int> array = *p_arg;
+ jintArray arr = env->NewIntArray(array.size());
+ const int *r = array.ptr();
+ env->SetIntArrayRegion(arr, 0, array.size(), r);
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ Vector<uint8_t> array = *p_arg;
+ jbyteArray arr = env->NewByteArray(array.size());
+ const uint8_t *r = array.ptr();
+ env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r));
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ Vector<float> array = *p_arg;
+ jfloatArray arr = env->NewFloatArray(array.size());
+ const float *r = array.ptr();
+ env->SetFloatArrayRegion(arr, 0, array.size(), r);
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+#ifndef _MSC_VER
+#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
+#endif
+
+ default: {
+ v.val.i = 0;
+ } break;
+ }
+ return v;
+}
+
+String _get_class_name(JNIEnv *env, jclass cls, bool *array) {
+ jclass cclass = env->FindClass("java/lang/Class");
+ jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;");
+ jstring clsName = (jstring)env->CallObjectMethod(cls, getName);
+
+ if (array) {
+ jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z");
+ jboolean isarr = env->CallBooleanMethod(cls, isArray);
+ (*array) = isarr ? true : false;
+ }
+ String name = jstring_to_string(clsName, env);
+ env->DeleteLocalRef(clsName);
+
+ return name;
+}
+
+Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
+ if (obj == nullptr) {
+ return Variant();
+ }
+
+ jclass c = env->GetObjectClass(obj);
+ bool array;
+ String name = _get_class_name(env, c, &array);
+
+ if (name == "java.lang.String") {
+ return jstring_to_string((jstring)obj, env);
+ };
+
+ if (name == "[Ljava.lang.String;") {
+ jobjectArray arr = (jobjectArray)obj;
+ int stringCount = env->GetArrayLength(arr);
+ Vector<String> sarr;
+
+ for (int i = 0; i < stringCount; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(arr, i);
+ sarr.push_back(jstring_to_string(string, env));
+ env->DeleteLocalRef(string);
+ }
+
+ return sarr;
+ };
+
+ if (name == "java.lang.Boolean") {
+ jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z");
+ bool ret = env->CallBooleanMethod(obj, boolValue);
+ return ret;
+ };
+
+ if (name == "java.lang.Integer" || name == "java.lang.Long") {
+ jclass nclass = env->FindClass("java/lang/Number");
+ jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J");
+ jlong ret = env->CallLongMethod(obj, longValue);
+ return ret;
+ };
+
+ if (name == "[I") {
+ jintArray arr = (jintArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ Vector<int> sarr;
+ sarr.resize(fCount);
+
+ int *w = sarr.ptrw();
+ env->GetIntArrayRegion(arr, 0, fCount, w);
+ return sarr;
+ };
+
+ if (name == "[B") {
+ jbyteArray arr = (jbyteArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ Vector<uint8_t> sarr;
+ sarr.resize(fCount);
+
+ uint8_t *w = sarr.ptrw();
+ env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w));
+ return sarr;
+ };
+
+ if (name == "java.lang.Float" || name == "java.lang.Double") {
+ jclass nclass = env->FindClass("java/lang/Number");
+ jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D");
+ double ret = env->CallDoubleMethod(obj, doubleValue);
+ return ret;
+ };
+
+ if (name == "[D") {
+ jdoubleArray arr = (jdoubleArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ PackedFloat32Array sarr;
+ sarr.resize(fCount);
+
+ real_t *w = sarr.ptrw();
+
+ for (int i = 0; i < fCount; i++) {
+ double n;
+ env->GetDoubleArrayRegion(arr, i, 1, &n);
+ w[i] = n;
+ };
+ return sarr;
+ };
+
+ if (name == "[F") {
+ jfloatArray arr = (jfloatArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ PackedFloat32Array sarr;
+ sarr.resize(fCount);
+
+ real_t *w = sarr.ptrw();
+
+ for (int i = 0; i < fCount; i++) {
+ float n;
+ env->GetFloatArrayRegion(arr, i, 1, &n);
+ w[i] = n;
+ };
+ return sarr;
+ };
+
+ if (name == "[Ljava.lang.Object;") {
+ jobjectArray arr = (jobjectArray)obj;
+ int objCount = env->GetArrayLength(arr);
+ Array varr;
+
+ for (int i = 0; i < objCount; i++) {
+ jobject jobj = env->GetObjectArrayElement(arr, i);
+ Variant v = _jobject_to_variant(env, jobj);
+ varr.push_back(v);
+ env->DeleteLocalRef(jobj);
+ }
+
+ return varr;
+ };
+
+ if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") {
+ Dictionary ret;
+ jclass oclass = c;
+ jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;");
+ jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys);
+
+ PackedStringArray keys = _jobject_to_variant(env, arr);
+ env->DeleteLocalRef(arr);
+
+ jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;");
+ arr = (jobjectArray)env->CallObjectMethod(obj, get_values);
+
+ Array vals = _jobject_to_variant(env, arr);
+ env->DeleteLocalRef(arr);
+
+ for (int i = 0; i < keys.size(); i++) {
+ ret[keys[i]] = vals[i];
+ };
+
+ return ret;
+ };
+
+ env->DeleteLocalRef(c);
+
+ return Variant();
+}
+
+Variant::Type get_jni_type(const String &p_type) {
+ static struct {
+ const char *name;
+ Variant::Type type;
+ } _type_to_vtype[] = {
+ { "void", Variant::NIL },
+ { "boolean", Variant::BOOL },
+ { "int", Variant::INT },
+ { "float", Variant::FLOAT },
+ { "double", Variant::FLOAT },
+ { "java.lang.String", Variant::STRING },
+ { "[I", Variant::PACKED_INT32_ARRAY },
+ { "[B", Variant::PACKED_BYTE_ARRAY },
+ { "[F", Variant::PACKED_FLOAT32_ARRAY },
+ { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY },
+ { "org.godotengine.godot.Dictionary", Variant::DICTIONARY },
+ { nullptr, Variant::NIL }
+ };
+
+ int idx = 0;
+
+ while (_type_to_vtype[idx].name) {
+ if (p_type == _type_to_vtype[idx].name)
+ return _type_to_vtype[idx].type;
+
+ idx++;
+ }
+
+ return Variant::NIL;
+}
+
+const char *get_jni_sig(const String &p_type) {
+ static struct {
+ const char *name;
+ const char *sig;
+ } _type_to_vtype[] = {
+ { "void", "V" },
+ { "boolean", "Z" },
+ { "int", "I" },
+ { "float", "F" },
+ { "double", "D" },
+ { "java.lang.String", "Ljava/lang/String;" },
+ { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" },
+ { "[I", "[I" },
+ { "[B", "[B" },
+ { "[F", "[F" },
+ { "[Ljava.lang.String;", "[Ljava/lang/String;" },
+ { nullptr, "V" }
+ };
+
+ int idx = 0;
+
+ while (_type_to_vtype[idx].name) {
+ if (p_type == _type_to_vtype[idx].name)
+ return _type_to_vtype[idx].sig;
+
+ idx++;
+ }
+
+ return "Ljava/lang/Object;";
+}
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
new file mode 100644
index 0000000000..5320715853
--- /dev/null
+++ b/platform/android/jni_utils.h
@@ -0,0 +1,55 @@
+/*************************************************************************/
+/* jni_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef JNI_UTILS_H
+#define JNI_UTILS_H
+
+#include "string_android.h"
+#include <core/engine.h>
+#include <core/variant.h>
+#include <jni.h>
+
+struct jvalret {
+ jobject obj;
+ jvalue val;
+ jvalret() { obj = nullptr; }
+};
+
+jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false);
+
+String _get_class_name(JNIEnv *env, jclass cls, bool *array);
+
+Variant _jobject_to_variant(JNIEnv *env, jobject obj);
+
+Variant::Type get_jni_type(const String &p_type);
+
+const char *get_jni_sig(const String &p_type);
+
+#endif // JNI_UTILS_H
diff --git a/platform/android/logo.png b/platform/android/logo.png
index df445f6a9c..f44d360a25 100644
--- a/platform/android/logo.png
+++ b/platform/android/logo.png
Binary files differ
diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp
index 320bdd3817..0341ef3ec6 100644
--- a/platform/android/net_socket_android.cpp
+++ b/platform/android/net_socket_android.cpp
@@ -38,7 +38,6 @@ jmethodID NetSocketAndroid::_multicast_lock_acquire = 0;
jmethodID NetSocketAndroid::_multicast_lock_release = 0;
void NetSocketAndroid::setup(jobject p_net_utils) {
-
JNIEnv *env = ThreadAndroid::get_env();
net_utils = env->NewGlobalRef(p_net_utils);
@@ -72,11 +71,6 @@ void NetSocketAndroid::make_default() {
_create = _create_func;
}
-NetSocketAndroid::NetSocketAndroid() :
- wants_broadcast(false),
- multicast_groups(0) {
-}
-
NetSocketAndroid::~NetSocketAndroid() {
close();
}
diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h
index 4fc80d2de1..955d906535 100644
--- a/platform/android/net_socket_android.h
+++ b/platform/android/net_socket_android.h
@@ -45,15 +45,14 @@
* joins/leaves a multicast group.
*/
class NetSocketAndroid : public NetSocketPosix {
-
private:
static jobject net_utils;
static jclass cls;
static jmethodID _multicast_lock_acquire;
static jmethodID _multicast_lock_release;
- bool wants_broadcast;
- int multicast_groups;
+ bool wants_broadcast = false;
+ int multicast_groups = 0;
static void multicast_lock_acquire();
static void multicast_lock_release();
@@ -71,7 +70,7 @@ public:
virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name);
virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name);
- NetSocketAndroid();
+ NetSocketAndroid() {}
~NetSocketAndroid();
};
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 44c5b5d6b4..baf6ee952a 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -32,14 +32,11 @@
#include "core/io/file_access_buffered_fa.h"
#include "core/project_settings.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "file_access_android.h"
#include "main/main.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
+#include "platform/android/display_server_android.h"
#include "dir_access_jandroid.h"
#include "file_access_jandroid.h"
@@ -59,40 +56,14 @@ public:
virtual ~AndroidLogger() {}
};
-int OS_Android::get_video_driver_count() const {
-
- return 2;
-}
-
-const char *OS_Android::get_video_driver_name(int p_driver) const {
-
- switch (p_driver) {
- case VIDEO_DRIVER_GLES3:
- return "GLES3";
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + ".");
-}
-int OS_Android::get_audio_driver_count() const {
-
- return 1;
-}
-
-const char *OS_Android::get_audio_driver_name(int p_driver) const {
-
- return "Android";
-}
-
void OS_Android::initialize_core() {
-
OS_Unix::initialize_core();
if (use_apk_expansion)
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
else {
#ifdef USE_JAVA_FILE_ACCESS
- FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid> >(FileAccess::ACCESS_RESOURCES);
+ FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid>>(FileAccess::ACCESS_RESOURCES);
#else
//FileAccess::make_default<FileAccessBufferedFA<FileAccessAndroid> >(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
@@ -111,92 +82,33 @@ void OS_Android::initialize_core() {
NetSocketAndroid::make_default();
}
-void OS_Android::set_opengl_extensions(const char *p_gl_extensions) {
-
- ERR_FAIL_COND(!p_gl_extensions);
- gl_extensions = p_gl_extensions;
+void OS_Android::initialize() {
+ initialize_core();
}
-int OS_Android::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- bool use_gl3 = godot_java->get_gles_version_code() >= 0x00030000;
- use_gl3 = use_gl3 && (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3");
- bool gl_initialization_error = false;
-
- while (true) {
- if (use_gl3) {
- if (RasterizerGLES3::is_viable() == OK) {
- godot_java->gfx_init(false);
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- use_gl3 = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- godot_java->gfx_init(true);
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n"
- "Please try updating your Android version.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
-
- visual_server = memnew(VisualServerRaster);
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, false));
- }
+void OS_Android::initialize_joypads() {
+ Input::get_singleton()->set_fallback_mapping(godot_java->get_input_fallback_mapping());
- visual_server->init();
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
- input->set_fallback_mapping(godot_java->get_input_fallback_mapping());
-
- //power_manager = memnew(PowerAndroid);
-
- return OK;
+ // This queries/updates the currently connected devices/joypads.
+ godot_java->init_input_devices();
}
void OS_Android::set_main_loop(MainLoop *p_main_loop) {
-
main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
}
void OS_Android::delete_main_loop() {
-
- memdelete(main_loop);
+ if (main_loop) {
+ memdelete(main_loop);
+ main_loop = nullptr;
+ }
}
void OS_Android::finalize() {
+}
- memdelete(input);
+OS_Android *OS_Android::get_singleton() {
+ return (OS_Android *)OS::get_singleton();
}
GodotJavaWrapper *OS_Android::get_godot_java() {
@@ -207,24 +119,15 @@ GodotIOJavaWrapper *OS_Android::get_godot_io_java() {
return godot_io_java;
}
-void OS_Android::alert(const String &p_alert, const String &p_title) {
-
- //print("ALERT: %s\n", p_alert.utf8().get_data());
- godot_java->alert(p_alert, p_title);
-}
-
bool OS_Android::request_permission(const String &p_name) {
-
return godot_java->request_permission(p_name);
}
bool OS_Android::request_permissions() {
-
return godot_java->request_permissions();
}
Vector<String> OS_Android::get_granted_permissions() const {
-
return godot_java->get_granted_permissions();
}
@@ -234,384 +137,53 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han
return OK;
}
-void OS_Android::set_mouse_show(bool p_show) {
-
- //android has no mouse...
-}
-
-void OS_Android::set_mouse_grab(bool p_grab) {
-
- //it really has no mouse...!
-}
-
-bool OS_Android::is_mouse_grab_enabled() const {
-
- //*sigh* technology has evolved so much since i was a kid..
- return false;
-}
-
-Point2 OS_Android::get_mouse_position() const {
-
- return Point2();
-}
-
-int OS_Android::get_mouse_button_state() const {
-
- return 0;
-}
-
-void OS_Android::set_window_title(const String &p_title) {
- //This queries/updates the currently connected devices/joypads
- //Set_window_title is called when initializing the main loop (main.cpp)
- //therefore this place is found to be suitable (I found no better).
- godot_java->init_input_devices();
-}
-
-void OS_Android::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-}
-
-OS::VideoMode OS_Android::get_video_mode(int p_screen) const {
-
- return default_videomode;
-}
-
-void OS_Android::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-
- p_list->push_back(default_videomode);
-}
-
-void OS_Android::set_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
-
- godot_java->set_keep_screen_on(p_enabled);
-}
-
-Size2 OS_Android::get_window_size() const {
-
- return Vector2(default_videomode.width, default_videomode.height);
-}
-
String OS_Android::get_name() const {
-
return "Android";
}
MainLoop *OS_Android::get_main_loop() const {
-
return main_loop;
}
-bool OS_Android::can_draw() const {
-
- return true; //always?
-}
-
void OS_Android::main_loop_begin() {
-
if (main_loop)
main_loop->init();
}
bool OS_Android::main_loop_iterate() {
-
if (!main_loop)
return false;
return Main::iteration();
}
void OS_Android::main_loop_end() {
-
if (main_loop)
main_loop->finish();
}
void OS_Android::main_loop_focusout() {
-
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
+ DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
audio_driver_android.set_pause(true);
}
void OS_Android::main_loop_focusin() {
-
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
+ DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
audio_driver_android.set_pause(false);
}
-void OS_Android::process_joy_event(OS_Android::JoypadEvent p_event) {
-
- switch (p_event.type) {
- case JOY_EVENT_BUTTON:
- input->joy_button(p_event.device, p_event.index, p_event.pressed);
- break;
- case JOY_EVENT_AXIS:
- InputDefault::JoyAxis value;
- value.min = -1;
- value.value = p_event.value;
- input->joy_axis(p_event.device, p_event.index, value);
- break;
- case JOY_EVENT_HAT:
- input->joy_hat(p_event.device, p_event.hat);
- break;
- default:
- return;
- }
-}
-
-void OS_Android::process_event(Ref<InputEvent> p_event) {
-
- input->parse_input_event(p_event);
-}
-
-void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points) {
-
- switch (p_what) {
- case 0: { //gesture begin
-
- if (touch.size()) {
- //end all if exist
- for (int i = 0; i < touch.size(); i++) {
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- input->parse_input_event(ev);
- }
- }
-
- touch.resize(p_points.size());
- for (int i = 0; i < p_points.size(); i++) {
- touch.write[i].id = p_points[i].id;
- touch.write[i].pos = p_points[i].pos;
- }
-
- //send touch
- for (int i = 0; i < touch.size(); i++) {
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(true);
- ev->set_position(touch[i].pos);
- input->parse_input_event(ev);
- }
-
- } break;
- case 1: { //motion
-
- ERR_FAIL_COND(touch.size() != p_points.size());
-
- for (int i = 0; i < touch.size(); i++) {
-
- int idx = -1;
- for (int j = 0; j < p_points.size(); j++) {
-
- if (touch[i].id == p_points[j].id) {
- idx = j;
- break;
- }
- }
-
- ERR_CONTINUE(idx == -1);
-
- if (touch[i].pos == p_points[idx].pos)
- continue; //no move unncesearily
-
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_position(p_points[idx].pos);
- ev->set_relative(p_points[idx].pos - touch[i].pos);
- input->parse_input_event(ev);
- touch.write[i].pos = p_points[idx].pos;
- }
-
- } break;
- case 2: { //release
-
- if (touch.size()) {
- //end all if exist
- for (int i = 0; i < touch.size(); i++) {
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- input->parse_input_event(ev);
- }
- touch.clear();
- }
- } break;
- case 3: { // add touch
-
- for (int i = 0; i < p_points.size(); i++) {
- if (p_points[i].id == p_pointer) {
- TouchPos tp = p_points[i];
- touch.push_back(tp);
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(tp.id);
- ev->set_pressed(true);
- ev->set_position(tp.pos);
- input->parse_input_event(ev);
-
- break;
- }
- }
- } break;
- case 4: { // remove touch
-
- for (int i = 0; i < touch.size(); i++) {
- if (touch[i].id == p_pointer) {
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- input->parse_input_event(ev);
- touch.remove(i);
-
- break;
- }
- }
- } break;
- }
-}
-
-void OS_Android::process_hover(int p_type, Point2 p_pos) {
- // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER
- switch (p_type) {
- case 7: // hover move
- case 9: // hover enter
- case 10: { // hover exit
- Ref<InputEventMouseMotion> ev;
- ev.instance();
- ev->set_position(p_pos);
- ev->set_global_position(p_pos);
- ev->set_relative(p_pos - hover_prev_pos);
- input->parse_input_event(ev);
- hover_prev_pos = p_pos;
- } break;
- }
-}
-
-void OS_Android::process_double_tap(Point2 p_pos) {
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_position(p_pos);
- ev->set_global_position(p_pos);
- ev->set_pressed(true);
- ev->set_doubleclick(true);
- ev->set_button_index(1);
- input->parse_input_event(ev);
-}
-
-void OS_Android::process_scroll(Point2 p_pos) {
- Ref<InputEventPanGesture> ev;
- ev.instance();
- ev->set_position(p_pos);
- ev->set_delta(p_pos - scroll_prev_pos);
- input->parse_input_event(ev);
- scroll_prev_pos = p_pos;
-}
-
-void OS_Android::process_accelerometer(const Vector3 &p_accelerometer) {
-
- input->set_accelerometer(p_accelerometer);
-}
-
-void OS_Android::process_gravity(const Vector3 &p_gravity) {
-
- input->set_gravity(p_gravity);
-}
-
-void OS_Android::process_magnetometer(const Vector3 &p_magnetometer) {
-
- input->set_magnetometer(p_magnetometer);
-}
-
-void OS_Android::process_gyroscope(const Vector3 &p_gyroscope) {
-
- input->set_gyroscope(p_gyroscope);
-}
-
-bool OS_Android::has_touchscreen_ui_hint() const {
-
- return true;
-}
-
-bool OS_Android::has_virtual_keyboard() const {
-
- return true;
-}
-
-int OS_Android::get_virtual_keyboard_height() const {
- return godot_io_java->get_vk_height();
-
- // ERR_PRINT("Cannot obtain virtual keyboard height.");
- // return 0;
-}
-
-void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) {
-
- if (godot_io_java->has_vk()) {
- godot_io_java->show_vk(p_existing_text, p_max_input_length);
- } else {
-
- ERR_PRINT("Virtual keyboard not available");
- };
-}
-
-void OS_Android::hide_virtual_keyboard() {
-
- if (godot_io_java->has_vk()) {
-
- godot_io_java->hide_vk();
- } else {
-
- ERR_PRINT("Virtual keyboard not available");
- };
-}
-
-void OS_Android::init_video_mode(int p_video_width, int p_video_height) {
-
- default_videomode.width = p_video_width;
- default_videomode.height = p_video_height;
- default_videomode.fullscreen = true;
- default_videomode.resizable = false;
-}
-
void OS_Android::main_loop_request_go_back() {
-
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_GO_BACK_REQUEST);
-}
-
-void OS_Android::set_display_size(Size2 p_size) {
-
- default_videomode.width = p_size.x;
- default_videomode.height = p_size.y;
+ DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST);
}
Error OS_Android::shell_open(String p_uri) {
-
return godot_io_java->open_uri(p_uri);
}
String OS_Android::get_resource_dir() const {
-
return "/"; //android has its own filesystem for resources inside the APK
}
String OS_Android::get_locale() const {
-
String locale = godot_io_java->get_locale();
if (locale != "") {
return locale;
@@ -620,28 +192,7 @@ String OS_Android::get_locale() const {
return OS_Unix::get_locale();
}
-void OS_Android::set_clipboard(const String &p_text) {
-
- // DO we really need the fallback to OS_Unix here?!
- if (godot_java->has_set_clipboard()) {
- godot_java->set_clipboard(p_text);
- } else {
- OS_Unix::set_clipboard(p_text);
- }
-}
-
-String OS_Android::get_clipboard() const {
-
- // DO we really need the fallback to OS_Unix here?!
- if (godot_java->has_get_clipboard()) {
- return godot_java->get_clipboard();
- }
-
- return OS_Unix::get_clipboard();
-}
-
String OS_Android::get_model_name() const {
-
String model = godot_io_java->get_model();
if (model != "")
return model;
@@ -649,19 +200,12 @@ String OS_Android::get_model_name() const {
return OS_Unix::get_model_name();
}
-int OS_Android::get_screen_dpi(int p_screen) const {
-
- return godot_io_java->get_screen_dpi();
-}
-
String OS_Android::get_user_data_dir() const {
-
if (data_dir_cache != String())
return data_dir_cache;
String data_dir = godot_io_java->get_user_data_dir();
if (data_dir != "") {
-
//store current dir
char real_current_dir_name[2048];
getcwd(real_current_dir_name, 2048);
@@ -685,13 +229,7 @@ String OS_Android::get_user_data_dir() const {
return ".";
}
-void OS_Android::set_screen_orientation(ScreenOrientation p_orientation) {
-
- godot_io_java->set_screen_orientation(p_orientation);
-}
-
String OS_Android::get_unique_id() const {
-
String unique_id = godot_io_java->get_unique_id();
if (unique_id != "")
return unique_id;
@@ -699,50 +237,45 @@ String OS_Android::get_unique_id() const {
return OS::get_unique_id();
}
-Error OS_Android::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- // FIXME: Add support for volume, audio and subtitle tracks
-
- godot_io_java->play_video(p_path);
- return OK;
-}
-
-bool OS_Android::native_video_is_playing() const {
-
- return godot_io_java->is_video_playing();
-}
-
-void OS_Android::native_video_pause() {
-
- godot_io_java->pause_video();
-}
-
String OS_Android::get_system_dir(SystemDir p_dir) const {
-
return godot_io_java->get_system_dir(p_dir);
}
-void OS_Android::native_video_stop() {
+void OS_Android::set_display_size(const Size2i &p_size) {
+ display_size = p_size;
+}
- godot_io_java->stop_video();
+Size2i OS_Android::get_display_size() const {
+ return display_size;
}
void OS_Android::set_context_is_16_bits(bool p_is_16) {
-
+#if defined(OPENGL_ENABLED)
//use_16bits_fbo = p_is_16;
//if (rasterizer)
// rasterizer->set_force_16_bits_fbo(p_is_16);
+#endif
}
-void OS_Android::joy_connection_changed(int p_device, bool p_connected, String p_name) {
- return input->joy_connection_changed(p_device, p_connected, p_name, "");
+void OS_Android::set_opengl_extensions(const char *p_gl_extensions) {
+#if defined(OPENGL_ENABLED)
+ ERR_FAIL_COND(!p_gl_extensions);
+ gl_extensions = p_gl_extensions;
+#endif
}
-bool OS_Android::is_joy_known(int p_device) {
- return input->is_joy_mapped(p_device);
+void OS_Android::set_native_window(ANativeWindow *p_native_window) {
+#if defined(VULKAN_ENABLED)
+ native_window = p_native_window;
+#endif
}
-String OS_Android::get_joy_guid(int p_device) const {
- return input->get_joy_guid_remapped(p_device);
+ANativeWindow *OS_Android::get_native_window() const {
+#if defined(VULKAN_ENABLED)
+ return native_window;
+#else
+ return nullptr;
+#endif
}
void OS_Android::vibrate_handheld(int p_duration_ms) {
@@ -751,7 +284,6 @@ void OS_Android::vibrate_handheld(int p_duration_ms) {
bool OS_Android::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "mobile") {
- //TODO support etc2 only if GLES3 driver is selected
return true;
}
#if defined(__aarch64__)
@@ -771,17 +303,21 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) {
}
OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion) {
+ display_size.width = 800;
+ display_size.height = 600;
use_apk_expansion = p_use_apk_expansion;
- default_videomode.width = 800;
- default_videomode.height = 600;
- default_videomode.fullscreen = true;
- default_videomode.resizable = false;
-
- main_loop = NULL;
- gl_extensions = NULL;
- //rasterizer = NULL;
+
+ main_loop = nullptr;
+
+#if defined(OPENGL_ENABLED)
+ gl_extensions = nullptr;
use_gl2 = false;
+#endif
+
+#if defined(VULKAN_ENABLED)
+ native_window = nullptr;
+#endif
godot_java = p_godot_java;
godot_io_java = p_godot_io_java;
@@ -791,6 +327,8 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver_android);
+
+ DisplayServerAndroid::register_android_driver();
}
OS_Android::~OS_Android() {
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index c2f9a0992f..cac7efaa88 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -33,82 +33,45 @@
#include "audio_driver_jandroid.h"
#include "audio_driver_opensl.h"
-#include "core/os/input.h"
#include "core/os/main_loop.h"
#include "drivers/unix/os_unix.h"
-#include "main/input_default.h"
-//#include "power_android.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
class GodotJavaWrapper;
class GodotIOJavaWrapper;
-class OS_Android : public OS_Unix {
-public:
- struct TouchPos {
- int id;
- Point2 pos;
- };
-
- enum {
- JOY_EVENT_BUTTON = 0,
- JOY_EVENT_AXIS = 1,
- JOY_EVENT_HAT = 2
- };
-
- struct JoypadEvent {
-
- int device;
- int type;
- int index;
- bool pressed;
- float value;
- int hat;
- };
+struct ANativeWindow;
+class OS_Android : public OS_Unix {
private:
- Vector<TouchPos> touch;
- Point2 hover_prev_pos; // needed to calculate the relative position on hover events
- Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events
+ Size2i display_size;
- bool use_gl2;
bool use_apk_expansion;
+#if defined(OPENGL_ENABLED)
bool use_16bits_fbo;
+ const char *gl_extensions;
+#endif
- VisualServer *visual_server;
+#if defined(VULKAN_ENABLED)
+ ANativeWindow *native_window;
+#endif
mutable String data_dir_cache;
//AudioDriverAndroid audio_driver_android;
AudioDriverOpenSL audio_driver_android;
- const char *gl_extensions;
-
- InputDefault *input;
- VideoMode default_videomode;
MainLoop *main_loop;
GodotJavaWrapper *godot_java;
GodotIOJavaWrapper *godot_io_java;
- //PowerAndroid *power_manager_func;
-
- int video_driver_index;
-
public:
- // functions used by main to initialize/deinitialize the OS
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_audio_driver_count() const;
- virtual const char *get_audio_driver_name(int p_driver) const;
-
- virtual int get_current_video_driver() const;
-
virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ virtual void initialize();
+
+ virtual void initialize_joypads();
virtual void set_main_loop(MainLoop *p_main_loop);
virtual void delete_main_loop();
@@ -117,37 +80,19 @@ public:
typedef int64_t ProcessID;
- static OS *get_singleton();
+ static OS_Android *get_singleton();
GodotJavaWrapper *get_godot_java();
GodotIOJavaWrapper *get_godot_io_java();
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual bool request_permission(const String &p_name);
virtual bool request_permissions();
virtual Vector<String> get_granted_permissions() const;
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
- virtual void set_keep_screen_on(bool p_enabled);
-
- virtual Size2 get_window_size() const;
-
virtual String get_name() const;
virtual MainLoop *get_main_loop() const;
- virtual bool can_draw() const;
-
void main_loop_begin();
bool main_loop_iterate();
void main_loop_request_go_back();
@@ -155,53 +100,25 @@ public:
void main_loop_focusout();
void main_loop_focusin();
- virtual bool has_touchscreen_ui_hint() const;
-
- virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1);
- virtual void hide_virtual_keyboard();
- virtual int get_virtual_keyboard_height() const;
-
- void set_opengl_extensions(const char *p_gl_extensions);
- void set_display_size(Size2 p_size);
+ void set_display_size(const Size2i &p_size);
+ Size2i get_display_size() const;
void set_context_is_16_bits(bool p_is_16);
+ void set_opengl_extensions(const char *p_gl_extensions);
- virtual void set_screen_orientation(ScreenOrientation p_orientation);
+ void set_native_window(ANativeWindow *p_native_window);
+ ANativeWindow *get_native_window() const;
virtual Error shell_open(String p_uri);
virtual String get_user_data_dir() const;
virtual String get_resource_dir() const;
virtual String get_locale() const;
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
virtual String get_model_name() const;
- virtual int get_screen_dpi(int p_screen = 0) const;
virtual String get_unique_id() const;
virtual String get_system_dir(SystemDir p_dir) const;
- void process_accelerometer(const Vector3 &p_accelerometer);
- void process_gravity(const Vector3 &p_gravity);
- void process_magnetometer(const Vector3 &p_magnetometer);
- void process_gyroscope(const Vector3 &p_gyroscope);
- void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points);
- void process_hover(int p_type, Point2 p_pos);
- void process_double_tap(Point2 p_pos);
- void process_scroll(Point2 p_pos);
- void process_joy_event(JoypadEvent p_event);
- void process_event(Ref<InputEvent> p_event);
- void init_video_mode(int p_video_width, int p_video_height);
-
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_stop();
-
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
- void joy_connection_changed(int p_device, bool p_connected, String p_name);
void vibrate_handheld(int p_duration_ms);
virtual bool _check_internal_feature_support(const String &p_feature);
diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h
new file mode 100644
index 0000000000..ea3c7b4f55
--- /dev/null
+++ b/platform/android/plugin/godot_plugin_config.h
@@ -0,0 +1,268 @@
+/*************************************************************************/
+/* godot_plugin_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error_list.h"
+#include "core/io/config_file.h"
+#include "core/ustring.h"
+
+static const char *PLUGIN_CONFIG_EXT = ".gdap";
+
+static const char *CONFIG_SECTION = "config";
+static const char *CONFIG_NAME_KEY = "name";
+static const char *CONFIG_BINARY_TYPE_KEY = "binary_type";
+static const char *CONFIG_BINARY_KEY = "binary";
+
+static const char *DEPENDENCIES_SECTION = "dependencies";
+static const char *DEPENDENCIES_LOCAL_KEY = "local";
+static const char *DEPENDENCIES_REMOTE_KEY = "remote";
+static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos";
+
+static const char *BINARY_TYPE_LOCAL = "local";
+static const char *BINARY_TYPE_REMOTE = "remote";
+
+static const char *PLUGIN_VALUE_SEPARATOR = "|";
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field
+- **binary**:
+ - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`).
+ - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0").
+
+The `dependencies` section and fields are optional and defined as follow:
+- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory.
+- **remote**: contains a list of remote binary gradle dependencies for the plugin.
+- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies.
+
+ See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871
+ */
+struct PluginConfig {
+ // Set to true when the config file is properly loaded.
+ bool valid_config = false;
+ // Unix timestamp of last change to this plugin.
+ uint64_t last_updated = 0;
+
+ // Required config section
+ String name;
+ String binary_type;
+ String binary;
+
+ // Optional dependencies section
+ Vector<String> local_dependencies;
+ Vector<String> remote_dependencies;
+ Vector<String> custom_maven_repos;
+};
+
+/*
+ * Set of prebuilt plugins.
+ * Currently unused, this is just for future reference:
+ */
+// static const PluginConfig MY_PREBUILT_PLUGIN = {
+// /*.valid_config =*/true,
+// /*.last_updated =*/0,
+// /*.name =*/"GodotPayment",
+// /*.binary_type =*/"local",
+// /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
+// /*.local_dependencies =*/{},
+// /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"),
+// /*.custom_maven_repos =*/{}
+// };
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+ if (!dependency_path.empty()) {
+ if (dependency_path.is_abs_path()) {
+ absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path);
+ } else {
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+ }
+ }
+
+ return absolute_path;
+}
+
+static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, String plugin_config_dir) {
+ PluginConfig resolved = prebuilt_plugin;
+ resolved.binary = resolved.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary;
+ if (!prebuilt_plugin.local_dependencies.empty()) {
+ resolved.local_dependencies.clear();
+ for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) {
+ resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i]));
+ }
+ }
+ return resolved;
+}
+
+static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) {
+ Vector<PluginConfig> prebuilt_plugins;
+ // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir));
+ return prebuilt_plugins;
+}
+
+static inline bool is_plugin_config_valid(PluginConfig plugin_config) {
+ bool valid_name = !plugin_config.name.empty();
+ bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL ||
+ plugin_config.binary_type == BINARY_TYPE_REMOTE;
+
+ bool valid_binary = false;
+ if (valid_binary_type) {
+ valid_binary = !plugin_config.binary.empty() &&
+ (plugin_config.binary_type == BINARY_TYPE_REMOTE ||
+ FileAccess::exists(plugin_config.binary));
+ }
+
+ bool valid_local_dependencies = true;
+ if (!plugin_config.local_dependencies.empty()) {
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ if (!FileAccess::exists(plugin_config.local_dependencies[i])) {
+ valid_local_dependencies = false;
+ break;
+ }
+ }
+ }
+ return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
+}
+
+static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) {
+ uint64_t last_updated = FileAccess::get_modified_time(config_path);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ String binary = plugin_config.local_dependencies.get(i);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(binary));
+ }
+
+ return last_updated;
+}
+
+static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+ PluginConfig plugin_config = {};
+
+ if (config_file.is_valid()) {
+ Error err = config_file->load(path);
+ if (err == OK) {
+ String config_base_dir = path.get_base_dir();
+
+ plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String());
+ plugin_config.binary_type = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_TYPE_KEY, String());
+
+ String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
+ plugin_config.binary = plugin_config.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path;
+
+ if (config_file->has_section(DEPENDENCIES_SECTION)) {
+ Vector<String> local_dependencies_paths = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LOCAL_KEY, Vector<String>());
+ if (!local_dependencies_paths.empty()) {
+ for (int i = 0; i < local_dependencies_paths.size(); i++) {
+ plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i]));
+ }
+ }
+
+ plugin_config.remote_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_REMOTE_KEY, Vector<String>());
+ plugin_config.custom_maven_repos = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>());
+ }
+
+ plugin_config.valid_config = is_plugin_config_valid(plugin_config);
+ plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
+ }
+ }
+
+ return plugin_config;
+}
+
+static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) {
+ String plugins_binaries;
+ if (!plugins_configs.empty()) {
+ Vector<String> binaries;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ if (config.binary_type == binary_type) {
+ binaries.push_back(config.binary);
+ }
+
+ if (binary_type == BINARY_TYPE_LOCAL) {
+ binaries.append_array(config.local_dependencies);
+ }
+
+ if (binary_type == BINARY_TYPE_REMOTE) {
+ binaries.append_array(config.remote_dependencies);
+ }
+ }
+
+ plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries);
+ }
+
+ return plugins_binaries;
+}
+
+static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) {
+ String custom_maven_repos;
+ if (!plugins_configs.empty()) {
+ Vector<String> repos_urls;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ repos_urls.append_array(config.custom_maven_repos);
+ }
+
+ custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls);
+ }
+ return custom_maven_repos;
+}
+
+static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) {
+ String plugins_names;
+ if (!plugins_configs.empty()) {
+ Vector<String> names;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ names.push_back(config.name);
+ }
+ plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names);
+ }
+
+ return plugins_names;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
new file mode 100644
index 0000000000..d2528bebeb
--- /dev/null
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -0,0 +1,158 @@
+/*************************************************************************/
+/* godot_plugin_jni.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "godot_plugin_jni.h"
+
+#include <core/engine.h>
+#include <core/error_macros.h>
+#include <core/project_settings.h>
+#include <platform/android/api/jni_singleton.h>
+#include <platform/android/jni_utils.h>
+#include <platform/android/string_android.h>
+
+static HashMap<String, JNISingleton *> jni_singletons;
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) {
+ String singname = jstring_to_string(name, env);
+ JNISingleton *s = (JNISingleton *)ClassDB::instance("JNISingleton");
+ s->set_instance(env->NewGlobalRef(obj));
+ jni_singletons[singname] = s;
+
+ Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
+ ProjectSettings::get_singleton()->set(singname, s);
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
+ String singname = jstring_to_string(sname, env);
+
+ ERR_FAIL_COND(!jni_singletons.has(singname));
+
+ JNISingleton *s = jni_singletons.get(singname);
+
+ String mname = jstring_to_string(name, env);
+ String retval = jstring_to_string(ret, env);
+ Vector<Variant::Type> types;
+ String cs = "(";
+
+ int stringCount = env->GetArrayLength(args);
+
+ for (int i = 0; i < stringCount; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(args, i);
+ const String rawString = jstring_to_string(string, env);
+ types.push_back(get_jni_type(rawString));
+ cs += get_jni_sig(rawString);
+ }
+
+ cs += ")";
+ cs += get_jni_sig(retval);
+ jclass cls = env->GetObjectClass(s->get_instance());
+ jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
+ if (!mid) {
+ print_line("Failed getting method ID " + mname);
+ }
+
+ s->add_method(mname, mid, types, get_jni_type(retval));
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) {
+ String singleton_name = jstring_to_string(j_plugin_name, env);
+
+ ERR_FAIL_COND(!jni_singletons.has(singleton_name));
+
+ JNISingleton *singleton = jni_singletons.get(singleton_name);
+
+ String signal_name = jstring_to_string(j_signal_name, env);
+ Vector<Variant::Type> types;
+
+ int stringCount = env->GetArrayLength(j_signal_param_types);
+
+ for (int i = 0; i < stringCount; i++) {
+ jstring j_signal_param_type = (jstring)env->GetObjectArrayElement(j_signal_param_types, i);
+ const String signal_param_type = jstring_to_string(j_signal_param_type, env);
+ types.push_back(get_jni_type(signal_param_type));
+ }
+
+ singleton->add_signal(signal_name, types);
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) {
+ String singleton_name = jstring_to_string(j_plugin_name, env);
+
+ ERR_FAIL_COND(!jni_singletons.has(singleton_name));
+
+ JNISingleton *singleton = jni_singletons.get(singleton_name);
+
+ String signal_name = jstring_to_string(j_signal_name, env);
+
+ int count = env->GetArrayLength(j_signal_params);
+ ERR_FAIL_COND_MSG(count > VARIANT_ARG_MAX, "Maximum argument count exceeded!");
+
+ Variant variant_params[VARIANT_ARG_MAX];
+ const Variant *args[VARIANT_ARG_MAX];
+
+ for (int i = 0; i < count; i++) {
+ jobject j_param = env->GetObjectArrayElement(j_signal_params, i);
+ variant_params[i] = _jobject_to_variant(env, j_param);
+ args[i] = &variant_params[i];
+ env->DeleteLocalRef(j_param);
+ };
+
+ singleton->emit_signal(signal_name, args, count);
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) {
+ int gdnlib_count = env->GetArrayLength(gdnlib_paths);
+ if (gdnlib_count == 0) {
+ return;
+ }
+
+ // Retrieve the current list of gdnative libraries.
+ Array singletons = Array();
+ if (ProjectSettings::get_singleton()->has_setting("gdnative/singletons")) {
+ singletons = ProjectSettings::get_singleton()->get("gdnative/singletons");
+ }
+
+ // Insert the libraries provided by the plugin
+ for (int i = 0; i < gdnlib_count; i++) {
+ jstring relative_path = (jstring)env->GetObjectArrayElement(gdnlib_paths, i);
+
+ String path = "res://" + jstring_to_string(relative_path, env);
+ if (!singletons.has(path)) {
+ singletons.push_back(path);
+ }
+ env->DeleteLocalRef(relative_path);
+ }
+
+ // Insert the updated list back into project settings.
+ ProjectSettings::get_singleton()->set("gdnative/singletons", singletons);
+}
+}
diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h
new file mode 100644
index 0000000000..80ce332e7c
--- /dev/null
+++ b/platform/android/plugin/godot_plugin_jni.h
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* godot_plugin_jni.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_JNI_H
+#define GODOT_PLUGIN_JNI_H
+
+#include <android/log.h>
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths);
+}
+
+#endif // GODOT_PLUGIN_JNI_H
diff --git a/platform/android/power_android.cpp b/platform/android/power_android.cpp
deleted file mode 100644
index b0a90312e5..0000000000
--- a/platform/android/power_android.cpp
+++ /dev/null
@@ -1,255 +0,0 @@
-/*************************************************************************/
-/* power_android.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-/*
-Adapted from corresponding SDL 2.0 code.
-*/
-
-/*
- Simple DirectMedia Layer
- Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
-*/
-
-#include "power_android.h"
-
-#include "core/error_macros.h"
-
-static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) {
- if (refholder->m_env) {
- JNIEnv *env = refholder->m_env;
- (*env)->PopLocalFrame(env, NULL);
- --s_active;
- }
-}
-
-static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) {
- struct LocalReferenceHolder refholder;
- refholder.m_env = NULL;
- refholder.m_func = func;
- return refholder;
-}
-
-static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) {
- const int capacity = 16;
- if ((*env)->PushLocalFrame(env, capacity) < 0) {
- return false;
- }
- ++s_active;
- refholder->m_env = env;
- return true;
-}
-
-static SDL_bool LocalReferenceHolder_IsActive(void) {
- return s_active > 0;
-}
-
-ANativeWindow *Android_JNI_GetNativeWindow(void) {
- ANativeWindow *anw;
- jobject s;
- JNIEnv *env = Android_JNI_GetEnv();
-
- s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
- anw = ANativeWindow_fromSurface(env, s);
- (*env)->DeleteLocalRef(env, s);
-
- return anw;
-}
-
-/*
- * CODE CHUNK IMPORTED FROM SDL 2.0
- * returns 0 on success or -1 on error (others undefined then)
- * returns truthy or falsy value in plugged, charged and battery
- * returns the value in seconds and percent or -1 if not available
- */
-int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) {
- env = Android_JNI_GetEnv();
- refs = LocalReferenceHolder_Setup(__FUNCTION__);
-
- if (!LocalReferenceHolder_Init(&refs, env)) {
- LocalReferenceHolder_Cleanup(&refs);
- return -1;
- }
- mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
- context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
- action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
- cls = (*env)->FindClass(env, "android/content/IntentFilter");
- mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
- filter = (*env)->NewObject(env, cls, mid, action);
- (*env)->DeleteLocalRef(env, action);
- mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
- intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
- (*env)->DeleteLocalRef(env, filter);
- cls = (*env)->GetObjectClass(env, intent);
- imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
-// Watch out for C89 scoping rules because of the macro
-#define GET_INT_EXTRA(var, key) \
- int var; \
- iname = (*env)->NewStringUTF(env, key); \
- var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
- (*env)->DeleteLocalRef(env, iname);
- bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
-// Watch out for C89 scoping rules because of the macro
-#define GET_BOOL_EXTRA(var, key) \
- int var; \
- bname = (*env)->NewStringUTF(env, key); \
- var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
- (*env)->DeleteLocalRef(env, bname);
- if (plugged) {
- // Watch out for C89 scoping rules because of the macro
- GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
- if (plug == -1) {
- LocalReferenceHolder_Cleanup(&refs);
- return -1;
- }
- // 1 == BatteryManager.BATTERY_PLUGGED_AC
- // 2 == BatteryManager.BATTERY_PLUGGED_USB
- *plugged = (0 < plug) ? 1 : 0;
- }
- if (charged) {
- // Watch out for C89 scoping rules because of the macro
- GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
- if (status == -1) {
- LocalReferenceHolder_Cleanup(&refs);
- return -1;
- }
- // 5 == BatteryManager.BATTERY_STATUS_FULL
- *charged = (status == 5) ? 1 : 0;
- }
- if (battery) {
- GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
- *battery = present ? 1 : 0;
- }
- if (seconds) {
- *seconds = -1; // not possible
- }
- if (percent) {
- int level;
- int scale;
- // Watch out for C89 scoping rules because of the macro
- {
- GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
- level = level_temp;
- }
- // Watch out for C89 scoping rules because of the macro
- {
- GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
- scale = scale_temp;
- }
- if ((level == -1) || (scale == -1)) {
- LocalReferenceHolder_Cleanup(&refs);
- return -1;
- }
- *percent = level * 100 / scale;
- }
- (*env)->DeleteLocalRef(env, intent);
- LocalReferenceHolder_Cleanup(&refs);
-
- return 0;
-}
-
-bool PowerAndroid::GetPowerInfo_Android() {
- int battery;
- int plugged;
- int charged;
-
- if (Android_JNI_GetPowerInfo(&plugged, &charged, &battery, &this->nsecs_left, &this->percent_left) != -1) {
- if (plugged) {
- if (charged) {
- this->power_state = OS::POWERSTATE_CHARGED;
- } else if (battery) {
- this->power_state = OS::POWERSTATE_CHARGING;
- } else {
- this->power_state = OS::POWERSTATE_NO_BATTERY;
- this->nsecs_left = -1;
- this->percent_left = -1;
- }
- } else {
- this->power_state = OS::POWERSTATE_ON_BATTERY;
- }
- } else {
- this->power_state = OS::POWERSTATE_UNKNOWN;
- this->nsecs_left = -1;
- this->percent_left = -1;
- }
-
- return true;
-}
-
-OS::PowerState PowerAndroid::get_power_state() {
- if (GetPowerInfo_Android()) {
- return power_state;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN");
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerAndroid::get_power_seconds_left() {
- if (GetPowerInfo_Android()) {
- return nsecs_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
-
-int PowerAndroid::get_power_percent_left() {
- if (GetPowerInfo_Android()) {
- return percent_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
-
-PowerAndroid::PowerAndroid() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerAndroid::~PowerAndroid() {
-}
diff --git a/platform/android/power_android.h b/platform/android/power_android.h
deleted file mode 100644
index 9f77f3fc6b..0000000000
--- a/platform/android/power_android.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*************************************************************************/
-/* power_android.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef POWER_ANDROID_H
-#define POWER_ANDROID_H
-
-#include "core/os/os.h"
-
-#include <android/native_window_jni.h>
-
-class PowerAndroid {
-
- struct LocalReferenceHolder {
- JNIEnv *m_env;
- const char *m_func;
- };
-
-private:
- static struct LocalReferenceHolder refs;
- static JNIEnv *env;
- static jmethodID mid;
- static jobject context;
- static jstring action;
- static jclass cls;
- static jobject filter;
- static jobject intent;
- static jstring iname;
- static jmethodID imid;
- static jstring bname;
- static jmethodID bmid;
-
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
-
- bool GetPowerInfo_Android();
- bool UpdatePowerInfo();
-
-public:
- static int s_active;
-
- PowerAndroid();
- virtual ~PowerAndroid();
- static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env);
- static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func);
- static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder);
-
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
-};
-
-#endif // POWER_ANDROID_H
diff --git a/platform/android/string_android.h b/platform/android/string_android.h
index 51c488c8cc..88ccd3b652 100644
--- a/platform/android/string_android.h
+++ b/platform/android/string_android.h
@@ -40,13 +40,13 @@
* @param env JNI environment instance. If null obtained by ThreadAndroid::get_env().
* @return Godot string instance.
*/
-static inline String jstring_to_string(jstring source, JNIEnv *env = NULL) {
+static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) {
String result;
if (source) {
if (!env) {
env = ThreadAndroid::get_env();
}
- const char *const source_utf8 = env->GetStringUTFChars(source, NULL);
+ const char *const source_utf8 = env->GetStringUTFChars(source, nullptr);
if (source_utf8) {
result.parse_utf8(source_utf8);
env->ReleaseStringUTFChars(source, source_utf8);
diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp
index 98b61ad755..13aa313ebf 100644
--- a/platform/android/thread_jandroid.cpp
+++ b/platform/android/thread_jandroid.cpp
@@ -48,17 +48,14 @@ pthread_key_t ThreadAndroid::thread_id_key = _create_thread_id_key();
Thread::ID ThreadAndroid::next_thread_id = 0;
Thread::ID ThreadAndroid::get_id() const {
-
return id;
}
Thread *ThreadAndroid::create_thread_jandroid() {
-
return memnew(ThreadAndroid);
}
void *ThreadAndroid::thread_callback(void *userdata) {
-
ThreadAndroid *t = reinterpret_cast<ThreadAndroid *>(userdata);
setup_thread();
ScriptServer::thread_enter(); //scripts may need to attach a stack
@@ -66,11 +63,10 @@ void *ThreadAndroid::thread_callback(void *userdata) {
pthread_setspecific(thread_id_key, (void *)memnew(ID(t->id)));
t->callback(t->user);
ScriptServer::thread_exit();
- return NULL;
+ return nullptr;
}
Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, void *p_user, const Settings &) {
-
ThreadAndroid *tr = memnew(ThreadAndroid);
tr->callback = p_callback;
tr->user = p_user;
@@ -83,7 +79,6 @@ Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, voi
}
Thread::ID ThreadAndroid::get_thread_id_func_jandroid() {
-
void *value = pthread_getspecific(thread_id_key);
if (value)
@@ -95,39 +90,35 @@ Thread::ID ThreadAndroid::get_thread_id_func_jandroid() {
}
void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) {
-
ThreadAndroid *tp = static_cast<ThreadAndroid *>(p_thread);
ERR_FAIL_COND(!tp);
ERR_FAIL_COND(tp->pthread == 0);
- pthread_join(tp->pthread, NULL);
+ pthread_join(tp->pthread, nullptr);
tp->pthread = 0;
}
void ThreadAndroid::_thread_destroyed(void *value) {
-
/* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
JNIEnv *env = (JNIEnv *)value;
- if (env != NULL) {
+ if (env != nullptr) {
java_vm->DetachCurrentThread();
- pthread_setspecific(jvm_key, NULL);
+ pthread_setspecific(jvm_key, nullptr);
}
}
pthread_key_t ThreadAndroid::jvm_key;
-JavaVM *ThreadAndroid::java_vm = NULL;
+JavaVM *ThreadAndroid::java_vm = nullptr;
void ThreadAndroid::setup_thread() {
-
if (pthread_getspecific(jvm_key))
return; //already setup
JNIEnv *env;
- java_vm->AttachCurrentThread(&env, NULL);
+ java_vm->AttachCurrentThread(&env, nullptr);
pthread_setspecific(jvm_key, (void *)env);
}
void ThreadAndroid::make_default(JavaVM *p_java_vm) {
-
java_vm = p_java_vm;
create_func = create_func_jandroid;
get_thread_id_func = get_thread_id_func_jandroid;
@@ -137,18 +128,16 @@ void ThreadAndroid::make_default(JavaVM *p_java_vm) {
}
JNIEnv *ThreadAndroid::get_env() {
-
if (!pthread_getspecific(jvm_key)) {
setup_thread();
}
- JNIEnv *env = NULL;
- java_vm->AttachCurrentThread(&env, NULL);
+ JNIEnv *env = nullptr;
+ java_vm->AttachCurrentThread(&env, nullptr);
return env;
}
ThreadAndroid::ThreadAndroid() {
-
pthread = 0;
}
diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h
index eb4725ae68..9cfcc64813 100644
--- a/platform/android/thread_jandroid.h
+++ b/platform/android/thread_jandroid.h
@@ -37,7 +37,6 @@
#include <sys/types.h>
class ThreadAndroid : public Thread {
-
static pthread_key_t thread_id_key;
static ID next_thread_id;
diff --git a/platform/haiku/context_gl_haiku.cpp b/platform/android/vulkan/vulkan_context_android.cpp
index 3c4d43ff71..5fb7a83da4 100644
--- a/platform/haiku/context_gl_haiku.cpp
+++ b/platform/android/vulkan/vulkan_context_android.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* context_gl_haiku.cpp */
+/* vulkan_context_android.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,56 +28,33 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "context_gl_haiku.h"
+#include "vulkan_context_android.h"
+#include <vulkan/vulkan_android.h>
-#if defined(OPENGL_ENABLED)
-
-ContextGL_Haiku::ContextGL_Haiku(HaikuDirectWindow *p_window) {
- window = p_window;
-
- uint32 type = BGL_RGB | BGL_DOUBLE | BGL_DEPTH;
- view = new HaikuGLView(window->Bounds(), type);
-
- use_vsync = false;
-}
-
-ContextGL_Haiku::~ContextGL_Haiku() {
- delete view;
+const char *VulkanContextAndroid::_get_platform_surface_extension() const {
+ return VK_KHR_ANDROID_SURFACE_EXTENSION_NAME;
}
-Error ContextGL_Haiku::initialize() {
- window->AddChild(view);
- window->SetHaikuGLView(view);
-
- return OK;
-}
+int VulkanContextAndroid::window_create(ANativeWindow *p_window, int p_width, int p_height) {
+ VkAndroidSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = nullptr;
+ createInfo.flags = 0;
+ createInfo.window = p_window;
-void ContextGL_Haiku::release_current() {
- view->UnlockGL();
-}
+ VkSurfaceKHR surface;
+ VkResult err = vkCreateAndroidSurfaceKHR(_get_instance(), &createInfo, nullptr, &surface);
+ if (err != VK_SUCCESS) {
+ ERR_FAIL_V_MSG(-1, "vkCreateAndroidSurfaceKHR failed with error " + itos(err));
+ }
-void ContextGL_Haiku::make_current() {
- view->LockGL();
+ return _window_create(DisplayServer::MAIN_WINDOW_ID, surface, p_width, p_height);
}
-void ContextGL_Haiku::swap_buffers() {
- view->SwapBuffers(use_vsync);
+VulkanContextAndroid::VulkanContextAndroid() {
+ // TODO: fix validation layers
+ use_validation_layers = false;
}
-int ContextGL_Haiku::get_window_width() {
- return window->Bounds().IntegerWidth();
+VulkanContextAndroid::~VulkanContextAndroid() {
}
-
-int ContextGL_Haiku::get_window_height() {
- return window->Bounds().IntegerHeight();
-}
-
-void ContextGL_Haiku::set_use_vsync(bool p_use) {
- use_vsync = p_use;
-}
-
-bool ContextGL_Haiku::is_using_vsync() const {
- return use_vsync;
-}
-
-#endif
diff --git a/platform/iphone/power_iphone.h b/platform/android/vulkan/vulkan_context_android.h
index 47a4508509..6bd3cbee36 100644
--- a/platform/iphone/power_iphone.h
+++ b/platform/android/vulkan/vulkan_context_android.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* power_iphone.h */
+/* vulkan_context_android.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,26 +28,21 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef POWER_IPHONE_H
-#define POWER_IPHONE_H
+#ifndef VULKAN_CONTEXT_ANDROID_H
+#define VULKAN_CONTEXT_ANDROID_H
-#include <os/os.h>
+#include "drivers/vulkan/vulkan_context.h"
-class PowerIphone {
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
+struct ANativeWindow;
- bool UpdatePowerInfo();
+class VulkanContextAndroid : public VulkanContext {
+ virtual const char *_get_platform_surface_extension() const;
public:
- PowerIphone();
- virtual ~PowerIphone();
+ int window_create(ANativeWindow *p_window, int p_width, int p_height);
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
+ VulkanContextAndroid();
+ ~VulkanContextAndroid();
};
-#endif // POWER_IPHONE_H
+#endif // VULKAN_CONTEXT_ANDROID_H
diff --git a/platform/haiku/SCsub b/platform/haiku/SCsub
deleted file mode 100644
index 592f56bbbf..0000000000
--- a/platform/haiku/SCsub
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env python
-
-Import('env')
-
-common_haiku = [
- 'os_haiku.cpp',
- 'context_gl_haiku.cpp',
- 'haiku_application.cpp',
- 'haiku_direct_window.cpp',
- 'haiku_gl_view.cpp',
- 'key_mapping_haiku.cpp',
- 'audio_driver_media_kit.cpp'
-]
-
-target = env.add_program(
- '#bin/godot',
- ['godot_haiku.cpp'] + common_haiku
-)
-
-command = env.Command('#bin/godot.rsrc', '#platform/haiku/godot.rdef',
- ['rc -o $TARGET $SOURCE'])
-
-
-def addResourcesAction(target=None, source=None, env=None):
- return env.Execute('xres -o ' + File(target)[0].path + ' bin/godot.rsrc')
-
-env.AddPostAction(target, addResourcesAction)
-env.Depends(target, command)
diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp
deleted file mode 100644
index b7f6b57244..0000000000
--- a/platform/haiku/audio_driver_media_kit.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/*************************************************************************/
-/* audio_driver_media_kit.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "audio_driver_media_kit.h"
-
-#ifdef MEDIA_KIT_ENABLED
-
-#include "core/project_settings.h"
-
-int32_t *AudioDriverMediaKit::samples_in = NULL;
-
-Error AudioDriverMediaKit::init() {
- active = false;
-
- mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE);
- speaker_mode = SPEAKER_MODE_STEREO;
- channels = 2;
-
- int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY);
- buffer_size = next_power_of_2(latency * mix_rate / 1000);
- samples_in = memnew_arr(int32_t, buffer_size * channels);
-
- media_raw_audio_format format;
- format = media_raw_audio_format::wildcard;
- format.frame_rate = mix_rate;
- format.channel_count = channels;
- format.format = media_raw_audio_format::B_AUDIO_INT;
- format.byte_order = B_MEDIA_LITTLE_ENDIAN;
- format.buffer_size = buffer_size * sizeof(int32_t) * channels;
-
- player = new BSoundPlayer(
- &format,
- "godot_sound_server",
- AudioDriverMediaKit::PlayBuffer,
- NULL,
- this);
-
- if (player->InitCheck() != B_OK) {
- fprintf(stderr, "MediaKit ERR: can not create a BSoundPlayer instance\n");
- ERR_FAIL_COND_V(player == NULL, ERR_CANT_OPEN);
- }
-
- mutex = Mutex::create();
- player->Start();
-
- return OK;
-}
-
-void AudioDriverMediaKit::PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format) {
- AudioDriverMediaKit *ad = (AudioDriverMediaKit *)cookie;
- int32_t *buf = (int32_t *)buffer;
-
- if (!ad->active) {
- for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) {
- AudioDriverMediaKit::samples_in[i] = 0;
- }
- } else {
- ad->lock();
- ad->audio_server_process(ad->buffer_size, AudioDriverMediaKit::samples_in);
- ad->unlock();
- }
-
- for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) {
- buf[i] = AudioDriverMediaKit::samples_in[i];
- }
-}
-
-void AudioDriverMediaKit::start() {
- active = true;
-}
-
-int AudioDriverMediaKit::get_mix_rate() const {
- return mix_rate;
-}
-
-AudioDriverMediaKit::SpeakerMode AudioDriverMediaKit::get_speaker_mode() const {
- return speaker_mode;
-}
-
-void AudioDriverMediaKit::lock() {
- if (!mutex)
- return;
-
- mutex->lock();
-}
-
-void AudioDriverMediaKit::unlock() {
- if (!mutex)
- return;
-
- mutex->unlock();
-}
-
-void AudioDriverMediaKit::finish() {
- delete player;
-
- if (samples_in) {
- memdelete_arr(samples_in);
- };
-
- if (mutex) {
- memdelete(mutex);
- mutex = NULL;
- }
-}
-
-AudioDriverMediaKit::AudioDriverMediaKit() {
- mutex = NULL;
- player = NULL;
-}
-
-AudioDriverMediaKit::~AudioDriverMediaKit() {
-}
-
-#endif
diff --git a/platform/haiku/detect.py b/platform/haiku/detect.py
deleted file mode 100644
index dd72294816..0000000000
--- a/platform/haiku/detect.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import os
-import sys
-
-
-def is_active():
- return True
-
-
-def get_name():
- return "Haiku"
-
-
-def can_build():
-
- if (os.name != "posix" or sys.platform == "darwin"):
- return False
-
- return True
-
-
-def get_opts():
- from SCons.Variables import EnumVariable
-
- return [
- EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')),
- ]
-
-
-def get_flags():
-
- return [
- ]
-
-
-def configure(env):
-
- ## Build type
-
- if (env["target"] == "release"):
- env.Prepend(CCFLAGS=['-O3'])
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "release_debug"):
- env.Prepend(CCFLAGS=['-O2', '-DDEBUG_ENABLED'])
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "debug"):
- env.Prepend(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED'])
-
- ## Architecture
-
- is64 = sys.maxsize > 2**32
- if (env["bits"] == "default"):
- env["bits"] = "64" if is64 else "32"
-
- ## Compiler configuration
-
- env["CC"] = "gcc-x86"
- env["CXX"] = "g++-x86"
-
- ## Dependencies
-
- if not env['builtin_libwebp']:
- env.ParseConfig('pkg-config libwebp --cflags --libs')
-
- # freetype depends on libpng and zlib, so bundling one of them while keeping others
- # as shared libraries leads to weird issues
- if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']:
- env['builtin_freetype'] = True
- env['builtin_libpng'] = True
- env['builtin_zlib'] = True
-
- if not env['builtin_freetype']:
- env.ParseConfig('pkg-config freetype2 --cflags --libs')
-
- if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng16 --cflags --libs')
-
- if not env['builtin_bullet']:
- # We need at least version 2.88
- import subprocess
- bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip()
- if bullet_version < "2.88":
- # Abort as system bullet was requested but too old
- print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88"))
- sys.exit(255)
- env.ParseConfig('pkg-config bullet --cflags --libs')
-
- if not env['builtin_enet']:
- env.ParseConfig('pkg-config libenet --cflags --libs')
-
- if not env['builtin_squish']:
- env.ParseConfig('pkg-config libsquish --cflags --libs')
-
- if not env['builtin_zstd']:
- env.ParseConfig('pkg-config libzstd --cflags --libs')
-
- # Sound and video libraries
- # Keep the order as it triggers chained dependencies (ogg needed by others, etc.)
-
- if not env['builtin_libtheora']:
- env['builtin_libogg'] = False # Needed to link against system libtheora
- env['builtin_libvorbis'] = False # Needed to link against system libtheora
- env.ParseConfig('pkg-config theora theoradec --cflags --libs')
-
- if not env['builtin_libvpx']:
- env.ParseConfig('pkg-config vpx --cflags --libs')
-
- if not env['builtin_libvorbis']:
- env['builtin_libogg'] = False # Needed to link against system libvorbis
- env.ParseConfig('pkg-config vorbis vorbisfile --cflags --libs')
-
- if not env['builtin_opus']:
- env['builtin_libogg'] = False # Needed to link against system opus
- env.ParseConfig('pkg-config opus opusfile --cflags --libs')
-
- if not env['builtin_libogg']:
- env.ParseConfig('pkg-config ogg --cflags --libs')
-
- if env['builtin_libtheora']:
- list_of_x86 = ['x86_64', 'x86', 'i386', 'i586']
- if any(platform.machine() in s for s in list_of_x86):
- env["x86_libtheora_opt_gcc"] = True
-
- if not env['builtin_wslay']:
- env.ParseConfig('pkg-config libwslay --cflags --libs')
-
- if not env['builtin_mbedtls']:
- # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228
- env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509'])
-
- if not env['builtin_miniupnpc']:
- # No pkgconfig file so far, hardcode default paths.
- env.Prepend(CPPPATH=["/system/develop/headers/x86/miniupnpc"])
- env.Append(LIBS=["miniupnpc"])
-
- # On Linux wchar_t should be 32-bits
- # 16-bit library shouldn't be required due to compiler optimisations
- if not env['builtin_pcre2']:
- env.ParseConfig('pkg-config libpcre2-32 --cflags --libs')
-
- ## Flags
-
- env.Prepend(CPPPATH=['#platform/haiku'])
- env.Append(CPPDEFINES=['UNIX_ENABLED', 'OPENGL_ENABLED', 'GLES_ENABLED'])
- env.Append(CPPDEFINES=['MEDIA_KIT_ENABLED'])
- env.Append(CPPDEFINES=['PTHREAD_NO_RENAME']) # TODO: enable when we have pthread_setname_np
- env.Append(LIBS=['be', 'game', 'media', 'network', 'bnetapi', 'z', 'GL'])
diff --git a/platform/haiku/godot.rdef b/platform/haiku/godot.rdef
deleted file mode 100644
index a55cddbf0d..0000000000
--- a/platform/haiku/godot.rdef
+++ /dev/null
@@ -1,60 +0,0 @@
-resource app_version {
- major = 2,
- middle = 0,
- minor = 0,
-
- variety = B_APPV_FINAL,
- internal = 0,
-
- short_info = "Godot Game Engine",
- long_info = "An advanced, feature packed, multi-platform 2D and 3D game engine."
-};
-
-resource app_signature "application/x-vnd.godot";
-
-resource vector_icon {
- $"6E6369660403A39F9F05FF03478CBF03414042090A04B37FB379CC26B379CC26"
- $"CC20B37FCC200A09B5E9C41B2AC240B8E1BDFBBFA1BDA4C6A7BDFFCA1AC45CC9"
- $"7AC607C01CC75BB6F4C65A062AFE9FFF9F69FE7FFEDFCF0FC95FC3D7C95FC51E"
- $"C95FC51EC95FC53EC92BC565C94AC55BC92BC565C728C60BC728C60BC712C612"
- $"C6E6C600C6F9C60EC6D3C5F2C6C7C5C4C6C7C5DCC6C7C5C4C460C4E5C4BCC4E5"
- $"C626C4E5C626C4E5C64BC4A5C670C4CAC66BC4A5C670C1EDC6CFC1EDC6CFC1E9"
- $"C6CFC1E2C6D0C1E6C6D0C1D1C6D0C1B2C6BEC1BFC6C9C1A2C6AFC19851C198C6"
- $"9BC19851C505C031C507C507C016C507BFFCC507C507BE94C505BE9451BE9451"
- $"BE94C69BBE7BC6BEBE8BC6AFBE6DC6C9BE4AC6D0BE5CC6D0BE47C6D0BE40C6CF"
- $"BE44C6CFBE40C6CFBB87C670BB87C670BB63C66BBB47C626BB47C64BBB47C626"
- $"C4BCB965C460B965C5C4B965C5C4B965C5DCB947C600B95AC5F2B934C60EB904"
- $"C60BB91BC612B904C60BB701C565B701C565B6E3C55BB6CEC51EB6CEC53EB6CE"
- $"C51EC3D7B590C36CB590C36CB581C3B0B578C43AB578C3F5B578C78FBFF8CA27"
- $"BA2ACA22BFF8CA27BFFABFFCCA27BFFCCA27C5CACA22CA7CC43ACA7CC78FCA7C"
- $"C3FBCA67C37ECA754ACA67C37E0639F6F97FFEF8E7FFF9F6FFFFFFFFFF03B67D"
- $"BDEEC31FB730C35CB730C35CB74EC3662BC3A22BC3822BC3A2C4E8B8D1C55EB8"
- $"D1C406B8D1C406B8D1C3F0B8ECC3CDB8DBC3DBB8FDC3BFB929C3BDB913C3B9B9"
- $"29C3BDBB9FC436BB9FC436BBC2C43CBBDCC47EBBDCC45BBBDCC47EC5E6BE00C6"
- $"31BE00C4BBBE00C4BBBE00C4A7BE16C486BE08C494BE24C479BE4AC471BE37C4"
- $"71BE4AC471BE4BC016C473C1E2C471C1E2C471C1F6C471C217C486C209C479C2"
- $"25C494C22DC4BBC22DC4A7C22DC4BBC631C451C5E6C451C47EC451C47EC451C4"
- $"5BC48DC436C46AC43CC48DC436C704C3BDC704C3BDC719C3B9C741C3CDC730C3"
- $"BF53C3DBC75CC406C75CC3F0C75CC406C55EC8CAC4E8C8CAC3A2C8CAC3A2C8CA"
- $"C382C8FDC35CC8DFC366C8FDC35CC977C333BDEEC97ABDEEC97ABDEEC9F1BD56"
- $"CAC9BC0BCA60BCB6CA3DBB1CC8D9B981C991BA47C82FB9D7C6EDBAA0C789BA38"
- $"C69FBA52C5F0B9D0C647BA12C59BB98BC4E0B91FC53BB959C4FBB855C50EB6C0"
- $"C509B78FC424B64AC22DB5C4C32AB5FCC1C8B66DC11BB7D9C16BB725C0BCB7C9"
- $"BFFCB7C2C05CB7C3BFFCB7C2BFFCB7C2BFFCB7C2BFFBB7C2BFFAB7C2BFFAB7C2"
- $"BFF9B7C2BFF8B7C2BFF9B7C2BFF8B7C2BFF8B7C2BFF8B7C2BF98B7C3BED9B7D9"
- $"BF38B7C9BE88B725BDC7B5C4BE2CB66DBCCAB5FCBAE6B6C0BBD0B64ABAEBB78F"
- $"BB13B91F34B855BAB8B959BA04B9D0BA59B98BB9ADBA12B907BAA0B955BA52B8"
- $"6ABA38B71AB981B7C5B9D7B663BA47B52BBC0BB5B7BB1CB594BCB6B679BDEEB6"
- $"02BD56B679BDEE0005BD3EC06CBD3EC06CBD3EC197BB2147BC4C47B9F647B904"
- $"C06CB904C197B904BF41BB21BE4FB9F6BE4FBC4CBE4FBD3EC06CBD3EBF41BD3E"
- $"C06C0005BCBC42BCBC42BCBCC153BB55C1F3BC1BC1F3BA8EC1F3B9ED42B9EDC1"
- $"53B9EDBFC6BB55BF25BA8EBF25BC1BBF25BCBC42BCBCBFC6BCBC420007C01BC2"
- $"BBC01BC2BBBFBAC2BBBF6CC21CBF6CC274BF6CC21CBF6CC02ABF6CC02ABF6CBF"
- $"D3C01BBF8CBFBABF8CC07BBF8CC0C9C02AC0C9BFD3C0C9C02AC0C9C21CC0C9C2"
- $"1CC0C9C274C01BC2BBC07BC2BBC01BC2BB0005C2F7C06CC2F7C06CC2F7C197C5"
- $"1547C3E947C64047C732C06CC732C197C732BF41C515BE4FC640BE4FC3E9BE4F"
- $"C2F7C06CC2F7BF41C2F7C06C0005C37942C37942C379C153C4E1C1F3C41AC1F3"
- $"C5A7C1F3C64842C648C153C648BFC6C4E1BF25C5A7BF25C41ABF25C37942C379"
- $"BFC6C37942090A0000000A010101000A020102000A020103000A010104000A03"
- $"0105000A010106000A010107000A03010800"
-};
diff --git a/platform/haiku/haiku_application.cpp b/platform/haiku/haiku_application.cpp
deleted file mode 100644
index 82d9c093e1..0000000000
--- a/platform/haiku/haiku_application.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*************************************************************************/
-/* haiku_application.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "haiku_application.h"
-
-HaikuApplication::HaikuApplication() :
- BApplication("application/x-vnd.godot") {
-}
diff --git a/platform/haiku/haiku_direct_window.cpp b/platform/haiku/haiku_direct_window.cpp
deleted file mode 100644
index 3c2b7f8d10..0000000000
--- a/platform/haiku/haiku_direct_window.cpp
+++ /dev/null
@@ -1,361 +0,0 @@
-/*************************************************************************/
-/* haiku_direct_window.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include <UnicodeChar.h>
-
-#include "core/os/keyboard.h"
-#include "haiku_direct_window.h"
-#include "key_mapping_haiku.h"
-#include "main/main.h"
-
-HaikuDirectWindow::HaikuDirectWindow(BRect p_frame) :
- BDirectWindow(p_frame, "Godot", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE) {
- last_mouse_pos_valid = false;
- last_buttons_state = 0;
- last_button_mask = 0;
- last_key_modifier_state = 0;
-
- view = NULL;
- update_runner = NULL;
- input = NULL;
- main_loop = NULL;
-}
-
-HaikuDirectWindow::~HaikuDirectWindow() {
-}
-
-void HaikuDirectWindow::SetHaikuGLView(HaikuGLView *p_view) {
- view = p_view;
-}
-
-void HaikuDirectWindow::StartMessageRunner() {
- update_runner = new BMessageRunner(BMessenger(this),
- new BMessage(REDRAW_MSG), 1000000 / 60 /* 60 fps */);
-}
-
-void HaikuDirectWindow::StopMessageRunner() {
- delete update_runner;
-}
-
-void HaikuDirectWindow::SetInput(InputDefault *p_input) {
- input = p_input;
-}
-
-void HaikuDirectWindow::SetMainLoop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-}
-
-bool HaikuDirectWindow::QuitRequested() {
- StopMessageRunner();
- main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
- return false;
-}
-
-void HaikuDirectWindow::DirectConnected(direct_buffer_info *info) {
- view->DirectConnected(info);
- view->EnableDirectMode(true);
-}
-
-void HaikuDirectWindow::MessageReceived(BMessage *message) {
- switch (message->what) {
- case REDRAW_MSG:
- if (Main::iteration()) {
- view->EnableDirectMode(false);
- Quit();
- }
- break;
-
- default:
- BDirectWindow::MessageReceived(message);
- }
-}
-
-void HaikuDirectWindow::DispatchMessage(BMessage *message, BHandler *handler) {
- switch (message->what) {
- case B_MOUSE_DOWN:
- case B_MOUSE_UP:
- HandleMouseButton(message);
- break;
-
- case B_MOUSE_MOVED:
- HandleMouseMoved(message);
- break;
-
- case B_MOUSE_WHEEL_CHANGED:
- HandleMouseWheelChanged(message);
- break;
-
- case B_KEY_DOWN:
- case B_KEY_UP:
- HandleKeyboardEvent(message);
- break;
-
- case B_MODIFIERS_CHANGED:
- HandleKeyboardModifierEvent(message);
- break;
-
- case B_WINDOW_RESIZED:
- HandleWindowResized(message);
- break;
-
- case LOCKGL_MSG:
- view->LockGL();
- break;
-
- case UNLOCKGL_MSG:
- view->UnlockGL();
- break;
-
- default:
- BDirectWindow::DispatchMessage(message, handler);
- }
-}
-
-void HaikuDirectWindow::HandleMouseButton(BMessage *message) {
- BPoint where;
- if (message->FindPoint("where", &where) != B_OK) {
- return;
- }
-
- uint32 modifiers = message->FindInt32("modifiers");
- uint32 buttons = message->FindInt32("buttons");
- uint32 button = buttons ^ last_buttons_state;
- last_buttons_state = buttons;
-
- // TODO: implement the mouse_mode checks
- /*
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- event.xbutton.x=last_mouse_pos.x;
- event.xbutton.y=last_mouse_pos.y;
- }
- */
-
- Ref<InputEventMouseButton> mouse_event;
- mouse_event.instance();
-
- mouse_event->set_button_mask(GetMouseButtonState(buttons));
- mouse_event->set_position({ where.x, where.y });
- mouse_event->set_global_position({ where.x, where.y });
- GetKeyModifierState(mouse_event, modifiers);
-
- switch (button) {
- default:
- case B_PRIMARY_MOUSE_BUTTON:
- mouse_event->set_button_index(1);
- break;
-
- case B_SECONDARY_MOUSE_BUTTON:
- mouse_event->set_button_index(2);
- break;
-
- case B_TERTIARY_MOUSE_BUTTON:
- mouse_event->set_button_index(3);
- break;
- }
-
- mouse_event->set_pressed(message->what == B_MOUSE_DOWN);
-
- if (message->what == B_MOUSE_DOWN && mouse_event->get_button_index() == 1) {
- int32 clicks = message->FindInt32("clicks");
-
- if (clicks > 1) {
- mouse_event->set_doubleclick(true);
- }
- }
-
- input->parse_input_event(mouse_event);
-}
-
-void HaikuDirectWindow::HandleMouseMoved(BMessage *message) {
- BPoint where;
- if (message->FindPoint("where", &where) != B_OK) {
- return;
- }
-
- Point2i pos(where.x, where.y);
- uint32 modifiers = message->FindInt32("modifiers");
- uint32 buttons = message->FindInt32("buttons");
-
- if (!last_mouse_pos_valid) {
- last_mouse_position = pos;
- last_mouse_pos_valid = true;
- }
-
- Point2i rel = pos - last_mouse_position;
-
- Ref<InputEventMouseMotion> motion_event;
- motion_event.instance();
- GetKeyModifierState(motion_event, modifiers);
-
- motion_event->set_button_mask(GetMouseButtonState(buttons));
- motion_event->set_position({ pos.x, pos.y });
- input->set_mouse_position(pos);
- motion_event->set_global_position({ pos.x, pos.y });
- motion_event->set_speed({ input->get_last_mouse_speed().x,
- input->get_last_mouse_speed().y });
-
- motion_event->set_relative({ rel.x, rel.y });
-
- last_mouse_position = pos;
-
- input->parse_input_event(motion_event);
-}
-
-void HaikuDirectWindow::HandleMouseWheelChanged(BMessage *message) {
- float wheel_delta_y = 0;
- if (message->FindFloat("be:wheel_delta_y", &wheel_delta_y) != B_OK) {
- return;
- }
-
- Ref<InputEventMouseButton> mouse_event;
- mouse_event.instance();
- //GetKeyModifierState(mouse_event, modifiers);
-
- mouse_event->set_button_index(wheel_delta_y < 0 ? 4 : 5);
- mouse_event->set_button_mask(last_button_mask);
- mouse_event->set_position({ last_mouse_position.x,
- last_mouse_position.y });
- mouse_event->set_global_position({ last_mouse_position.x,
- last_mouse_position.y });
-
- mouse_event->set_pressed(true);
- input->parse_input_event(mouse_event);
-
- mouse_event->set_pressed(false);
- input->parse_input_event(mouse_event);
-}
-
-void HaikuDirectWindow::HandleKeyboardEvent(BMessage *message) {
- int32 raw_char = 0;
- int32 key = 0;
- int32 modifiers = 0;
-
- if (message->FindInt32("raw_char", &raw_char) != B_OK) {
- return;
- }
-
- if (message->FindInt32("key", &key) != B_OK) {
- return;
- }
-
- if (message->FindInt32("modifiers", &modifiers) != B_OK) {
- return;
- }
-
- Ref<InputEventKey> event;
- event.instance();
- GetKeyModifierState(event, modifiers);
- event->set_pressed(message->what == B_KEY_DOWN);
- event->set_scancode(KeyMappingHaiku::get_keysym(raw_char, key));
- event->set_echo(message->HasInt32("be:key_repeat"));
- event->set_unicode(0);
-
- const char *bytes = NULL;
- if (message->FindString("bytes", &bytes) == B_OK) {
- event->set_unicode(BUnicodeChar::FromUTF8(&bytes));
- }
-
- //make it consistent across platforms.
- if (event->get_scancode() == KEY_BACKTAB) {
- event->set_scancode(KEY_TAB);
- event->set_shift(true);
- }
-
- input->parse_input_event(event);
-}
-
-void HaikuDirectWindow::HandleKeyboardModifierEvent(BMessage *message) {
- int32 old_modifiers = 0;
- int32 modifiers = 0;
-
- if (message->FindInt32("be:old_modifiers", &old_modifiers) != B_OK) {
- return;
- }
-
- if (message->FindInt32("modifiers", &modifiers) != B_OK) {
- return;
- }
-
- int32 key = old_modifiers ^ modifiers;
-
- Ref<InputEventWithModifiers> event;
- event.instance();
- GetKeyModifierState(event, modifiers);
-
- event->set_shift(key & B_SHIFT_KEY);
- event->set_alt(key & B_OPTION_KEY);
- event->set_control(key & B_CONTROL_KEY);
- event->set_command(key & B_COMMAND_KEY);
-
- input->parse_input_event(event);
-}
-
-void HaikuDirectWindow::HandleWindowResized(BMessage *message) {
- int32 width = 0;
- int32 height = 0;
-
- if ((message->FindInt32("width", &width) != B_OK) || (message->FindInt32("height", &height) != B_OK)) {
- return;
- }
-
- current_video_mode->width = width;
- current_video_mode->height = height;
-}
-
-inline void HaikuDirectWindow::GetKeyModifierState(Ref<InputEventWithModifiers> event, uint32 p_state) {
- last_key_modifier_state = p_state;
-
- event->set_shift(p_state & B_SHIFT_KEY);
- event->set_control(p_state & B_CONTROL_KEY);
- event->set_alt(p_state & B_OPTION_KEY);
- event->set_metakey(p_state & B_COMMAND_KEY);
-
- return state;
-}
-
-inline int HaikuDirectWindow::GetMouseButtonState(uint32 p_state) {
- int state = 0;
-
- if (p_state & B_PRIMARY_MOUSE_BUTTON) {
- state |= 1 << 0;
- }
-
- if (p_state & B_SECONDARY_MOUSE_BUTTON) {
- state |= 1 << 1;
- }
-
- if (p_state & B_TERTIARY_MOUSE_BUTTON) {
- state |= 1 << 2;
- }
-
- last_button_mask = state;
-
- return state;
-}
diff --git a/platform/haiku/haiku_direct_window.h b/platform/haiku/haiku_direct_window.h
deleted file mode 100644
index ccc9542f0e..0000000000
--- a/platform/haiku/haiku_direct_window.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*************************************************************************/
-/* haiku_direct_window.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef HAIKU_DIRECT_WINDOW_H
-#define HAIKU_DIRECT_WINDOW_H
-
-#include <kernel/image.h> // needed for image_id
-
-#include <DirectWindow.h>
-
-#include "core/os/os.h"
-#include "main/input_default.h"
-
-#include "haiku_gl_view.h"
-
-#define REDRAW_MSG 'rdrw'
-#define LOCKGL_MSG 'glck'
-#define UNLOCKGL_MSG 'ulck'
-
-class HaikuDirectWindow : public BDirectWindow {
-private:
- Point2i last_mouse_position;
- bool last_mouse_pos_valid;
- uint32 last_buttons_state;
- uint32 last_key_modifier_state;
- int last_button_mask;
- OS::VideoMode *current_video_mode;
-
- MainLoop *main_loop;
- InputDefault *input;
- HaikuGLView *view;
- BMessageRunner *update_runner;
-
- void HandleMouseButton(BMessage *message);
- void HandleMouseMoved(BMessage *message);
- void HandleMouseWheelChanged(BMessage *message);
- void HandleWindowResized(BMessage *message);
- void HandleKeyboardEvent(BMessage *message);
- void HandleKeyboardModifierEvent(BMessage *message);
- inline void GetKeyModifierState(Ref<InputEventWithModifiers> event, uint32 p_state);
- inline int GetMouseButtonState(uint32 p_state);
-
-public:
- HaikuDirectWindow(BRect p_frame);
- ~HaikuDirectWindow();
-
- void SetHaikuGLView(HaikuGLView *p_view);
- void StartMessageRunner();
- void StopMessageRunner();
- void SetInput(InputDefault *p_input);
- void SetMainLoop(MainLoop *p_main_loop);
- inline void SetVideoMode(OS::VideoMode *video_mode) { current_video_mode = video_mode; };
- virtual bool QuitRequested();
- virtual void DirectConnected(direct_buffer_info *info);
- virtual void MessageReceived(BMessage *message);
- virtual void DispatchMessage(BMessage *message, BHandler *handler);
-
- inline Point2i GetLastMousePosition() { return last_mouse_position; };
- inline int GetLastButtonMask() { return last_button_mask; };
-};
-
-#endif
diff --git a/platform/haiku/key_mapping_haiku.cpp b/platform/haiku/key_mapping_haiku.cpp
deleted file mode 100644
index 692a1e5a78..0000000000
--- a/platform/haiku/key_mapping_haiku.cpp
+++ /dev/null
@@ -1,249 +0,0 @@
-/*************************************************************************/
-/* key_mapping_haiku.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include <InterfaceDefs.h>
-
-#include "core/os/keyboard.h"
-#include "key_mapping_haiku.h"
-
-struct _HaikuTranslatePair {
- unsigned int keysym;
- int32 keycode;
-};
-
-static _HaikuTranslatePair _mod_to_keycode[] = {
- { KEY_SHIFT, B_SHIFT_KEY },
- { KEY_ALT, B_COMMAND_KEY },
- { KEY_CONTROL, B_CONTROL_KEY },
- { KEY_CAPSLOCK, B_CAPS_LOCK },
- { KEY_SCROLLLOCK, B_SCROLL_LOCK },
- { KEY_NUMLOCK, B_NUM_LOCK },
- { KEY_SUPER_L, B_OPTION_KEY },
- { KEY_MENU, B_MENU_KEY },
- { KEY_SHIFT, B_LEFT_SHIFT_KEY },
- { KEY_SHIFT, B_RIGHT_SHIFT_KEY },
- { KEY_ALT, B_LEFT_COMMAND_KEY },
- { KEY_ALT, B_RIGHT_COMMAND_KEY },
- { KEY_CONTROL, B_LEFT_CONTROL_KEY },
- { KEY_CONTROL, B_RIGHT_CONTROL_KEY },
- { KEY_SUPER_L, B_LEFT_OPTION_KEY },
- { KEY_SUPER_R, B_RIGHT_OPTION_KEY },
- { KEY_UNKNOWN, 0 }
-};
-
-static _HaikuTranslatePair _fn_to_keycode[] = {
- { KEY_F1, B_F1_KEY },
- { KEY_F2, B_F2_KEY },
- { KEY_F3, B_F3_KEY },
- { KEY_F4, B_F4_KEY },
- { KEY_F5, B_F5_KEY },
- { KEY_F6, B_F6_KEY },
- { KEY_F7, B_F7_KEY },
- { KEY_F8, B_F8_KEY },
- { KEY_F9, B_F9_KEY },
- { KEY_F10, B_F10_KEY },
- { KEY_F11, B_F11_KEY },
- { KEY_F12, B_F12_KEY },
- //{ KEY_F13, ? },
- //{ KEY_F14, ? },
- //{ KEY_F15, ? },
- //{ KEY_F16, ? },
- { KEY_PRINT, B_PRINT_KEY },
- { KEY_SCROLLLOCK, B_SCROLL_KEY },
- { KEY_PAUSE, B_PAUSE_KEY },
- { KEY_UNKNOWN, 0 }
-};
-
-static _HaikuTranslatePair _hb_to_keycode[] = {
- { KEY_BACKSPACE, B_BACKSPACE },
- { KEY_TAB, B_TAB },
- { KEY_ENTER, B_RETURN },
- { KEY_CAPSLOCK, B_CAPS_LOCK },
- { KEY_ESCAPE, B_ESCAPE },
- { KEY_SPACE, B_SPACE },
- { KEY_PAGEUP, B_PAGE_UP },
- { KEY_PAGEDOWN, B_PAGE_DOWN },
- { KEY_END, B_END },
- { KEY_HOME, B_HOME },
- { KEY_LEFT, B_LEFT_ARROW },
- { KEY_UP, B_UP_ARROW },
- { KEY_RIGHT, B_RIGHT_ARROW },
- { KEY_DOWN, B_DOWN_ARROW },
- { KEY_PRINT, B_PRINT_KEY },
- { KEY_INSERT, B_INSERT },
- { KEY_DELETE, B_DELETE },
- // { KEY_HELP, ??? },
-
- { KEY_0, (0x30) },
- { KEY_1, (0x31) },
- { KEY_2, (0x32) },
- { KEY_3, (0x33) },
- { KEY_4, (0x34) },
- { KEY_5, (0x35) },
- { KEY_6, (0x36) },
- { KEY_7, (0x37) },
- { KEY_8, (0x38) },
- { KEY_9, (0x39) },
- { KEY_A, (0x61) },
- { KEY_B, (0x62) },
- { KEY_C, (0x63) },
- { KEY_D, (0x64) },
- { KEY_E, (0x65) },
- { KEY_F, (0x66) },
- { KEY_G, (0x67) },
- { KEY_H, (0x68) },
- { KEY_I, (0x69) },
- { KEY_J, (0x6A) },
- { KEY_K, (0x6B) },
- { KEY_L, (0x6C) },
- { KEY_M, (0x6D) },
- { KEY_N, (0x6E) },
- { KEY_O, (0x6F) },
- { KEY_P, (0x70) },
- { KEY_Q, (0x71) },
- { KEY_R, (0x72) },
- { KEY_S, (0x73) },
- { KEY_T, (0x74) },
- { KEY_U, (0x75) },
- { KEY_V, (0x76) },
- { KEY_W, (0x77) },
- { KEY_X, (0x78) },
- { KEY_Y, (0x79) },
- { KEY_Z, (0x7A) },
-
- /*
-{ KEY_PLAY, VK_PLAY},// (0xFA)
-{ KEY_STANDBY,VK_SLEEP },//(0x5F)
-{ KEY_BACK,VK_BROWSER_BACK},// (0xA6)
-{ KEY_FORWARD,VK_BROWSER_FORWARD},// (0xA7)
-{ KEY_REFRESH,VK_BROWSER_REFRESH},// (0xA8)
-{ KEY_STOP,VK_BROWSER_STOP},// (0xA9)
-{ KEY_SEARCH,VK_BROWSER_SEARCH},// (0xAA)
-{ KEY_FAVORITES, VK_BROWSER_FAVORITES},// (0xAB)
-{ KEY_HOMEPAGE,VK_BROWSER_HOME},// (0xAC)
-{ KEY_VOLUMEMUTE,VK_VOLUME_MUTE},// (0xAD)
-{ KEY_VOLUMEDOWN,VK_VOLUME_DOWN},// (0xAE)
-{ KEY_VOLUMEUP,VK_VOLUME_UP},// (0xAF)
-{ KEY_MEDIANEXT,VK_MEDIA_NEXT_TRACK},// (0xB0)
-{ KEY_MEDIAPREVIOUS,VK_MEDIA_PREV_TRACK},// (0xB1)
-{ KEY_MEDIASTOP,VK_MEDIA_STOP},// (0xB2)
-{ KEY_LAUNCHMAIL, VK_LAUNCH_MAIL},// (0xB4)
-{ KEY_LAUNCHMEDIA,VK_LAUNCH_MEDIA_SELECT},// (0xB5)
-{ KEY_LAUNCH0,VK_LAUNCH_APP1},// (0xB6)
-{ KEY_LAUNCH1,VK_LAUNCH_APP2},// (0xB7)
-*/
-
- { KEY_SEMICOLON, 0x3B },
- { KEY_EQUAL, 0x3D },
- { KEY_COLON, 0x2C },
- { KEY_MINUS, 0x2D },
- { KEY_PERIOD, 0x2E },
- { KEY_SLASH, 0x2F },
- { KEY_KP_MULTIPLY, 0x2A },
- { KEY_KP_ADD, 0x2B },
-
- { KEY_QUOTELEFT, 0x60 },
- { KEY_BRACKETLEFT, 0x5B },
- { KEY_BACKSLASH, 0x5C },
- { KEY_BRACKETRIGHT, 0x5D },
- { KEY_APOSTROPHE, 0x27 },
-
- { KEY_UNKNOWN, 0 }
-};
-
-unsigned int KeyMappingHaiku::get_keysym(int32 raw_char, int32 key) {
- if (raw_char == B_INSERT && key == 0x64) {
- return KEY_KP_0;
- }
- if (raw_char == B_END && key == 0x58) {
- return KEY_KP_1;
- }
- if (raw_char == B_DOWN_ARROW && key == 0x59) {
- return KEY_KP_2;
- }
- if (raw_char == B_PAGE_DOWN && key == 0x5A) {
- return KEY_KP_3;
- }
- if (raw_char == B_LEFT_ARROW && key == 0x48) {
- return KEY_KP_4;
- }
- if (raw_char == 0x35 && key == 0x49) {
- return KEY_KP_5;
- }
- if (raw_char == B_RIGHT_ARROW && key == 0x4A) {
- return KEY_KP_6;
- }
- if (raw_char == B_HOME && key == 0x37) {
- return KEY_KP_7;
- }
- if (raw_char == B_UP_ARROW && key == 0x38) {
- return KEY_KP_8;
- }
- if (raw_char == B_PAGE_UP && key == 0x39) {
- return KEY_KP_9;
- }
- if (raw_char == 0x2F && key == 0x23) {
- return KEY_KP_DIVIDE;
- }
- if (raw_char == 0x2D && key == 0x25) {
- return KEY_KP_SUBTRACT;
- }
- if (raw_char == B_DELETE && key == 0x65) {
- return KEY_KP_PERIOD;
- }
-
- if (raw_char == 0x10) {
- for (int i = 0; _fn_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
- if (_fn_to_keycode[i].keycode == key) {
- return _fn_to_keycode[i].keysym;
- }
- }
-
- return KEY_UNKNOWN;
- }
-
- for (int i = 0; _hb_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
- if (_hb_to_keycode[i].keycode == raw_char) {
- return _hb_to_keycode[i].keysym;
- }
- }
-
- return KEY_UNKNOWN;
-}
-
-unsigned int KeyMappingHaiku::get_modifier_keysym(int32 key) {
- for (int i = 0; _mod_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
- if ((_mod_to_keycode[i].keycode & key) != 0) {
- return _mod_to_keycode[i].keysym;
- }
- }
-
- return KEY_UNKNOWN;
-}
diff --git a/platform/haiku/logo.png b/platform/haiku/logo.png
deleted file mode 100644
index a2d8e242a6..0000000000
--- a/platform/haiku/logo.png
+++ /dev/null
Binary files differ
diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp
deleted file mode 100644
index 80ab9c6aa1..0000000000
--- a/platform/haiku/os_haiku.cpp
+++ /dev/null
@@ -1,378 +0,0 @@
-/*************************************************************************/
-/* os_haiku.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "drivers/gles3/rasterizer_gles3.h"
-
-#include "os_haiku.h"
-
-#include "main/main.h"
-#include "servers/physics/physics_server_sw.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
-
-#include <Screen.h>
-
-OS_Haiku::OS_Haiku() {
-#ifdef MEDIA_KIT_ENABLED
- AudioDriverManager::add_driver(&driver_media_kit);
-#endif
-};
-
-void OS_Haiku::run() {
- if (!main_loop) {
- return;
- }
-
- main_loop->init();
- context_gl->release_current();
-
- // TODO: clean up
- BMessenger *bms = new BMessenger(window);
- BMessage *msg = new BMessage();
- bms->SendMessage(LOCKGL_MSG, msg);
-
- window->StartMessageRunner();
- app->Run();
- window->StopMessageRunner();
-
- delete app;
-
- delete bms;
- delete msg;
- main_loop->finish();
-}
-
-String OS_Haiku::get_name() const {
- return "Haiku";
-}
-
-int OS_Haiku::get_video_driver_count() const {
- return 1;
-}
-
-const char *OS_Haiku::get_video_driver_name(int p_driver) const {
- return "GLES3";
-}
-
-int OS_Haiku::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OS_Haiku::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- main_loop = NULL;
- current_video_mode = p_desired;
-
- app = new HaikuApplication();
-
- BRect frame;
- frame.Set(50, 50, 50 + current_video_mode.width - 1, 50 + current_video_mode.height - 1);
-
- window = new HaikuDirectWindow(frame);
- window->SetVideoMode(&current_video_mode);
-
- if (current_video_mode.fullscreen) {
- window->SetFullScreen(true);
- }
-
- if (!current_video_mode.resizable) {
- uint32 flags = window->Flags();
- flags |= B_NOT_RESIZABLE;
- window->SetFlags(flags);
- }
-
-#if defined(OPENGL_ENABLED)
- context_gl = memnew(ContextGL_Haiku(window));
- context_gl->initialize();
- context_gl->make_current();
- context_gl->set_use_vsync(current_video_mode.use_vsync);
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
-
-#endif
-
- visual_server = memnew(VisualServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, false));
- }
-
- ERR_FAIL_COND_V(!visual_server, ERR_UNAVAILABLE);
-
- video_driver_index = p_video_driver;
-
- input = memnew(InputDefault);
- window->SetInput(input);
-
- window->Show();
- visual_server->init();
-
- AudioDriverManager::initialize(p_audio_driver);
-
- return OK;
-}
-
-void OS_Haiku::finalize() {
- if (main_loop) {
- memdelete(main_loop);
- }
-
- main_loop = NULL;
-
- visual_server->finish();
- memdelete(visual_server);
-
- memdelete(input);
-
-#if defined(OPENGL_ENABLED)
- memdelete(context_gl);
-#endif
-}
-
-void OS_Haiku::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
- window->SetMainLoop(p_main_loop);
-}
-
-MainLoop *OS_Haiku::get_main_loop() const {
- return main_loop;
-}
-
-void OS_Haiku::delete_main_loop() {
- if (main_loop) {
- memdelete(main_loop);
- }
-
- main_loop = NULL;
- window->SetMainLoop(NULL);
-}
-
-void OS_Haiku::release_rendering_thread() {
- context_gl->release_current();
-}
-
-void OS_Haiku::make_rendering_thread() {
- context_gl->make_current();
-}
-
-bool OS_Haiku::can_draw() const {
- // TODO: implement
- return true;
-}
-
-void OS_Haiku::swap_buffers() {
- context_gl->swap_buffers();
-}
-
-Point2 OS_Haiku::get_mouse_position() const {
- return window->GetLastMousePosition();
-}
-
-int OS_Haiku::get_mouse_button_state() const {
- return window->GetLastButtonMask();
-}
-
-void OS_Haiku::set_cursor_shape(CursorShape p_shape) {
- //ERR_PRINT("set_cursor_shape() NOT IMPLEMENTED");
-}
-
-OS::CursorShape OS_Haiku::get_cursor_shape() const {
- // TODO: implement get_cursor_shape
-}
-
-void OS_Haiku::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
- // TODO
-}
-
-int OS_Haiku::get_screen_count() const {
- // TODO: implement get_screen_count()
- return 1;
-}
-
-int OS_Haiku::get_current_screen() const {
- // TODO: implement get_current_screen()
- return 0;
-}
-
-void OS_Haiku::set_current_screen(int p_screen) {
- // TODO: implement set_current_screen()
-}
-
-Point2 OS_Haiku::get_screen_position(int p_screen) const {
- // TODO: make this work with the p_screen parameter
- BScreen *screen = new BScreen(window);
- BRect frame = screen->Frame();
- delete screen;
- return Point2i(frame.left, frame.top);
-}
-
-Size2 OS_Haiku::get_screen_size(int p_screen) const {
- // TODO: make this work with the p_screen parameter
- BScreen *screen = new BScreen(window);
- BRect frame = screen->Frame();
- delete screen;
- return Size2i(frame.IntegerWidth() + 1, frame.IntegerHeight() + 1);
-}
-
-void OS_Haiku::set_window_title(const String &p_title) {
- window->SetTitle(p_title.utf8().get_data());
-}
-
-Size2 OS_Haiku::get_window_size() const {
- BSize size = window->Size();
- return Size2i(size.IntegerWidth() + 1, size.IntegerHeight() + 1);
-}
-
-void OS_Haiku::set_window_size(const Size2 p_size) {
- // TODO: why does it stop redrawing after this is called?
- window->ResizeTo(p_size.x, p_size.y);
-}
-
-Point2 OS_Haiku::get_window_position() const {
- BPoint point(0, 0);
- window->ConvertToScreen(&point);
- return Point2i(point.x, point.y);
-}
-
-void OS_Haiku::set_window_position(const Point2 &p_position) {
- window->MoveTo(p_position.x, p_position.y);
-}
-
-void OS_Haiku::set_window_fullscreen(bool p_enabled) {
- window->SetFullScreen(p_enabled);
- current_video_mode.fullscreen = p_enabled;
- visual_server->init();
-}
-
-bool OS_Haiku::is_window_fullscreen() const {
- return current_video_mode.fullscreen;
-}
-
-void OS_Haiku::set_window_resizable(bool p_enabled) {
- uint32 flags = window->Flags();
-
- if (p_enabled) {
- flags &= ~(B_NOT_RESIZABLE);
- } else {
- flags |= B_NOT_RESIZABLE;
- }
-
- window->SetFlags(flags);
- current_video_mode.resizable = p_enabled;
-}
-
-bool OS_Haiku::is_window_resizable() const {
- return current_video_mode.resizable;
-}
-
-void OS_Haiku::set_window_minimized(bool p_enabled) {
- window->Minimize(p_enabled);
-}
-
-bool OS_Haiku::is_window_minimized() const {
- return window->IsMinimized();
-}
-
-void OS_Haiku::set_window_maximized(bool p_enabled) {
- window->Minimize(!p_enabled);
-}
-
-bool OS_Haiku::is_window_maximized() const {
- return !window->IsMinimized();
-}
-
-void OS_Haiku::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- ERR_PRINT("set_video_mode() NOT IMPLEMENTED");
-}
-
-OS::VideoMode OS_Haiku::get_video_mode(int p_screen) const {
- return current_video_mode;
-}
-
-void OS_Haiku::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
- ERR_PRINT("get_fullscreen_mode_list() NOT IMPLEMENTED");
-}
-
-String OS_Haiku::get_executable_path() const {
- return OS::get_executable_path();
-}
-
-bool OS_Haiku::_check_internal_feature_support(const String &p_feature) {
-
- return p_feature == "pc";
-}
-
-String OS_Haiku::get_config_path() const {
-
- if (has_environment("XDG_CONFIG_HOME")) {
- return get_environment("XDG_CONFIG_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file("config/settings");
- } else {
- return ".";
- }
-}
-
-String OS_Haiku::get_data_path() const {
-
- if (has_environment("XDG_DATA_HOME")) {
- return get_environment("XDG_DATA_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file("config/data");
- } else {
- return get_config_path();
- }
-}
-
-String OS_Haiku::get_cache_path() const {
-
- if (has_environment("XDG_CACHE_HOME")) {
- return get_environment("XDG_CACHE_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file("config/cache");
- } else {
- return get_config_path();
- }
-}
-
-OS::PowerState OS_Haiku::get_power_state() {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN");
- return OS::POWERSTATE_UNKNOWN;
-}
-
-int OS_Haiku::get_power_seconds_left() {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
-}
-
-int OS_Haiku::get_power_percent_left() {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
-}
diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h
deleted file mode 100644
index c99147198d..0000000000
--- a/platform/haiku/os_haiku.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*************************************************************************/
-/* os_haiku.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef OS_HAIKU_H
-#define OS_HAIKU_H
-
-#include "audio_driver_media_kit.h"
-#include "context_gl_haiku.h"
-#include "drivers/unix/os_unix.h"
-#include "haiku_application.h"
-#include "haiku_direct_window.h"
-#include "main/input_default.h"
-#include "servers/audio_server.h"
-#include "servers/visual_server.h"
-
-class OS_Haiku : public OS_Unix {
-private:
- HaikuApplication *app;
- HaikuDirectWindow *window;
- MainLoop *main_loop;
- InputDefault *input;
- VisualServer *visual_server;
- VideoMode current_video_mode;
- int video_driver_index;
-
-#ifdef MEDIA_KIT_ENABLED
- AudioDriverMediaKit driver_media_kit;
-#endif
-
-#if defined(OPENGL_ENABLED)
- ContextGL_Haiku *context_gl;
-#endif
-
- virtual void delete_main_loop();
-
-protected:
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
- virtual int get_current_video_driver() const;
-
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
- virtual void finalize();
-
- virtual void set_main_loop(MainLoop *p_main_loop);
-
-public:
- OS_Haiku();
- void run();
-
- virtual String get_name() const;
-
- virtual MainLoop *get_main_loop() const;
-
- virtual bool can_draw() const;
- virtual void release_rendering_thread();
- virtual void make_rendering_thread();
- virtual void swap_buffers();
-
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual CursorShape get_cursor_shape() const;
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
-
- virtual int get_screen_count() const;
- virtual int get_current_screen() const;
- virtual void set_current_screen(int p_screen);
- virtual Point2 get_screen_position(int p_screen = -1) const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
- virtual void set_window_title(const String &p_title);
- virtual Size2 get_window_size() const;
- virtual void set_window_size(const Size2 p_size);
- virtual Point2 get_window_position() const;
- virtual void set_window_position(const Point2 &p_position);
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual void set_window_resizable(bool p_enabled);
- virtual bool is_window_resizable() const;
- virtual void set_window_minimized(bool p_enabled);
- virtual bool is_window_minimized() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
- virtual String get_executable_path() const;
-
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
-
- virtual bool _check_internal_feature_support(const String &p_feature);
-
- virtual String get_config_path() const;
- virtual String get_data_path() const;
- virtual String get_cache_path() const;
-};
-
-#endif
diff --git a/platform/haiku/platform_config.h b/platform/haiku/platform_config.h
deleted file mode 100644
index 2df3c05f36..0000000000
--- a/platform/haiku/platform_config.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*************************************************************************/
-/* platform_config.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include <alloca.h>
-
-// for ifaddrs.h needed in drivers/unix/ip_unix.cpp
-#define _BSD_SOURCE 1
-
-#define GLES3_INCLUDE_H "thirdparty/glad/glad/glad.h"
-#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index fa1b124561..49c77468ed 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -1,30 +1,42 @@
#!/usr/bin/env python
-Import('env')
+Import("env")
iphone_lib = [
- 'godot_iphone.cpp',
- 'os_iphone.cpp',
- 'semaphore_iphone.cpp',
- 'gl_view.mm',
- 'main.m',
- 'app_delegate.mm',
- 'view_controller.mm',
- 'game_center.mm',
- 'in_app_store.mm',
- 'icloud.mm',
- 'ios.mm',
+ "godot_iphone.mm",
+ "os_iphone.mm",
+ "main.m",
+ "app_delegate.mm",
+ "view_controller.mm",
+ "game_center.mm",
+ "in_app_store.mm",
+ "icloud.mm",
+ "ios.mm",
+ "vulkan_context_iphone.mm",
+ "display_server_iphone.mm",
+ "joypad_iphone.mm",
+ "godot_view.mm",
+ "display_layer.mm",
+ "godot_view_renderer.mm",
+ "godot_view_gesture_recognizer.m",
]
env_ios = env.Clone()
-ios_lib = env_ios.add_library('iphone', iphone_lib)
+ios_lib = env_ios.add_library("iphone", iphone_lib)
+
+# (iOS) Enable module support
+env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
def combine_libs(target=None, source=None, env=None):
lib_path = target[0].srcnode().abspath
if "osxcross" in env:
- libtool = '$IPHONEPATH/usr/bin/${ios_triple}libtool'
+ libtool = "$IPHONEPATH/usr/bin/${ios_triple}libtool"
else:
libtool = "$IPHONEPATH/usr/bin/libtool"
- env.Execute(libtool + ' -static -o "' + lib_path + '" ' + ' '.join([('"' + lib.srcnode().abspath + '"') for lib in source]))
+ env.Execute(
+ libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
+ )
+
-combine_command = env_ios.Command('#bin/libgodot' + env_ios['LIBSUFFIX'], [ios_lib] + env_ios['LIBS'], combine_libs)
+combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs)
diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h
index b4454aab11..2f082f1e07 100644
--- a/platform/iphone/app_delegate.h
+++ b/platform/iphone/app_delegate.h
@@ -28,20 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import "gl_view.h"
-#import "view_controller.h"
#import <UIKit/UIKit.h>
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
-@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
- //@property (strong, nonatomic) UIWindow *window;
- ViewController *view_controller;
- bool is_focus_out;
-};
+// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
+// so it can't be done with compilation time branching.
+//#if defined(OPENGL_ENABLED)
+//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
+//#endif
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject <UIApplicationDelegate>
+//#endif
@property(strong, nonatomic) UIWindow *window;
-
-+ (ViewController *)getViewController;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index 4de321fa04..7edbcc4667 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -29,643 +29,60 @@
/*************************************************************************/
#import "app_delegate.h"
-
#include "core/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
-#import "gl_view.h"
+#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
+#import "view_controller.h"
-#import "GameController/GameController.h"
#import <AudioToolbox/AudioServices.h>
-#define kFilteringFactor 0.1
#define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
- NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-
- if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
- return ERR_CANT_OPEN;
-
- printf("opening url %ls\n", p_uri.c_str());
- [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
- [url release];
- return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
- [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
- AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
extern int gargc;
extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
extern void iphone_finish();
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
static ViewController *mainViewController = nil;
-+ (ViewController *)getViewController {
- return mainViewController;
-}
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
- bool have_player_1 = false;
- bool have_player_2 = false;
- bool have_player_3 = false;
- bool have_player_4 = false;
-
- if (ios_joysticks == nil) {
- NSArray *keys = [ios_joysticks allKeys];
- for (NSNumber *key in keys) {
- GCController *controller = [ios_joysticks objectForKey:key];
- if (controller.playerIndex == GCControllerPlayerIndex1) {
- have_player_1 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex2) {
- have_player_2 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex3) {
- have_player_3 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex4) {
- have_player_4 = true;
- };
- };
- };
-
- if (!have_player_1) {
- return GCControllerPlayerIndex1;
- } else if (!have_player_2) {
- return GCControllerPlayerIndex2;
- } else if (!have_player_3) {
- return GCControllerPlayerIndex3;
- } else if (!have_player_4) {
- return GCControllerPlayerIndex4;
- } else {
- return GCControllerPlayerIndexUnset;
- };
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
- // get a new id for our controller
- int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
- if (joy_id != -1) {
- // assign our player index
- if (controller.playerIndex == GCControllerPlayerIndexUnset) {
- controller.playerIndex = [delegate getFreePlayerIndex];
- };
-
- // tell Godot about our new controller
- OSIPhone::get_singleton()->joy_connection_changed(
- joy_id, true, [controller.vendorName UTF8String]);
-
- // add it to our dictionary, this will retain our controllers
- [ios_joysticks setObject:controller
- forKey:[NSNumber numberWithInt:joy_id]];
-
- // set our input handler
- [delegate setControllerInputHandler:controller];
- } else {
- printf("Couldn't retrieve new joy id\n");
- };
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
- if (!*is_focus_out) {
- *is_focus_out = true;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- [view_controller.view stopAnimation];
- if (OS::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_focus_out();
- }
-
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->stop();
- }
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
- if (*is_focus_out) {
- *is_focus_out = false;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
- [view_controller.view startAnimation];
- if (OSIPhone::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_unpause();
- }
-
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->start();
- }
++ (ViewController *)viewController {
+ return mainViewController;
}
-- (void)controllerWasConnected:(NSNotification *)notification {
- // create our dictionary if we don't have one yet
- if (ios_joysticks == nil) {
- ios_joysticks = [[NSMutableDictionary alloc] init];
- };
-
- // get our controller
- GCController *controller = (GCController *)notification.object;
- if (controller == nil) {
- printf("Couldn't retrieve new controller\n");
- } else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
- printf("Controller is already registered\n");
- } else if (frame_count > 1) {
- _ios_add_joystick(controller, self);
- } else {
- if (pending_ios_joysticks == nil)
- pending_ios_joysticks = [[NSMutableArray alloc] init];
- [pending_ios_joysticks addObject:controller];
- };
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- GCController *controller = (GCController *)notification.object;
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- // tell Godot this joystick is no longer there
- int joy_id = [key intValue];
- OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
- // and remove it from our dictionary
- [ios_joysticks removeObjectForKey:key];
- };
- };
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- int joy_id = [key intValue];
- return joy_id;
- };
- };
-
- return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
- // Hook in the callback handler for the correct gamepad profile.
- // This is a bit of a weird design choice on Apples part.
- // You need to select the most capable gamepad profile for the
- // gamepad attached.
- if (controller.extendedGamepad != nil) {
- // The extended gamepad profile has all the input you could possibly find on
- // a gamepad but will only be active if your gamepad actually has all of
- // these...
- controller.extendedGamepad.valueChangedHandler = ^(
- GCExtendedGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.leftTrigger) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
- gamepad.leftTrigger.isPressed);
- } else if (element == gamepad.rightTrigger) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
- gamepad.rightTrigger.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
-
- InputDefault::JoyAxis jx;
- jx.min = -1;
- if (element == gamepad.leftThumbstick) {
- jx.value = gamepad.leftThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
- jx.value = -gamepad.leftThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
- } else if (element == gamepad.rightThumbstick) {
- jx.value = gamepad.rightThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
- jx.value = -gamepad.rightThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
- } else if (element == gamepad.leftTrigger) {
- jx.value = gamepad.leftTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
- } else if (element == gamepad.rightTrigger) {
- jx.value = gamepad.rightTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
- };
- };
- } else if (controller.gamepad != nil) {
- // gamepad is the standard profile with 4 buttons, shoulder buttons and a
- // D-pad
- controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
- GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
- // while we are setting that as the minimum, seems our
- // build environment doesn't like it
- } else if (controller.microGamepad != nil) {
- // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
- controller.microGamepad.valueChangedHandler =
- ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#endif
- };
-
- ///@TODO need to add support for controller.motion which gives us access to
- /// the orientation of the device (if supported)
-
- ///@TODO need to add support for controllerPausedHandler which should be a
- /// toggle
-};
-
-- (void)initGameControllers {
- // get told when controllers connect, this will be called right away for
- // already connected controllers
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasConnected:)
- name:GCControllerDidConnectNotification
- object:nil];
-
- // get told when controllers disconnect
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasDisconnected:)
- name:GCControllerDidDisconnectNotification
- object:nil];
-};
-
-- (void)deinitGameControllers {
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidConnectNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidDisconnectNotification
- object:nil];
-
- if (ios_joysticks != nil) {
- [ios_joysticks dealloc];
- ios_joysticks = nil;
- };
-
- if (pending_ios_joysticks != nil) {
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- };
-};
-
-OS::VideoMode _get_video_mode() {
- int backingWidth;
- int backingHeight;
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(GLView *)view;
-{
-
- switch (frame_count) {
- case 0: {
- OS::get_singleton()->set_video_mode(_get_video_mode());
-
- if (!OS::get_singleton()) {
- exit(0);
- };
- ++frame_count;
-
- NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
- OSIPhone::get_singleton()->set_locale(
- String::utf8([locale_code UTF8String]));
-
- NSString *uuid;
- if ([[UIDevice currentDevice]
- respondsToSelector:@selector(identifierForVendor)]) {
- uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- } else {
- // before iOS 6, so just generate an identifier and store it
- uuid = [[NSUserDefaults standardUserDefaults]
- objectForKey:@"identiferForVendor"];
- if (!uuid) {
- CFUUIDRef cfuuid = CFUUIDCreate(NULL);
- uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
- CFRelease(cfuuid);
- [[NSUserDefaults standardUserDefaults]
- setObject:uuid
- forKey:@"identifierForVendor"];
- }
- }
-
- OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-
- }; break;
-
- case 1: {
-
- Main::setup2();
- ++frame_count;
-
- if (pending_ios_joysticks != nil) {
- for (GCController *controller in pending_ios_joysticks) {
- _ios_add_joystick(controller, self);
- }
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- }
-
- // this might be necessary before here
- NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
- for (NSString *key in dict) {
- NSObject *value = [dict objectForKey:key];
- String ukey = String::utf8([key UTF8String]);
-
- // we need a NSObject to Variant conversor
-
- if ([value isKindOfClass:[NSString class]]) {
- NSString *str = (NSString *)value;
- String uval = String::utf8([str UTF8String]);
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
- } else if ([value isKindOfClass:[NSNumber class]]) {
-
- NSNumber *n = (NSNumber *)value;
- double dval = [n doubleValue];
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- };
- // do stuff
- }
-
- }; break;
-
- case 2: {
-
- Main::start();
- ++frame_count;
-
- }; break; // no fallthrough
-
- default: {
- if (OSIPhone::get_singleton()) {
- // OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
- // accel[2]);
- if (motionInitialised) {
- // Just using polling approach for now, we can set this up so it sends
- // data to us in intervals, might be better. See Apple reference pages
- // for more details:
- // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
- // Apple splits our accelerometer date into a gravity and user movement
- // component. We add them back together
- CMAcceleration gravity = motionManager.deviceMotion.gravity;
- CMAcceleration acceleration =
- motionManager.deviceMotion.userAcceleration;
-
- ///@TODO We don't seem to be getting data here, is my device broken or
- /// is this code incorrect?
- CMMagneticField magnetic =
- motionManager.deviceMotion.magneticField.field;
-
- ///@TODO we can access rotationRate as a CMRotationRate variable
- ///(processed date) or CMGyroData (raw data), have to see what works
- /// best
- CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
- // Adjust for screen orientation.
- // [[UIDevice currentDevice] orientation] changes even if we've fixed
- // our orientation which is not a good thing when you're trying to get
- // your user to move the screen in all directions and want consistent
- // output
-
- ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
- /// is a bit of a hack. Godot obviously knows the orientation so maybe
- /// we
- // can use that instead? (note that left and right seem swapped)
-
- switch ([[UIApplication sharedApplication] statusBarOrientation]) {
- case UIDeviceOrientationLandscapeLeft: {
- OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.y + gravity.y), (acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.y, magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationLandscapeRight: {
- OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- (acceleration.y + gravity.y), -(acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- magnetic.y, -magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationPortraitUpsideDown: {
- OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.x + gravity.x), (acceleration.y + gravity.y),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.x, magnetic.y, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
- rotation.z);
- }; break;
- default: { // assume portrait
- OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- acceleration.x + gravity.x, acceleration.y + gravity.y,
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
- magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
- rotation.z);
- }; break;
- };
- }
-
- bool quit_request = OSIPhone::get_singleton()->iterate();
- };
-
- }; break;
- };
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
- }
-};
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- CGRect rect = [[UIScreen mainScreen] bounds];
+ // TODO: might be required to make an early return, so app wouldn't crash because of timeout.
+ // TODO: logo screen is not displayed while shaders are compiling
+ // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
- is_focus_out = false;
-
- [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
- // disable idle timer
- // application.idleTimerDisabled = YES;
+ CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
- window = [[UIWindow alloc] initWithFrame:rect];
-
- OS::VideoMode vm = _get_video_mode();
+ self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
- NSUserDomainMask, YES);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
- int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+ int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
- return FALSE;
+ return NO;
};
- // WARNING: We must *always* create the GLView after we have constructed the
- // OS with iphone_main. This allows the GLView to access project settings so
- // it can properly initialize the OpenGL context
- GLView *glView = [[GLView alloc] initWithFrame:rect];
- glView.delegate = self;
-
- view_controller = [[ViewController alloc] init];
- view_controller.view = glView;
- window.rootViewController = view_controller;
+ ViewController *viewController = [[ViewController alloc] init];
+ viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+ viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
- _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
- glView.useCADisplayLink =
- bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
- printf("cadisaplylink: %d", glView.useCADisplayLink);
- glView.animationInterval = 1.0 / kRenderingFrequency;
- [glView startAnimation];
+ self.window.rootViewController = viewController;
// Show the window
- [window makeKeyAndVisible];
-
- // Configure and start accelerometer
- if (!motionInitialised) {
- motionManager = [[CMMotionManager alloc] init];
- if (motionManager.deviceMotionAvailable) {
- motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
- [motionManager startDeviceMotionUpdatesUsingReferenceFrame:
- CMAttitudeReferenceFrameXMagneticNorthZVertical];
- motionInitialised = YES;
- };
- };
-
- [self initGameControllers];
+ [self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -673,40 +90,33 @@ static int frame_count = 0;
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
- // OSIPhone::screen_width = rect.size.width - rect.origin.x;
- // OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
- mainViewController = view_controller;
+ mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
- return TRUE;
+ return YES;
};
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
}
};
-- (void)applicationWillTerminate:(UIApplication *)application {
- [self deinitGameControllers];
-
- if (motionInitialised) {
- ///@TODO is this the right place to clean this up?
- [motionManager stopDeviceMotionUpdates];
- [motionManager release];
- motionManager = nil;
- motionInitialised = NO;
- };
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+ }
+};
+- (void)applicationWillTerminate:(UIApplication *)application {
iphone_finish();
};
@@ -721,15 +131,15 @@ static int frame_count = 0;
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
- (void)dealloc {
- [window release];
+ self.window = nil;
[super dealloc];
}
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index f646b8b1d5..66579c1ad7 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -2,6 +2,7 @@ import os
import sys
from methods import detect_darwin_sdk_path
+
def is_active():
return True
@@ -12,7 +13,7 @@ def get_name():
def can_build():
- if sys.platform == 'darwin' or ("OSXCROSS_IOS" in os.environ):
+ if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ):
return True
return False
@@ -20,21 +21,32 @@ def can_build():
def get_opts():
from SCons.Variables import BoolVariable
+
return [
- ('IPHONEPATH', 'Path to iPhone toolchain', '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain'),
- ('IPHONESDK', 'Path to the iPhone SDK', ''),
- BoolVariable('game_center', 'Support for game center', True),
- BoolVariable('store_kit', 'Support for in-app store', True),
- BoolVariable('icloud', 'Support for iCloud', True),
- BoolVariable('ios_exceptions', 'Enable exceptions', False),
- ('ios_triple', 'Triple for ios toolchain', ''),
+ (
+ "IPHONEPATH",
+ "Path to iPhone toolchain",
+ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
+ ),
+ ("IPHONESDK", "Path to the iPhone SDK", ""),
+ BoolVariable(
+ "use_static_mvk",
+ "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables"
+ " validation layers)",
+ False,
+ ),
+ BoolVariable("game_center", "Support for game center", True),
+ BoolVariable("store_kit", "Support for in-app store", True),
+ BoolVariable("icloud", "Support for iCloud", True),
+ BoolVariable("ios_exceptions", "Enable exceptions", False),
+ ("ios_triple", "Triple for ios toolchain", ""),
]
def get_flags():
return [
- ('tools', False),
+ ("tools", False),
]
@@ -42,32 +54,32 @@ def configure(env):
## Build type
- if (env["target"].startswith("release")):
- env.Append(CPPDEFINES=['NDEBUG', ('NS_BLOCK_ASSERTIONS', 1)])
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Append(CCFLAGS=['-O2', '-ftree-vectorize', '-fomit-frame-pointer'])
- env.Append(LINKFLAGS=['-O2'])
- else: #optimize for size
- env.Append(CCFLAGS=['-Os', '-ftree-vectorize'])
- env.Append(LINKFLAGS=['-Os'])
+ if env["target"].startswith("release"):
+ env.Append(CPPDEFINES=["NDEBUG", ("NS_BLOCK_ASSERTIONS", 1)])
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"])
+ env.Append(LINKFLAGS=["-O2"])
+ else: # optimize for size
+ env.Append(CCFLAGS=["-Os", "-ftree-vectorize"])
+ env.Append(LINKFLAGS=["-Os"])
if env["target"] == "release_debug":
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
- elif (env["target"] == "debug"):
- env.Append(CCFLAGS=['-gdwarf-2', '-O0'])
- env.Append(CPPDEFINES=['_DEBUG', ('DEBUG', 1), 'DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
+ elif env["target"] == "debug":
+ env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
+ env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"])
- if (env["use_lto"]):
- env.Append(CCFLAGS=['-flto'])
- env.Append(LINKFLAGS=['-flto'])
+ if env["use_lto"]:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
## Architecture
if env["arch"] == "x86": # i386
env["bits"] = "32"
elif env["arch"] == "x86_64":
env["bits"] = "64"
- elif (env["arch"] == "arm" or env["arch"] == "arm32" or env["arch"] == "armv7" or env["bits"] == "32"): # arm
+ elif env["arch"] == "arm" or env["arch"] == "arm32" or env["arch"] == "armv7" or env["bits"] == "32": # arm
env["arch"] = "arm"
env["bits"] = "32"
else: # armv64
@@ -80,101 +92,163 @@ def configure(env):
if "OSXCROSS_IOS" in os.environ:
env["osxcross"] = True
- env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH']
+ env["ENV"]["PATH"] = env["IPHONEPATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
- compiler_path = '$IPHONEPATH/usr/bin/${ios_triple}'
- s_compiler_path = '$IPHONEPATH/Developer/usr/bin/'
+ compiler_path = "$IPHONEPATH/usr/bin/${ios_triple}"
+ s_compiler_path = "$IPHONEPATH/Developer/usr/bin/"
ccache_path = os.environ.get("CCACHE")
if ccache_path is None:
- env['CC'] = compiler_path + 'clang'
- env['CXX'] = compiler_path + 'clang++'
- env['S_compiler'] = s_compiler_path + 'gcc'
+ env["CC"] = compiler_path + "clang"
+ env["CXX"] = compiler_path + "clang++"
+ env["S_compiler"] = s_compiler_path + "gcc"
else:
# there aren't any ccache wrappers available for iOS,
# to enable caching we need to prepend the path to the ccache binary
- env['CC'] = ccache_path + ' ' + compiler_path + 'clang'
- env['CXX'] = ccache_path + ' ' + compiler_path + 'clang++'
- env['S_compiler'] = ccache_path + ' ' + s_compiler_path + 'gcc'
- env['AR'] = compiler_path + 'ar'
- env['RANLIB'] = compiler_path + 'ranlib'
+ env["CC"] = ccache_path + " " + compiler_path + "clang"
+ env["CXX"] = ccache_path + " " + compiler_path + "clang++"
+ env["S_compiler"] = ccache_path + " " + s_compiler_path + "gcc"
+ env["AR"] = compiler_path + "ar"
+ env["RANLIB"] = compiler_path + "ranlib"
## Compile flags
- if (env["arch"] == "x86" or env["arch"] == "x86_64"):
- detect_darwin_sdk_path('iphonesimulator', env)
- env['ENV']['MACOSX_DEPLOYMENT_TARGET'] = '10.9'
+ if env["arch"] == "x86" or env["arch"] == "x86_64":
+ detect_darwin_sdk_path("iphonesimulator", env)
+ env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
- env.Append(CCFLAGS=('-arch ' + arch_flag + ' -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0').split())
- elif (env["arch"] == "arm"):
- detect_darwin_sdk_path('iphone', env)
- env.Append(CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split())
- elif (env["arch"] == "arm64"):
- detect_darwin_sdk_path('iphone', env)
- env.Append(CCFLAGS='-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK'.split())
- env.Append(CPPDEFINES=['NEED_LONG_INT'])
- env.Append(CPPDEFINES=['LIBYUV_DISABLE_NEON'])
+ env.Append(
+ CCFLAGS=(
+ "-arch "
+ + arch_flag
+ + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks"
+ " -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"
+ ).split()
+ )
+ elif env["arch"] == "arm":
+ detect_darwin_sdk_path("iphone", env)
+ env.Append(
+ CCFLAGS=(
+ "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing"
+ " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
+ " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb"
+ ' "-DIBOutlet=__attribute__((iboutlet))"'
+ ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"'
+ ' "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split()
+ )
+ )
+ elif env["arch"] == "arm64":
+ detect_darwin_sdk_path("iphone", env)
+ env.Append(
+ CCFLAGS=(
+ "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
+ " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0"
+ " -isysroot $IPHONESDK".split()
+ )
+ )
+ env.Append(CPPDEFINES=["NEED_LONG_INT"])
+ env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
# Disable exceptions on non-tools (template) builds
- if not env['tools']:
- if env['ios_exceptions']:
- env.Append(CCFLAGS=['-fexceptions'])
+ if not env["tools"]:
+ if env["ios_exceptions"]:
+ env.Append(CCFLAGS=["-fexceptions"])
else:
- env.Append(CCFLAGS=['-fno-exceptions'])
+ env.Append(CCFLAGS=["-fno-exceptions"])
+
+ # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+ env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
## Link flags
- if (env["arch"] == "x86" or env["arch"] == "x86_64"):
+ if env["arch"] == "x86" or env["arch"] == "x86_64":
arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
- env.Append(LINKFLAGS=['-arch', arch_flag, '-mios-simulator-version-min=10.0',
- '-isysroot', '$IPHONESDK',
- '-Xlinker',
- '-objc_abi_version',
- '-Xlinker', '2',
- '-F$IPHONESDK',
- ])
- elif (env["arch"] == "arm"):
- env.Append(LINKFLAGS=['-arch', 'armv7', '-Wl,-dead_strip', '-miphoneos-version-min=10.0'])
- if (env["arch"] == "arm64"):
- env.Append(LINKFLAGS=['-arch', 'arm64', '-Wl,-dead_strip', '-miphoneos-version-min=10.0'])
-
- env.Append(LINKFLAGS=['-isysroot', '$IPHONESDK',
- '-framework', 'AudioToolbox',
- '-framework', 'AVFoundation',
- '-framework', 'CoreAudio',
- '-framework', 'CoreGraphics',
- '-framework', 'CoreMedia',
- '-framework', 'CoreVideo',
- '-framework', 'CoreMotion',
- '-framework', 'Foundation',
- '-framework', 'GameController',
- '-framework', 'MediaPlayer',
- '-framework', 'OpenGLES',
- '-framework', 'QuartzCore',
- '-framework', 'Security',
- '-framework', 'SystemConfiguration',
- '-framework', 'UIKit',
- '-framework', 'ARKit',
- ])
+ env.Append(
+ LINKFLAGS=[
+ "-arch",
+ arch_flag,
+ "-mios-simulator-version-min=13.0",
+ "-isysroot",
+ "$IPHONESDK",
+ "-Xlinker",
+ "-objc_abi_version",
+ "-Xlinker",
+ "2",
+ "-F$IPHONESDK",
+ ]
+ )
+ elif env["arch"] == "arm":
+ env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
+ if env["arch"] == "arm64":
+ env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
+
+ env.Append(
+ LINKFLAGS=[
+ "-isysroot",
+ "$IPHONESDK",
+ "-framework",
+ "AudioToolbox",
+ "-framework",
+ "AVFoundation",
+ "-framework",
+ "CoreAudio",
+ "-framework",
+ "CoreGraphics",
+ "-framework",
+ "CoreMedia",
+ "-framework",
+ "CoreVideo",
+ "-framework",
+ "CoreMotion",
+ "-framework",
+ "Foundation",
+ "-framework",
+ "GameController",
+ "-framework",
+ "MediaPlayer",
+ "-framework",
+ "Metal",
+ "-framework",
+ "QuartzCore",
+ "-framework",
+ "Security",
+ "-framework",
+ "SystemConfiguration",
+ "-framework",
+ "UIKit",
+ "-framework",
+ "ARKit",
+ ]
+ )
# Feature options
- if env['game_center']:
- env.Append(CPPDEFINES=['GAME_CENTER_ENABLED'])
- env.Append(LINKFLAGS=['-framework', 'GameKit'])
+ if env["game_center"]:
+ env.Append(CPPDEFINES=["GAME_CENTER_ENABLED"])
+ env.Append(LINKFLAGS=["-framework", "GameKit"])
+
+ if env["store_kit"]:
+ env.Append(CPPDEFINES=["STOREKIT_ENABLED"])
+ env.Append(LINKFLAGS=["-framework", "StoreKit"])
+
+ if env["icloud"]:
+ env.Append(CPPDEFINES=["ICLOUD_ENABLED"])
- if env['store_kit']:
- env.Append(CPPDEFINES=['STOREKIT_ENABLED'])
- env.Append(LINKFLAGS=['-framework', 'StoreKit'])
+ env.Prepend(
+ CPPPATH=[
+ "$IPHONESDK/usr/include",
+ "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",
+ ]
+ )
- if env['icloud']:
- env.Append(CPPDEFINES=['ICLOUD_ENABLED'])
+ env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate"
- env.Prepend(CPPPATH=['$IPHONESDK/usr/include',
- '$IPHONESDK/System/Library/Frameworks/OpenGLES.framework/Headers',
- '$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers',
- ])
+ env.Prepend(CPPPATH=["#platform/iphone"])
+ env.Append(CPPDEFINES=["IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
- env['ENV']['CODESIGN_ALLOCATE'] = '/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate'
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(LINKFLAGS=["-framework", "IOSurface"])
- env.Prepend(CPPPATH=['#platform/iphone'])
- env.Append(CPPDEFINES=['IPHONE_ENABLED', 'UNIX_ENABLED', 'GLES_ENABLED', 'COREAUDIO_ENABLED'])
+ # Use Static Vulkan for iOS. Dynamic Framework works fine too.
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h
new file mode 100644
index 0000000000..bfde8f96a3
--- /dev/null
+++ b/platform/iphone/display_layer.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* display_layer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
+
+@protocol DisplayLayer <NSObject>
+
+- (void)renderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
+
+@end
+
+// An ugly workaround for iOS simulator
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+#if defined(__IPHONE_13_0)
+API_AVAILABLE(ios(13.0))
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#else
+@interface GodotMetalLayer : CALayer <DisplayLayer>
+#endif
+#else
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#endif
+@end
+
+API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end
diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm
new file mode 100644
index 0000000000..5ec94fb471
--- /dev/null
+++ b/platform/iphone/display_layer.mm
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* display_layer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "display_layer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@implementation GodotMetalLayer
+
+- (void)initializeDisplayLayer {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ if (@available(iOS 13, *)) {
+ // Simulator supports Metal since iOS 13
+ } else {
+ NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
+ }
+#endif
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)renderDisplayLayer {
+}
+
+@end
+
+@implementation GodotOpenGLLayer {
+ // The pixel dimensions of the backbuffer
+ GLint backingWidth;
+ GLint backingHeight;
+
+ EAGLContext *context;
+ GLuint viewRenderbuffer, viewFramebuffer;
+ GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+ // Get our backing layer
+
+ // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+ self.opaque = YES;
+ self.drawableProperties = [NSDictionary
+ dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+ kEAGLDrawablePropertyRetainedBacking,
+ kEAGLColorFormatRGBA8,
+ kEAGLDrawablePropertyColorFormat,
+ nil];
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ // Create GL ES 2 context
+ if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
+ context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ NSLog(@"Setting up an OpenGL ES 2.0 context.");
+ if (!context) {
+ NSLog(@"Failed to create OpenGL ES 2.0 context!");
+ return;
+ }
+ }
+
+ if (![EAGLContext setCurrentContext:context]) {
+ NSLog(@"Failed to set EAGLContext!");
+ return;
+ }
+ if (![self createFramebuffer]) {
+ NSLog(@"Failed to create frame buffer!");
+ return;
+ }
+}
+
+- (void)layoutDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+ [self destroyFramebuffer];
+ [self createFramebuffer];
+}
+
+- (void)renderDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+}
+
+- (void)dealloc {
+ if ([EAGLContext currentContext] == context) {
+ [EAGLContext setCurrentContext:nil];
+ }
+
+ if (context) {
+ [context release];
+ context = nil;
+ }
+
+ [super dealloc];
+}
+
+- (BOOL)createFramebuffer {
+ glGenFramebuffersOES(1, &viewFramebuffer);
+ glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+ // This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
+ // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+ [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+ // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+ glGenRenderbuffersOES(1, &depthRenderbuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+ if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+ NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+ return NO;
+ }
+
+ // if (OS::get_singleton()) {
+ // OS::VideoMode vm;
+ // vm.fullscreen = true;
+ // vm.width = backingWidth;
+ // vm.height = backingHeight;
+ // vm.resizable = false;
+ // OS::get_singleton()->set_video_mode(vm);
+ // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
+ // };
+ // gl_view_base_fb = viewFramebuffer;
+
+ return YES;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+ glDeleteFramebuffersOES(1, &viewFramebuffer);
+ viewFramebuffer = 0;
+ glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+ viewRenderbuffer = 0;
+
+ if (depthRenderbuffer) {
+ glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+ depthRenderbuffer = 0;
+ }
+}
+
+@end
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
new file mode 100644
index 0000000000..229b1e80db
--- /dev/null
+++ b/platform/iphone/display_server_iphone.h
@@ -0,0 +1,202 @@
+/*************************************************************************/
+/* display_server_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef display_server_iphone_h
+#define display_server_iphone_h
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+
+#include "vulkan_context_iphone.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+class DisplayServerIPhone : public DisplayServer {
+ GDCLASS(DisplayServerIPhone, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextIPhone *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ DisplayServer::ScreenOrientation screen_orientation;
+
+ ObjectID window_attached_instance_id;
+
+ Callable window_event_callback;
+ Callable window_resize_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+
+ int virtual_keyboard_height = 0;
+
+ void perform_event(const Ref<InputEvent> &p_event);
+
+ DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerIPhone();
+
+public:
+ String rendering_driver;
+
+ static DisplayServerIPhone *get_singleton();
+
+ static void register_iphone_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ // MARK: - Events
+
+ virtual void process_events() override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void send_input_event(const Ref<InputEvent> &p_event) const;
+ void send_input_text(const String &p_text) const;
+ void send_window_event(DisplayServer::WindowEvent p_event) const;
+ void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+ // MARK: - Input
+
+ // MARK: Touches
+
+ void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
+ void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
+ void touches_cancelled(int p_idx);
+
+ // MARK: Keyboard
+
+ void key(uint32_t p_key, bool p_pressed);
+
+ // MARK: Motion
+
+ void update_gravity(float p_x, float p_y, float p_z);
+ void update_accelerometer(float p_x, float p_y, float p_z);
+ void update_magnetometer(float p_x, float p_y, float p_z);
+ void update_gyroscope(float p_x, float p_y, float p_z);
+
+ // MARK: -
+
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+ virtual int get_screen_count() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual WindowID
+ get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual float screen_get_max_scale() const override;
+
+ virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
+ virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual bool screen_is_touchscreen(int p_screen) const override;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+ virtual void virtual_keyboard_hide() override;
+
+ void virtual_keyboard_set_height(int height);
+ virtual int virtual_keyboard_get_height() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+ virtual bool native_video_is_playing() const override;
+ virtual void native_video_pause() override;
+ virtual void native_video_unpause() override;
+ virtual void native_video_stop() override;
+
+ void resize_window(CGSize size);
+};
+
+#endif /* display_server_iphone_h */
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
new file mode 100644
index 0000000000..eea87cecc9
--- /dev/null
+++ b/platform/iphone/display_server_iphone.mm
@@ -0,0 +1,766 @@
+/*************************************************************************/
+/* display_server_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/project_settings.h"
+#import "godot_view.h"
+#include "ios.h"
+#include "os_iphone.h"
+#import "view_controller.h"
+
+#import <Foundation/Foundation.h>
+#import <sys/utsname.h>
+
+static const float kDisplayServerIPhoneAcceleration = 1;
+static NSDictionary *iOSModelToDPI = @{
+ @[
+ @"iPad1,1",
+ @"iPad2,1",
+ @"iPad2,2",
+ @"iPad2,3",
+ @"iPad2,4",
+ ] : @132,
+ @[
+ @"iPhone1,1",
+ @"iPhone1,2",
+ @"iPhone2,1",
+ @"iPad2,5",
+ @"iPad2,6",
+ @"iPad2,7",
+ @"iPod1,1",
+ @"iPod2,1",
+ @"iPod3,1",
+ ] : @163,
+ @[
+ @"iPad3,1",
+ @"iPad3,2",
+ @"iPad3,3",
+ @"iPad3,4",
+ @"iPad3,5",
+ @"iPad3,6",
+ @"iPad4,1",
+ @"iPad4,2",
+ @"iPad4,3",
+ @"iPad5,3",
+ @"iPad5,4",
+ @"iPad6,3",
+ @"iPad6,4",
+ @"iPad6,7",
+ @"iPad6,8",
+ @"iPad6,11",
+ @"iPad6,12",
+ @"iPad7,1",
+ @"iPad7,2",
+ @"iPad7,3",
+ @"iPad7,4",
+ @"iPad7,5",
+ @"iPad7,6",
+ @"iPad7,11",
+ @"iPad7,12",
+ @"iPad8,1",
+ @"iPad8,2",
+ @"iPad8,3",
+ @"iPad8,4",
+ @"iPad8,5",
+ @"iPad8,6",
+ @"iPad8,7",
+ @"iPad8,8",
+ @"iPad8,9",
+ @"iPad8,10",
+ @"iPad8,11",
+ @"iPad8,12",
+ @"iPad11,3",
+ @"iPad11,4",
+ ] : @264,
+ @[
+ @"iPhone3,1",
+ @"iPhone3,2",
+ @"iPhone3,3",
+ @"iPhone4,1",
+ @"iPhone5,1",
+ @"iPhone5,2",
+ @"iPhone5,3",
+ @"iPhone5,4",
+ @"iPhone6,1",
+ @"iPhone6,2",
+ @"iPhone7,2",
+ @"iPhone8,1",
+ @"iPhone8,4",
+ @"iPhone9,1",
+ @"iPhone9,3",
+ @"iPhone10,1",
+ @"iPhone10,4",
+ @"iPhone11,8",
+ @"iPhone12,1",
+ @"iPhone12,8",
+ @"iPad4,4",
+ @"iPad4,5",
+ @"iPad4,6",
+ @"iPad4,7",
+ @"iPad4,8",
+ @"iPad4,9",
+ @"iPad5,1",
+ @"iPad5,2",
+ @"iPad11,1",
+ @"iPad11,2",
+ @"iPod4,1",
+ @"iPod5,1",
+ @"iPod7,1",
+ @"iPod9,1",
+ ] : @326,
+ @[
+ @"iPhone7,1",
+ @"iPhone8,2",
+ @"iPhone9,2",
+ @"iPhone9,4",
+ @"iPhone10,2",
+ @"iPhone10,5",
+ ] : @401,
+ @[
+ @"iPhone10,3",
+ @"iPhone10,6",
+ @"iPhone11,2",
+ @"iPhone11,4",
+ @"iPhone11,6",
+ @"iPhone12,3",
+ @"iPhone12,5",
+ ] : @458,
+};
+
+DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
+ return (DisplayServerIPhone *)DisplayServer::get_singleton();
+}
+
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ rendering_driver = p_rendering_driver;
+
+#if defined(OPENGL_ENABLED)
+ // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
+ // again,
+
+ if (rendering_driver == "opengl_es") {
+ bool gl_initialization_error = false;
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
+ // return ERR_UNAVAILABLE;
+ }
+
+ // rendering_server = memnew(RenderingServerRaster);
+ // // FIXME: Reimplement threaded rendering
+ // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+ // rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+ // false));
+ // }
+ // rendering_server->init();
+ // rendering_server->cursor_set_visible(false, 0);
+
+ // reset this to what it should be, it will have been set to 0 after
+ // rendering_server->init() is called
+ // RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+ }
+#endif
+
+#if defined(VULKAN_ENABLED)
+ rendering_driver = "vulkan";
+
+ context_vulkan = nullptr;
+ rendering_device_vulkan = nullptr;
+
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextIPhone);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to initialize Vulkan context");
+ }
+
+ CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+
+ if (!layer) {
+ ERR_FAIL_MSG("Failed to create iOS rendering layer.");
+ }
+
+ Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+ if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to create Vulkan window.");
+ }
+
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+ screen_set_keep_on(keep_screen_on);
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+}
+
+DisplayServerIPhone::~DisplayServerIPhone() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ rendering_device_vulkan = NULL;
+ }
+
+ if (context_vulkan) {
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+ memdelete(context_vulkan);
+ context_vulkan = NULL;
+ }
+ }
+#endif
+}
+
+DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+void DisplayServerIPhone::register_iphone_driver() {
+ register_create_function("iphone", create_func, get_rendering_drivers_func);
+}
+
+// MARK: Events
+
+void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ window_resize_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::process_events() {
+}
+
+void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ DisplayServerIPhone::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
+ _window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerIPhone::send_input_text(const String &p_text) const {
+ _window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
+ _window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+ if (!p_callable.is_null()) {
+ const Variant *argp = &p_arg;
+ Variant ret;
+ Callable::CallError ce;
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ }
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+
+ ev->set_index(p_idx);
+ ev->set_pressed(p_pressed);
+ ev->set_position(Vector2(p_x, p_y));
+ perform_event(ev);
+ }
+};
+
+void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ ev->set_index(p_idx);
+ ev->set_position(Vector2(p_x, p_y));
+ ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+ perform_event(ev);
+ };
+};
+
+void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
+ Input::get_singleton()->parse_input_event(p_event);
+};
+
+void DisplayServerIPhone::touches_cancelled(int p_idx) {
+ touch_press(p_idx, -1, -1, false, false);
+};
+
+// MARK: Keyboard
+
+void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(false);
+ ev->set_pressed(p_pressed);
+ ev->set_keycode(p_key);
+ ev->set_physical_keycode(p_key);
+ ev->set_unicode(p_key);
+ perform_event(ev);
+};
+
+// MARK: Motion
+
+void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
+ float p_z) {
+ // Found out the Z should not be negated! Pass as is!
+ Vector3 v_accelerometer = Vector3(
+ p_x / kDisplayServerIPhoneAcceleration,
+ p_y / kDisplayServerIPhoneAcceleration,
+ p_z / kDisplayServerIPhoneAcceleration);
+
+ Input::get_singleton()->set_accelerometer(v_accelerometer);
+
+ /*
+ if (p_x != last_accel.x) {
+ //printf("updating accel x %f\n", p_x);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_0;
+ ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
+ last_accel.x = p_x;
+ queue_event(ev);
+ };
+ if (p_y != last_accel.y) {
+ //printf("updating accel y %f\n", p_y);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_1;
+ ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
+ last_accel.y = p_y;
+ queue_event(ev);
+ };
+ if (p_z != last_accel.z) {
+ //printf("updating accel z %f\n", p_z);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_2;
+ ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
+ last_accel.z = p_z;
+ queue_event(ev);
+ };
+ */
+};
+
+void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
+};
+
+// MARK: -
+
+bool DisplayServerIPhone::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_CONSOLE_WINDOW:
+ // case FEATURE_CURSOR_SHAPE:
+ // case FEATURE_CUSTOM_CURSOR_SHAPE:
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_HIDPI:
+ // case FEATURE_ICON:
+ // case FEATURE_IME:
+ // case FEATURE_MOUSE:
+ // case FEATURE_MOUSE_WARP:
+ // case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_ICON:
+ // case FEATURE_NATIVE_VIDEO:
+ // case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerIPhone::get_name() const {
+ return "iPhone";
+}
+
+void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+int DisplayServerIPhone::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
+ return Size2i();
+}
+
+Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
+ CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
+
+ if (!layer) {
+ return Size2i();
+ }
+
+ return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
+ if (@available(iOS 11, *)) {
+ UIEdgeInsets insets = UIEdgeInsetsZero;
+ UIView *view = AppDelegate.viewController.godotView;
+
+ if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+ insets = [view safeAreaInsets];
+ }
+
+ float scale = screen_get_scale(p_screen);
+ Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+ Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+
+ return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size);
+ } else {
+ return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+ }
+}
+
+int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
+ struct utsname systemInfo;
+ uname(&systemInfo);
+
+ NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+ for (NSArray *keyArray in iOSModelToDPI) {
+ if ([keyArray containsObject:string]) {
+ NSNumber *value = iOSModelToDPI[keyArray];
+ return [value intValue];
+ }
+ }
+
+ return 163;
+}
+
+float DisplayServerIPhone::screen_get_scale(int p_screen) const {
+ return [UIScreen mainScreen].nativeScale;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
+ Vector<DisplayServer::WindowID> list;
+ list.push_back(MAIN_WINDOW_ID);
+ return list;
+}
+
+DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
+ return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
+ return Point2i();
+}
+
+void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Probably not supported for single window iOS app
+}
+
+void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
+ CGRect screenBounds = [UIScreen mainScreen].bounds;
+ return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
+}
+
+Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
+ return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_request_attention(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+float DisplayServerIPhone::screen_get_max_scale() const {
+ return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+};
+
+void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+ screen_orientation = p_orientation;
+}
+
+DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
+ return screen_orientation;
+}
+
+bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerIPhone::can_any_window_draw() const {
+ return true;
+}
+
+bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
+ return true;
+}
+
+void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+}
+
+void DisplayServerIPhone::virtual_keyboard_hide() {
+ [AppDelegate.viewController.godotView resignFirstResponder];
+}
+
+void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
+ virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerIPhone::virtual_keyboard_get_height() const {
+ return virtual_keyboard_height;
+}
+
+void DisplayServerIPhone::clipboard_set(const String &p_text) {
+ [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
+}
+
+String DisplayServerIPhone::clipboard_get() const {
+ NSString *text = [UIPasteboard generalPasteboard].string;
+
+ return String::utf8([text UTF8String]);
+}
+
+void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
+ [UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerIPhone::screen_is_kept_on() const {
+ return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+ bool exists = f && f->is_open();
+
+ String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (!exists) {
+ return FAILED;
+ }
+
+ String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (p_path.begins_with("res://")) {
+ if (PackedData::get_singleton()->has_path(p_path)) {
+ printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data());
+ return ERR_INVALID_PARAMETER;
+ } else {
+ p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
+ }
+ } else if (p_path.begins_with("user://")) {
+ p_path = p_path.replace("user:/", user_data_dir);
+ }
+
+ memdelete(f);
+
+ printf("Playing video: %s\n", p_path.utf8().get_data());
+
+ String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+ NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease];
+ NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+ NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+ if (![AppDelegate.viewController playVideoAtPath:filePath
+ volume:p_volume
+ audio:audioTrack
+ subtitle:subtitleTrack]) {
+ return OK;
+ }
+
+ return FAILED;
+}
+
+bool DisplayServerIPhone::native_video_is_playing() const {
+ return [AppDelegate.viewController isVideoPlaying];
+}
+
+void DisplayServerIPhone::native_video_pause() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController pauseVideo];
+ }
+}
+
+void DisplayServerIPhone::native_video_unpause() {
+ [AppDelegate.viewController unpauseVideo];
+};
+
+void DisplayServerIPhone::native_video_stop() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController stopVideo];
+ }
+}
+
+void DisplayServerIPhone::resize_window(CGSize viewSize) {
+ Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (context_vulkan) {
+ context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
+ }
+ }
+#endif
+
+ Variant resize_rect = Rect2i(Point2i(), size);
+ _window_callback(window_resize_callback, resize_rect);
+}
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index b606d570c2..97f954ebb2 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -29,6 +29,8 @@
/*************************************************************************/
#include "export.h"
+
+#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "core/io/zip_io.h"
@@ -39,13 +41,13 @@
#include "editor/editor_export.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "main/splash.gen.h"
#include "platform/iphone/logo.gen.h"
#include "string.h"
#include <sys/stat.h>
class EditorExportPlatformIOS : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
int version_code;
@@ -55,6 +57,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
typedef Error (*FileHandler)(String p_file, void *p_userdata);
static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
static Error _codesign(String p_file, void *p_userdata);
+ void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
struct IOSConfigData {
String pkg_name;
@@ -68,15 +71,11 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
String modules_buildphase;
String modules_buildgrp;
};
-
struct ExportArchitecture {
String name;
- bool is_default;
+ bool is_default = false;
- ExportArchitecture() :
- name(""),
- is_default(false) {
- }
+ ExportArchitecture() {}
ExportArchitecture(String p_name, bool p_is_default) {
name = p_name;
@@ -87,24 +86,25 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
struct IOSExportAsset {
String exported_path;
bool is_framework; // framework is anything linked to the binary, otherwise it's a resource
+ bool should_embed;
};
String _get_additional_plist_content();
String _get_linker_flags();
String _get_cpp_code();
void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
- Error _export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
+ Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
+ Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
Vector<ExportArchitecture> _get_supported_architectures();
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
- Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets);
+ Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
- bool is_package_name_valid(const String &p_package, String *r_error = NULL) const {
-
+ bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
String pname = p_package;
if (pname.length() == 0) {
@@ -115,7 +115,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
for (int i = 0; i < pname.length(); i++) {
- CharType c = pname[i];
+ char32_t c = pname[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
@@ -128,31 +128,30 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
protected:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
+ virtual void get_export_options(List<ExportOption> *r_options) override;
public:
- virtual String get_name() const { return "iOS"; }
- virtual String get_os_name() const { return "iOS"; }
- virtual Ref<Texture> get_logo() const { return logo; }
+ virtual String get_name() const override { return "iOS"; }
+ virtual String get_os_name() const override { return "iOS"; }
+ virtual Ref<Texture2D> get_logo() const override { return logo; }
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
list.push_back("ipa");
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
virtual void add_module_code(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid);
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
-
- virtual void get_platform_features(List<String> *r_features) {
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("mobile");
r_features->push_back("iOS");
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
EditorExportPlatformIOS();
@@ -160,15 +159,12 @@ public:
};
void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
-
String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
if (driver == "GLES2") {
r_features->push_back("etc");
- } else if (driver == "GLES3") {
+ } else if (driver == "Vulkan") {
+ // FIXME: Review if this is correct.
r_features->push_back("etc2");
- if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) {
- r_features->push_back("etc");
- }
}
Vector<String> architectures = _get_preset_architectures(p_preset);
@@ -187,25 +183,27 @@ Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_ge
struct LoadingScreenInfo {
const char *preset_key;
const char *export_name;
+ int width;
+ int height;
+ bool rotate;
};
static const LoadingScreenInfo loading_screen_infos[] = {
- { "landscape_launch_screens/iphone_2436x1125", "Default-Landscape-X.png" },
- { "landscape_launch_screens/iphone_2208x1242", "Default-Landscape-736h@3x.png" },
- { "landscape_launch_screens/ipad_1024x768", "Default-Landscape.png" },
- { "landscape_launch_screens/ipad_2048x1536", "Default-Landscape@2x.png" },
-
- { "portrait_launch_screens/iphone_640x960", "Default-480h@2x.png" },
- { "portrait_launch_screens/iphone_640x1136", "Default-568h@2x.png" },
- { "portrait_launch_screens/iphone_750x1334", "Default-667h@2x.png" },
- { "portrait_launch_screens/iphone_1125x2436", "Default-Portrait-X.png" },
- { "portrait_launch_screens/ipad_768x1024", "Default-Portrait.png" },
- { "portrait_launch_screens/ipad_1536x2048", "Default-Portrait@2x.png" },
- { "portrait_launch_screens/iphone_1242x2208", "Default-Portrait-736h@3x.png" }
+ { "landscape_launch_screens/iphone_2436x1125", "Default-Landscape-X.png", 2436, 1125, false },
+ { "landscape_launch_screens/iphone_2208x1242", "Default-Landscape-736h@3x.png", 2208, 1242, false },
+ { "landscape_launch_screens/ipad_1024x768", "Default-Landscape.png", 1024, 768, false },
+ { "landscape_launch_screens/ipad_2048x1536", "Default-Landscape@2x.png", 2048, 1536, false },
+
+ { "portrait_launch_screens/iphone_640x960", "Default-480h@2x.png", 640, 960, true },
+ { "portrait_launch_screens/iphone_640x1136", "Default-568h@2x.png", 640, 1136, true },
+ { "portrait_launch_screens/iphone_750x1334", "Default-667h@2x.png", 750, 1334, true },
+ { "portrait_launch_screens/iphone_1125x2436", "Default-Portrait-X.png", 1125, 2436, true },
+ { "portrait_launch_screens/ipad_768x1024", "Default-Portrait.png", 768, 1024, true },
+ { "portrait_launch_screens/ipad_1536x2048", "Default-Portrait@2x.png", 1536, 2048, true },
+ { "portrait_launch_screens/iphone_1242x2208", "Default-Portrait-736h@3x.png", 1242, 2208, true }
};
void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) {
-
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
@@ -221,7 +219,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
@@ -247,6 +245,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_right"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_upside_down"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "icons/generate_missing"), false));
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with retina display
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
@@ -257,6 +257,15 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale To Fit,Scale To Fill,Scale"), 0));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color()));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "launch_screens/generate_missing"), false));
+
for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), ""));
}
@@ -274,6 +283,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
"ad-hoc",
"enterprise"
};
+ static const String storyboard_image_scale_mode[] = {
+ "center",
+ "scaleAspectFit",
+ "scaleAspectFill",
+ "scaleToFill"
+ };
String str;
String strnew;
str.parse_utf8((const char *)pfile.ptr(), pfile.size());
@@ -293,8 +308,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
} else if (lines[i].find("$info") != -1) {
strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
- } else if (lines[i].find("$identifier") != -1) {
- strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n";
+ } else if (lines[i].find("$bundle_identifier") != -1) {
+ strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
} else if (lines[i].find("$short_version") != -1) {
strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
@@ -343,6 +358,9 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
} else if (lines[i].find("$push_notifications") != -1) {
bool is_on = p_preset->get("capabilities/push_notifications");
strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n";
+ } else if (lines[i].find("$entitlements_push_notifications") != -1) {
+ bool is_on = p_preset->get("capabilities/push_notifications");
+ strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n";
} else if (lines[i].find("$required_device_capabilities") != -1) {
String capabilities;
@@ -387,6 +405,60 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
} else if (lines[i].find("$photolibrary_usage_description") != -1) {
String description = p_preset->get("privacy/photolibrary_usage_description");
strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n";
+ } else if (lines[i].find("$plist_launch_screen_name") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : "";
+ strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : "";
+ strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : "";
+ strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : "";
+ strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : "";
+ strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
+ } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
+ bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
+ String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;";
+ strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n";
+ } else if (lines[i].find("$launch_screen_image_mode") != -1) {
+ int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
+ String value;
+
+ switch (image_scale_mode) {
+ case 0: {
+ String logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+ bool is_on = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
+ // If custom logo is not specified, Godot does not scale default one, so we should do the same.
+ value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center";
+ } break;
+ default: {
+ value = storyboard_image_scale_mode[image_scale_mode - 1];
+ }
+ }
+
+ strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n";
+ } else if (lines[i].find("$launch_screen_background_color") != -1) {
+ bool use_custom = p_preset->get("storyboard/use_custom_bg_color");
+ Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
+ const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"";
+
+ Dictionary value_dictionary;
+ value_dictionary["red"] = color.r;
+ value_dictionary["green"] = color.g;
+ value_dictionary["blue"] = color.b;
+ value_dictionary["alpha"] = color.a;
+ String value = value_format.format(value_dictionary, "$_");
+
+ strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
} else {
strnew += lines[i] + "\n";
}
@@ -402,7 +474,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
String EditorExportPlatformIOS::_get_additional_plist_content() {
- Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
String result;
for (int i = 0; i < export_plugins.size(); ++i) {
result += export_plugins[i]->get_ios_plist_content();
@@ -411,11 +483,13 @@ String EditorExportPlatformIOS::_get_additional_plist_content() {
}
String EditorExportPlatformIOS::_get_linker_flags() {
- Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
String result;
for (int i = 0; i < export_plugins.size(); ++i) {
String flags = export_plugins[i]->get_ios_linker_flags();
- if (flags.length() == 0) continue;
+ if (flags.length() == 0) {
+ continue;
+ }
if (result.length() > 0) {
result += ' ';
}
@@ -426,7 +500,7 @@ String EditorExportPlatformIOS::_get_linker_flags() {
}
String EditorExportPlatformIOS::_get_cpp_code() {
- Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
String result;
for (int i = 0; i < export_plugins.size(); ++i) {
result += export_plugins[i]->get_ios_cpp_code();
@@ -434,6 +508,39 @@ String EditorExportPlatformIOS::_get_cpp_code() {
return result;
}
+void EditorExportPlatformIOS::_blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot) {
+ ERR_FAIL_COND(p_dst.is_null());
+ ERR_FAIL_COND(p_src.is_null());
+
+ int sw = p_rot ? p_src->get_height() : p_src->get_width();
+ int sh = p_rot ? p_src->get_width() : p_src->get_height();
+
+ int x_pos = (p_dst->get_width() - sw) / 2;
+ int y_pos = (p_dst->get_height() - sh) / 2;
+
+ int xs = (x_pos >= 0) ? 0 : -x_pos;
+ int ys = (y_pos >= 0) ? 0 : -y_pos;
+
+ if (sw + x_pos > p_dst->get_width()) {
+ sw = p_dst->get_width() - x_pos;
+ }
+ if (sh + y_pos > p_dst->get_height()) {
+ sh = p_dst->get_height() - y_pos;
+ }
+
+ for (int y = ys; y < sh; y++) {
+ for (int x = xs; x < sw; x++) {
+ Color sc = p_rot ? p_src->get_pixel(p_src->get_width() - y - 1, x) : p_src->get_pixel(x, y);
+ Color dc = p_dst->get_pixel(x_pos + x, y_pos + y);
+ dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r);
+ dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g);
+ dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b);
+ dc.a = (double)(sc.a + dc.a * (1.0 - sc.a));
+ p_dst->set_pixel(x_pos + x, y_pos + y, dc);
+ }
+ }
+}
+
struct IconInfo {
const char *preset_key;
const char *idiom;
@@ -448,8 +555,8 @@ static const IconInfo icon_infos[] = {
{ "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", true },
{ "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", true },
- { "required_icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", false },
- { "required_icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", false },
+ { "required_icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", true },
+ { "required_icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true },
{ "optional_icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false },
@@ -473,20 +580,56 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
IconInfo info = icon_infos[i];
+ int side_size = String(info.actual_size_side).to_int();
String icon_path = p_preset->get(info.preset_key);
if (icon_path.length() == 0) {
- if (info.is_required) {
- ERR_PRINT("Required icon is not specified in the preset");
+ if ((bool)p_preset->get("icons/generate_missing")) {
+ // Resize main app icon
+ icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(icon_path, img);
+ if (err != OK) {
+ ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
+ return ERR_UNCONFIGURED;
+ }
+ img->resize(side_size, side_size);
+ err = img->save_png(p_iconset_dir + info.export_name);
+ if (err) {
+ String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
+ ERR_PRINT(err_str.utf8().get_data());
+ return err;
+ }
+ } else {
+ if (info.is_required) {
+ String err_str = String("Required icon (") + info.preset_key + ") is not specified in the preset.";
+ ERR_PRINT(err_str);
+ return ERR_UNCONFIGURED;
+ } else {
+ String err_str = String("Icon (") + info.preset_key + ") is not specified in the preset.";
+ WARN_PRINT(err_str);
+ }
+ continue;
+ }
+ } else {
+ // Load custom icon
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(icon_path, img);
+ if (err != OK) {
+ ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
return ERR_UNCONFIGURED;
}
- continue;
- }
- Error err = da->copy(icon_path, p_iconset_dir + info.export_name);
- if (err) {
- memdelete(da);
- String err_str = String("Failed to export icon: ") + icon_path;
- ERR_PRINT(err_str.utf8().get_data());
- return err;
+ if (img->get_width() != side_size || img->get_height() != side_size) {
+ ERR_PRINT("Invalid icon size (" + String(info.preset_key) + "): '" + icon_path + "'.");
+ return ERR_UNCONFIGURED;
+ }
+
+ err = da->copy(icon_path, p_iconset_dir + info.export_name);
+ if (err) {
+ memdelete(da);
+ String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
+ ERR_PRINT(err_str.utf8().get_data());
+ return err;
+ }
}
sizes += String(info.actual_size_side) + "\n";
if (i > 0) {
@@ -517,7 +660,75 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
return OK;
}
-Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
+Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
+ const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x");
+ const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x");
+
+ if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) {
+ Ref<Image> image;
+ String image_path = p_dest_dir.plus_file("splash@2x.png");
+ image.instance();
+ Error err = image->load(custom_launch_image_2x);
+
+ if (err) {
+ image.unref();
+ return err;
+ }
+
+ if (image->save_png(image_path) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+
+ image.unref();
+ image_path = p_dest_dir.plus_file("splash@3x.png");
+ image.instance();
+ err = image->load(custom_launch_image_3x);
+
+ if (err) {
+ image.unref();
+ return err;
+ }
+
+ if (image->save_png(image_path) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+ } else {
+ Ref<Image> splash;
+
+ const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+
+ if (!splash_path.empty()) {
+ splash.instance();
+ const Error err = splash->load(splash_path);
+ if (err) {
+ splash.unref();
+ }
+ }
+
+ if (splash.is_null()) {
+ splash = Ref<Image>(memnew(Image(boot_splash_png)));
+ }
+
+ // Using same image for both @2x and @3x
+ // because Godot's own boot logo uses single image for all resolutions.
+ // Also not using @1x image, because devices using this image variant
+ // are not supported by iOS 9, which is minimal target.
+ const String splash_png_path_2x = p_dest_dir.plus_file("splash@2x.png");
+ const String splash_png_path_3x = p_dest_dir.plus_file("splash@3x.png");
+
+ if (splash->save_png(splash_png_path_2x) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+
+ if (splash->save_png(splash_png_path_3x) != OK) {
+ return ERR_FILE_CANT_WRITE;
+ }
+ }
+
+ return OK;
+}
+
+Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
DirAccess *da = DirAccess::open(p_dest_dir);
ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'.");
@@ -525,13 +736,72 @@ Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPre
LoadingScreenInfo info = loading_screen_infos[i];
String loading_screen_file = p_preset->get(info.preset_key);
if (loading_screen_file.size() > 0) {
- Error err = da->copy(loading_screen_file, p_dest_dir + info.export_name);
+ // Load custom loading screens
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(loading_screen_file, img);
+ if (err != OK) {
+ ERR_PRINT("Invalid loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "'.");
+ return ERR_UNCONFIGURED;
+ }
+ if (img->get_width() != info.width || img->get_height() != info.height) {
+ ERR_PRINT("Invalid loading screen size (" + String(info.preset_key) + "): '" + loading_screen_file + "'.");
+ return ERR_UNCONFIGURED;
+ }
+ err = da->copy(loading_screen_file, p_dest_dir + info.export_name);
if (err) {
memdelete(da);
String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'.";
ERR_PRINT(err_str.utf8().get_data());
return err;
}
+ } else if ((bool)p_preset->get("launch_screens/generate_missing")) {
+ // Generate loading screen from the splash screen
+ Color boot_bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
+ String boot_logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+ bool boot_logo_scale = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
+
+ Ref<Image> img = memnew(Image);
+ img->create(info.width, info.height, false, Image::FORMAT_RGBA8);
+ img->fill(boot_bg_color);
+
+ Ref<Image> img_bs;
+
+ if (boot_logo_path.length() > 0) {
+ img_bs = Ref<Image>(memnew(Image));
+ ImageLoader::load_image(boot_logo_path, img_bs);
+ }
+ if (!img_bs.is_valid()) {
+ img_bs = Ref<Image>(memnew(Image(boot_splash_png)));
+ }
+ if (img_bs.is_valid()) {
+ float aspect_ratio = (float)img_bs->get_width() / (float)img_bs->get_height();
+ if (info.rotate) {
+ if (boot_logo_scale) {
+ if (info.width * aspect_ratio <= info.height) {
+ img_bs->resize(info.width * aspect_ratio, info.width);
+ } else {
+ img_bs->resize(info.height, info.height / aspect_ratio);
+ }
+ }
+ } else {
+ if (boot_logo_scale) {
+ if (info.height * aspect_ratio <= info.width) {
+ img_bs->resize(info.height * aspect_ratio, info.height);
+ } else {
+ img_bs->resize(info.width, info.width / aspect_ratio);
+ }
+ }
+ }
+ _blend_and_rotate(img, img_bs, info.rotate);
+ }
+ Error err = img->save_png(p_dest_dir + info.export_name);
+ if (err) {
+ String err_str = String("Failed to export loading screen (") + info.preset_key + ") from splash screen.";
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+ } else {
+ String err_str = String("No loading screen (") + info.preset_key + ") specified.";
+ WARN_PRINT(err_str.utf8().get_data());
}
}
memdelete(da);
@@ -643,15 +913,6 @@ struct ExportLibsData {
};
void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
- Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
- Vector<String> frameworks;
- for (int i = 0; i < export_plugins.size(); ++i) {
- Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks();
- for (int j = 0; j < plugin_frameworks.size(); ++j) {
- frameworks.push_back(plugin_frameworks[j]);
- }
- }
-
// that is just a random number, we just need Godot IDs not to clash with
// existing IDs in the project.
PbxId current_id = { 0x58938401, 0, 0 };
@@ -660,17 +921,37 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
String pbx_frameworks_refs;
String pbx_resources_build;
String pbx_resources_refs;
+ String pbx_embeded_frameworks;
const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
- "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = $name; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
+ "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
+
for (int i = 0; i < p_additional_assets.size(); ++i) {
+ String additional_asset_info_format = file_info_format;
+
String build_id = (++current_id).str();
String ref_id = (++current_id).str();
+ String framework_id = "";
+
const IOSExportAsset &asset = p_additional_assets[i];
String type;
if (asset.exported_path.ends_with(".framework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
type = "wrapper.framework";
+ } else if (asset.exported_path.ends_with(".xcframework")) {
+ if (asset.should_embed) {
+ additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+ framework_id = (++current_id).str();
+ pbx_embeded_frameworks += framework_id + ",\n";
+ }
+
+ type = "wrapper.xcframework";
} else if (asset.exported_path.ends_with(".dylib")) {
type = "compiled.mach-o.dylib";
} else if (asset.exported_path.ends_with(".a")) {
@@ -695,7 +976,10 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
format_dict["name"] = asset.exported_path.get_file();
format_dict["file_path"] = asset.exported_path;
format_dict["file_type"] = type;
- pbx_files += file_info_format.format(format_dict, "$_");
+ if (framework_id.length() > 0) {
+ format_dict["framework_id"] = framework_id;
+ }
+ pbx_files += additional_asset_info_format.format(format_dict, "$_");
}
// Note, frameworks like gamekit are always included in our project.pbxprof file
@@ -729,6 +1013,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
+ str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks);
CharString cs = str.utf8();
p_project_data.resize(cs.size() - 1);
@@ -737,14 +1022,16 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
}
}
-Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) {
+Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ String binary_name = p_out_dir.get_file().get_basename();
+
ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
String asset = p_assets[f_idx];
if (!asset.begins_with("res://")) {
// either SDK-builtin or already a part of the export template
- IOSExportAsset exported_asset = { asset, p_is_framework };
+ IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed };
r_exported_assets.push_back(exported_asset);
} else {
DirAccess *da = DirAccess::create_for_path(asset);
@@ -759,8 +1046,42 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
memdelete(filesystem_da);
return ERR_FILE_NOT_FOUND;
}
- String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/";
- String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", "");
+
+ String base_dir = asset.get_base_dir().replace("res://", "");
+ String destination_dir;
+ String destination;
+ String asset_path;
+
+ bool create_framework = false;
+
+ if (p_is_framework && asset.ends_with(".dylib")) {
+ // For iOS we need to turn .dylib into .framework
+ // to be able to send application to AppStore
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name = asset.get_basename().get_file();
+ String framework_name = file_name + ".framework";
+
+ asset_path = asset_path.plus_file(framework_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir.plus_file(file_name);
+ create_framework = true;
+ } else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) {
+ asset_path = String("dylibs").plus_file(base_dir);
+
+ String file_name = asset.get_file();
+ asset_path = asset_path.plus_file(file_name);
+ destination_dir = p_out_dir.plus_file(asset_path);
+ destination = destination_dir;
+ } else {
+ asset_path = base_dir;
+
+ String file_name = asset.get_file();
+ destination_dir = p_out_dir.plus_file(asset_path);
+ asset_path = asset_path.plus_file(file_name);
+ destination = p_out_dir.plus_file(asset_path);
+ }
+
if (!filesystem_da->dir_exists(destination_dir)) {
Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
if (make_dir_err) {
@@ -770,15 +1091,66 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
}
}
- String destination = destination_dir.plus_file(asset.get_file());
Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
memdelete(da);
if (err) {
memdelete(filesystem_da);
return err;
}
- IOSExportAsset exported_asset = { destination, p_is_framework };
+ IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
r_exported_assets.push_back(exported_asset);
+
+ if (create_framework) {
+ String file_name = asset.get_basename().get_file();
+ String framework_name = file_name + ".framework";
+
+ // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+ {
+ List<String> install_name_args;
+ install_name_args.push_back("-id");
+ install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
+ install_name_args.push_back(destination);
+
+ OS::get_singleton()->execute("install_name_tool", install_name_args, true);
+ }
+
+ // Creating Info.plist
+ {
+ String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<dict>\n"
+ "<key>CFBundleShortVersionString</key>\n"
+ "<string>1.0</string>\n"
+ "<key>CFBundleIdentifier</key>\n"
+ "<string>com.gdnative.framework.$name</string>\n"
+ "<key>CFBundleName</key>\n"
+ "<string>$name</string>\n"
+ "<key>CFBundleExecutable</key>\n"
+ "<string>$name</string>\n"
+ "<key>DTPlatformName</key>\n"
+ "<string>iphoneos</string>\n"
+ "<key>CFBundleInfoDictionaryVersion</key>\n"
+ "<string>6.0</string>\n"
+ "<key>CFBundleVersion</key>\n"
+ "<string>1</string>\n"
+ "<key>CFBundlePackageType</key>\n"
+ "<string>FMWK</string>\n"
+ "<key>MinimumOSVersion</key>\n"
+ "<string>10.0</string>\n"
+ "</dict>\n"
+ "</plist>";
+
+ String info_plist = info_plist_format.replace("$name", file_name);
+
+ FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
+ if (f) {
+ f->store_string(info_plist);
+ f->close();
+ memdelete(f);
+ }
+ }
+ }
}
}
memdelete(filesystem_da);
@@ -787,13 +1159,25 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
}
Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
- Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
for (int i = 0; i < export_plugins.size(); i++) {
- Vector<String> frameworks = export_plugins[i]->get_ios_frameworks();
- Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets);
+ Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks();
+ Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
+
+ Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks();
+ err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
+ Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
+ for (int j = 0; j < project_static_libs.size(); j++) {
+ project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
+ }
+ err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets);
+ ERR_FAIL_COND_V(err, err);
+
Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
- err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets);
+ err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
}
@@ -801,7 +1185,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
for (int i = 0; i < p_libraries.size(); ++i) {
library_paths.push_back(p_libraries[i].path);
}
- Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets);
+ Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
return OK;
@@ -847,10 +1231,11 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String team_id = p_preset->get("application/app_store_team_id");
ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project.");
- if (p_debug)
+ if (p_debug) {
src_pkg_name = p_preset->get("custom_template/debug");
- else
+ } else {
src_pkg_name = p_preset->get("custom_template/release");
+ }
if (src_pkg_name == "") {
String err;
@@ -896,8 +1281,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String pack_path = dest_dir + binary_name + ".pck";
Vector<SharedObject> libraries;
Error err = save_pack(p_preset, pack_path, &libraries);
- if (err)
+ if (err) {
return err;
+ }
if (ep.step("Extracting and configuring Xcode project", 1)) {
return ERR_SKIP;
@@ -907,12 +1293,13 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
print_line("Static library: " + library_to_use);
String pkg_name;
- if (p_preset->get("application/name") != "")
+ if (p_preset->get("application/name") != "") {
pkg_name = p_preset->get("application/name"); // app_name
- else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "")
+ } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
- else
+ } else {
pkg_name = "Unnamed";
+ }
bool found_library = false;
int total_size = 0;
@@ -925,6 +1312,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
files_to_parse.insert("godot_ios/dummy.cpp");
files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata");
files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme");
+ files_to_parse.insert("godot_ios/godot_ios.entitlements");
+ files_to_parse.insert("godot_ios/Launch Screen.storyboard");
IOSConfigData config_data = {
pkg_name,
@@ -943,7 +1332,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
print_line("Unzipping...");
- FileAccess *src_f = NULL;
+ FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
if (!src_pkg_zip) {
@@ -965,7 +1354,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
//get filename
unz_file_info info;
char fname[16384];
- ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, NULL, 0, NULL, 0);
+ ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
String file = fname;
@@ -1030,7 +1419,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
print_line("Creating " + dir_name);
Error dir_err = tmp_app_path->make_dir_recursive(dir_name);
if (dir_err) {
- ERR_PRINTS("Can't create '" + dir_name + "'.");
+ ERR_PRINT("Can't create '" + dir_name + "'.");
unzClose(src_pkg_zip);
memdelete(tmp_app_path);
return ERR_CANT_CREATE;
@@ -1040,7 +1429,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
/* write the file */
FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
if (!f) {
- ERR_PRINTS("Can't write '" + file + "'.");
+ ERR_PRINT("Can't write '" + file + "'.");
unzClose(src_pkg_zip);
memdelete(tmp_app_path);
return ERR_CANT_CREATE;
@@ -1064,27 +1453,82 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
unzClose(src_pkg_zip);
if (!found_library) {
- ERR_PRINTS("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
+ ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
memdelete(tmp_app_path);
return ERR_FILE_NOT_FOUND;
}
+ // Copy project static libs to the project
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ for (int i = 0; i < export_plugins.size(); i++) {
+ Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
+ for (int j = 0; j < project_static_libs.size(); j++) {
+ const String &static_lib_path = project_static_libs[j];
+ String dest_lib_file_path = dest_dir + static_lib_path.get_file();
+ Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path);
+ if (lib_copy_err != OK) {
+ ERR_PRINT("Can't copy '" + static_lib_path + "'.");
+ memdelete(tmp_app_path);
+ return lib_copy_err;
+ }
+ }
+ }
+
String iconset_dir = dest_dir + binary_name + "/Images.xcassets/AppIcon.appiconset/";
err = OK;
if (!tmp_app_path->dir_exists(iconset_dir)) {
err = tmp_app_path->make_dir_recursive(iconset_dir);
}
memdelete(tmp_app_path);
- if (err)
+ if (err) {
return err;
+ }
err = _export_icons(p_preset, iconset_dir);
- if (err)
+ if (err) {
return err;
+ }
+
+ bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard");
+
+ String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/";
+ String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/";
- err = _export_loading_screens(p_preset, dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/");
- if (err)
+ DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ if (!launch_screen_da) {
+ return ERR_CANT_CREATE;
+ }
+
+ if (use_storyboard) {
+ print_line("Using Launch Storyboard");
+
+ if (launch_screen_da->change_dir(launch_image_path) == OK) {
+ launch_screen_da->erase_contents_recursive();
+ launch_screen_da->remove(launch_image_path);
+ }
+
+ err = _export_loading_screen_file(p_preset, splash_image_path);
+ } else {
+ print_line("Using Launch Images");
+
+ const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard";
+
+ launch_screen_da->remove(launch_screen_path);
+
+ if (launch_screen_da->change_dir(splash_image_path) == OK) {
+ launch_screen_da->erase_contents_recursive();
+ launch_screen_da->remove(splash_image_path);
+ }
+
+ err = _export_loading_screen_images(p_preset, launch_image_path);
+ }
+
+ memdelete(launch_screen_da);
+
+ if (err) {
return err;
+ }
print_line("Exporting additional assets");
Vector<IOSExportAsset> assets;
@@ -1093,7 +1537,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
if (!f) {
- ERR_PRINTS("Can't write '" + project_file_name + "'.");
+ ERR_PRINT("Can't write '" + project_file_name + "'.");
return ERR_CANT_CREATE;
};
f->store_buffer(project_file_data.ptr(), project_file_data.size());
@@ -1154,7 +1598,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
}
bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
-
String err;
bool valid = false;
@@ -1187,7 +1630,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
valid = false;
}
- String identifier = p_preset->get("application/identifier");
+ String identifier = p_preset->get("application/bundle_identifier");
String pn_err;
if (!is_package_name_valid(identifier, &pn_err)) {
err += TTR("Invalid Identifier:") + " " + pn_err + "\n";
@@ -1212,14 +1655,14 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
err += etc_error;
}
- if (!err.empty())
+ if (!err.empty()) {
r_error = err;
+ }
return valid;
}
EditorExportPlatformIOS::EditorExportPlatformIOS() {
-
Ref<Image> img = memnew(Image(_iphone_logo));
logo.instance();
logo->create_from_image(img);
@@ -1229,7 +1672,6 @@ EditorExportPlatformIOS::~EditorExportPlatformIOS() {
}
void register_iphone_exporter() {
-
Ref<EditorExportPlatformIOS> platform;
platform.instance();
diff --git a/platform/iphone/export/export.h b/platform/iphone/export/export.h
index 77b2a07bd1..043d21f533 100644
--- a/platform/iphone/export/export.h
+++ b/platform/iphone/export/export.h
@@ -28,4 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef IPHONE_EXPORT_H
+#define IPHONE_EXPORT_H
+
void register_iphone_exporter();
+
+#endif // IPHONE_EXPORT_H
diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h
index d35cc4b87c..1e9a68fe48 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/game_center.h
@@ -36,7 +36,6 @@
#include "core/object.h"
class GameCenter : public Object {
-
GDCLASS(GameCenter, Object);
static GameCenter *instance;
@@ -52,12 +51,12 @@ public:
void connect();
bool is_authenticated();
- Error post_score(Variant p_score);
- Error award_achievement(Variant p_params);
+ Error post_score(Dictionary p_score);
+ Error award_achievement(Dictionary p_params);
void reset_achievements();
void request_achievements();
void request_achievement_descriptions();
- Error show_game_center(Variant p_params);
+ Error show_game_center(Dictionary p_params);
Error request_identity_verification_signature();
void game_center_closed();
diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm
index 696f61f954..4481775c32 100644
--- a/platform/iphone/game_center.mm
+++ b/platform/iphone/game_center.mm
@@ -47,13 +47,15 @@ extern "C" {
#import "app_delegate.h"
};
+#import "view_controller.h"
+
GameCenter *GameCenter::instance = NULL;
void GameCenter::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
- ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
+ ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
@@ -75,7 +77,6 @@ void GameCenter::return_connect_error(const char *p_error_description) {
}
void GameCenter::connect() {
-
//if this class isn't available, game center isn't implemented
if ((NSClassFromString(@"GKLocalPlayer")) == nil) {
return_connect_error("GameCenter not available");
@@ -106,7 +107,14 @@ void GameCenter::connect() {
ret["type"] = "authentication";
if (player.isAuthenticated) {
ret["result"] = "ok";
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
+
GameCenter::get_singleton()->authenticated = true;
} else {
ret["result"] = "error";
@@ -124,12 +132,10 @@ bool GameCenter::is_authenticated() {
return authenticated;
};
-Error GameCenter::post_score(Variant p_score) {
-
- Dictionary params = p_score;
- ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
- float score = params["score"];
- String category = params["category"];
+Error GameCenter::post_score(Dictionary p_score) {
+ ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
+ float score = p_score["score"];
+ String category = p_score["category"];
NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
@@ -155,12 +161,10 @@ Error GameCenter::post_score(Variant p_score) {
return OK;
};
-Error GameCenter::award_achievement(Variant p_params) {
-
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
- String name = params["name"];
- float progress = params["progress"];
+Error GameCenter::award_achievement(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
+ String name = p_params["name"];
+ float progress = p_params["progress"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
@@ -170,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {
achievement.percentComplete = progress;
achievement.showsCompletionBanner = NO;
- if (params.has("show_completion_banner")) {
- achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
+ if (p_params.has("show_completion_banner")) {
+ achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
}
[GKAchievement reportAchievements:@[ achievement ]
@@ -192,22 +196,20 @@ Error GameCenter::award_achievement(Variant p_params) {
};
void GameCenter::request_achievement_descriptions() {
-
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
Dictionary ret;
ret["type"] = "achievement_descriptions";
if (error == nil) {
ret["result"] = "ok";
- PoolStringArray names;
- PoolStringArray titles;
- PoolStringArray unachieved_descriptions;
- PoolStringArray achieved_descriptions;
- PoolIntArray maximum_points;
+ PackedStringArray names;
+ PackedStringArray titles;
+ PackedStringArray unachieved_descriptions;
+ PackedStringArray achieved_descriptions;
+ PackedInt32Array maximum_points;
Array hidden;
Array replayable;
- for (int i = 0; i < [descriptions count]; i++) {
-
+ for (NSUInteger i = 0; i < [descriptions count]; i++) {
GKAchievementDescription *description = [descriptions objectAtIndex:i];
const char *str = [description.identifier UTF8String];
@@ -247,17 +249,15 @@ void GameCenter::request_achievement_descriptions() {
};
void GameCenter::request_achievements() {
-
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
Dictionary ret;
ret["type"] = "achievements";
if (error == nil) {
ret["result"] = "ok";
- PoolStringArray names;
- PoolRealArray percentages;
-
- for (int i = 0; i < [achievements count]; i++) {
+ PackedStringArray names;
+ PackedFloat32Array percentages;
+ for (NSUInteger i = 0; i < [achievements count]; i++) {
GKAchievement *achievement = [achievements objectAtIndex:i];
const char *str = [achievement.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
@@ -278,7 +278,6 @@ void GameCenter::request_achievements() {
};
void GameCenter::reset_achievements() {
-
[GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) {
Dictionary ret;
ret["type"] = "reset_achievements";
@@ -293,15 +292,12 @@ void GameCenter::reset_achievements() {
}];
};
-Error GameCenter::show_game_center(Variant p_params) {
-
+Error GameCenter::show_game_center(Dictionary p_params) {
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
- Dictionary params = p_params;
-
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
- if (params.has("view")) {
- String view_name = params["view"];
+ if (p_params.has("view")) {
+ String view_name = p_params["view"];
if (view_name == "default") {
view_state = GKGameCenterViewControllerStateDefault;
} else if (view_name == "leaderboards") {
@@ -315,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {
}
}
- GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
+ GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];
ERR_FAIL_COND_V(!controller, FAILED);
ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
@@ -325,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {
controller.viewState = view_state;
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
controller.leaderboardIdentifier = nil;
- if (params.has("leaderboard_name")) {
- String name = params["leaderboard_name"];
+ if (p_params.has("leaderboard_name")) {
+ String name = p_params["leaderboard_name"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
controller.leaderboardIdentifier = name_str;
}
@@ -338,7 +334,6 @@ Error GameCenter::show_game_center(Variant p_params) {
};
Error GameCenter::request_identity_verification_signature() {
-
ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED);
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
@@ -351,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
ret["timestamp"] = timestamp;
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
@@ -365,7 +366,6 @@ Error GameCenter::request_identity_verification_signature() {
};
void GameCenter::game_center_closed() {
-
Dictionary ret;
ret["type"] = "show_game_center";
ret["result"] = "ok";
@@ -373,12 +373,10 @@ void GameCenter::game_center_closed() {
}
int GameCenter::get_pending_event_count() {
-
return pending_events.size();
};
Variant GameCenter::pop_pending_event() {
-
Variant front = pending_events.front()->get();
pending_events.pop_front();
@@ -395,6 +393,6 @@ GameCenter::GameCenter() {
authenticated = false;
};
-GameCenter::~GameCenter(){};
+GameCenter::~GameCenter() {}
#endif
diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h
deleted file mode 100644
index 975aa4b70a..0000000000
--- a/platform/iphone/gl_view.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*************************************************************************/
-/* gl_view.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import <AVFoundation/AVFoundation.h>
-#import <MediaPlayer/MediaPlayer.h>
-#import <OpenGLES/EAGL.h>
-#import <OpenGLES/ES1/gl.h>
-#import <OpenGLES/ES1/glext.h>
-#import <UIKit/UIKit.h>
-
-@protocol GLViewDelegate;
-
-@interface GLView : UIView <UIKeyInput> {
-@private
- // The pixel dimensions of the backbuffer
- GLint backingWidth;
- GLint backingHeight;
-
- EAGLContext *context;
-
- // OpenGL names for the renderbuffer and framebuffers used to render to this view
- GLuint viewRenderbuffer, viewFramebuffer;
-
- // OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
- GLuint depthRenderbuffer;
-
- BOOL useCADisplayLink;
- // CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
- CADisplayLink *displayLink;
-
- // An animation timer that, when animation is started, will periodically call -drawView at the given rate.
- // Only used if CADisplayLink is not
- NSTimer *animationTimer;
-
- NSTimeInterval animationInterval;
-
- // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
- id<GLViewDelegate> delegate;
-
- // Flag to denote that the -setupView method of a delegate has been called.
- // Resets to NO whenever the delegate changes.
- BOOL delegateSetup;
- BOOL active;
- float screen_scale;
-}
-
-@property(nonatomic, assign) id<GLViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property NSTimeInterval animationInterval;
-@property(nonatomic, assign) BOOL useCADisplayLink;
-
-@end
-
-@protocol GLViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GLView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GLView *)view;
-
-@end
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
deleted file mode 100644
index e8d737d9c3..0000000000
--- a/platform/iphone/gl_view.mm
+++ /dev/null
@@ -1,733 +0,0 @@
-/*************************************************************************/
-/* gl_view.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import "gl_view.h"
-
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "os_iphone.h"
-#include "servers/audio_server.h"
-
-#import <OpenGLES/EAGLDrawable.h>
-#import <QuartzCore/QuartzCore.h>
-
-/*
-@interface GLView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-bool gles3_available = true;
-int gl_view_base_fb;
-static String keyboard_text;
-static GLView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
- keyboard_text = p_existing;
- printf("instance on show is %p\n", _instance);
- [_instance open_keyboard];
-};
-
-void _hide_keyboard() {
- printf("instance on hide is %p\n", _instance);
- [_instance hide_keyboard];
- keyboard_text = "";
-};
-
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
- UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
- if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
- insets = [_instance safeAreaInsets];
- }
- ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
- Rect2(0, 0, p_window_width, p_window_height));
- UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
- return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
-}
-
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
- NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
-
- _instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
-
- _instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
- [_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-
- _instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
- _instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:_instance
- selector:@selector(playerItemDidReachEnd:)
- name:AVPlayerItemDidPlayToEndTimeNotification
- object:[_instance.avPlayer currentItem]];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
- [_instance.avPlayerLayer setFrame:_instance.bounds];
- [_instance.layer addSublayer:_instance.avPlayerLayer];
- [_instance.avPlayer play];
-
- AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
- NSMutableArray *allAudioParams = [NSMutableArray array];
- for (id track in audioGroup.options) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
- AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
- [audioInputParams setVolume:p_volume atTime:kCMTimeZero];
- [audioInputParams setTrackID:[track trackID]];
- [allAudioParams addObject:audioInputParams];
-
- AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
- [audioMix setInputParameters:allAudioParams];
-
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
- [_instance.avPlayer.currentItem setAudioMix:audioMix];
-
- break;
- }
- }
-
- AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
- NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
- for (id track in useableTracks) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
- break;
- }
- }
-
- video_playing = true;
-
- return true;
-}
-
-bool _is_video_playing() {
- if (_instance.avPlayer.error) {
- printf("Error during playback\n");
- }
- return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
-
-void _pause_video() {
- video_current_time = _instance.avPlayer.currentTime;
- [_instance.avPlayer pause];
- video_playing = false;
-}
-
-void _focus_out_video() {
- printf("focus out pausing video\n");
- [_instance.avPlayer pause];
-};
-
-void _unpause_video() {
-
- [_instance.avPlayer play];
- video_playing = true;
-};
-
-void _stop_video() {
- [_instance.avPlayer pause];
- [_instance.avPlayerLayer removeFromSuperlayer];
- _instance.avPlayer = nil;
- video_playing = false;
-}
-
-CGFloat _points_to_pixels(CGFloat points) {
- float pixelPerInch;
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
- pixelPerInch = 132;
- } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
- pixelPerInch = 163;
- } else {
- pixelPerInch = 160;
- }
- CGFloat pointsPerInch = 72.0;
- return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GLView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
-
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-static int get_touch_id(UITouch *p_touch) {
-
- int first = -1;
- for (int i = 0; i < max_touches; i++) {
- if (first == -1 && touches[i] == NULL) {
- first = i;
- continue;
- };
- if (touches[i] == p_touch)
- return i;
- };
-
- if (first != -1) {
- touches[first] = p_touch;
- return first;
- };
-
- return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
-
- int remaining = 0;
- for (int i = 0; i < max_touches; i++) {
-
- if (touches[i] == NULL)
- continue;
- if (touches[i] == p_touch)
- touches[i] = NULL;
- else
- ++remaining;
- };
- return remaining;
-};
-
-static void clear_touches() {
-
- for (int i = 0; i < max_touches; i++) {
-
- touches[i] = NULL;
- };
-};
-
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
- return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
- active = FALSE;
- if ((self = [super initWithCoder:coder])) {
- self = [self initGLES];
- }
- return self;
-}
-
-- (id)initGLES {
- // Get our backing layer
- CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
- // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
- eaglLayer.opaque = YES;
- eaglLayer.drawableProperties = [NSDictionary
- dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
- kEAGLDrawablePropertyRetainedBacking,
- kEAGLColorFormatRGBA8,
- kEAGLDrawablePropertyColorFormat,
- nil];
- bool fallback_gl2 = false;
- // Create a GL ES 3 context based on the gl driver from project settings
- if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3") {
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
- NSLog(@"Setting up an OpenGL ES 3.0 context. Based on Project Settings \"rendering/quality/driver/driver_name\"");
- if (!context && GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- gles3_available = false;
- fallback_gl2 = true;
- NSLog(@"Failed to create OpenGL ES 3.0 context. Falling back to OpenGL ES 2.0");
- }
- }
-
- // Create GL ES 2 context
- if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2" || fallback_gl2) {
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- NSLog(@"Setting up an OpenGL ES 2.0 context.");
- if (!context) {
- NSLog(@"Failed to create OpenGL ES 2.0 context!");
- return nil;
- }
- }
-
- if (![EAGLContext setCurrentContext:context]) {
- NSLog(@"Failed to set EAGLContext!");
- return nil;
- }
- if (![self createFramebuffer]) {
- NSLog(@"Failed to create frame buffer!");
- return nil;
- }
-
- // Default the animation interval to 1/60th of a second.
- animationInterval = 1.0 / 60.0;
- return self;
-}
-
-- (id<GLViewDelegate>)delegate {
- return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GLViewDelegate>)d {
- delegate = d;
- delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
-}
-
-@synthesize useCADisplayLink;
-
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
- [EAGLContext setCurrentContext:context];
- [self destroyFramebuffer];
- [self createFramebuffer];
- [self drawView];
-}
-
-- (BOOL)createFramebuffer {
- // Generate IDs for a framebuffer object and a color renderbuffer
- UIScreen *mainscr = [UIScreen mainScreen];
- printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
- self.contentScaleFactor = mainscr.nativeScale;
-
- glGenFramebuffersOES(1, &viewFramebuffer);
- glGenRenderbuffersOES(1, &viewRenderbuffer);
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
- // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
- [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
- // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
- glGenRenderbuffersOES(1, &depthRenderbuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
- glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
- if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
- NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
- return NO;
- }
-
- if (OS::get_singleton()) {
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- OS::get_singleton()->set_video_mode(vm);
- OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
- };
- gl_view_base_fb = viewFramebuffer;
-
- return YES;
-}
-
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
- glDeleteFramebuffersOES(1, &viewFramebuffer);
- viewFramebuffer = 0;
- glDeleteRenderbuffersOES(1, &viewRenderbuffer);
- viewRenderbuffer = 0;
-
- if (depthRenderbuffer) {
- glDeleteRenderbuffersOES(1, &depthRenderbuffer);
- depthRenderbuffer = 0;
- }
-}
-
-- (void)startAnimation {
- if (active)
- return;
- active = TRUE;
- printf("start animation!\n");
- if (useCADisplayLink) {
-
- // Approximate frame rate
- // assumes device refreshes at 60 fps
- int frameInterval = (int)floor(animationInterval * 60.0f);
-
- displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
- [displayLink setFrameInterval:frameInterval];
-
- // Setup DisplayLink in main thread
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- } else {
- animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
- }
-
- if (video_playing) {
- _unpause_video();
- }
-}
-
-- (void)stopAnimation {
- if (!active)
- return;
- active = FALSE;
- printf("******** stop animation!\n");
-
- if (useCADisplayLink) {
- [displayLink invalidate];
- displayLink = nil;
- } else {
- [animationTimer invalidate];
- animationTimer = nil;
- }
-
- clear_touches();
-
- if (video_playing) {
- // save position
- }
-}
-
-- (void)setAnimationInterval:(NSTimeInterval)interval {
- animationInterval = interval;
- if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
- [self stopAnimation];
- [self startAnimation];
- }
-}
-
-// Updates the OpenGL view when the timer fires
-- (void)drawView {
-
- if (!active) {
- printf("draw view not active!\n");
- return;
- };
- if (useCADisplayLink) {
- // Pause the CADisplayLink to avoid recursion
- [displayLink setPaused:YES];
-
- // Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
- ;
-
- // We are good to go, resume the CADisplayLink
- [displayLink setPaused:NO];
- }
-
- // Make sure that you are drawing to the current context
- [EAGLContext setCurrentContext:context];
-
- // If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
- if (!delegateSetup) {
- [delegate setupView:self];
- delegateSetup = YES;
- }
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
- [delegate drawView:self];
-
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- [context presentRenderbuffer:GL_RENDERBUFFER_OES];
-
-#ifdef DEBUG_ENABLED
- GLenum err = glGetError();
- if (err)
- NSLog(@"DrawView: %x error", err);
-#endif
-}
-
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
-
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
-
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseBegan)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
- };
- };
-}
-
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
-
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
-
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
-
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseMoved)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- CGPoint prev_point = [touch previousLocationInView:self];
- OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
- };
- };
-}
-
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
-
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
-
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseEnded)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- remove_touch(touch);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
- };
- };
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
-
- OSIPhone::get_singleton()->touches_cancelled();
- clear_touches();
-};
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-};
-
-- (void)open_keyboard {
- //keyboard_text = p_existing;
- [self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
- //keyboard_text = p_existing;
- [self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
- NSDictionary *info = notification.userInfo;
- NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
- CGRect rawFrame = [value CGRectValue];
- CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
-
- OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
-}
-
-- (void)keyboardHidden:(NSNotification *)notification {
- OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length())
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
-
-- (BOOL)hasText {
- return keyboard_text.length() ? YES : NO;
-};
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
- printf("inserting text with character %lc\n", (CharType)character[0]);
-};
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
- printf("*********** route changed!\n");
- NSDictionary *interuptionDict = notification.userInfo;
-
- NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
- switch (routeChangeReason) {
-
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
- NSLog(@"Headphone/Line plugged in");
- }; break;
-
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
- NSLog(@"Headphone/Line was pulled. Resuming video play....");
- if (_is_video_playing()) {
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
- };
- }; break;
-
- case AVAudioSessionRouteChangeReasonCategoryChange: {
- // called at start - also when other audio wants to play
- NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
- }; break;
- }
-}
-
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- _instance = self;
- printf("after init super %p\n", self);
- if (self != nil) {
- self = [self initGLES];
- printf("after init gles %p\n", self);
- }
- init_touches();
- self.multipleTouchEnabled = YES;
- self.autocorrectionType = UITextAutocorrectionTypeNo;
-
- printf("******** adding observer for sound routing changes\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-
- printf("******** adding observer for keyboard show/hide\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardOnScreen:)
- name:UIKeyboardDidShowNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardHidden:)
- name:UIKeyboardDidHideNotification
- object:nil];
-
- //self.autoresizesSubviews = YES;
- //[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
-
- return self;
-}
-
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-// return YES;
-//}
-
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-// return YES;
-//}
-
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
- [self stopAnimation];
-
- if ([EAGLContext currentContext] == context) {
- [EAGLContext setCurrentContext:nil];
- }
-
- [context release];
- context = nil;
-
- [super dealloc];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
-
- if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
- if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
- _stop_video();
- video_found_error = true;
- }
-
- if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
- _instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
- CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
-
- //NSLog(@"time: %@", video_current_time);
-
- [_instance.avPlayer seekToTime:video_current_time];
- video_current_time = kCMTimeZero;
- }
- }
-
- if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
- NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
- if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
-
- NSLog(@" . . . PAUSED (or just started)");
- }
- }
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
- _stop_video();
-}
-
-@end
diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm
index 8c3eddc5f7..090b772947 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_iphone.cpp */
+/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -36,21 +36,55 @@
#include <string.h>
#include <unistd.h>
-static OSIPhone *os = NULL;
+static OSIPhone *os = nullptr;
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
+int add_path(int, char **);
+int add_cmdline(int, char **);
+int iphone_main(int, char **, String);
+
+int add_path(int p_argc, char **p_args) {
+ NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+ if (!str) {
+ return p_argc;
+ }
+
+ p_args[p_argc++] = (char *)"--path";
+ [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ p_args[p_argc] = NULL;
+ [str release];
+
+ return p_argc;
};
-int iphone_main(int, int, int, char **, String);
+int add_cmdline(int p_argc, char **p_args) {
+ NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr) {
+ return p_argc;
+ }
+
+ for (NSUInteger i = 0; i < [arr count]; i++) {
+ NSString *str = [arr objectAtIndex:i];
+ if (!str) {
+ continue;
+ }
+ [str retain]; // @todo delete these at some point
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ [str release];
+ };
+
+ p_args[p_argc] = NULL;
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+ return p_argc;
+};
+int iphone_main(int argc, char **argv, String data_dir) {
size_t len = strlen(argv[0]);
while (len--) {
- if (argv[0][len] == '/') break;
+ if (argv[0][len] == '/') {
+ break;
+ }
}
if (len >= 0) {
@@ -65,27 +99,33 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
- os = new OSIPhone(width, height, data_dir);
+ os = new OSIPhone(data_dir);
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
char *fargv[64];
for (int i = 0; i < argc; i++) {
fargv[i] = argv[i];
};
- fargv[argc] = NULL;
+ fargv[argc] = nullptr;
argc = add_path(argc, fargv);
argc = add_cmdline(argc, fargv);
printf("os created\n");
+
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
- if (err != OK)
+ if (err != OK) {
return 255;
+ }
+
+ os->initialize_modules();
return 0;
};
void iphone_finish() {
-
printf("iphone_finish\n");
Main::cleanup();
delete os;
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
new file mode 100644
index 0000000000..62fa2f5a32
--- /dev/null
+++ b/platform/iphone/godot_view.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* godot_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+class String;
+
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
+
+@interface GodotView : UIView <UIKeyInput>
+
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
+
+@property(assign, readonly, nonatomic) BOOL isActive;
+
+@property(assign, nonatomic) BOOL useCADisplayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
+
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+- (void)stopRendering;
+- (void)startRendering;
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing;
+
+@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
new file mode 100644
index 0000000000..c0a31549c4
--- /dev/null
+++ b/platform/iphone/godot_view.mm
@@ -0,0 +1,499 @@
+/*************************************************************************/
+/* godot_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view.h"
+#include "core/os/keyboard.h"
+#include "core/ustring.h"
+#import "display_layer.h"
+#include "display_server_iphone.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
+
+#import <CoreMotion/CoreMotion.h>
+
+static const int max_touches = 8;
+
+@interface GodotView () {
+ UITouch *godot_touches[max_touches];
+ String keyboard_text;
+}
+
+@property(assign, nonatomic) BOOL isActive;
+
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
+
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
+
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+
+@property(strong, nonatomic) CMMotionManager *motionManager;
+
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
+@end
+
+@implementation GodotView
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+ if (self.renderingLayer) {
+ return self.renderingLayer;
+ }
+
+ CALayer<DisplayLayer> *layer;
+
+ if ([driverName isEqualToString:@"vulkan"]) {
+ layer = [GodotMetalLayer layer];
+ } else if ([driverName isEqualToString:@"opengl_es"]) {
+ if (@available(iOS 13, *)) {
+ NSLog(@"OpenGL ES is deprecated on iOS 13");
+ }
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ return nil;
+#else
+ layer = [GodotOpenGLLayer layer];
+#endif
+ } else {
+ return nil;
+ }
+
+ layer.frame = self.bounds;
+ layer.contentsScale = self.contentScaleFactor;
+
+ [self.layer addSublayer:layer];
+ self.renderingLayer = layer;
+
+ [layer initializeDisplayLayer];
+
+ return self.renderingLayer;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self stopRendering];
+
+ self.renderer = nil;
+
+ if (self.renderingLayer) {
+ [self.renderingLayer removeFromSuperlayer];
+ self.renderingLayer = nil;
+ }
+
+ if (self.motionManager) {
+ [self.motionManager stopDeviceMotionUpdates];
+ self.motionManager = nil;
+ }
+
+ if (self.displayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ }
+
+ if (self.animationTimer) {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ if (self.delayGestureRecognizer) {
+ self.delayGestureRecognizer = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)godot_commonInit {
+ self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
+
+ [self initTouches];
+
+ // Configure and start accelerometer
+ if (!self.motionManager) {
+ self.motionManager = [[[CMMotionManager alloc] init] autorelease];
+ if (self.motionManager.deviceMotionAvailable) {
+ self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+ } else {
+ self.motionManager = nil;
+ }
+ }
+
+ // Initialize delay gesture recognizer
+ GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+ self.delayGestureRecognizer = gestureRecognizer;
+ [self addGestureRecognizer:self.delayGestureRecognizer];
+ [gestureRecognizer release];
+}
+
+- (void)stopRendering {
+ if (!self.isActive) {
+ return;
+ }
+
+ self.isActive = NO;
+
+ printf("******** stop animation!\n");
+
+ if (self.useCADisplayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ } else {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ [self clearTouches];
+}
+
+- (void)startRendering {
+ if (self.isActive) {
+ return;
+ }
+
+ self.isActive = YES;
+
+ printf("start animation!\n");
+
+ if (self.useCADisplayLink) {
+ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
+
+ // if (@available(iOS 10, *)) {
+ self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
+ // } else {
+ // // Approximate frame rate
+ // // assumes device refreshes at 60 fps
+ // int frameInterval = (int)floor(self.renderingInterval * 60.0f);
+ // [self.displayLink setFrameInterval:frameInterval];
+ // }
+
+ // Setup DisplayLink in main thread
+ [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ } else {
+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
+ }
+}
+
+- (void)drawView {
+ if (!self.isActive) {
+ printf("draw view not active!\n");
+ return;
+ }
+
+ if (self.useCADisplayLink) {
+ // Pause the CADisplayLink to avoid recursion
+ [self.displayLink setPaused:YES];
+
+ // Process all input events
+ while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
+ ;
+
+ // We are good to go, resume the CADisplayLink
+ [self.displayLink setPaused:NO];
+ }
+
+ [self.renderingLayer renderDisplayLayer];
+
+ if (!self.renderer) {
+ return;
+ }
+
+ if ([self.renderer setupView:self]) {
+ return;
+ }
+
+ [self handleMotion];
+ [self.renderer renderOnView:self];
+}
+
+- (BOOL)canRender {
+ if (self.useCADisplayLink) {
+ return self.displayLink != nil;
+ } else {
+ return self.animationTimer != nil;
+ }
+}
+
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+ _renderingInterval = renderingInterval;
+
+ if (self.canRender) {
+ [self stopRendering];
+ [self startRendering];
+ }
+}
+
+- (void)layoutSubviews {
+ if (self.renderingLayer) {
+ self.renderingLayer.frame = self.bounds;
+ [self.renderingLayer layoutDisplayLayer];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
+ }
+ }
+
+ [super layoutSubviews];
+}
+
+// MARK: - Input
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing {
+ keyboard_text = p_existing;
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ keyboard_text = String();
+ return [super resignFirstResponder];
+}
+
+- (void)deleteBackward {
+ if (keyboard_text.length()) {
+ keyboard_text.erase(keyboard_text.length() - 1, 1);
+ }
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+}
+
+- (BOOL)hasText {
+ return keyboard_text.length() > 0;
+}
+
+- (void)insertText:(NSString *)p_text {
+ String character;
+ character.parse_utf8([p_text UTF8String]);
+ keyboard_text = keyboard_text + character;
+ DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
+}
+
+// MARK: Touches
+
+- (void)initTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+ int first = -1;
+ for (int i = 0; i < max_touches; i++) {
+ if (first == -1 && godot_touches[i] == NULL) {
+ first = i;
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ return i;
+ }
+ }
+
+ if (first != -1) {
+ godot_touches[first] = p_touch;
+ return first;
+ }
+
+ return -1;
+}
+
+- (int)removeTouch:(UITouch *)p_touch {
+ int remaining = 0;
+ for (int i = 0; i < max_touches; i++) {
+ if (godot_touches[i] == NULL) {
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ godot_touches[i] = NULL;
+ } else {
+ ++remaining;
+ }
+ }
+ return remaining;
+}
+
+- (void)clearTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+ }
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ CGPoint prev_point = [touch previousLocationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+ }
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ [self removeTouch:touch];
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+ }
+ }
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
+ }
+ }
+ [self clearTouches];
+}
+
+// MARK: Motion
+
+- (void)handleMotion {
+ if (!self.motionManager) {
+ return;
+ }
+
+ // Just using polling approach for now, we can set this up so it sends
+ // data to us in intervals, might be better. See Apple reference pages
+ // for more details:
+ // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
+
+ // Apple splits our accelerometer date into a gravity and user movement
+ // component. We add them back together
+ CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+ CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
+
+ ///@TODO We don't seem to be getting data here, is my device broken or
+ /// is this code incorrect?
+ CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
+
+ ///@TODO we can access rotationRate as a CMRotationRate variable
+ ///(processed date) or CMGyroData (raw data), have to see what works
+ /// best
+ CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
+
+ // Adjust for screen orientation.
+ // [[UIDevice currentDevice] orientation] changes even if we've fixed
+ // our orientation which is not a good thing when you're trying to get
+ // your user to move the screen in all directions and want consistent
+ // output
+
+ ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+ /// is a bit of a hack. Godot obviously knows the orientation so maybe
+ /// we
+ // can use that instead? (note that left and right seem swapped)
+
+ UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
+ switch (interfaceOrientation) {
+ case UIInterfaceOrientationLandscapeLeft: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationPortraitUpsideDown: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+ } break;
+ default: { // assume portrait
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+ } break;
+ }
+}
+
+@end
diff --git a/platform/haiku/haiku_application.h b/platform/iphone/godot_view_gesture_recognizer.h
index 2e04d921bf..8d84914712 100644
--- a/platform/haiku/haiku_application.h
+++ b/platform/iphone/godot_view_gesture_recognizer.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* haiku_application.h */
+/* godot_view_gesture_recognizer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,16 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef HAIKU_APPLICATION_H
-#define HAIKU_APPLICATION_H
+// GLViewGestureRecognizer allows iOS gestures to work currectly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancellation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
-#include <kernel/image.h> // needed for image_id
+#import <UIKit/UIKit.h>
-#include <Application.h>
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
-class HaikuApplication : public BApplication {
-public:
- HaikuApplication();
-};
+- (instancetype)init;
-#endif
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.m
new file mode 100644
index 0000000000..377ccd52a5
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.m
@@ -0,0 +1,171 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_gesture_recognizer.h"
+
+// Using same delay interval that is used for `UIScrollView`
+const NSTimeInterval kGLGestureDelayInterval = 0.150;
+
+// Minimum distance for touches to move to fire
+// a delay timer before scheduled time.
+// Should be the low enough to not cause issues with dragging
+// but big enough to allow click to work.
+const CGFloat kGLGestureMovementDistance = 0.5;
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay begin touch message.
+// Should work as simple emulation of UIDelayedAction
+@property(strong, nonatomic) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(strong, nonatomic) NSSet *delayedTouches;
+@property(strong, nonatomic) UIEvent *delayedEvent;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+ self = [super init];
+
+ self.cancelsTouchesInView = YES;
+ self.delaysTouchesBegan = YES;
+ self.delaysTouchesEnded = YES;
+
+ return self;
+}
+
+- (void)dealloc {
+ if (self.delayTimer) {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+ }
+
+ if (self.delayedTouches) {
+ self.delayedTouches = nil;
+ }
+
+ if (self.delayedEvent) {
+ self.delayedEvent = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ self.delayedTouches = touches;
+ self.delayedEvent = event;
+
+ self.delayTimer = [NSTimer
+ scheduledTimerWithTimeInterval:kGLGestureDelayInterval
+ target:self
+ selector:@selector(fireDelayedTouches:)
+ userInfo:nil
+ repeats:NO];
+}
+
+- (void)fireDelayedTouches:(id)timer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+
+ if (self.delayedTouches) {
+ [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ }
+
+ self.delayedTouches = nil;
+ self.delayedEvent = nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
+ [self delayTouches:cleared andEvent:event];
+ [cleared release];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
+
+ if (self.delayTimer) {
+ // We should check if movement was significant enough to fire an event
+ // for dragging to work correctly.
+ for (UITouch *touch in cleared) {
+ CGPoint from = [touch locationInView:self.view];
+ CGPoint to = [touch previousLocationInView:self.view];
+ CGFloat xDistance = from.x - to.x;
+ CGFloat yDistance = from.y - to.y;
+
+ CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
+
+ // Early exit, since one of touches has moved enough to fire a drag event.
+ if (distance > kGLGestureMovementDistance) {
+ [self.delayTimer fire];
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+ return;
+ }
+ }
+
+ [cleared release];
+ return;
+ }
+
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
+ [self.view touchesEnded:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+ [self.view touchesCancelled:touches withEvent:event];
+};
+
+- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
+ NSMutableSet *cleared = [touches mutableCopy];
+
+ for (UITouch *touch in touches) {
+ if (touch.phase != phaseToSave) {
+ [cleared removeObject:touch];
+ }
+ }
+
+ return cleared;
+}
+
+@end
diff --git a/platform/haiku/godot_haiku.cpp b/platform/iphone/godot_view_renderer.h
index 0657f4c052..ea8998c808 100644
--- a/platform/haiku/godot_haiku.cpp
+++ b/platform/iphone/godot_view_renderer.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_haiku.cpp */
+/* godot_view_renderer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,22 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "main/main.h"
-#include "os_haiku.h"
+#import <UIKit/UIKit.h>
-int main(int argc, char *argv[]) {
- OS_Haiku os;
+@protocol GodotViewRendererProtocol <NSObject>
- Error error = Main::setup(argv[0], argc - 1, &argv[1]);
- if (error != OK) {
- return 255;
- }
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
- if (Main::start()) {
- os.run();
- }
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
- Main::cleanup();
+@end
- return os.get_exit_code();
-}
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
new file mode 100644
index 0000000000..045fb451f0
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.mm
@@ -0,0 +1,117 @@
+/*************************************************************************/
+/* godot_view_renderer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_renderer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#import "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+ if (self.hasFinishedSetup) {
+ return NO;
+ }
+
+ if (!OS::get_singleton()) {
+ exit(0);
+ }
+
+ if (!self.hasFinishedProjectDataSetup) {
+ [self setupProjectData];
+ return YES;
+ }
+
+ if (!self.hasStartedMain) {
+ self.hasStartedMain = YES;
+ OSIPhone::get_singleton()->start();
+ return YES;
+ }
+
+ self.hasFinishedSetup = YES;
+
+ return NO;
+}
+
+- (void)setupProjectData {
+ self.hasFinishedProjectDataSetup = YES;
+
+ Main::setup2();
+
+ // this might be necessary before here
+ NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+ for (NSString *key in dict) {
+ NSObject *value = [dict objectForKey:key];
+ String ukey = String::utf8([key UTF8String]);
+
+ // we need a NSObject to Variant conversor
+
+ if ([value isKindOfClass:[NSString class]]) {
+ NSString *str = (NSString *)value;
+ String uval = String::utf8([str UTF8String]);
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+ } else if ([value isKindOfClass:[NSNumber class]]) {
+ NSNumber *n = (NSNumber *)value;
+ double dval = [n doubleValue];
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+ };
+ // do stuff
+ }
+}
+
+- (void)renderOnView:(UIView *)view {
+ if (!OSIPhone::get_singleton()) {
+ return;
+ }
+
+ OSIPhone::get_singleton()->iterate();
+}
+
+@end
diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h
index 401a6cbeb8..381edfa718 100644
--- a/platform/iphone/icloud.h
+++ b/platform/iphone/icloud.h
@@ -36,7 +36,6 @@
#include "core/object.h"
class ICloud : public Object {
-
GDCLASS(ICloud, Object);
static ICloud *instance;
@@ -45,9 +44,9 @@ class ICloud : public Object {
List<Variant> pending_events;
public:
- Error remove_key(Variant p_param);
- Variant set_key_values(Variant p_param);
- Variant get_key_value(Variant p_param);
+ Error remove_key(String p_param);
+ Array set_key_values(Dictionary p_params);
+ Variant get_key_value(String p_param);
Error synchronize_key_values();
Variant get_all_key_values();
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
index f846043dde..d3086e6cea 100644
--- a/platform/iphone/icloud.mm
+++ b/platform/iphone/icloud.mm
@@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
+
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
+
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
@@ -58,12 +60,10 @@ void ICloud::_bind_methods() {
};
int ICloud::get_pending_event_count() {
-
return pending_events.size();
};
Variant ICloud::pop_pending_event() {
-
Variant front = pending_events.front()->get();
pending_events.pop_front();
@@ -80,20 +80,20 @@ Variant nsobject_to_variant(NSObject *object) {
const char *str = [(NSString *)object UTF8String];
return String::utf8(str != NULL ? str : "");
} else if ([object isKindOfClass:[NSData class]]) {
- PoolByteArray ret;
+ PackedByteArray ret;
NSData *data = (NSData *)object;
if ([data length] > 0) {
ret.resize([data length]);
{
- PoolByteArray::Write w = ret.write();
- copymem(w.ptr(), [data bytes], [data length]);
+ // PackedByteArray::Write w = ret.write();
+ copymem((void *)ret.ptr(), [data bytes], [data length]);
}
}
return ret;
} else if ([object isKindOfClass:[NSArray class]]) {
Array result;
NSArray *array = (NSArray *)object;
- for (unsigned int i = 0; i < [array count]; ++i) {
+ for (NSUInteger i = 0; i < [array count]; ++i) {
NSObject *value = [array objectAtIndex:i];
result.push_back(nsobject_to_variant(value));
}
@@ -151,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {
NSObject *variant_to_nsobject(Variant v) {
if (v.get_type() == Variant::STRING) {
return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
- } else if (v.get_type() == Variant::REAL) {
+ } else if (v.get_type() == Variant::FLOAT) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
return [NSNumber numberWithLongLong:(long)(int)v];
@@ -161,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
Dictionary dic = v;
Array keys = dic.keys();
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
@@ -175,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
Array arr = v;
- for (unsigned int i = 0; i < arr.size(); ++i) {
+ for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
if (value == NULL) {
//trying to add something unsupported to the array. cancel the whole array
@@ -184,19 +184,18 @@ NSObject *variant_to_nsobject(Variant v) {
[result addObject:value];
}
return result;
- } else if (v.get_type() == Variant::POOL_BYTE_ARRAY) {
- PoolByteArray arr = v;
- PoolByteArray::Read r = arr.read();
- NSData *result = [NSData dataWithBytes:r.ptr() length:arr.size()];
+ } else if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
+ PackedByteArray arr = v;
+ // PackedByteArray::Read r = arr.read();
+ NSData *result = [NSData dataWithBytes:arr.ptr() length:arr.size()];
return result;
}
WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data());
return NULL;
}
-Error ICloud::remove_key(Variant p_param) {
- String param = p_param;
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Error ICloud::remove_key(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
@@ -209,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {
}
//return an array of the keys that could not be set
-Variant ICloud::set_key_values(Variant p_params) {
- Dictionary params = p_params;
- Array keys = params.keys();
+Array ICloud::set_key_values(Dictionary p_params) {
+ Array keys = p_params.keys();
Array error_keys;
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
String variant_key = keys[i];
- Variant variant_value = params[variant_key];
+ Variant variant_value = p_params[variant_key];
NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
if (key == NULL) {
@@ -239,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {
return error_keys;
}
-Variant ICloud::get_key_value(Variant p_param) {
- String param = p_param;
-
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Variant ICloud::get_key_value(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {
@@ -284,6 +280,7 @@ Error ICloud::synchronize_key_values() {
return FAILED;
}
}
+
/*
Error ICloud::initial_sync() {
//you sometimes have to write something to the store to get it to download new data. go apple!
@@ -298,6 +295,7 @@ Error ICloud::initial_sync() {
}
return synchronize();
}
+
*/
ICloud::ICloud() {
ERR_FAIL_COND(instance != NULL);
@@ -315,7 +313,7 @@ ICloud::ICloud() {
Dictionary ret;
ret["type"] = "key_value_changed";
- //PoolStringArray result_keys;
+ //PackedStringArray result_keys;
//Array result_values;
Dictionary keyValues;
String reason = "";
@@ -354,6 +352,6 @@ ICloud::ICloud() {
}];
}
-ICloud::~ICloud(){};
+ICloud::~ICloud() {}
#endif
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h
index 493877a5a7..beb58af2c7 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/in_app_store.h
@@ -36,7 +36,6 @@
#include "core/object.h"
class InAppStore : public Object {
-
GDCLASS(InAppStore, Object);
static InAppStore *instance;
@@ -45,9 +44,9 @@ class InAppStore : public Object {
List<Variant> pending_events;
public:
- Error request_product_info(Variant p_params);
+ Error request_product_info(Dictionary p_params);
Error restore_purchases();
- Error purchase(Variant p_params);
+ Error purchase(Dictionary p_params);
int get_pending_event_count();
Variant pop_pending_event();
diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm
index 855ab195b0..1477f92200 100644
--- a/platform/iphone/in_app_store.mm
+++ b/platform/iphone/in_app_store.mm
@@ -39,8 +39,10 @@ extern "C" {
bool auto_finish_transactions = true;
NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
+static NSArray *latestProducts;
@interface SKProduct (LocalizedPrice)
+
@property(nonatomic, readonly) NSString *localizedPrice;
@end
@@ -57,6 +59,7 @@ NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
[numberFormatter release];
return formattedString;
}
+
@end
InAppStore *InAppStore::instance = NULL;
@@ -80,20 +83,20 @@ void InAppStore::_bind_methods() {
@implementation ProductsDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
-
NSArray *products = response.products;
+ latestProducts = products;
+
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "ok";
- PoolStringArray titles;
- PoolStringArray descriptions;
- PoolRealArray prices;
- PoolStringArray ids;
- PoolStringArray localized_prices;
- PoolStringArray currency_codes;
+ PackedStringArray titles;
+ PackedStringArray descriptions;
+ PackedFloat32Array prices;
+ PackedStringArray ids;
+ PackedStringArray localized_prices;
+ PackedStringArray currency_codes;
for (NSUInteger i = 0; i < [products count]; i++) {
-
SKProduct *product = [products objectAtIndex:i];
const char *str = [product.localizedTitle UTF8String];
@@ -113,10 +116,9 @@ void InAppStore::_bind_methods() {
ret["localized_prices"] = localized_prices;
ret["currency_codes"] = currency_codes;
- PoolStringArray invalid_ids;
+ PackedStringArray invalid_ids;
for (NSString *ipid in response.invalidProductIdentifiers) {
-
invalid_ids.push_back(String::utf8([ipid UTF8String]));
};
ret["invalid_ids"] = invalid_ids;
@@ -128,17 +130,15 @@ void InAppStore::_bind_methods() {
@end
-Error InAppStore::request_product_info(Variant p_params) {
+Error InAppStore::request_product_info(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
-
- PoolStringArray pids = params["product_ids"];
+ PackedStringArray pids = p_params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
for (int i = 0; i < pids.size(); i++) {
- printf("******** adding %ls to product list\n", pids[i].c_str());
+ printf("******** adding %s to product list\n", pids[i].utf8().get_data());
NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease];
[array addObject:pid];
};
@@ -155,7 +155,6 @@ Error InAppStore::request_product_info(Variant p_params) {
};
Error InAppStore::restore_purchases() {
-
printf("restoring purchases!\n");
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
@@ -169,10 +168,8 @@ Error InAppStore::restore_purchases() {
@implementation TransObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
-
printf("transactions updated!\n");
for (SKPaymentTransaction *transaction in transactions) {
-
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
printf("status purchased!\n");
@@ -189,11 +186,9 @@ Error InAppStore::restore_purchases() {
int sdk_version = 6;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
-
NSURL *receiptFileURL = nil;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
-
// Get the transaction receipt file path location in the app bundle.
receiptFileURL = [bundle appStoreReceiptURL];
@@ -206,16 +201,16 @@ Error InAppStore::restore_purchases() {
// which is still available in iOS 7.
// Use SKPaymentTransaction's transactionReceipt.
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
} else {
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
NSString *receipt_to_send = nil;
if (receipt != nil) {
- receipt_to_send = [receipt description];
+ receipt_to_send = [receipt base64EncodedStringWithOptions:0];
}
Dictionary receipt_ret;
receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : "");
@@ -262,18 +257,32 @@ Error InAppStore::restore_purchases() {
@end
-Error InAppStore::purchase(Variant p_params) {
-
+Error InAppStore::purchase(Dictionary p_params) {
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
if (![SKPaymentQueue canMakePayments])
return ERR_UNAVAILABLE;
printf("purchasing!\n");
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
+
+ NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
+
+ SKProduct *product = nil;
- NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
- SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
+ if (latestProducts) {
+ for (SKProduct *storedProduct in latestProducts) {
+ if ([storedProduct.productIdentifier isEqualToString:pid]) {
+ product = storedProduct;
+ break;
+ }
+ }
+ }
+
+ if (!product) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
[defq addPayment:payment];
printf("purchase sent!\n");
@@ -286,7 +295,6 @@ int InAppStore::get_pending_event_count() {
};
Variant InAppStore::pop_pending_event() {
-
Variant front = pending_events.front()->get();
pending_events.pop_front();
@@ -294,12 +302,10 @@ Variant InAppStore::pop_pending_event() {
};
void InAppStore::_post_event(Variant p_event) {
-
pending_events.push_back(p_event);
};
void InAppStore::_record_purchase(String product_id) {
-
String skey = "purchased/" + product_id;
NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
@@ -307,7 +313,6 @@ void InAppStore::_record_purchase(String product_id) {
};
InAppStore *InAppStore::get_singleton() {
-
return instance;
};
@@ -334,6 +339,6 @@ void InAppStore::set_auto_finish_transaction(bool b) {
auto_finish_transactions = b;
}
-InAppStore::~InAppStore(){};
+InAppStore::~InAppStore() {}
#endif
diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h
index 1378fdbbc5..2b29e6f268 100644
--- a/platform/iphone/ios.h
+++ b/platform/iphone/ios.h
@@ -34,7 +34,6 @@
#include "core/object.h"
class iOS : public Object {
-
GDCLASS(iOS, Object);
static void _bind_methods();
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index 2656f125b9..6d7699c0c9 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -29,18 +29,27 @@
/*************************************************************************/
#include "ios.h"
-#include <sys/sysctl.h>
-
+#import "app_delegate.h"
#import <UIKit/UIKit.h>
+#include <sys/sysctl.h>
void iOS::_bind_methods() {
-
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
};
void iOS::alert(const char *p_alert, const char *p_title) {
- UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
- [alert show];
+ NSString *title = [NSString stringWithUTF8String:p_title];
+ NSString *message = [NSString stringWithUTF8String:p_alert];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(id){
+ }];
+
+ [alert addAction:button];
+
+ [AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {
@@ -77,8 +86,8 @@ String iOS::get_rate_url(int p_app_id) const {
// ios7 for everything?
ret = templ_iOS7.replace("APP_ID", String::num(p_app_id));
- printf("returning rate url %ls\n", ret.c_str());
+ printf("returning rate url %s\n", ret.utf8().get_data());
return ret;
};
-iOS::iOS(){};
+iOS::iOS() {}
diff --git a/platform/haiku/haiku_gl_view.h b/platform/iphone/joypad_iphone.h
index 59e02d2367..85e26e1dc8 100644
--- a/platform/haiku/haiku_gl_view.h
+++ b/platform/iphone/joypad_iphone.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* haiku_gl_view.h */
+/* joypad_iphone.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,18 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef HAIKU_GL_VIEW_H
-#define HAIKU_GL_VIEW_H
+#import <GameController/GameController.h>
-#include <kernel/image.h> // needed for image_id
+@interface JoypadIPhoneObserver : NSObject
-#include <GLView.h>
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
+
+@end
+
+class JoypadIPhone {
+private:
+ JoypadIPhoneObserver *observer;
-class HaikuGLView : public BGLView {
public:
- HaikuGLView(BRect frame, uint32 type);
- virtual void AttachedToWindow(void);
- virtual void Draw(BRect updateRect);
-};
+ JoypadIPhone();
+ ~JoypadIPhone();
-#endif
+ void start_processing();
+};
diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
new file mode 100644
index 0000000000..6088f1c25c
--- /dev/null
+++ b/platform/iphone/joypad_iphone.mm
@@ -0,0 +1,380 @@
+/*************************************************************************/
+/* joypad_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "joypad_iphone.h"
+#include "core/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "main/main.h"
+
+#import "godot_view.h"
+
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+ observer = [[JoypadIPhoneObserver alloc] init];
+ [observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+ if (observer) {
+ [observer finishObserving];
+ observer = nil;
+ }
+}
+
+void JoypadIPhone::start_processing() {
+ if (observer) {
+ [observer startProcessing];
+ }
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isObserving = NO;
+ self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+ self.isProcessing = YES;
+
+ for (GCController *controller in self.joypadsQueue) {
+ [self addiOSJoypad:controller];
+ }
+
+ [self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+ if (self.isObserving) {
+ return;
+ }
+
+ self.isObserving = YES;
+
+ self.connectedJoypads = [NSMutableDictionary dictionary];
+ self.joypadsQueue = [NSMutableArray array];
+
+ // get told when controllers connect, this will be called right away for
+ // already connected controllers
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+
+ // get told when controllers disconnect
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
+}
+
+- (void)finishObserving {
+ if (self.isObserving) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+ self.isObserving = NO;
+ self.isProcessing = NO;
+
+ self.connectedJoypads = nil;
+ self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+ [self finishObserving];
+
+ [super dealloc];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+ for (NSNumber *key in keys) {
+ int joy_id = [key intValue];
+ return joy_id;
+ };
+
+ return -1;
+};
+
+- (void)addiOSJoypad:(GCController *)controller {
+ // get a new id for our controller
+ int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+ if (joy_id == -1) {
+ printf("Couldn't retrieve new joy id\n");
+ return;
+ }
+
+ // assign our player index
+ if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+ controller.playerIndex = [self getFreePlayerIndex];
+ };
+
+ // tell Godot about our new controller
+ Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
+
+ // add it to our dictionary, this will retain our controllers
+ [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+ // set our input handler
+ [self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+ // get our controller
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ printf("Couldn't retrieve new controller\n");
+ return;
+ }
+
+ if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+ printf("Controller is already registered\n");
+ } else if (!self.isProcessing) {
+ [self.joypadsQueue addObject:controller];
+ } else {
+ [self addiOSJoypad:controller];
+ }
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+ // find our joystick, there should be only one in our dictionary
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ return;
+ }
+
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+ for (NSNumber *key in keys) {
+ // tell Godot this joystick is no longer there
+ int joy_id = [key intValue];
+ Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+ // and remove it from our dictionary
+ [self.connectedJoypads removeObjectForKey:key];
+ };
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+ bool have_player_1 = false;
+ bool have_player_2 = false;
+ bool have_player_3 = false;
+ bool have_player_4 = false;
+
+ if (self.connectedJoypads == nil) {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ for (NSNumber *key in keys) {
+ GCController *controller = [self.connectedJoypads objectForKey:key];
+ if (controller.playerIndex == GCControllerPlayerIndex1) {
+ have_player_1 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex2) {
+ have_player_2 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex3) {
+ have_player_3 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex4) {
+ have_player_4 = true;
+ };
+ };
+ };
+
+ if (!have_player_1) {
+ return GCControllerPlayerIndex1;
+ } else if (!have_player_2) {
+ return GCControllerPlayerIndex2;
+ } else if (!have_player_3) {
+ return GCControllerPlayerIndex3;
+ } else if (!have_player_4) {
+ return GCControllerPlayerIndex4;
+ } else {
+ return GCControllerPlayerIndexUnset;
+ };
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+ // Hook in the callback handler for the correct gamepad profile.
+ // This is a bit of a weird design choice on Apples part.
+ // You need to select the most capable gamepad profile for the
+ // gamepad attached.
+ if (controller.extendedGamepad != nil) {
+ // The extended gamepad profile has all the input you could possibly find on
+ // a gamepad but will only be active if your gamepad actually has all of
+ // these...
+ controller.extendedGamepad.valueChangedHandler = ^(
+ GCExtendedGamepad *gamepad, GCControllerElement *element) {
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonB) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ gamepad.buttonB.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.buttonY) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ gamepad.buttonY.isPressed);
+ } else if (element == gamepad.leftShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ gamepad.leftShoulder.isPressed);
+ } else if (element == gamepad.rightShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ gamepad.rightShoulder.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ };
+
+ Input::JoyAxis jx;
+ jx.min = -1;
+ if (element == gamepad.leftThumbstick) {
+ jx.value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
+ jx.value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
+ } else if (element == gamepad.rightThumbstick) {
+ jx.value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
+ jx.value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
+ } else if (element == gamepad.leftTrigger) {
+ jx.value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
+ } else if (element == gamepad.rightTrigger) {
+ jx.value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
+ };
+ };
+ }
+ // else if (controller.gamepad != nil) {
+ // // gamepad is the standard profile with 4 buttons, shoulder buttons and a
+ // // D-pad
+ // controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
+ // GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonB) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ // gamepad.buttonB.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.buttonY) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ // gamepad.buttonY.isPressed);
+ // } else if (element == gamepad.leftShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ // gamepad.leftShoulder.isPressed);
+ // } else if (element == gamepad.rightShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ // gamepad.rightShoulder.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
+ // // while we are setting that as the minimum, seems our
+ // // build environment doesn't like it
+ // } else if (controller.microGamepad != nil) {
+ // // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+ // controller.microGamepad.valueChangedHandler =
+ // ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#endif
+ // };
+
+ ///@TODO need to add support for controller.motion which gives us access to
+ /// the orientation of the device (if supported)
+
+ ///@TODO need to add support for controllerPausedHandler which should be a
+ /// toggle
+};
+
+@end
diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png
index 405b6f93ca..966d8aa70a 100644
--- a/platform/iphone/logo.png
+++ b/platform/iphone/logo.png
Binary files differ
diff --git a/platform/iphone/main.m b/platform/iphone/main.m
index 164db2a74b..c292f02822 100644
--- a/platform/iphone/main.m
+++ b/platform/iphone/main.m
@@ -32,20 +32,25 @@
#import <UIKit/UIKit.h>
#include <stdio.h>
+#include <vulkan/vulkan.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+ //MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- AppDelegate *app = [AppDelegate alloc];
printf("running app main\n");
- UIApplicationMain(argc, argv, nil, @"AppDelegate");
- printf("main done, pool release\n");
- [pool release];
+ @autoreleasepool {
+ UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+ printf("main done\n");
return 0;
}
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
deleted file mode 100644
index 7a699f9b50..0000000000
--- a/platform/iphone/os_iphone.cpp
+++ /dev/null
@@ -1,679 +0,0 @@
-/*************************************************************************/
-/* os_iphone.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifdef IPHONE_ENABLED
-
-#include "os_iphone.h"
-
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
-
-#include "main/main.h"
-
-#include "core/io/file_access_pack.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
-#include "drivers/unix/syslog_logger.h"
-
-#include "semaphore_iphone.h"
-
-#include <dlfcn.h>
-
-int OSIPhone::get_video_driver_count() const {
-
- return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
-
- switch (p_driver) {
- case VIDEO_DRIVER_GLES3:
- return "GLES3";
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + ".");
-};
-
-OSIPhone *OSIPhone::get_singleton() {
-
- return (OSIPhone *)OS::get_singleton();
-};
-
-extern int gl_view_base_fb; // from gl_view.mm
-
-void OSIPhone::set_data_dir(String p_dir) {
-
- DirAccess *da = DirAccess::open(p_dir);
-
- data_dir = da->get_current_dir();
- printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
- memdelete(da);
-};
-
-void OSIPhone::set_unique_id(String p_id) {
-
- unique_id = p_id;
-};
-
-String OSIPhone::get_unique_id() const {
-
- return unique_id;
-};
-
-void OSIPhone::initialize_core() {
-
- OS_Unix::initialize_core();
- SemaphoreIphone::make_default();
-
- set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
- return video_driver_index;
-}
-
-extern bool gles3_available; // from gl_view.mm
-
-Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- bool use_gl3 = GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3";
- bool gl_initialization_error = false;
-
- while (true) {
- if (use_gl3) {
- if (RasterizerGLES3::is_viable() == OK && gles3_available) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- use_gl3 = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
- visual_server = memnew(VisualServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, false));
- }
-
- visual_server->init();
- //visual_server->cursor_set_visible(false, 0);
-
- // reset this to what it should be, it will have been set to 0 after visual_server->init() is called
- if (use_gl3)
- RasterizerStorageGLES3::system_fbo = gl_view_base_fb;
- else
- RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
-#ifdef GAME_CENTER_ENABLED
- game_center = memnew(GameCenter);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
- game_center->connect();
-#endif
-
-#ifdef STOREKIT_ENABLED
- store_kit = memnew(InAppStore);
- Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
- icloud = memnew(ICloud);
- Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
- //icloud->connect();
-#endif
- ios = memnew(iOS);
- Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
- return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
-
- return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
-
- main_loop = p_main_loop;
-
- if (main_loop) {
- input->set_main_loop(p_main_loop);
- main_loop->init();
- }
-};
-
-bool OSIPhone::iterate() {
-
- if (!main_loop)
- return true;
-
- if (main_loop) {
- for (int i = 0; i < event_count; i++) {
-
- input->parse_input_event(event_queue[i]);
- };
- };
- event_count = 0;
-
- return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
-
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_scancode(p_key);
- ev->set_unicode(p_key);
- queue_event(ev);
-};
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
-
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- queue_event(ev);
- };
-
- touch_list.pressed[p_idx] = p_pressed;
-};
-
-void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
-
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
-
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(p_idx);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- queue_event(ev);
- };
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
-
- ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
- event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
-
- for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
-
- if (touch_list.pressed[i]) {
-
- // send a mouse_up outside the screen
- touch_press(i, -1, -1, false, false);
- };
- };
-};
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
- input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
-
- // Found out the Z should not be negated! Pass as is!
- input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
- /*
- if (p_x != last_accel.x) {
- //printf("updating accel x %f\n", p_x);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_0;
- ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
- last_accel.x = p_x;
- queue_event(ev);
- };
- if (p_y != last_accel.y) {
- //printf("updating accel y %f\n", p_y);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_1;
- ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
- last_accel.y = p_y;
- queue_event(ev);
- };
- if (p_z != last_accel.z) {
- //printf("updating accel z %f\n", p_z);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_2;
- ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
- last_accel.z = p_z;
- queue_event(ev);
- };
- */
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
- return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
- input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
- input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
- input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
-
- if (main_loop) {
- main_loop->finish();
- memdelete(main_loop);
- };
-
- main_loop = NULL;
-};
-
-void OSIPhone::finalize() {
-
- delete_main_loop();
-
- memdelete(input);
- memdelete(ios);
-
-#ifdef GAME_CENTER_ENABLED
- memdelete(game_center);
-#endif
-
-#ifdef STOREKIT_ENABLED
- memdelete(store_kit);
-#endif
-
-#ifdef ICLOUD_ENABLED
- memdelete(icloud);
-#endif
-
- visual_server->finish();
- memdelete(visual_server);
- // memdelete(rasterizer);
-
- // Free unhandled events before close
- for (int i = 0; i < MAX_EVENTS; i++) {
- event_queue[i].unref();
- };
- event_count = 0;
-};
-
-void OSIPhone::set_mouse_show(bool p_show){};
-void OSIPhone::set_mouse_grab(bool p_grab){};
-
-bool OSIPhone::is_mouse_grab_enabled() const {
-
- return true;
-};
-
-Point2 OSIPhone::get_mouse_position() const {
-
- return Point2();
-};
-
-int OSIPhone::get_mouse_button_state() const {
-
- return 0;
-};
-
-void OSIPhone::set_window_title(const String &p_title){};
-
-void OSIPhone::alert(const String &p_alert, const String &p_title) {
-
- const CharString utf8_alert = p_alert.utf8();
- const CharString utf8_title = p_title.utf8();
- iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
-}
-
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- if (p_path.length() == 0) {
- p_library_handle = RTLD_SELF;
- return OK;
- }
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
- if (p_library_handle == RTLD_SELF) {
- return OK;
- }
- return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
-void register_dynamic_symbol(char *name, void *address) {
- OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
- if (p_library_handle == RTLD_SELF) {
- void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
- if (ptr) {
- p_symbol_handle = *ptr;
- return OK;
- }
- }
- return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-
- video_mode = p_video_mode;
-};
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
-
- return video_mode;
-};
-
-void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-
- p_list->push_back(video_mode);
-};
-
-bool OSIPhone::can_draw() const {
-
- if (native_video_is_playing())
- return false;
- return true;
-};
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
-
- // gl_view_base_fb has not been updated yet
- RasterizerStorageGLES3::system_fbo = p_fb;
-
- return 0;
-};
-
-bool OSIPhone::has_virtual_keyboard() const {
- return true;
-};
-
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) {
- _show_keyboard(p_existing_text);
-};
-
-void OSIPhone::hide_virtual_keyboard() {
- _hide_keyboard();
-};
-
-void OSIPhone::set_virtual_keyboard_height(int p_height) {
- virtual_keyboard_height = p_height;
-}
-
-int OSIPhone::get_virtual_keyboard_height() const {
- return virtual_keyboard_height;
-}
-
-Error OSIPhone::shell_open(String p_uri) {
- return _shell_open(p_uri);
-};
-
-void OSIPhone::set_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
- _set_keep_screen_on(p_enabled);
-};
-
-String OSIPhone::get_user_data_dir() const {
-
- return data_dir;
-};
-
-String OSIPhone::get_name() const {
-
- return "iOS";
-};
-
-String OSIPhone::get_model_name() const {
-
- String model = ios->get_model();
- if (model != "")
- return model;
-
- return OS_Unix::get_model_name();
-}
-
-Size2 OSIPhone::get_window_size() const {
-
- return Vector2(video_mode.width, video_mode.height);
-}
-
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
-
-Rect2 OSIPhone::get_window_safe_area() const {
- return _get_ios_window_safe_area(video_mode.width, video_mode.height);
-}
-
-bool OSIPhone::has_touchscreen_ui_hint() const {
-
- return true;
-}
-
-void OSIPhone::set_locale(String p_locale) {
- locale_code = p_locale;
-}
-
-String OSIPhone::get_locale() const {
- return locale_code;
-}
-
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
-Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- bool exists = f && f->is_open();
-
- String tempFile = get_user_data_dir();
- if (!exists)
- return FAILED;
-
- if (p_path.begins_with("res://")) {
- if (PackedData::get_singleton()->has_path(p_path)) {
- print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
- return ERR_INVALID_PARAMETER;
- } else {
- p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
- }
- } else if (p_path.begins_with("user://"))
- p_path = p_path.replace("user:/", get_user_data_dir());
-
- memdelete(f);
-
- print("Playing video: %S\n", p_path.c_str());
- if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
- return OK;
- return FAILED;
-}
-
-bool OSIPhone::native_video_is_playing() const {
- return _is_video_playing();
-}
-
-void OSIPhone::native_video_pause() {
- if (native_video_is_playing())
- _pause_video();
-}
-
-void OSIPhone::native_video_unpause() {
- _unpause_video();
-};
-
-void OSIPhone::native_video_focus_out() {
- _focus_out_video();
-};
-
-void OSIPhone::native_video_stop() {
- if (native_video_is_playing())
- _stop_video();
-}
-
-void OSIPhone::vibrate_handheld(int p_duration_ms) {
- // iOS does not support duration for vibration
- _vibrate();
-}
-
-bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
-
- return p_feature == "mobile";
-}
-
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = NULL;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-
-void add_ios_init_callback(init_callback cb) {
- if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
- void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
- if (new_ptr) {
- ios_init_callbacks = (init_callback *)(new_ptr);
- ios_init_callbacks_capacity += 32;
- }
- }
- if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
- ios_init_callbacks[ios_init_callbacks_count] = cb;
- ++ios_init_callbacks_count;
- }
-}
-
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
- for (int i = 0; i < ios_init_callbacks_count; ++i) {
- ios_init_callbacks[i]();
- }
- free(ios_init_callbacks);
- ios_init_callbacks = NULL;
- ios_init_callbacks_count = 0;
- ios_init_callbacks_capacity = 0;
-
- main_loop = NULL;
- visual_server = NULL;
-
- VideoMode vm;
- vm.fullscreen = true;
- vm.width = width;
- vm.height = height;
- vm.resizable = false;
- set_video_mode(vm);
- event_count = 0;
- virtual_keyboard_height = 0;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- data_dir = p_data_dir;
-
- Vector<Logger *> loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
- _set_logger(memnew(CompositeLogger(loggers)));
-
- AudioDriverManager::add_driver(&audio_driver);
-};
-
-OSIPhone::~OSIPhone() {
-}
-
-#endif
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index d2d96181f5..c6f95869ee 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -33,32 +33,26 @@
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
-#include "core/os/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
-
#include "game_center.h"
#include "icloud.h"
#include "in_app_store.h"
#include "ios.h"
-#include "main/input_default.h"
+#include "joypad_iphone.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
+#include "servers/rendering/rasterizer.h"
-class OSIPhone : public OS_Unix {
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/iphone/vulkan_context_iphone.h"
+#endif
+class OSIPhone : public OS_Unix {
private:
- enum {
- MAX_MOUSE_COUNT = 8,
- MAX_EVENTS = 64,
- };
-
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
- VisualServer *visual_server;
-
AudioDriverCoreAudio audio_driver;
#ifdef GAME_CENTER_ENABLED
@@ -72,136 +66,68 @@ private:
#endif
iOS *ios;
- MainLoop *main_loop;
-
- VideoMode video_mode;
+ JoypadIPhone *joypad_iphone;
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual MainLoop *get_main_loop() const;
-
- virtual void delete_main_loop();
-
- virtual void finalize();
-
- struct MouseList {
+ MainLoop *main_loop;
- bool pressed[MAX_MOUSE_COUNT];
- MouseList() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++)
- pressed[i] = false;
- };
- };
+ virtual void initialize_core() override;
+ virtual void initialize() override;
- MouseList touch_list;
+ virtual void initialize_joypads() override {
+ }
- Vector3 last_accel;
+ virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual MainLoop *get_main_loop() const override;
- Ref<InputEvent> event_queue[MAX_EVENTS];
- int event_count;
- void queue_event(const Ref<InputEvent> &p_event);
+ virtual void delete_main_loop() override;
- String data_dir;
- String unique_id;
- String locale_code;
+ virtual void finalize() override;
- InputDefault *input;
+ String user_data_dir;
- int virtual_keyboard_height;
+ bool is_focused = false;
- int video_driver_index;
+ void deinitialize_modules();
public:
- bool iterate();
-
- uint8_t get_orientations() const;
-
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
- void touches_cancelled();
- void key(uint32_t p_key, bool p_pressed);
- void set_virtual_keyboard_height(int p_height);
-
- int set_base_framebuffer(int p_fb);
-
- void update_gravity(float p_x, float p_y, float p_z);
- void update_accelerometer(float p_x, float p_y, float p_z);
- void update_magnetometer(float p_x, float p_y, float p_z);
- void update_gyroscope(float p_x, float p_y, float p_z);
-
- int get_unused_joy_id();
- void joy_connection_changed(int p_idx, bool p_connected, String p_name);
- void joy_button(int p_device, int p_button, bool p_pressed);
- void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
-
static OSIPhone *get_singleton();
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual Error close_dynamic_library(void *p_library_handle);
- virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
+ OSIPhone(String p_data_dir);
+ ~OSIPhone();
- virtual void set_keep_screen_on(bool p_enabled);
+ void initialize_modules();
- virtual bool can_draw() const;
+ bool iterate();
- virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1);
- virtual void hide_virtual_keyboard();
- virtual int get_virtual_keyboard_height() const;
+ void start();
- virtual Size2 get_window_size() const;
- virtual Rect2 get_window_safe_area() const;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error close_dynamic_library(void *p_library_handle) override;
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
- virtual bool has_touchscreen_ui_hint() const;
+ virtual void alert(const String &p_alert,
+ const String &p_title = "ALERT!") override;
- void set_data_dir(String p_dir);
+ virtual String get_name() const override;
+ virtual String get_model_name() const override;
- virtual String get_name() const;
- virtual String get_model_name() const;
+ virtual Error shell_open(String p_uri) override;
- Error shell_open(String p_uri);
+ void set_user_data_dir(String p_dir);
+ virtual String get_user_data_dir() const override;
- String get_user_data_dir() const;
+ virtual String get_locale() const override;
- void set_locale(String p_locale);
- String get_locale() const;
+ virtual String get_unique_id() const override;
- void set_unique_id(String p_id);
- String get_unique_id() const;
+ virtual void vibrate_handheld(int p_duration_ms = 500) override;
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_unpause();
- virtual void native_video_focus_out();
- virtual void native_video_stop();
- virtual void vibrate_handheld(int p_duration_ms = 500);
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
- virtual bool _check_internal_feature_support(const String &p_feature);
- OSIPhone(int width, int height, String p_data_dir);
- ~OSIPhone();
+ void on_focus_out();
+ void on_focus_in();
};
#endif // OS_IPHONE_H
-#endif
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
new file mode 100644
index 0000000000..946fd51923
--- /dev/null
+++ b/platform/iphone/os_iphone.mm
@@ -0,0 +1,365 @@
+/*************************************************************************/
+/* os_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef IPHONE_ENABLED
+
+#include "os_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#include "main/main.h"
+#import "view_controller.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = nullptr;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
+
+void add_ios_init_callback(init_callback cb) {
+ if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+ void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ ios_init_callbacks = (init_callback *)(new_ptr);
+ ios_init_callbacks_capacity += 32;
+ }
+ }
+ if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+ ios_init_callbacks[ios_init_callbacks_count] = cb;
+ ++ios_init_callbacks_count;
+ }
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSIPhone *OSIPhone::get_singleton() {
+ return (OSIPhone *)OS::get_singleton();
+}
+
+OSIPhone::OSIPhone(String p_data_dir) {
+ for (int i = 0; i < ios_init_callbacks_count; ++i) {
+ ios_init_callbacks[i]();
+ }
+ free(ios_init_callbacks);
+ ios_init_callbacks = nullptr;
+ ios_init_callbacks_count = 0;
+ ios_init_callbacks_capacity = 0;
+
+ main_loop = nullptr;
+
+ // can't call set_data_dir from here, since it requires DirAccess
+ // which is initialized in initialize_core
+ user_data_dir = p_data_dir;
+
+ Vector<Logger *> loggers;
+ loggers.push_back(memnew(SyslogLogger));
+#ifdef DEBUG_ENABLED
+ // it seems iOS app's stdout/stderr is only obtainable if you launch it from
+ // Xcode
+ loggers.push_back(memnew(StdLogger));
+#endif
+ _set_logger(memnew(CompositeLogger(loggers)));
+
+ AudioDriverManager::add_driver(&audio_driver);
+
+ DisplayServerIPhone::register_iphone_driver();
+}
+
+OSIPhone::~OSIPhone() {}
+
+void OSIPhone::initialize_core() {
+ OS_Unix::initialize_core();
+
+ set_user_data_dir(user_data_dir);
+}
+
+void OSIPhone::initialize() {
+ initialize_core();
+}
+
+void OSIPhone::initialize_modules() {
+#ifdef GAME_CENTER_ENABLED
+ game_center = memnew(GameCenter);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
+ game_center->connect();
+#endif
+
+#ifdef STOREKIT_ENABLED
+ store_kit = memnew(InAppStore);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
+#endif
+
+#ifdef ICLOUD_ENABLED
+ icloud = memnew(ICloud);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
+#endif
+
+ ios = memnew(iOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
+
+ joypad_iphone = memnew(JoypadIPhone);
+}
+
+void OSIPhone::deinitialize_modules() {
+ if (joypad_iphone) {
+ memdelete(joypad_iphone);
+ }
+
+ if (ios) {
+ memdelete(ios);
+ }
+
+#ifdef GAME_CENTER_ENABLED
+ if (game_center) {
+ memdelete(game_center);
+ }
+#endif
+
+#ifdef STOREKIT_ENABLED
+ if (store_kit) {
+ memdelete(store_kit);
+ }
+#endif
+
+#ifdef ICLOUD_ENABLED
+ if (icloud) {
+ memdelete(icloud);
+ }
+#endif
+}
+
+void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+ main_loop = p_main_loop;
+
+ if (main_loop) {
+ main_loop->init();
+ }
+}
+
+MainLoop *OSIPhone::get_main_loop() const {
+ return main_loop;
+}
+
+void OSIPhone::delete_main_loop() {
+ if (main_loop) {
+ main_loop->finish();
+ memdelete(main_loop);
+ };
+
+ main_loop = nullptr;
+}
+
+bool OSIPhone::iterate() {
+ if (!main_loop) {
+ return true;
+ }
+
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events();
+ }
+
+ return Main::iteration();
+}
+
+void OSIPhone::start() {
+ Main::start();
+
+ if (joypad_iphone) {
+ joypad_iphone->start_processing();
+ }
+}
+
+void OSIPhone::finalize() {
+ deinitialize_modules();
+
+ // Already gets called
+ // delete_main_loop();
+}
+
+// MARK: Dynamic Libraries
+
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+ if (p_path.length() == 0) {
+ p_library_handle = RTLD_SELF;
+ return OK;
+ }
+ return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+}
+
+Error OSIPhone::close_dynamic_library(void *p_library_handle) {
+ if (p_library_handle == RTLD_SELF) {
+ return OK;
+ }
+ return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+ if (p_library_handle == RTLD_SELF) {
+ void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
+ }
+ }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+void OSIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+String OSIPhone::get_name() const {
+ return "iOS";
+};
+
+String OSIPhone::get_model_name() const {
+ String model = ios->get_model();
+ if (model != "")
+ return model;
+
+ return OS_Unix::get_model_name();
+}
+
+Error OSIPhone::shell_open(String p_uri) {
+ NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+ NSURL *url = [NSURL URLWithString:urlPath];
+ [urlPath release];
+
+ if (![[UIApplication sharedApplication] canOpenURL:url]) {
+ return ERR_CANT_OPEN;
+ }
+
+ printf("opening url %s\n", p_uri.utf8().get_data());
+
+ // if (@available(iOS 10, *)) {
+ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+ // } else {
+ // [[UIApplication sharedApplication] openURL:url];
+ // }
+
+ return OK;
+};
+
+void OSIPhone::set_user_data_dir(String p_dir) {
+ DirAccess *da = DirAccess::open(p_dir);
+
+ user_data_dir = da->get_current_dir();
+ printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
+ memdelete(da);
+}
+
+String OSIPhone::get_user_data_dir() const {
+ return user_data_dir;
+}
+
+String OSIPhone::get_locale() const {
+ NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
+
+ if (preferedLanguage) {
+ return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
+ }
+
+ NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
+ return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
+}
+
+String OSIPhone::get_unique_id() const {
+ NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+ return String::utf8([uuid UTF8String]);
+}
+
+void OSIPhone::vibrate_handheld(int p_duration_ms) {
+ // iOS does not support duration for vibration
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
+bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void OSIPhone::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_pause();
+ }
+
+ audio_driver.stop();
+ }
+}
+
+void OSIPhone::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+ }
+
+ [AppDelegate.viewController.godotView startRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_unpause();
+ }
+
+ audio_driver.start();
+ }
+}
+
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h
index d39c64eed6..2bbbe47c0d 100644
--- a/platform/iphone/platform_config.h
+++ b/platform/iphone/platform_config.h
@@ -30,9 +30,6 @@
#include <alloca.h>
-#define GLES2_INCLUDE_H <ES2/gl.h>
-#define GLES3_INCLUDE_H <ES3/gl.h>
-
#define PLATFORM_REFCOUNT
#define PTHREAD_RENAME_SELF
diff --git a/platform/iphone/semaphore_iphone.cpp b/platform/iphone/semaphore_iphone.cpp
deleted file mode 100644
index 0c1d4d2d5c..0000000000
--- a/platform/iphone/semaphore_iphone.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*************************************************************************/
-/* semaphore_iphone.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "semaphore_iphone.h"
-
-#include <fcntl.h>
-#include <unistd.h>
-
-void cgsem_init(cgsem_t *);
-void cgsem_post(cgsem_t *);
-void cgsem_wait(cgsem_t *);
-void cgsem_destroy(cgsem_t *);
-
-void cgsem_init(cgsem_t *cgsem) {
- int flags, fd, i;
-
- pipe(cgsem->pipefd);
-
- /* Make the pipes FD_CLOEXEC to allow them to close should we call
- * execv on restart. */
- for (i = 0; i < 2; i++) {
- fd = cgsem->pipefd[i];
- flags = fcntl(fd, F_GETFD, 0);
- flags |= FD_CLOEXEC;
- fcntl(fd, F_SETFD, flags);
- }
-}
-
-void cgsem_post(cgsem_t *cgsem) {
- const char buf = 1;
-
- write(cgsem->pipefd[1], &buf, 1);
-}
-
-void cgsem_wait(cgsem_t *cgsem) {
- char buf;
-
- read(cgsem->pipefd[0], &buf, 1);
-}
-
-void cgsem_destroy(cgsem_t *cgsem) {
- close(cgsem->pipefd[1]);
- close(cgsem->pipefd[0]);
-}
-
-#include "core/os/memory.h"
-
-#include <errno.h>
-
-Error SemaphoreIphone::wait() {
-
- cgsem_wait(&sem);
- return OK;
-}
-
-Error SemaphoreIphone::post() {
-
- cgsem_post(&sem);
-
- return OK;
-}
-int SemaphoreIphone::get() const {
-
- return 0;
-}
-
-Semaphore *SemaphoreIphone::create_semaphore_iphone() {
-
- return memnew(SemaphoreIphone);
-}
-
-void SemaphoreIphone::make_default() {
-
- create_func = create_semaphore_iphone;
-}
-
-SemaphoreIphone::SemaphoreIphone() {
-
- cgsem_init(&sem);
-}
-
-SemaphoreIphone::~SemaphoreIphone() {
-
- cgsem_destroy(&sem);
-}
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index f6bbe11d97..dffdc01d4a 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -31,20 +31,18 @@
#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@class GodotView;
-- (BOOL)shouldAutorotateToInterfaceOrientation:
- (UIInterfaceOrientation)p_orientation;
+@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
-- (void)didReceiveMemoryWarning;
+- (GodotView *)godotView;
-- (void)viewDidLoad;
+// MARK: Native Video Player
-- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
-
-- (BOOL)prefersStatusBarHidden;
-
-- (BOOL)prefersHomeIndicatorAutoHidden;
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 465e38e45e..31597f7856 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -29,100 +29,174 @@
/*************************************************************************/
#import "view_controller.h"
-
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
#include "os_iphone.h"
-#include "core/project_settings.h"
+#import <GameController/GameController.h>
-extern "C" {
+@interface ViewController ()
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@property(strong, nonatomic) GodotViewRenderer *renderer;
-int add_path(int p_argc, char **p_args) {
+// TODO: separate view to handle video
+// AVPlayer-related properties
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+@property(assign, nonatomic) BOOL videoHasFoundError;
- NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
- if (!str)
- return p_argc;
+@end
- p_args[p_argc++] = "--path";
- [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
- p_args[p_argc++] = (char *)[str cString];
- p_args[p_argc] = NULL;
+@implementation ViewController
- return p_argc;
-};
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
-int add_cmdline(int p_argc, char **p_args) {
+- (void)loadView {
+ GodotView *view = [[GodotView alloc] init];
+ GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
- NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
- if (!arr)
- return p_argc;
+ self.renderer = renderer;
+ self.view = view;
- for (int i = 0; i < [arr count]; i++) {
+ view.renderer = self.renderer;
- NSString *str = [arr objectAtIndex:i];
- if (!str)
- continue;
- [str retain]; // @todo delete these at some point
- p_args[p_argc++] = (char *)[str cString];
- };
+ [renderer release];
+ [view release];
+}
- p_args[p_argc] = NULL;
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
- return p_argc;
-};
-}; // extern "C"
+ if (self) {
+ [self godot_commonInit];
+ }
-@interface ViewController ()
+ return self;
+}
-@end
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
-@implementation ViewController
+ if (self) {
+ [self godot_commonInit];
+ }
-- (void)didReceiveMemoryWarning {
+ return self;
+}
+- (void)godot_commonInit {
+ self.isVideoCurrentlyPlaying = NO;
+ self.videoCurrentTime = kCMTimeZero;
+ self.videoHasFoundError = false;
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
-};
+}
- (void)viewDidLoad {
[super viewDidLoad];
+ [self observeKeyboard];
+ [self observeAudio];
+
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
+- (void)observeKeyboard {
+ printf("******** adding observer for keyboard show/hide\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardOnScreen:)
+ name:UIKeyboardDidShowNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardHidden:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+}
+
+- (void)observeAudio {
+ printf("******** adding observer for sound routing changes\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(audioRouteChangeListenerCallback:)
+ name:AVAudioSessionRouteChangeNotification
+ object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+ [self handleVideoOrPlayerStatus];
+ }
+
+ if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+ [self handleVideoPlayRate];
+ }
+}
+
+- (void)dealloc {
+ [self stopVideo];
+
+ self.renderer = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+// MARK: Orientation
+
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_SENSOR:
- case OS::SCREEN_SENSOR_LANDSCAPE:
- case OS::SCREEN_SENSOR_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return NO;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
-};
+}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return UIInterfaceOrientationMaskAll;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
- case OS::SCREEN_REVERSE_LANDSCAPE:
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
- case OS::SCREEN_REVERSE_PORTRAIT:
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
- case OS::SCREEN_SENSOR_PORTRAIT:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
- case OS::SCREEN_LANDSCAPE:
+ case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
@@ -139,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {
}
}
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+ NSDictionary *info = notification.userInfo;
+ NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+ CGRect rawFrame = [value CGRectValue];
+ CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+ }
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
+ }
+}
+
+// MARK: Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+ printf("*********** route changed!\n");
+ NSDictionary *interuptionDict = notification.userInfo;
+
+ NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+ switch (routeChangeReason) {
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+ NSLog(@"Headphone/Line plugged in");
+ } break;
+ case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+ NSLog(@"Headphone/Line was pulled. Resuming video play....");
+ if ([self isVideoPlaying]) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+ }
+ } break;
+ case AVAudioSessionRouteChangeReasonCategoryChange: {
+ // called at start - also when other audio wants to play
+ NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+ } break;
+ }
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+ if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+ [self stopVideo];
+ self.videoHasFoundError = true;
+ }
+
+ if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+ // NSLog(@"time: %@", self.video_current_time);
+ [self.avPlayer seekToTime:self.videoCurrentTime];
+ self.videoCurrentTime = kCMTimeZero;
+ }
+}
+
+- (void)handleVideoPlayRate {
+ NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+ if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+
+ NSLog(@" . . . PAUSED (or just started)");
+ }
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+ self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+ self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+ [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+ self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+ self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+ [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(playerItemDidReachEnd:)
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[self.avPlayer currentItem]];
+
+ [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+ [self.avPlayerLayer setFrame:self.view.bounds];
+ [self.view.layer addSublayer:self.avPlayerLayer];
+ [self.avPlayer play];
+
+ AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ NSMutableArray *allAudioParams = [NSMutableArray array];
+ for (id track in audioGroup.options) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:audioTrack]) {
+ AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+ [audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+ [audioInputParams setTrackID:[track trackID]];
+ [allAudioParams addObject:audioInputParams];
+
+ AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+ [audioMix setInputParameters:allAudioParams];
+
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+ [self.avPlayer.currentItem setAudioMix:audioMix];
+
+ break;
+ }
+ }
+
+ AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+ NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+ for (id track in useableTracks) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:subtitleTrack]) {
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+ break;
+ }
+ }
+
+ self.isVideoCurrentlyPlaying = YES;
+
+ return true;
+}
+
+- (BOOL)isVideoPlaying {
+ if (self.avPlayer.error) {
+ printf("Error during playback\n");
+ }
+ return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+ self.videoCurrentTime = self.avPlayer.currentTime;
+ [self.avPlayer pause];
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unpauseVideo {
+ [self.avPlayer play];
+ self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+ [self stopVideo];
+}
+
+- (void)stopVideo {
+ [self.avPlayer pause];
+ [self.avPlayerLayer removeFromSuperlayer];
+ self.avPlayerLayer = nil;
+
+ if (self.avPlayerItem) {
+ [self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+ self.avPlayerItem = nil;
+ }
+
+ if (self.avPlayer) {
+ [self.avPlayer removeObserver:self forKeyPath:@"status"];
+ self.avPlayer = nil;
+ }
+
+ self.avAsset = nil;
+
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+// MARK: Delegates
+
#ifdef GAME_CENTER_ENABLED
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
diff --git a/platform/iphone/semaphore_iphone.h b/platform/iphone/vulkan_context_iphone.h
index 9356c65f1e..5c3d5fe33e 100644
--- a/platform/iphone/semaphore_iphone.h
+++ b/platform/iphone/vulkan_context_iphone.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_iphone.h */
+/* vulkan_context_iphone.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,32 +28,21 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef SEMAPHORE_IPHONE_H
-#define SEMAPHORE_IPHONE_H
+#ifndef VULKAN_CONTEXT_IPHONE_H
+#define VULKAN_CONTEXT_IPHONE_H
-struct cgsem {
- int pipefd[2];
-};
-
-typedef struct cgsem cgsem_t;
-
-#include "core/os/semaphore.h"
+#include "drivers/vulkan/vulkan_context.h"
-class SemaphoreIphone : public Semaphore {
+#import <UIKit/UIKit.h>
- mutable cgsem_t sem;
-
- static Semaphore *create_semaphore_iphone();
+class VulkanContextIPhone : public VulkanContext {
+ virtual const char *_get_platform_surface_extension() const;
public:
- virtual Error wait();
- virtual Error post();
- virtual int get() const;
-
- static void make_default();
- SemaphoreIphone();
+ Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
- ~SemaphoreIphone();
+ VulkanContextIPhone();
+ ~VulkanContextIPhone();
};
-#endif // SEMAPHORE_IPHONE_H
+#endif // VULKAN_CONTEXT_IPHONE_H
diff --git a/platform/haiku/haiku_gl_view.cpp b/platform/iphone/vulkan_context_iphone.mm
index 970a1276fd..cb4dbe7f85 100644
--- a/platform/haiku/haiku_gl_view.cpp
+++ b/platform/iphone/vulkan_context_iphone.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* haiku_gl_view.cpp */
+/* vulkan_context_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,20 +28,30 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "haiku_gl_view.h"
-#include "main/main.h"
+#include "vulkan_context_iphone.h"
+#include <vulkan/vulkan_ios.h>
-HaikuGLView::HaikuGLView(BRect frame, uint32 type) :
- BGLView(frame, "GodotGLView", B_FOLLOW_ALL_SIDES, 0, type) {
+const char *VulkanContextIPhone::_get_platform_surface_extension() const {
+ return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
-void HaikuGLView::AttachedToWindow(void) {
- LockGL();
- BGLView::AttachedToWindow();
- UnlockGL();
- MakeFocus();
-}
+Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
+ CALayer *p_metal_layer, int p_width,
+ int p_height) {
+ VkIOSSurfaceCreateInfoMVK createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.pView = p_metal_layer;
+
+ VkSurfaceKHR surface;
+ VkResult err =
+ vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
-void HaikuGLView::Draw(BRect updateRect) {
- Main::force_redraw();
+ return _window_create(p_window_id, surface, p_width, p_height);
}
+
+VulkanContextIPhone::VulkanContextIPhone() {}
+
+VulkanContextIPhone::~VulkanContextIPhone() {}
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 85a633442e..21456efde5 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -1,44 +1,70 @@
#!/usr/bin/env python
-Import('env')
+Import("env")
javascript_files = [
- 'audio_driver_javascript.cpp',
- 'http_client_javascript.cpp',
- 'javascript_eval.cpp',
- 'javascript_main.cpp',
- 'os_javascript.cpp',
+ "audio_driver_javascript.cpp",
+ "display_server_javascript.cpp",
+ "http_client_javascript.cpp",
+ "javascript_eval.cpp",
+ "javascript_main.cpp",
+ "os_javascript.cpp",
]
-build = env.add_program(['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm'], javascript_files);
-js, wasm = build
+build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
+if env["threads_enabled"]:
+ build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
+
+build = env.add_program(build_targets, javascript_files)
js_libraries = [
- 'http_request.js',
+ "native/http_request.js",
]
for lib in js_libraries:
- env.Append(LINKFLAGS=['--js-library', env.File(lib).path])
+ env.Append(LINKFLAGS=["--js-library", env.File(lib).path])
env.Depends(build, js_libraries)
-js_modules = [
- 'id_handler.js',
+js_pre = [
+ "native/id_handler.js",
+ "native/utils.js",
+]
+for js in js_pre:
+ env.Append(LINKFLAGS=["--pre-js", env.File(js).path])
+env.Depends(build, js_pre)
+
+engine = [
+ "engine/preloader.js",
+ "engine/utils.js",
+ "engine/engine.js",
+]
+externs = [env.File("#platform/javascript/engine/externs.js")]
+js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs)
+env.Depends(js_engine, externs)
+
+wrap_list = [
+ build[0],
+ js_engine,
+]
+js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js")
+
+zip_dir = env.Dir("#bin/.javascript_zip")
+binary_name = "godot.tools" if env["tools"] else "godot"
+out_files = [
+ zip_dir.File(binary_name + ".js"),
+ zip_dir.File(binary_name + ".wasm"),
+ zip_dir.File(binary_name + ".html"),
]
-for module in js_modules:
- env.Append(LINKFLAGS=['--pre-js', env.File(module).path])
-env.Depends(build, js_modules)
-
-wrapper_start = env.File('pre.js')
-wrapper_end = env.File('engine.js')
-js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js')
-
-zip_dir = env.Dir('#bin/.javascript_zip')
-zip_files = env.InstallAs([
- zip_dir.File('godot.js'),
- zip_dir.File('godot.wasm'),
- zip_dir.File('godot.html')
-], [
- js_wrapped,
- wasm,
- '#misc/dist/html/full-size.html'
-])
-env.Zip('#bin/godot', zip_files, ZIPROOT=zip_dir, ZIPSUFFIX='${PROGSUFFIX}${ZIPSUFFIX}', ZIPCOMSTR='Archving $SOURCES as $TARGET')
+html_file = "#misc/dist/html/full-size.html"
+in_files = [js_wrapped, build[1], html_file]
+if env["threads_enabled"]:
+ in_files.append(build[2])
+ out_files.append(zip_dir.File(binary_name + ".worker.js"))
+
+zip_files = env.InstallAs(out_files, in_files)
+env.Zip(
+ "#bin/godot",
+ zip_files,
+ ZIPROOT=zip_dir,
+ ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}",
+ ZIPCOMSTR="Archiving $SOURCES as $TARGET",
+)
diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp
index 88de13d771..9c73e5c4c4 100644
--- a/platform/javascript/api/api.cpp
+++ b/platform/javascript/api/api.cpp
@@ -35,40 +35,34 @@
static JavaScript *javascript_eval;
void register_javascript_api() {
-
ClassDB::register_virtual_class<JavaScript>();
javascript_eval = memnew(JavaScript);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval));
}
void unregister_javascript_api() {
-
memdelete(javascript_eval);
}
-JavaScript *JavaScript::singleton = NULL;
+JavaScript *JavaScript::singleton = nullptr;
JavaScript *JavaScript::get_singleton() {
-
return singleton;
}
JavaScript::JavaScript() {
-
- ERR_FAIL_COND_MSG(singleton != NULL, "JavaScript singleton already exist.");
+ ERR_FAIL_COND_MSG(singleton != nullptr, "JavaScript singleton already exist.");
singleton = this;
}
JavaScript::~JavaScript() {}
void JavaScript::_bind_methods() {
-
ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false));
}
#if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED)
Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
-
return Variant();
}
#endif
diff --git a/platform/javascript/api/api.h b/platform/javascript/api/api.h
index 164d679205..8afe0f33ce 100644
--- a/platform/javascript/api/api.h
+++ b/platform/javascript/api/api.h
@@ -28,5 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef JAVASCRIPT_API_H
+#define JAVASCRIPT_API_H
+
void register_javascript_api();
void unregister_javascript_api();
+
+#endif // JAVASCRIPT_API_H
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index f1bc7c4382..9604914b2c 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -30,27 +30,34 @@
#include "audio_driver_javascript.h"
+#include "core/project_settings.h"
+
#include <emscripten.h>
-AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL;
+AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr;
-const char *AudioDriverJavaScript::get_name() const {
+bool AudioDriverJavaScript::is_available() {
+ return EM_ASM_INT({
+ if (!(window.AudioContext || window.webkitAudioContext)) {
+ return 0;
+ }
+ return 1;
+ }) != 0;
+}
+const char *AudioDriverJavaScript::get_name() const {
return "JavaScript";
}
extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() {
-
AudioDriverJavaScript::singleton->mix_to_js();
}
extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) {
-
AudioDriverJavaScript::singleton->process_capture(sample);
}
void AudioDriverJavaScript::mix_to_js() {
-
int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
int sample_count = memarr_len(internal_buffer) / channel_count;
int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer);
@@ -61,39 +68,41 @@ void AudioDriverJavaScript::mix_to_js() {
}
void AudioDriverJavaScript::process_capture(float sample) {
-
int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16);
input_buffer_write(sample32);
}
Error AudioDriverJavaScript::init() {
+ int mix_rate = GLOBAL_GET("audio/mix_rate");
+ int latency = GLOBAL_GET("audio/output_latency");
/* clang-format off */
- EM_ASM({
- _audioDriver_audioContext = new (window.AudioContext || window.webkitAudioContext);
- _audioDriver_audioInput = null;
- _audioDriver_inputStream = null;
- _audioDriver_scriptNode = null;
- });
+ _driver_id = EM_ASM_INT({
+ const MIX_RATE = $0;
+ const LATENCY = $1 / 1000;
+ return Module.IDHandler.add({
+ 'context': new (window.AudioContext || window.webkitAudioContext)({ sampleRate: MIX_RATE, latencyHint: LATENCY}),
+ 'input': null,
+ 'stream': null,
+ 'script': null
+ });
+ }, mix_rate, latency);
/* clang-format on */
int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
+ buffer_length = closest_power_of_2((latency * mix_rate / 1000) * channel_count);
/* clang-format off */
buffer_length = EM_ASM_INT({
- var CHANNEL_COUNT = $0;
-
- var channelCount = _audioDriver_audioContext.destination.channelCount;
- try {
- // Try letting the browser recommend a buffer length.
- _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(0, 2, channelCount);
- } catch (e) {
- // ...otherwise, default to 4096.
- _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(4096, 2, channelCount);
- }
- _audioDriver_scriptNode.connect(_audioDriver_audioContext.destination);
-
- return _audioDriver_scriptNode.bufferSize;
- }, channel_count);
+ var ref = Module.IDHandler.get($0);
+ const ctx = ref['context'];
+ const BUFFER_LENGTH = $1;
+ const CHANNEL_COUNT = $2;
+
+ var script = ctx.createScriptProcessor(BUFFER_LENGTH, 2, CHANNEL_COUNT);
+ script.connect(ctx.destination);
+ ref['script'] = script;
+ return script.bufferSize;
+ }, _driver_id, buffer_length, channel_count);
/* clang-format on */
if (!buffer_length) {
return FAILED;
@@ -109,14 +118,14 @@ Error AudioDriverJavaScript::init() {
}
void AudioDriverJavaScript::start() {
-
/* clang-format off */
EM_ASM({
- var INTERNAL_BUFFER_PTR = $0;
+ const ref = Module.IDHandler.get($0);
+ var INTERNAL_BUFFER_PTR = $1;
var audioDriverMixFunction = cwrap('audio_driver_js_mix');
var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']);
- _audioDriver_scriptNode.onaudioprocess = function(audioProcessingEvent) {
+ ref['script'].onaudioprocess = function(audioProcessingEvent) {
audioDriverMixFunction();
var input = audioProcessingEvent.inputBuffer;
@@ -133,7 +142,7 @@ void AudioDriverJavaScript::start() {
}
}
- if (_audioDriver_audioInput) {
+ if (ref['input']) {
var inputDataL = input.getChannelData(0);
var inputDataR = input.getChannelData(1);
for (var i = 0; i < inputDataL.length; i++) {
@@ -142,34 +151,54 @@ void AudioDriverJavaScript::start() {
}
}
};
- }, internal_buffer);
+ }, _driver_id, internal_buffer);
/* clang-format on */
}
void AudioDriverJavaScript::resume() {
/* clang-format off */
EM_ASM({
- if (_audioDriver_audioContext.resume)
- _audioDriver_audioContext.resume();
- });
+ const ref = Module.IDHandler.get($0);
+ if (ref && ref['context'] && ref['context'].resume)
+ ref['context'].resume();
+ }, _driver_id);
/* clang-format on */
}
-int AudioDriverJavaScript::get_mix_rate() const {
+float AudioDriverJavaScript::get_latency() {
+ /* clang-format off */
+ return EM_ASM_DOUBLE({
+ const ref = Module.IDHandler.get($0);
+ var latency = 0;
+ if (ref && ref['context']) {
+ const ctx = ref['context'];
+ if (ctx.baseLatency) {
+ latency += ctx.baseLatency;
+ }
+ if (ctx.outputLatency) {
+ latency += ctx.outputLatency;
+ }
+ }
+ return latency;
+ }, _driver_id);
+ /* clang-format on */
+}
+int AudioDriverJavaScript::get_mix_rate() const {
/* clang-format off */
- return EM_ASM_INT_V({
- return _audioDriver_audioContext.sampleRate;
- });
+ return EM_ASM_INT({
+ const ref = Module.IDHandler.get($0);
+ return ref && ref['context'] ? ref['context'].sampleRate : 0;
+ }, _driver_id);
/* clang-format on */
}
AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
-
/* clang-format off */
- return get_speaker_mode_by_total_channels(EM_ASM_INT_V({
- return _audioDriver_audioContext.destination.channelCount;
- }));
+ return get_speaker_mode_by_total_channels(EM_ASM_INT({
+ const ref = Module.IDHandler.get($0);
+ return ref && ref['context'] ? ref['context'].destination.channelCount : 0;
+ }, _driver_id));
/* clang-format on */
}
@@ -180,32 +209,55 @@ void AudioDriverJavaScript::lock() {
void AudioDriverJavaScript::unlock() {
}
-void AudioDriverJavaScript::finish() {
+void AudioDriverJavaScript::finish_async() {
+ // Close the context, add the operation to the async_finish list in module.
+ int id = _driver_id;
+ _driver_id = 0;
/* clang-format off */
EM_ASM({
- _audioDriver_audioContext = null;
- _audioDriver_audioInput = null;
- _audioDriver_scriptNode = null;
- });
+ const id = $0;
+ var ref = Module.IDHandler.get(id);
+ Module.async_finish.push(new Promise(function(accept, reject) {
+ if (!ref) {
+ console.log("Ref not found!", id, Module.IDHandler);
+ setTimeout(accept, 0);
+ } else {
+ Module.IDHandler.remove(id);
+ const context = ref['context'];
+ // Disconnect script and input.
+ ref['script'].disconnect();
+ if (ref['input'])
+ ref['input'].disconnect();
+ ref = null;
+ context.close().then(function() {
+ accept();
+ }).catch(function(e) {
+ accept();
+ });
+ }
+ }));
+ }, id);
/* clang-format on */
+}
+void AudioDriverJavaScript::finish() {
if (internal_buffer) {
memdelete_arr(internal_buffer);
- internal_buffer = NULL;
+ internal_buffer = nullptr;
}
}
Error AudioDriverJavaScript::capture_start() {
-
input_buffer_init(buffer_length);
/* clang-format off */
EM_ASM({
function gotMediaInput(stream) {
- _audioDriver_inputStream = stream;
- _audioDriver_audioInput = _audioDriver_audioContext.createMediaStreamSource(stream);
- _audioDriver_audioInput.connect(_audioDriver_scriptNode);
+ var ref = Module.IDHandler.get($0);
+ ref['stream'] = stream;
+ ref['input'] = ref['context'].createMediaStreamSource(stream);
+ ref['input'].connect(ref['script']);
}
function gotMediaInputError(e) {
@@ -219,30 +271,30 @@ Error AudioDriverJavaScript::capture_start() {
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError);
}
- });
+ }, _driver_id);
/* clang-format on */
return OK;
}
Error AudioDriverJavaScript::capture_stop() {
-
/* clang-format off */
EM_ASM({
- if (_audioDriver_inputStream) {
- const tracks = _audioDriver_inputStream.getTracks();
+ var ref = Module.IDHandler.get($0);
+ if (ref['stream']) {
+ const tracks = ref['stream'].getTracks();
for (var i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
- _audioDriver_inputStream = null;
+ ref['stream'] = null;
}
- if (_audioDriver_audioInput) {
- _audioDriver_audioInput.disconnect();
- _audioDriver_audioInput = null;
+ if (ref['input']) {
+ ref['input'].disconnect();
+ ref['input'] = null;
}
- });
+ }, _driver_id);
/* clang-format on */
input_buffer.clear();
@@ -251,8 +303,5 @@ Error AudioDriverJavaScript::capture_stop() {
}
AudioDriverJavaScript::AudioDriverJavaScript() {
-
- internal_buffer = NULL;
-
singleton = this;
}
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index 2bb97ba192..c1607301d7 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -34,30 +34,33 @@
#include "servers/audio_server.h"
class AudioDriverJavaScript : public AudioDriver {
+ float *internal_buffer = nullptr;
- float *internal_buffer;
-
- int buffer_length;
+ int _driver_id = 0;
+ int buffer_length = 0;
public:
+ static bool is_available();
void mix_to_js();
void process_capture(float sample);
static AudioDriverJavaScript *singleton;
- virtual const char *get_name() const;
+ const char *get_name() const override;
- virtual Error init();
- virtual void start();
+ Error init() override;
+ void start() override;
void resume();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ float get_latency() override;
+ int get_mix_rate() const override;
+ SpeakerMode get_speaker_mode() const override;
+ void lock() override;
+ void unlock() override;
+ void finish() override;
+ void finish_async();
- virtual Error capture_start();
- virtual Error capture_stop();
+ Error capture_start() override;
+ Error capture_stop() override;
AudioDriverJavaScript();
};
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 1766833364..81287cead8 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,35 +1,40 @@
import os
+from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file
+
def is_active():
return True
def get_name():
- return 'JavaScript'
+ return "JavaScript"
def can_build():
- return 'EM_CONFIG' in os.environ or os.path.exists(os.path.expanduser('~/.emscripten'))
+ return "EM_CONFIG" in os.environ or os.path.exists(os.path.expanduser("~/.emscripten"))
def get_opts():
from SCons.Variables import BoolVariable
+
return [
# eval() can be a security concern, so it can be disabled.
- BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True),
+ BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
+ BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False),
+ BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
]
def get_flags():
return [
- ('tools', False),
- ('builtin_pcre2_with_jit', False),
+ ("tools", False),
+ ("builtin_pcre2_with_jit", False),
# Disabling the mbedtls module reduces file size.
# The module has little use due to the limited networking functionality
# in this platform. For the available networking methods, the browser
# manages TLS.
- ('module_mbedtls_enabled', False),
+ ("module_mbedtls_enabled", False),
]
@@ -37,124 +42,130 @@ def configure(env):
## Build type
- if env['target'] != 'debug':
+ if env["target"] == "release":
# Use -Os to prioritize optimizing for reduced file size. This is
# particularly valuable for the web platform because it directly
# decreases download time.
# -Os reduces file size by around 5 MiB over -O3. -Oz only saves about
# 100 KiB over -Os, which does not justify the negative impact on
# run-time performance.
- env.Append(CCFLAGS=['-Os'])
- env.Append(LINKFLAGS=['-Os'])
- if env['target'] == 'release_debug':
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- # Retain function names for backtraces at the cost of file size.
- env.Append(LINKFLAGS=['--profiling-funcs'])
- else:
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- env.Append(CCFLAGS=['-O1', '-g'])
- env.Append(LINKFLAGS=['-O1', '-g'])
- env.Append(LINKFLAGS=['-s', 'ASSERTIONS=1'])
-
- ## Compiler configuration
-
- env['ENV'] = os.environ
-
- em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten')
- if not os.path.exists(em_config_file):
- raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
- with open(em_config_file) as f:
- em_config = {}
- try:
- # Emscripten configuration file is a Python file with simple assignments.
- exec(f.read(), em_config)
- except StandardError as e:
- raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
- if 'BINARYEN_ROOT' in em_config and os.path.isdir(os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten')):
- # New style, emscripten path as a subfolder of BINARYEN_ROOT
- env.PrependENVPath('PATH', os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten'))
- elif 'EMSCRIPTEN_ROOT' in em_config:
- # Old style (but can be there as a result from previous activation, so do last)
- env.PrependENVPath('PATH', em_config.get('EMSCRIPTEN_ROOT'))
+ env.Append(CCFLAGS=["-Os"])
+ env.Append(LINKFLAGS=["-Os"])
+ elif env["target"] == "release_debug":
+ env.Append(CCFLAGS=["-Os"])
+ env.Append(LINKFLAGS=["-Os"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ # Retain function names for backtraces at the cost of file size.
+ env.Append(LINKFLAGS=["--profiling-funcs"])
+ else: # "debug"
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(CCFLAGS=["-O1", "-g"])
+ env.Append(LINKFLAGS=["-O1", "-g"])
+ env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"])
+
+ if env["tools"]:
+ if not env["threads_enabled"]:
+ raise RuntimeError(
+ "Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option"
+ )
+ # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY).
+ env.Append(LINKFLAGS=["-s", "TOTAL_MEMORY=33554432"])
else:
- raise RuntimeError("'BINARYEN_ROOT' or 'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file)
+ # Disable exceptions and rtti on non-tools (template) builds
+ # These flags help keep the file size down.
+ env.Append(CCFLAGS=["-fno-exceptions", "-fno-rtti"])
+ # Don't use dynamic_cast, necessary with no-rtti.
+ env.Append(CPPDEFINES=["NO_SAFE_CAST"])
- env['CC'] = 'emcc'
- env['CXX'] = 'em++'
- env['LINK'] = 'emcc'
+ ## Copy env variables.
+ env["ENV"] = os.environ
- env['AR'] = 'emar'
- env['RANLIB'] = 'emranlib'
+ # LTO
+ if env["use_lto"]:
+ env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"])
+ env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"])
+ env.Append(LINKFLAGS=["--llvm-lto", "1"])
- # Use TempFileMunge since some AR invocations are too long for cmd.exe.
- # Use POSIX-style paths, required with TempFileMunge.
- env['ARCOM_POSIX'] = env['ARCOM'].replace(
- '$TARGET', '$TARGET.posix').replace(
- '$SOURCES', '$SOURCES.posix')
- env['ARCOM'] = '${TEMPFILE(ARCOM_POSIX)}'
+ # Closure compiler
+ if env["use_closure_compiler"]:
+ # For emscripten support code.
+ env.Append(LINKFLAGS=["--closure", "1"])
+ # Register builder for our Engine files
+ jscc = env.Builder(generator=run_closure_compiler, suffix=".cc.js", src_suffix=".js")
+ env.Append(BUILDERS={"BuildJS": jscc})
- # All intermediate files are just LLVM bitcode.
- env['OBJPREFIX'] = ''
- env['OBJSUFFIX'] = '.bc'
- env['PROGPREFIX'] = ''
- # Program() output consists of multiple files, so specify suffixes manually at builder.
- env['PROGSUFFIX'] = ''
- env['LIBPREFIX'] = 'lib'
- env['LIBSUFFIX'] = '.bc'
- env['LIBPREFIXES'] = ['$LIBPREFIX']
- env['LIBSUFFIXES'] = ['$LIBSUFFIX']
+ # Add method that joins/compiles our Engine files.
+ env.AddMethod(create_engine_file, "CreateEngineFile")
- ## Compile flags
+ # Closure compiler extern and support for ecmascript specs (const, let, etc).
+ env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6"
- env.Prepend(CPPPATH=['#platform/javascript'])
- env.Append(CPPDEFINES=['JAVASCRIPT_ENABLED', 'UNIX_ENABLED'])
+ em_config = parse_config()
+ env.PrependENVPath("PATH", em_config["EMCC_ROOT"])
- # No multi-threading (SharedArrayBuffer) available yet,
- # once feasible also consider memory buffer size issues.
- env.Append(CPPDEFINES=['NO_THREADS'])
+ env["CC"] = "emcc"
+ env["CXX"] = "em++"
+ env["LINK"] = "emcc"
- # Disable exceptions and rtti on non-tools (template) builds
- if not env['tools']:
- # These flags help keep the file size down.
- env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
- # Don't use dynamic_cast, necessary with no-rtti.
- env.Append(CPPDEFINES=['NO_SAFE_CAST'])
+ env["AR"] = "emar"
+ env["RANLIB"] = "emranlib"
- if env['javascript_eval']:
- env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED'])
+ # Use TempFileMunge since some AR invocations are too long for cmd.exe.
+ # Use POSIX-style paths, required with TempFileMunge.
+ env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix")
+ env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}"
- ## Link flags
+ # All intermediate files are just LLVM bitcode.
+ env["OBJPREFIX"] = ""
+ env["OBJSUFFIX"] = ".bc"
+ env["PROGPREFIX"] = ""
+ # Program() output consists of multiple files, so specify suffixes manually at builder.
+ env["PROGSUFFIX"] = ""
+ env["LIBPREFIX"] = "lib"
+ env["LIBSUFFIX"] = ".bc"
+ env["LIBPREFIXES"] = ["$LIBPREFIX"]
+ env["LIBSUFFIXES"] = ["$LIBSUFFIX"]
+
+ env.Prepend(CPPPATH=["#platform/javascript"])
+ env.Append(CPPDEFINES=["JAVASCRIPT_ENABLED", "UNIX_ENABLED"])
+
+ if env["javascript_eval"]:
+ env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
+
+ # Thread support (via SharedArrayBuffer).
+ if env["threads_enabled"]:
+ env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
+ env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=4"])
+ env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ else:
+ env.Append(CPPDEFINES=["NO_THREADS"])
+
+ # Reduce code size by generating less support code (e.g. skip NodeJS support).
+ env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
# We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to
# be linked explicitly.
- env.Append(LIBS=['idbfs.js'])
-
- env.Append(LINKFLAGS=['-s', 'BINARYEN=1'])
-
- # Only include the JavaScript support code for the web environment
- # (i.e. exclude Node.js and other unused environments).
- # This makes the JavaScript support code about 4 KB smaller.
- env.Append(LINKFLAGS=['-s', 'ENVIRONMENT=web'])
+ env.Append(LIBS=["idbfs.js"])
- # This needs to be defined for Emscripten using 'fastcomp' (default pre-1.39.0)
- # and undefined if using 'upstream'. And to make things simple, earlier
- # Emscripten versions didn't include 'fastcomp' in their path, so we check
- # against the presence of 'upstream' to conditionally add the flag.
- if not "upstream" in em_config['EMSCRIPTEN_ROOT']:
- env.Append(LINKFLAGS=['-s', 'BINARYEN_TRAP_MODE=\'clamp\''])
+ env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
+ env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
# Allow increasing memory buffer size during runtime. This is efficient
# when using WebAssembly (in comparison to asm.js) and works well for
# us since we don't know requirements at compile-time.
- env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1'])
+ env.Append(LINKFLAGS=["-s", "ALLOW_MEMORY_GROWTH=1"])
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
- env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
+ env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
- env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0'])
+ env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"])
- # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime.
- env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1'])
+ # Allow use to take control of swapping WebGL buffers.
+ env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
- #adding flag due to issue with emscripten 1.38.41 callMain method https://github.com/emscripten-core/emscripten/blob/incoming/ChangeLog.md#v13841-08072019
- env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain"]'])
+ # callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS.
+ env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH']"])
+ # Add code that allow exiting runtime.
+ env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
new file mode 100644
index 0000000000..8dc33bdf64
--- /dev/null
+++ b/platform/javascript/display_server_javascript.cpp
@@ -0,0 +1,1201 @@
+/*************************************************************************/
+/* display_server_javascript.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "platform/javascript/display_server_javascript.h"
+
+#include "drivers/dummy/rasterizer_dummy.h"
+#include "platform/javascript/os_javascript.h"
+
+#include <emscripten.h>
+#include <png.h>
+
+#include "dom_keys.inc"
+
+#define DOM_BUTTON_LEFT 0
+#define DOM_BUTTON_MIDDLE 1
+#define DOM_BUTTON_RIGHT 2
+#define DOM_BUTTON_XBUTTON1 3
+#define DOM_BUTTON_XBUTTON2 4
+
+char DisplayServerJavaScript::canvas_id[256] = { 0 };
+static bool cursor_inside_canvas = true;
+
+DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() {
+ return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton());
+}
+
+// Window (canvas)
+void DisplayServerJavaScript::focus_canvas() {
+ /* clang-format off */
+ EM_ASM(
+ Module['canvas'].focus();
+ );
+ /* clang-format on */
+}
+
+bool DisplayServerJavaScript::is_canvas_focused() {
+ /* clang-format off */
+ return EM_ASM_INT_V(
+ return document.activeElement == Module['canvas'];
+ );
+ /* clang-format on */
+}
+
+bool DisplayServerJavaScript::check_size_force_redraw() {
+ int canvas_width;
+ int canvas_height;
+ emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height);
+ if (last_width != canvas_width || last_height != canvas_height) {
+ last_width = canvas_width;
+ last_height = canvas_height;
+ // Update the framebuffer size for redraw.
+ emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height);
+ return true;
+ }
+ return false;
+}
+
+Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) {
+ int canvas_x = EM_ASM_INT({
+ return Module['canvas'].getBoundingClientRect().x;
+ });
+ int canvas_y = EM_ASM_INT({
+ return Module['canvas'].getBoundingClientRect().y;
+ });
+ int canvas_width;
+ int canvas_height;
+ emscripten_get_canvas_element_size(canvas_id, &canvas_width, &canvas_height);
+
+ double element_width;
+ double element_height;
+ emscripten_get_element_css_size(canvas_id, &element_width, &element_height);
+
+ return Point2((int)(canvas_width / element_width * (p_x - canvas_x)),
+ (int)(canvas_height / element_height * (p_y - canvas_y)));
+}
+
+EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ // Empty ID is canvas.
+ String target_id = String::utf8(p_event->id);
+ String canvas_str_id = String::utf8(canvas_id);
+ if (target_id.empty() || target_id == canvas_str_id) {
+ // This event property is the only reliable data on
+ // browser fullscreen state.
+ if (p_event->isFullscreen) {
+ display->window_mode = WINDOW_MODE_FULLSCREEN;
+ } else {
+ display->window_mode = WINDOW_MODE_WINDOWED;
+ }
+ }
+ return false;
+}
+
+// Drag and drop callback (see native/utils.js).
+extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) {
+ DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
+ if (!ds) {
+ ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active");
+ }
+ if (ds->drop_files_callback.is_null())
+ return;
+ Vector<String> files;
+ for (int i = 0; i < p_filec; i++) {
+ files.push_back(String::utf8(p_filev[i]));
+ }
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+}
+
+// Keys
+
+template <typename T>
+void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
+ godot_event->set_shift(emscripten_event_ptr->shiftKey);
+ godot_event->set_alt(emscripten_event_ptr->altKey);
+ godot_event->set_control(emscripten_event_ptr->ctrlKey);
+ godot_event->set_metakey(emscripten_event_ptr->metaKey);
+}
+
+Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(emscripten_event->repeat);
+ dom2godot_mod(emscripten_event, ev);
+ ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false));
+ ev->set_physical_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, true));
+
+ String unicode = String::utf8(emscripten_event->key);
+ // Check if empty or multi-character (e.g. `CapsLock`).
+ if (unicode.length() != 1) {
+ // Might be empty as well, but better than nonsense.
+ unicode = String::utf8(emscripten_event->charValue);
+ }
+ if (unicode.length() == 1) {
+ ev->set_unicode(unicode[0]);
+ }
+
+ return ev;
+}
+
+EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventKey> ev = setup_key_event(p_event);
+ ev->set_pressed(true);
+ if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) {
+ // Defer to keypress event for legacy unicode retrieval.
+ display->deferred_key_event = ev;
+ // Do not suppress keypress event.
+ return false;
+ }
+ Input::get_singleton()->parse_input_event(ev);
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ display->deferred_key_event->set_unicode(p_event->charCode);
+ Input::get_singleton()->parse_input_event(display->deferred_key_event);
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ Ref<InputEventKey> ev = setup_key_event(p_event);
+ ev->set_pressed(false);
+ Input::get_singleton()->parse_input_event(ev);
+ return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0;
+}
+
+// Mouse
+
+EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
+ ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY));
+ ev->set_global_position(ev->get_position());
+ dom2godot_mod(p_event, ev);
+
+ switch (p_event->button) {
+ case DOM_BUTTON_LEFT:
+ ev->set_button_index(BUTTON_LEFT);
+ break;
+ case DOM_BUTTON_MIDDLE:
+ ev->set_button_index(BUTTON_MIDDLE);
+ break;
+ case DOM_BUTTON_RIGHT:
+ ev->set_button_index(BUTTON_RIGHT);
+ break;
+ case DOM_BUTTON_XBUTTON1:
+ ev->set_button_index(BUTTON_XBUTTON1);
+ break;
+ case DOM_BUTTON_XBUTTON2:
+ ev->set_button_index(BUTTON_XBUTTON2);
+ break;
+ default:
+ return false;
+ }
+
+ if (ev->is_pressed()) {
+ double diff = emscripten_get_now() - display->last_click_ms;
+
+ if (ev->get_button_index() == display->last_click_button_index) {
+ if (diff < 400 && Point2(display->last_click_pos).distance_to(ev->get_position()) < 5) {
+ display->last_click_ms = 0;
+ display->last_click_pos = Point2(-100, -100);
+ display->last_click_button_index = -1;
+ ev->set_doubleclick(true);
+ }
+
+ } else {
+ display->last_click_button_index = ev->get_button_index();
+ }
+
+ if (!ev->is_doubleclick()) {
+ display->last_click_ms += diff;
+ display->last_click_pos = ev->get_position();
+ }
+ }
+
+ Input *input = Input::get_singleton();
+ int mask = input->get_mouse_button_mask();
+ int button_flag = 1 << (ev->get_button_index() - 1);
+ if (ev->is_pressed()) {
+ // Since the event is consumed, focus manually. The containing iframe,
+ // if exists, may not have focus yet, so focus even if already focused.
+ focus_canvas();
+ mask |= button_flag;
+ } else if (mask & button_flag) {
+ mask &= ~button_flag;
+ } else {
+ // Received release event, but press was outside the canvas, so ignore.
+ return false;
+ }
+ ev->set_button_mask(mask);
+
+ input->parse_input_event(ev);
+ // Prevent multi-click text selection and wheel-click scrolling anchor.
+ // Context menu is prevented through contextmenu event.
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
+ Input *input = Input::get_singleton();
+ int input_mask = input->get_mouse_button_mask();
+ Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY);
+ // For motion outside the canvas, only read mouse movement if dragging
+ // started inside the canvas; imitating desktop app behaviour.
+ if (!cursor_inside_canvas && !input_mask)
+ return false;
+
+ Ref<InputEventMouseMotion> ev;
+ ev.instance();
+ dom2godot_mod(p_event, ev);
+ ev->set_button_mask(input_mask);
+
+ ev->set_position(pos);
+ ev->set_global_position(ev->get_position());
+
+ ev->set_relative(Vector2(p_event->movementX, p_event->movementY));
+ input->set_mouse_position(ev->get_position());
+ ev->set_speed(input->get_last_mouse_speed());
+
+ input->parse_input_event(ev);
+ // Don't suppress mouseover/-leave events.
+ return false;
+}
+
+// Cursor
+const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) {
+ switch (p_shape) {
+ case DisplayServer::CURSOR_ARROW:
+ return "auto";
+ case DisplayServer::CURSOR_IBEAM:
+ return "text";
+ case DisplayServer::CURSOR_POINTING_HAND:
+ return "pointer";
+ case DisplayServer::CURSOR_CROSS:
+ return "crosshair";
+ case DisplayServer::CURSOR_WAIT:
+ return "progress";
+ case DisplayServer::CURSOR_BUSY:
+ return "wait";
+ case DisplayServer::CURSOR_DRAG:
+ return "grab";
+ case DisplayServer::CURSOR_CAN_DROP:
+ return "grabbing";
+ case DisplayServer::CURSOR_FORBIDDEN:
+ return "no-drop";
+ case DisplayServer::CURSOR_VSIZE:
+ return "ns-resize";
+ case DisplayServer::CURSOR_HSIZE:
+ return "ew-resize";
+ case DisplayServer::CURSOR_BDIAGSIZE:
+ return "nesw-resize";
+ case DisplayServer::CURSOR_FDIAGSIZE:
+ return "nwse-resize";
+ case DisplayServer::CURSOR_MOVE:
+ return "move";
+ case DisplayServer::CURSOR_VSPLIT:
+ return "row-resize";
+ case DisplayServer::CURSOR_HSPLIT:
+ return "col-resize";
+ case DisplayServer::CURSOR_HELP:
+ return "help";
+ default:
+ return "auto";
+ }
+}
+
+void DisplayServerJavaScript::set_css_cursor(const char *p_cursor) {
+ /* clang-format off */
+ EM_ASM_({
+ Module['canvas'].style.cursor = UTF8ToString($0);
+ }, p_cursor);
+ /* clang-format on */
+}
+
+bool DisplayServerJavaScript::is_css_cursor_hidden() const {
+ /* clang-format off */
+ return EM_ASM_INT({
+ return Module['canvas'].style.cursor === 'none';
+ });
+ /* clang-format on */
+}
+
+void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) {
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ if (mouse_get_mode() == MOUSE_MODE_VISIBLE) {
+ if (cursors[p_shape] != "") {
+ Vector<String> url = cursors[p_shape].split("?");
+ set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8());
+ } else {
+ set_css_cursor(godot2dom_cursor(p_shape));
+ }
+ }
+
+ cursor_shape = p_shape;
+}
+
+DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
+ return cursor_shape;
+}
+
+void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ if (p_cursor.is_valid()) {
+ Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
+ cursor_set_shape(p_shape);
+ return;
+ }
+
+ cursors_cache.erase(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ Ref<AtlasTexture> atlas_texture = p_cursor;
+ Ref<Image> image;
+ Size2 texture_size;
+ Rect2 atlas_rect;
+
+ if (texture.is_valid()) {
+ image = texture->get_data();
+ }
+
+ if (!image.is_valid() && atlas_texture.is_valid()) {
+ texture = atlas_texture->get_atlas();
+
+ atlas_rect.size.width = texture->get_width();
+ atlas_rect.size.height = texture->get_height();
+ atlas_rect.position.x = atlas_texture->get_region().position.x;
+ atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else if (image.is_valid()) {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(!texture.is_valid());
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+ image = texture->get_data();
+
+ ERR_FAIL_COND(!image.is_valid());
+
+ image = image->duplicate();
+
+ if (atlas_texture.is_valid())
+ image->crop_from_point(
+ atlas_rect.position.x,
+ atlas_rect.position.y,
+ texture_size.width,
+ texture_size.height);
+
+ if (image->get_format() != Image::FORMAT_RGBA8) {
+ image->convert(Image::FORMAT_RGBA8);
+ }
+
+ png_image png_meta;
+ memset(&png_meta, 0, sizeof png_meta);
+ png_meta.version = PNG_IMAGE_VERSION;
+ png_meta.width = texture_size.width;
+ png_meta.height = texture_size.height;
+ png_meta.format = PNG_FORMAT_RGBA;
+
+ PackedByteArray png;
+ size_t len;
+ PackedByteArray data = image->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
+
+ png.resize(len);
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
+
+ char *object_url;
+ /* clang-format off */
+ EM_ASM({
+ var PNG_PTR = $0;
+ var PNG_LEN = $1;
+ var PTR = $2;
+
+ var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' });
+ var url = URL.createObjectURL(png);
+ var length_bytes = lengthBytesUTF8(url) + 1;
+ var string_on_wasm_heap = _malloc(length_bytes);
+ setValue(PTR, string_on_wasm_heap, '*');
+ stringToUTF8(url, string_on_wasm_heap, length_bytes);
+ }, png.ptr(), len, &object_url);
+ /* clang-format on */
+
+ String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y);
+
+ /* clang-format off */
+ EM_ASM({ _free($0); }, object_url);
+ /* clang-format on */
+
+ if (cursors[p_shape] != "") {
+ /* clang-format off */
+ EM_ASM({
+ URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
+ }, cursors[p_shape].utf8().get_data());
+ /* clang-format on */
+ cursors[p_shape] = "";
+ }
+
+ cursors[p_shape] = url;
+
+ Vector<Variant> params;
+ params.push_back(p_cursor);
+ params.push_back(p_hotspot);
+ cursors_cache.insert(p_shape, params);
+
+ } else if (cursors[p_shape] != "") {
+ /* clang-format off */
+ EM_ASM({
+ URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
+ }, cursors[p_shape].utf8().get_data());
+ /* clang-format on */
+ cursors[p_shape] = "";
+
+ cursors_cache.erase(p_shape);
+ }
+
+ cursor_set_shape(cursor_shape);
+}
+
+// Mouse mode
+void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) {
+ ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform.");
+ if (p_mode == mouse_get_mode())
+ return;
+
+ if (p_mode == MOUSE_MODE_VISIBLE) {
+ // set_css_cursor must be called before set_cursor_shape to make the cursor visible
+ set_css_cursor(godot2dom_cursor(cursor_shape));
+ cursor_set_shape(cursor_shape);
+ emscripten_exit_pointerlock();
+
+ } else if (p_mode == MOUSE_MODE_HIDDEN) {
+ set_css_cursor("none");
+ emscripten_exit_pointerlock();
+
+ } else if (p_mode == MOUSE_MODE_CAPTURED) {
+ EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false);
+ ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
+ ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
+ // set_css_cursor must be called before cursor_set_shape to make the cursor visible
+ set_css_cursor(godot2dom_cursor(cursor_shape));
+ cursor_set_shape(cursor_shape);
+ }
+}
+
+DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const {
+ if (is_css_cursor_hidden())
+ return MOUSE_MODE_HIDDEN;
+
+ EmscriptenPointerlockChangeEvent ev;
+ emscripten_get_pointerlock_status(&ev);
+ return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
+}
+
+// Wheel
+
+EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
+ ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
+ if (!is_canvas_focused()) {
+ if (cursor_inside_canvas) {
+ focus_canvas();
+ } else {
+ return false;
+ }
+ }
+
+ Input *input = Input::get_singleton();
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_position(input->get_mouse_position());
+ ev->set_global_position(ev->get_position());
+
+ ev->set_shift(input->is_key_pressed(KEY_SHIFT));
+ ev->set_alt(input->is_key_pressed(KEY_ALT));
+ ev->set_control(input->is_key_pressed(KEY_CONTROL));
+ ev->set_metakey(input->is_key_pressed(KEY_META));
+
+ if (p_event->deltaY < 0)
+ ev->set_button_index(BUTTON_WHEEL_UP);
+ else if (p_event->deltaY > 0)
+ ev->set_button_index(BUTTON_WHEEL_DOWN);
+ else if (p_event->deltaX > 0)
+ ev->set_button_index(BUTTON_WHEEL_LEFT);
+ else if (p_event->deltaX < 0)
+ ev->set_button_index(BUTTON_WHEEL_RIGHT);
+ else
+ return false;
+
+ // Different browsers give wildly different delta values, and we can't
+ // interpret deltaMode, so use default value for wheel events' factor.
+
+ int button_flag = 1 << (ev->get_button_index() - 1);
+
+ ev->set_pressed(true);
+ ev->set_button_mask(input->get_mouse_button_mask() | button_flag);
+ input->parse_input_event(ev);
+
+ ev->set_pressed(false);
+ ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag);
+ input->parse_input_event(ev);
+
+ return true;
+}
+
+// Touch
+EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
+ const EmscriptenTouchPoint &touch = p_event->touches[i];
+ if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
+ lowest_id_index = i;
+ if (!touch.isChanged)
+ continue;
+ ev->set_index(touch.identifier);
+ ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
+ display->touches[i] = ev->get_position();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
+
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ // Resume audio context after input in case autoplay was denied.
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
+ const EmscriptenTouchPoint &touch = p_event->touches[i];
+ if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
+ lowest_id_index = i;
+ if (!touch.isChanged)
+ continue;
+ ev->set_index(touch.identifier);
+ ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
+ Point2 &prev = display->touches[i];
+ ev->set_relative(ev->get_position() - prev);
+ prev = ev->get_position();
+
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ return true;
+}
+
+bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const {
+ return EM_ASM_INT({ return 'ontouchstart' in window; });
+}
+
+// Gamepad
+
+EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) {
+ Input *input = Input::get_singleton();
+ if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) {
+ String guid = "";
+ if (String::utf8(p_event->mapping) == "standard")
+ guid = "Default HTML5 Gamepad";
+ input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid);
+ } else {
+ input->joy_connection_changed(p_event->index, false, "");
+ }
+ return true;
+}
+
+void DisplayServerJavaScript::process_joypads() {
+ int joypad_count = emscripten_get_num_gamepads();
+ Input *input = Input::get_singleton();
+ for (int joypad = 0; joypad < joypad_count; joypad++) {
+ EmscriptenGamepadEvent state;
+ EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state);
+ // Chromium reserves gamepads slots, so NO_DATA is an expected result.
+ ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS &&
+ query_result != EMSCRIPTEN_RESULT_NO_DATA);
+ if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) {
+ int button_count = MIN(state.numButtons, 18);
+ int axis_count = MIN(state.numAxes, 8);
+ for (int button = 0; button < button_count; button++) {
+ float value = state.analogButton[button];
+ input->joy_button(joypad, button, value);
+ }
+ for (int axis = 0; axis < axis_count; axis++) {
+ Input::JoyAxis joy_axis;
+ joy_axis.min = -1;
+ joy_axis.value = state.axis[axis];
+ input->joy_axis(joypad, axis, joy_axis);
+ }
+ }
+ }
+}
+
+#if 0
+bool DisplayServerJavaScript::is_joy_known(int p_device) {
+
+ return Input::get_singleton()->is_joy_mapped(p_device);
+}
+
+
+String DisplayServerJavaScript::get_joy_guid(int p_device) const {
+
+ return Input::get_singleton()->get_joy_guid_remapped(p_device);
+}
+#endif
+
+Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() {
+ Vector<String> drivers;
+ drivers.push_back("dummy");
+ return drivers;
+}
+
+// Clipboard
+extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) {
+ // Only call set_clipboard from OS (sets local clipboard)
+ DisplayServerJavaScript::get_singleton()->clipboard = p_text;
+}
+
+void DisplayServerJavaScript::clipboard_set(const String &p_text) {
+ /* clang-format off */
+ int err = EM_ASM_INT({
+ var text = UTF8ToString($0);
+ if (!navigator.clipboard || !navigator.clipboard.writeText)
+ return 1;
+ navigator.clipboard.writeText(text).catch(function(e) {
+ // Setting OS clipboard is only possible from an input callback.
+ console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
+ });
+ return 0;
+ }, p_text.utf8().get_data());
+ /* clang-format on */
+ ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");
+}
+
+String DisplayServerJavaScript::clipboard_get() const {
+ /* clang-format off */
+ EM_ASM({
+ try {
+ navigator.clipboard.readText().then(function (result) {
+ ccall('update_clipboard', 'void', ['string'], [result]);
+ }).catch(function (e) {
+ // Fail graciously.
+ });
+ } catch (e) {
+ // Fail graciously.
+ }
+ });
+ /* clang-format on */
+ return clipboard;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) {
+ if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) {
+ cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+ }
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+ if (os->is_finalizing())
+ return; // We don't want events anymore.
+ DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
+ if (ds && !ds->window_event_callback.is_null()) {
+ Variant event = int(p_notification);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) {
+ /* clang-format off */
+ EM_ASM_({
+ window.alert(UTF8ToString($0));
+ }, p_alert.utf8().get_data());
+ /* clang-format on */
+}
+
+void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(p_icon.is_null());
+ Ref<Image> icon = p_icon;
+ if (icon->is_compressed()) {
+ icon = icon->duplicate();
+ ERR_FAIL_COND(icon->decompress() != OK);
+ }
+ if (icon->get_format() != Image::FORMAT_RGBA8) {
+ if (icon == p_icon)
+ icon = icon->duplicate();
+ icon->convert(Image::FORMAT_RGBA8);
+ }
+
+ png_image png_meta;
+ memset(&png_meta, 0, sizeof png_meta);
+ png_meta.version = PNG_IMAGE_VERSION;
+ png_meta.width = icon->get_width();
+ png_meta.height = icon->get_height();
+ png_meta.format = PNG_FORMAT_RGBA;
+
+ PackedByteArray png;
+ size_t len;
+ PackedByteArray data = icon->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
+
+ png.resize(len);
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
+
+ /* clang-format off */
+ EM_ASM({
+ var PNG_PTR = $0;
+ var PNG_LEN = $1;
+
+ var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" });
+ var url = URL.createObjectURL(png);
+ var link = document.getElementById('-gd-engine-icon');
+ if (link === null) {
+ link = document.createElement('link');
+ link.rel = 'icon';
+ link.id = '-gd-engine-icon';
+ document.head.appendChild(link);
+ }
+ link.href = url;
+ }, png.ptr(), len);
+ /* clang-format on */
+}
+
+void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+ if (os->is_finalizing())
+ return; // We don't want events anymore.
+
+ // Resume audio context after input in case autoplay was denied.
+ os->resume_audio();
+
+ Callable cb = get_singleton()->input_event_callback;
+ if (!cb.is_null()) {
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
+ cb.call((const Variant **)&evp, 1, ret, ce);
+ }
+}
+
+DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerJavaScript(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ r_error = OK; // Always succeeds for now.
+
+ /* clang-format off */
+ swap_cancel_ok = EM_ASM_INT({
+ const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
+ const plat = navigator.platform || "";
+ if (win.indexOf(plat) !== -1) {
+ return 1;
+ }
+ return 0;
+ }) == 1;
+ /* clang-format on */
+
+ RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?
+#if 0
+ EmscriptenWebGLContextAttributes attributes;
+ emscripten_webgl_init_context_attributes(&attributes);
+ attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
+ attributes.antialias = false;
+ ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER);
+
+ if (p_desired.layered) {
+ set_window_per_pixel_transparency_enabled(true);
+ }
+
+ bool gl_initialization_error = false;
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ attributes.majorVersion = 1;
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes);
+ if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.",
+ "Unable to initialize video driver");
+ return ERR_UNAVAILABLE;
+ }
+
+ video_driver_index = p_video_driver;
+#endif
+
+ /* clang-format off */
+ window_set_mode(p_mode);
+ if (EM_ASM_INT_V({ return Module['resizeCanvasOnStart'] })) {
+ /* clang-format on */
+ window_set_size(p_resolution);
+ }
+
+ EMSCRIPTEN_RESULT result;
+#define EM_CHECK(ev) \
+ if (result != EMSCRIPTEN_RESULT_SUCCESS) \
+ ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result));
+#define SET_EM_CALLBACK(target, ev, cb) \
+ result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \
+ EM_CHECK(ev)
+#define SET_EM_WINDOW_CALLBACK(ev, cb) \
+ result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \
+ EM_CHECK(ev)
+#define SET_EM_CALLBACK_NOTARGET(ev, cb) \
+ result = emscripten_set_##ev##_callback(nullptr, true, &cb); \
+ EM_CHECK(ev)
+ // These callbacks from Emscripten's html5.h suffice to access most
+ // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM
+ // is used below.
+ SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback)
+ SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback)
+ SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback)
+ SET_EM_CALLBACK(canvas_id, wheel, wheel_callback)
+ SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback)
+ SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, keydown, keydown_callback)
+ SET_EM_CALLBACK(canvas_id, keypress, keypress_callback)
+ SET_EM_CALLBACK(canvas_id, keyup, keyup_callback)
+ SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback)
+ SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback)
+ SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback)
+#undef SET_EM_CALLBACK_NOTARGET
+#undef SET_EM_CALLBACK
+#undef EM_CHECK
+
+ /* clang-format off */
+ EM_ASM_ARGS({
+ // Bind native event listeners.
+ // Module.listeners, and Module.drop_handler are defined in native/utils.js
+ const canvas = Module['canvas'];
+ const send_window_event = cwrap('send_window_event', null, ['number']);
+ const notifications = arguments;
+ (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
+ Module.listeners.add(canvas, event, send_window_event.bind(null, notifications[index]), true);
+ });
+ // Clipboard
+ const update_clipboard = cwrap('update_clipboard', null, ['string']);
+ Module.listeners.add(window, 'paste', function(evt) {
+ update_clipboard(evt.clipboardData.getData('text'));
+ }, false);
+ // Drag an drop
+ Module.listeners.add(canvas, 'dragover', function(ev) {
+ // Prevent default behavior (which would try to open the file(s))
+ ev.preventDefault();
+ }, false);
+ Module.listeners.add(canvas, 'drop', Module.drop_handler, false);
+ },
+ WINDOW_EVENT_MOUSE_ENTER,
+ WINDOW_EVENT_MOUSE_EXIT,
+ WINDOW_EVENT_FOCUS_IN,
+ WINDOW_EVENT_FOCUS_OUT
+ );
+ /* clang-format on */
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event);
+}
+
+DisplayServerJavaScript::~DisplayServerJavaScript() {
+ EM_ASM({
+ Module.listeners.clear();
+ });
+ //emscripten_webgl_commit_frame();
+ //emscripten_webgl_destroy_context(webgl_ctx);
+}
+
+bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ //case FEATURE_CONSOLE_WINDOW:
+ //case FEATURE_GLOBAL_MENU:
+ //case FEATURE_HIDPI:
+ //case FEATURE_IME:
+ case FEATURE_ICON:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
+ case FEATURE_MOUSE:
+ case FEATURE_TOUCHSCREEN:
+ return true;
+ //case FEATURE_MOUSE_WARP:
+ //case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_ICON:
+ //case FEATURE_NATIVE_VIDEO:
+ //case FEATURE_WINDOW_TRANSPARENCY:
+ //case FEATURE_KEEP_SCREEN_ON:
+ //case FEATURE_ORIENTATION:
+ //case FEATURE_VIRTUAL_KEYBOARD:
+ default:
+ return false;
+ }
+}
+
+void DisplayServerJavaScript::register_javascript_driver() {
+ register_create_function("javascript", create_func, get_rendering_drivers_func);
+}
+
+String DisplayServerJavaScript::get_name() const {
+ return "javascript";
+}
+
+int DisplayServerJavaScript::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerJavaScript::screen_get_position(int p_screen) const {
+ return Point2i(); // TODO offsetX/Y?
+}
+
+Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const {
+ EmscriptenFullscreenChangeEvent ev;
+ EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev);
+ ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2i());
+ return Size2i(ev.screenWidth, ev.screenHeight);
+}
+
+Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const {
+ int canvas[2];
+ emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1);
+ return Rect2i(0, 0, canvas[0], canvas[1]);
+}
+
+int DisplayServerJavaScript::screen_get_dpi(int p_screen) const {
+ return 96; // TODO maybe check pixel ratio via window.devicePixelRatio * 96? Inexact.
+}
+
+Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const {
+ Vector<WindowID> ret;
+ ret.push_back(MAIN_WINDOW_ID);
+ return ret;
+}
+
+DisplayServerJavaScript::WindowID DisplayServerJavaScript::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerJavaScript::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerJavaScript::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerJavaScript::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable; // TODO unused... do I need this?
+}
+
+void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ drop_files_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) {
+ /* clang-format off */
+ EM_ASM_({
+ document.title = UTF8ToString($0);
+ }, p_title.utf8().get_data());
+ /* clang-format on */
+}
+
+int DisplayServerJavaScript::window_get_current_screen(WindowID p_window) const {
+ return 1;
+}
+
+void DisplayServerJavaScript::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Not implemented.
+}
+
+Point2i DisplayServerJavaScript::window_get_position(WindowID p_window) const {
+ return Point2i(); // TODO Does this need implementation?
+}
+
+void DisplayServerJavaScript::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Not supported.
+}
+
+Size2i DisplayServerJavaScript::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerJavaScript::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Not supported.
+}
+
+Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) {
+ last_width = p_size.x;
+ last_height = p_size.y;
+ double scale = EM_ASM_DOUBLE({
+ return window.devicePixelRatio || 1;
+ });
+ emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale);
+ emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y);
+}
+
+Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const {
+ int canvas[2];
+ emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1);
+ return Size2(canvas[0], canvas[1]);
+}
+
+Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ if (window_mode == p_mode)
+ return;
+
+ switch (p_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ if (window_mode == WINDOW_MODE_FULLSCREEN) {
+ emscripten_exit_fullscreen();
+ }
+ window_mode = WINDOW_MODE_WINDOWED;
+ window_set_size(windowed_size);
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ EmscriptenFullscreenStrategy strategy;
+ strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
+ strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
+ strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
+ strategy.canvasResizedCallback = nullptr;
+ EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy);
+ ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
+ ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
+ } break;
+ case WINDOW_MODE_MAXIMIZED:
+ case WINDOW_MODE_MINIMIZED:
+ WARN_PRINT("WindowMode MAXIMIZED and MINIMIZED are not supported in HTML5 platform.");
+ break;
+ default:
+ break;
+ }
+}
+
+DisplayServerJavaScript::WindowMode DisplayServerJavaScript::window_get_mode(WindowID p_window) const {
+ return window_mode;
+}
+
+bool DisplayServerJavaScript::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerJavaScript::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Not supported.
+}
+
+bool DisplayServerJavaScript::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerJavaScript::window_request_attention(WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_move_to_foreground(WindowID p_window) {
+ // Not supported.
+}
+
+bool DisplayServerJavaScript::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerJavaScript::can_any_window_draw() const {
+ return true;
+}
+
+void DisplayServerJavaScript::process_events() {
+ if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS)
+ process_joypads();
+}
+
+int DisplayServerJavaScript::get_current_video_driver() const {
+ return 1;
+}
+
+bool DisplayServerJavaScript::get_swap_cancel_ok() {
+ return swap_cancel_ok;
+}
+
+void DisplayServerJavaScript::swap_buffers() {
+ //emscripten_webgl_commit_frame();
+}
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
new file mode 100644
index 0000000000..d7116be36f
--- /dev/null
+++ b/platform/javascript/display_server_javascript.h
@@ -0,0 +1,208 @@
+/*************************************************************************/
+/* display_server_javascript.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_JAVASCRIPT_H
+#define DISPLAY_SERVER_JAVASCRIPT_H
+
+#include "servers/display_server.h"
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+
+class DisplayServerJavaScript : public DisplayServer {
+ //int video_driver_index;
+
+ Vector2 windowed_size;
+
+ ObjectID window_attached_instance_id = {};
+
+ Ref<InputEventKey> deferred_key_event;
+ CursorShape cursor_shape = CURSOR_ARROW;
+ String cursors[CURSOR_MAX];
+ Map<CursorShape, Vector<Variant>> cursors_cache;
+ Point2 touches[32];
+
+ Point2i last_click_pos = Point2(-100, -100); // TODO check this again.
+ double last_click_ms = 0;
+ int last_click_button_index = -1;
+
+ int last_width = 0;
+ int last_height = 0;
+
+ bool swap_cancel_ok = false;
+
+ // utilities
+ static Point2 compute_position_in_canvas(int p_x, int p_y);
+ static void focus_canvas();
+ static bool is_canvas_focused();
+ template <typename T>
+ static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event);
+ static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event);
+ static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape);
+ static void set_css_cursor(const char *p_cursor);
+ bool is_css_cursor_hidden() const;
+
+ // events
+ static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
+
+ static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+
+ static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
+ static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
+
+ static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data);
+
+ static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+ static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+
+ static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data);
+ void process_joypads();
+
+ static Vector<String> get_rendering_drivers_func();
+ static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+
+ static void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+protected:
+ int get_current_video_driver() const;
+
+public:
+ // Override return type to make writing static callbacks less tedious.
+ static DisplayServerJavaScript *get_singleton();
+ static char canvas_id[256];
+
+ WindowMode window_mode = WINDOW_MODE_WINDOWED;
+
+ String clipboard;
+
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ // utilities
+ bool check_size_force_redraw();
+
+ // from DisplayServer
+ void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+ bool has_feature(Feature p_feature) const override;
+ String get_name() const override;
+
+ // cursor
+ void cursor_set_shape(CursorShape p_shape) override;
+ CursorShape cursor_get_shape() const override;
+ void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+
+ // mouse
+ void mouse_set_mode(MouseMode p_mode) override;
+ MouseMode mouse_get_mode() const override;
+
+ // touch
+ bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ // clipboard
+ void clipboard_set(const String &p_text) override;
+ String clipboard_get() const override;
+
+ // screen
+ int get_screen_count() const override;
+ Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ // windows
+ Vector<DisplayServer::WindowID> get_window_list() const override;
+ WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ bool can_any_window_draw() const override;
+
+ // events
+ void process_events() override;
+
+ // icon
+ void set_icon(const Ref<Image> &p_icon) override;
+
+ // others
+ bool get_swap_cancel_ok() override;
+ void swap_buffers() override;
+
+ static void register_javascript_driver();
+ DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerJavaScript();
+};
+
+#endif // DISPLAY_SERVER_JAVASCRIPT_H
diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc
index 25e88f99d1..e3f2ce42b4 100644
--- a/platform/javascript/dom_keys.inc
+++ b/platform/javascript/dom_keys.inc
@@ -30,351 +30,203 @@
#include "core/os/keyboard.h"
-// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Constants_for_keyCode_value
-#define DOM_VK_CANCEL 0x03
-#define DOM_VK_HELP 0x06
-#define DOM_VK_BACK_SPACE 0x08
-#define DOM_VK_TAB 0x09
-#define DOM_VK_CLEAR 0x0C
-#define DOM_VK_RETURN 0x0D
-#define DOM_VK_ENTER 0x0E // "Reserved, but not used."
-#define DOM_VK_SHIFT 0x10
-#define DOM_VK_CONTROL 0x11
-#define DOM_VK_ALT 0x12
-#define DOM_VK_PAUSE 0x13
-#define DOM_VK_CAPS_LOCK 0x14
-#define DOM_VK_KANA 0x15
-#define DOM_VK_HANGUL 0x15
-#define DOM_VK_EISU 0x16
-#define DOM_VK_JUNJA 0x17
-#define DOM_VK_FINAL 0x18
-#define DOM_VK_HANJA 0x19
-#define DOM_VK_KANJI 0x19
-#define DOM_VK_ESCAPE 0x1B
-#define DOM_VK_CONVERT 0x1C
-#define DOM_VK_NONCONVERT 0x1D
-#define DOM_VK_ACCEPT 0x1E
-#define DOM_VK_MODECHANGE 0x1F
-#define DOM_VK_SPACE 0x20
-#define DOM_VK_PAGE_UP 0x21
-#define DOM_VK_PAGE_DOWN 0x22
-#define DOM_VK_END 0x23
-#define DOM_VK_HOME 0x24
-#define DOM_VK_LEFT 0x25
-#define DOM_VK_UP 0x26
-#define DOM_VK_RIGHT 0x27
-#define DOM_VK_DOWN 0x28
-#define DOM_VK_SELECT 0x29
-#define DOM_VK_PRINT 0x2A
-#define DOM_VK_EXECUTE 0x2B
-#define DOM_VK_PRINTSCREEN 0x2C
-#define DOM_VK_INSERT 0x2D
-#define DOM_VK_DELETE 0x2E
-#define DOM_VK_0 0x30
-#define DOM_VK_1 0x31
-#define DOM_VK_2 0x32
-#define DOM_VK_3 0x33
-#define DOM_VK_4 0x34
-#define DOM_VK_5 0x35
-#define DOM_VK_6 0x36
-#define DOM_VK_7 0x37
-#define DOM_VK_8 0x38
-#define DOM_VK_9 0x39
-#define DOM_VK_COLON 0x3A
-#define DOM_VK_SEMICOLON 0x3B
-#define DOM_VK_LESS_THAN 0x3C
-#define DOM_VK_EQUALS 0x3D
-#define DOM_VK_GREATER_THAN 0x3E
-#define DOM_VK_QUESTION_MARK 0x3F
-#define DOM_VK_AT 0x40
-#define DOM_VK_A 0x41
-#define DOM_VK_B 0x42
-#define DOM_VK_C 0x43
-#define DOM_VK_D 0x44
-#define DOM_VK_E 0x45
-#define DOM_VK_F 0x46
-#define DOM_VK_G 0x47
-#define DOM_VK_H 0x48
-#define DOM_VK_I 0x49
-#define DOM_VK_J 0x4A
-#define DOM_VK_K 0x4B
-#define DOM_VK_L 0x4C
-#define DOM_VK_M 0x4D
-#define DOM_VK_N 0x4E
-#define DOM_VK_O 0x4F
-#define DOM_VK_P 0x50
-#define DOM_VK_Q 0x51
-#define DOM_VK_R 0x52
-#define DOM_VK_S 0x53
-#define DOM_VK_T 0x54
-#define DOM_VK_U 0x55
-#define DOM_VK_V 0x56
-#define DOM_VK_W 0x57
-#define DOM_VK_X 0x58
-#define DOM_VK_Y 0x59
-#define DOM_VK_Z 0x5A
-#define DOM_VK_WIN 0x5B
-#define DOM_VK_CONTEXT_MENU 0x5D
-#define DOM_VK_SLEEP 0x5F
-#define DOM_VK_NUMPAD0 0x60
-#define DOM_VK_NUMPAD1 0x61
-#define DOM_VK_NUMPAD2 0x62
-#define DOM_VK_NUMPAD3 0x63
-#define DOM_VK_NUMPAD4 0x64
-#define DOM_VK_NUMPAD5 0x65
-#define DOM_VK_NUMPAD6 0x66
-#define DOM_VK_NUMPAD7 0x67
-#define DOM_VK_NUMPAD8 0x68
-#define DOM_VK_NUMPAD9 0x69
-#define DOM_VK_MULTIPLY 0x6A
-#define DOM_VK_ADD 0x6B
-#define DOM_VK_SEPARATOR 0x6C
-#define DOM_VK_SUBTRACT 0x6D
-#define DOM_VK_DECIMAL 0x6E
-#define DOM_VK_DIVIDE 0x6F
-#define DOM_VK_F1 0x70
-#define DOM_VK_F2 0x71
-#define DOM_VK_F3 0x72
-#define DOM_VK_F4 0x73
-#define DOM_VK_F5 0x74
-#define DOM_VK_F6 0x75
-#define DOM_VK_F7 0x76
-#define DOM_VK_F8 0x77
-#define DOM_VK_F9 0x78
-#define DOM_VK_F10 0x79
-#define DOM_VK_F11 0x7A
-#define DOM_VK_F12 0x7B
-#define DOM_VK_F13 0x7C
-#define DOM_VK_F14 0x7D
-#define DOM_VK_F15 0x7E
-#define DOM_VK_F16 0x7F
-#define DOM_VK_F17 0x80
-#define DOM_VK_F18 0x81
-#define DOM_VK_F19 0x82
-#define DOM_VK_F20 0x83
-#define DOM_VK_F21 0x84
-#define DOM_VK_F22 0x85
-#define DOM_VK_F23 0x86
-#define DOM_VK_F24 0x87
-#define DOM_VK_NUM_LOCK 0x90
-#define DOM_VK_SCROLL_LOCK 0x91
-#define DOM_VK_WIN_OEM_FJ_JISHO 0x92
-#define DOM_VK_WIN_OEM_FJ_MASSHOU 0x93
-#define DOM_VK_WIN_OEM_FJ_TOUROKU 0x94
-#define DOM_VK_WIN_OEM_FJ_LOYA 0x95
-#define DOM_VK_WIN_OEM_FJ_ROYA 0x96
-#define DOM_VK_CIRCUMFLEX 0xA0
-#define DOM_VK_EXCLAMATION 0xA1
-#define DOM_VK_DOUBLE_QUOTE 0xA2
-#define DOM_VK_HASH 0xA3
-#define DOM_VK_DOLLAR 0xA4
-#define DOM_VK_PERCENT 0xA5
-#define DOM_VK_AMPERSAND 0xA6
-#define DOM_VK_UNDERSCORE 0xA7
-#define DOM_VK_OPEN_PAREN 0xA8
-#define DOM_VK_CLOSE_PAREN 0xA9
-#define DOM_VK_ASTERISK 0xAA
-#define DOM_VK_PLUS 0xAB
-#define DOM_VK_PIPE 0xAC
-#define DOM_VK_HYPHEN_MINUS 0xAD
-#define DOM_VK_OPEN_CURLY_BRACKET 0xAE
-#define DOM_VK_CLOSE_CURLY_BRACKET 0xAF
-#define DOM_VK_TILDE 0xB0
-#define DOM_VK_VOLUME_MUTE 0xB5
-#define DOM_VK_VOLUME_DOWN 0xB6
-#define DOM_VK_VOLUME_UP 0xB7
-#define DOM_VK_COMMA 0xBC
-#define DOM_VK_PERIOD 0xBE
-#define DOM_VK_SLASH 0xBF
-#define DOM_VK_BACK_QUOTE 0xC0
-#define DOM_VK_OPEN_BRACKET 0xDB
-#define DOM_VK_BACK_SLASH 0xDC
-#define DOM_VK_CLOSE_BRACKET 0xDD
-#define DOM_VK_QUOTE 0xDE
-#define DOM_VK_META 0xE0
-#define DOM_VK_ALTGR 0xE1
-#define DOM_VK_WIN_ICO_HELP 0xE3
-#define DOM_VK_WIN_ICO_00 0xE4
-#define DOM_VK_WIN_ICO_CLEAR 0xE6
-#define DOM_VK_WIN_OEM_RESET 0xE9
-#define DOM_VK_WIN_OEM_JUMP 0xEA
-#define DOM_VK_WIN_OEM_PA1 0xEB
-#define DOM_VK_WIN_OEM_PA2 0xEC
-#define DOM_VK_WIN_OEM_PA3 0xED
-#define DOM_VK_WIN_OEM_WSCTRL 0xEE
-#define DOM_VK_WIN_OEM_CUSEL 0xEF
-#define DOM_VK_WIN_OEM_ATTN 0xF0
-#define DOM_VK_WIN_OEM_FINISH 0xF1
-#define DOM_VK_WIN_OEM_COPY 0xF2
-#define DOM_VK_WIN_OEM_AUTO 0xF3
-#define DOM_VK_WIN_OEM_ENLW 0xF4
-#define DOM_VK_WIN_OEM_BACKTAB 0xF5
-#define DOM_VK_ATTN 0xF6
-#define DOM_VK_CRSEL 0xF7
-#define DOM_VK_EXSEL 0xF8
-#define DOM_VK_EREOF 0xF9
-#define DOM_VK_PLAY 0xFA
-#define DOM_VK_ZOOM 0xFB
-#define DOM_VK_PA1 0xFD
-#define DOM_VK_WIN_OEM_CLEAR 0xFE
-
-int dom2godot_scancode(int dom_keycode) {
-
- if (DOM_VK_0 <= dom_keycode && dom_keycode <= DOM_VK_Z) {
- // ASCII intersection
- return dom_keycode;
- }
-
- if (DOM_VK_NUMPAD0 <= dom_keycode && dom_keycode <= DOM_VK_NUMPAD9) {
- // Numpad numbers
- return KEY_KP_0 + (dom_keycode - DOM_VK_NUMPAD0);
+// See https://w3c.github.io/uievents-code/#code-value-tables
+int dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], bool p_physical) {
+#define DOM2GODOT(p_str, p_godot_code) \
+ if (memcmp((const void *)p_str, (void *)p_code, strlen(p_str) + 1) == 0) { \
+ return KEY_##p_godot_code; \
}
- if (DOM_VK_F1 <= dom_keycode && dom_keycode <= DOM_VK_F16) {
- // F1-F16
- return KEY_F1 + (dom_keycode - DOM_VK_F1);
+ // Numpad section.
+ DOM2GODOT("NumLock", NUMLOCK);
+ DOM2GODOT("Numpad0", KP_0);
+ DOM2GODOT("Numpad1", KP_1);
+ DOM2GODOT("Numpad2", KP_2);
+ DOM2GODOT("Numpad3", KP_3);
+ DOM2GODOT("Numpad4", KP_4);
+ DOM2GODOT("Numpad5", KP_5);
+ DOM2GODOT("Numpad6", KP_6);
+ DOM2GODOT("Numpad7", KP_7);
+ DOM2GODOT("Numpad8", KP_8);
+ DOM2GODOT("Numpad9", KP_9);
+ DOM2GODOT("NumpadAdd", KP_ADD);
+ DOM2GODOT("NumpadBackspace", BACKSPACE);
+ DOM2GODOT("NumpadClear", CLEAR);
+ DOM2GODOT("NumpadClearEntry", CLEAR);
+ //DOM2GODOT("NumpadComma", UNKNOWN);
+ DOM2GODOT("NumpadDecimal", KP_PERIOD);
+ DOM2GODOT("NumpadDivide", KP_DIVIDE);
+ DOM2GODOT("NumpadEnter", KP_ENTER);
+ DOM2GODOT("NumpadEqual", EQUAL);
+ //DOM2GODOT("NumpadHash", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryAdd", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryClear", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryRecall", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryStore", UNKNOWN);
+ //DOM2GODOT("NumpadMemorySubtract", UNKNOWN);
+ DOM2GODOT("NumpadMultiply", KP_MULTIPLY);
+ DOM2GODOT("NumpadParenLeft", PARENLEFT);
+ DOM2GODOT("NumpadParenRight", PARENRIGHT);
+ DOM2GODOT("NumpadStar", KP_MULTIPLY); // or ASTERISK ?
+ DOM2GODOT("NumpadSubtract", KP_SUBTRACT);
+
+ // Printable ASCII.
+ if (!p_physical) {
+ uint8_t b0 = (uint8_t)p_key[0];
+ uint8_t b1 = (uint8_t)p_key[1];
+ uint8_t b2 = (uint8_t)p_key[2];
+ if (b1 == 0 && b0 > 0x1F && b0 < 0x7F) { // ASCII.
+ if (b0 > 0x60 && b0 < 0x7B) { // Lowercase ASCII.
+ b0 -= 32;
+ }
+ return b0;
+ }
+
+#define _U_2BYTES_MASK 0xE0
+#define _U_2BYTES 0xC0
+ // Latin-1 codes.
+ if (b2 == 0 && (b0 & _U_2BYTES_MASK) == _U_2BYTES) { // 2-bytes utf8, only known latin.
+ uint32_t key = ((b0 & ~_U_2BYTES_MASK) << 6) | (b1 & 0x3F);
+ if (key >= 0xA0 && key <= 0xDF) {
+ return key;
+ }
+ if (key >= 0xE0 && key <= 0xFF) { // Lowercase known latin.
+ key -= 0x20;
+ return key;
+ }
+ }
+#undef _U_2BYTES_MASK
+#undef _U_2BYTES
}
- switch (dom_keycode) {
- //case DOM_VK_CANCEL: return KEY_UNKNOWN;
- case DOM_VK_HELP: return KEY_HELP;
- case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
- case DOM_VK_TAB: return KEY_TAB;
-
- case DOM_VK_CLEAR:
- case DOM_VK_WIN_OEM_CLEAR: // OEM duplicate
- return KEY_CLEAR;
-
- case DOM_VK_RETURN:
- case DOM_VK_ENTER: // unused according to MDN
- return KEY_ENTER;
-
- case DOM_VK_SHIFT: return KEY_SHIFT;
- case DOM_VK_CONTROL: return KEY_CONTROL;
-
- case DOM_VK_ALT:
- case DOM_VK_ALTGR:
- return KEY_ALT;
-
- case DOM_VK_PAUSE: return KEY_PAUSE;
- case DOM_VK_CAPS_LOCK:
- return KEY_CAPSLOCK;
-
- /*
- case DOM_VK_KANA: return KEY_UNKNOWN;
- case DOM_VK_HANGUL: return KEY_UNKNOWN;
- case DOM_VK_EISU: return KEY_UNKNOWN;
- case DOM_VK_JUNJA: return KEY_UNKNOWN;
- case DOM_VK_FINAL: return KEY_UNKNOWN;
- case DOM_VK_HANJA: return KEY_UNKNOWN;
- case DOM_VK_KANJI: return KEY_UNKNOWN;
- */
-
- case DOM_VK_ESCAPE:
- return KEY_ESCAPE;
- /*
- case DOM_VK_CONVERT: return KEY_UNKNOWN;
- case DOM_VK_NONCONVERT: return KEY_UNKNOWN;
- case DOM_VK_ACCEPT: return KEY_UNKNOWN;
- case DOM_VK_MODECHANGE: return KEY_UNKNOWN;
- */
-
- case DOM_VK_SPACE: return KEY_SPACE;
- case DOM_VK_PAGE_UP: return KEY_PAGEUP;
- case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
- case DOM_VK_END: return KEY_END;
- case DOM_VK_HOME: return KEY_HOME;
- case DOM_VK_LEFT: return KEY_LEFT;
- case DOM_VK_UP: return KEY_UP;
- case DOM_VK_RIGHT: return KEY_RIGHT;
- case DOM_VK_DOWN:
- return KEY_DOWN;
-
- //case DOM_VK_SELECT: return KEY_UNKNOWN;
-
- case DOM_VK_PRINTSCREEN:
- case DOM_VK_PRINT:
- return KEY_PRINT;
-
- //case DOM_VK_EXECUTE: return KEY_UNKNOWN;
- case DOM_VK_INSERT: return KEY_INSERT;
- case DOM_VK_DELETE: return KEY_DELETE;
-
- case DOM_VK_META:
- case DOM_VK_WIN:
- return KEY_META;
-
- case DOM_VK_CONTEXT_MENU: return KEY_MENU;
- case DOM_VK_SLEEP:
- return KEY_STANDBY;
-
- // Numpad keys
- case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
- case DOM_VK_ADD: return KEY_KP_ADD;
- case DOM_VK_SEPARATOR:
- return KEY_KP_PERIOD; // Good enough?
- case DOM_VK_SUBTRACT: return KEY_KP_SUBTRACT;
- case DOM_VK_DECIMAL: return KEY_KP_PERIOD;
- case DOM_VK_DIVIDE:
- return KEY_KP_DIVIDE;
-
- /*
- case DOM_VK_F17: return KEY_UNKNOWN;
- case DOM_VK_F18: return KEY_UNKNOWN;
- case DOM_VK_F19: return KEY_UNKNOWN;
- case DOM_VK_F20: return KEY_UNKNOWN;
- case DOM_VK_F21: return KEY_UNKNOWN;
- case DOM_VK_F22: return KEY_UNKNOWN;
- case DOM_VK_F23: return KEY_UNKNOWN;
- case DOM_VK_F24: return KEY_UNKNOWN;
- */
-
- case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
- case DOM_VK_SCROLL_LOCK:
- return KEY_SCROLLLOCK;
-
- /*
- case DOM_VK_WIN_OEM_FJ_JISHO: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_MASSHOU: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_TOUROKU: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_LOYA: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_ROYA: return KEY_UNKNOWN;
- */
-
- case DOM_VK_CIRCUMFLEX: return KEY_ASCIICIRCUM;
- case DOM_VK_EXCLAMATION: return KEY_EXCLAM;
- case DOM_VK_DOUBLE_QUOTE: return KEY_QUOTEDBL;
- case DOM_VK_HASH: return KEY_NUMBERSIGN;
- case DOM_VK_DOLLAR: return KEY_DOLLAR;
- case DOM_VK_PERCENT: return KEY_PERCENT;
- case DOM_VK_AMPERSAND: return KEY_AMPERSAND;
- case DOM_VK_UNDERSCORE: return KEY_UNDERSCORE;
- case DOM_VK_OPEN_PAREN: return KEY_PARENLEFT;
- case DOM_VK_CLOSE_PAREN: return KEY_PARENRIGHT;
- case DOM_VK_ASTERISK: return KEY_ASTERISK;
- case DOM_VK_PLUS: return KEY_PLUS;
- case DOM_VK_PIPE: return KEY_BAR;
- case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
- case DOM_VK_OPEN_CURLY_BRACKET: return KEY_BRACELEFT;
- case DOM_VK_CLOSE_CURLY_BRACKET: return KEY_BRACERIGHT;
- case DOM_VK_TILDE: return KEY_ASCIITILDE;
-
- case DOM_VK_VOLUME_MUTE: return KEY_VOLUMEMUTE;
- case DOM_VK_VOLUME_DOWN: return KEY_VOLUMEDOWN;
- case DOM_VK_VOLUME_UP: return KEY_VOLUMEUP;
-
- case DOM_VK_COMMA: return KEY_COMMA;
- case DOM_VK_PERIOD: return KEY_PERIOD;
- case DOM_VK_SLASH: return KEY_SLASH;
- case DOM_VK_BACK_QUOTE: return KEY_QUOTELEFT;
- case DOM_VK_OPEN_BRACKET: return KEY_BRACKETLEFT;
- case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
- case DOM_VK_CLOSE_BRACKET: return KEY_BRACKETRIGHT;
- case DOM_VK_QUOTE:
- return KEY_APOSTROPHE;
-
- // The rest is OEM/unusual.
-
- default: return KEY_UNKNOWN;
- };
+ // Alphanumeric section.
+ DOM2GODOT("Backquote", QUOTELEFT);
+ DOM2GODOT("Backslash", BACKSLASH);
+ DOM2GODOT("BracketLeft", BRACKETLEFT);
+ DOM2GODOT("BracketRight", BRACKETRIGHT);
+ DOM2GODOT("Comma", COMMA);
+ DOM2GODOT("Digit0", 0);
+ DOM2GODOT("Digit1", 1);
+ DOM2GODOT("Digit2", 2);
+ DOM2GODOT("Digit3", 3);
+ DOM2GODOT("Digit4", 4);
+ DOM2GODOT("Digit5", 5);
+ DOM2GODOT("Digit6", 6);
+ DOM2GODOT("Digit7", 7);
+ DOM2GODOT("Digit8", 8);
+ DOM2GODOT("Digit9", 9);
+ DOM2GODOT("Equal", EQUAL);
+ DOM2GODOT("IntlBackslash", BACKSLASH);
+ //DOM2GODOT("IntlRo", UNKNOWN);
+ DOM2GODOT("IntlYen", YEN);
+
+ DOM2GODOT("KeyA", A);
+ DOM2GODOT("KeyB", B);
+ DOM2GODOT("KeyC", C);
+ DOM2GODOT("KeyD", D);
+ DOM2GODOT("KeyE", E);
+ DOM2GODOT("KeyF", F);
+ DOM2GODOT("KeyG", G);
+ DOM2GODOT("KeyH", H);
+ DOM2GODOT("KeyI", I);
+ DOM2GODOT("KeyJ", J);
+ DOM2GODOT("KeyK", K);
+ DOM2GODOT("KeyL", L);
+ DOM2GODOT("KeyM", M);
+ DOM2GODOT("KeyN", N);
+ DOM2GODOT("KeyO", O);
+ DOM2GODOT("KeyP", P);
+ DOM2GODOT("KeyQ", Q);
+ DOM2GODOT("KeyR", R);
+ DOM2GODOT("KeyS", S);
+ DOM2GODOT("KeyT", T);
+ DOM2GODOT("KeyU", U);
+ DOM2GODOT("KeyV", V);
+ DOM2GODOT("KeyW", W);
+ DOM2GODOT("KeyX", X);
+ DOM2GODOT("KeyY", Y);
+ DOM2GODOT("KeyZ", Z);
+
+ DOM2GODOT("Minus", MINUS);
+ DOM2GODOT("Period", PERIOD);
+ DOM2GODOT("Quote", APOSTROPHE);
+ DOM2GODOT("Semicolon", SEMICOLON);
+ DOM2GODOT("Slash", SLASH);
+
+ // Functional keys in the Alphanumeric section.
+ DOM2GODOT("AltLeft", ALT);
+ DOM2GODOT("AltRight", ALT);
+ DOM2GODOT("Backspace", BACKSPACE);
+ DOM2GODOT("CapsLock", CAPSLOCK);
+ DOM2GODOT("ContextMenu", MENU);
+ DOM2GODOT("ControlLeft", CONTROL);
+ DOM2GODOT("ControlRight", CONTROL);
+ DOM2GODOT("Enter", ENTER);
+ DOM2GODOT("MetaLeft", SUPER_L);
+ DOM2GODOT("MetaRight", SUPER_R);
+ DOM2GODOT("ShiftLeft", SHIFT);
+ DOM2GODOT("ShiftRight", SHIFT);
+ DOM2GODOT("Space", SPACE);
+ DOM2GODOT("Tab", TAB);
+
+ // ControlPad section.
+ DOM2GODOT("Delete", DELETE);
+ DOM2GODOT("End", END);
+ DOM2GODOT("Help", HELP);
+ DOM2GODOT("Home", HOME);
+ DOM2GODOT("Insert", INSERT);
+ DOM2GODOT("PageDown", PAGEDOWN);
+ DOM2GODOT("PageUp", PAGEUP);
+
+ // ArrowPad section.
+ DOM2GODOT("ArrowDown", DOWN);
+ DOM2GODOT("ArrowLeft", LEFT);
+ DOM2GODOT("ArrowRight", RIGHT);
+ DOM2GODOT("ArrowUp", UP);
+
+ // Function section.
+ DOM2GODOT("Escape", ESCAPE);
+ DOM2GODOT("F1", F1);
+ DOM2GODOT("F2", F2);
+ DOM2GODOT("F3", F3);
+ DOM2GODOT("F4", F4);
+ DOM2GODOT("F5", F5);
+ DOM2GODOT("F6", F6);
+ DOM2GODOT("F7", F7);
+ DOM2GODOT("F8", F8);
+ DOM2GODOT("F9", F9);
+ DOM2GODOT("F10", F10);
+ DOM2GODOT("F11", F11);
+ DOM2GODOT("F12", F12);
+ //DOM2GODOT("Fn", UNKNOWN); // never actually fired, but included in the standard draft.
+ //DOM2GODOT("FnLock", UNKNOWN);
+ DOM2GODOT("PrintScreen", PRINT);
+ DOM2GODOT("ScrollLock", SCROLLLOCK);
+ DOM2GODOT("Pause", PAUSE);
+
+ // Media keys section.
+ DOM2GODOT("BrowserBack", BACK);
+ DOM2GODOT("BrowserFavorites", FAVORITES);
+ DOM2GODOT("BrowserForward", FORWARD);
+ DOM2GODOT("BrowserHome", OPENURL);
+ DOM2GODOT("BrowserRefresh", REFRESH);
+ DOM2GODOT("BrowserSearch", SEARCH);
+ DOM2GODOT("BrowserStop", STOP);
+ //DOM2GODOT("Eject", UNKNOWN);
+ DOM2GODOT("LaunchApp1", LAUNCH0);
+ DOM2GODOT("LaunchApp2", LAUNCH1);
+ DOM2GODOT("LaunchMail", LAUNCHMAIL);
+ DOM2GODOT("MediaPlayPause", MEDIAPLAY);
+ DOM2GODOT("MediaSelect", LAUNCHMEDIA);
+ DOM2GODOT("MediaStop", MEDIASTOP);
+ DOM2GODOT("MediaTrackNext", MEDIANEXT);
+ DOM2GODOT("MediaTrackPrevious", MEDIAPREVIOUS);
+ //DOM2GODOT("Power", UNKNOWN);
+ //DOM2GODOT("Sleep", UNKNOWN);
+ DOM2GODOT("AudioVolumeDown", VOLUMEDOWN);
+ DOM2GODOT("AudioVolumeMute", VOLUMEMUTE);
+ DOM2GODOT("AudioVolumeUp", VOLUMEUP);
+ //DOM2GODOT("WakeUp", UNKNOWN);
+ return KEY_UNKNOWN;
+#undef DOM2GODOT
}
diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py
new file mode 100644
index 0000000000..a55c9d3f48
--- /dev/null
+++ b/platform/javascript/emscripten_helpers.py
@@ -0,0 +1,38 @@
+import os
+
+
+def parse_config():
+ em_config_file = os.getenv("EM_CONFIG") or os.path.expanduser("~/.emscripten")
+ if not os.path.exists(em_config_file):
+ raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
+
+ normalized = {}
+ em_config = {}
+ with open(em_config_file) as f:
+ try:
+ # Emscripten configuration file is a Python file with simple assignments.
+ exec(f.read(), em_config)
+ except StandardError as e:
+ raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
+ normalized["EMCC_ROOT"] = em_config.get("EMSCRIPTEN_ROOT")
+ normalized["NODE_JS"] = em_config.get("NODE_JS")
+ normalized["CLOSURE_BIN"] = os.path.join(normalized["EMCC_ROOT"], "node_modules", ".bin", "google-closure-compiler")
+ return normalized
+
+
+def run_closure_compiler(target, source, env, for_signature):
+ cfg = parse_config()
+ cmd = [cfg["NODE_JS"], cfg["CLOSURE_BIN"]]
+ cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"])
+ for f in env["JSEXTERNS"]:
+ cmd.extend(["--externs", f.get_abspath()])
+ for f in source:
+ cmd.extend(["--js", f.get_abspath()])
+ cmd.extend(["--js_output_file", target[0].get_abspath()])
+ return " ".join(cmd)
+
+
+def create_engine_file(env, target, source, externs):
+ if env["use_closure_compiler"]:
+ return env.BuildJS(target, source, JSEXTERNS=externs)
+ return env.Textfile(target, [env.File(s) for s in source])
diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js
deleted file mode 100644
index 1f78aa672d..0000000000
--- a/platform/javascript/engine.js
+++ /dev/null
@@ -1,413 +0,0 @@
- // The following is concatenated with generated code, and acts as the end
- // of a wrapper for said code. See pre.js for the other part of the
- // wrapper.
- exposedLibs['PATH'] = PATH;
- exposedLibs['FS'] = FS;
- return Module;
- },
-};
-
-(function() {
- var engine = Engine;
-
- var DOWNLOAD_ATTEMPTS_MAX = 4;
-
- var basePath = null;
- var wasmFilenameExtensionOverride = null;
- var engineLoadPromise = null;
-
- var loadingFiles = {};
-
- function getPathLeaf(path) {
-
- while (path.endsWith('/'))
- path = path.slice(0, -1);
- return path.slice(path.lastIndexOf('/') + 1);
- }
-
- function getBasePath(path) {
-
- if (path.endsWith('/'))
- path = path.slice(0, -1);
- if (path.lastIndexOf('.') > path.lastIndexOf('/'))
- path = path.slice(0, path.lastIndexOf('.'));
- return path;
- }
-
- function getBaseName(path) {
-
- return getPathLeaf(getBasePath(path));
- }
-
- Engine = function Engine() {
-
- this.rtenv = null;
-
- var LIBS = {};
-
- var initPromise = null;
- var unloadAfterInit = true;
-
- var preloadedFiles = [];
-
- var resizeCanvasOnStart = true;
- var progressFunc = null;
- var preloadProgressTracker = {};
- var lastProgress = { loaded: 0, total: 0 };
-
- var canvas = null;
- var executableName = null;
- var locale = null;
- var stdout = null;
- var stderr = null;
-
- this.init = function(newBasePath) {
-
- if (!initPromise) {
- initPromise = Engine.load(newBasePath).then(
- instantiate.bind(this)
- );
- requestAnimationFrame(animateProgress);
- if (unloadAfterInit)
- initPromise.then(Engine.unloadEngine);
- }
- return initPromise;
- };
-
- function instantiate(wasmBuf) {
-
- var rtenvProps = {
- engine: this,
- ENV: {},
- };
- if (typeof stdout === 'function')
- rtenvProps.print = stdout;
- if (typeof stderr === 'function')
- rtenvProps.printErr = stderr;
- rtenvProps.instantiateWasm = function(imports, onSuccess) {
- WebAssembly.instantiate(wasmBuf, imports).then(function(result) {
- onSuccess(result.instance);
- });
- return {};
- };
-
- return new Promise(function(resolve, reject) {
- rtenvProps.onRuntimeInitialized = resolve;
- rtenvProps.onAbort = reject;
- rtenvProps.thisProgram = executableName;
- rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps, LIBS);
- });
- }
-
- this.preloadFile = function(pathOrBuffer, destPath) {
-
- if (pathOrBuffer instanceof ArrayBuffer) {
- pathOrBuffer = new Uint8Array(pathOrBuffer);
- } else if (ArrayBuffer.isView(pathOrBuffer)) {
- pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
- }
- if (pathOrBuffer instanceof Uint8Array) {
- preloadedFiles.push({
- path: destPath,
- buffer: pathOrBuffer
- });
- return Promise.resolve();
- } else if (typeof pathOrBuffer === 'string') {
- return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) {
- preloadedFiles.push({
- path: destPath || pathOrBuffer,
- buffer: xhr.response
- });
- });
- } else {
- throw Promise.reject("Invalid object for preloading");
- }
- };
-
- this.start = function() {
-
- return this.init().then(
- Function.prototype.apply.bind(synchronousStart, this, arguments)
- );
- };
-
- this.startGame = function(execName, mainPack) {
-
- executableName = execName;
- var mainArgs = [ '--main-pack', mainPack ];
-
- return Promise.all([
- // Load from directory,
- this.init(getBasePath(mainPack)),
- // ...but write to root where the engine expects it.
- this.preloadFile(mainPack, getPathLeaf(mainPack))
- ]).then(
- Function.prototype.apply.bind(synchronousStart, this, mainArgs)
- );
- };
-
- function synchronousStart() {
-
- if (canvas instanceof HTMLCanvasElement) {
- this.rtenv.canvas = canvas;
- } else {
- var firstCanvas = document.getElementsByTagName('canvas')[0];
- if (firstCanvas instanceof HTMLCanvasElement) {
- this.rtenv.canvas = firstCanvas;
- } else {
- throw new Error("No canvas found");
- }
- }
-
- var actualCanvas = this.rtenv.canvas;
- // canvas can grab focus on click
- if (actualCanvas.tabIndex < 0) {
- actualCanvas.tabIndex = 0;
- }
- // necessary to calculate cursor coordinates correctly
- actualCanvas.style.padding = 0;
- actualCanvas.style.borderWidth = 0;
- actualCanvas.style.borderStyle = 'none';
- // disable right-click context menu
- actualCanvas.addEventListener('contextmenu', function(ev) {
- ev.preventDefault();
- }, false);
- // until context restoration is implemented
- actualCanvas.addEventListener('webglcontextlost', function(ev) {
- alert("WebGL context lost, please reload the page");
- ev.preventDefault();
- }, false);
-
- if (locale) {
- this.rtenv.locale = locale;
- } else {
- this.rtenv.locale = navigator.languages ? navigator.languages[0] : navigator.language;
- }
- this.rtenv.locale = this.rtenv.locale.split('.')[0];
- this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart;
-
- preloadedFiles.forEach(function(file) {
- var dir = LIBS.PATH.dirname(file.path);
- try {
- LIBS.FS.stat(dir);
- } catch (e) {
- if (e.code !== 'ENOENT') {
- throw e;
- }
- LIBS.FS.mkdirTree(dir);
- }
- // With memory growth, canOwn should be false.
- LIBS.FS.createDataFile(file.path, null, new Uint8Array(file.buffer), true, true, false);
- }, this);
-
- preloadedFiles = null;
- initPromise = null;
- this.rtenv.callMain(arguments);
- }
-
- this.setProgressFunc = function(func) {
- progressFunc = func;
- };
-
- this.setResizeCanvasOnStart = function(enabled) {
- resizeCanvasOnStart = enabled;
- };
-
- function animateProgress() {
-
- var loaded = 0;
- var total = 0;
- var totalIsValid = true;
- var progressIsFinal = true;
-
- [loadingFiles, preloadProgressTracker].forEach(function(tracker) {
- Object.keys(tracker).forEach(function(file) {
- if (!tracker[file].final)
- progressIsFinal = false;
- if (!totalIsValid || tracker[file].total === 0) {
- totalIsValid = false;
- total = 0;
- } else {
- total += tracker[file].total;
- }
- loaded += tracker[file].loaded;
- });
- });
- if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
- lastProgress.loaded = loaded;
- lastProgress.total = total;
- if (typeof progressFunc === 'function')
- progressFunc(loaded, total);
- }
- if (!progressIsFinal)
- requestAnimationFrame(animateProgress);
- }
-
- this.setCanvas = function(elem) {
- canvas = elem;
- };
-
- this.setExecutableName = function(newName) {
-
- executableName = newName;
- };
-
- this.setLocale = function(newLocale) {
-
- locale = newLocale;
- };
-
- this.setUnloadAfterInit = function(enabled) {
-
- if (enabled && !unloadAfterInit && initPromise) {
- initPromise.then(Engine.unloadEngine);
- }
- unloadAfterInit = enabled;
- };
-
- this.setStdoutFunc = function(func) {
-
- var print = function(text) {
- if (arguments.length > 1) {
- text = Array.prototype.slice.call(arguments).join(" ");
- }
- func(text);
- };
- if (this.rtenv)
- this.rtenv.print = print;
- stdout = print;
- };
-
- this.setStderrFunc = function(func) {
-
- var printErr = function(text) {
- if (arguments.length > 1)
- text = Array.prototype.slice.call(arguments).join(" ");
- func(text);
- };
- if (this.rtenv)
- this.rtenv.printErr = printErr;
- stderr = printErr;
- };
-
-
- }; // Engine()
-
- Engine.RuntimeEnvironment = engine.RuntimeEnvironment;
-
- Engine.isWebGLAvailable = function(majorVersion = 1) {
-
- var testContext = false;
- try {
- var testCanvas = document.createElement('canvas');
- if (majorVersion === 1) {
- testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
- } else if (majorVersion === 2) {
- testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
- }
- } catch (e) {}
- return !!testContext;
- };
-
- Engine.setWebAssemblyFilenameExtension = function(override) {
-
- if (String(override).length === 0) {
- throw new Error('Invalid WebAssembly filename extension override');
- }
- wasmFilenameExtensionOverride = String(override);
- }
-
- Engine.load = function(newBasePath) {
-
- if (newBasePath !== undefined) basePath = getBasePath(newBasePath);
- if (engineLoadPromise === null) {
- if (typeof WebAssembly !== 'object')
- return Promise.reject(new Error("Browser doesn't support WebAssembly"));
- // TODO cache/retrieve module to/from idb
- engineLoadPromise = loadPromise(basePath + '.' + (wasmFilenameExtensionOverride || 'wasm')).then(function(xhr) {
- return xhr.response;
- });
- engineLoadPromise = engineLoadPromise.catch(function(err) {
- engineLoadPromise = null;
- throw err;
- });
- }
- return engineLoadPromise;
- };
-
- Engine.unload = function() {
- engineLoadPromise = null;
- };
-
- function loadPromise(file, tracker) {
- if (tracker === undefined)
- tracker = loadingFiles;
- return new Promise(function(resolve, reject) {
- loadXHR(resolve, reject, file, tracker);
- });
- }
-
- function loadXHR(resolve, reject, file, tracker) {
-
- var xhr = new XMLHttpRequest;
- xhr.open('GET', file);
- if (!file.endsWith('.js')) {
- xhr.responseType = 'arraybuffer';
- }
- ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
- xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
- });
- xhr.send();
- }
-
- function onXHREvent(resolve, reject, file, tracker, ev) {
-
- if (this.status >= 400) {
-
- if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
- reject(new Error("Failed loading file '" + file + "': " + this.statusText));
- this.abort();
- return;
- } else {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- }
-
- switch (ev.type) {
- case 'loadstart':
- if (tracker[file] === undefined) {
- tracker[file] = {
- total: ev.total,
- loaded: ev.loaded,
- attempts: 0,
- final: false,
- };
- }
- break;
-
- case 'progress':
- tracker[file].loaded = ev.loaded;
- tracker[file].total = ev.total;
- break;
-
- case 'load':
- tracker[file].final = true;
- resolve(this);
- break;
-
- case 'error':
- if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
- tracker[file].final = true;
- reject(new Error("Failed loading file '" + file + "'"));
- } else {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- break;
-
- case 'abort':
- tracker[file].final = true;
- reject(new Error("Loading file '" + file + "' was aborted."));
- break;
- }
- }
-})();
diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js
new file mode 100644
index 0000000000..adcd919a6b
--- /dev/null
+++ b/platform/javascript/engine/engine.js
@@ -0,0 +1,253 @@
+Function('return this')()['Engine'] = (function() {
+ var preloader = new Preloader();
+
+ var wasmExt = '.wasm';
+ var unloadAfterInit = true;
+ var loadPath = '';
+ var loadPromise = null;
+ var initPromise = null;
+ var stderr = null;
+ var stdout = null;
+ var progressFunc = null;
+
+ function load(basePath) {
+ if (loadPromise == null) {
+ loadPath = basePath;
+ loadPromise = preloader.loadPromise(basePath + wasmExt);
+ preloader.setProgressFunc(progressFunc);
+ requestAnimationFrame(preloader.animateProgress);
+ }
+ return loadPromise;
+ };
+
+ function unload() {
+ loadPromise = null;
+ };
+
+ /** @constructor */
+ function Engine() {
+ this.canvas = null;
+ this.executableName = '';
+ this.rtenv = null;
+ this.customLocale = null;
+ this.resizeCanvasOnStart = false;
+ this.onExecute = null;
+ this.onExit = null;
+ this.persistentPaths = [];
+ };
+
+ Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
+ if (initPromise) {
+ return initPromise;
+ }
+ if (loadPromise == null) {
+ if (!basePath) {
+ initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded."));
+ return initPromise;
+ }
+ load(basePath);
+ }
+ var config = {};
+ if (typeof stdout === 'function')
+ config.print = stdout;
+ if (typeof stderr === 'function')
+ config.printErr = stderr;
+ var me = this;
+ initPromise = new Promise(function(resolve, reject) {
+ config['locateFile'] = Utils.createLocateRewrite(loadPath);
+ config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
+ Godot(config).then(function(module) {
+ module['initFS'](me.persistentPaths).then(function(fs_err) {
+ me.rtenv = module;
+ if (unloadAfterInit) {
+ unload();
+ }
+ resolve();
+ config = null;
+ });
+ });
+ });
+ return initPromise;
+ };
+
+ /** @type {function(string, string):Object} */
+ Engine.prototype.preloadFile = function(file, path) {
+ return preloader.preload(file, path);
+ };
+
+ /** @type {function(...string):Object} */
+ Engine.prototype.start = function() {
+ // Start from arguments.
+ var args = [];
+ for (var i = 0; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ var me = this;
+ return me.init().then(function() {
+ if (!me.rtenv) {
+ return Promise.reject(new Error('The engine must be initialized before it can be started'));
+ }
+
+ if (!(me.canvas instanceof HTMLCanvasElement)) {
+ me.canvas = Utils.findCanvas();
+ }
+
+ // Canvas can grab focus on click, or key events won't work.
+ if (me.canvas.tabIndex < 0) {
+ me.canvas.tabIndex = 0;
+ }
+
+ // Disable right-click context menu.
+ me.canvas.addEventListener('contextmenu', function(ev) {
+ ev.preventDefault();
+ }, false);
+
+ // Until context restoration is implemented warn the user of context loss.
+ me.canvas.addEventListener('webglcontextlost', function(ev) {
+ alert("WebGL context lost, please reload the page");
+ ev.preventDefault();
+ }, false);
+
+ // Browser locale, or custom one if defined.
+ var locale = me.customLocale;
+ if (!locale) {
+ locale = navigator.languages ? navigator.languages[0] : navigator.language;
+ locale = locale.split('.')[0];
+ }
+ me.rtenv['locale'] = locale;
+ me.rtenv['canvas'] = me.canvas;
+ me.rtenv['thisProgram'] = me.executableName;
+ me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart;
+ me.rtenv['noExitRuntime'] = true;
+ me.rtenv['onExecute'] = me.onExecute;
+ me.rtenv['onExit'] = function(code) {
+ if (me.onExit)
+ me.onExit(code);
+ me.rtenv = null;
+ };
+ return new Promise(function(resolve, reject) {
+ preloader.preloadedFiles.forEach(function(file) {
+ me.rtenv['copyToFS'](file.path, file.buffer);
+ });
+ preloader.preloadedFiles.length = 0; // Clear memory
+ me.rtenv['callMain'](args);
+ initPromise = null;
+ resolve();
+ });
+ });
+ };
+
+ Engine.prototype.startGame = function(execName, mainPack, extraArgs) {
+ // Start and init with execName as loadPath if not inited.
+ this.executableName = execName;
+ var me = this;
+ return Promise.all([
+ this.init(execName),
+ this.preloadFile(mainPack, mainPack)
+ ]).then(function() {
+ var args = ['--main-pack', mainPack];
+ if (extraArgs)
+ args = args.concat(extraArgs);
+ return me.start.apply(me, args);
+ });
+ };
+
+ Engine.prototype.setWebAssemblyFilenameExtension = function(override) {
+ if (String(override).length === 0) {
+ throw new Error('Invalid WebAssembly filename extension override');
+ }
+ wasmExt = String(override);
+ };
+
+ Engine.prototype.setUnloadAfterInit = function(enabled) {
+ unloadAfterInit = enabled;
+ };
+
+ Engine.prototype.setCanvas = function(canvasElem) {
+ this.canvas = canvasElem;
+ };
+
+ Engine.prototype.setCanvasResizedOnStart = function(enabled) {
+ this.resizeCanvasOnStart = enabled;
+ };
+
+ Engine.prototype.setLocale = function(locale) {
+ this.customLocale = locale;
+ };
+
+ Engine.prototype.setExecutableName = function(newName) {
+ this.executableName = newName;
+ };
+
+ Engine.prototype.setProgressFunc = function(func) {
+ progressFunc = func;
+ };
+
+ Engine.prototype.setStdoutFunc = function(func) {
+ var print = function(text) {
+ if (arguments.length > 1) {
+ text = Array.prototype.slice.call(arguments).join(" ");
+ }
+ func(text);
+ };
+ if (this.rtenv)
+ this.rtenv.print = print;
+ stdout = print;
+ };
+
+ Engine.prototype.setStderrFunc = function(func) {
+ var printErr = function(text) {
+ if (arguments.length > 1)
+ text = Array.prototype.slice.call(arguments).join(" ");
+ func(text);
+ };
+ if (this.rtenv)
+ this.rtenv.printErr = printErr;
+ stderr = printErr;
+ };
+
+ Engine.prototype.setOnExecute = function(onExecute) {
+ if (this.rtenv)
+ this.rtenv.onExecute = onExecute;
+ this.onExecute = onExecute;
+ };
+
+ Engine.prototype.setOnExit = function(onExit) {
+ this.onExit = onExit;
+ };
+
+ Engine.prototype.copyToFS = function(path, buffer) {
+ if (this.rtenv == null) {
+ throw new Error("Engine must be inited before copying files");
+ }
+ this.rtenv['copyToFS'](path, buffer);
+ };
+
+ Engine.prototype.setPersistentPaths = function(persistentPaths) {
+ this.persistentPaths = persistentPaths;
+ };
+
+ // Closure compiler exported engine methods.
+ /** @export */
+ Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
+ Engine['load'] = load;
+ Engine['unload'] = unload;
+ Engine.prototype['init'] = Engine.prototype.init;
+ Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
+ Engine.prototype['start'] = Engine.prototype.start;
+ Engine.prototype['startGame'] = Engine.prototype.startGame;
+ Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension;
+ Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit;
+ Engine.prototype['setCanvas'] = Engine.prototype.setCanvas;
+ Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart;
+ Engine.prototype['setLocale'] = Engine.prototype.setLocale;
+ Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName;
+ Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc;
+ Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc;
+ Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc;
+ Engine.prototype['setOnExecute'] = Engine.prototype.setOnExecute;
+ Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
+ Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
+ Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
+ return Engine;
+})();
diff --git a/platform/javascript/engine/externs.js b/platform/javascript/engine/externs.js
new file mode 100644
index 0000000000..1a94dd15ec
--- /dev/null
+++ b/platform/javascript/engine/externs.js
@@ -0,0 +1,3 @@
+var Godot;
+var WebAssembly = {};
+WebAssembly.instantiate = function(buffer, imports) {};
diff --git a/platform/javascript/engine/preloader.js b/platform/javascript/engine/preloader.js
new file mode 100644
index 0000000000..17918eae38
--- /dev/null
+++ b/platform/javascript/engine/preloader.js
@@ -0,0 +1,139 @@
+var Preloader = /** @constructor */ function() {
+
+ var DOWNLOAD_ATTEMPTS_MAX = 4;
+ var progressFunc = null;
+ var lastProgress = { loaded: 0, total: 0 };
+
+ var loadingFiles = {};
+ this.preloadedFiles = [];
+
+ function loadXHR(resolve, reject, file, tracker) {
+ var xhr = new XMLHttpRequest;
+ xhr.open('GET', file);
+ if (!file.endsWith('.js')) {
+ xhr.responseType = 'arraybuffer';
+ }
+ ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
+ xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
+ });
+ xhr.send();
+ }
+
+ function onXHREvent(resolve, reject, file, tracker, ev) {
+
+ if (this.status >= 400) {
+
+ if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ reject(new Error("Failed loading file '" + file + "': " + this.statusText));
+ this.abort();
+ return;
+ } else {
+ setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
+ }
+ }
+
+ switch (ev.type) {
+ case 'loadstart':
+ if (tracker[file] === undefined) {
+ tracker[file] = {
+ total: ev.total,
+ loaded: ev.loaded,
+ attempts: 0,
+ final: false,
+ };
+ }
+ break;
+
+ case 'progress':
+ tracker[file].loaded = ev.loaded;
+ tracker[file].total = ev.total;
+ break;
+
+ case 'load':
+ tracker[file].final = true;
+ resolve(this);
+ break;
+
+ case 'error':
+ if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ tracker[file].final = true;
+ reject(new Error("Failed loading file '" + file + "'"));
+ } else {
+ setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
+ }
+ break;
+
+ case 'abort':
+ tracker[file].final = true;
+ reject(new Error("Loading file '" + file + "' was aborted."));
+ break;
+ }
+ }
+
+ this.loadPromise = function(file) {
+ return new Promise(function(resolve, reject) {
+ loadXHR(resolve, reject, file, loadingFiles);
+ });
+ }
+
+ this.preload = function(pathOrBuffer, destPath) {
+ if (pathOrBuffer instanceof ArrayBuffer) {
+ pathOrBuffer = new Uint8Array(pathOrBuffer);
+ } else if (ArrayBuffer.isView(pathOrBuffer)) {
+ pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
+ }
+ if (pathOrBuffer instanceof Uint8Array) {
+ this.preloadedFiles.push({
+ path: destPath,
+ buffer: pathOrBuffer
+ });
+ return Promise.resolve();
+ } else if (typeof pathOrBuffer === 'string') {
+ var me = this;
+ return this.loadPromise(pathOrBuffer).then(function(xhr) {
+ me.preloadedFiles.push({
+ path: destPath || pathOrBuffer,
+ buffer: xhr.response
+ });
+ return Promise.resolve();
+ });
+ } else {
+ throw Promise.reject("Invalid object for preloading");
+ }
+ };
+
+ var animateProgress = function() {
+
+ var loaded = 0;
+ var total = 0;
+ var totalIsValid = true;
+ var progressIsFinal = true;
+
+ Object.keys(loadingFiles).forEach(function(file) {
+ const stat = loadingFiles[file];
+ if (!stat.final) {
+ progressIsFinal = false;
+ }
+ if (!totalIsValid || stat.total === 0) {
+ totalIsValid = false;
+ total = 0;
+ } else {
+ total += stat.total;
+ }
+ loaded += stat.loaded;
+ });
+ if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
+ lastProgress.loaded = loaded;
+ lastProgress.total = total;
+ if (typeof progressFunc === 'function')
+ progressFunc(loaded, total);
+ }
+ if (!progressIsFinal)
+ requestAnimationFrame(animateProgress);
+ }
+ this.animateProgress = animateProgress; // Also exposed to start it.
+
+ this.setProgressFunc = function(callback) {
+ progressFunc = callback;
+ }
+};
diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js
new file mode 100644
index 0000000000..0c97b38199
--- /dev/null
+++ b/platform/javascript/engine/utils.js
@@ -0,0 +1,51 @@
+var Utils = {
+
+ createLocateRewrite: function(execName) {
+ function rw(path) {
+ if (path.endsWith('.worker.js')) {
+ return execName + '.worker.js';
+ } else if (path.endsWith('.js')) {
+ return execName + '.js';
+ } else if (path.endsWith('.wasm')) {
+ return execName + '.wasm';
+ }
+ }
+ return rw;
+ },
+
+ createInstantiatePromise: function(wasmLoader) {
+ function instantiateWasm(imports, onSuccess) {
+ wasmLoader.then(function(xhr) {
+ WebAssembly.instantiate(xhr.response, imports).then(function(result) {
+ onSuccess(result['instance'], result['module']);
+ });
+ });
+ wasmLoader = null;
+ return {};
+ };
+
+ return instantiateWasm;
+ },
+
+ findCanvas: function() {
+ var nodes = document.getElementsByTagName('canvas');
+ if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
+ return nodes[0];
+ }
+ throw new Error("No canvas found");
+ },
+
+ isWebGLAvailable: function(majorVersion = 1) {
+
+ var testContext = false;
+ try {
+ var testCanvas = document.createElement('canvas');
+ if (majorVersion === 1) {
+ testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
+ } else if (majorVersion === 2) {
+ testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
+ }
+ } catch (e) {}
+ return !!testContext;
+ }
+};
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 9b93d4f140..230575abce 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -28,6 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#include "core/io/json.h"
#include "core/io/tcp_server.h"
#include "core/io/zip_io.h"
#include "editor/editor_export.h"
@@ -40,7 +41,6 @@
#define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip"
class EditorHTTPServer : public Reference {
-
private:
Ref<TCP_Server> server;
Ref<StreamPeerTCP> connection;
@@ -86,7 +86,7 @@ public:
ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export");
- String basereq = "/tmp_js_export";
+ const String basereq = "/tmp_js_export";
String ctype = "";
if (req[1] == basereq + ".html") {
filepath += ".html";
@@ -94,11 +94,19 @@ public:
} else if (req[1] == basereq + ".js") {
filepath += ".js";
ctype = "application/javascript";
+ } else if (req[1] == basereq + ".worker.js") {
+ filepath += ".worker.js";
+ ctype = "application/javascript";
} else if (req[1] == basereq + ".pck") {
filepath += ".pck";
ctype = "application/octet-stream";
- } else if (req[1] == basereq + ".png") {
- filepath += ".png";
+ } else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") {
+ // Also allow serving the generated favicon for a smoother loading experience.
+ if (req[1] == "/favicon.png") {
+ filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png");
+ } else {
+ filepath += ".png";
+ }
ctype = "image/png";
} else if (req[1] == basereq + ".wasm") {
filepath += ".wasm";
@@ -140,11 +148,13 @@ public:
}
void poll() {
- if (!server->is_listening())
+ if (!server->is_listening()) {
return;
+ }
if (connection.is_null()) {
- if (!server->is_connection_available())
+ if (!server->is_connection_available()) {
return;
+ }
connection = server->take_connection();
time = OS::get_singleton()->get_ticks_usec();
}
@@ -152,11 +162,11 @@ public:
_clear_client();
return;
}
- if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED)
+ if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
return;
+ }
while (true) {
-
char *r = (char *)req_buf;
int l = req_pos - 1;
if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
@@ -182,7 +192,6 @@ public:
};
class EditorExportPlatformJavaScript : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform);
Ref<ImageTexture> logo;
@@ -190,62 +199,68 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
Ref<ImageTexture> stop_icon;
int menu_options;
- void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug);
+ void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags);
private:
Ref<EditorHTTPServer> server;
bool server_quit;
- Mutex *server_lock;
+ Mutex server_lock;
Thread *server_thread;
static void _server_thread_poll(void *data);
public:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
-
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
- virtual String get_name() const;
- virtual String get_os_name() const;
- virtual Ref<Texture> get_logo() const;
+ virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual String get_name() const override;
+ virtual String get_os_name() const override;
+ virtual Ref<Texture2D> get_logo() const override;
- virtual bool poll_export();
- virtual int get_options_count() const;
- virtual String get_option_label(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); }
- virtual String get_option_tooltip(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); }
- virtual Ref<ImageTexture> get_option_icon(int p_index) const;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags);
- virtual Ref<Texture> get_run_icon() const;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual void get_platform_features(List<String> *r_features) {
+ virtual bool poll_export() override;
+ virtual int get_options_count() const override;
+ virtual String get_option_label(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); }
+ virtual String get_option_tooltip(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); }
+ virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
+ virtual Ref<Texture2D> get_run_icon() const override;
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("web");
r_features->push_back(get_os_name());
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
+ String get_debug_protocol() const override { return "ws://"; }
+
EditorExportPlatformJavaScript();
~EditorExportPlatformJavaScript();
};
-void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug) {
-
+void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) {
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
String str_export;
Vector<String> lines = str_template.split("\n");
+ Vector<String> flags;
+ String flags_json;
+ gen_export_flags(flags, p_flags);
+ flags_json = JSON::print(flags);
for (int i = 0; i < lines.size(); i++) {
-
String current_line = lines[i];
current_line = current_line.replace("$GODOT_BASENAME", p_name);
+ current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name"));
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
+ current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false");
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
+ current_line = current_line.replace("$GODOT_ARGS", flags_json);
str_export += current_line + "\n";
}
@@ -257,7 +272,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
}
void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
-
if (p_preset->get("vram_texture_compression/for_desktop")) {
r_features->push_back("s3tc");
}
@@ -266,42 +280,36 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP
String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
if (driver == "GLES2") {
r_features->push_back("etc");
- } else if (driver == "GLES3") {
+ } else if (driver == "Vulkan") {
+ // FIXME: Review if this is correct.
r_features->push_back("etc2");
- if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) {
- r_features->push_back("etc");
- }
}
}
}
void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) {
-
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
}
String EditorExportPlatformJavaScript::get_name() const {
-
return "HTML5";
}
String EditorExportPlatformJavaScript::get_os_name() const {
-
return "HTML5";
}
-Ref<Texture> EditorExportPlatformJavaScript::get_logo() const {
-
+Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
return logo;
}
bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
-
String err;
bool valid = false;
@@ -336,14 +344,14 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p
}
}
- if (!err.empty())
+ if (!err.empty()) {
r_error = err;
+ }
return valid;
}
List<String> EditorExportPlatformJavaScript::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
-
List<String> list;
list.push_back("html");
return list;
@@ -361,11 +369,11 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
template_path = template_path.strip_edges();
if (template_path == String()) {
-
- if (p_debug)
+ if (p_debug) {
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG);
- else
+ } else {
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE);
+ }
}
if (!DirAccess::exists(p_path.get_base_dir())) {
@@ -384,12 +392,11 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
return error;
}
- FileAccess *src_f = NULL;
+ FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io);
if (!pkg) {
-
EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + template_path);
return ERR_FILE_NOT_FOUND;
}
@@ -404,7 +411,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
//get filename
unz_file_info info;
char fname[16384];
- unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
+ unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
String file = fname;
@@ -419,18 +426,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
//write
if (file == "godot.html") {
-
if (!custom_html.empty()) {
continue;
}
- _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug);
+ _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags);
file = p_path.get_file();
} else if (file == "godot.js") {
-
file = p_path.get_file().get_basename() + ".js";
- } else if (file == "godot.wasm") {
+ } else if (file == "godot.worker.js") {
+ file = p_path.get_file().get_basename() + ".worker.js";
+ } else if (file == "godot.wasm") {
file = p_path.get_file().get_basename() + ".wasm";
}
@@ -448,7 +455,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
unzClose(pkg);
if (!custom_html.empty()) {
-
FileAccess *f = FileAccess::open(custom_html, FileAccess::READ);
if (!f) {
EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html);
@@ -458,7 +464,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
buf.resize(f->get_len());
f->get_buffer(buf.ptrw(), buf.size());
memdelete(f);
- _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug);
+ _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags);
f = FileAccess::open(p_path, FileAccess::WRITE);
if (!f) {
@@ -470,11 +476,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
}
Ref<Image> splash;
- String splash_path = GLOBAL_GET("application/boot_splash/image");
- splash_path = splash_path.strip_edges();
+ const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges();
if (!splash_path.empty()) {
splash.instance();
- Error err = splash->load(splash_path);
+ const Error err = splash->load(splash_path);
if (err) {
EditorNode::get_singleton()->show_warning(TTR("Could not read boot splash image file:") + "\n" + splash_path + "\n" + TTR("Using default boot splash image."));
splash.unref();
@@ -483,20 +488,39 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
if (splash.is_null()) {
splash = Ref<Image>(memnew(Image(boot_splash_png)));
}
- String png_path = p_path.get_base_dir().plus_file(p_path.get_file().get_basename() + ".png");
- if (splash->save_png(png_path) != OK) {
- EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + png_path);
+ const String splash_png_path = p_path.get_base_dir().plus_file(p_path.get_file().get_basename() + ".png");
+ if (splash->save_png(splash_png_path) != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + splash_png_path);
return ERR_FILE_CANT_WRITE;
}
+
+ // Save a favicon that can be accessed without waiting for the project to finish loading.
+ // This way, the favicon can be displayed immediately when loading the page.
+ Ref<Image> favicon;
+ const String favicon_path = String(GLOBAL_GET("application/config/icon")).strip_edges();
+ if (!favicon_path.empty()) {
+ favicon.instance();
+ const Error err = favicon->load(favicon_path);
+ if (err) {
+ favicon.unref();
+ }
+ }
+
+ if (favicon.is_valid()) {
+ const String favicon_png_path = p_path.get_base_dir().plus_file("favicon.png");
+ if (favicon->save_png(favicon_png_path) != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + favicon_png_path);
+ return ERR_FILE_CANT_WRITE;
+ }
+ }
+
return OK;
}
bool EditorExportPlatformJavaScript::poll_export() {
-
Ref<EditorExportPreset> preset;
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
-
Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
if (ep->is_runnable() && ep->get_platform() == this) {
preset = ep;
@@ -508,9 +532,8 @@ bool EditorExportPlatformJavaScript::poll_export() {
menu_options = preset.is_valid();
if (server->is_listening()) {
if (menu_options == 0) {
- server_lock->lock();
+ MutexLock lock(server_lock);
server->stop();
- server_lock->unlock();
} else {
menu_options += 1;
}
@@ -523,36 +546,34 @@ Ref<ImageTexture> EditorExportPlatformJavaScript::get_option_icon(int p_index) c
}
int EditorExportPlatformJavaScript::get_options_count() const {
-
return menu_options;
}
Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
-
if (p_option == 1) {
- server_lock->lock();
+ MutexLock lock(server_lock);
server->stop();
- server_lock->unlock();
return OK;
}
- String basepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export");
- String path = basepath + ".html";
- Error err = export_project(p_preset, true, path, p_debug_flags);
+ const String basepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export");
+ Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags);
if (err != OK) {
// Export generates several files, clean them up on failure.
DirAccess::remove_file_or_error(basepath + ".html");
DirAccess::remove_file_or_error(basepath + ".js");
+ DirAccess::remove_file_or_error(basepath + ".worker.js");
DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png");
DirAccess::remove_file_or_error(basepath + ".wasm");
+ DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"));
return err;
}
- IP_Address bind_ip;
- uint16_t bind_port = EDITOR_GET("export/web/http_port");
+ const uint16_t bind_port = EDITOR_GET("export/web/http_port");
// Resolve host if needed.
- String bind_host = EDITOR_GET("export/web/http_host");
+ const String bind_host = EDITOR_GET("export/web/http_host");
+ IP_Address bind_ip;
if (bind_host.is_valid_ip_address()) {
bind_ip = bind_host;
} else {
@@ -561,10 +582,12 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'.");
// Restart server.
- server_lock->lock();
- server->stop();
- err = server->listen(bind_port, bind_ip);
- server_lock->unlock();
+ {
+ MutexLock lock(server_lock);
+
+ server->stop();
+ err = server->listen(bind_port, bind_ip);
+ }
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to start HTTP server.");
OS::get_singleton()->shell_open(String("http://" + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html"));
@@ -573,8 +596,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
return OK;
}
-Ref<Texture> EditorExportPlatformJavaScript::get_run_icon() const {
-
+Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const {
return run_icon;
}
@@ -582,17 +604,16 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) {
EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data;
while (!ej->server_quit) {
OS::get_singleton()->delay_usec(1000);
- ej->server_lock->lock();
- ej->server->poll();
- ej->server_lock->unlock();
+ {
+ MutexLock lock(ej->server_lock);
+ ej->server->poll();
+ }
}
}
EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
-
server.instance();
server_quit = false;
- server_lock = Mutex::create();
server_thread = Thread::create(_server_thread_poll, this);
Ref<Image> img = memnew(Image(_javascript_logo));
@@ -604,10 +625,11 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
run_icon->create_from_image(img);
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
- if (theme.is_valid())
+ if (theme.is_valid()) {
stop_icon = theme->get_icon("Stop", "EditorIcons");
- else
+ } else {
stop_icon.instance();
+ }
menu_options = 0;
}
@@ -616,12 +638,10 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() {
server->stop();
server_quit = true;
Thread::wait_to_finish(server_thread);
- memdelete(server_lock);
memdelete(server_thread);
}
void register_javascript_exporter() {
-
EDITOR_DEF("export/web/http_host", "localhost");
EDITOR_DEF("export/web/http_port", 8060);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1"));
diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc
index 03e2ce8b8a..4d5ff88bdd 100644
--- a/platform/javascript/http_client.h.inc
+++ b/platform/javascript/http_client.h.inc
@@ -33,21 +33,21 @@
Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers);
int xhr_id;
-int read_limit;
-int response_read_offset;
-Status status;
+int read_limit = 4096;
+int response_read_offset = 0;
+Status status = STATUS_DISCONNECTED;
String host;
-int port;
-bool use_tls;
+int port = -1;
+bool use_tls = false;
String username;
String password;
-int polled_response_code;
+int polled_response_code = 0;
String polled_response_header;
-PoolByteArray polled_response;
+PackedByteArray polled_response;
#ifdef DEBUG_ENABLED
-bool has_polled;
-uint64_t last_polling_frame;
+bool has_polled = false;
+uint64_t last_polling_frame = 0;
#endif
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index 2c2511a3a5..cb0e48b8a9 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -29,10 +29,10 @@
/*************************************************************************/
#include "core/io/http_client.h"
+
#include "http_request.h"
Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
-
close();
if (p_ssl && !p_verify_host) {
WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified");
@@ -67,17 +67,14 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl,
}
void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) {
-
ERR_FAIL_MSG("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
}
Ref<StreamPeer> HTTPClient::get_connection() const {
-
ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
}
Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) {
-
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform.");
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
@@ -88,8 +85,8 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
godot_xhr_reset(xhr_id);
godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(),
- username.empty() ? NULL : username.utf8().get_data(),
- password.empty() ? NULL : password.utf8().get_data());
+ username.empty() ? nullptr : username.utf8().get_data(),
+ password.empty() ? nullptr : password.utf8().get_data());
for (int i = 0; i < p_headers.size(); i++) {
int header_separator = p_headers[i].find(": ");
@@ -103,18 +100,15 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve
return OK;
}
-Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const PoolVector<uint8_t> &p_body) {
-
+Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
- PoolByteArray::Read read = p_body.read();
- godot_xhr_send_data(xhr_id, read.ptr(), p_body.size());
+ godot_xhr_send_data(xhr_id, p_body.ptr(), p_body.size());
return OK;
}
Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
-
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
@@ -123,7 +117,6 @@ Error HTTPClient::request(Method p_method, const String &p_url, const Vector<Str
}
void HTTPClient::close() {
-
host = "";
port = -1;
use_tls = false;
@@ -135,28 +128,23 @@ void HTTPClient::close() {
}
HTTPClient::Status HTTPClient::get_status() const {
-
return status;
}
bool HTTPClient::has_response() const {
-
return !polled_response_header.empty();
}
bool HTTPClient::is_response_chunked() const {
-
// TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream
return false;
}
int HTTPClient::get_response_code() const {
-
return polled_response_code;
}
Error HTTPClient::get_response_headers(List<String> *r_response) {
-
if (polled_response_header.empty())
return ERR_INVALID_PARAMETER;
@@ -169,22 +157,16 @@ Error HTTPClient::get_response_headers(List<String> *r_response) {
}
int HTTPClient::get_response_body_length() const {
-
return polled_response.size();
}
-PoolByteArray HTTPClient::read_response_body_chunk() {
-
- ERR_FAIL_COND_V(status != STATUS_BODY, PoolByteArray());
+PackedByteArray HTTPClient::read_response_body_chunk() {
+ ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
int to_read = MIN(read_limit, polled_response.size() - response_read_offset);
- PoolByteArray chunk;
+ PackedByteArray chunk;
chunk.resize(to_read);
- PoolByteArray::Write write = chunk.write();
- PoolByteArray::Read read = polled_response.read();
- memcpy(write.ptr(), read.ptr() + response_read_offset, to_read);
- write = PoolByteArray::Write();
- read = PoolByteArray::Read();
+ memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read);
response_read_offset += to_read;
if (response_read_offset == polled_response.size()) {
@@ -197,17 +179,14 @@ PoolByteArray HTTPClient::read_response_body_chunk() {
}
void HTTPClient::set_blocking_mode(bool p_enable) {
-
ERR_FAIL_COND_MSG(p_enable, "HTTPClient blocking mode is not supported for the HTML5 platform.");
}
bool HTTPClient::is_blocking_mode_enabled() const {
-
return false;
}
void HTTPClient::set_read_chunk_size(int p_size) {
-
read_limit = p_size;
}
@@ -216,9 +195,7 @@ int HTTPClient::get_read_chunk_size() const {
}
Error HTTPClient::poll() {
-
switch (status) {
-
case STATUS_DISCONNECTED:
return ERR_UNCONFIGURED;
@@ -238,7 +215,6 @@ Error HTTPClient::poll() {
return ERR_CONNECTION_ERROR;
case STATUS_REQUESTING: {
-
#ifdef DEBUG_ENABLED
if (!has_polled) {
has_polled = true;
@@ -263,23 +239,17 @@ Error HTTPClient::poll() {
status = STATUS_BODY;
- PoolByteArray bytes;
+ PackedByteArray bytes;
int len = godot_xhr_get_response_headers_length(xhr_id);
bytes.resize(len + 1);
- PoolByteArray::Write write = bytes.write();
- godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(write.ptr()), len);
- write[len] = 0;
- write = PoolByteArray::Write();
+ godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(bytes.ptrw()), len);
+ bytes.ptrw()[len] = 0;
- PoolByteArray::Read read = bytes.read();
- polled_response_header = String::utf8(reinterpret_cast<const char *>(read.ptr()));
- read = PoolByteArray::Read();
+ polled_response_header = String::utf8(reinterpret_cast<const char *>(bytes.ptr()));
polled_response.resize(godot_xhr_get_response_length(xhr_id));
- write = polled_response.write();
- godot_xhr_get_response(xhr_id, write.ptr(), polled_response.size());
- write = PoolByteArray::Write();
+ godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size());
break;
}
@@ -290,20 +260,9 @@ Error HTTPClient::poll() {
}
HTTPClient::HTTPClient() {
-
xhr_id = godot_xhr_new();
- read_limit = 4096;
- status = STATUS_DISCONNECTED;
- port = -1;
- use_tls = false;
- polled_response_code = 0;
-#ifdef DEBUG_ENABLED
- has_polled = false;
- last_polling_frame = 0;
-#endif
}
HTTPClient::~HTTPClient() {
-
godot_xhr_free(xhr_id);
}
diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h
index 57dc4f0d9f..54e98c1927 100644
--- a/platform/javascript/http_request.h
+++ b/platform/javascript/http_request.h
@@ -49,7 +49,7 @@ extern int godot_xhr_new();
extern void godot_xhr_reset(int p_xhr_id);
extern bool godot_xhr_free(int p_xhr_id);
-extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = NULL, const char *p_password = NULL);
+extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr);
extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value);
diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp
index d907222d24..3a72b10dd4 100644
--- a/platform/javascript/javascript_eval.cpp
+++ b/platform/javascript/javascript_eval.cpp
@@ -33,23 +33,21 @@
#include "api/javascript_eval.h"
#include "emscripten.h"
-extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_poolbytearray_and_open_write(PoolByteArray *p_arr, PoolByteArray::Write *r_write, int p_len) {
-
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, VectorWriteProxy<uint8_t> *r_write, int p_len) {
p_arr->resize(p_len);
- *r_write = p_arr->write();
- return r_write->ptr();
+ *r_write = p_arr->write;
+ return p_arr->ptrw();
}
Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
-
union {
bool b;
double d;
char *s;
} js_data;
- PoolByteArray arr;
- PoolByteArray::Write arr_write;
+ PackedByteArray arr;
+ VectorWriteProxy<uint8_t> arr_write;
/* clang-format off */
Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({
@@ -81,7 +79,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
case 'number':
setValue(PTR, eval_ret, 'double');
- return 3; // REAL
+ return 3; // FLOAT
case 'string':
var array_len = lengthBytesUTF8(eval_ret)+1;
@@ -114,9 +112,9 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
eval_ret = new Uint8Array(eval_ret);
}
if (eval_ret instanceof Uint8Array) {
- var bytes_ptr = ccall('resize_poolbytearray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]);
+ var bytes_ptr = ccall('resize_PackedByteArray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]);
HEAPU8.set(eval_ret, bytes_ptr);
- return 20; // POOL_BYTE_ARRAY
+ return 20; // PACKED_BYTE_ARRAY
}
break;
}
@@ -128,7 +126,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
switch (return_type) {
case Variant::BOOL:
return js_data.b;
- case Variant::REAL:
+ case Variant::FLOAT:
return js_data.d;
case Variant::STRING: {
String str = String::utf8(js_data.s);
@@ -137,8 +135,8 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
/* clang-format on */
return str;
}
- case Variant::POOL_BYTE_ARRAY:
- arr_write = PoolByteArray::Write();
+ case Variant::PACKED_BYTE_ARRAY:
+ arr_write = VectorWriteProxy<uint8_t>();
return arr;
default:
return Variant();
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 815bc7e456..01722c4bc8 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -30,42 +30,135 @@
#include "core/io/resource_loader.h"
#include "main/main.h"
-#include "os_javascript.h"
+#include "platform/javascript/display_server_javascript.h"
+#include "platform/javascript/os_javascript.h"
#include <emscripten/emscripten.h>
+#include <stdlib.h>
-extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
+static OS_JavaScript *os = nullptr;
+static uint64_t target_ticks = 0;
- String idbfs_err = String::utf8(p_idbfs_err);
- if (!idbfs_err.empty()) {
- print_line("IndexedDB not available: " + idbfs_err);
+extern "C" EMSCRIPTEN_KEEPALIVE void _request_quit_callback(char *p_filev[], int p_filec) {
+ DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
+ if (ds) {
+ Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+void exit_callback() {
+ emscripten_cancel_main_loop(); // After this, we can exit!
+ Main::cleanup();
+ int exit_code = OS_JavaScript::get_singleton()->get_exit_code();
+ memdelete(os);
+ os = nullptr;
+ emscripten_force_exit(exit_code); // No matter that we call cancel_main_loop, regular "exit" will not work, forcing.
+}
+
+void main_loop_callback() {
+ uint64_t current_ticks = os->get_ticks_usec();
+
+ bool force_draw = DisplayServerJavaScript::get_singleton()->check_size_force_redraw();
+ if (force_draw) {
+ Main::force_redraw();
+ } else if (current_ticks < target_ticks) {
+ return; // Skip frame.
+ }
+
+ int target_fps = Engine::get_singleton()->get_target_fps();
+ if (target_fps > 0) {
+ target_ticks += (uint64_t)(1000000 / target_fps);
+ }
+ if (os->main_loop_iterate()) {
+ emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async.
+ /* clang-format off */
+ EM_ASM({
+ // This will contain the list of operations that need to complete before cleanup.
+ Module.async_finish = [
+ // Always contains at least one async promise, to avoid firing immediately if nothing is added.
+ new Promise(function(accept, reject) {
+ setTimeout(accept, 0);
+ })
+ ];
+ });
+ /* clang-format on */
+ os->get_main_loop()->finish();
+ os->finalize_async(); // Will add all the async finish functions.
+ /* clang-format off */
+ EM_ASM({
+ Promise.all(Module.async_finish).then(function() {
+ Module.async_finish = [];
+ return new Promise(function(accept, reject) {
+ if (!Module.idbfs) {
+ accept();
+ return;
+ }
+ FS.syncfs(function(error) {
+ if (error) {
+ err('Failed to save IDB file system: ' + error.message);
+ }
+ accept();
+ });
+ });
+ }).then(function() {
+ ccall("cleanup_after_sync", null, []);
+ });
+ });
+ /* clang-format on */
}
- OS_JavaScript *os = OS_JavaScript::get_singleton();
- os->set_idb_available(idbfs_err.empty());
- // Ease up compatibility.
- ResourceLoader::set_abort_on_missing_resources(false);
- Main::start();
- os->run_async();
}
+extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() {
+ emscripten_set_main_loop(exit_callback, -1, false);
+}
+
+/// When calling main, it is assumed FS is setup and synced.
int main(int argc, char *argv[]) {
+ // Configure locale.
+ char locale_ptr[16];
+ /* clang-format off */
+ EM_ASM({
+ stringToUTF8(Module['locale'], $0, 16);
+ }, locale_ptr);
+ /* clang-format on */
+ setenv("LANG", locale_ptr, true);
- // Sync from persistent state into memory and then
- // run the 'main_after_fs_sync' function.
+ // Ensure the canvas ID.
/* clang-format off */
- EM_ASM(
- FS.mkdir('/userfs');
- FS.mount(IDBFS, {}, '/userfs');
- FS.syncfs(true, function(err) {
- ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""])
- });
- );
+ EM_ASM({
+ stringToUTF8("#" + Module['canvas'].id, $0, 255);
+ }, DisplayServerJavaScript::canvas_id);
/* clang-format on */
- new OS_JavaScript(argc, argv);
- // TODO: Check error return value.
+ os = new OS_JavaScript();
+ os->set_idb_available((bool)EM_ASM_INT({ return Module.idbfs }));
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
Main::setup(argv[0], argc - 1, &argv[1]);
+ // Ease up compatibility.
+ ResourceLoader::set_abort_on_missing_resources(false);
+
+ Main::start();
+ os->get_main_loop()->init();
+ // Expose method for requesting quit.
+ /* clang-format off */
+ EM_ASM({
+ Module['request_quit'] = function() {
+ ccall("_request_quit_callback", null, []);
+ };
+ });
+ /* clang-format on */
+ emscripten_set_main_loop(main_loop_callback, -1, false);
+ // Immediately run the first iteration.
+ // We are inside an animation frame, we want to immediately draw on the newly setup canvas.
+ main_loop_callback();
+
return 0;
- // Continued async in main_after_fs_sync() from the syncfs() callback.
}
diff --git a/platform/javascript/http_request.js b/platform/javascript/native/http_request.js
index f621689f9d..f621689f9d 100644
--- a/platform/javascript/http_request.js
+++ b/platform/javascript/native/http_request.js
diff --git a/platform/javascript/id_handler.js b/platform/javascript/native/id_handler.js
index 3851123ed1..67d29075b8 100644
--- a/platform/javascript/id_handler.js
+++ b/platform/javascript/native/id_handler.js
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var IDHandler = function() {
+var IDHandler = /** @constructor */ function() {
var ids = {};
var size = 0;
diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js
new file mode 100644
index 0000000000..0b3698fd86
--- /dev/null
+++ b/platform/javascript/native/utils.js
@@ -0,0 +1,277 @@
+/*************************************************************************/
+/* utils.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+Module['initFS'] = function(persistentPaths) {
+ FS.mkdir('/userfs');
+ FS.mount(IDBFS, {}, '/userfs');
+
+ function createRecursive(dir) {
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ }
+
+ persistentPaths.forEach(function(path) {
+ createRecursive(path);
+ FS.mount(IDBFS, {}, path);
+ });
+ return new Promise(function(resolve, reject) {
+ FS.syncfs(true, function(err) {
+ if (err) {
+ Module.idbfs = false;
+ console.log("IndexedDB not available: " + err.message);
+ } else {
+ Module.idbfs = true;
+ }
+ resolve(err);
+ });
+ });
+};
+
+Module['copyToFS'] = function(path, buffer) {
+ var p = path.lastIndexOf("/");
+ var dir = "/";
+ if (p > 0) {
+ dir = path.slice(0, path.lastIndexOf("/"));
+ }
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ // With memory growth, canOwn should be false.
+ FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'});
+}
+
+Module.drop_handler = (function() {
+ var upload = [];
+ var uploadPromises = [];
+ var uploadCallback = null;
+
+ function readFilePromise(entry, path) {
+ return new Promise(function(resolve, reject) {
+ entry.file(function(file) {
+ var reader = new FileReader();
+ reader.onload = function() {
+ var f = {
+ "path": file.relativePath || file.webkitRelativePath,
+ "name": file.name,
+ "type": file.type,
+ "size": file.size,
+ "data": reader.result
+ };
+ if (!f['path'])
+ f['path'] = f['name'];
+ upload.push(f);
+ resolve()
+ };
+ reader.onerror = function() {
+ console.log("Error reading file");
+ reject();
+ }
+
+ reader.readAsArrayBuffer(file);
+
+ }, function(err) {
+ console.log("Error!");
+ reject();
+ });
+ });
+ }
+
+ function readDirectoryPromise(entry) {
+ return new Promise(function(resolve, reject) {
+ var reader = entry.createReader();
+ reader.readEntries(function(entries) {
+ for (var i = 0; i < entries.length; i++) {
+ var ent = entries[i];
+ if (ent.isDirectory) {
+ uploadPromises.push(readDirectoryPromise(ent));
+ } else if (ent.isFile) {
+ uploadPromises.push(readFilePromise(ent));
+ }
+ }
+ resolve();
+ });
+ });
+ }
+
+ function processUploadsPromises(resolve, reject) {
+ if (uploadPromises.length == 0) {
+ resolve();
+ return;
+ }
+ uploadPromises.pop().then(function() {
+ setTimeout(function() {
+ processUploadsPromises(resolve, reject);
+ //processUploadsPromises.bind(null, resolve, reject)
+ }, 0);
+ });
+ }
+
+ function dropFiles(files) {
+ var args = files || [];
+ var argc = args.length;
+ var argv = stackAlloc((argc + 1) * 4);
+ for (var i = 0; i < argc; i++) {
+ HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i]);
+ }
+ HEAP32[(argv >> 2) + argc] = 0;
+ // Defined in display_server_javascript.cpp
+ ccall('_drop_files_callback', 'void', ['number', 'number'], [argv, argc]);
+ }
+
+ return function(ev) {
+ ev.preventDefault();
+ if (ev.dataTransfer.items) {
+ // Use DataTransferItemList interface to access the file(s)
+ for (var i = 0; i < ev.dataTransfer.items.length; i++) {
+ const item = ev.dataTransfer.items[i];
+ var entry = null;
+ if ("getAsEntry" in item) {
+ entry = item.getAsEntry();
+ } else if ("webkitGetAsEntry" in item) {
+ entry = item.webkitGetAsEntry();
+ }
+ if (!entry) {
+ console.error("File upload not supported");
+ } else if (entry.isDirectory) {
+ uploadPromises.push(readDirectoryPromise(entry));
+ } else if (entry.isFile) {
+ uploadPromises.push(readFilePromise(entry));
+ } else {
+ console.error("Unrecognized entry...", entry);
+ }
+ }
+ } else {
+ console.error("File upload not supported");
+ }
+ uploadCallback = new Promise(processUploadsPromises).then(function() {
+ const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/";
+ var drops = [];
+ var files = [];
+ upload.forEach((elem) => {
+ var path = elem['path'];
+ Module['copyToFS'](DROP + path, elem['data']);
+ var idx = path.indexOf("/");
+ if (idx == -1) {
+ // Root file
+ drops.push(DROP + path);
+ } else {
+ // Subdir
+ var sub = path.substr(0, idx);
+ idx = sub.indexOf("/");
+ if (idx < 0 && drops.indexOf(DROP + sub) == -1) {
+ drops.push(DROP + sub);
+ }
+ }
+ files.push(DROP + path);
+ });
+ uploadPromises = [];
+ upload = [];
+ dropFiles(drops);
+ var dirs = [DROP.substr(0, DROP.length -1)];
+ files.forEach(function (file) {
+ FS.unlink(file);
+ var dir = file.replace(DROP, "");
+ var idx = dir.lastIndexOf("/");
+ while (idx > 0) {
+ dir = dir.substr(0, idx);
+ if (dirs.indexOf(DROP + dir) == -1) {
+ dirs.push(DROP + dir);
+ }
+ idx = dir.lastIndexOf("/");
+ }
+ });
+ // Remove dirs.
+ dirs = dirs.sort(function(a, b) {
+ var al = (a.match(/\//g) || []).length;
+ var bl = (b.match(/\//g) || []).length;
+ if (al > bl)
+ return -1;
+ else if (al < bl)
+ return 1;
+ return 0;
+ });
+ dirs.forEach(function(dir) {
+ FS.rmdir(dir);
+ });
+ });
+ }
+})();
+
+function EventHandlers() {
+ function Handler(target, event, method, capture) {
+ this.target = target;
+ this.event = event;
+ this.method = method;
+ this.capture = capture;
+ }
+
+ var listeners = [];
+
+ function has(target, event, method, capture) {
+ return listeners.findIndex(function(e) {
+ return e.target === target && e.event === event && e.method === method && e.capture == capture;
+ }) !== -1;
+ }
+
+ this.add = function(target, event, method, capture) {
+ if (has(target, event, method, capture)) {
+ return;
+ }
+ listeners.push(new Handler(target, event, method, capture));
+ target.addEventListener(event, method, capture);
+ };
+
+ this.remove = function(target, event, method, capture) {
+ if (!has(target, event, method, capture)) {
+ return;
+ }
+ target.removeEventListener(event, method, capture);
+ };
+
+ this.clear = function() {
+ listeners.forEach(function(h) {
+ h.target.removeEventListener(h.event, h.method, h.capture);
+ });
+ listeners.length = 0;
+ };
+}
+
+Module.listeners = new EventHandlers();
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index 632a7df9e8..cf5751f384 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -30,1117 +30,127 @@
#include "os_javascript.h"
+#include "core/debugger/engine_debugger.h"
#include "core/io/file_access_buffered_fa.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
+#include "core/io/json.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "main/main.h"
-#include "servers/visual/visual_server_raster.h"
+#include "modules/modules_enabled.gen.h"
+#include "platform/javascript/display_server_javascript.h"
+
+#ifdef MODULE_WEBSOCKET_ENABLED
+#include "modules/websocket/remote_debugger_peer_websocket.h"
+#endif
#include <emscripten.h>
-#include <png.h>
#include <stdlib.h>
-#include "dom_keys.inc"
-
-#define DOM_BUTTON_LEFT 0
-#define DOM_BUTTON_MIDDLE 1
-#define DOM_BUTTON_RIGHT 2
-#define DOM_BUTTON_XBUTTON1 3
-#define DOM_BUTTON_XBUTTON2 4
-#define GODOT_CANVAS_SELECTOR "#canvas"
-
-// Window (canvas)
-
-static void focus_canvas() {
-
- /* clang-format off */
- EM_ASM(
- Module.canvas.focus();
- );
- /* clang-format on */
-}
-
-static bool is_canvas_focused() {
-
- /* clang-format off */
- return EM_ASM_INT_V(
- return document.activeElement == Module.canvas;
- );
- /* clang-format on */
-}
-
-static Point2 compute_position_in_canvas(int x, int y) {
- int canvas_x = EM_ASM_INT({
- return document.getElementById('canvas').getBoundingClientRect().x;
- });
- int canvas_y = EM_ASM_INT({
- return document.getElementById('canvas').getBoundingClientRect().y;
- });
- int canvas_width;
- int canvas_height;
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, &canvas_width, &canvas_height);
-
- double element_width;
- double element_height;
- emscripten_get_element_css_size(GODOT_CANVAS_SELECTOR, &element_width, &element_height);
-
- return Point2((int)(canvas_width / element_width * (x - canvas_x)),
- (int)(canvas_height / element_height * (y - canvas_y)));
-}
-
-static bool cursor_inside_canvas = true;
-
-EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- // Empty ID is canvas.
- String target_id = String::utf8(p_event->id);
- if (target_id.empty() || target_id == "canvas") {
- // This event property is the only reliable data on
- // browser fullscreen state.
- os->video_mode.fullscreen = p_event->isFullscreen;
- if (os->video_mode.fullscreen) {
- os->entering_fullscreen = false;
- } else {
- // Restoring maximized window now will cause issues,
- // so delay until main_loop_iterate.
- os->just_exited_fullscreen = true;
- }
- }
- return false;
-}
-
-void OS_JavaScript::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-
- video_mode = p_video_mode;
-}
-
-OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const {
-
- return video_mode;
-}
-
-Size2 OS_JavaScript::get_screen_size(int p_screen) const {
-
- EmscriptenFullscreenChangeEvent ev;
- EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev);
- ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2());
- return Size2(ev.screenWidth, ev.screenHeight);
-}
-
-void OS_JavaScript::set_window_size(const Size2 p_size) {
-
- windowed_size = p_size;
- if (video_mode.fullscreen) {
- window_maximized = false;
- set_window_fullscreen(false);
- } else {
- if (window_maximized) {
- emscripten_exit_soft_fullscreen();
- window_maximized = false;
- }
- emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, p_size.x, p_size.y);
- }
-}
-
-Size2 OS_JavaScript::get_window_size() const {
-
- int canvas[2];
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1);
- return Size2(canvas[0], canvas[1]);
-}
-
-void OS_JavaScript::set_window_maximized(bool p_enabled) {
-
- if (video_mode.fullscreen) {
- window_maximized = p_enabled;
- set_window_fullscreen(false);
- } else if (!p_enabled) {
- emscripten_exit_soft_fullscreen();
- window_maximized = false;
- } else if (!window_maximized) {
- // Prevent calling emscripten_enter_soft_fullscreen mutltiple times,
- // this would hide page elements permanently.
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = NULL;
- emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy);
- window_maximized = p_enabled;
- }
-}
-
-bool OS_JavaScript::is_window_maximized() const {
-
- return window_maximized;
-}
-
-void OS_JavaScript::set_window_fullscreen(bool p_enabled) {
-
- if (p_enabled == video_mode.fullscreen) {
- return;
- }
-
- // Just request changes here, if successful, logic continues in
- // fullscreen_change_callback.
- if (p_enabled) {
- if (window_maximized) {
- // Soft fullsreen during real fullscreen can cause issues, so exit.
- // This must be called before requesting full screen.
- emscripten_exit_soft_fullscreen();
- }
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = NULL;
- EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(GODOT_CANVAS_SELECTOR, false, &strategy);
- ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
- ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
- // Not fullscreen yet, so prevent "windowed" canvas dimensions from
- // being overwritten.
- entering_fullscreen = true;
- } else {
- // No logic allowed here, since exiting w/ ESC key won't use this function.
- ERR_FAIL_COND(emscripten_exit_fullscreen() != EMSCRIPTEN_RESULT_SUCCESS);
- }
-}
-
-bool OS_JavaScript::is_window_fullscreen() const {
-
- return video_mode.fullscreen;
-}
-
-void OS_JavaScript::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-
- Size2 screen = get_screen_size();
- p_list->push_back(OS::VideoMode(screen.width, screen.height, true));
-}
-
-// Keys
-
-template <typename T>
-static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
-
- godot_event->set_shift(emscripten_event_ptr->shiftKey);
- godot_event->set_alt(emscripten_event_ptr->altKey);
- godot_event->set_control(emscripten_event_ptr->ctrlKey);
- godot_event->set_metakey(emscripten_event_ptr->metaKey);
-}
-
-static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
-
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(emscripten_event->repeat);
- dom2godot_mod(emscripten_event, ev);
- ev->set_scancode(dom2godot_scancode(emscripten_event->keyCode));
-
- String unicode = String::utf8(emscripten_event->key);
- // Check if empty or multi-character (e.g. `CapsLock`).
- if (unicode.length() != 1) {
- // Might be empty as well, but better than nonsense.
- unicode = String::utf8(emscripten_event->charValue);
- }
- if (unicode.length() == 1) {
- ev->set_unicode(unicode[0]);
- }
-
- return ev;
-}
-
-EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventKey> ev = setup_key_event(p_event);
- ev->set_pressed(true);
- if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_scancode())) {
- // Defer to keypress event for legacy unicode retrieval.
- os->deferred_key_event = ev;
- // Do not suppress keypress event.
- return false;
- }
- os->input->parse_input_event(ev);
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- return true;
-}
-
-EM_BOOL OS_JavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- os->deferred_key_event->set_unicode(p_event->charCode);
- os->input->parse_input_event(os->deferred_key_event);
- return true;
-}
-
-EM_BOOL OS_JavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- Ref<InputEventKey> ev = setup_key_event(p_event);
- ev->set_pressed(false);
- get_singleton()->input->parse_input_event(ev);
- return ev->get_scancode() != KEY_UNKNOWN && ev->get_scancode() != 0;
-}
-
-// Mouse
-
-Point2 OS_JavaScript::get_mouse_position() const {
-
- return input->get_mouse_position();
-}
-
-int OS_JavaScript::get_mouse_button_state() const {
-
- return input->get_mouse_button_mask();
-}
-
-EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
-
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
- ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY));
- ev->set_global_position(ev->get_position());
- dom2godot_mod(p_event, ev);
-
- switch (p_event->button) {
- case DOM_BUTTON_LEFT: ev->set_button_index(BUTTON_LEFT); break;
- case DOM_BUTTON_MIDDLE: ev->set_button_index(BUTTON_MIDDLE); break;
- case DOM_BUTTON_RIGHT: ev->set_button_index(BUTTON_RIGHT); break;
- case DOM_BUTTON_XBUTTON1: ev->set_button_index(BUTTON_XBUTTON1); break;
- case DOM_BUTTON_XBUTTON2: ev->set_button_index(BUTTON_XBUTTON2); break;
- default: return false;
- }
-
- if (ev->is_pressed()) {
-
- double diff = emscripten_get_now() - os->last_click_ms;
-
- if (ev->get_button_index() == os->last_click_button_index) {
-
- if (diff < 400 && Point2(os->last_click_pos).distance_to(ev->get_position()) < 5) {
-
- os->last_click_ms = 0;
- os->last_click_pos = Point2(-100, -100);
- os->last_click_button_index = -1;
- ev->set_doubleclick(true);
- }
-
- } else {
- os->last_click_button_index = ev->get_button_index();
- }
-
- if (!ev->is_doubleclick()) {
- os->last_click_ms += diff;
- os->last_click_pos = ev->get_position();
- }
- }
-
- int mask = os->input->get_mouse_button_mask();
- int button_flag = 1 << (ev->get_button_index() - 1);
- if (ev->is_pressed()) {
- // Since the event is consumed, focus manually. The containing iframe,
- // if exists, may not have focus yet, so focus even if already focused.
- focus_canvas();
- mask |= button_flag;
- } else if (mask & button_flag) {
- mask &= ~button_flag;
- } else {
- // Received release event, but press was outside the canvas, so ignore.
- return false;
- }
- ev->set_button_mask(mask);
-
- os->input->parse_input_event(ev);
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- // Prevent multi-click text selection and wheel-click scrolling anchor.
- // Context menu is prevented through contextmenu event.
- return true;
-}
-
-EM_BOOL OS_JavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
-
- int input_mask = os->input->get_mouse_button_mask();
- Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY);
- // For motion outside the canvas, only read mouse movement if dragging
- // started inside the canvas; imitating desktop app behaviour.
- if (!cursor_inside_canvas && !input_mask)
- return false;
-
- Ref<InputEventMouseMotion> ev;
- ev.instance();
- dom2godot_mod(p_event, ev);
- ev->set_button_mask(input_mask);
-
- ev->set_position(pos);
- ev->set_global_position(ev->get_position());
-
- ev->set_relative(Vector2(p_event->movementX, p_event->movementY));
- os->input->set_mouse_position(ev->get_position());
- ev->set_speed(os->input->get_last_mouse_speed());
-
- os->input->parse_input_event(ev);
- // Don't suppress mouseover/-leave events.
- return false;
-}
-
-static const char *godot2dom_cursor(OS::CursorShape p_shape) {
-
- switch (p_shape) {
- case OS::CURSOR_ARROW:
- default:
- return "auto";
- case OS::CURSOR_IBEAM: return "text";
- case OS::CURSOR_POINTING_HAND: return "pointer";
- case OS::CURSOR_CROSS: return "crosshair";
- case OS::CURSOR_WAIT: return "progress";
- case OS::CURSOR_BUSY: return "wait";
- case OS::CURSOR_DRAG: return "grab";
- case OS::CURSOR_CAN_DROP: return "grabbing";
- case OS::CURSOR_FORBIDDEN: return "no-drop";
- case OS::CURSOR_VSIZE: return "ns-resize";
- case OS::CURSOR_HSIZE: return "ew-resize";
- case OS::CURSOR_BDIAGSIZE: return "nesw-resize";
- case OS::CURSOR_FDIAGSIZE: return "nwse-resize";
- case OS::CURSOR_MOVE: return "move";
- case OS::CURSOR_VSPLIT: return "row-resize";
- case OS::CURSOR_HSPLIT: return "col-resize";
- case OS::CURSOR_HELP: return "help";
- }
-}
-
-static void set_css_cursor(const char *p_cursor) {
-
- /* clang-format off */
- EM_ASM_({
- Module.canvas.style.cursor = UTF8ToString($0);
- }, p_cursor);
- /* clang-format on */
-}
-
-static bool is_css_cursor_hidden() {
-
- /* clang-format off */
- return EM_ASM_INT({
- return Module.canvas.style.cursor === 'none';
- });
- /* clang-format on */
-}
-
-void OS_JavaScript::set_cursor_shape(CursorShape p_shape) {
-
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
-
- if (get_mouse_mode() == MOUSE_MODE_VISIBLE) {
- if (cursors[p_shape] != "") {
- Vector<String> url = cursors[p_shape].split("?");
- set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8());
- } else {
- set_css_cursor(godot2dom_cursor(p_shape));
- }
- }
-
- cursor_shape = p_shape;
-}
-
-void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-
- if (p_cursor.is_valid()) {
-
- Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- set_cursor_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
- }
-
- Ref<Texture> texture = p_cursor;
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
- Size2 texture_size;
- Rect2 atlas_rect;
-
- if (texture.is_valid()) {
- image = texture->get_data();
- if (image.is_valid()) {
- image->duplicate();
- }
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(!texture.is_valid());
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- image = texture->get_data();
-
- ERR_FAIL_COND(!image.is_valid());
-
- image = image->duplicate();
-
- if (atlas_texture.is_valid())
- image->crop_from_point(
- atlas_rect.position.x,
- atlas_rect.position.y,
- texture_size.width,
- texture_size.height);
-
- if (image->get_format() != Image::FORMAT_RGBA8) {
- image->convert(Image::FORMAT_RGBA8);
- }
-
- png_image png_meta;
- memset(&png_meta, 0, sizeof png_meta);
- png_meta.version = PNG_IMAGE_VERSION;
- png_meta.width = texture_size.width;
- png_meta.height = texture_size.height;
- png_meta.format = PNG_FORMAT_RGBA;
-
- PoolByteArray png;
- size_t len;
- PoolByteArray::Read r = image->get_data().read();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, r.ptr(), 0, NULL));
-
- png.resize(len);
- PoolByteArray::Write w = png.write();
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, w.ptr(), &len, 0, r.ptr(), 0, NULL));
- w = PoolByteArray::Write();
-
- r = png.read();
-
- char *object_url;
- /* clang-format off */
- EM_ASM({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
- var PTR = $2;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' });
- var url = URL.createObjectURL(png);
- var length_bytes = lengthBytesUTF8(url) + 1;
- var string_on_wasm_heap = _malloc(length_bytes);
- setValue(PTR, string_on_wasm_heap, '*');
- stringToUTF8(url, string_on_wasm_heap, length_bytes);
- }, r.ptr(), len, &object_url);
- /* clang-format on */
- r = PoolByteArray::Read();
-
- String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y);
-
- /* clang-format off */
- EM_ASM({ _free($0); }, object_url);
- /* clang-format on */
-
- if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
- }
-
- cursors[p_shape] = url;
-
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- } else if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
-
- cursors_cache.erase(p_shape);
- }
-
- set_cursor_shape(cursor_shape);
-}
-
-void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) {
-
- ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform.");
- if (p_mode == get_mouse_mode())
- return;
-
- if (p_mode == MOUSE_MODE_VISIBLE) {
-
- // set_css_cursor must be called before set_cursor_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- set_cursor_shape(cursor_shape);
- emscripten_exit_pointerlock();
-
- } else if (p_mode == MOUSE_MODE_HIDDEN) {
-
- set_css_cursor("none");
- emscripten_exit_pointerlock();
-
- } else if (p_mode == MOUSE_MODE_CAPTURED) {
-
- EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false);
- ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
- ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
- // set_css_cursor must be called before set_cursor_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- set_cursor_shape(cursor_shape);
- }
-}
-
-OS::MouseMode OS_JavaScript::get_mouse_mode() const {
-
- if (is_css_cursor_hidden())
- return MOUSE_MODE_HIDDEN;
-
- EmscriptenPointerlockChangeEvent ev;
- emscripten_get_pointerlock_status(&ev);
- return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
-}
-
-// Wheel
-
-EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
-
- ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
- if (!is_canvas_focused()) {
- if (cursor_inside_canvas) {
- focus_canvas();
- } else {
- return false;
- }
- }
-
- InputDefault *input = get_singleton()->input;
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_position(input->get_mouse_position());
- ev->set_global_position(ev->get_position());
-
- ev->set_shift(input->is_key_pressed(KEY_SHIFT));
- ev->set_alt(input->is_key_pressed(KEY_ALT));
- ev->set_control(input->is_key_pressed(KEY_CONTROL));
- ev->set_metakey(input->is_key_pressed(KEY_META));
-
- if (p_event->deltaY < 0)
- ev->set_button_index(BUTTON_WHEEL_UP);
- else if (p_event->deltaY > 0)
- ev->set_button_index(BUTTON_WHEEL_DOWN);
- else if (p_event->deltaX > 0)
- ev->set_button_index(BUTTON_WHEEL_LEFT);
- else if (p_event->deltaX < 0)
- ev->set_button_index(BUTTON_WHEEL_RIGHT);
- else
- return false;
-
- // Different browsers give wildly different delta values, and we can't
- // interpret deltaMode, so use default value for wheel events' factor.
-
- int button_flag = 1 << (ev->get_button_index() - 1);
-
- ev->set_pressed(true);
- ev->set_button_mask(input->get_mouse_button_mask() | button_flag);
- input->parse_input_event(ev);
-
- ev->set_pressed(false);
- ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag);
- input->parse_input_event(ev);
-
- return true;
-}
-
-// Touch
-
-bool OS_JavaScript::has_touchscreen_ui_hint() const {
-
- /* clang-format off */
- return EM_ASM_INT_V(
- return 'ontouchstart' in window;
- );
- /* clang-format on */
-}
-
-EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < p_event->numTouches; ++i) {
-
- const EmscriptenTouchPoint &touch = p_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
- os->touches[i] = ev->get_position();
- ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
-
- os->input->parse_input_event(ev);
- }
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- return true;
-}
-
-EM_BOOL OS_JavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < p_event->numTouches; ++i) {
-
- const EmscriptenTouchPoint &touch = p_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
- Point2 &prev = os->touches[i];
- ev->set_relative(ev->get_position() - prev);
- prev = ev->get_position();
-
- os->input->parse_input_event(ev);
- }
- return true;
-}
-
-// Gamepad
-
-EM_BOOL OS_JavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) {
-
- InputDefault *input = get_singleton()->input;
- if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) {
-
- String guid = "";
- if (String::utf8(p_event->mapping) == "standard")
- guid = "Default HTML5 Gamepad";
- input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid);
- } else {
- input->joy_connection_changed(p_event->index, false, "");
- }
- return true;
-}
-
-void OS_JavaScript::process_joypads() {
-
- int joypad_count = emscripten_get_num_gamepads();
- for (int joypad = 0; joypad < joypad_count; joypad++) {
- EmscriptenGamepadEvent state;
- EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state);
- // Chromium reserves gamepads slots, so NO_DATA is an expected result.
- ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS &&
- query_result != EMSCRIPTEN_RESULT_NO_DATA);
- if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) {
-
- int button_count = MIN(state.numButtons, 18);
- int axis_count = MIN(state.numAxes, 8);
- for (int button = 0; button < button_count; button++) {
-
- float value = state.analogButton[button];
- if (String::utf8(state.mapping) == "standard" && (button == JOY_ANALOG_L2 || button == JOY_ANALOG_R2)) {
- InputDefault::JoyAxis joy_axis;
- joy_axis.min = 0;
- joy_axis.value = value;
- input->joy_axis(joypad, button, joy_axis);
- } else {
- input->joy_button(joypad, button, value);
- }
- }
- for (int axis = 0; axis < axis_count; axis++) {
-
- InputDefault::JoyAxis joy_axis;
- joy_axis.min = -1;
- joy_axis.value = state.axis[axis];
- input->joy_axis(joypad, axis, joy_axis);
- }
- }
- }
-}
-
-bool OS_JavaScript::is_joy_known(int p_device) {
-
- return input->is_joy_mapped(p_device);
-}
-
-String OS_JavaScript::get_joy_guid(int p_device) const {
-
- return input->get_joy_guid_remapped(p_device);
-}
-
-// Video
-
-int OS_JavaScript::get_video_driver_count() const {
-
- return VIDEO_DRIVER_MAX;
-}
-
-const char *OS_JavaScript::get_video_driver_name(int p_driver) const {
-
- switch (p_driver) {
- case VIDEO_DRIVER_GLES3:
- return "GLES3";
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + ".");
-}
-
-// Audio
-
-int OS_JavaScript::get_audio_driver_count() const {
-
- return 1;
-}
-
-const char *OS_JavaScript::get_audio_driver_name(int p_driver) const {
-
- return "JavaScript";
-}
-
-// Clipboard
-extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) {
- // Only call set_clipboard from OS (sets local clipboard)
- OS::get_singleton()->OS::set_clipboard(p_text);
-}
-
-void OS_JavaScript::set_clipboard(const String &p_text) {
- OS::set_clipboard(p_text);
- /* clang-format off */
- int err = EM_ASM_INT({
- var text = UTF8ToString($0);
- if (!navigator.clipboard || !navigator.clipboard.writeText)
- return 1;
- navigator.clipboard.writeText(text).catch(function(e) {
- // Setting OS clipboard is only possible from an input callback.
- console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
- });
- return 0;
- }, p_text.utf8().get_data());
- /* clang-format on */
- ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");
-}
-
-String OS_JavaScript::get_clipboard() const {
- /* clang-format off */
- EM_ASM({
- try {
- navigator.clipboard.readText().then(function (result) {
- ccall('update_clipboard', 'void', ['string'], [result]);
- }).catch(function (e) {
- // Fail graciously.
- });
- } catch (e) {
- // Fail graciously.
- }
- });
- /* clang-format on */
- return this->OS::get_clipboard();
-}
-
// Lifecycle
-int OS_JavaScript::get_current_video_driver() const {
- return video_driver_index;
-}
-
-void OS_JavaScript::initialize_core() {
-
+void OS_JavaScript::initialize() {
OS_Unix::initialize_core();
- FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES);
-}
-
-Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- EmscriptenWebGLContextAttributes attributes;
- emscripten_webgl_init_context_attributes(&attributes);
- attributes.alpha = false;
- attributes.antialias = false;
- ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER);
+ FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES);
+ DisplayServerJavaScript::register_javascript_driver();
- bool gles3 = true;
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gles3 = false;
- }
-
- bool gl_initialization_error = false;
-
- while (true) {
- if (gles3) {
- if (RasterizerGLES3::is_viable() == OK) {
- attributes.majorVersion = 2;
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- gles3 = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- attributes.majorVersion = 1;
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(GODOT_CANVAS_SELECTOR, &attributes);
- if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your browser does not support any of the supported WebGL versions.\n"
- "Please update your browser version.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
+#ifdef MODULE_WEBSOCKET_ENABLED
+ EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create);
+ EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create);
+#endif
+}
- video_mode = p_desired;
- // fullscreen_change_callback will correct this if the request is successful.
- video_mode.fullscreen = false;
- // Emscripten only attempts fullscreen requests if the user input callback
- // was registered through one its own functions, so request manually for
- // start-up fullscreen.
- if (p_desired.fullscreen) {
- /* clang-format off */
- EM_ASM({
- (canvas.requestFullscreen || canvas.msRequestFullscreen ||
- canvas.mozRequestFullScreen || canvas.mozRequestFullscreen ||
- canvas.webkitRequestFullscreen
- ).call(canvas);
- });
- /* clang-format on */
+void OS_JavaScript::resume_audio() {
+ if (audio_driver_javascript) {
+ audio_driver_javascript->resume();
}
- /* clang-format off */
- if (EM_ASM_INT_V({ return Module.resizeCanvasOnStart })) {
- /* clang-format on */
- set_window_size(Size2(video_mode.width, video_mode.height));
- } else {
- set_window_size(get_window_size());
- }
-
- char locale_ptr[16];
- /* clang-format off */
- EM_ASM_ARGS({
- stringToUTF8(Module.locale, $0, 16);
- }, locale_ptr);
- /* clang-format on */
- setenv("LANG", locale_ptr, true);
-
- AudioDriverManager::initialize(p_audio_driver);
- VisualServer *visual_server = memnew(VisualServerRaster());
- input = memnew(InputDefault);
-
- EMSCRIPTEN_RESULT result;
-#define EM_CHECK(ev) \
- if (result != EMSCRIPTEN_RESULT_SUCCESS) \
- ERR_PRINTS("Error while setting " #ev " callback: Code " + itos(result))
-#define SET_EM_CALLBACK(target, ev, cb) \
- result = emscripten_set_##ev##_callback(target, NULL, true, &cb); \
- EM_CHECK(ev)
-#define SET_EM_CALLBACK_NOTARGET(ev, cb) \
- result = emscripten_set_##ev##_callback(NULL, true, &cb); \
- EM_CHECK(ev)
- // These callbacks from Emscripten's html5.h suffice to access most
- // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM
- // is used below.
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, mousedown, mouse_button_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, wheel, wheel_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchstart, touch_press_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchmove, touchmove_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchend, touch_press_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchcancel, touch_press_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keydown, keydown_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keypress, keypress_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keyup, keyup_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback)
- SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback)
- SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback)
-#undef SET_EM_CALLBACK_NOTARGET
-#undef SET_EM_CALLBACK
-#undef EM_CHECK
-
- /* clang-format off */
- EM_ASM_ARGS({
- const send_notification = cwrap('send_notification', null, ['number']);
- const notifications = arguments;
- (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
- Module.canvas.addEventListener(event, send_notification.bind(null, notifications[index]));
- });
- // Clipboard
- const update_clipboard = cwrap('update_clipboard', null, ['string']);
- window.addEventListener('paste', function(evt) {
- update_clipboard(evt.clipboardData.getData('text'));
- }, true);
- },
- MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
- MainLoop::NOTIFICATION_WM_MOUSE_EXIT,
- MainLoop::NOTIFICATION_WM_FOCUS_IN,
- MainLoop::NOTIFICATION_WM_FOCUS_OUT
- );
- /* clang-format on */
-
- visual_server->init();
-
- return OK;
}
void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) {
-
main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
}
MainLoop *OS_JavaScript::get_main_loop() const {
-
return main_loop;
}
-void OS_JavaScript::run_async() {
-
- main_loop->init();
- emscripten_set_main_loop(main_loop_callback, -1, false);
-}
-
-void OS_JavaScript::main_loop_callback() {
-
- get_singleton()->main_loop_iterate();
+extern "C" EMSCRIPTEN_KEEPALIVE void _idb_synced() {
+ OS_JavaScript::get_singleton()->idb_is_syncing = false;
}
bool OS_JavaScript::main_loop_iterate() {
-
- if (is_userfs_persistent() && sync_wait_time >= 0) {
- int64_t current_time = get_ticks_msec();
- int64_t elapsed_time = current_time - last_sync_check_time;
- last_sync_check_time = current_time;
-
- sync_wait_time -= elapsed_time;
-
- if (sync_wait_time < 0) {
- /* clang-format off */
- EM_ASM(
- FS.syncfs(function(error) {
- if (error) { err('Failed to save IDB file system: ' + error.message); }
- });
- );
- /* clang-format on */
- }
- }
-
- if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS)
- process_joypads();
-
- if (just_exited_fullscreen) {
- if (window_maximized) {
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = NULL;
- emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy);
- } else {
- emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, windowed_size.width, windowed_size.height);
- }
- just_exited_fullscreen = false;
+ if (is_userfs_persistent() && idb_needs_sync && !idb_is_syncing) {
+ idb_is_syncing = true;
+ idb_needs_sync = false;
+ /* clang-format off */
+ EM_ASM({
+ FS.syncfs(function(error) {
+ if (error) {
+ err('Failed to save IDB file system: ' + error.message);
+ }
+ ccall("_idb_synced", 'void', [], []);
+ });
+ });
+ /* clang-format on */
}
- int canvas[2];
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1);
- video_mode.width = canvas[0];
- video_mode.height = canvas[1];
- if (!window_maximized && !video_mode.fullscreen && !just_exited_fullscreen && !entering_fullscreen) {
- windowed_size.width = canvas[0];
- windowed_size.height = canvas[1];
- }
+ DisplayServer::get_singleton()->process_events();
return Main::iteration();
}
void OS_JavaScript::delete_main_loop() {
+ if (main_loop) {
+ memdelete(main_loop);
+ }
+ main_loop = nullptr;
+}
- memdelete(main_loop);
+void OS_JavaScript::finalize_async() {
+ finalizing = true;
+ if (audio_driver_javascript) {
+ audio_driver_javascript->finish_async();
+ }
}
void OS_JavaScript::finalize() {
-
- memdelete(input);
+ delete_main_loop();
+ if (audio_driver_javascript) {
+ memdelete(audio_driver_javascript);
+ audio_driver_javascript = nullptr;
+ }
}
// Miscellaneous
Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
-
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::execute() is not available on the HTML5 platform.");
+ Array args;
+ for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
+ args.push_back(E->get());
+ }
+ String json_args = JSON::print(args);
+ /* clang-format off */
+ int failed = EM_ASM_INT({
+ const json_args = UTF8ToString($0);
+ const args = JSON.parse(json_args);
+ if (Module["onExecute"]) {
+ Module["onExecute"](args);
+ return 0;
+ }
+ return 1;
+ }, json_args.utf8().get_data());
+ /* clang-format on */
+ ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() must be implemented in JavaScript via 'engine.setOnExecute' if required.");
+ return OK;
}
Error OS_JavaScript::kill(const ProcessID &p_pid) {
-
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::kill() is not available on the HTML5 platform.");
}
int OS_JavaScript::get_process_id() const {
-
ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform.");
}
-extern "C" EMSCRIPTEN_KEEPALIVE void send_notification(int p_notification) {
-
- if (p_notification == MainLoop::NOTIFICATION_WM_MOUSE_ENTER || p_notification == MainLoop::NOTIFICATION_WM_MOUSE_EXIT) {
- cursor_inside_canvas = p_notification == MainLoop::NOTIFICATION_WM_MOUSE_ENTER;
- }
- OS_JavaScript::get_singleton()->get_main_loop()->notification(p_notification);
-}
-
bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
-
if (p_feature == "HTML5" || p_feature == "web")
return true;
@@ -1152,82 +162,11 @@ bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
return false;
}
-void OS_JavaScript::alert(const String &p_alert, const String &p_title) {
-
- /* clang-format off */
- EM_ASM_({
- window.alert(UTF8ToString($0));
- }, p_alert.utf8().get_data());
- /* clang-format on */
-}
-
-void OS_JavaScript::set_window_title(const String &p_title) {
-
- /* clang-format off */
- EM_ASM_({
- document.title = UTF8ToString($0);
- }, p_title.utf8().get_data());
- /* clang-format on */
-}
-
-void OS_JavaScript::set_icon(const Ref<Image> &p_icon) {
-
- ERR_FAIL_COND(p_icon.is_null());
- Ref<Image> icon = p_icon;
- if (icon->is_compressed()) {
- icon = icon->duplicate();
- ERR_FAIL_COND(icon->decompress() != OK);
- }
- if (icon->get_format() != Image::FORMAT_RGBA8) {
- if (icon == p_icon)
- icon = icon->duplicate();
- icon->convert(Image::FORMAT_RGBA8);
- }
-
- png_image png_meta;
- memset(&png_meta, 0, sizeof png_meta);
- png_meta.version = PNG_IMAGE_VERSION;
- png_meta.width = icon->get_width();
- png_meta.height = icon->get_height();
- png_meta.format = PNG_FORMAT_RGBA;
-
- PoolByteArray png;
- size_t len;
- PoolByteArray::Read r = icon->get_data().read();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, r.ptr(), 0, NULL));
-
- png.resize(len);
- PoolByteArray::Write w = png.write();
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, w.ptr(), &len, 0, r.ptr(), 0, NULL));
- w = PoolByteArray::Write();
-
- r = png.read();
- /* clang-format off */
- EM_ASM_ARGS({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" });
- var url = URL.createObjectURL(png);
- var link = document.getElementById('-gd-engine-icon');
- if (link === null) {
- link = document.createElement('link');
- link.rel = 'icon';
- link.id = '-gd-engine-icon';
- document.head.appendChild(link);
- }
- link.href = url;
- }, r.ptr(), len);
- /* clang-format on */
-}
-
String OS_JavaScript::get_executable_path() const {
-
return OS::get_executable_path();
}
Error OS_JavaScript::shell_open(String p_uri) {
-
// Open URI in a new tab, browser will deal with it by protocol.
/* clang-format off */
EM_ASM_({
@@ -1238,90 +177,60 @@ Error OS_JavaScript::shell_open(String p_uri) {
}
String OS_JavaScript::get_name() const {
-
return "HTML5";
}
-bool OS_JavaScript::can_draw() const {
-
- return true; // Always?
-}
-
String OS_JavaScript::get_user_data_dir() const {
-
return "/userfs";
};
-String OS_JavaScript::get_resource_dir() const {
-
- return "/";
+String OS_JavaScript::get_cache_path() const {
+ return "/home/web_user/.cache";
}
-OS::PowerState OS_JavaScript::get_power_state() {
-
- WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to POWERSTATE_UNKNOWN");
- return OS::POWERSTATE_UNKNOWN;
+String OS_JavaScript::get_config_path() const {
+ return "/home/web_user/.config";
}
-int OS_JavaScript::get_power_seconds_left() {
-
- WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to -1");
- return -1;
-}
-
-int OS_JavaScript::get_power_percent_left() {
-
- WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to -1");
- return -1;
+String OS_JavaScript::get_data_path() const {
+ return "/home/web_user/.local/share";
}
void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) {
-
- OS_JavaScript *os = get_singleton();
- if (os->is_userfs_persistent() && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) {
- os->last_sync_check_time = OS::get_singleton()->get_ticks_msec();
- // Wait five seconds in case more files are about to be closed.
- os->sync_wait_time = 5000;
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+ if (!(os->is_userfs_persistent() && (p_flags & FileAccess::WRITE))) {
+ return; // FS persistence is not working or we are not writing.
+ }
+ bool is_file_persistent = p_file.begins_with("/userfs");
+#ifdef TOOLS_ENABLED
+ // Hack for editor persistence (can we track).
+ is_file_persistent = is_file_persistent || p_file.begins_with("/home/web_user/");
+#endif
+ if (is_file_persistent) {
+ os->idb_needs_sync = true;
}
}
void OS_JavaScript::set_idb_available(bool p_idb_available) {
-
idb_available = p_idb_available;
}
bool OS_JavaScript::is_userfs_persistent() const {
-
return idb_available;
}
OS_JavaScript *OS_JavaScript::get_singleton() {
-
return static_cast<OS_JavaScript *>(OS::get_singleton());
}
-OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) {
+void OS_JavaScript::initialize_joypads() {
+}
- List<String> arguments;
- for (int i = 1; i < p_argc; i++) {
- arguments.push_back(String::utf8(p_argv[i]));
+OS_JavaScript::OS_JavaScript() {
+ if (AudioDriverJavaScript::is_available()) {
+ audio_driver_javascript = memnew(AudioDriverJavaScript);
+ AudioDriverManager::add_driver(audio_driver_javascript);
}
- set_cmdline(p_argv[0], arguments);
-
- last_click_button_index = -1;
- last_click_ms = 0;
- last_click_pos = Point2(-100, -100);
-
- window_maximized = false;
- entering_fullscreen = false;
- just_exited_fullscreen = false;
-
- main_loop = NULL;
-
- idb_available = false;
- sync_wait_time = -1;
-
- AudioDriverManager::add_driver(&audio_driver_javascript);
Vector<Logger *> loggers;
loggers.push_back(memnew(StdLogger));
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 5c02a292ee..85551d708b 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -32,138 +32,69 @@
#define OS_JAVASCRIPT_H
#include "audio_driver_javascript.h"
+#include "core/input/input.h"
#include "drivers/unix/os_unix.h"
-#include "main/input_default.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
#include <emscripten/html5.h>
class OS_JavaScript : public OS_Unix {
+ MainLoop *main_loop = nullptr;
+ AudioDriverJavaScript *audio_driver_javascript = nullptr;
- VideoMode video_mode;
- Vector2 windowed_size;
- bool window_maximized;
- bool entering_fullscreen;
- bool just_exited_fullscreen;
-
- InputDefault *input;
- Ref<InputEventKey> deferred_key_event;
- CursorShape cursor_shape;
- String cursors[CURSOR_MAX];
- Map<CursorShape, Vector<Variant> > cursors_cache;
- Point2 touches[32];
-
- Point2i last_click_pos;
- double last_click_ms;
- int last_click_button_index;
-
- MainLoop *main_loop;
- int video_driver_index;
- AudioDriverJavaScript audio_driver_javascript;
-
- bool idb_available;
- int64_t sync_wait_time;
- int64_t last_sync_check_time;
-
- static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
-
- static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
- static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
- static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
-
- static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
- static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
-
- static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data);
-
- static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
- static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
-
- static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data);
- void process_joypads();
+ bool finalizing = false;
+ bool idb_available = false;
+ bool idb_needs_sync = false;
static void main_loop_callback();
static void file_access_close_callback(const String &p_file, int p_flags);
protected:
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ void initialize() override;
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual void delete_main_loop();
+ void set_main_loop(MainLoop *p_main_loop) override;
+ void delete_main_loop() override;
- virtual void finalize();
+ void finalize() override;
- virtual bool _check_internal_feature_support(const String &p_feature);
+ bool _check_internal_feature_support(const String &p_feature) override;
public:
+ bool idb_is_syncing = false;
+
// Override return type to make writing static callbacks less tedious.
static OS_JavaScript *get_singleton();
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
- virtual void set_window_size(const Size2);
- virtual Size2 get_window_size() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
-
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
- virtual void set_mouse_mode(MouseMode p_mode);
- virtual MouseMode get_mouse_mode() const;
-
- virtual bool has_touchscreen_ui_hint() const;
-
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
+ void initialize_joypads() override;
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_audio_driver_count() const;
- virtual const char *get_audio_driver_name(int p_driver) const;
-
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
-
- virtual MainLoop *get_main_loop() const;
- void run_async();
+ MainLoop *get_main_loop() const override;
+ void finalize_async();
bool main_loop_iterate();
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
- virtual Error kill(const ProcessID &p_pid);
- virtual int get_process_id() const;
+ Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override;
+ Error kill(const ProcessID &p_pid) override;
+ int get_process_id() const override;
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
- virtual void set_window_title(const String &p_title);
- virtual void set_icon(const Ref<Image> &p_icon);
- String get_executable_path() const;
- virtual Error shell_open(String p_uri);
- virtual String get_name() const;
- virtual bool can_draw() const;
+ String get_executable_path() const override;
+ Error shell_open(String p_uri) override;
+ String get_name() const override;
+ // Override default OS implementation which would block the main thread with delay_usec.
+ // Implemented in javascript_main.cpp loop callback instead.
+ void add_frame_delay(bool p_can_draw) override {}
- virtual String get_resource_dir() const;
- virtual String get_user_data_dir() const;
-
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
+ String get_cache_path() const override;
+ String get_config_path() const override;
+ String get_data_path() const override;
+ String get_user_data_dir() const override;
void set_idb_available(bool p_idb_available);
- virtual bool is_userfs_persistent() const;
+ bool is_userfs_persistent() const override;
+
+ void resume_audio();
+ bool is_finalizing() { return finalizing; }
- OS_JavaScript(int p_argc, char *p_argv[]);
+ OS_JavaScript();
};
#endif
diff --git a/platform/javascript/pre.js b/platform/javascript/pre.js
deleted file mode 100644
index a870e676ea..0000000000
--- a/platform/javascript/pre.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var Engine = {
- RuntimeEnvironment: function(Module, exposedLibs) {
- // The above is concatenated with generated code, and acts as the start of
- // a wrapper for said code. See engine.js for the other part of the
- // wrapper.
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub
new file mode 100644
index 0000000000..ae75a75830
--- /dev/null
+++ b/platform/linuxbsd/SCsub
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+Import("env")
+
+from platform_methods import run_in_subprocess
+import platform_linuxbsd_builders
+
+common_x11 = [
+ "crash_handler_linuxbsd.cpp",
+ "os_linuxbsd.cpp",
+ "joypad_linux.cpp",
+ "context_gl_x11.cpp",
+ "detect_prime_x11.cpp",
+ "display_server_x11.cpp",
+ "vulkan_context_x11.cpp",
+ "key_mapping_x11.cpp",
+]
+
+prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_x11)
+
+if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]:
+ env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd))
diff --git a/platform/x11/context_gl_x11.cpp b/platform/linuxbsd/context_gl_x11.cpp
index 3b88af28e4..71dc9602b3 100644
--- a/platform/x11/context_gl_x11.cpp
+++ b/platform/linuxbsd/context_gl_x11.cpp
@@ -46,22 +46,18 @@
typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
struct ContextGL_X11_Private {
-
::GLXContext glx_context;
};
void ContextGL_X11::release_current() {
-
- glXMakeCurrent(x11_display, None, NULL);
+ glXMakeCurrent(x11_display, None, nullptr);
}
void ContextGL_X11::make_current() {
-
glXMakeCurrent(x11_display, x11_window, p->glx_context);
}
void ContextGL_X11::swap_buffers() {
-
glXSwapBuffers(x11_display, x11_window);
}
@@ -85,7 +81,6 @@ static void set_class_hint(Display *p_display, Window p_window) {
}
Error ContextGL_X11::initialize() {
-
//const char *extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display));
GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB");
@@ -117,7 +112,7 @@ Error ContextGL_X11::initialize() {
int fbcount;
GLXFBConfig fbconfig = 0;
- XVisualInfo *vi = NULL;
+ XVisualInfo *vi = nullptr;
XSetWindowAttributes swa;
swa.event_mask = StructureNotifyMask;
@@ -136,7 +131,7 @@ Error ContextGL_X11::initialize() {
XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual);
if (!pict_format) {
XFree(vi);
- vi = NULL;
+ vi = nullptr;
continue;
}
@@ -166,29 +161,10 @@ Error ContextGL_X11::initialize() {
int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&ctxErrorHandler);
switch (context_type) {
- case OLDSTYLE: {
-
- p->glx_context = glXCreateContext(x11_display, vi, 0, GL_TRUE);
- ERR_FAIL_COND_V(!p->glx_context, ERR_UNCONFIGURED);
- } break;
case GLES_2_0_COMPATIBLE: {
-
p->glx_context = glXCreateNewContext(x11_display, fbconfig, GLX_RGBA_TYPE, 0, true);
ERR_FAIL_COND_V(!p->glx_context, ERR_UNCONFIGURED);
} break;
- case GLES_3_0_COMPATIBLE: {
-
- static int context_attribs[] = {
- GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
- GLX_CONTEXT_MINOR_VERSION_ARB, 3,
- GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
- GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB /*|GLX_CONTEXT_DEBUG_BIT_ARB*/,
- None
- };
-
- p->glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, NULL, true, context_attribs);
- ERR_FAIL_COND_V(ctxErrorOccurred || !p->glx_context, ERR_UNCONFIGURED);
- } break;
}
swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone);
@@ -210,7 +186,6 @@ Error ContextGL_X11::initialize() {
}
int ContextGL_X11::get_window_width() {
-
XWindowAttributes xwa;
XGetWindowAttributes(x11_display, x11_window, &xwa);
@@ -226,9 +201,9 @@ int ContextGL_X11::get_window_height() {
void ContextGL_X11::set_use_vsync(bool p_use) {
static bool setup = false;
- static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = NULL;
- static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalMESA = NULL;
- static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = NULL;
+ static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr;
+ static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalMESA = nullptr;
+ static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = nullptr;
if (!setup) {
setup = true;
@@ -252,14 +227,13 @@ void ContextGL_X11::set_use_vsync(bool p_use) {
return;
use_vsync = p_use;
}
-bool ContextGL_X11::is_using_vsync() const {
+bool ContextGL_X11::is_using_vsync() const {
return use_vsync;
}
ContextGL_X11::ContextGL_X11(::Display *p_x11_display, ::Window &p_x11_window, const OS::VideoMode &p_default_video_mode, ContextType p_context_type) :
x11_window(p_x11_window) {
-
default_video_mode = p_default_video_mode;
x11_display = p_x11_display;
diff --git a/platform/x11/context_gl_x11.h b/platform/linuxbsd/context_gl_x11.h
index 5e5ccc5c86..7aed280c98 100644
--- a/platform/x11/context_gl_x11.h
+++ b/platform/linuxbsd/context_gl_x11.h
@@ -42,18 +42,14 @@
struct ContextGL_X11_Private;
class ContextGL_X11 {
-
public:
enum ContextType {
- OLDSTYLE,
GLES_2_0_COMPATIBLE,
- GLES_3_0_COMPATIBLE
};
private:
ContextGL_X11_Private *p;
OS::VideoMode default_video_mode;
- //::Colormap x11_colormap;
::Display *x11_display;
::Window &x11_window;
bool double_buffer;
diff --git a/platform/x11/crash_handler_x11.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index 19c8f71d0e..e2b88b7704 100644
--- a/platform/x11/crash_handler_x11.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* crash_handler_x11.cpp */
+/* crash_handler_linuxbsd.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "crash_handler_x11.h"
+#include "crash_handler_linuxbsd.h"
#include "core/os/os.h"
#include "core/project_settings.h"
@@ -46,7 +46,7 @@
#include <stdlib.h>
static void handle_crash(int sig) {
- if (OS::get_singleton() == NULL) {
+ if (OS::get_singleton() == nullptr) {
abort();
}
@@ -63,10 +63,11 @@ static void handle_crash(int sig) {
// Dump the backtrace to stderr with a message to the user
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
- if (OS::get_singleton()->get_main_loop())
+ if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
+ }
- fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
+ fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
if (strings) {
for (size_t i = 1; i < size; i++) {
@@ -79,14 +80,15 @@ static void handle_crash(int sig) {
if (dladdr(bt_buffer[i], &info) && info.dli_sname) {
if (info.dli_sname[0] == '_') {
int status;
- char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
+ char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
if (status == 0 && demangled) {
snprintf(fname, 1024, "%s", demangled);
}
- if (demangled)
+ if (demangled) {
free(demangled);
+ }
}
}
@@ -102,12 +104,12 @@ static void handle_crash(int sig) {
// Try to get the file/line number using addr2line
int ret;
- Error err = OS::get_singleton()->execute(String("addr2line"), args, true, NULL, &output, &ret);
+ Error err = OS::get_singleton()->execute(String("addr2line"), args, true, nullptr, &output, &ret);
if (err == OK) {
output.erase(output.length() - 1, 1);
}
- fprintf(stderr, "[%ld] %s (%ls)\n", (long int)i, fname, output.c_str());
+ fprintf(stderr, "[%ld] %s (%s)\n", (long int)i, fname, output.utf8().get_data());
}
free(strings);
@@ -128,13 +130,14 @@ CrashHandler::~CrashHandler() {
}
void CrashHandler::disable() {
- if (disabled)
+ if (disabled) {
return;
+ }
#ifdef CRASH_HANDLER_ENABLED
- signal(SIGSEGV, NULL);
- signal(SIGFPE, NULL);
- signal(SIGILL, NULL);
+ signal(SIGSEGV, nullptr);
+ signal(SIGFPE, nullptr);
+ signal(SIGILL, nullptr);
#endif
disabled = true;
diff --git a/platform/x11/crash_handler_x11.h b/platform/linuxbsd/crash_handler_linuxbsd.h
index 98620cc789..9bb03579bc 100644
--- a/platform/x11/crash_handler_x11.h
+++ b/platform/linuxbsd/crash_handler_linuxbsd.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* crash_handler_x11.h */
+/* crash_handler_linuxbsd.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -32,7 +32,6 @@
#define CRASH_HANDLER_X11_H
class CrashHandler {
-
bool disabled;
public:
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
new file mode 100644
index 0000000000..f5e2c72bbc
--- /dev/null
+++ b/platform/linuxbsd/detect.py
@@ -0,0 +1,385 @@
+import os
+import platform
+import sys
+
+
+def is_active():
+ return True
+
+
+def get_name():
+ return "LinuxBSD"
+
+
+def can_build():
+
+ if os.name != "posix" or sys.platform == "darwin":
+ return False
+
+ # Check the minimal dependencies
+ x11_error = os.system("pkg-config --version > /dev/null")
+ if x11_error:
+ return False
+
+ x11_error = os.system("pkg-config x11 --modversion > /dev/null ")
+ if x11_error:
+ return False
+
+ x11_error = os.system("pkg-config xcursor --modversion > /dev/null ")
+ if x11_error:
+ print("xcursor not found.. x11 disabled.")
+ return False
+
+ x11_error = os.system("pkg-config xinerama --modversion > /dev/null ")
+ if x11_error:
+ print("xinerama not found.. x11 disabled.")
+ return False
+
+ x11_error = os.system("pkg-config xext --modversion > /dev/null ")
+ if x11_error:
+ print("xext not found.. x11 disabled.")
+ return False
+
+ x11_error = os.system("pkg-config xrandr --modversion > /dev/null ")
+ if x11_error:
+ print("xrandr not found.. x11 disabled.")
+ return False
+
+ x11_error = os.system("pkg-config xrender --modversion > /dev/null ")
+ if x11_error:
+ print("xrender not found.. x11 disabled.")
+ return False
+
+ x11_error = os.system("pkg-config xi --modversion > /dev/null ")
+ if x11_error:
+ print("xi not found.. Aborting.")
+ return False
+
+ return True
+
+
+def get_opts():
+ from SCons.Variables import BoolVariable, EnumVariable
+
+ return [
+ BoolVariable("use_llvm", "Use the LLVM compiler", False),
+ BoolVariable("use_lld", "Use the LLD linker", False),
+ BoolVariable("use_thinlto", "Use ThinLTO", False),
+ BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", False),
+ BoolVariable("use_coverage", "Test Godot coverage", False),
+ BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False),
+ BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False),
+ BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False),
+ BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False),
+ BoolVariable("pulseaudio", "Detect and use PulseAudio", True),
+ BoolVariable("udev", "Use udev for gamepad connection callbacks", False),
+ EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
+ BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
+ BoolVariable("touch", "Enable touch events", True),
+ BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
+ ]
+
+
+def get_flags():
+
+ return []
+
+
+def configure(env):
+
+ ## Build type
+
+ if env["target"] == "release":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O3"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "release_debug":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O2"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "debug":
+ env.Prepend(CCFLAGS=["-g3"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["-rdynamic"])
+
+ ## Architecture
+
+ is64 = sys.maxsize > 2 ** 32
+ if env["bits"] == "default":
+ env["bits"] = "64" if is64 else "32"
+
+ ## Compiler configuration
+
+ if "CXX" in env and "clang" in os.path.basename(env["CXX"]):
+ # Convenience check to enforce the use_llvm overrides when CXX is clang(++)
+ env["use_llvm"] = True
+
+ if env["use_llvm"]:
+ if "clang++" not in os.path.basename(env["CXX"]):
+ env["CC"] = "clang"
+ env["CXX"] = "clang++"
+ env["LINK"] = "clang++"
+ env.Append(CPPDEFINES=["TYPED_METHOD_BIND"])
+ env.extra_suffix = ".llvm" + env.extra_suffix
+
+ if env["use_lld"]:
+ if env["use_llvm"]:
+ env.Append(LINKFLAGS=["-fuse-ld=lld"])
+ if env["use_thinlto"]:
+ # A convenience so you don't need to write use_lto too when using SCons
+ env["use_lto"] = True
+ else:
+ print("Using LLD with GCC is not supported yet, try compiling with 'use_llvm=yes'.")
+ sys.exit(255)
+
+ if env["use_coverage"]:
+ env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"])
+ env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"])
+
+ if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]:
+ env.extra_suffix += "s"
+
+ if env["use_ubsan"]:
+ env.Append(CCFLAGS=["-fsanitize=undefined"])
+ env.Append(LINKFLAGS=["-fsanitize=undefined"])
+
+ if env["use_asan"]:
+ env.Append(CCFLAGS=["-fsanitize=address"])
+ env.Append(LINKFLAGS=["-fsanitize=address"])
+
+ if env["use_lsan"]:
+ env.Append(CCFLAGS=["-fsanitize=leak"])
+ env.Append(LINKFLAGS=["-fsanitize=leak"])
+
+ if env["use_tsan"]:
+ env.Append(CCFLAGS=["-fsanitize=thread"])
+ env.Append(LINKFLAGS=["-fsanitize=thread"])
+
+ if env["use_lto"]:
+ if not env["use_llvm"] and env.GetOption("num_jobs") > 1:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))])
+ else:
+ if env["use_lld"] and env["use_thinlto"]:
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+ else:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
+
+ if not env["use_llvm"]:
+ env["RANLIB"] = "gcc-ranlib"
+ env["AR"] = "gcc-ar"
+
+ env.Append(CCFLAGS=["-pipe"])
+ env.Append(LINKFLAGS=["-pipe"])
+
+ # -fpie and -no-pie is supported on GCC 6+ and Clang 4+, both below our
+ # minimal requirements.
+ env.Append(CCFLAGS=["-fpie"])
+ env.Append(LINKFLAGS=["-no-pie"])
+
+ ## Dependencies
+
+ env.ParseConfig("pkg-config x11 --cflags --libs")
+ env.ParseConfig("pkg-config xcursor --cflags --libs")
+ env.ParseConfig("pkg-config xinerama --cflags --libs")
+ env.ParseConfig("pkg-config xext --cflags --libs")
+ env.ParseConfig("pkg-config xrandr --cflags --libs")
+ env.ParseConfig("pkg-config xrender --cflags --libs")
+ env.ParseConfig("pkg-config xi --cflags --libs")
+
+ if env["touch"]:
+ env.Append(CPPDEFINES=["TOUCH_ENABLED"])
+
+ # FIXME: Check for existence of the libs before parsing their flags with pkg-config
+
+ # freetype depends on libpng and zlib, so bundling one of them while keeping others
+ # as shared libraries leads to weird issues
+ if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]:
+ env["builtin_freetype"] = True
+ env["builtin_libpng"] = True
+ env["builtin_zlib"] = True
+
+ if not env["builtin_freetype"]:
+ env.ParseConfig("pkg-config freetype2 --cflags --libs")
+
+ if not env["builtin_libpng"]:
+ env.ParseConfig("pkg-config libpng16 --cflags --libs")
+
+ if not env["builtin_bullet"]:
+ # We need at least version 2.90
+ min_bullet_version = "2.90"
+
+ import subprocess
+
+ bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip()
+ if str(bullet_version) < min_bullet_version:
+ # Abort as system bullet was requested but too old
+ print(
+ "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(
+ bullet_version, min_bullet_version
+ )
+ )
+ sys.exit(255)
+ env.ParseConfig("pkg-config bullet --cflags --libs")
+
+ if False: # not env['builtin_assimp']:
+ # FIXME: Add min version check
+ env.ParseConfig("pkg-config assimp --cflags --libs")
+
+ if not env["builtin_enet"]:
+ env.ParseConfig("pkg-config libenet --cflags --libs")
+
+ if not env["builtin_squish"]:
+ env.ParseConfig("pkg-config libsquish --cflags --libs")
+
+ if not env["builtin_zstd"]:
+ env.ParseConfig("pkg-config libzstd --cflags --libs")
+
+ # Sound and video libraries
+ # Keep the order as it triggers chained dependencies (ogg needed by others, etc.)
+
+ if not env["builtin_libtheora"]:
+ env["builtin_libogg"] = False # Needed to link against system libtheora
+ env["builtin_libvorbis"] = False # Needed to link against system libtheora
+ env.ParseConfig("pkg-config theora theoradec --cflags --libs")
+ else:
+ list_of_x86 = ["x86_64", "x86", "i386", "i586"]
+ if any(platform.machine() in s for s in list_of_x86):
+ env["x86_libtheora_opt_gcc"] = True
+
+ if not env["builtin_libvpx"]:
+ env.ParseConfig("pkg-config vpx --cflags --libs")
+
+ if not env["builtin_libvorbis"]:
+ env["builtin_libogg"] = False # Needed to link against system libvorbis
+ env.ParseConfig("pkg-config vorbis vorbisfile --cflags --libs")
+
+ if not env["builtin_opus"]:
+ env["builtin_libogg"] = False # Needed to link against system opus
+ env.ParseConfig("pkg-config opus opusfile --cflags --libs")
+
+ if not env["builtin_libogg"]:
+ env.ParseConfig("pkg-config ogg --cflags --libs")
+
+ if not env["builtin_libwebp"]:
+ env.ParseConfig("pkg-config libwebp --cflags --libs")
+
+ if not env["builtin_mbedtls"]:
+ # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228
+ env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"])
+
+ if not env["builtin_wslay"]:
+ env.ParseConfig("pkg-config libwslay --cflags --libs")
+
+ if not env["builtin_miniupnpc"]:
+ # No pkgconfig file so far, hardcode default paths.
+ env.Prepend(CPPPATH=["/usr/include/miniupnpc"])
+ env.Append(LIBS=["miniupnpc"])
+
+ # On Linux wchar_t should be 32-bits
+ # 16-bit library shouldn't be required due to compiler optimisations
+ if not env["builtin_pcre2"]:
+ env.ParseConfig("pkg-config libpcre2-32 --cflags --libs")
+
+ ## Flags
+
+ if os.system("pkg-config --exists alsa") == 0: # 0 means found
+ print("Enabling ALSA")
+ env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
+ # Don't parse --cflags, we don't need to add /usr/include/alsa to include path
+ env.ParseConfig("pkg-config alsa --libs")
+ else:
+ print("ALSA libraries not found, disabling driver")
+
+ if env["pulseaudio"]:
+ if os.system("pkg-config --exists libpulse") == 0: # 0 means found
+ print("Enabling PulseAudio")
+ env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
+ env.ParseConfig("pkg-config --cflags --libs libpulse")
+ else:
+ print("PulseAudio development libraries not found, disabling driver")
+
+ if platform.system() == "Linux":
+ env.Append(CPPDEFINES=["JOYDEV_ENABLED"])
+
+ if env["udev"]:
+ if os.system("pkg-config --exists libudev") == 0: # 0 means found
+ print("Enabling udev support")
+ env.Append(CPPDEFINES=["UDEV_ENABLED"])
+ env.ParseConfig("pkg-config libudev --cflags --libs")
+ else:
+ print("libudev development libraries not found, disabling udev support")
+
+ # Linkflags below this line should typically stay the last ones
+ if not env["builtin_zlib"]:
+ env.ParseConfig("pkg-config zlib --cflags --libs")
+
+ env.Prepend(CPPPATH=["#platform/linuxbsd"])
+ env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED"])
+
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ if not env["builtin_vulkan"]:
+ env.ParseConfig("pkg-config vulkan --cflags --libs")
+ if not env["builtin_glslang"]:
+ # No pkgconfig file for glslang so far
+ env.Append(LIBS=["glslang", "SPIRV"])
+
+ # env.Append(CPPDEFINES=['OPENGL_ENABLED'])
+ env.Append(LIBS=["GL"])
+
+ env.Append(LIBS=["pthread"])
+
+ if platform.system() == "Linux":
+ env.Append(LIBS=["dl"])
+
+ if platform.system().find("BSD") >= 0:
+ env["execinfo"] = True
+
+ if env["execinfo"]:
+ env.Append(LIBS=["execinfo"])
+
+ if not env["tools"]:
+ import subprocess
+ import re
+
+ linker_version_str = subprocess.check_output([env.subst(env["LINK"]), "-Wl,--version"]).decode("utf-8")
+ gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE)
+ if not gnu_ld_version:
+ print(
+ "Warning: Creating template binaries enabled for PCK embedding is currently only supported with GNU ld"
+ )
+ else:
+ if float(gnu_ld_version.group(1)) >= 2.30:
+ env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.ld"])
+ else:
+ env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"])
+
+ ## Cross-compilation
+
+ if is64 and env["bits"] == "32":
+ env.Append(CCFLAGS=["-m32"])
+ env.Append(LINKFLAGS=["-m32", "-L/usr/lib/i386-linux-gnu"])
+ elif not is64 and env["bits"] == "64":
+ env.Append(CCFLAGS=["-m64"])
+ env.Append(LINKFLAGS=["-m64", "-L/usr/lib/i686-linux-gnu"])
+
+ # Link those statically for portability
+ if env["use_static_cpp"]:
+ env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"])
diff --git a/platform/x11/detect_prime.cpp b/platform/linuxbsd/detect_prime_x11.cpp
index 98a51ff27c..1e46d3222d 100644
--- a/platform/x11/detect_prime.cpp
+++ b/platform/linuxbsd/detect_prime_x11.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* detect_prime.cpp */
+/* detect_prime_x11.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -31,6 +31,8 @@
#ifdef X11_ENABLED
#if defined(OPENGL_ENABLED)
+#include "detect_prime.h"
+
#include "core/print_string.h"
#include "core/ustring.h"
@@ -62,14 +64,15 @@ vendor vendormap[] = {
{ "NVIDIA Corporation", 30 },
{ "X.Org", 30 },
{ "Intel Open Source Technology Center", 20 },
+ { "Intel", 20 },
{ "nouveau", 10 },
{ "Mesa Project", 0 },
- { NULL, 0 }
+ { nullptr, 0 }
};
// Runs inside a child. Exiting will not quit the engine.
void create_context() {
- Display *x11_display = XOpenDisplay(NULL);
+ Display *x11_display = XOpenDisplay(nullptr);
Window x11_window;
GLXContext glx_context;
@@ -88,7 +91,7 @@ void create_context() {
int fbcount;
GLXFBConfig fbconfig = 0;
- XVisualInfo *vi = NULL;
+ XVisualInfo *vi = nullptr;
XSetWindowAttributes swa;
swa.event_mask = StructureNotifyMask;
@@ -111,7 +114,7 @@ void create_context() {
None
};
- glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, NULL, true, context_attribs);
+ glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs);
swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone);
x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, 10, 10, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa);
@@ -175,7 +178,8 @@ int detect_prime() {
close(fdset[0]);
- if (i) setenv("DRI_PRIME", "1", 1);
+ if (i)
+ setenv("DRI_PRIME", "1", 1);
create_context();
const char *vendor = (const char *)glGetString(GL_VENDOR);
diff --git a/platform/x11/detect_prime.h b/platform/linuxbsd/detect_prime_x11.h
index df636449f4..039bdee76b 100644
--- a/platform/x11/detect_prime.h
+++ b/platform/linuxbsd/detect_prime_x11.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* detect_prime.h */
+/* detect_prime_x11.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
new file mode 100644
index 0000000000..e3cf992302
--- /dev/null
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -0,0 +1,3991 @@
+/*************************************************************************/
+/* display_server_x11.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_x11.h"
+
+#ifdef X11_ENABLED
+
+#include "core/print_string.h"
+#include "core/project_settings.h"
+#include "detect_prime_x11.h"
+#include "key_mapping_x11.h"
+#include "main/main.h"
+#include "scene/resources/texture.h"
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xinerama.h>
+#include <X11/extensions/shape.h>
+
+// ICCCM
+#define WM_NormalState 1L // window normal state
+#define WM_IconicState 3L // window minimized
+// EWMH
+#define _NET_WM_STATE_REMOVE 0L // remove/unset property
+#define _NET_WM_STATE_ADD 1L // add/set property
+#define _NET_WM_STATE_TOGGLE 2L // toggle property
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+//stupid linux.h
+#ifdef KEY_TAB
+#undef KEY_TAB
+#endif
+
+#undef CursorShape
+#include <X11/XKBlib.h>
+
+// 2.2 is the first release with multitouch
+#define XINPUT_CLIENT_VERSION_MAJOR 2
+#define XINPUT_CLIENT_VERSION_MINOR 2
+
+#define VALUATOR_ABSX 0
+#define VALUATOR_ABSY 1
+#define VALUATOR_PRESSURE 2
+#define VALUATOR_TILTX 3
+#define VALUATOR_TILTY 4
+
+//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
+#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
+#define DEBUG_LOG_X11(...) printf(__VA_ARGS__)
+#else
+#define DEBUG_LOG_X11(...)
+#endif
+
+static const double abs_resolution_mult = 10000.0;
+static const double abs_resolution_range_mult = 10.0;
+
+bool DisplayServerX11::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_SUBWINDOWS:
+#ifdef TOUCH_ENABLED
+ case FEATURE_TOUCHSCREEN:
+#endif
+ case FEATURE_MOUSE:
+ case FEATURE_MOUSE_WARP:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
+ case FEATURE_IME:
+ case FEATURE_WINDOW_TRANSPARENCY:
+ //case FEATURE_HIDPI:
+ case FEATURE_ICON:
+ case FEATURE_NATIVE_ICON:
+ case FEATURE_SWAP_BUFFERS:
+ return true;
+ default: {
+ }
+ }
+
+ return false;
+}
+
+String DisplayServerX11::get_name() const {
+ return "X11";
+}
+
+void DisplayServerX11::alert(const String &p_alert, const String &p_title) {
+ const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" };
+
+ String path = OS::get_singleton()->get_environment("PATH");
+ Vector<String> path_elems = path.split(":", false);
+ String program;
+
+ for (int i = 0; i < path_elems.size(); i++) {
+ for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) {
+ String tested_path = path_elems[i].plus_file(message_programs[k]);
+
+ if (FileAccess::exists(tested_path)) {
+ program = tested_path;
+ break;
+ }
+ }
+
+ if (program.length()) {
+ break;
+ }
+ }
+
+ List<String> args;
+
+ if (program.ends_with("zenity")) {
+ args.push_back("--error");
+ args.push_back("--width");
+ args.push_back("500");
+ args.push_back("--title");
+ args.push_back(p_title);
+ args.push_back("--text");
+ args.push_back(p_alert);
+ }
+
+ if (program.ends_with("kdialog")) {
+ args.push_back("--error");
+ args.push_back(p_alert);
+ args.push_back("--title");
+ args.push_back(p_title);
+ }
+
+ if (program.ends_with("Xdialog")) {
+ args.push_back("--title");
+ args.push_back(p_title);
+ args.push_back("--msgbox");
+ args.push_back(p_alert);
+ args.push_back("0");
+ args.push_back("0");
+ }
+
+ if (program.ends_with("xmessage")) {
+ args.push_back("-center");
+ args.push_back("-title");
+ args.push_back(p_title);
+ args.push_back(p_alert);
+ }
+
+ if (program.length()) {
+ OS::get_singleton()->execute(program, args, true);
+ } else {
+ print_line(p_alert);
+ }
+}
+
+void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) {
+ Window root_return, child_return;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask_return;
+
+ Bool xquerypointer_result = XQueryPointer(x11_display, wd.x11_window, &root_return, &child_return, &root_x, &root_y,
+ &win_x, &win_y, &mask_return);
+
+ if (xquerypointer_result) {
+ if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) {
+ last_mouse_pos.x = win_x;
+ last_mouse_pos.y = win_y;
+ last_mouse_pos_valid = true;
+ Input::get_singleton()->set_mouse_position(last_mouse_pos);
+ }
+ }
+}
+
+bool DisplayServerX11::_refresh_device_info() {
+ int event_base, error_base;
+
+ print_verbose("XInput: Refreshing devices.");
+
+ if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {
+ print_verbose("XInput extension not available. Please upgrade your distribution.");
+ return false;
+ }
+
+ int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;
+ int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;
+
+ if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {
+ print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));
+ xi.opcode = 0;
+ return false;
+ }
+
+ if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {
+ print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",
+ XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));
+ }
+
+ xi.absolute_devices.clear();
+ xi.touch_devices.clear();
+
+ int dev_count;
+ XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
+
+ for (int i = 0; i < dev_count; i++) {
+ XIDeviceInfo *dev = &info[i];
+ if (!dev->enabled) {
+ continue;
+ }
+ if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) {
+ continue;
+ }
+
+ bool direct_touch = false;
+ bool absolute_mode = false;
+ int resolution_x = 0;
+ int resolution_y = 0;
+ double abs_x_min = 0;
+ double abs_x_max = 0;
+ double abs_y_min = 0;
+ double abs_y_max = 0;
+ double pressure_min = 0;
+ double pressure_max = 0;
+ double tilt_x_min = 0;
+ double tilt_x_max = 0;
+ double tilt_y_min = 0;
+ double tilt_y_max = 0;
+ for (int j = 0; j < dev->num_classes; j++) {
+#ifdef TOUCH_ENABLED
+ if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
+ direct_touch = true;
+ }
+#endif
+ if (dev->classes[j]->type == XIValuatorClass) {
+ XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];
+
+ if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {
+ resolution_x = class_info->resolution;
+ abs_x_min = class_info->min;
+ abs_y_max = class_info->max;
+ absolute_mode = true;
+ } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {
+ resolution_y = class_info->resolution;
+ abs_y_min = class_info->min;
+ abs_y_max = class_info->max;
+ absolute_mode = true;
+ } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {
+ pressure_min = class_info->min;
+ pressure_max = class_info->max;
+ } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {
+ tilt_x_min = class_info->min;
+ tilt_x_max = class_info->max;
+ } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {
+ tilt_x_min = class_info->min;
+ tilt_x_max = class_info->max;
+ }
+ }
+ }
+ if (direct_touch) {
+ xi.touch_devices.push_back(dev->deviceid);
+ print_verbose("XInput: Using touch device: " + String(dev->name));
+ }
+ if (absolute_mode) {
+ // If no resolution was reported, use the min/max ranges.
+ if (resolution_x <= 0) {
+ resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult;
+ }
+ if (resolution_y <= 0) {
+ resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult;
+ }
+ xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);
+ print_verbose("XInput: Absolute pointing device: " + String(dev->name));
+ }
+
+ xi.pressure = 0;
+ xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max);
+ xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max);
+ xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max);
+ }
+
+ XIFreeDeviceInfo(info);
+#ifdef TOUCH_ENABLED
+ if (!xi.touch_devices.size()) {
+ print_verbose("XInput: No touch devices found.");
+ }
+#endif
+
+ return true;
+}
+
+void DisplayServerX11::_flush_mouse_motion() {
+ while (true) {
+ if (XPending(x11_display) > 0) {
+ XEvent event;
+ XPeekEvent(x11_display, &event);
+
+ if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
+ XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
+
+ if (event_data->evtype == XI_RawMotion) {
+ XNextEvent(x11_display, &event);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ xi.relative_motion.x = 0;
+ xi.relative_motion.y = 0;
+}
+
+void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) {
+ XUngrabPointer(x11_display, CurrentTime);
+ }
+
+ // The only modes that show a cursor are VISIBLE and CONFINED
+ bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (showCursor) {
+ XDefineCursor(x11_display, E->get().x11_window, cursors[current_cursor]); // show cursor
+ } else {
+ XDefineCursor(x11_display, E->get().x11_window, null_cursor); // hide cursor
+ }
+ }
+ mouse_mode = p_mode;
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) {
+ //flush pending motion events
+ _flush_mouse_motion();
+ WindowData &main_window = windows[MAIN_WINDOW_ID];
+
+ if (XGrabPointer(
+ x11_display, main_window.x11_window, True,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, windows[MAIN_WINDOW_ID].x11_window, None, CurrentTime) != GrabSuccess) {
+ ERR_PRINT("NO GRAB");
+ }
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ center.x = main_window.size.width / 2;
+ center.y = main_window.size.height / 2;
+
+ XWarpPointer(x11_display, None, main_window.x11_window,
+ 0, 0, 0, 0, (int)center.x, (int)center.y);
+
+ Input::get_singleton()->set_mouse_position(center);
+ }
+ } else {
+ do_mouse_warp = false;
+ }
+
+ XFlush(x11_display);
+}
+
+DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const {
+ return mouse_mode;
+}
+
+void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) {
+ _THREAD_SAFE_METHOD_
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ last_mouse_pos = p_to;
+ } else {
+ XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,
+ 0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
+ }
+}
+
+Point2i DisplayServerX11::mouse_get_position() const {
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask_return;
+ Window window_returned;
+
+ Bool result = XQueryPointer(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), &window_returned,
+ &window_returned, &root_x, &root_y, &win_x, &win_y,
+ &mask_return);
+ if (result == True) {
+ return Point2i(root_x, root_y);
+ }
+ return Point2i();
+}
+
+Point2i DisplayServerX11::mouse_get_absolute_position() const {
+ int number_of_screens = XScreenCount(x11_display);
+ for (int i = 0; i < number_of_screens; i++) {
+ Window root, child;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
+ XWindowAttributes root_attrs;
+ XGetWindowAttributes(x11_display, root, &root_attrs);
+
+ return Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
+ }
+ }
+ return Vector2i();
+}
+
+int DisplayServerX11::mouse_get_button_state() const {
+ return last_button_state;
+}
+
+void DisplayServerX11::clipboard_set(const String &p_text) {
+ _THREAD_SAFE_METHOD_
+
+ internal_clipboard = p_text;
+ XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
+ XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
+}
+
+static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) {
+ String ret;
+
+ Atom type;
+ Atom selection = XA_PRIMARY;
+ int format, result;
+ unsigned long len, bytes_left, dummy;
+ unsigned char *data;
+ Window Sown = XGetSelectionOwner(x11_display, p_source);
+
+ if (Sown == x11_window) {
+ return p_internal_clipboard;
+ };
+
+ if (Sown != None) {
+ XConvertSelection(x11_display, p_source, target, selection,
+ x11_window, CurrentTime);
+ XFlush(x11_display);
+ while (true) {
+ XEvent event;
+ XNextEvent(x11_display, &event);
+ if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
+ break;
+ };
+ };
+
+ //
+ // Do not get any data, see how much data is there
+ //
+ XGetWindowProperty(x11_display, x11_window,
+ selection, // Tricky..
+ 0, 0, // offset - len
+ 0, // Delete 0==FALSE
+ AnyPropertyType, //flag
+ &type, // return type
+ &format, // return format
+ &len, &bytes_left, //that
+ &data);
+ // DATA is There
+ if (bytes_left > 0) {
+ result = XGetWindowProperty(x11_display, x11_window,
+ selection, 0, bytes_left, 0,
+ AnyPropertyType, &type, &format,
+ &len, &dummy, &data);
+ if (result == Success) {
+ ret.parse_utf8((const char *)data);
+ } else {
+ printf("FAIL\n");
+ }
+ if (data) {
+ XFree(data);
+ }
+ }
+ }
+
+ return ret;
+}
+
+static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) {
+ String ret;
+ Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
+ if (utf8_atom != None) {
+ ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom);
+ }
+ if (ret == "") {
+ ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING);
+ }
+ return ret;
+}
+
+String DisplayServerX11::clipboard_get() const {
+ _THREAD_SAFE_METHOD_
+
+ String ret;
+ ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard);
+
+ if (ret == "") {
+ ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard);
+ };
+
+ return ret;
+}
+
+int DisplayServerX11::get_screen_count() const {
+ _THREAD_SAFE_METHOD_
+
+ // Using Xinerama Extension
+ int event_base, error_base;
+ const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
+ if (!ext_okay) {
+ return 0;
+ }
+
+ int count;
+ XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
+ XFree(xsi);
+ return count;
+}
+
+Point2i DisplayServerX11::screen_get_position(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ // Using Xinerama Extension
+ int event_base, error_base;
+ const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
+ if (!ext_okay) {
+ return Point2i(0, 0);
+ }
+
+ int count;
+ XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
+ if (p_screen >= count) {
+ return Point2i(0, 0);
+ }
+
+ Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org);
+
+ XFree(xsi);
+
+ return position;
+}
+
+Size2i DisplayServerX11::screen_get_size(int p_screen) const {
+ return screen_get_usable_rect(p_screen).size;
+}
+
+Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ // Using Xinerama Extension
+ int event_base, error_base;
+ const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
+ if (!ext_okay) {
+ return Rect2i(0, 0, 0, 0);
+ }
+
+ int count;
+ XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
+ if (p_screen >= count) {
+ return Rect2i(0, 0, 0, 0);
+ }
+
+ Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height);
+ XFree(xsi);
+ return rect;
+}
+
+int DisplayServerX11::screen_get_dpi(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ //invalid screen?
+ ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0);
+
+ //Get physical monitor Dimensions through XRandR and calculate dpi
+ Size2i sc = screen_get_size(p_screen);
+ if (xrandr_ext_ok) {
+ int count = 0;
+ if (xrr_get_monitors) {
+ xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
+ if (p_screen < count) {
+ double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;
+ double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;
+ xrr_free_monitors(monitors);
+ return (xdpi + ydpi) / 2;
+ }
+ xrr_free_monitors(monitors);
+ } else if (p_screen == 0) {
+ XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);
+ if (sizes) {
+ double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;
+ double ydpi = sc.height / (double)sizes[0].mheight * 25.4;
+ return (xdpi + ydpi) / 2;
+ }
+ }
+ }
+
+ int width_mm = DisplayWidthMM(x11_display, p_screen);
+ int height_mm = DisplayHeightMM(x11_display, p_screen);
+ double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);
+ double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);
+ if (xdpi || ydpi) {
+ return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
+ }
+
+ //could not get dpi
+ return 96;
+}
+
+bool DisplayServerX11::screen_is_touchscreen(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+#ifndef _MSC_VER
+#warning Need to get from proper window
+#endif
+
+ return DisplayServer::screen_is_touchscreen(p_screen);
+}
+
+Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {
+ _THREAD_SAFE_METHOD_
+
+ Vector<int> ret;
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+
+DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ WindowID id = _create_window(p_mode, p_flags, p_rect);
+ for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+ if (p_flags & (1 << i)) {
+ window_set_flag(WindowFlags(i), true, id);
+ }
+ }
+
+ return id;
+}
+
+void DisplayServerX11::show_window(WindowID p_id) {
+ _THREAD_SAFE_METHOD_
+
+ WindowData &wd = windows[p_id];
+
+ XMapWindow(x11_display, wd.x11_window);
+}
+
+void DisplayServerX11::delete_sub_window(WindowID p_id) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_id));
+ ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); //ma
+
+ WindowData &wd = windows[p_id];
+
+ DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id);
+
+ while (wd.transient_children.size()) {
+ window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);
+ }
+
+ if (wd.transient_parent != INVALID_WINDOW_ID) {
+ window_set_transient(p_id, INVALID_WINDOW_ID);
+ }
+
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_destroy(p_id);
+ }
+#endif
+ XUnmapWindow(x11_display, wd.x11_window);
+ XDestroyWindow(x11_display, wd.x11_window);
+ if (wd.xic) {
+ XDestroyIC(wd.xic);
+ }
+
+ windows.erase(p_id);
+}
+
+void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.instance_id = p_instance;
+}
+
+ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const {
+ ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
+ const WindowData &wd = windows[p_window];
+ return wd.instance_id;
+}
+
+DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {
+ WindowID found_window = INVALID_WINDOW_ID;
+ WindowID parent_window = INVALID_WINDOW_ID;
+ unsigned int focus_order = 0;
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ const WindowData &wd = E->get();
+
+ // Discard windows with no focus.
+ if (wd.focus_order == 0) {
+ continue;
+ }
+
+ // Find topmost window which contains the given position.
+ WindowID window_id = E->key();
+ Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));
+ if (win_rect.has_point(p_position)) {
+ // For siblings, pick the window which was focused last.
+ if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {
+ found_window = window_id;
+ parent_window = wd.transient_parent;
+ focus_order = wd.focus_order;
+ }
+ }
+ }
+
+ return found_window;
+}
+
+void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ XStoreName(x11_display, wd.x11_window, p_title.utf8().get_data());
+
+ Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
+ Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
+ XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
+}
+
+void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ const WindowData &wd = windows[p_window];
+
+ int event_base, error_base;
+ const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base);
+ if (ext_okay) {
+ Region region;
+ if (p_region.size() == 0) {
+ region = XCreateRegion();
+ XRectangle rect;
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = window_get_real_size(p_window).x;
+ rect.height = window_get_real_size(p_window).y;
+ XUnionRectWithRegion(&rect, region, region);
+ } else {
+ XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size());
+ for (int i = 0; i < p_region.size(); i++) {
+ points[i].x = p_region[i].x;
+ points[i].y = p_region[i].y;
+ }
+ region = XPolygonRegion(points, p_region.size(), EvenOddRule);
+ memfree(points);
+ }
+ XShapeCombineRegion(x11_display, wd.x11_window, ShapeInput, 0, 0, region, ShapeSet);
+ XDestroyRegion(region);
+ }
+}
+
+void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.rect_changed_callback = p_callable;
+}
+
+void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.event_callback = p_callable;
+}
+
+void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.input_event_callback = p_callable;
+}
+
+void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.input_text_callback = p_callable;
+}
+
+void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.drop_files_callback = p_callable;
+}
+
+int DisplayServerX11::window_get_current_screen(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+ const WindowData &wd = windows[p_window];
+
+ int x, y;
+ Window child;
+ XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
+
+ int count = get_screen_count();
+ for (int i = 0; i < count; i++) {
+ Point2i pos = screen_get_position(i);
+ Size2i size = screen_get_size(i);
+ if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height)) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ int count = get_screen_count();
+ if (p_screen >= count) {
+ return;
+ }
+
+ if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) {
+ Point2i position = screen_get_position(p_screen);
+ Size2i size = screen_get_size(p_screen);
+
+ XMoveResizeWindow(x11_display, wd.x11_window, position.x, position.y, size.x, size.y);
+ } else {
+ if (p_screen != window_get_current_screen(p_window)) {
+ Point2i position = screen_get_position(p_screen);
+ XMoveWindow(x11_display, wd.x11_window, position.x, position.y);
+ }
+ }
+}
+
+void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(p_window == p_parent);
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd_window = windows[p_window];
+
+ WindowID prev_parent = wd_window.transient_parent;
+ ERR_FAIL_COND(prev_parent == p_parent);
+
+ ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
+ if (p_parent == INVALID_WINDOW_ID) {
+ //remove transient
+
+ ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID);
+ ERR_FAIL_COND(!windows.has(prev_parent));
+
+ WindowData &wd_parent = windows[prev_parent];
+
+ wd_window.transient_parent = INVALID_WINDOW_ID;
+ wd_parent.transient_children.erase(p_window);
+
+ XSetTransientForHint(x11_display, wd_window.x11_window, None);
+
+ // Set focus to parent sub window to avoid losing all focus with nested menus.
+ // RevertToPointerRoot is used to make sure we don't lose all focus in case
+ // a subwindow and its parent are both destroyed.
+ if (wd_window.menu_type && !wd_window.no_focus) {
+ if (!wd_parent.no_focus) {
+ XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
+ }
+ }
+ } else {
+ ERR_FAIL_COND(!windows.has(p_parent));
+ ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
+ WindowData &wd_parent = windows[p_parent];
+
+ wd_window.transient_parent = p_parent;
+ wd_parent.transient_children.insert(p_window);
+
+ XSetTransientForHint(x11_display, wd_window.x11_window, wd_parent.x11_window);
+ }
+}
+
+// Helper method. Assumes that the window id has already been checked and exists.
+void DisplayServerX11::_update_size_hints(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ WindowMode window_mode = window_get_mode(p_window);
+ XSizeHints *xsh = XAllocSizeHints();
+
+ // Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway
+ xsh->flags |= PPosition | PSize;
+ xsh->x = wd.position.x;
+ xsh->y = wd.position.y;
+ xsh->width = wd.size.width;
+ xsh->height = wd.size.height;
+
+ if (window_mode == WINDOW_MODE_FULLSCREEN) {
+ // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags
+ } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) {
+ // If resizing is disabled, use the forced size
+ xsh->flags |= PMinSize | PMaxSize;
+ xsh->min_width = wd.size.x;
+ xsh->max_width = wd.size.x;
+ xsh->min_height = wd.size.y;
+ xsh->max_height = wd.size.y;
+ } else {
+ // Otherwise, just respect min_size and max_size
+ if (wd.min_size != Size2i()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = wd.min_size.x;
+ xsh->min_height = wd.min_size.y;
+ }
+ if (wd.max_size != Size2i()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = wd.max_size.x;
+ xsh->max_height = wd.max_size.y;
+ }
+ }
+
+ XSetWMNormalHints(x11_display, wd.x11_window, xsh);
+ XFree(xsh);
+}
+
+Point2i DisplayServerX11::window_get_position(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
+ const WindowData &wd = windows[p_window];
+
+ return wd.position;
+}
+
+void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ int x = 0;
+ int y = 0;
+ if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {
+ //exclude window decorations
+ XSync(x11_display, False);
+ Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
+ if (prop != None) {
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = nullptr;
+ if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (format == 32 && len == 4 && data) {
+ long *extents = (long *)data;
+ x = extents[0];
+ y = extents[2];
+ }
+ XFree(data);
+ }
+ }
+ }
+ XMoveWindow(x11_display, wd.x11_window, p_position.x - x, p_position.y - y);
+ _update_real_mouse_position(wd);
+}
+
+void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+ ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ wd.max_size = p_size;
+
+ _update_size_hints(p_window);
+ XFlush(x11_display);
+}
+
+Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ return wd.max_size;
+}
+
+void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+ ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ wd.min_size = p_size;
+
+ _update_size_hints(p_window);
+ XFlush(x11_display);
+}
+
+Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ return wd.min_size;
+}
+
+void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+
+ Size2i size = p_size;
+ size.x = MAX(1, size.x);
+ size.y = MAX(1, size.y);
+
+ WindowData &wd = windows[p_window];
+
+ if (wd.size.width == size.width && wd.size.height == size.height) {
+ return;
+ }
+
+ XWindowAttributes xwa;
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+ int old_w = xwa.width;
+ int old_h = xwa.height;
+
+ // Update our videomode width and height
+ wd.size = size;
+
+ // Update the size hints first to make sure the window size can be set
+ _update_size_hints(p_window);
+
+ // Resize the window
+ XResizeWindow(x11_display, wd.x11_window, size.x, size.y);
+
+ for (int timeout = 0; timeout < 50; ++timeout) {
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+
+ if (old_w != xwa.width || old_h != xwa.height) {
+ break;
+ }
+
+ usleep(10000);
+ }
+}
+
+Size2i DisplayServerX11::window_get_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ return wd.size;
+}
+
+Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ XWindowAttributes xwa;
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+ int w = xwa.width;
+ int h = xwa.height;
+ Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
+ if (prop != None) {
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = nullptr;
+ if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (format == 32 && len == 4 && data) {
+ long *extents = (long *)data;
+ w += extents[0] + extents[1]; // left, right
+ h += extents[2] + extents[3]; // top, bottom
+ }
+ XFree(data);
+ }
+ }
+ return Size2i(w, h);
+}
+
+// Just a helper to reduce code duplication in `window_is_maximize_allowed`
+// and `_set_wm_maximized`.
+bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const {
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+ const WindowData &wd = windows[p_window];
+
+ Atom property = XInternAtom(x11_display, p_atom_name, False);
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = nullptr;
+ bool retval = false;
+
+ int result = XGetWindowProperty(
+ x11_display,
+ wd.x11_window,
+ property,
+ 0,
+ 1024,
+ False,
+ XA_ATOM,
+ &type,
+ &format,
+ &len,
+ &remaining,
+ &data);
+
+ if (result == Success && data) {
+ Atom *atoms = (Atom *)data;
+ Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);
+ Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);
+ bool found_wm_act_max_horz = false;
+ bool found_wm_act_max_vert = false;
+
+ for (uint64_t i = 0; i < len; i++) {
+ if (atoms[i] == wm_act_max_horz) {
+ found_wm_act_max_horz = true;
+ }
+ if (atoms[i] == wm_act_max_vert) {
+ found_wm_act_max_vert = true;
+ }
+
+ if (found_wm_act_max_horz || found_wm_act_max_vert) {
+ retval = true;
+ break;
+ }
+ }
+
+ XFree(data);
+ }
+
+ return retval;
+}
+
+bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+ return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS");
+}
+
+void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) {
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ // Using EWMH -- Extended Window Manager Hints
+ XEvent xev;
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
+ Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_state;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+ xev.xclient.data.l[1] = wm_max_horz;
+ xev.xclient.data.l[2] = wm_max_vert;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+ if (p_enabled && window_is_maximize_allowed(p_window)) {
+ // Wait for effective resizing (so the GLX context is too).
+ // Give up after 0.5s, it's not going to happen on this WM.
+ // https://github.com/godotengine/godot/issues/19978
+ for (int attempt = 0; window_get_mode(p_window) != WINDOW_MODE_MAXIMIZED && attempt < 50; attempt++) {
+ usleep(10000);
+ }
+ }
+}
+
+void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (p_enabled && !window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {
+ // remove decorations if the window is not already borderless
+ Hints hints;
+ Atom property;
+ hints.flags = 2;
+ hints.decorations = 0;
+ property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
+
+ if (p_enabled) {
+ // Set the window as resizable to prevent window managers to ignore the fullscreen state flag.
+ _update_size_hints(p_window);
+ }
+
+ // Using EWMH -- Extended Window Manager Hints
+ XEvent xev;
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_state;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+ xev.xclient.data.l[1] = wm_fullscreen;
+ xev.xclient.data.l[2] = 0;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+ // set bypass compositor hint
+ Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
+ unsigned long compositing_disable_on = p_enabled ? 1 : 0;
+ XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
+
+ XFlush(x11_display);
+
+ if (!p_enabled) {
+ // Reset the non-resizable flags if we un-set these before.
+ _update_size_hints(p_window);
+
+ // put back or remove decorations according to the last set borderless state
+ Hints hints;
+ Atom property;
+ hints.flags = 2;
+ hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1;
+ property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
+}
+
+void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ WindowMode old_mode = window_get_mode(p_window);
+ if (old_mode == p_mode) {
+ return; // do nothing
+ }
+ //remove all "extra" modes
+
+ switch (old_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ //do nothing
+ } break;
+ case WINDOW_MODE_MINIMIZED: {
+ //Un-Minimize
+ // Using ICCCM -- Inter-Client Communication Conventions Manual
+ XEvent xev;
+ Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_change;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = WM_NormalState;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_state;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
+ xev.xclient.data.l[1] = wm_hidden;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ //Remove full-screen
+ wd.fullscreen = false;
+
+ _set_wm_fullscreen(p_window, false);
+
+ //un-maximize required for always on top
+ bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window);
+
+ window_set_position(wd.last_position_before_fs, p_window);
+
+ if (on_top) {
+ _set_wm_maximized(p_window, false);
+ }
+
+ } break;
+ case WINDOW_MODE_MAXIMIZED: {
+ _set_wm_maximized(p_window, false);
+ } break;
+ }
+
+ switch (p_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ //do nothing
+ } break;
+ case WINDOW_MODE_MINIMIZED: {
+ // Using ICCCM -- Inter-Client Communication Conventions Manual
+ XEvent xev;
+ Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_change;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = WM_IconicState;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_state;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
+ xev.xclient.data.l[1] = wm_hidden;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ wd.last_position_before_fs = wd.position;
+
+ if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {
+ _set_wm_maximized(p_window, true);
+ }
+
+ wd.fullscreen = true;
+ _set_wm_fullscreen(p_window, true);
+ } break;
+ case WINDOW_MODE_MAXIMIZED: {
+ _set_wm_maximized(p_window, true);
+ } break;
+ }
+}
+
+DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
+ const WindowData &wd = windows[p_window];
+
+ if (wd.fullscreen) { //if fullscreen, it's not in another mode
+ return WINDOW_MODE_FULLSCREEN;
+ }
+
+ // Test maximized.
+ // Using EWMH -- Extended Window Manager Hints
+ if (_window_maximize_check(p_window, "_NET_WM_STATE")) {
+ return WINDOW_MODE_MAXIMIZED;
+ }
+
+ { // Test minimized.
+ // Using ICCCM -- Inter-Client Communication Conventions Manual
+ Atom property = XInternAtom(x11_display, "WM_STATE", True);
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = nullptr;
+
+ int result = XGetWindowProperty(
+ x11_display,
+ wd.x11_window,
+ property,
+ 0,
+ 32,
+ False,
+ AnyPropertyType,
+ &type,
+ &format,
+ &len,
+ &remaining,
+ &data);
+
+ if (result == Success && data) {
+ long *state = (long *)data;
+ if (state[0] == WM_IconicState) {
+ XFree(data);
+ return WINDOW_MODE_MINIMIZED;
+ }
+ XFree(data);
+ }
+ }
+
+ // All other discarded, return windowed.
+
+ return WINDOW_MODE_WINDOWED;
+}
+
+void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ wd.resize_disabled = p_enabled;
+
+ _update_size_hints(p_window);
+
+ XFlush(x11_display);
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ Hints hints;
+ Atom property;
+ hints.flags = 2;
+ hints.decorations = p_enabled ? 0 : 1;
+ property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+
+ // Preserve window size
+ window_set_size(window_get_size(p_window), p_window);
+
+ wd.borderless = p_enabled;
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");
+ if (p_enabled && wd.fullscreen) {
+ _set_wm_maximized(p_window, true);
+ }
+
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
+
+ XClientMessageEvent xev;
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.window = wd.x11_window;
+ xev.message_type = wm_state;
+ xev.format = 32;
+ xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+ xev.data.l[1] = wm_above;
+ xev.data.l[3] = 1;
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
+
+ if (!p_enabled && !wd.fullscreen) {
+ _set_wm_maximized(p_window, false);
+ }
+ wd.on_top = p_enabled;
+
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ //todo reimplement
+ } break;
+ default: {
+ }
+ }
+}
+
+bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+ const WindowData &wd = windows[p_window];
+
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ return wd.resize_disabled;
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ bool borderless = wd.borderless;
+ Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ if (prop != None) {
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = nullptr;
+ if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (data && (format == 32) && (len >= 5)) {
+ borderless = !((Hints *)data)->decorations;
+ }
+ if (data) {
+ XFree(data);
+ }
+ }
+ }
+ return borderless;
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ return wd.on_top;
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ //todo reimplement
+ } break;
+ default: {
+ }
+ }
+
+ return false;
+}
+
+void DisplayServerX11::window_request_attention(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ // Using EWMH -- Extended Window Manager Hints
+ //
+ // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
+ // Will be unset by the window manager after user react on the request for attention
+
+ XEvent xev;
+ Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
+ Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = wm_state;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
+ xev.xclient.data.l[1] = wm_attention;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+ XFlush(x11_display);
+}
+
+void DisplayServerX11::window_move_to_foreground(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ XEvent xev;
+ Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);
+
+ memset(&xev, 0, sizeof(xev));
+ xev.type = ClientMessage;
+ xev.xclient.window = wd.x11_window;
+ xev.xclient.message_type = net_active_window;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = 1;
+ xev.xclient.data.l[1] = CurrentTime;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+ XFlush(x11_display);
+}
+
+bool DisplayServerX11::window_can_draw(WindowID p_window) const {
+ //this seems to be all that is provided by X11
+ return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;
+}
+
+bool DisplayServerX11::can_any_window_draw() const {
+ _THREAD_SAFE_METHOD_
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.im_active = p_active;
+
+ if (!wd.xic) {
+ return;
+ }
+
+ if (p_active) {
+ XSetICFocus(wd.xic);
+ window_set_ime_position(wd.im_position, p_window);
+ } else {
+ XUnsetICFocus(wd.xic);
+ }
+}
+
+void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.im_position = p_pos;
+
+ if (!wd.xic) {
+ return;
+ }
+
+ ::XPoint spot;
+ spot.x = short(p_pos.x);
+ spot.y = short(p_pos.y);
+ XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr);
+ XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr);
+ XFree(preedit_attr);
+}
+
+void DisplayServerX11::cursor_set_shape(CursorShape p_shape) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ if (p_shape == current_cursor) {
+ return;
+ }
+
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ if (cursors[p_shape] != None) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]);
+ }
+ } else if (cursors[CURSOR_ARROW] != None) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ XDefineCursor(x11_display, E->get().x11_window, cursors[CURSOR_ARROW]);
+ }
+ }
+ }
+
+ current_cursor = p_shape;
+}
+
+DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {
+ return current_cursor;
+}
+
+void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_cursor.is_valid()) {
+ Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
+ cursor_set_shape(p_shape);
+ return;
+ }
+
+ cursors_cache.erase(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ Ref<AtlasTexture> atlas_texture = p_cursor;
+ Ref<Image> image;
+ Size2i texture_size;
+ Rect2i atlas_rect;
+
+ if (texture.is_valid()) {
+ image = texture->get_data();
+ }
+
+ if (!image.is_valid() && atlas_texture.is_valid()) {
+ texture = atlas_texture->get_atlas();
+
+ atlas_rect.size.width = texture->get_width();
+ atlas_rect.size.height = texture->get_height();
+ atlas_rect.position.x = atlas_texture->get_region().position.x;
+ atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else if (image.is_valid()) {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(!texture.is_valid());
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+ image = texture->get_data();
+
+ ERR_FAIL_COND(!image.is_valid());
+
+ // Create the cursor structure
+ XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
+ XcursorUInt image_size = texture_size.width * texture_size.height;
+ XcursorDim size = sizeof(XcursorPixel) * image_size;
+
+ cursor_image->version = 1;
+ cursor_image->size = size;
+ cursor_image->xhot = p_hotspot.x;
+ cursor_image->yhot = p_hotspot.y;
+
+ // allocate memory to contain the whole file
+ cursor_image->pixels = (XcursorPixel *)memalloc(size);
+
+ for (XcursorPixel index = 0; index < image_size; index++) {
+ int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
+ int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
+
+ if (atlas_texture.is_valid()) {
+ column_index = MIN(column_index, atlas_rect.size.width - 1);
+ row_index = MIN(row_index, atlas_rect.size.height - 1);
+ }
+
+ *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();
+ }
+
+ ERR_FAIL_COND(cursor_image->pixels == nullptr);
+
+ // Save it for a further usage
+ cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
+
+ Vector<Variant> params;
+ params.push_back(p_cursor);
+ params.push_back(p_hotspot);
+ cursors_cache.insert(p_shape, params);
+
+ if (p_shape == current_cursor) {
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]);
+ }
+ }
+ }
+
+ memfree(cursor_image->pixels);
+ XcursorImageDestroy(cursor_image);
+ } else {
+ // Reset to default system cursor
+ if (img[p_shape]) {
+ cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]);
+ }
+
+ CursorShape c = current_cursor;
+ current_cursor = CURSOR_MAX;
+ cursor_set_shape(c);
+
+ cursors_cache.erase(p_shape);
+ }
+}
+
+int DisplayServerX11::keyboard_get_layout_count() const {
+ int _group_count = 0;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
+ XkbFreeKeyboard(kbd, 0, true);
+ }
+ return _group_count;
+}
+
+int DisplayServerX11::keyboard_get_current_layout() const {
+ XkbStateRec state;
+ XkbGetState(x11_display, XkbUseCoreKbd, &state);
+ return state.group;
+}
+
+void DisplayServerX11::keyboard_set_current_layout(int p_index) {
+ ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());
+ XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);
+}
+
+String DisplayServerX11::keyboard_get_layout_language(int p_index) const {
+ String ret;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+ XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
+
+ int _group_count = 0;
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
+
+ Atom names = kbd->names->symbols;
+ if (names != None) {
+ char *name = XGetAtomName(x11_display, names);
+ Vector<String> info = String(name).split("+");
+ if (p_index >= 0 && p_index < _group_count) {
+ if (p_index + 1 < info.size()) {
+ ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.
+ } else {
+ ret = "en"; // No symbol for layout fallback to "en".
+ }
+ } else {
+ ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
+ }
+ XFree(name);
+ }
+ XkbFreeKeyboard(kbd, 0, true);
+ }
+ return ret.substr(0, 2);
+}
+
+String DisplayServerX11::keyboard_get_layout_name(int p_index) const {
+ String ret;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+ XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
+
+ int _group_count = 0;
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
+
+ if (p_index >= 0 && p_index < _group_count) {
+ char *full_name = XGetAtomName(x11_display, groups[p_index]);
+ ret.parse_utf8(full_name);
+ XFree(full_name);
+ } else {
+ ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
+ }
+ XkbFreeKeyboard(kbd, 0, true);
+ }
+ return ret;
+}
+
+DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems;
+ unsigned long bytes_after;
+ unsigned char *ret = nullptr;
+
+ int read_bytes = 1024;
+
+ //Keep trying to read the property until there are no
+ //bytes unread.
+ do {
+ if (ret != nullptr) {
+ XFree(ret);
+ }
+
+ XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
+ &actual_type, &actual_format, &nitems, &bytes_after,
+ &ret);
+
+ read_bytes *= 2;
+
+ } while (bytes_after != 0);
+
+ Property p = { ret, actual_format, (int)nitems, actual_type };
+
+ return p;
+}
+
+static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) {
+ static const char *target_type = "text/uri-list";
+
+ for (int i = 0; i < p_count; i++) {
+ Atom atom = p_list[i];
+
+ if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) {
+ return atom;
+ }
+ }
+ return None;
+}
+
+static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
+ static const char *target_type = "text/uri-list";
+ if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) {
+ return p_t1;
+ }
+
+ if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) {
+ return p_t2;
+ }
+
+ if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) {
+ return p_t3;
+ }
+
+ return None;
+}
+
+void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {
+ state->set_shift((p_x11_state & ShiftMask));
+ state->set_control((p_x11_state & ControlMask));
+ state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt
+ state->set_metakey((p_x11_state & Mod4Mask));
+}
+
+unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) {
+ unsigned int mask = 1 << (p_x11_button - 1);
+
+ if (p_x11_type == ButtonPress) {
+ last_button_state |= mask;
+ } else {
+ last_button_state &= ~mask;
+ }
+
+ return last_button_state;
+}
+
+void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) {
+ WindowData wd = windows[p_window];
+ // X11 functions don't know what const is
+ XKeyEvent *xkeyevent = p_event;
+
+ // This code was pretty difficult to write.
+ // The docs stink and every toolkit seems to
+ // do it in a different way.
+
+ /* Phase 1, obtain a proper keysym */
+
+ // This was also very difficult to figure out.
+ // You'd expect you could just use Keysym provided by
+ // XKeycodeToKeysym to obtain internationalized
+ // input.. WRONG!!
+ // you must use XLookupString (???) which not only wastes
+ // cycles generating an unnecessary string, but also
+ // still works in half the cases. (won't handle deadkeys)
+ // For more complex input methods (deadkeys and more advanced)
+ // you have to use XmbLookupString (??).
+ // So.. then you have to chosse which of both results
+ // you want to keep.
+ // This is a real bizarreness and cpu waster.
+
+ KeySym keysym_keycode = 0; // keysym used to find a keycode
+ KeySym keysym_unicode = 0; // keysym used to find unicode
+
+ // XLookupString returns keysyms usable as nice keycodes.
+ char str[256 + 1];
+ XKeyEvent xkeyevent_no_mod = *xkeyevent;
+ xkeyevent_no_mod.state &= ~ShiftMask;
+ xkeyevent_no_mod.state &= ~ControlMask;
+ XLookupString(xkeyevent, str, 256, &keysym_unicode, nullptr);
+ XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);
+
+ // Meanwhile, XLookupString returns keysyms useful for unicode.
+
+ if (!xmbstring) {
+ // keep a temporary buffer for the string
+ xmbstring = (char *)memalloc(sizeof(char) * 8);
+ xmblen = 8;
+ }
+
+ if (xkeyevent->type == KeyPress && wd.xic) {
+ Status status;
+#ifdef X_HAVE_UTF8_STRING
+ int utf8len = 8;
+ char *utf8string = (char *)memalloc(sizeof(char) * utf8len);
+ int utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,
+ utf8len - 1, &keysym_unicode, &status);
+ if (status == XBufferOverflow) {
+ utf8len = utf8bytes + 1;
+ utf8string = (char *)memrealloc(utf8string, utf8len);
+ utf8bytes = Xutf8LookupString(wd.xic, xkeyevent, utf8string,
+ utf8len - 1, &keysym_unicode, &status);
+ }
+ utf8string[utf8bytes] = '\0';
+
+ if (status == XLookupChars) {
+ bool keypress = xkeyevent->type == KeyPress;
+ unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
+ unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+
+ if (keycode >= 'a' && keycode <= 'z') {
+ keycode -= 'a' - 'A';
+ }
+
+ String tmp;
+ tmp.parse_utf8(utf8string, utf8bytes);
+ for (int i = 0; i < tmp.length(); i++) {
+ Ref<InputEventKey> k;
+ k.instance();
+ if (physical_keycode == 0 && keycode == 0 && tmp[i] == 0) {
+ continue;
+ }
+
+ if (keycode == 0) {
+ keycode = physical_keycode;
+ }
+
+ _get_key_modifier_state(xkeyevent->state, k);
+
+ k->set_window_id(p_window);
+ k->set_unicode(tmp[i]);
+
+ k->set_pressed(keypress);
+
+ k->set_keycode(keycode);
+
+ k->set_physical_keycode(physical_keycode);
+
+ k->set_echo(false);
+
+ if (k->get_keycode() == KEY_BACKTAB) {
+ //make it consistent across platforms.
+ k->set_keycode(KEY_TAB);
+ k->set_physical_keycode(KEY_TAB);
+ k->set_shift(true);
+ }
+
+ Input::get_singleton()->accumulate_input_event(k);
+ }
+ memfree(utf8string);
+ return;
+ }
+ memfree(utf8string);
+#else
+ do {
+ int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
+ xmbstring[mnbytes] = '\0';
+
+ if (status == XBufferOverflow) {
+ xmblen = mnbytes + 1;
+ xmbstring = (char *)memrealloc(xmbstring, xmblen);
+ }
+ } while (status == XBufferOverflow);
+#endif
+ }
+
+ /* Phase 2, obtain a Godot keycode from the keysym */
+
+ // KeyMappingX11 just translated the X11 keysym to a PIGUI
+ // keysym, so it works in all platforms the same.
+
+ unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
+ unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+
+ /* Phase 3, obtain a unicode character from the keysym */
+
+ // KeyMappingX11 also translates keysym to unicode.
+ // It does a binary search on a table to translate
+ // most properly.
+ unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;
+
+ /* Phase 4, determine if event must be filtered */
+
+ // This seems to be a side-effect of using XIM.
+ // XEventFilter looks like a core X11 function,
+ // but it's actually just used to see if we must
+ // ignore a deadkey, or events XIM determines
+ // must not reach the actual gui.
+ // Guess it was a design problem of the extension
+
+ bool keypress = xkeyevent->type == KeyPress;
+
+ if (physical_keycode == 0 && keycode == 0 && unicode == 0) {
+ return;
+ }
+
+ if (keycode == 0) {
+ keycode = physical_keycode;
+ }
+
+ /* Phase 5, determine modifier mask */
+
+ // No problems here, except I had no way to
+ // know Mod1 was ALT and Mod4 was META (applekey/winkey)
+ // just tried Mods until i found them.
+
+ //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
+
+ Ref<InputEventKey> k;
+ k.instance();
+ k->set_window_id(p_window);
+
+ _get_key_modifier_state(xkeyevent->state, k);
+
+ /* Phase 6, determine echo character */
+
+ // Echo characters in X11 are a keyrelease and a keypress
+ // one after the other with the (almot) same timestamp.
+ // To detect them, i use XPeekEvent and check that their
+ // difference in time is below a threshold.
+
+ if (xkeyevent->type != KeyPress) {
+ p_echo = false;
+
+ // make sure there are events pending,
+ // so this call won't block.
+ if (XPending(x11_display) > 0) {
+ XEvent peek_event;
+ XPeekEvent(x11_display, &peek_event);
+
+ // I'm using a threshold of 5 msecs,
+ // since sometimes there seems to be a little
+ // jitter. I'm still not convinced that all this approach
+ // is correct, but the xorg developers are
+ // not very helpful today.
+
+#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
+ ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
+#undef ABSDIFF
+ if (peek_event.type == KeyPress && threshold < 5) {
+ KeySym rk;
+ XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);
+ if (rk == keysym_keycode) {
+ XEvent event;
+ XNextEvent(x11_display, &event); //erase next event
+ _handle_key_event(p_window, (XKeyEvent *)&event, true);
+ return; //ignore current, echo next
+ }
+ }
+
+ // use the time from peek_event so it always works
+ }
+
+ // save the time to check for echo when keypress happens
+ }
+
+ /* Phase 7, send event to Window */
+
+ k->set_pressed(keypress);
+
+ if (keycode >= 'a' && keycode <= 'z') {
+ keycode -= 'a' - 'A';
+ }
+
+ k->set_keycode(keycode);
+ k->set_physical_keycode(physical_keycode);
+ k->set_unicode(unicode);
+ k->set_echo(p_echo);
+
+ if (k->get_keycode() == KEY_BACKTAB) {
+ //make it consistent across platforms.
+ k->set_keycode(KEY_TAB);
+ k->set_physical_keycode(KEY_TAB);
+ k->set_shift(true);
+ }
+
+ //don't set mod state if modifier keys are released by themselves
+ //else event.is_action() will not work correctly here
+ if (!k->is_pressed()) {
+ if (k->get_keycode() == KEY_SHIFT) {
+ k->set_shift(false);
+ } else if (k->get_keycode() == KEY_CONTROL) {
+ k->set_control(false);
+ } else if (k->get_keycode() == KEY_ALT) {
+ k->set_alt(false);
+ } else if (k->get_keycode() == KEY_META) {
+ k->set_metakey(false);
+ }
+ }
+
+ bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());
+ if (k->is_pressed()) {
+ if (last_is_pressed) {
+ k->set_echo(true);
+ }
+ }
+
+ Input::get_singleton()->accumulate_input_event(k);
+}
+
+void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,
+ ::XPointer call_data) {
+ WARN_PRINT("Input method stopped");
+ DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
+ ds->xim = nullptr;
+
+ for (Map<WindowID, WindowData>::Element *E = ds->windows.front(); E; E = E->next()) {
+ E->get().xic = nullptr;
+ }
+}
+
+void DisplayServerX11::_window_changed(XEvent *event) {
+ WindowID window_id = MAIN_WINDOW_ID;
+
+ // Assign the event to the relevant window
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (event->xany.window == E->get().x11_window) {
+ window_id = E->key();
+ break;
+ }
+ }
+
+ Rect2i new_rect;
+
+ WindowData &wd = windows[window_id];
+ if (wd.x11_window != event->xany.window) { // Check if the correct window, in case it was not main window or anything else
+ return;
+ }
+
+ {
+ //the position in xconfigure is not useful here, obtain it manually
+ int x, y;
+ Window child;
+ XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
+ new_rect.position.x = x;
+ new_rect.position.y = y;
+
+ new_rect.size.width = event->xconfigure.width;
+ new_rect.size.height = event->xconfigure.height;
+ }
+
+ if (new_rect == Rect2i(wd.position, wd.size)) {
+ return;
+ }
+ if (wd.xic) {
+ // Not portable.
+ window_set_ime_position(Point2(0, 1));
+ }
+
+ wd.position = new_rect.position;
+ wd.size = new_rect.size;
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_resize(window_id, wd.size.width, wd.size.height);
+ }
+#endif
+
+ print_line("DisplayServer::_window_changed: " + itos(window_id) + " rect: " + new_rect);
+ if (!wd.rect_changed_callback.is_null()) {
+ Rect2i r = new_rect;
+
+ Variant rect = r;
+
+ Variant *rectp = &rect;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&rectp, 1, ret, ce);
+ }
+}
+
+void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerX11 *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
+
+ Ref<InputEventFromWindow> event_from_window = p_event;
+ if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
+ //send to a window
+ ERR_FAIL_COND(!windows.has(event_from_window->get_window_id()));
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_null()) {
+ return;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ } else {
+ //send to all windows
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ Callable callable = E->get().input_event_callback;
+ if (callable.is_null()) {
+ continue;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+}
+
+void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) {
+ if (!wd.event_callback.is_null()) {
+ Variant event = int(p_event);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+void DisplayServerX11::process_events() {
+ _THREAD_SAFE_METHOD_
+
+#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
+ static int frame = 0;
+ ++frame;
+#endif
+
+ if (app_focused) {
+ //verify that one of the windows has focus, else send focus out notification
+ bool focus_found = false;
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (E->get().focused) {
+ focus_found = true;
+ break;
+ }
+ }
+
+ if (!focus_found) {
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;
+
+ if (delta > 250) {
+ //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs.
+ if (OS::get_singleton()->get_main_loop()) {
+ DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+ app_focused = false;
+ }
+ } else {
+ time_since_no_focus = OS::get_singleton()->get_ticks_msec();
+ }
+ }
+
+ do_mouse_warp = false;
+
+ // Is the current mouse mode one where it needs to be grabbed.
+ bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED;
+
+ xi.pressure = 0;
+ xi.tilt = Vector2();
+ xi.pressure_supported = false;
+
+ while (XPending(x11_display) > 0) {
+ XEvent event;
+ XNextEvent(x11_display, &event);
+
+ if (XFilterEvent(&event, None)) {
+ continue;
+ }
+
+ WindowID window_id = MAIN_WINDOW_ID;
+
+ // Assign the event to the relevant window
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (event.xany.window == E->get().x11_window) {
+ window_id = E->key();
+ break;
+ }
+ }
+
+ if (XGetEventData(x11_display, &event.xcookie)) {
+ if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
+ XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
+ int index = event_data->detail;
+ Vector2 pos = Vector2(event_data->event_x, event_data->event_y);
+
+ switch (event_data->evtype) {
+ case XI_HierarchyChanged:
+ case XI_DeviceChanged: {
+ _refresh_device_info();
+ } break;
+ case XI_RawMotion: {
+ XIRawEvent *raw_event = (XIRawEvent *)event_data;
+ int device_id = raw_event->deviceid;
+
+ // Determine the axis used (called valuators in XInput for some forsaken reason)
+ // Mask is a bitmask indicating which axes are involved.
+ // We are interested in the values of axes 0 and 1.
+ if (raw_event->valuators.mask_len <= 0) {
+ break;
+ }
+
+ const double *values = raw_event->raw_values;
+
+ double rel_x = 0.0;
+ double rel_y = 0.0;
+
+ if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {
+ rel_x = *values;
+ values++;
+ }
+
+ if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {
+ rel_y = *values;
+ values++;
+ }
+
+ if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {
+ Map<int, Vector2>::Element *pen_pressure = xi.pen_pressure_range.find(device_id);
+ if (pen_pressure) {
+ Vector2 pen_pressure_range = pen_pressure->value();
+ if (pen_pressure_range != Vector2()) {
+ xi.pressure_supported = true;
+ xi.pressure = (*values - pen_pressure_range[0]) /
+ (pen_pressure_range[1] - pen_pressure_range[0]);
+ }
+ }
+
+ values++;
+ }
+
+ if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {
+ Map<int, Vector2>::Element *pen_tilt_x = xi.pen_tilt_x_range.find(device_id);
+ if (pen_tilt_x) {
+ Vector2 pen_tilt_x_range = pen_tilt_x->value();
+ if (pen_tilt_x_range != Vector2()) {
+ xi.tilt.x = ((*values - pen_tilt_x_range[0]) / (pen_tilt_x_range[1] - pen_tilt_x_range[0])) * 2 - 1;
+ }
+ }
+
+ values++;
+ }
+
+ if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {
+ Map<int, Vector2>::Element *pen_tilt_y = xi.pen_tilt_y_range.find(device_id);
+ if (pen_tilt_y) {
+ Vector2 pen_tilt_y_range = pen_tilt_y->value();
+ if (pen_tilt_y_range != Vector2()) {
+ xi.tilt.y = ((*values - pen_tilt_y_range[0]) / (pen_tilt_y_range[1] - pen_tilt_y_range[0])) * 2 - 1;
+ }
+ }
+
+ values++;
+ }
+
+ // https://bugs.freedesktop.org/show_bug.cgi?id=71609
+ // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html
+ if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {
+ break; // Flush duplicate to avoid overly fast motion
+ }
+
+ xi.old_raw_pos.x = xi.raw_pos.x;
+ xi.old_raw_pos.y = xi.raw_pos.y;
+ xi.raw_pos.x = rel_x;
+ xi.raw_pos.y = rel_y;
+
+ Map<int, Vector2>::Element *abs_info = xi.absolute_devices.find(device_id);
+
+ if (abs_info) {
+ // Absolute mode device
+ Vector2 mult = abs_info->value();
+
+ xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;
+ xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;
+ } else {
+ // Relative mode device
+ xi.relative_motion.x = xi.raw_pos.x;
+ xi.relative_motion.y = xi.raw_pos.y;
+ }
+
+ xi.last_relative_time = raw_event->time;
+ } break;
+#ifdef TOUCH_ENABLED
+ case XI_TouchBegin: // Fall-through
+ // Disabled hand-in-hand with the grabbing
+ //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
+
+ case XI_TouchEnd: {
+ bool is_begin = event_data->evtype == XI_TouchBegin;
+
+ Ref<InputEventScreenTouch> st;
+ st.instance();
+ st->set_window_id(window_id);
+ st->set_index(index);
+ st->set_position(pos);
+ st->set_pressed(is_begin);
+
+ if (is_begin) {
+ if (xi.state.has(index)) { // Defensive
+ break;
+ }
+ xi.state[index] = pos;
+ if (xi.state.size() == 1) {
+ // X11 may send a motion event when a touch gesture begins, that would result
+ // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out
+ xi.mouse_pos_to_filter = pos;
+ }
+ Input::get_singleton()->accumulate_input_event(st);
+ } else {
+ if (!xi.state.has(index)) { // Defensive
+ break;
+ }
+ xi.state.erase(index);
+ Input::get_singleton()->accumulate_input_event(st);
+ }
+ } break;
+
+ case XI_TouchUpdate: {
+ Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index);
+ if (!curr_pos_elem) { // Defensive
+ break;
+ }
+
+ if (curr_pos_elem->value() != pos) {
+ Ref<InputEventScreenDrag> sd;
+ sd.instance();
+ sd->set_window_id(window_id);
+ sd->set_index(index);
+ sd->set_position(pos);
+ sd->set_relative(pos - curr_pos_elem->value());
+ Input::get_singleton()->accumulate_input_event(sd);
+
+ curr_pos_elem->value() = pos;
+ }
+ } break;
+#endif
+ }
+ }
+ }
+ XFreeEventData(x11_display, &event.xcookie);
+
+ switch (event.type) {
+ case MapNotify: {
+ DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id);
+
+ const WindowData &wd = windows[window_id];
+
+ // Set focus when menu window is started.
+ // RevertToPointerRoot is used to make sure we don't lose all focus in case
+ // a subwindow and its parent are both destroyed.
+ if (wd.menu_type && !wd.no_focus) {
+ XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ }
+ } break;
+
+ case Expose: {
+ DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count);
+
+ Main::force_redraw();
+ } break;
+
+ case NoExpose: {
+ DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id);
+
+ windows[window_id].minimized = true;
+ } break;
+
+ case VisibilityNotify: {
+ DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state);
+
+ XVisibilityEvent *visibility = (XVisibilityEvent *)&event;
+ windows[window_id].minimized = (visibility->state == VisibilityFullyObscured);
+ } break;
+
+ case LeaveNotify: {
+ DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);
+
+ if (!mouse_mode_grab) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);
+ }
+
+ } break;
+
+ case EnterNotify: {
+ DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);
+
+ if (!mouse_mode_grab) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
+ }
+ } break;
+
+ case FocusIn: {
+ DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);
+
+ WindowData &wd = windows[window_id];
+
+ wd.focused = true;
+
+ // Keep track of focus order for overlapping windows.
+ static unsigned int focus_order = 0;
+ wd.focus_order = ++focus_order;
+
+ _send_window_event(wd, WINDOW_EVENT_FOCUS_IN);
+
+ if (mouse_mode_grab) {
+ // Show and update the cursor if confined and the window regained focus.
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (mouse_mode == MOUSE_MODE_CONFINED) {
+ XUndefineCursor(x11_display, E->get().x11_window);
+ } else if (mouse_mode == MOUSE_MODE_CAPTURED) { // or re-hide it in captured mode
+ XDefineCursor(x11_display, E->get().x11_window, null_cursor);
+ }
+
+ XGrabPointer(
+ x11_display, E->get().x11_window, True,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync, GrabModeAsync, E->get().x11_window, None, CurrentTime);
+ }
+ }
+#ifdef TOUCH_ENABLED
+ // Grab touch devices to avoid OS gesture interference
+ /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
+ XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
+ }*/
+#endif
+ if (wd.xic) {
+ XSetICFocus(wd.xic);
+ }
+
+ if (!app_focused) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+ app_focused = true;
+ }
+ } break;
+
+ case FocusOut: {
+ DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);
+
+ WindowData &wd = windows[window_id];
+
+ wd.focused = false;
+
+ Input::get_singleton()->release_pressed_events();
+ _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);
+
+ if (mouse_mode_grab) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ //dear X11, I try, I really try, but you never work, you do whathever you want.
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ // Show the cursor if we're in captured mode so it doesn't look weird.
+ XUndefineCursor(x11_display, E->get().x11_window);
+ }
+ }
+ XUngrabPointer(x11_display, CurrentTime);
+ }
+#ifdef TOUCH_ENABLED
+ // Ungrab touch devices so input works as usual while we are unfocused
+ /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
+ XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);
+ }*/
+
+ // Release every pointer to avoid sticky points
+ for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) {
+ Ref<InputEventScreenTouch> st;
+ st.instance();
+ st->set_index(E->key());
+ st->set_window_id(window_id);
+ st->set_position(E->get());
+ Input::get_singleton()->accumulate_input_event(st);
+ }
+ xi.state.clear();
+#endif
+ if (wd.xic) {
+ XSetICFocus(wd.xic);
+ }
+ } break;
+
+ case ConfigureNotify: {
+ DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect);
+
+ const WindowData &wd = windows[window_id];
+
+ // Set focus when menu window is re-used.
+ // RevertToPointerRoot is used to make sure we don't lose all focus in case
+ // a subwindow and its parent are both destroyed.
+ if (wd.menu_type && !wd.no_focus) {
+ XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ }
+
+ _window_changed(&event);
+ } break;
+
+ case ButtonPress:
+ case ButtonRelease: {
+ /* exit in case of a mouse button press */
+ last_timestamp = event.xbutton.time;
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ event.xbutton.x = last_mouse_pos.x;
+ event.xbutton.y = last_mouse_pos.y;
+ }
+
+ Ref<InputEventMouseButton> mb;
+ mb.instance();
+
+ mb->set_window_id(window_id);
+ _get_key_modifier_state(event.xbutton.state, mb);
+ mb->set_button_index(event.xbutton.button);
+ if (mb->get_button_index() == 2) {
+ mb->set_button_index(3);
+ } else if (mb->get_button_index() == 3) {
+ mb->set_button_index(2);
+ }
+ mb->set_button_mask(_get_mouse_button_state(mb->get_button_index(), event.xbutton.type));
+ mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));
+ mb->set_global_position(mb->get_position());
+
+ mb->set_pressed((event.type == ButtonPress));
+
+ const WindowData &wd = windows[window_id];
+
+ if (event.type == ButtonPress) {
+ DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
+
+ // Ensure window focus on click.
+ // RevertToPointerRoot is used to make sure we don't lose all focus in case
+ // a subwindow and its parent are both destroyed.
+ if (!wd.no_focus) {
+ XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ }
+
+ uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;
+
+ if (mb->get_button_index() == last_click_button_index) {
+ if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) {
+ last_click_ms = 0;
+ last_click_pos = Point2i(-100, -100);
+ last_click_button_index = -1;
+ mb->set_doubleclick(true);
+ }
+
+ } else if (mb->get_button_index() < 4 || mb->get_button_index() > 7) {
+ last_click_button_index = mb->get_button_index();
+ }
+
+ if (!mb->is_doubleclick()) {
+ last_click_ms += diff;
+ last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);
+ }
+ } else {
+ DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
+
+ if (!wd.focused) {
+ // Propagate the event to the focused window,
+ // because it's received only on the topmost window.
+ // Note: This is needed for drag & drop to work between windows,
+ // because the engine expects events to keep being processed
+ // on the same window dragging started.
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ const WindowData &wd_other = E->get();
+ WindowID window_id_other = E->key();
+ if (wd_other.focused) {
+ if (window_id_other != window_id) {
+ int x, y;
+ Window child;
+ XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);
+
+ mb->set_window_id(window_id_other);
+ mb->set_position(Vector2(x, y));
+ mb->set_global_position(mb->get_position());
+ Input::get_singleton()->accumulate_input_event(mb);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ Input::get_singleton()->accumulate_input_event(mb);
+
+ } break;
+ case MotionNotify: {
+ // The X11 API requires filtering one-by-one through the motion
+ // notify events, in order to figure out which event is the one
+ // generated by warping the mouse pointer.
+
+ while (true) {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[MAIN_WINDOW_ID].size.width / 2 && event.xmotion.y == windows[MAIN_WINDOW_ID].size.height / 2) {
+ //this is likely the warp event since it was warped here
+ center = Vector2(event.xmotion.x, event.xmotion.y);
+ break;
+ }
+
+ if (XPending(x11_display) > 0) {
+ XEvent tevent;
+ XPeekEvent(x11_display, &tevent);
+ if (tevent.type == MotionNotify) {
+ XNextEvent(x11_display, &event);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ last_timestamp = event.xmotion.time;
+
+ // Motion is also simple.
+ // A little hack is in order
+ // to be able to send relative motion events.
+ Point2i pos(event.xmotion.x, event.xmotion.y);
+
+ // Avoidance of spurious mouse motion (see handling of touch)
+ bool filter = false;
+ // Adding some tolerance to match better Point2i to Vector2
+ if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {
+ filter = true;
+ }
+ // Invalidate to avoid filtering a possible legitimate similar event coming later
+ xi.mouse_pos_to_filter = Vector2(1e10, 1e10);
+ if (filter) {
+ break;
+ }
+
+ const WindowData &wd = windows[window_id];
+ bool focused = wd.focused;
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
+ break;
+ }
+
+ Point2i new_center = pos;
+ pos = last_mouse_pos + xi.relative_motion;
+ center = new_center;
+ do_mouse_warp = focused; // warp the cursor if we're focused in
+ }
+
+ if (!last_mouse_pos_valid) {
+ last_mouse_pos = pos;
+ last_mouse_pos_valid = true;
+ }
+
+ // Hackish but relative mouse motion is already handled in the RawMotion event.
+ // RawMotion does not provide the absolute mouse position (whereas MotionNotify does).
+ // Therefore, RawMotion cannot be the authority on absolute mouse position.
+ // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.
+ // Therefore, MotionNotify cannot be the authority on relative mouse motion.
+ // This means we need to take a combined approach...
+ Point2i rel;
+
+ // Only use raw input if in capture mode. Otherwise use the classic behavior.
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ rel = xi.relative_motion;
+ } else {
+ rel = pos - last_mouse_pos;
+ }
+
+ // Reset to prevent lingering motion
+ xi.relative_motion.x = 0;
+ xi.relative_motion.y = 0;
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ pos = Point2i(windows[MAIN_WINDOW_ID].size.width / 2, windows[MAIN_WINDOW_ID].size.height / 2);
+ }
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+
+ mm->set_window_id(window_id);
+ if (xi.pressure_supported) {
+ mm->set_pressure(xi.pressure);
+ } else {
+ mm->set_pressure((mouse_get_button_state() & (1 << (BUTTON_LEFT - 1))) ? 1.0f : 0.0f);
+ }
+ mm->set_tilt(xi.tilt);
+
+ _get_key_modifier_state(event.xmotion.state, mm);
+ mm->set_button_mask(mouse_get_button_state());
+ mm->set_position(pos);
+ mm->set_global_position(pos);
+ Input::get_singleton()->set_mouse_position(pos);
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+
+ mm->set_relative(rel);
+
+ last_mouse_pos = pos;
+
+ // printf("rel: %d,%d\n", rel.x, rel.y );
+ // Don't propagate the motion event unless we have focus
+ // this is so that the relative motion doesn't get messed up
+ // after we regain focus.
+ if (focused) {
+ Input::get_singleton()->accumulate_input_event(mm);
+ } else {
+ // Propagate the event to the focused window,
+ // because it's received only on the topmost window.
+ // Note: This is needed for drag & drop to work between windows,
+ // because the engine expects events to keep being processed
+ // on the same window dragging started.
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ const WindowData &wd_other = E->get();
+ if (wd_other.focused) {
+ int x, y;
+ Window child;
+ XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);
+
+ Point2i pos_focused(x, y);
+
+ mm->set_window_id(E->key());
+ mm->set_position(pos_focused);
+ mm->set_global_position(pos_focused);
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ Input::get_singleton()->accumulate_input_event(mm);
+
+ break;
+ }
+ }
+ }
+
+ } break;
+ case KeyPress:
+ case KeyRelease: {
+ last_timestamp = event.xkey.time;
+
+ // key event is a little complex, so
+ // it will be handled in its own function.
+ _handle_key_event(window_id, (XKeyEvent *)&event);
+ } break;
+ case SelectionRequest: {
+ XSelectionRequestEvent *req;
+ XEvent e, respond;
+ e = event;
+
+ req = &(e.xselectionrequest);
+ if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
+ req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
+ req->target == XInternAtom(x11_display, "TEXT", 0) ||
+ req->target == XA_STRING ||
+ req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
+ req->target == XInternAtom(x11_display, "text/plain", 0)) {
+ CharString clip = clipboard_get().utf8();
+ XChangeProperty(x11_display,
+ req->requestor,
+ req->property,
+ req->target,
+ 8,
+ PropModeReplace,
+ (unsigned char *)clip.get_data(),
+ clip.length());
+ respond.xselection.property = req->property;
+ } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {
+ Atom data[7];
+ data[0] = XInternAtom(x11_display, "TARGETS", 0);
+ data[1] = XInternAtom(x11_display, "UTF8_STRING", 0);
+ data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
+ data[3] = XInternAtom(x11_display, "TEXT", 0);
+ data[4] = XA_STRING;
+ data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
+ data[6] = XInternAtom(x11_display, "text/plain", 0);
+
+ XChangeProperty(x11_display,
+ req->requestor,
+ req->property,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ (unsigned char *)&data,
+ sizeof(data) / sizeof(data[0]));
+ respond.xselection.property = req->property;
+
+ } else {
+ char *targetname = XGetAtomName(x11_display, req->target);
+ printf("No Target '%s'\n", targetname);
+ if (targetname) {
+ XFree(targetname);
+ }
+ respond.xselection.property = None;
+ }
+
+ respond.xselection.type = SelectionNotify;
+ respond.xselection.display = req->display;
+ respond.xselection.requestor = req->requestor;
+ respond.xselection.selection = req->selection;
+ respond.xselection.target = req->target;
+ respond.xselection.time = req->time;
+ XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond);
+ XFlush(x11_display);
+ } break;
+
+ case SelectionNotify:
+
+ if (event.xselection.target == requested) {
+ Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));
+
+ Vector<String> files = String((char *)p.data).split("\n", false);
+ for (int i = 0; i < files.size(); i++) {
+ files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges();
+ }
+
+ if (!windows[window_id].drop_files_callback.is_null()) {
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+ }
+
+ //Reply that all is well.
+ XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = x11_display;
+ m.window = xdnd_source_window;
+ m.message_type = xdnd_finished;
+ m.format = 32;
+ m.data.l[0] = windows[window_id].x11_window;
+ m.data.l[1] = 1;
+ m.data.l[2] = xdnd_action_copy; //We only ever copy.
+
+ XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);
+ }
+ break;
+
+ case ClientMessage:
+
+ if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
+ }
+
+ else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
+ //File(s) have been dragged over the window, check for supported target (text/uri-list)
+ xdnd_version = (event.xclient.data.l[1] >> 24);
+ Window source = event.xclient.data.l[0];
+ bool more_than_3 = event.xclient.data.l[1] & 1;
+ if (more_than_3) {
+ Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
+ requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
+ } else {
+ requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
+ }
+ } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {
+ //xdnd position event, reply with an XDND status message
+ //just depending on type of data for now
+ XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xdnd_status;
+ m.format = 32;
+ m.data.l[0] = windows[window_id].x11_window;
+ m.data.l[1] = (requested != None);
+ m.data.l[2] = 0; //empty rectangle
+ m.data.l[3] = 0;
+ m.data.l[4] = xdnd_action_copy;
+
+ XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
+ XFlush(x11_display);
+ } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
+ if (requested != None) {
+ xdnd_source_window = event.xclient.data.l[0];
+ if (xdnd_version >= 1) {
+ XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]);
+ } else {
+ XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime);
+ }
+ } else {
+ //Reply that we're not interested.
+ XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xdnd_finished;
+ m.format = 32;
+ m.data.l[0] = windows[window_id].x11_window;
+ m.data.l[1] = 0;
+ m.data.l[2] = None; //Failed.
+ XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ XFlush(x11_display);
+
+ if (do_mouse_warp) {
+ XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,
+ 0, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2);
+
+ /*
+ Window root, child;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+ XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );
+
+ printf("Root: %d,%d\n", root_x, root_y);
+ printf("Win: %d,%d\n", win_x, win_y);
+ */
+ }
+
+ Input::get_singleton()->flush_accumulated_events();
+}
+
+void DisplayServerX11::release_rendering_thread() {
+}
+
+void DisplayServerX11::make_rendering_thread() {
+}
+
+void DisplayServerX11::swap_buffers() {
+}
+
+void DisplayServerX11::_update_context(WindowData &wd) {
+ XClassHint *classHint = XAllocClassHint();
+
+ if (classHint) {
+ CharString name_str;
+ switch (context) {
+ case CONTEXT_EDITOR:
+ name_str = "Godot_Editor";
+ break;
+ case CONTEXT_PROJECTMAN:
+ name_str = "Godot_ProjectList";
+ break;
+ case CONTEXT_ENGINE:
+ name_str = "Godot_Engine";
+ break;
+ }
+
+ CharString class_str;
+ if (context == CONTEXT_ENGINE) {
+ String config_name = GLOBAL_GET("application/config/name");
+ if (config_name.length() == 0) {
+ class_str = "Godot_Engine";
+ } else {
+ class_str = config_name.utf8();
+ }
+ } else {
+ class_str = "Godot";
+ }
+
+ classHint->res_class = class_str.ptrw();
+ classHint->res_name = name_str.ptrw();
+
+ XSetClassHint(x11_display, wd.x11_window, classHint);
+ XFree(classHint);
+ }
+}
+
+void DisplayServerX11::set_context(Context p_context) {
+ _THREAD_SAFE_METHOD_
+
+ context = p_context;
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ _update_context(E->get());
+ }
+}
+
+void DisplayServerX11::set_native_icon(const String &p_filename) {
+ WARN_PRINT("Native icon not supported by this display server.");
+}
+
+bool g_set_icon_error = false;
+int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {
+ g_set_icon_error = true;
+ return 0;
+}
+
+void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {
+ _THREAD_SAFE_METHOD_
+
+ WindowData &wd = windows[MAIN_WINDOW_ID];
+
+ int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);
+
+ Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);
+
+ if (p_icon.is_valid()) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ while (true) {
+ int w = img->get_width();
+ int h = img->get_height();
+
+ if (g_set_icon_error) {
+ g_set_icon_error = false;
+
+ WARN_PRINT("Icon too large, attempting to resize icon.");
+
+ int new_width, new_height;
+ if (w > h) {
+ new_width = w / 2;
+ new_height = h * new_width / w;
+ } else {
+ new_height = h / 2;
+ new_width = w * new_height / h;
+ }
+
+ w = new_width;
+ h = new_height;
+
+ if (!w || !h) {
+ WARN_PRINT("Unable to set icon.");
+ break;
+ }
+
+ img->resize(w, h, Image::INTERPOLATE_CUBIC);
+ }
+
+ // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
+ Vector<long> pd;
+
+ pd.resize(2 + w * h);
+
+ pd.write[0] = w;
+ pd.write[1] = h;
+
+ const uint8_t *r = img->get_data().ptr();
+
+ long *wr = &pd.write[2];
+ uint8_t const *pr = r;
+
+ for (int i = 0; i < w * h; i++) {
+ long v = 0;
+ // A R G B
+ v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
+ *wr++ = v;
+ pr += 4;
+ }
+
+ XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
+
+ if (!g_set_icon_error) {
+ break;
+ }
+ }
+ } else {
+ XDeleteProperty(x11_display, wd.x11_window, net_wm_icon);
+ }
+
+ XFlush(x11_display);
+ XSetErrorHandler(oldHandler);
+}
+
+Vector<String> DisplayServerX11::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#ifdef VULKAN_ENABLED
+ drivers.push_back("vulkan");
+#endif
+#ifdef OPENGL_ENABLED
+ drivers.push_back("opengl");
+#endif
+
+ return drivers;
+}
+
+DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n"
+ "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
+ "Unable to initialize Video driver");
+ }
+ return ds;
+}
+
+DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+ //Create window
+
+ long visualMask = VisualScreenMask;
+ int numberOfVisuals;
+ XVisualInfo vInfoTemplate = {};
+ vInfoTemplate.screen = DefaultScreen(x11_display);
+ XVisualInfo *visualInfo = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals);
+
+ Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, vInfoTemplate.screen), visualInfo->visual, AllocNone);
+
+ XSetWindowAttributes windowAttributes = {};
+ windowAttributes.colormap = colormap;
+ windowAttributes.background_pixel = 0xFFFFFFFF;
+ windowAttributes.border_pixel = 0;
+ windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
+
+ unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;
+
+ WindowID id = window_id_counter++;
+ WindowData &wd = windows[id];
+
+ if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) {
+ wd.menu_type = true;
+ }
+
+ if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
+ wd.menu_type = true;
+ wd.no_focus = true;
+ }
+
+ // Setup for menu subwindows:
+ // - override_redirect forces the WM not to interfere with the window, to avoid delays due to
+ // handling decorations and placement.
+ // On the other hand, focus changes need to be handled manually when this is set.
+ // - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.
+ if (wd.menu_type) {
+ windowAttributes.override_redirect = True;
+ windowAttributes.save_under = True;
+ valuemask |= CWOverrideRedirect | CWSaveUnder;
+ }
+
+ {
+ wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes);
+
+ // Enable receiving notification when the window is initialized (MapNotify)
+ // so the focus can be set at the right time.
+ if (wd.menu_type && !wd.no_focus) {
+ XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);
+ }
+
+ //associate PID
+ // make PID known to X11
+ {
+ const long pid = OS::get_singleton()->get_process_id();
+ Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);
+ XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
+ }
+
+ long im_event_mask = 0;
+
+ {
+ XIEventMask all_event_mask;
+ XSetWindowAttributes new_attr;
+
+ new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
+ ButtonReleaseMask | EnterWindowMask |
+ LeaveWindowMask | PointerMotionMask |
+ Button1MotionMask |
+ Button2MotionMask | Button3MotionMask |
+ Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask |
+ ExposureMask | VisibilityChangeMask |
+ StructureNotifyMask |
+ SubstructureNotifyMask | SubstructureRedirectMask |
+ FocusChangeMask | PropertyChangeMask |
+ ColormapChangeMask | OwnerGrabButtonMask |
+ im_event_mask;
+
+ XChangeWindowAttributes(x11_display, wd.x11_window, CWEventMask, &new_attr);
+
+ static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
+
+ all_event_mask.deviceid = XIAllDevices;
+ all_event_mask.mask_len = sizeof(all_mask_data);
+ all_event_mask.mask = all_mask_data;
+
+ XISetMask(all_event_mask.mask, XI_HierarchyChanged);
+
+#ifdef TOUCH_ENABLED
+ if (xi.touch_devices.size()) {
+ XISetMask(all_event_mask.mask, XI_TouchBegin);
+ XISetMask(all_event_mask.mask, XI_TouchUpdate);
+ XISetMask(all_event_mask.mask, XI_TouchEnd);
+ XISetMask(all_event_mask.mask, XI_TouchOwnership);
+ }
+#endif
+
+ XISelectEvents(x11_display, wd.x11_window, &all_event_mask, 1);
+ }
+
+ /* set the titlebar name */
+ XStoreName(x11_display, wd.x11_window, "Godot");
+ XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);
+ XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
+
+ if (xim && xim_style) {
+ wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr);
+ if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {
+ WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
+ XDestroyIC(wd.xic);
+ wd.xic = nullptr;
+ }
+ if (wd.xic) {
+ XUnsetICFocus(wd.xic);
+ } else {
+ WARN_PRINT("XCreateIC couldn't create wd.xic");
+ }
+ } else {
+ wd.xic = nullptr;
+ WARN_PRINT("XCreateIC couldn't create wd.xic");
+ }
+
+ _update_context(wd);
+
+ if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
+ Hints hints;
+ Atom property;
+ hints.flags = 2;
+ hints.decorations = 0;
+ property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
+
+ if (wd.menu_type) {
+ // Set Utility type to disable fade animations.
+ Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
+
+ XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ } else {
+ Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
+
+ XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ }
+
+ _update_size_hints(id);
+
+#if defined(VULKAN_ENABLED)
+ if (context_vulkan) {
+ Error err = context_vulkan->window_create(id, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window");
+ }
+#endif
+
+ //set_class_hint(x11_display, wd.x11_window);
+ XFlush(x11_display);
+
+ XSync(x11_display, False);
+ //XSetErrorHandler(oldHandler);
+
+ XFree(visualInfo);
+ }
+
+ window_set_mode(p_mode, id);
+
+ //sync size
+ {
+ XWindowAttributes xwa;
+
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+
+ wd.position.x = xwa.x;
+ wd.position.y = xwa.y;
+ wd.size.width = xwa.width;
+ wd.size.height = xwa.height;
+
+ print_line("DisplayServer::_create_window " + itos(id) + " want rect: " + p_rect + " got rect " + Rect2i(xwa.x, xwa.y, xwa.width, xwa.height));
+ }
+
+ //set cursor
+ if (cursors[current_cursor] != None) {
+ XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);
+ }
+
+ return id;
+}
+
+DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+
+ current_cursor = CURSOR_ARROW;
+ mouse_mode = MOUSE_MODE_VISIBLE;
+
+ for (int i = 0; i < CURSOR_MAX; i++) {
+ cursors[i] = None;
+ img[i] = nullptr;
+ }
+
+ last_button_state = 0;
+
+ xmbstring = nullptr;
+
+ last_click_ms = 0;
+ last_click_button_index = -1;
+ last_click_pos = Point2i(-100, -100);
+
+ last_timestamp = 0;
+ last_mouse_pos_valid = false;
+ last_keyrelease_time = 0;
+
+ XInitThreads(); //always use threads
+
+ /** XLIB INITIALIZATION **/
+ x11_display = XOpenDisplay(nullptr);
+
+ if (!x11_display) {
+ ERR_PRINT("X11 Display is not available");
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
+
+ char *modifiers = nullptr;
+ Bool xkb_dar = False;
+ XAutoRepeatOn(x11_display);
+ xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, nullptr);
+
+ // Try to support IME if detectable auto-repeat is supported
+ if (xkb_dar == True) {
+#ifdef X_HAVE_UTF8_STRING
+ // Xutf8LookupString will be used later instead of XmbLookupString before
+ // the multibyte sequences can be converted to unicode string.
+ modifiers = XSetLocaleModifiers("");
+#endif
+ }
+
+ if (modifiers == nullptr) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ WARN_PRINT("IME is disabled");
+ }
+ XSetLocaleModifiers("@im=none");
+ WARN_PRINT("Error setting locale modifiers");
+ }
+
+ const char *err;
+ xrr_get_monitors = nullptr;
+ xrr_free_monitors = nullptr;
+ int xrandr_major = 0;
+ int xrandr_minor = 0;
+ int event_base, error_base;
+ xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);
+ xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);
+ if (!xrandr_handle) {
+ err = dlerror();
+ // For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2...
+ // In case this happens for other X11 platforms in the future, let's give it a try too before failing.
+ xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY);
+ if (!xrandr_handle) {
+ fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);
+ }
+ } else {
+ XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
+ if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
+ xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");
+ if (!xrr_get_monitors) {
+ err = dlerror();
+ fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);
+ } else {
+ xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");
+ if (!xrr_free_monitors) {
+ err = dlerror();
+ fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);
+ xrr_get_monitors = nullptr;
+ }
+ }
+ }
+ }
+
+ if (!_refresh_device_info()) {
+ alert("Your system does not support XInput 2.\n"
+ "Please upgrade your distribution.",
+ "Unable to initialize XInput");
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
+
+ xim = XOpenIM(x11_display, nullptr, nullptr, nullptr);
+
+ if (xim == nullptr) {
+ WARN_PRINT("XOpenIM failed");
+ xim_style = 0L;
+ } else {
+ ::XIMCallback im_destroy_callback;
+ im_destroy_callback.client_data = (::XPointer)(this);
+ im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);
+ if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
+ nullptr) != nullptr) {
+ WARN_PRINT("Error setting XIM destroy callback");
+ }
+
+ ::XIMStyles *xim_styles = nullptr;
+ xim_style = 0L;
+ char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr);
+ if (imvalret != nullptr || xim_styles == nullptr) {
+ fprintf(stderr, "Input method doesn't support any styles\n");
+ }
+
+ if (xim_styles) {
+ xim_style = 0L;
+ for (int i = 0; i < xim_styles->count_styles; i++) {
+ if (xim_styles->supported_styles[i] ==
+ (XIMPreeditNothing | XIMStatusNothing)) {
+ xim_style = xim_styles->supported_styles[i];
+ break;
+ }
+ }
+
+ XFree(xim_styles);
+ }
+ XFree(imvalret);
+ }
+
+ /* Atorm internment */
+ wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
+ //Set Xdnd (drag & drop) support
+ xdnd_aware = XInternAtom(x11_display, "XdndAware", False);
+ xdnd_version = 5;
+ xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
+ xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
+ xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
+ xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
+ xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
+ xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
+ xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
+
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //TODO - do Vulkan and GLES2 support checks, driver selection and fallback
+ rendering_driver = p_rendering_driver;
+
+#ifndef _MSC_VER
+#warning Forcing vulkan rendering driver because OpenGL not implemented yet
+#endif
+ rendering_driver = "vulkan";
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextX11);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ r_error = ERR_CANT_CREATE;
+ ERR_FAIL_MSG("Could not initialize Vulkan");
+ }
+ }
+#endif
+ // Init context and rendering device
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ if (getenv("DRI_PRIME") == nullptr) {
+ int use_prime = -1;
+
+ if (getenv("PRIMUS_DISPLAY") ||
+ getenv("PRIMUS_libGLd") ||
+ getenv("PRIMUS_libGLa") ||
+ getenv("PRIMUS_libGL") ||
+ getenv("PRIMUS_LOAD_GLOBAL") ||
+ getenv("BUMBLEBEE_SOCKET")) {
+ print_verbose("Optirun/primusrun detected. Skipping GPU detection");
+ use_prime = 0;
+ }
+
+ if (getenv("LD_LIBRARY_PATH")) {
+ String ld_library_path(getenv("LD_LIBRARY_PATH"));
+ Vector<String> libraries = ld_library_path.split(":");
+
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
+ FileAccess::exists(libraries[i] + "/libGL.so")) {
+ print_verbose("Custom libGL override detected. Skipping GPU detection");
+ use_prime = 0;
+ }
+ }
+ }
+
+ if (use_prime == -1) {
+ print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
+ use_prime = detect_prime();
+ }
+
+ if (use_prime) {
+ print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");
+ print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
+ setenv("DRI_PRIME", "1", 1);
+ }
+ }
+
+ ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
+
+ context_gles2 = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, opengl_api_type));
+
+ if (context_gles2->initialize() != OK) {
+ memdelete(context_gles2);
+ context_gles2 = nullptr;
+ ERR_FAIL_V(ERR_UNAVAILABLE);
+ }
+
+ context_gles2->set_use_vsync(current_videomode.use_vsync);
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ memdelete(context_gles2);
+ context_gles2 = nullptr;
+ ERR_FAIL_V(ERR_UNAVAILABLE);
+ }
+ }
+#endif
+ Point2i window_position(
+ (screen_get_size(0).width - p_resolution.width) / 2,
+ (screen_get_size(0).height - p_resolution.height) / 2);
+ WindowID main_window = _create_window(p_mode, p_flags, Rect2i(window_position, p_resolution));
+ for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+ if (p_flags & (1 << i)) {
+ window_set_flag(WindowFlags(i), true, main_window);
+ }
+ }
+ show_window(main_window);
+
+//create RenderingDevice if used
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ //temporary
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ /*
+ rendering_server = memnew(RenderingServerRaster);
+ if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+ rendering_server = memnew(RenderingServerWrapMT(rendering_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
+ }
+ */
+
+ {
+ //set all event master mask
+ XIEventMask all_master_event_mask;
+ static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
+ all_master_event_mask.deviceid = XIAllMasterDevices;
+ all_master_event_mask.mask_len = sizeof(all_master_mask_data);
+ all_master_event_mask.mask = all_master_mask_data;
+ XISetMask(all_master_event_mask.mask, XI_DeviceChanged);
+ XISetMask(all_master_event_mask.mask, XI_RawMotion);
+ XISelectEvents(x11_display, DefaultRootWindow(x11_display), &all_master_event_mask, 1);
+ }
+
+ // Disabled by now since grabbing also blocks mouse events
+ // (they are received as extended events instead of standard events)
+ /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership);
+
+ // Grab touch devices to avoid OS gesture interference
+ for (int i = 0; i < xi.touch_devices.size(); ++i) {
+ XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
+ }*/
+
+ cursor_size = XcursorGetDefaultSize(x11_display);
+ cursor_theme = XcursorGetTheme(x11_display);
+
+ if (!cursor_theme) {
+ print_verbose("XcursorGetTheme could not get cursor theme");
+ cursor_theme = "default";
+ }
+
+ for (int i = 0; i < CURSOR_MAX; i++) {
+ static const char *cursor_file[] = {
+ "left_ptr",
+ "xterm",
+ "hand2",
+ "cross",
+ "watch",
+ "left_ptr_watch",
+ "fleur",
+ "dnd-move",
+ "crossed_circle",
+ "v_double_arrow",
+ "h_double_arrow",
+ "size_bdiag",
+ "size_fdiag",
+ "move",
+ "row_resize",
+ "col_resize",
+ "question_arrow"
+ };
+
+ img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);
+ if (!img[i]) {
+ const char *fallback = nullptr;
+
+ switch (i) {
+ case CURSOR_POINTING_HAND:
+ fallback = "pointer";
+ break;
+ case CURSOR_CROSS:
+ fallback = "crosshair";
+ break;
+ case CURSOR_WAIT:
+ fallback = "wait";
+ break;
+ case CURSOR_BUSY:
+ fallback = "progress";
+ break;
+ case CURSOR_DRAG:
+ fallback = "grabbing";
+ break;
+ case CURSOR_CAN_DROP:
+ fallback = "hand1";
+ break;
+ case CURSOR_FORBIDDEN:
+ fallback = "forbidden";
+ break;
+ case CURSOR_VSIZE:
+ fallback = "ns-resize";
+ break;
+ case CURSOR_HSIZE:
+ fallback = "ew-resize";
+ break;
+ case CURSOR_BDIAGSIZE:
+ fallback = "fd_double_arrow";
+ break;
+ case CURSOR_FDIAGSIZE:
+ fallback = "bd_double_arrow";
+ break;
+ case CURSOR_MOVE:
+ img[i] = img[CURSOR_DRAG];
+ break;
+ case CURSOR_VSPLIT:
+ fallback = "sb_v_double_arrow";
+ break;
+ case CURSOR_HSPLIT:
+ fallback = "sb_h_double_arrow";
+ break;
+ case CURSOR_HELP:
+ fallback = "help";
+ break;
+ }
+ if (fallback != nullptr) {
+ img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size);
+ }
+ }
+ if (img[i]) {
+ cursors[i] = XcursorImageLoadCursor(x11_display, img[i]);
+ } else {
+ print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));
+ }
+ }
+
+ {
+ // Creating an empty/transparent cursor
+
+ // Create 1x1 bitmap
+ Pixmap cursormask = XCreatePixmap(x11_display,
+ RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
+
+ // Fill with zero
+ XGCValues xgc;
+ xgc.function = GXclear;
+ GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
+ XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
+
+ // Color value doesn't matter. Mask zero means no foreground or background will be drawn
+ XColor col = {};
+
+ Cursor cursor = XCreatePixmapCursor(x11_display,
+ cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)
+ cursormask, // mask
+ &col, &col, 0, 0);
+
+ XFreePixmap(x11_display, cursormask);
+ XFreeGC(x11_display, gc);
+
+ if (cursor == None) {
+ ERR_PRINT("FAILED CREATING CURSOR");
+ }
+
+ null_cursor = cursor;
+ }
+ cursor_set_shape(CURSOR_BUSY);
+
+ requested = None;
+
+ /*if (p_desired.layered) {
+ set_window_per_pixel_transparency_enabled(true);
+ }*/
+
+ XEvent xevent;
+ while (XPending(x11_display) > 0) {
+ XNextEvent(x11_display, &xevent);
+ if (xevent.type == ConfigureNotify) {
+ _window_changed(&xevent);
+ }
+ }
+
+ _update_real_mouse_position(windows[MAIN_WINDOW_ID]);
+
+ r_error = OK;
+}
+
+DisplayServerX11::~DisplayServerX11() {
+ //destroy all windows
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_destroy(E->key());
+ }
+#endif
+
+ if (E->get().xic) {
+ XDestroyIC(E->get().xic);
+ }
+ XUnmapWindow(x11_display, E->get().x11_window);
+ XDestroyWindow(x11_display, E->get().x11_window);
+ }
+
+ //destroy drivers
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ }
+
+ if (context_vulkan) {
+ memdelete(context_vulkan);
+ }
+ }
+#endif
+
+ if (xrandr_handle) {
+ dlclose(xrandr_handle);
+ }
+
+ for (int i = 0; i < CURSOR_MAX; i++) {
+ if (cursors[i] != None) {
+ XFreeCursor(x11_display, cursors[i]);
+ }
+ if (img[i] != nullptr) {
+ XcursorImageDestroy(img[i]);
+ }
+ };
+
+ if (xim) {
+ XCloseIM(xim);
+ }
+
+ XCloseDisplay(x11_display);
+ if (xmbstring) {
+ memfree(xmbstring);
+ }
+}
+
+void DisplayServerX11::register_x11_driver() {
+ register_create_function("x11", create_func, get_rendering_drivers_func);
+}
+
+#endif // X11 enabled
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
new file mode 100644
index 0000000000..e894cf515c
--- /dev/null
+++ b/platform/linuxbsd/display_server_x11.h
@@ -0,0 +1,371 @@
+/*************************************************************************/
+/* display_server_x11.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_X11_H
+#define DISPLAY_SERVER_X11_H
+
+#ifdef X11_ENABLED
+
+#include "servers/display_server.h"
+
+#include "core/input/input.h"
+#include "drivers/alsa/audio_driver_alsa.h"
+#include "drivers/alsamidi/midi_driver_alsamidi.h"
+#include "drivers/pulseaudio/audio_driver_pulseaudio.h"
+#include "drivers/unix/os_unix.h"
+#include "joypad_linux.h"
+#include "servers/audio_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering_server.h"
+
+#if defined(OPENGL_ENABLED)
+#include "context_gl_x11.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/linuxbsd/vulkan_context_x11.h"
+#endif
+
+#include <X11/Xcursor/Xcursor.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/keysym.h>
+
+// Hints for X11 fullscreen
+typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+} Hints;
+
+typedef struct _xrr_monitor_info {
+ Atom name;
+ Bool primary;
+ Bool automatic;
+ int noutput;
+ int x;
+ int y;
+ int width;
+ int height;
+ int mwidth;
+ int mheight;
+ RROutput *outputs;
+} xrr_monitor_info;
+
+#undef CursorShape
+
+class DisplayServerX11 : public DisplayServer {
+ //No need to register, it's platform-specific and nothing is added
+ //GDCLASS(DisplayServerX11, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+ Atom wm_delete;
+ Atom xdnd_enter;
+ Atom xdnd_position;
+ Atom xdnd_status;
+ Atom xdnd_action_copy;
+ Atom xdnd_drop;
+ Atom xdnd_finished;
+ Atom xdnd_selection;
+ Atom xdnd_aware;
+ Atom requested;
+ int xdnd_version;
+
+#if defined(OPENGL_ENABLED)
+ ContextGL_X11 *context_gles2;
+#endif
+#if defined(VULKAN_ENABLED)
+ VulkanContextX11 *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ struct WindowData {
+ Window x11_window;
+ ::XIC xic;
+
+ Size2i min_size;
+ Size2i max_size;
+ Point2i position;
+ Size2i size;
+ Point2i im_position;
+ bool im_active = false;
+ Callable rect_changed_callback;
+ Callable event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ WindowID transient_parent = INVALID_WINDOW_ID;
+ Set<WindowID> transient_children;
+
+ ObjectID instance_id;
+
+ bool menu_type = false;
+ bool no_focus = false;
+
+ //better to guess on the fly, given WM can change it
+ //WindowMode mode;
+ bool fullscreen = false; //OS can't exit from this mode
+ bool on_top = false;
+ bool borderless = false;
+ bool resize_disabled = false;
+ Vector2i last_position_before_fs;
+ bool focused = false;
+ bool minimized = false;
+
+ unsigned int focus_order = 0;
+ };
+
+ Map<WindowID, WindowData> windows;
+
+ WindowID window_id_counter = MAIN_WINDOW_ID;
+ WindowID _create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect);
+
+ String internal_clipboard;
+ Window xdnd_source_window;
+ ::Display *x11_display;
+ char *xmbstring;
+ int xmblen;
+ unsigned long last_timestamp;
+ ::Time last_keyrelease_time;
+ ::XIM xim;
+ ::XIMStyle xim_style;
+ static void _xim_destroy_callback(::XIM im, ::XPointer client_data,
+ ::XPointer call_data);
+
+ Point2i last_mouse_pos;
+ bool last_mouse_pos_valid;
+ Point2i last_click_pos;
+ uint64_t last_click_ms;
+ int last_click_button_index;
+ uint32_t last_button_state;
+ bool app_focused = false;
+ uint64_t time_since_no_focus = 0;
+
+ struct {
+ int opcode;
+ Vector<int> touch_devices;
+ Map<int, Vector2> absolute_devices;
+ Map<int, Vector2> pen_pressure_range;
+ Map<int, Vector2> pen_tilt_x_range;
+ Map<int, Vector2> pen_tilt_y_range;
+ XIEventMask all_event_mask;
+ Map<int, Vector2> state;
+ double pressure;
+ bool pressure_supported;
+ Vector2 tilt;
+ Vector2 mouse_pos_to_filter;
+ Vector2 relative_motion;
+ Vector2 raw_pos;
+ Vector2 old_raw_pos;
+ ::Time last_relative_time;
+ } xi;
+
+ bool _refresh_device_info();
+
+ unsigned int _get_mouse_button_state(unsigned int p_x11_button, int p_x11_type);
+ void _get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state);
+ void _flush_mouse_motion();
+
+ MouseMode mouse_mode;
+ Point2i center;
+
+ void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false);
+
+ //bool minimized;
+ //bool window_has_focus;
+ bool do_mouse_warp;
+
+ const char *cursor_theme;
+ int cursor_size;
+ XcursorImage *img[CURSOR_MAX];
+ Cursor cursors[CURSOR_MAX];
+ Cursor null_cursor;
+ CursorShape current_cursor;
+ Map<CursorShape, Vector<Variant>> cursors_cache;
+
+ bool layered_window;
+
+ String rendering_driver;
+ //bool window_focused;
+ //void set_wm_border(bool p_enabled);
+ void set_wm_fullscreen(bool p_enabled);
+ void set_wm_above(bool p_enabled);
+
+ typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors);
+ typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors);
+ xrr_get_monitors_t xrr_get_monitors;
+ xrr_free_monitors_t xrr_free_monitors;
+ void *xrandr_handle;
+ Bool xrandr_ext_ok;
+
+ struct Property {
+ unsigned char *data;
+ int format, nitems;
+ Atom type;
+ };
+ static Property _read_property(Display *p_display, Window p_window, Atom p_property);
+
+ void _update_real_mouse_position(const WindowData &wd);
+ bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const;
+ void _update_size_hints(WindowID p_window);
+ void _set_wm_fullscreen(WindowID p_window, bool p_enabled);
+ void _set_wm_maximized(WindowID p_window, bool p_enabled);
+
+ void _update_context(WindowData &wd);
+
+ Context context = CONTEXT_ENGINE;
+
+ void _send_window_event(const WindowData &wd, WindowEvent p_event);
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+protected:
+ void _window_changed(XEvent *event);
+
+public:
+ virtual bool has_feature(Feature p_feature) const;
+ virtual String get_name() const;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+
+ virtual void mouse_set_mode(MouseMode p_mode);
+ virtual MouseMode mouse_get_mode() const;
+
+ virtual void mouse_warp_to_position(const Point2i &p_to);
+ virtual Point2i mouse_get_position() const;
+ virtual Point2i mouse_get_absolute_position() const;
+ virtual int mouse_get_button_state() const;
+
+ virtual void clipboard_set(const String &p_text);
+ virtual String clipboard_get() const;
+
+ virtual int get_screen_count() const;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const;
+
+ virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
+ virtual void show_window(WindowID p_id);
+ virtual void delete_sub_window(WindowID p_id);
+
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent);
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual bool can_any_window_draw() const;
+
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void cursor_set_shape(CursorShape p_shape);
+ virtual CursorShape cursor_get_shape() const;
+ virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
+
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
+
+ virtual void process_events();
+
+ virtual void release_rendering_thread();
+ virtual void make_rendering_thread();
+ virtual void swap_buffers();
+
+ virtual void set_context(Context p_context);
+
+ virtual void set_native_icon(const String &p_filename);
+ virtual void set_icon(const Ref<Image> &p_icon);
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ static void register_x11_driver();
+
+ DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerX11();
+};
+
+#endif // X11 enabled
+
+#endif // DISPLAY_SERVER_X11_H
diff --git a/platform/x11/export/export.cpp b/platform/linuxbsd/export/export.cpp
index 1c0c6ec096..86ea95c563 100644
--- a/platform/x11/export/export.cpp
+++ b/platform/linuxbsd/export/export.cpp
@@ -32,17 +32,16 @@
#include "core/os/file_access.h"
#include "editor/editor_export.h"
-#include "platform/x11/logo.gen.h"
+#include "platform/linuxbsd/logo.gen.h"
#include "scene/resources/texture.h"
static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size);
-void register_x11_exporter() {
-
+void register_linuxbsd_exporter() {
Ref<EditorExportPlatformPC> platform;
platform.instance();
- Ref<Image> img = memnew(Image(_x11_logo));
+ Ref<Image> img = memnew(Image(_linuxbsd_logo));
Ref<ImageTexture> logo;
logo.instance();
logo->create_from_image(img);
@@ -62,7 +61,6 @@ void register_x11_exporter() {
}
static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
-
// Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data
FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE);
@@ -139,7 +137,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start,
bool found = false;
for (int i = 0; i < num_sections; ++i) {
-
int64_t section_header_pos = section_table_pos + i * section_header_size;
f->seek(section_header_pos);
diff --git a/platform/x11/export/export.h b/platform/linuxbsd/export/export.h
index d94ea114a8..5ee81f485e 100644
--- a/platform/x11/export/export.h
+++ b/platform/linuxbsd/export/export.h
@@ -28,4 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-void register_x11_exporter();
+#ifndef LINUXBSD_EXPORT_H
+#define LINUXBSD_EXPORT_H
+
+void register_linuxbsd_exporter();
+
+#endif // LINUXBSD_EXPORT_H
diff --git a/platform/x11/godot_x11.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index 77b74184ad..e1796ccefe 100644
--- a/platform/x11/godot_x11.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_x11.cpp */
+/* godot_linuxbsd.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -34,14 +34,16 @@
#include <unistd.h>
#include "main/main.h"
-#include "os_x11.h"
+#include "os_linuxbsd.h"
int main(int argc, char *argv[]) {
-
- OS_X11 os;
+ OS_LinuxBSD os;
setlocale(LC_CTYPE, "");
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
char *cwd = (char *)malloc(PATH_MAX);
ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY);
char *ret = getcwd(cwd, PATH_MAX);
@@ -52,8 +54,9 @@ int main(int argc, char *argv[]) {
return 255;
}
- if (Main::start())
+ if (Main::start()) {
os.run(); // it is actually the OS that decides how to run
+ }
Main::cleanup();
if (ret) { // Previous getcwd was successful
diff --git a/platform/x11/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index a64a25aeee..fda1358dfd 100644
--- a/platform/x11/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -54,12 +54,11 @@ JoypadLinux::Joypad::Joypad() {
dpad = 0;
devpath = "";
for (int i = 0; i < MAX_ABS; i++) {
- abs_info[i] = NULL;
+ abs_info[i] = nullptr;
}
}
JoypadLinux::Joypad::~Joypad() {
-
for (int i = 0; i < MAX_ABS; i++) {
if (abs_info[i]) {
memdelete(abs_info[i]);
@@ -71,7 +70,7 @@ void JoypadLinux::Joypad::reset() {
dpad = 0;
fd = -1;
- InputDefault::JoyAxis jx;
+ Input::JoyAxis jx;
jx.min = -1;
jx.value = 0.0f;
for (int i = 0; i < MAX_ABS; i++) {
@@ -80,10 +79,9 @@ void JoypadLinux::Joypad::reset() {
}
}
-JoypadLinux::JoypadLinux(InputDefault *in) {
+JoypadLinux::JoypadLinux(Input *in) {
exit_udev = false;
input = in;
- joy_mutex = Mutex::create();
joy_thread = Thread::create(joy_thread_func, this);
}
@@ -91,12 +89,10 @@ JoypadLinux::~JoypadLinux() {
exit_udev = true;
Thread::wait_to_finish(joy_thread);
memdelete(joy_thread);
- memdelete(joy_mutex);
close_joypad();
}
void JoypadLinux::joy_thread_func(void *p_user) {
-
if (p_user) {
JoypadLinux *joy = (JoypadLinux *)p_user;
joy->run_joypad_thread();
@@ -117,7 +113,6 @@ void JoypadLinux::run_joypad_thread() {
#ifdef UDEV_ENABLED
void JoypadLinux::enumerate_joypads(udev *p_udev) {
-
udev_enumerate *enumerate;
udev_list_entry *devices, *dev_list_entry;
udev_device *dev;
@@ -128,18 +123,15 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) {
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices) {
-
const char *path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(p_udev, path);
const char *devnode = udev_device_get_devnode(dev);
if (devnode) {
-
String devnode_str = devnode;
if (devnode_str.find(ignore_str) == -1) {
- joy_mutex->lock();
+ MutexLock lock(joy_mutex);
open_joypad(devnode);
- joy_mutex->unlock();
}
}
udev_device_unref(dev);
@@ -148,15 +140,13 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) {
}
void JoypadLinux::monitor_joypads(udev *p_udev) {
-
- udev_device *dev = NULL;
+ udev_device *dev = nullptr;
udev_monitor *mon = udev_monitor_new_from_netlink(p_udev, "udev");
- udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
+ udev_monitor_filter_add_match_subsystem_devtype(mon, "input", nullptr);
udev_monitor_enable_receiving(mon);
int fd = udev_monitor_get_fd(mon);
while (!exit_udev) {
-
fd_set fds;
struct timeval tv;
int ret;
@@ -166,7 +156,7 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
tv.tv_sec = 0;
tv.tv_usec = 0;
- ret = select(fd + 1, &fds, NULL, NULL, &tv);
+ ret = select(fd + 1, &fds, nullptr, nullptr, &tv);
/* Check if our file descriptor has received data. */
if (ret > 0 && FD_ISSET(fd, &fds)) {
@@ -175,15 +165,12 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
dev = udev_monitor_receive_device(mon);
if (dev && udev_device_get_devnode(dev) != 0) {
-
- joy_mutex->lock();
+ MutexLock lock(joy_mutex);
String action = udev_device_get_action(dev);
const char *devnode = udev_device_get_devnode(dev);
if (devnode) {
-
String devnode_str = devnode;
if (devnode_str.find(ignore_str) == -1) {
-
if (action == "add")
open_joypad(devnode);
else if (String(action) == "remove")
@@ -192,7 +179,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
}
udev_device_unref(dev);
- joy_mutex->unlock();
}
}
usleep(50000);
@@ -202,25 +188,24 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
#endif
void JoypadLinux::monitor_joypads() {
-
while (!exit_udev) {
- joy_mutex->lock();
- for (int i = 0; i < 32; i++) {
- char fname[64];
- sprintf(fname, "/dev/input/event%d", i);
- if (attached_devices.find(fname) == -1) {
- open_joypad(fname);
+ {
+ MutexLock lock(joy_mutex);
+
+ for (int i = 0; i < 32; i++) {
+ char fname[64];
+ sprintf(fname, "/dev/input/event%d", i);
+ if (attached_devices.find(fname) == -1) {
+ open_joypad(fname);
+ }
}
}
- joy_mutex->unlock();
usleep(1000000); // 1s
}
}
int JoypadLinux::get_joy_from_path(String p_path) const {
-
for (int i = 0; i < JOYPADS_MAX; i++) {
-
if (joypads[i].devpath == p_path) {
return i;
}
@@ -231,17 +216,16 @@ int JoypadLinux::get_joy_from_path(String p_path) const {
void JoypadLinux::close_joypad(int p_id) {
if (p_id == -1) {
for (int i = 0; i < JOYPADS_MAX; i++) {
-
close_joypad(i);
};
return;
- } else if (p_id < 0)
+ } else if (p_id < 0) {
return;
+ }
Joypad &joy = joypads[p_id];
if (joy.fd != -1) {
-
close(joy.fd);
joy.fd = -1;
attached_devices.remove(attached_devices.find(joy.devpath));
@@ -250,7 +234,6 @@ void JoypadLinux::close_joypad(int p_id) {
}
static String _hex_str(uint8_t p_byte) {
-
static const char *dict = "0123456789abcdef";
char ret[3];
ret[2] = 0;
@@ -262,7 +245,6 @@ static String _hex_str(uint8_t p_byte) {
}
void JoypadLinux::setup_joypad_properties(int p_id) {
-
Joypad *joy = &joypads[p_id];
unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
@@ -276,16 +258,12 @@ void JoypadLinux::setup_joypad_properties(int p_id) {
return;
}
for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
-
if (test_bit(i, keybit)) {
-
joy->key_map[i] = num_buttons++;
}
}
for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) {
-
if (test_bit(i, keybit)) {
-
joy->key_map[i] = num_buttons++;
}
}
@@ -296,12 +274,11 @@ void JoypadLinux::setup_joypad_properties(int p_id) {
continue;
}
if (test_bit(i, absbit)) {
-
joy->abs_map[i] = num_axes++;
joy->abs_info[i] = memnew(input_absinfo);
if (ioctl(joy->fd, EVIOCGABS(i), joy->abs_info[i]) < 0) {
memdelete(joy->abs_info[i]);
- joy->abs_info[i] = NULL;
+ joy->abs_info[i] = nullptr;
}
}
}
@@ -317,11 +294,9 @@ void JoypadLinux::setup_joypad_properties(int p_id) {
}
void JoypadLinux::open_joypad(const char *p_path) {
-
int joy_num = input->get_unused_joy_id();
int fd = open(p_path, O_RDWR | O_NONBLOCK);
if (fd != -1 && joy_num != -1) {
-
unsigned long evbit[NBITS(EV_MAX)] = { 0 };
unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
@@ -336,13 +311,9 @@ void JoypadLinux::open_joypad(const char *p_path) {
return;
}
- //check if the device supports basic gamepad events, prevents certain keyboards from
- //being detected as joypads
+ // Check if the device supports basic gamepad events
if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) &&
- (test_bit(ABS_X, absbit) || test_bit(ABS_Y, absbit) || test_bit(ABS_HAT0X, absbit) ||
- test_bit(ABS_GAS, absbit) || test_bit(ABS_RUDDER, absbit)) &&
- (test_bit(BTN_A, keybit) || test_bit(BTN_THUMBL, keybit) ||
- test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_1, keybit)))) {
+ test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) {
close(fd);
return;
}
@@ -368,7 +339,6 @@ void JoypadLinux::open_joypad(const char *p_path) {
setup_joypad_properties(joy_num);
sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0);
if (inpid.vendor && inpid.product && inpid.version) {
-
uint16_t vendor = BSWAP16(inpid.vendor);
uint16_t product = BSWAP16(inpid.product);
uint16_t version = BSWAP16(inpid.version);
@@ -379,7 +349,6 @@ void JoypadLinux::open_joypad(const char *p_path) {
String uidname = uid;
int uidlen = MIN(name.length(), 11);
for (int i = 0; i < uidlen; i++) {
-
uidname = uidname + _hex_str(name[i]);
}
uidname += "00";
@@ -435,11 +404,10 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
joy.ff_effect_timestamp = p_timestamp;
}
-InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
-
+Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
int min = p_abs->minimum;
int max = p_abs->maximum;
- InputDefault::JoyAxis jx;
+ Input::JoyAxis jx;
if (min < 0) {
jx.min = -1;
@@ -456,13 +424,13 @@ InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int
}
void JoypadLinux::process_joypads() {
-
- if (joy_mutex->try_lock() != OK) {
+ if (joy_mutex.try_lock() != OK) {
return;
}
for (int i = 0; i < JOYPADS_MAX; i++) {
-
- if (joypads[i].fd == -1) continue;
+ if (joypads[i].fd == -1) {
+ continue;
+ }
input_event events[32];
Joypad *joy = &joypads[i];
@@ -472,13 +440,13 @@ void JoypadLinux::process_joypads() {
while ((len = read(joy->fd, events, (sizeof events))) > 0) {
len /= sizeof(events[0]);
for (int j = 0; j < len; j++) {
-
input_event &ev = events[j];
// ev may be tainted and out of MAX_KEY range, which will cause
// joy->key_map[ev.code] to crash
- if (ev.code >= MAX_KEY)
+ if (ev.code >= MAX_KEY) {
return;
+ }
switch (ev.type) {
case EV_KEY:
@@ -490,33 +458,38 @@ void JoypadLinux::process_joypads() {
switch (ev.code) {
case ABS_HAT0X:
if (ev.value != 0) {
- if (ev.value < 0)
- joy->dpad |= InputDefault::HAT_MASK_LEFT;
- else
- joy->dpad |= InputDefault::HAT_MASK_RIGHT;
- } else
- joy->dpad &= ~(InputDefault::HAT_MASK_LEFT | InputDefault::HAT_MASK_RIGHT);
+ if (ev.value < 0) {
+ joy->dpad |= Input::HAT_MASK_LEFT;
+ } else {
+ joy->dpad |= Input::HAT_MASK_RIGHT;
+ }
+ } else {
+ joy->dpad &= ~(Input::HAT_MASK_LEFT | Input::HAT_MASK_RIGHT);
+ }
input->joy_hat(i, joy->dpad);
break;
case ABS_HAT0Y:
if (ev.value != 0) {
- if (ev.value < 0)
- joy->dpad |= InputDefault::HAT_MASK_UP;
- else
- joy->dpad |= InputDefault::HAT_MASK_DOWN;
- } else
- joy->dpad &= ~(InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_DOWN);
+ if (ev.value < 0) {
+ joy->dpad |= Input::HAT_MASK_UP;
+ } else {
+ joy->dpad |= Input::HAT_MASK_DOWN;
+ }
+ } else {
+ joy->dpad &= ~(Input::HAT_MASK_UP | Input::HAT_MASK_DOWN);
+ }
input->joy_hat(i, joy->dpad);
break;
default:
- if (ev.code >= MAX_ABS)
+ if (ev.code >= MAX_ABS) {
return;
+ }
if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) {
- InputDefault::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value);
+ Input::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value);
joy->curr_axis[joy->abs_map[ev.code]] = value;
}
break;
@@ -548,6 +521,6 @@ void JoypadLinux::process_joypads() {
}
}
}
- joy_mutex->unlock();
+ joy_mutex.unlock();
}
#endif
diff --git a/platform/x11/joypad_linux.h b/platform/linuxbsd/joypad_linux.h
index e5638899bf..0d175193a5 100644
--- a/platform/x11/joypad_linux.h
+++ b/platform/linuxbsd/joypad_linux.h
@@ -33,15 +33,15 @@
#define JOYPAD_LINUX_H
#ifdef JOYDEV_ENABLED
+#include "core/input/input.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
-#include "main/input_default.h"
struct input_absinfo;
class JoypadLinux {
public:
- JoypadLinux(InputDefault *in);
+ JoypadLinux(Input *in);
~JoypadLinux();
void process_joypads();
@@ -53,7 +53,7 @@ private:
};
struct Joypad {
- InputDefault::JoyAxis curr_axis[MAX_ABS];
+ Input::JoyAxis curr_axis[MAX_ABS];
int key_map[MAX_KEY];
int abs_map[MAX_ABS];
int dpad;
@@ -72,9 +72,9 @@ private:
};
bool exit_udev;
- Mutex *joy_mutex;
+ Mutex joy_mutex;
Thread *joy_thread;
- InputDefault *input;
+ Input *input;
Joypad joypads[JOYPADS_MAX];
Vector<String> attached_devices;
@@ -95,7 +95,7 @@ private:
void joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
void joypad_vibration_stop(int p_id, uint64_t p_timestamp);
- InputDefault::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const;
+ Input::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const;
};
#endif
diff --git a/platform/x11/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp
index 54e1e1d357..77512b1a9e 100644
--- a/platform/x11/key_mapping_x11.cpp
+++ b/platform/linuxbsd/key_mapping_x11.cpp
@@ -33,7 +33,6 @@
/***** SCAN CODE CONVERSION ******/
struct _XTranslatePair {
-
KeySym keysym;
unsigned int keycode;
};
@@ -180,35 +179,166 @@ static _XTranslatePair _xkeysym_to_keycode[] = {
{ 0, 0 }
};
-unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) {
+struct _TranslatePair {
+ unsigned int keysym;
+ unsigned int keycode;
+};
+
+static _TranslatePair _scancode_to_keycode[] = {
+
+ { KEY_ESCAPE, 0x09 },
+ { KEY_1, 0x0A },
+ { KEY_2, 0x0B },
+ { KEY_3, 0x0C },
+ { KEY_4, 0x0D },
+ { KEY_5, 0x0E },
+ { KEY_6, 0x0F },
+ { KEY_7, 0x10 },
+ { KEY_8, 0x11 },
+ { KEY_9, 0x12 },
+ { KEY_0, 0x13 },
+ { KEY_MINUS, 0x14 },
+ { KEY_EQUAL, 0x15 },
+ { KEY_BACKSPACE, 0x16 },
+ { KEY_TAB, 0x17 },
+ { KEY_Q, 0x18 },
+ { KEY_W, 0x19 },
+ { KEY_E, 0x1A },
+ { KEY_R, 0x1B },
+ { KEY_T, 0x1C },
+ { KEY_Y, 0x1D },
+ { KEY_U, 0x1E },
+ { KEY_I, 0x1F },
+ { KEY_O, 0x20 },
+ { KEY_P, 0x21 },
+ { KEY_BRACELEFT, 0x22 },
+ { KEY_BRACERIGHT, 0x23 },
+ { KEY_ENTER, 0x24 },
+ { KEY_CONTROL, 0x25 },
+ { KEY_A, 0x26 },
+ { KEY_S, 0x27 },
+ { KEY_D, 0x28 },
+ { KEY_F, 0x29 },
+ { KEY_G, 0x2A },
+ { KEY_H, 0x2B },
+ { KEY_J, 0x2C },
+ { KEY_K, 0x2D },
+ { KEY_L, 0x2E },
+ { KEY_SEMICOLON, 0x2F },
+ { KEY_APOSTROPHE, 0x30 },
+ { KEY_QUOTELEFT, 0x31 },
+ { KEY_SHIFT, 0x32 },
+ { KEY_BACKSLASH, 0x33 },
+ { KEY_Z, 0x34 },
+ { KEY_X, 0x35 },
+ { KEY_C, 0x36 },
+ { KEY_V, 0x37 },
+ { KEY_B, 0x38 },
+ { KEY_N, 0x39 },
+ { KEY_M, 0x3A },
+ { KEY_COMMA, 0x3B },
+ { KEY_PERIOD, 0x3C },
+ { KEY_SLASH, 0x3D },
+ { KEY_SHIFT, 0x3E },
+ { KEY_KP_MULTIPLY, 0x3F },
+ { KEY_ALT, 0x40 },
+ { KEY_SPACE, 0x41 },
+ { KEY_CAPSLOCK, 0x42 },
+ { KEY_F1, 0x43 },
+ { KEY_F2, 0x44 },
+ { KEY_F3, 0x45 },
+ { KEY_F4, 0x46 },
+ { KEY_F5, 0x47 },
+ { KEY_F6, 0x48 },
+ { KEY_F7, 0x49 },
+ { KEY_F8, 0x4A },
+ { KEY_F9, 0x4B },
+ { KEY_F10, 0x4C },
+ { KEY_NUMLOCK, 0x4D },
+ { KEY_SCROLLLOCK, 0x4E },
+ { KEY_KP_7, 0x4F },
+ { KEY_KP_8, 0x50 },
+ { KEY_KP_9, 0x51 },
+ { KEY_KP_SUBTRACT, 0x52 },
+ { KEY_KP_4, 0x53 },
+ { KEY_KP_5, 0x54 },
+ { KEY_KP_6, 0x55 },
+ { KEY_KP_ADD, 0x56 },
+ { KEY_KP_1, 0x57 },
+ { KEY_KP_2, 0x58 },
+ { KEY_KP_3, 0x59 },
+ { KEY_KP_0, 0x5A },
+ { KEY_KP_PERIOD, 0x5B },
+ //{ KEY_???, 0x5E }, //NON US BACKSLASH
+ { KEY_F11, 0x5F },
+ { KEY_F12, 0x60 },
+ { KEY_KP_ENTER, 0x68 },
+ { KEY_CONTROL, 0x69 },
+ { KEY_KP_DIVIDE, 0x6A },
+ { KEY_PRINT, 0x6B },
+ { KEY_ALT, 0x6C },
+ { KEY_ENTER, 0x6D },
+ { KEY_HOME, 0x6E },
+ { KEY_UP, 0x6F },
+ { KEY_PAGEUP, 0x70 },
+ { KEY_LEFT, 0x71 },
+ { KEY_RIGHT, 0x72 },
+ { KEY_END, 0x73 },
+ { KEY_DOWN, 0x74 },
+ { KEY_PAGEDOWN, 0x75 },
+ { KEY_INSERT, 0x76 },
+ { KEY_DELETE, 0x77 },
+ { KEY_VOLUMEMUTE, 0x79 },
+ { KEY_VOLUMEDOWN, 0x7A },
+ { KEY_VOLUMEUP, 0x7B },
+ { KEY_PAUSE, 0x7F },
+ { KEY_SUPER_L, 0x85 },
+ { KEY_SUPER_R, 0x86 },
+ { KEY_MENU, 0x87 },
+ { KEY_UNKNOWN, 0 }
+};
+unsigned int KeyMappingX11::get_scancode(unsigned int p_code) {
+ unsigned int keycode = KEY_UNKNOWN;
+ for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+ if (_scancode_to_keycode[i].keycode == p_code) {
+ keycode = _scancode_to_keycode[i].keysym;
+ break;
+ }
+ }
+
+ return keycode;
+}
+
+unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) {
// kinda bruteforce.. could optimize.
- if (p_keysym < 0x100) // Latin 1, maps 1-1
+ if (p_keysym < 0x100) { // Latin 1, maps 1-1
return p_keysym;
+ }
// look for special key
for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) {
-
- if (_xkeysym_to_keycode[idx].keysym == p_keysym)
+ if (_xkeysym_to_keycode[idx].keysym == p_keysym) {
return _xkeysym_to_keycode[idx].keycode;
+ }
}
return 0;
}
KeySym KeyMappingX11::get_keysym(unsigned int p_code) {
-
// kinda bruteforce.. could optimize.
- if (p_code < 0x100) // Latin 1, maps 1-1
+ if (p_code < 0x100) { // Latin 1, maps 1-1
return p_code;
+ }
// look for special key
for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) {
-
- if (_xkeysym_to_keycode[idx].keycode == p_code)
+ if (_xkeysym_to_keycode[idx].keycode == p_code) {
return _xkeysym_to_keycode[idx].keysym;
+ }
}
return 0;
@@ -219,7 +349,6 @@ KeySym KeyMappingX11::get_keysym(unsigned int p_code) {
// Tables taken from FOX toolkit
struct _XTranslateUnicodePair {
-
KeySym keysym;
unsigned int unicode;
};
@@ -991,37 +1120,41 @@ static _XTranslateUnicodePair _xkeysym_to_unicode[_KEYSYM_MAX] = {
};
unsigned int KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) {
-
/* Latin-1 */
- if (p_keysym >= 0x20 && p_keysym <= 0x7e)
+ if (p_keysym >= 0x20 && p_keysym <= 0x7e) {
return p_keysym;
- if (p_keysym >= 0xa0 && p_keysym <= 0xff)
+ }
+ if (p_keysym >= 0xa0 && p_keysym <= 0xff) {
return p_keysym;
+ }
// keypad to latin1 is easy
- if (p_keysym >= 0xffaa && p_keysym <= 0xffb9)
+ if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) {
return p_keysym - 0xff80;
+ }
/* Unicode (may be present)*/
- if ((p_keysym & 0xff000000) == 0x01000000)
+ if ((p_keysym & 0xff000000) == 0x01000000) {
return p_keysym & 0x00ffffff;
+ }
int middle, low = 0, high = _KEYSYM_MAX - 1;
do {
middle = (high + low) / 2;
- if (_xkeysym_to_unicode[middle].keysym == p_keysym)
+ if (_xkeysym_to_unicode[middle].keysym == p_keysym) {
return _xkeysym_to_unicode[middle].unicode;
- if (_xkeysym_to_unicode[middle].keysym <= p_keysym)
+ }
+ if (_xkeysym_to_unicode[middle].keysym <= p_keysym) {
low = middle + 1;
- else
+ } else {
high = middle - 1;
+ }
} while (high >= low);
return 0;
}
struct _XTranslateUnicodePairReverse {
-
unsigned int unicode;
KeySym keysym;
};
@@ -1785,24 +1918,27 @@ static _XTranslateUnicodePairReverse _unicode_to_xkeysym[_UNICODE_MAX] = {
};
KeySym KeyMappingX11::get_keysym_from_unicode(unsigned int p_unicode) {
-
/* Latin 1 */
- if (p_unicode >= 0x20 && p_unicode <= 0x7e)
+ if (p_unicode >= 0x20 && p_unicode <= 0x7e) {
return p_unicode;
+ }
- if (p_unicode >= 0xa0 && p_unicode <= 0xff)
+ if (p_unicode >= 0xa0 && p_unicode <= 0xff) {
return p_unicode;
+ }
int middle, low = 0, high = _UNICODE_MAX - 1;
do {
middle = (high + low) / 2;
- if (_unicode_to_xkeysym[middle].keysym == p_unicode)
+ if (_unicode_to_xkeysym[middle].keysym == p_unicode) {
return _unicode_to_xkeysym[middle].keysym;
- if (_unicode_to_xkeysym[middle].keysym <= p_unicode)
+ }
+ if (_unicode_to_xkeysym[middle].keysym <= p_unicode) {
low = middle + 1;
- else
+ } else {
high = middle - 1;
+ }
} while (high >= low);
// if not found, let's hope X understands it as unicode
diff --git a/platform/x11/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h
index e99bf1694b..8f5e01a3c2 100644
--- a/platform/x11/key_mapping_x11.h
+++ b/platform/linuxbsd/key_mapping_x11.h
@@ -41,10 +41,11 @@
#include "core/os/keyboard.h"
class KeyMappingX11 {
- KeyMappingX11(){};
+ KeyMappingX11() {}
public:
static unsigned int get_keycode(KeySym p_keysym);
+ static unsigned int get_scancode(unsigned int p_code);
static KeySym get_keysym(unsigned int p_code);
static unsigned int get_unicode_from_keysym(KeySym p_keysym);
static KeySym get_keysym_from_unicode(unsigned int p_unicode);
diff --git a/platform/x11/logo.png b/platform/linuxbsd/logo.png
index 078654b757..078654b757 100644
--- a/platform/x11/logo.png
+++ b/platform/linuxbsd/logo.png
Binary files differ
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
new file mode 100644
index 0000000000..e00a32e3ba
--- /dev/null
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -0,0 +1,362 @@
+/*************************************************************************/
+/* os_linuxbsd.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "os_linuxbsd.h"
+
+#include "core/os/dir_access.h"
+#include "main/main.h"
+
+#ifdef X11_ENABLED
+#include "display_server_x11.h"
+#endif
+
+#ifdef HAVE_MNTENT
+#include <mntent.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+void OS_LinuxBSD::initialize() {
+ crash_handler.initialize();
+
+ OS_Unix::initialize_core();
+}
+
+void OS_LinuxBSD::initialize_joypads() {
+#ifdef JOYDEV_ENABLED
+ joypad = memnew(JoypadLinux(Input::get_singleton()));
+#endif
+}
+
+String OS_LinuxBSD::get_unique_id() const {
+ static String machine_id;
+ if (machine_id.empty()) {
+ if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) {
+ while (machine_id.empty() && !f->eof_reached()) {
+ machine_id = f->get_line().strip_edges();
+ }
+ f->close();
+ memdelete(f);
+ }
+ }
+ return machine_id;
+}
+
+void OS_LinuxBSD::finalize() {
+ if (main_loop) {
+ memdelete(main_loop);
+ }
+ main_loop = nullptr;
+
+#ifdef ALSAMIDI_ENABLED
+ driver_alsamidi.close();
+#endif
+
+#ifdef JOYDEV_ENABLED
+ if (joypad) {
+ memdelete(joypad);
+ }
+#endif
+}
+
+MainLoop *OS_LinuxBSD::get_main_loop() const {
+ return main_loop;
+}
+
+void OS_LinuxBSD::delete_main_loop() {
+ if (main_loop) {
+ memdelete(main_loop);
+ }
+ main_loop = nullptr;
+}
+
+void OS_LinuxBSD::set_main_loop(MainLoop *p_main_loop) {
+ main_loop = p_main_loop;
+}
+
+String OS_LinuxBSD::get_name() const {
+#ifdef __linux__
+ return "Linux";
+#elif defined(__FreeBSD__)
+ return "FreeBSD";
+#elif defined(__NetBSD__)
+ return "NetBSD";
+#else
+ return "BSD";
+#endif
+}
+
+Error OS_LinuxBSD::shell_open(String p_uri) {
+ Error ok;
+ List<String> args;
+ args.push_back(p_uri);
+ ok = execute("xdg-open", args, false);
+ if (ok == OK) {
+ return OK;
+ }
+ ok = execute("gnome-open", args, false);
+ if (ok == OK) {
+ return OK;
+ }
+ ok = execute("kde-open", args, false);
+ return ok;
+}
+
+bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "pc";
+}
+
+String OS_LinuxBSD::get_config_path() const {
+ if (has_environment("XDG_CONFIG_HOME")) {
+ return get_environment("XDG_CONFIG_HOME");
+ } else if (has_environment("HOME")) {
+ return get_environment("HOME").plus_file(".config");
+ } else {
+ return ".";
+ }
+}
+
+String OS_LinuxBSD::get_data_path() const {
+ if (has_environment("XDG_DATA_HOME")) {
+ return get_environment("XDG_DATA_HOME");
+ } else if (has_environment("HOME")) {
+ return get_environment("HOME").plus_file(".local/share");
+ } else {
+ return get_config_path();
+ }
+}
+
+String OS_LinuxBSD::get_cache_path() const {
+ if (has_environment("XDG_CACHE_HOME")) {
+ return get_environment("XDG_CACHE_HOME");
+ } else if (has_environment("HOME")) {
+ return get_environment("HOME").plus_file(".cache");
+ } else {
+ return get_config_path();
+ }
+}
+
+String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const {
+ String xdgparam;
+
+ switch (p_dir) {
+ case SYSTEM_DIR_DESKTOP: {
+ xdgparam = "DESKTOP";
+ } break;
+ case SYSTEM_DIR_DCIM: {
+ xdgparam = "PICTURES";
+
+ } break;
+ case SYSTEM_DIR_DOCUMENTS: {
+ xdgparam = "DOCUMENTS";
+
+ } break;
+ case SYSTEM_DIR_DOWNLOADS: {
+ xdgparam = "DOWNLOAD";
+
+ } break;
+ case SYSTEM_DIR_MOVIES: {
+ xdgparam = "VIDEOS";
+
+ } break;
+ case SYSTEM_DIR_MUSIC: {
+ xdgparam = "MUSIC";
+
+ } break;
+ case SYSTEM_DIR_PICTURES: {
+ xdgparam = "PICTURES";
+
+ } break;
+ case SYSTEM_DIR_RINGTONES: {
+ xdgparam = "MUSIC";
+
+ } break;
+ }
+
+ String pipe;
+ List<String> arg;
+ arg.push_back(xdgparam);
+ Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe);
+ if (err != OK) {
+ return ".";
+ }
+ return pipe.strip_edges();
+}
+
+void OS_LinuxBSD::run() {
+ force_quit = false;
+
+ if (!main_loop) {
+ return;
+ }
+
+ main_loop->init();
+
+ //uint64_t last_ticks=get_ticks_usec();
+
+ //int frames=0;
+ //uint64_t frame=0;
+
+ while (!force_quit) {
+ DisplayServer::get_singleton()->process_events(); // get rid of pending events
+#ifdef JOYDEV_ENABLED
+ joypad->process_joypads();
+#endif
+ if (Main::iteration()) {
+ break;
+ }
+ };
+
+ main_loop->finish();
+}
+
+void OS_LinuxBSD::disable_crash_handler() {
+ crash_handler.disable();
+}
+
+bool OS_LinuxBSD::is_disable_crash_handler() const {
+ return crash_handler.is_disabled();
+}
+
+static String get_mountpoint(const String &p_path) {
+ struct stat s;
+ if (stat(p_path.utf8().get_data(), &s)) {
+ return "";
+ }
+
+#ifdef HAVE_MNTENT
+ dev_t dev = s.st_dev;
+ FILE *fd = setmntent("/proc/mounts", "r");
+ if (!fd) {
+ return "";
+ }
+
+ struct mntent mnt;
+ char buf[1024];
+ size_t buflen = 1024;
+ while (getmntent_r(fd, &mnt, buf, buflen)) {
+ if (!stat(mnt.mnt_dir, &s) && s.st_dev == dev) {
+ endmntent(fd);
+ return String(mnt.mnt_dir);
+ }
+ }
+
+ endmntent(fd);
+#endif
+ return "";
+}
+
+Error OS_LinuxBSD::move_to_trash(const String &p_path) {
+ String trash_can = "";
+ String mnt = get_mountpoint(p_path);
+
+ // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can.
+ if (mnt != "") {
+ String path(mnt + "/.Trash-" + itos(getuid()) + "/files");
+ struct stat s;
+ if (!stat(path.utf8().get_data(), &s)) {
+ trash_can = path;
+ }
+ }
+
+ // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can.
+ if (trash_can == "") {
+ char *dhome = getenv("XDG_DATA_HOME");
+ if (dhome) {
+ trash_can = String(dhome) + "/Trash/files";
+ }
+ }
+
+ // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can.
+ if (trash_can == "") {
+ char *home = getenv("HOME");
+ if (home) {
+ trash_can = String(home) + "/.local/share/Trash/files";
+ }
+ }
+
+ // Issue an error if none of the previous locations is appropriate for the trash can.
+ if (trash_can == "") {
+ ERR_PRINT("move_to_trash: Could not determine the trash can location");
+ return FAILED;
+ }
+
+ // Create needed directories for decided trash can location.
+ DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Error err = dir_access->make_dir_recursive(trash_can);
+ memdelete(dir_access);
+
+ // Issue an error if trash can is not created proprely.
+ if (err != OK) {
+ ERR_PRINT("move_to_trash: Could not create the trash can \"" + trash_can + "\"");
+ return err;
+ }
+
+ // The trash can is successfully created, now move the given resource to it.
+ // Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
+ List<String> mv_args;
+ mv_args.push_back(p_path);
+ mv_args.push_back(trash_can);
+ int retval;
+ err = execute("mv", mv_args, true, nullptr, nullptr, &retval);
+
+ // Issue an error if "mv" failed to move the given resource to the trash can.
+ if (err != OK || retval != 0) {
+ ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\"");
+ return FAILED;
+ }
+
+ return OK;
+}
+
+OS_LinuxBSD::OS_LinuxBSD() {
+ main_loop = nullptr;
+ force_quit = false;
+
+#ifdef PULSEAUDIO_ENABLED
+ AudioDriverManager::add_driver(&driver_pulseaudio);
+#endif
+
+#ifdef ALSA_ENABLED
+ AudioDriverManager::add_driver(&driver_alsa);
+#endif
+
+#ifdef X11_ENABLED
+ DisplayServerX11::register_x11_driver();
+#endif
+}
diff --git a/platform/haiku/audio_driver_media_kit.h b/platform/linuxbsd/os_linuxbsd.h
index 06a362a89e..cd4fbd9db5 100644
--- a/platform/haiku/audio_driver_media_kit.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* audio_driver_media_kit.h */
+/* os_linuxbsd.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,47 +28,78 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef OS_LINUXBSD_H
+#define OS_LINUXBSD_H
+
+#include "core/input/input.h"
+#include "crash_handler_linuxbsd.h"
+#include "drivers/alsa/audio_driver_alsa.h"
+#include "drivers/alsamidi/midi_driver_alsamidi.h"
+#include "drivers/pulseaudio/audio_driver_pulseaudio.h"
+#include "drivers/unix/os_unix.h"
+#include "joypad_linux.h"
#include "servers/audio_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering_server.h"
-#ifdef MEDIA_KIT_ENABLED
+class OS_LinuxBSD : public OS_Unix {
+ virtual void delete_main_loop();
-#include "core/os/mutex.h"
-#include "core/os/thread.h"
+ bool force_quit;
-#include <kernel/image.h> // needed for image_id
+#ifdef JOYDEV_ENABLED
+ JoypadLinux *joypad = nullptr;
+#endif
-#include <SoundPlayer.h>
+#ifdef ALSA_ENABLED
+ AudioDriverALSA driver_alsa;
+#endif
-class AudioDriverMediaKit : public AudioDriver {
- Mutex *mutex;
+#ifdef ALSAMIDI_ENABLED
+ MIDIDriverALSAMidi driver_alsamidi;
+#endif
- BSoundPlayer *player;
- static int32_t *samples_in;
+#ifdef PULSEAUDIO_ENABLED
+ AudioDriverPulseAudio driver_pulseaudio;
+#endif
- static void PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format);
+ CrashHandler crash_handler;
- unsigned int mix_rate;
- SpeakerMode speaker_mode;
- unsigned int buffer_size;
- int channels;
+ MainLoop *main_loop;
- bool active;
+protected:
+ virtual void initialize();
+ virtual void finalize();
+
+ virtual void initialize_joypads();
+
+ virtual void set_main_loop(MainLoop *p_main_loop);
public:
- const char *get_name() const {
- return "MediaKit";
- };
-
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
-
- AudioDriverMediaKit();
- ~AudioDriverMediaKit();
+ virtual String get_name() const;
+
+ virtual MainLoop *get_main_loop() const;
+
+ virtual String get_config_path() const;
+ virtual String get_data_path() const;
+ virtual String get_cache_path() const;
+
+ virtual String get_system_dir(SystemDir p_dir) const;
+
+ virtual Error shell_open(String p_uri);
+
+ virtual String get_unique_id() const;
+
+ virtual bool _check_internal_feature_support(const String &p_feature);
+
+ void run();
+
+ void disable_crash_handler();
+ bool is_disable_crash_handler() const;
+
+ virtual Error move_to_trash(const String &p_path);
+
+ OS_LinuxBSD();
};
#endif
diff --git a/platform/x11/pck_embed.ld b/platform/linuxbsd/pck_embed.ld
index 57a1994043..57a1994043 100644
--- a/platform/x11/pck_embed.ld
+++ b/platform/linuxbsd/pck_embed.ld
diff --git a/platform/x11/pck_embed.legacy.ld b/platform/linuxbsd/pck_embed.legacy.ld
index a23013ba7a..a23013ba7a 100644
--- a/platform/x11/pck_embed.legacy.ld
+++ b/platform/linuxbsd/pck_embed.legacy.ld
diff --git a/platform/x11/platform_config.h b/platform/linuxbsd/platform_config.h
index c905ddb236..571ad03db0 100644
--- a/platform/x11/platform_config.h
+++ b/platform/linuxbsd/platform_config.h
@@ -31,10 +31,15 @@
#ifdef __linux__
#include <alloca.h>
#endif
-#if defined(__FreeBSD__) || defined(__OpenBSD__)
-#include <stdlib.h>
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+#include <stdlib.h> // alloca
+// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms,
+// include NetBSD, use pthread_setname_np. NetBSD's version however requires
+// a different format, we handle this directly in thread_posix.
+#ifdef __NetBSD__
+#define PTHREAD_NETBSD_SET_NAME
+#else
#define PTHREAD_BSD_SET_NAME
#endif
-
-#define GLES3_INCLUDE_H "thirdparty/glad/glad/glad.h"
-#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"
+#endif
diff --git a/platform/x11/platform_x11_builders.py b/platform/linuxbsd/platform_linuxbsd_builders.py
index 5ff0c6fb14..58234f3748 100644
--- a/platform/x11/platform_x11_builders.py
+++ b/platform/linuxbsd/platform_linuxbsd_builders.py
@@ -7,11 +7,11 @@ import os
from platform_methods import subprocess_main
-def make_debug_x11(target, source, env):
- os.system('objcopy --only-keep-debug {0} {0}.debugsymbols'.format(target[0]))
- os.system('strip --strip-debug --strip-unneeded {0}'.format(target[0]))
- os.system('objcopy --add-gnu-debuglink={0}.debugsymbols {0}'.format(target[0]))
+def make_debug_linuxbsd(target, source, env):
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
-if __name__ == '__main__':
+if __name__ == "__main__":
subprocess_main(globals())
diff --git a/platform/linuxbsd/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp
new file mode 100644
index 0000000000..2eaa9f9446
--- /dev/null
+++ b/platform/linuxbsd/vulkan_context_x11.cpp
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* vulkan_context_x11.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "vulkan_context_x11.h"
+#include <vulkan/vulkan_xlib.h>
+
+const char *VulkanContextX11::_get_platform_surface_extension() const {
+ return VK_KHR_XLIB_SURFACE_EXTENSION_NAME;
+}
+
+Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) {
+ VkXlibSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = nullptr;
+ createInfo.flags = 0;
+ createInfo.dpy = p_display;
+ createInfo.window = p_window;
+
+ VkSurfaceKHR surface;
+ VkResult err = vkCreateXlibSurfaceKHR(_get_instance(), &createInfo, nullptr, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+ return _window_create(p_window_id, surface, p_width, p_height);
+}
+
+VulkanContextX11::VulkanContextX11() {
+}
+
+VulkanContextX11::~VulkanContextX11() {
+}
diff --git a/platform/osx/semaphore_osx.h b/platform/linuxbsd/vulkan_context_x11.h
index 2348c8efa6..af3d923cfe 100644
--- a/platform/osx/semaphore_osx.h
+++ b/platform/linuxbsd/vulkan_context_x11.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_osx.h */
+/* vulkan_context_x11.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,32 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef SEMAPHORE_OSX_H
-#define SEMAPHORE_OSX_H
+#ifndef VULKAN_DEVICE_X11_H
+#define VULKAN_DEVICE_X11_H
-struct cgsem {
- int pipefd[2];
-};
-
-typedef struct cgsem cgsem_t;
-
-#include "core/os/semaphore.h"
+#include "drivers/vulkan/vulkan_context.h"
+#include <X11/Xlib.h>
-class SemaphoreOSX : public Semaphore {
-
- mutable cgsem_t sem;
-
- static Semaphore *create_semaphore_osx();
+class VulkanContextX11 : public VulkanContext {
+ virtual const char *_get_platform_surface_extension() const;
public:
- virtual Error wait();
- virtual Error post();
- virtual int get() const;
-
- static void make_default();
- SemaphoreOSX();
+ Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height);
- ~SemaphoreOSX();
+ VulkanContextX11();
+ ~VulkanContextX11();
};
-#endif // SEMAPHORE_OSX_H
+#endif // VULKAN_DEVICE_X11_H
diff --git a/platform/osx/SCsub b/platform/osx/SCsub
index e15b4339a7..ad62db358b 100644
--- a/platform/osx/SCsub
+++ b/platform/osx/SCsub
@@ -1,22 +1,22 @@
#!/usr/bin/env python
-Import('env')
+Import("env")
from platform_methods import run_in_subprocess
import platform_osx_builders
files = [
- 'crash_handler_osx.mm',
- 'os_osx.mm',
- 'godot_main_osx.mm',
- 'semaphore_osx.cpp',
- 'dir_access_osx.mm',
- 'joypad_osx.cpp',
- 'power_osx.cpp',
+ "crash_handler_osx.mm",
+ "os_osx.mm",
+ "display_server_osx.mm",
+ "godot_main_osx.mm",
+ "dir_access_osx.mm",
+ "joypad_osx.cpp",
+ "vulkan_context_osx.mm",
+ "context_gl_osx.mm",
]
-prog = env.add_program('#bin/godot', files)
+prog = env.add_program("#bin/godot", files)
if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]:
env.AddPostAction(prog, run_in_subprocess(platform_osx_builders.make_debug_osx))
-
diff --git a/platform/haiku/context_gl_haiku.h b/platform/osx/context_gl_osx.h
index c5d258915d..cce00fb35f 100644
--- a/platform/haiku/context_gl_haiku.h
+++ b/platform/osx/context_gl_osx.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* context_gl_haiku.h */
+/* context_gl_osx.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,34 +28,46 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef CONTEXT_GL_HAIKU_H
-#define CONTEXT_GL_HAIKU_H
+#ifndef CONTEXT_GL_OSX_H
+#define CONTEXT_GL_OSX_H
-#if defined(OPENGL_ENABLED)
+#if defined(OPENGL_ENABLED) || defined(GLES_ENABLED)
-#include "haiku_direct_window.h"
-#include "haiku_gl_view.h"
+#include "core/error_list.h"
+#include "core/os/os.h"
-class ContextGL_Haiku {
-private:
- HaikuGLView *view;
- HaikuDirectWindow *window;
+#include <AppKit/AppKit.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreVideo/CoreVideo.h>
+class ContextGL_OSX {
+ bool opengl_3_context;
bool use_vsync;
+ void *framework;
+ id window_view;
+ NSOpenGLPixelFormat *pixelFormat;
+ NSOpenGLContext *context;
+
public:
- Error initialize();
void release_current();
+
void make_current();
- void swap_buffers();
+ void update();
+
+ void set_opacity(GLint p_opacity);
+
int get_window_width();
int get_window_height();
+ void swap_buffers();
+
+ Error initialize();
void set_use_vsync(bool p_use);
bool is_using_vsync() const;
- ContextGL_Haiku(HaikuDirectWindow *p_window);
- ~ContextGL_Haiku();
+ ContextGL_OSX(id p_view, bool p_opengl_3_context);
+ ~ContextGL_OSX();
};
#endif
diff --git a/platform/osx/context_gl_osx.mm b/platform/osx/context_gl_osx.mm
new file mode 100644
index 0000000000..2319e9eb1f
--- /dev/null
+++ b/platform/osx/context_gl_osx.mm
@@ -0,0 +1,161 @@
+/*************************************************************************/
+/* context_gl_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "context_gl_osx.h"
+
+#if defined(OPENGL_ENABLED) || defined(GLES_ENABLED)
+
+void ContextGL_OSX::release_current() {
+ [NSOpenGLContext clearCurrentContext];
+}
+
+void ContextGL_OSX::make_current() {
+ [context makeCurrentContext];
+}
+
+void ContextGL_OSX::update() {
+ [context update];
+}
+
+void ContextGL_OSX::set_opacity(GLint p_opacity) {
+ [context setValues:&p_opacity forParameter:NSOpenGLCPSurfaceOpacity];
+}
+
+int ContextGL_OSX::get_window_width() {
+ return OS::get_singleton()->get_video_mode().width;
+}
+
+int ContextGL_OSX::get_window_height() {
+ return OS::get_singleton()->get_video_mode().height;
+}
+
+void ContextGL_OSX::swap_buffers() {
+ [context flushBuffer];
+}
+
+void ContextGL_OSX::set_use_vsync(bool p_use) {
+ CGLContextObj ctx = CGLGetCurrentContext();
+ if (ctx) {
+ GLint swapInterval = p_use ? 1 : 0;
+ CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval);
+ use_vsync = p_use;
+ }
+}
+
+bool ContextGL_OSX::is_using_vsync() const {
+ return use_vsync;
+}
+
+Error ContextGL_OSX::initialize() {
+ framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
+ ERR_FAIL_COND_V(!framework, ERR_CANT_CREATE);
+
+ unsigned int attributeCount = 0;
+
+ // OS X needs non-zero color size, so set reasonable values
+ int colorBits = 32;
+
+ // Fail if a robustness strategy was requested
+
+#define ADD_ATTR(x) \
+ { attributes[attributeCount++] = x; }
+#define ADD_ATTR2(x, y) \
+ { \
+ ADD_ATTR(x); \
+ ADD_ATTR(y); \
+ }
+
+ // Arbitrary array size here
+ NSOpenGLPixelFormatAttribute attributes[40];
+
+ ADD_ATTR(NSOpenGLPFADoubleBuffer);
+ ADD_ATTR(NSOpenGLPFAClosestPolicy);
+
+ if (!opengl_3_context) {
+ ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy);
+ } else {
+ //we now need OpenGL 3 or better, maybe even change this to 3_3Core ?
+ ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core);
+ }
+
+ ADD_ATTR2(NSOpenGLPFAColorSize, colorBits);
+
+ /*
+ if (fbconfig->alphaBits > 0)
+ ADD_ATTR2(NSOpenGLPFAAlphaSize, fbconfig->alphaBits);
+*/
+
+ ADD_ATTR2(NSOpenGLPFADepthSize, 24);
+
+ ADD_ATTR2(NSOpenGLPFAStencilSize, 8);
+
+ /*
+ if (fbconfig->stereo)
+ ADD_ATTR(NSOpenGLPFAStereo);
+*/
+
+ /*
+ if (fbconfig->samples > 0) {
+ ADD_ATTR2(NSOpenGLPFASampleBuffers, 1);
+ ADD_ATTR2(NSOpenGLPFASamples, fbconfig->samples);
+ }
+*/
+
+ // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB
+ // framebuffer, so there's no need (and no way) to request it
+
+ ADD_ATTR(0);
+
+#undef ADD_ATTR
+#undef ADD_ATTR2
+
+ pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
+ ERR_FAIL_COND_V(pixelFormat == nil, ERR_CANT_CREATE);
+
+ context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
+
+ ERR_FAIL_COND_V(context == nil, ERR_CANT_CREATE);
+
+ [context setView:window_view];
+
+ [context makeCurrentContext];
+
+ return OK;
+}
+
+ContextGL_OSX::ContextGL_OSX(id p_view, bool p_opengl_3_context) {
+ opengl_3_context = p_opengl_3_context;
+ window_view = p_view;
+ use_vsync = false;
+}
+
+ContextGL_OSX::~ContextGL_OSX() {}
+
+#endif
diff --git a/platform/osx/crash_handler_osx.h b/platform/osx/crash_handler_osx.h
index abd9812596..9970f6045a 100644
--- a/platform/osx/crash_handler_osx.h
+++ b/platform/osx/crash_handler_osx.h
@@ -32,7 +32,6 @@
#define CRASH_HANDLER_OSX_H
class CrashHandler {
-
bool disabled;
public:
diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm
index 5da0118686..9fb2f63935 100644
--- a/platform/osx/crash_handler_osx.mm
+++ b/platform/osx/crash_handler_osx.mm
@@ -90,7 +90,7 @@ static void handle_crash(int sig) {
if (OS::get_singleton()->get_main_loop())
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
- fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
+ fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
if (strings) {
void *load_addr = (void *)load_address();
@@ -142,7 +142,7 @@ static void handle_crash(int sig) {
}
}
- fprintf(stderr, "[%zu] %ls\n", i, output.c_str());
+ fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data());
}
free(strings);
diff --git a/platform/osx/detect.py b/platform/osx/detect.py
index fe839199e8..6fc1dc65af 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -13,7 +13,7 @@ def get_name():
def can_build():
- if (sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ)):
+ if sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ):
return True
return False
@@ -23,51 +23,59 @@ def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
return [
- ('osxcross_sdk', 'OSXCross SDK version', 'darwin14'),
- ('MACOS_SDK_PATH', 'Path to the macOS SDK', ''),
- EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')),
- BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False),
- BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False),
- BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False),
- BoolVariable('use_tsan', 'Use LLVM/GCC compiler thread sanitizer (TSAN))', False),
+ ("osxcross_sdk", "OSXCross SDK version", "darwin16"),
+ ("MACOS_SDK_PATH", "Path to the macOS SDK", ""),
+ BoolVariable(
+ "use_static_mvk",
+ "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables"
+ " validation layers)",
+ False,
+ ),
+ EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
+ BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
+ BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False),
+ BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False),
+ BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False),
]
def get_flags():
- return [
- ]
+ return []
def configure(env):
- ## Build type
-
- if (env["target"] == "release"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O3', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os','-ftree-vectorize', '-msse2'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "release_debug"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED'])
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "debug"):
- env.Prepend(CCFLAGS=['-g3'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
+ ## Build type
+
+ if env["target"] == "release":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"])
+ if env["arch"] != "arm64":
+ env.Prepend(CCFLAGS=["-msse2"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "release_debug":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O2"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "debug":
+ env.Prepend(CCFLAGS=["-g3"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"])
## Architecture
@@ -81,76 +89,117 @@ def configure(env):
if "OSXCROSS_ROOT" in os.environ:
env["osxcross"] = True
- if not "osxcross" in env: # regular native build
- env.Append(CCFLAGS=['-arch', 'x86_64'])
- env.Append(LINKFLAGS=['-arch', 'x86_64'])
- if (env["macports_clang"] != 'no'):
+ if env["arch"] == "arm64":
+ print("Building for macOS 10.15+, platform arm64.")
+ env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
+ else:
+ print("Building for macOS 10.12+, platform x86-64.")
+ env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+ env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+
+ if not "osxcross" in env: # regular native build
+ if env["macports_clang"] != "no":
mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local")
mpclangver = env["macports_clang"]
env["CC"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang"
env["LINK"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang++"
env["CXX"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/clang++"
- env['AR'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ar"
- env['RANLIB'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib"
- env['AS'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as"
- env.Append(CPPDEFINES=['__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define
+ env["AR"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ar"
+ env["RANLIB"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib"
+ env["AS"] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as"
+ env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define
else:
- env['CC'] = 'clang'
- env['CXX'] = 'clang++'
+ env["CC"] = "clang"
+ env["CXX"] = "clang++"
- detect_darwin_sdk_path('osx', env)
- env.Append(CCFLAGS=['-isysroot', '$MACOS_SDK_PATH'])
- env.Append(LINKFLAGS=['-isysroot', '$MACOS_SDK_PATH'])
+ detect_darwin_sdk_path("osx", env)
+ env.Append(CCFLAGS=["-isysroot", "$MACOS_SDK_PATH"])
+ env.Append(LINKFLAGS=["-isysroot", "$MACOS_SDK_PATH"])
- else: # osxcross build
+ else: # osxcross build
root = os.environ.get("OSXCROSS_ROOT", 0)
- basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
+ if env["arch"] == "arm64":
+ basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
+ else:
+ basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
ccache_path = os.environ.get("CCACHE")
if ccache_path is None:
- env['CC'] = basecmd + "cc"
- env['CXX'] = basecmd + "c++"
+ env["CC"] = basecmd + "cc"
+ env["CXX"] = basecmd + "c++"
else:
# there aren't any ccache wrappers available for OS X cross-compile,
# to enable caching we need to prepend the path to the ccache binary
- env['CC'] = ccache_path + ' ' + basecmd + "cc"
- env['CXX'] = ccache_path + ' ' + basecmd + "c++"
- env['AR'] = basecmd + "ar"
- env['RANLIB'] = basecmd + "ranlib"
- env['AS'] = basecmd + "as"
- env.Append(CPPDEFINES=['__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define
-
- if (env["CXX"] == "clang++"):
- env.Append(CPPDEFINES=['TYPED_METHOD_BIND'])
+ env["CC"] = ccache_path + " " + basecmd + "cc"
+ env["CXX"] = ccache_path + " " + basecmd + "c++"
+ env["AR"] = basecmd + "ar"
+ env["RANLIB"] = basecmd + "ranlib"
+ env["AS"] = basecmd + "as"
+ env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define
+
+ if env["CXX"] == "clang++":
+ env.Append(CPPDEFINES=["TYPED_METHOD_BIND"])
env["CC"] = "clang"
env["LINK"] = "clang++"
- if env['use_ubsan'] or env['use_asan'] or env['use_tsan']:
+ if env["use_ubsan"] or env["use_asan"] or env["use_tsan"]:
env.extra_suffix += "s"
- if env['use_ubsan']:
- env.Append(CCFLAGS=['-fsanitize=undefined'])
- env.Append(LINKFLAGS=['-fsanitize=undefined'])
+ if env["use_ubsan"]:
+ env.Append(CCFLAGS=["-fsanitize=undefined"])
+ env.Append(LINKFLAGS=["-fsanitize=undefined"])
- if env['use_asan']:
- env.Append(CCFLAGS=['-fsanitize=address'])
- env.Append(LINKFLAGS=['-fsanitize=address'])
+ if env["use_asan"]:
+ env.Append(CCFLAGS=["-fsanitize=address"])
+ env.Append(LINKFLAGS=["-fsanitize=address"])
- if env['use_tsan']:
- env.Append(CCFLAGS=['-fsanitize=thread'])
- env.Append(LINKFLAGS=['-fsanitize=thread'])
+ if env["use_tsan"]:
+ env.Append(CCFLAGS=["-fsanitize=thread"])
+ env.Append(LINKFLAGS=["-fsanitize=thread"])
## Dependencies
- if env['builtin_libtheora']:
- env["x86_libtheora_opt_gcc"] = True
+ if env["builtin_libtheora"]:
+ if env["arch"] != "arm64":
+ env["x86_libtheora_opt_gcc"] = True
## Flags
- env.Prepend(CPPPATH=['#platform/osx'])
- env.Append(CPPDEFINES=['OSX_ENABLED', 'UNIX_ENABLED', 'GLES_ENABLED', 'APPLE_STYLE_KEYS', 'COREAUDIO_ENABLED', 'COREMIDI_ENABLED'])
- env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'AVFoundation', '-framework', 'CoreMedia', '-framework', 'CoreVideo'])
- env.Append(LIBS=['pthread'])
-
- env.Append(CCFLAGS=['-mmacosx-version-min=10.9'])
- env.Append(LINKFLAGS=['-mmacosx-version-min=10.9'])
+ env.Prepend(CPPPATH=["#platform/osx"])
+ env.Append(CPPDEFINES=["OSX_ENABLED", "UNIX_ENABLED", "APPLE_STYLE_KEYS", "COREAUDIO_ENABLED", "COREMIDI_ENABLED"])
+ env.Append(
+ LINKFLAGS=[
+ "-framework",
+ "Cocoa",
+ "-framework",
+ "Carbon",
+ "-framework",
+ "AudioUnit",
+ "-framework",
+ "CoreAudio",
+ "-framework",
+ "CoreMIDI",
+ "-framework",
+ "IOKit",
+ "-framework",
+ "ForceFeedback",
+ "-framework",
+ "CoreVideo",
+ "-framework",
+ "AVFoundation",
+ "-framework",
+ "CoreMedia",
+ ]
+ )
+ env.Append(LIBS=["pthread", "z"])
+
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"])
+ if env["use_static_mvk"]:
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
+ elif not env["builtin_vulkan"]:
+ env.Append(LIBS=["vulkan"])
+
+ # env.Append(CPPDEFINES=['GLES_ENABLED', 'OPENGL_ENABLED'])
diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm
index 66ea380903..7791ba5407 100644
--- a/platform/osx/dir_access_osx.mm
+++ b/platform/osx/dir_access_osx.mm
@@ -38,7 +38,6 @@
#include <Foundation/Foundation.h>
String DirAccessOSX::fix_unicode_name(const char *p_name) const {
-
String fname;
NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping];
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
new file mode 100644
index 0000000000..073d35008b
--- /dev/null
+++ b/platform/osx/display_server_osx.h
@@ -0,0 +1,325 @@
+/*************************************************************************/
+/* display_server_osx.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_OSX_H
+#define DISPLAY_SERVER_OSX_H
+
+#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(OPENGL_ENABLED)
+#include "context_gl_osx.h"
+//TODO - reimplement OpenGLES
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/osx/vulkan_context_osx.h"
+#endif
+
+#include <AppKit/AppKit.h>
+#include <AppKit/NSCursor.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreVideo/CoreVideo.h>
+
+#undef BitMap
+#undef CursorShape
+
+class DisplayServerOSX : public DisplayServer {
+ GDCLASS(DisplayServerOSX, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+public:
+#if defined(OPENGL_ENABLED)
+ ContextGL_OSX *context_gles2;
+#endif
+#if defined(VULKAN_ENABLED)
+ VulkanContextOSX *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ const NSMenu *_get_menu_root(const String &p_menu_root) const;
+ NSMenu *_get_menu_root(const String &p_menu_root);
+
+ NSMenu *apple_menu = nullptr;
+ NSMenu *dock_menu = nullptr;
+ Map<String, NSMenu *> submenu;
+
+ struct KeyEvent {
+ WindowID window_id;
+ unsigned int osx_state;
+ bool pressed;
+ bool echo;
+ bool raw;
+ uint32_t keycode;
+ uint32_t physical_keycode;
+ uint32_t unicode;
+ };
+
+ struct WarpEvent {
+ NSTimeInterval timestamp;
+ NSPoint delta;
+ };
+
+ List<WarpEvent> warp_events;
+ NSTimeInterval last_warp = 0;
+
+ Vector<KeyEvent> key_event_buffer;
+ int key_event_pos;
+
+ struct WindowData {
+ id window_delegate;
+ id window_object;
+ id window_view;
+
+ Vector<Vector2> mpath;
+
+#if defined(OPENGL_ENABLED)
+ ContextGL_OSX *context_gles2 = nullptr;
+#endif
+ Point2i mouse_pos;
+
+ Size2i min_size;
+ Size2i max_size;
+ Size2i size;
+
+ bool mouse_down_control = false;
+
+ bool im_active = false;
+ Size2i im_position;
+
+ Callable rect_changed_callback;
+ Callable event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ ObjectID instance_id;
+
+ WindowID transient_parent = INVALID_WINDOW_ID;
+ Set<WindowID> transient_children;
+
+ bool layered_window = false;
+ bool fullscreen = false;
+ bool on_top = false;
+ bool borderless = false;
+ bool resize_disabled = false;
+ bool no_focus = false;
+ };
+
+ Point2i im_selection;
+ String im_text;
+
+ Map<WindowID, WindowData> windows;
+
+ WindowID window_id_counter = MAIN_WINDOW_ID;
+
+ WindowID _create_window(WindowMode p_mode, const Rect2i &p_rect);
+ void _update_window(WindowData p_wd);
+ void _send_window_event(const WindowData &wd, WindowEvent p_event);
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
+ WindowID _find_window_id(id p_window);
+
+ void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window);
+
+ Point2i _get_screens_origin() const;
+ Point2i _get_native_screen_position(int p_screen) const;
+
+ void _push_input(const Ref<InputEvent> &p_event);
+ void _process_key_events();
+ void _release_pressed_events();
+
+ String rendering_driver;
+
+ id delegate;
+ id autoreleasePool;
+ CGEventSourceRef eventSource;
+
+ CursorShape cursor_shape;
+ NSCursor *cursors[CURSOR_MAX];
+ Map<CursorShape, Vector<Variant>> cursors_cache;
+
+ MouseMode mouse_mode;
+ Point2i last_mouse_pos;
+ uint32_t last_button_state;
+
+ bool window_focused;
+ bool drop_events;
+ bool in_dispatch_input_event = false;
+
+public:
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override;
+ virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override;
+ virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override;
+ virtual void global_menu_add_separator(const String &p_menu_root) override;
+
+ virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override;
+ virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override;
+ virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override;
+ virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override;
+ virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override;
+ virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override;
+
+ virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
+ virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
+ virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
+ virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
+ virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
+ virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
+
+ virtual int global_menu_get_item_count(const String &p_menu_root) const override;
+
+ virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override;
+ virtual void global_menu_clear(const String &p_menu_root) override;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+ virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override;
+ virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
+
+ virtual void mouse_set_mode(MouseMode p_mode) override;
+ virtual MouseMode mouse_get_mode() const override;
+
+ virtual void mouse_warp_to_position(const Point2i &p_to) override;
+ virtual Point2i mouse_get_position() const override;
+ virtual Point2i mouse_get_absolute_position() const override;
+ virtual int mouse_get_button_state() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+
+ virtual int get_screen_count() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_max_scale() const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual Vector<int> get_window_list() const override;
+
+ virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override;
+ virtual void show_window(WindowID p_id) override;
+ virtual void delete_sub_window(WindowID p_id) override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual Point2i ime_get_selection() const override;
+ virtual String ime_get_text() const override;
+
+ virtual void cursor_set_shape(CursorShape p_shape) override;
+ virtual CursorShape cursor_get_shape() const override;
+ virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+
+ virtual bool get_swap_cancel_ok() override;
+
+ virtual int keyboard_get_layout_count() const override;
+ virtual int keyboard_get_current_layout() const override;
+ virtual void keyboard_set_current_layout(int p_index) override;
+ virtual String keyboard_get_layout_language(int p_index) const override;
+ virtual String keyboard_get_layout_name(int p_index) const override;
+
+ virtual void process_events() override;
+ virtual void force_process_and_drop_events() override;
+
+ virtual void release_rendering_thread() override;
+ virtual void make_rendering_thread() override;
+ virtual void swap_buffers() override;
+
+ virtual void set_native_icon(const String &p_filename) override;
+ virtual void set_icon(const Ref<Image> &p_icon) override;
+
+ virtual void console_set_visible(bool p_enabled) override;
+ virtual bool is_console_visible() const override;
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ static void register_osx_driver();
+
+ DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerOSX();
+};
+
+#endif // DISPLAY_SERVER_OSX_H
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
new file mode 100644
index 0000000000..e33af520c9
--- /dev/null
+++ b/platform/osx/display_server_osx.mm
@@ -0,0 +1,3875 @@
+/*************************************************************************/
+/* display_server_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_osx.h"
+
+#include "os_osx.h"
+
+#include "core/io/marshalls.h"
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+#include "main/main.h"
+#include "scene/resources/texture.h"
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDLib.h>
+
+#if defined(OPENGL_ENABLED)
+//TODO - reimplement OpenGLES
+
+#import <AppKit/NSOpenGLView.h>
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+
+#include <QuartzCore/CAMetalLayer.h>
+#endif
+
+#ifndef NSAppKitVersionNumber10_14
+#define NSAppKitVersionNumber10_14 1671
+#endif
+
+#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton()))
+
+static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) {
+ r_state->set_shift((p_osx_state & NSEventModifierFlagShift));
+ r_state->set_control((p_osx_state & NSEventModifierFlagControl));
+ r_state->set_alt((p_osx_state & NSEventModifierFlagOption));
+ r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand));
+}
+
+static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) {
+ const NSRect contentRect = [p_wd.window_view frame];
+ const float scale = DS_OSX->screen_get_max_scale();
+ p_wd.mouse_pos.x = p_locationInWindow.x * scale;
+ p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale;
+ DS_OSX->last_mouse_pos = p_wd.mouse_pos;
+ Input::get_singleton()->set_mouse_position(p_wd.mouse_pos);
+ return p_wd.mouse_pos;
+}
+
+static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) {
+ Vector<DisplayServerOSX::KeyEvent> &buffer = DS_OSX->key_event_buffer;
+ if (DS_OSX->key_event_pos >= buffer.size()) {
+ buffer.resize(1 + DS_OSX->key_event_pos);
+ }
+ buffer.write[DS_OSX->key_event_pos++] = p_event;
+}
+
+static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
+ if ([NSCursor respondsToSelector:selector]) {
+ id object = [NSCursor performSelector:selector];
+ if ([object isKindOfClass:[NSCursor class]]) {
+ return object;
+ }
+ }
+ if (fallback) {
+ // Fallback should be a reasonable default, no need to check.
+ return [NSCursor performSelector:fallback];
+ }
+ return [NSCursor arrowCursor];
+}
+
+/*************************************************************************/
+/* GodotApplication */
+/*************************************************************************/
+
+@interface GodotApplication : NSApplication
+@end
+
+@implementation GodotApplication
+
+- (void)sendEvent:(NSEvent *)event {
+ // special case handling of command-period, which is traditionally a special
+ // shortcut in macOS and doesn't arrive at our regular keyDown handler.
+ if ([event type] == NSEventTypeKeyDown) {
+ if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) {
+ Ref<InputEventKey> k;
+ k.instance();
+
+ _get_key_modifier_state([event modifierFlags], k);
+ k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID);
+ k->set_pressed(true);
+ k->set_keycode(KEY_PERIOD);
+ k->set_physical_keycode(KEY_PERIOD);
+ k->set_echo([event isARepeat]);
+
+ Input::get_singleton()->accumulate_input_event(k);
+ }
+ }
+
+ // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
+ // This works around an AppKit bug, where key up events while holding
+ // down the command key don't get sent to the key window.
+ if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) {
+ [[self keyWindow] sendEvent:event];
+ } else {
+ [super sendEvent:event];
+ }
+}
+
+@end
+
+/*************************************************************************/
+/* GlobalMenuItem */
+/*************************************************************************/
+
+@interface GlobalMenuItem : NSObject {
+@public
+ Callable callback;
+ Variant meta;
+ bool checkable;
+}
+
+@end
+
+@implementation GlobalMenuItem
+@end
+
+/*************************************************************************/
+/* GodotApplicationDelegate */
+/*************************************************************************/
+
+@interface GodotApplicationDelegate : NSObject
+- (void)forceUnbundledWindowActivationHackStep1;
+- (void)forceUnbundledWindowActivationHackStep2;
+- (void)forceUnbundledWindowActivationHackStep3;
+@end
+
+@implementation GodotApplicationDelegate
+
+- (void)forceUnbundledWindowActivationHackStep1 {
+ // Step1: Switch focus to macOS Dock.
+ // Required to perform step 2, TransformProcessType will fail if app is already the in focus.
+ for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
+ [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+ break;
+ }
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep2 {
+ // Step 2: Register app as foreground process.
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep3 {
+ // Step 3: Switch focus back to app window.
+ [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notice {
+ NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+ if (nsappname == nil) {
+ // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
+ }
+}
+
+- (void)applicationDidResignActive:(NSNotification *)notification {
+ if (OS_OSX::get_singleton()->get_main_loop()) {
+ OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+}
+
+- (void)applicationDidBecomeActive:(NSNotification *)notification {
+ if (OS_OSX::get_singleton()->get_main_loop()) {
+ OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+}
+
+- (void)globalMenuCallback:(id)sender {
+ if (![sender representedObject]) {
+ return;
+ }
+
+ GlobalMenuItem *value = [sender representedObject];
+
+ if (value) {
+ if (value->checkable) {
+ if ([sender state] == NSControlStateValueOff) {
+ [sender setState:NSControlStateValueOn];
+ } else {
+ [sender setState:NSControlStateValueOff];
+ }
+ }
+
+ if (value->callback != Callable()) {
+ Variant tag = value->meta;
+ Variant *tagp = &tag;
+ Variant ret;
+ Callable::CallError ce;
+ value->callback.call((const Variant **)&tagp, 1, ret, ce);
+ }
+ }
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+ return DS_OSX->dock_menu;
+}
+
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+ // Note: may be called called before main loop init!
+ char *utfs = strdup([filename UTF8String]);
+ ((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename.parse_utf8(utfs);
+ free(utfs);
+
+#ifdef TOOLS_ENABLED
+ // Open new instance
+ if (OS_OSX::get_singleton()->get_main_loop()) {
+ List<String> args;
+ args.push_back(((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename);
+ String exec = OS::get_singleton()->get_executable_path();
+
+ OS::ProcessID pid = 0;
+ OS::get_singleton()->execute(exec, args, false, &pid);
+ }
+#endif
+ return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
+ DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ return NSTerminateCancel;
+}
+
+- (void)showAbout:(id)sender {
+ if (OS_OSX::get_singleton()->get_main_loop()) {
+ OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
+ }
+}
+
+@end
+
+/*************************************************************************/
+/* GodotWindowDelegate */
+/*************************************************************************/
+
+@interface GodotWindowDelegate : NSObject {
+ DisplayServerOSX::WindowID window_id;
+}
+
+- (void)windowWillClose:(NSNotification *)notification;
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
+
+@end
+
+@implementation GodotWindowDelegate
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+ window_id = wid;
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return YES;
+ }
+ DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ return NO;
+}
+
+- (void)windowWillClose:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ while (wd.transient_children.size()) {
+ DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
+ }
+
+ if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {
+ DisplayServerOSX::WindowData &pwd = DS_OSX->windows[wd.transient_parent];
+ [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent.
+ DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID);
+ } else if ((window_id != DisplayServerOSX::MAIN_WINDOW_ID) && (DS_OSX->windows.size() == 1)) {
+ DisplayServerOSX::WindowData &pwd = DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID];
+ [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to main window if there is no parent or other windows left.
+ }
+
+#if defined(OPENGL_ENABLED)
+ if (DS_OSX->rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#ifdef VULKAN_ENABLED
+ if (DS_OSX->rendering_driver == "vulkan") {
+ DS_OSX->context_vulkan->window_destroy(window_id);
+ }
+#endif
+
+ DS_OSX->windows.erase(window_id);
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ wd.fullscreen = true;
+
+ [wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+ [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ wd.fullscreen = false;
+
+ const float scale = DS_OSX->screen_get_max_scale();
+ if (wd.min_size != Size2i()) {
+ Size2i size = wd.min_size / scale;
+ [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ }
+ if (wd.max_size != Size2i()) {
+ Size2i size = wd.max_size / scale;
+ [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ }
+
+ if (wd.resize_disabled) {
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+ }
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
+ if (!DisplayServerOSX::get_singleton()) {
+ return;
+ }
+
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor];
+ CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+
+ if (newBackingScaleFactor != oldBackingScaleFactor) {
+ //Set new display scale and window size
+ const float scale = DS_OSX->screen_get_max_scale();
+ const NSRect contentRect = [wd.window_view frame];
+
+ wd.size.width = contentRect.size.width * scale;
+ wd.size.height = contentRect.size.height * scale;
+
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE);
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+ //Force window resize event
+ [self windowDidResize:notification];
+ }
+}
+
+- (void)windowDidResize:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ const NSRect contentRect = [wd.window_view frame];
+
+ const float scale = DS_OSX->screen_get_max_scale();
+ wd.size.width = contentRect.size.width * scale;
+ wd.size.height = contentRect.size.height * scale;
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+#if defined(OPENGL_ENABLED)
+ if (DS_OSX->rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (DS_OSX->rendering_driver == "vulkan") {
+ DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height);
+ }
+#endif
+
+ if (!wd.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+
+ if (OS_OSX::get_singleton()->get_main_loop()) {
+ Main::force_redraw();
+ //Event retrieval blocks until resize is over. Call Main::iteration() directly.
+ if (!Main::is_iterating()) { //avoid cyclic loop
+ Main::iteration();
+ }
+ }
+}
+
+- (void)windowDidMove:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ DS_OSX->_release_pressed_events();
+
+ if (!wd.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ Input::get_singleton()->set_mouse_position(wd.mouse_pos);
+
+ DS_OSX->window_focused = true;
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ DS_OSX->window_focused = false;
+
+ DS_OSX->_release_pressed_events();
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ DS_OSX->window_focused = false;
+
+ DS_OSX->_release_pressed_events();
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ DS_OSX->window_focused = true;
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+@end
+
+/*************************************************************************/
+/* GodotContentView */
+/*************************************************************************/
+
+@interface GodotContentView : NSView <NSTextInputClient> {
+ DisplayServerOSX::WindowID window_id;
+ NSTrackingArea *trackingArea;
+ NSMutableAttributedString *markedText;
+ bool imeInputEventInProgress;
+}
+
+- (void)cancelComposition;
+- (CALayer *)makeBackingLayer;
+- (BOOL)wantsUpdateLayer;
+- (void)updateLayer;
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
+
+@end
+
+@implementation GodotContentView
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+ window_id = wid;
+}
+
++ (void)initialize {
+ if (self == [GodotContentView class]) {
+ // nothing left to do here at the moment..
+ }
+}
+
+- (CALayer *)makeBackingLayer {
+#if defined(OPENGL_ENABLED)
+ if (DS_OSX->rendering_driver == "opengl_es") {
+ CALayer *layer = [[NSOpenGLLayer class] layer];
+ return layer;
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (DS_OSX->rendering_driver == "vulkan") {
+ CALayer *layer = [[CAMetalLayer class] layer];
+ return layer;
+ }
+#endif
+ return [super makeBackingLayer];
+}
+
+- (void)updateLayer {
+#if defined(OPENGL_ENABLED)
+ if (DS_OSX->rendering_driver == "opengl_es") {
+ [super updateLayer];
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (DS_OSX->rendering_driver == "vulkan") {
+ [super updateLayer];
+ }
+#endif
+}
+
+- (BOOL)wantsUpdateLayer {
+ return YES;
+}
+
+- (id)init {
+ self = [super init];
+ trackingArea = nil;
+ imeInputEventInProgress = false;
+ [self updateTrackingAreas];
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
+ [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
+#else
+ [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
+#endif
+ markedText = [[NSMutableAttributedString alloc] init];
+ return self;
+}
+
+- (void)dealloc {
+ [trackingArea release];
+ [markedText release];
+ [super dealloc];
+}
+
+static const NSRange kEmptyRange = { NSNotFound, 0 };
+
+- (BOOL)hasMarkedText {
+ return (markedText.length > 0);
+}
+
+- (NSRange)markedRange {
+ return NSMakeRange(0, markedText.length);
+}
+
+- (NSRange)selectedRange {
+ return kEmptyRange;
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ [markedText initWithAttributedString:aString];
+ } else {
+ [markedText initWithString:aString];
+ }
+ if (markedText.length == 0) {
+ [self unmarkText];
+ return;
+ }
+
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (wd.im_active) {
+ imeInputEventInProgress = true;
+ DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]);
+ DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length);
+
+ OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ }
+}
+
+- (void)doCommandBySelector:(SEL)aSelector {
+ if ([self respondsToSelector:aSelector]) {
+ [self performSelector:aSelector];
+ }
+}
+
+- (void)unmarkText {
+ imeInputEventInProgress = false;
+ [[markedText mutableString] setString:@""];
+
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (wd.im_active) {
+ DS_OSX->im_text = String();
+ DS_OSX->im_selection = Point2i();
+
+ OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ }
+}
+
+- (NSArray *)validAttributesForMarkedText {
+ return [NSArray array];
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+ return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
+ return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+ ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ const NSRect contentRect = [wd.window_view frame];
+ const float scale = DS_OSX->screen_get_max_scale();
+ NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0);
+ NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin;
+
+ return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0);
+}
+
+- (void)cancelComposition {
+ [self unmarkText];
+ NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
+ [currentInputContext discardMarkedText];
+}
+
+- (void)insertText:(id)aString {
+ [self insertText:aString replacementRange:NSMakeRange(0, 0)];
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
+ NSEvent *event = [NSApp currentEvent];
+
+ NSString *characters;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ characters = [aString string];
+ } else {
+ characters = (NSString *)aString;
+ }
+
+ NSUInteger i, length = [characters length];
+
+ NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
+ NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) {
+ NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
+ [currentInputContext discardMarkedText];
+ [self cancelComposition];
+ return;
+ }
+
+ for (i = 0; i < length; i++) {
+ const unichar codepoint = [characters characterAtIndex:i];
+ if ((codepoint & 0xFF00) == 0xF700) {
+ continue;
+ }
+
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = false;
+ ke.raw = false; // IME input event
+ ke.keycode = 0;
+ ke.physical_keycode = 0;
+ ke.unicode = codepoint;
+
+ _push_to_key_event_buffer(ke);
+ }
+ [self cancelComposition];
+}
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+ return NSDragOperationCopy;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+ return NSDragOperationCopy;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+ ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO);
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ NSPasteboard *pboard = [sender draggingPasteboard];
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
+ NSArray<NSURL *> *filenames = [pboard propertyListForType:NSPasteboardTypeFileURL];
+#else
+ NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
+#endif
+
+ Vector<String> files;
+ for (NSUInteger i = 0; i < filenames.count; i++) {
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
+ NSString *ns = [[filenames objectAtIndex:i] path];
+#else
+ NSString *ns = [filenames objectAtIndex:i];
+#endif
+ char *utfs = strdup([ns UTF8String]);
+ String ret;
+ ret.parse_utf8(utfs);
+ free(utfs);
+ files.push_back(ret);
+ }
+
+ if (!wd.drop_files_callback.is_null()) {
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+ }
+
+ return NO;
+}
+
+- (BOOL)isOpaque {
+ return YES;
+}
+
+- (BOOL)canBecomeKeyView {
+ if (DS_OSX->windows.has(window_id)) {
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+ if (wd.no_focus) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (void)cursorUpdate:(NSEvent *)event {
+ DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
+ DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
+ DS_OSX->cursor_set_shape(p_shape);
+}
+
+static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, int index, int mask, bool pressed) {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (pressed) {
+ DS_OSX->last_button_state |= mask;
+ } else {
+ DS_OSX->last_button_state &= ~mask;
+ }
+
+ Ref<InputEventMouseButton> mb;
+ mb.instance();
+ mb->set_window_id(window_id);
+ const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]);
+ _get_key_modifier_state([event modifierFlags], mb);
+ mb->set_button_index(index);
+ mb->set_pressed(pressed);
+ mb->set_position(pos);
+ mb->set_global_position(pos);
+ mb->set_button_mask(DS_OSX->last_button_state);
+ if (index == BUTTON_LEFT && pressed) {
+ mb->set_doubleclick([event clickCount] == 2);
+ }
+
+ Input::get_singleton()->accumulate_input_event(mb);
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (([event modifierFlags] & NSEventModifierFlagControl)) {
+ wd.mouse_down_control = true;
+ _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
+ } else {
+ wd.mouse_down_control = false;
+ _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, true);
+ }
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)mouseUp:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (wd.mouse_down_control) {
+ _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
+ } else {
+ _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, false);
+ }
+}
+
+- (void)mouseMoved:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ NSPoint delta = NSMakePoint([event deltaX], [event deltaY]);
+ NSPoint mpos = [event locationInWindow];
+
+ if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED) {
+ // Discard late events
+ if (([event timestamp]) < DS_OSX->last_warp) {
+ return;
+ }
+
+ // Warp affects next event delta, subtract previous warp deltas
+ List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front();
+ while (F) {
+ if (F->get().timestamp < [event timestamp]) {
+ List<DisplayServerOSX::WarpEvent>::Element *E = F;
+ delta.x -= E->get().delta.x;
+ delta.y -= E->get().delta.y;
+ F = F->next();
+ DS_OSX->warp_events.erase(E);
+ } else {
+ F = F->next();
+ }
+ }
+
+ // Confine mouse position to the window, and update delta
+ NSRect frame = [wd.window_object frame];
+ NSPoint conf_pos = mpos;
+ conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width);
+ conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height);
+ delta.x = conf_pos.x - mpos.x;
+ delta.y = mpos.y - conf_pos.y;
+ mpos = conf_pos;
+
+ // Move mouse cursor
+ NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0);
+ conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
+ conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y;
+ CGWarpMouseCursorPosition(conf_pos);
+
+ // Save warp data
+ DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime];
+ DisplayServerOSX::WarpEvent ev;
+ ev.timestamp = DS_OSX->last_warp;
+ ev.delta = delta;
+ DS_OSX->warp_events.push_back(ev);
+ }
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+
+ mm->set_window_id(window_id);
+ mm->set_button_mask(DS_OSX->last_button_state);
+ const Vector2i pos = _get_mouse_pos(wd, mpos);
+ mm->set_position(pos);
+ mm->set_pressure([event pressure]);
+ if ([event subtype] == NSEventSubtypeTabletPoint) {
+ const NSPoint p = [event tilt];
+ mm->set_tilt(Vector2(p.x, p.y));
+ }
+ mm->set_global_position(pos);
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale();
+ mm->set_relative(relativeMotion);
+ _get_key_modifier_state([event modifierFlags], mm);
+
+ Input::get_singleton()->set_mouse_position(wd.mouse_pos);
+ Input::get_singleton()->accumulate_input_event(mm);
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+ _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
+}
+
+- (void)rightMouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)rightMouseUp:(NSEvent *)event {
+ _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+ if ((int)[event buttonNumber] == 2) {
+ _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true);
+ } else if ((int)[event buttonNumber] == 3) {
+ _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true);
+ } else if ((int)[event buttonNumber] == 4) {
+ _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true);
+ } else {
+ return;
+ }
+}
+
+- (void)otherMouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)otherMouseUp:(NSEvent *)event {
+ if ((int)[event buttonNumber] == 2) {
+ _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false);
+ } else if ((int)[event buttonNumber] == 3) {
+ _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false);
+ } else if ((int)[event buttonNumber] == 4) {
+ _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false);
+ } else {
+ return;
+ }
+}
+
+- (void)mouseExited:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) {
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT);
+ }
+}
+
+- (void)mouseEntered:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) {
+ DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER);
+ }
+
+ DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
+ DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
+ DS_OSX->cursor_set_shape(p_shape);
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ Ref<InputEventMagnifyGesture> ev;
+ ev.instance();
+ ev->set_window_id(window_id);
+ _get_key_modifier_state([event modifierFlags], ev);
+ ev->set_position(_get_mouse_pos(wd, [event locationInWindow]));
+ ev->set_factor([event magnification] + 1.0);
+
+ Input::get_singleton()->accumulate_input_event(ev);
+}
+
+- (void)viewDidChangeBackingProperties {
+ // nothing left to do here
+}
+
+- (void)updateTrackingAreas {
+ if (trackingArea != nil) {
+ [self removeTrackingArea:trackingArea];
+ [trackingArea release];
+ }
+
+ NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect;
+ trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
+
+ [self addTrackingArea:trackingArea];
+ [super updateTrackingAreas];
+}
+
+static bool isNumpadKey(unsigned int key) {
+ static const unsigned int table[] = {
+ 0x41, /* kVK_ANSI_KeypadDecimal */
+ 0x43, /* kVK_ANSI_KeypadMultiply */
+ 0x45, /* kVK_ANSI_KeypadPlus */
+ 0x47, /* kVK_ANSI_KeypadClear */
+ 0x4b, /* kVK_ANSI_KeypadDivide */
+ 0x4c, /* kVK_ANSI_KeypadEnter */
+ 0x4e, /* kVK_ANSI_KeypadMinus */
+ 0x51, /* kVK_ANSI_KeypadEquals */
+ 0x52, /* kVK_ANSI_Keypad0 */
+ 0x53, /* kVK_ANSI_Keypad1 */
+ 0x54, /* kVK_ANSI_Keypad2 */
+ 0x55, /* kVK_ANSI_Keypad3 */
+ 0x56, /* kVK_ANSI_Keypad4 */
+ 0x57, /* kVK_ANSI_Keypad5 */
+ 0x58, /* kVK_ANSI_Keypad6 */
+ 0x59, /* kVK_ANSI_Keypad7 */
+ 0x5b, /* kVK_ANSI_Keypad8 */
+ 0x5c, /* kVK_ANSI_Keypad9 */
+ 0x5f, /* kVK_JIS_KeypadComma */
+ 0x00
+ };
+ for (int i = 0; table[i] != 0; i++) {
+ if (key == table[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Translates a OS X keycode to a Godot keycode
+//
+static int translateKey(unsigned int key) {
+ // Keyboard symbol translation table
+ static const unsigned int table[128] = {
+ /* 00 */ KEY_A,
+ /* 01 */ KEY_S,
+ /* 02 */ KEY_D,
+ /* 03 */ KEY_F,
+ /* 04 */ KEY_H,
+ /* 05 */ KEY_G,
+ /* 06 */ KEY_Z,
+ /* 07 */ KEY_X,
+ /* 08 */ KEY_C,
+ /* 09 */ KEY_V,
+ /* 0a */ KEY_SECTION, /* ISO Section */
+ /* 0b */ KEY_B,
+ /* 0c */ KEY_Q,
+ /* 0d */ KEY_W,
+ /* 0e */ KEY_E,
+ /* 0f */ KEY_R,
+ /* 10 */ KEY_Y,
+ /* 11 */ KEY_T,
+ /* 12 */ KEY_1,
+ /* 13 */ KEY_2,
+ /* 14 */ KEY_3,
+ /* 15 */ KEY_4,
+ /* 16 */ KEY_6,
+ /* 17 */ KEY_5,
+ /* 18 */ KEY_EQUAL,
+ /* 19 */ KEY_9,
+ /* 1a */ KEY_7,
+ /* 1b */ KEY_MINUS,
+ /* 1c */ KEY_8,
+ /* 1d */ KEY_0,
+ /* 1e */ KEY_BRACERIGHT,
+ /* 1f */ KEY_O,
+ /* 20 */ KEY_U,
+ /* 21 */ KEY_BRACELEFT,
+ /* 22 */ KEY_I,
+ /* 23 */ KEY_P,
+ /* 24 */ KEY_ENTER,
+ /* 25 */ KEY_L,
+ /* 26 */ KEY_J,
+ /* 27 */ KEY_APOSTROPHE,
+ /* 28 */ KEY_K,
+ /* 29 */ KEY_SEMICOLON,
+ /* 2a */ KEY_BACKSLASH,
+ /* 2b */ KEY_COMMA,
+ /* 2c */ KEY_SLASH,
+ /* 2d */ KEY_N,
+ /* 2e */ KEY_M,
+ /* 2f */ KEY_PERIOD,
+ /* 30 */ KEY_TAB,
+ /* 31 */ KEY_SPACE,
+ /* 32 */ KEY_QUOTELEFT,
+ /* 33 */ KEY_BACKSPACE,
+ /* 34 */ KEY_UNKNOWN,
+ /* 35 */ KEY_ESCAPE,
+ /* 36 */ KEY_META,
+ /* 37 */ KEY_META,
+ /* 38 */ KEY_SHIFT,
+ /* 39 */ KEY_CAPSLOCK,
+ /* 3a */ KEY_ALT,
+ /* 3b */ KEY_CONTROL,
+ /* 3c */ KEY_SHIFT,
+ /* 3d */ KEY_ALT,
+ /* 3e */ KEY_CONTROL,
+ /* 3f */ KEY_UNKNOWN, /* Function */
+ /* 40 */ KEY_UNKNOWN, /* F17 */
+ /* 41 */ KEY_KP_PERIOD,
+ /* 42 */ KEY_UNKNOWN,
+ /* 43 */ KEY_KP_MULTIPLY,
+ /* 44 */ KEY_UNKNOWN,
+ /* 45 */ KEY_KP_ADD,
+ /* 46 */ KEY_UNKNOWN,
+ /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */
+ /* 48 */ KEY_VOLUMEUP, /* VolumeUp */
+ /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */
+ /* 4a */ KEY_VOLUMEMUTE, /* Mute */
+ /* 4b */ KEY_KP_DIVIDE,
+ /* 4c */ KEY_KP_ENTER,
+ /* 4d */ KEY_UNKNOWN,
+ /* 4e */ KEY_KP_SUBTRACT,
+ /* 4f */ KEY_UNKNOWN, /* F18 */
+ /* 50 */ KEY_UNKNOWN, /* F19 */
+ /* 51 */ KEY_EQUAL, /* KeypadEqual */
+ /* 52 */ KEY_KP_0,
+ /* 53 */ KEY_KP_1,
+ /* 54 */ KEY_KP_2,
+ /* 55 */ KEY_KP_3,
+ /* 56 */ KEY_KP_4,
+ /* 57 */ KEY_KP_5,
+ /* 58 */ KEY_KP_6,
+ /* 59 */ KEY_KP_7,
+ /* 5a */ KEY_UNKNOWN, /* F20 */
+ /* 5b */ KEY_KP_8,
+ /* 5c */ KEY_KP_9,
+ /* 5d */ KEY_YEN, /* JIS Yen */
+ /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */
+ /* 5f */ KEY_COMMA, /* JIS KeypadComma */
+ /* 60 */ KEY_F5,
+ /* 61 */ KEY_F6,
+ /* 62 */ KEY_F7,
+ /* 63 */ KEY_F3,
+ /* 64 */ KEY_F8,
+ /* 65 */ KEY_F9,
+ /* 66 */ KEY_UNKNOWN, /* JIS Eisu */
+ /* 67 */ KEY_F11,
+ /* 68 */ KEY_UNKNOWN, /* JIS Kana */
+ /* 69 */ KEY_F13,
+ /* 6a */ KEY_F16,
+ /* 6b */ KEY_F14,
+ /* 6c */ KEY_UNKNOWN,
+ /* 6d */ KEY_F10,
+ /* 6e */ KEY_MENU,
+ /* 6f */ KEY_F12,
+ /* 70 */ KEY_UNKNOWN,
+ /* 71 */ KEY_F15,
+ /* 72 */ KEY_INSERT, /* Really Help... */
+ /* 73 */ KEY_HOME,
+ /* 74 */ KEY_PAGEUP,
+ /* 75 */ KEY_DELETE,
+ /* 76 */ KEY_F4,
+ /* 77 */ KEY_END,
+ /* 78 */ KEY_F2,
+ /* 79 */ KEY_PAGEDOWN,
+ /* 7a */ KEY_F1,
+ /* 7b */ KEY_LEFT,
+ /* 7c */ KEY_RIGHT,
+ /* 7d */ KEY_DOWN,
+ /* 7e */ KEY_UP,
+ /* 7f */ KEY_UNKNOWN,
+ };
+
+ if (key >= 128) {
+ return KEY_UNKNOWN;
+ }
+
+ return table[key];
+}
+
+struct _KeyCodeMap {
+ UniChar kchar;
+ int kcode;
+};
+
+static const _KeyCodeMap _keycodes[55] = {
+ { '`', KEY_QUOTELEFT },
+ { '~', KEY_ASCIITILDE },
+ { '0', KEY_0 },
+ { '1', KEY_1 },
+ { '2', KEY_2 },
+ { '3', KEY_3 },
+ { '4', KEY_4 },
+ { '5', KEY_5 },
+ { '6', KEY_6 },
+ { '7', KEY_7 },
+ { '8', KEY_8 },
+ { '9', KEY_9 },
+ { '-', KEY_MINUS },
+ { '_', KEY_UNDERSCORE },
+ { '=', KEY_EQUAL },
+ { '+', KEY_PLUS },
+ { 'q', KEY_Q },
+ { 'w', KEY_W },
+ { 'e', KEY_E },
+ { 'r', KEY_R },
+ { 't', KEY_T },
+ { 'y', KEY_Y },
+ { 'u', KEY_U },
+ { 'i', KEY_I },
+ { 'o', KEY_O },
+ { 'p', KEY_P },
+ { '[', KEY_BRACELEFT },
+ { ']', KEY_BRACERIGHT },
+ { '{', KEY_BRACELEFT },
+ { '}', KEY_BRACERIGHT },
+ { 'a', KEY_A },
+ { 's', KEY_S },
+ { 'd', KEY_D },
+ { 'f', KEY_F },
+ { 'g', KEY_G },
+ { 'h', KEY_H },
+ { 'j', KEY_J },
+ { 'k', KEY_K },
+ { 'l', KEY_L },
+ { ';', KEY_SEMICOLON },
+ { ':', KEY_COLON },
+ { '\'', KEY_APOSTROPHE },
+ { '\"', KEY_QUOTEDBL },
+ { '\\', KEY_BACKSLASH },
+ { '#', KEY_NUMBERSIGN },
+ { 'z', KEY_Z },
+ { 'x', KEY_X },
+ { 'c', KEY_C },
+ { 'v', KEY_V },
+ { 'b', KEY_B },
+ { 'n', KEY_N },
+ { 'm', KEY_M },
+ { ',', KEY_COMMA },
+ { '.', KEY_PERIOD },
+ { '/', KEY_SLASH }
+};
+
+static int remapKey(unsigned int key, unsigned int state) {
+ if (isNumpadKey(key)) {
+ return translateKey(key);
+ }
+
+ TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
+ if (!currentKeyboard) {
+ return translateKey(key);
+ }
+
+ CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
+ if (!layoutData) {
+ return translateKey(key);
+ }
+
+ const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
+
+ UInt32 keysDown = 0;
+ UniChar chars[4];
+ UniCharCount realLength;
+
+ OSStatus err = UCKeyTranslate(keyboardLayout,
+ key,
+ kUCKeyActionDisplay,
+ (state >> 8) & 0xFF,
+ LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysBit,
+ &keysDown,
+ sizeof(chars) / sizeof(chars[0]),
+ &realLength,
+ chars);
+
+ if (err != noErr) {
+ return translateKey(key);
+ }
+
+ for (unsigned int i = 0; i < 55; i++) {
+ if (_keycodes[i].kchar == chars[0]) {
+ return _keycodes[i].kcode;
+ }
+ }
+ return translateKey(key);
+}
+
+- (void)keyDown:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
+
+ if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+ // Fallback unicode character handler used if IME is not active
+ for (NSUInteger i = 0; i < length; i++) {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
+ ke.raw = true;
+ ke.unicode = [characters characterAtIndex:i];
+
+ _push_to_key_event_buffer(ke);
+ }
+ } else {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
+ ke.raw = false;
+ ke.unicode = 0;
+
+ _push_to_key_event_buffer(ke);
+ }
+ }
+
+ // Pass events to IME handler
+ if (wd.im_active) {
+ [self interpretKeyEvents:[NSArray arrayWithObject:event]];
+ }
+}
+
+- (void)flagsChanged:(NSEvent *)event {
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.echo = false;
+ ke.raw = true;
+
+ int key = [event keyCode];
+ int mod = [event modifierFlags];
+
+ if (key == 0x36 || key == 0x37) {
+ if (mod & NSEventModifierFlagCommand) {
+ mod &= ~NSEventModifierFlagCommand;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x38 || key == 0x3c) {
+ if (mod & NSEventModifierFlagShift) {
+ mod &= ~NSEventModifierFlagShift;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x3a || key == 0x3d) {
+ if (mod & NSEventModifierFlagOption) {
+ mod &= ~NSEventModifierFlagOption;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x3b || key == 0x3e) {
+ if (mod & NSEventModifierFlagControl) {
+ mod &= ~NSEventModifierFlagControl;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else {
+ return;
+ }
+
+ ke.osx_state = mod;
+ ke.keycode = remapKey(key, mod);
+ ke.physical_keycode = translateKey(key);
+ ke.unicode = 0;
+
+ _push_to_key_event_buffer(ke);
+ }
+}
+
+- (void)keyUp:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
+
+ // Fallback unicode character handler used if IME is not active
+ if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+ for (NSUInteger i = 0; i < length; i++) {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
+ ke.raw = true;
+ ke.unicode = [characters characterAtIndex:i];
+
+ _push_to_key_event_buffer(ke);
+ }
+ } else {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
+ ke.raw = true;
+ ke.unicode = 0;
+
+ _push_to_key_event_buffer(ke);
+ }
+ }
+}
+
+inline void sendScrollEvent(DisplayServer::WindowID window_id, int button, double factor, int modifierFlags) {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ unsigned int mask = 1 << (button - 1);
+
+ Ref<InputEventMouseButton> sc;
+ sc.instance();
+
+ sc->set_window_id(window_id);
+ _get_key_modifier_state(modifierFlags, sc);
+ sc->set_button_index(button);
+ sc->set_factor(factor);
+ sc->set_pressed(true);
+ sc->set_position(wd.mouse_pos);
+ sc->set_global_position(wd.mouse_pos);
+ DS_OSX->last_button_state |= mask;
+ sc->set_button_mask(DS_OSX->last_button_state);
+
+ Input::get_singleton()->accumulate_input_event(sc);
+
+ sc.instance();
+ sc->set_window_id(window_id);
+ sc->set_button_index(button);
+ sc->set_factor(factor);
+ sc->set_pressed(false);
+ sc->set_position(wd.mouse_pos);
+ sc->set_global_position(wd.mouse_pos);
+ DS_OSX->last_button_state &= ~mask;
+ sc->set_button_mask(DS_OSX->last_button_state);
+
+ Input::get_singleton()->accumulate_input_event(sc);
+}
+
+inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ Ref<InputEventPanGesture> pg;
+ pg.instance();
+
+ pg->set_window_id(window_id);
+ _get_key_modifier_state(modifierFlags, pg);
+ pg->set_position(wd.mouse_pos);
+ pg->set_delta(Vector2(-dx, -dy));
+
+ Input::get_singleton()->accumulate_input_event(pg);
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+ ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
+ double deltaX, deltaY;
+
+ _get_mouse_pos(wd, [event locationInWindow]);
+
+ deltaX = [event scrollingDeltaX];
+ deltaY = [event scrollingDeltaY];
+
+ if ([event hasPreciseScrollingDeltas]) {
+ deltaX *= 0.03;
+ deltaY *= 0.03;
+ }
+
+ if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
+ sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]);
+ } else {
+ if (fabs(deltaX)) {
+ sendScrollEvent(window_id, 0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
+ }
+ if (fabs(deltaY)) {
+ sendScrollEvent(window_id, 0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
+ }
+ }
+}
+
+@end
+
+/*************************************************************************/
+/* GodotWindow */
+/*************************************************************************/
+
+@interface GodotWindow : NSWindow {
+}
+
+@end
+
+@implementation GodotWindow
+
+- (BOOL)canBecomeKeyWindow {
+ // Required for NSBorderlessWindowMask windows
+ for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) {
+ if (E->get().window_object == self) {
+ if (E->get().no_focus) {
+ return NO;
+ }
+ }
+ }
+ return YES;
+}
+
+- (BOOL)canBecomeMainWindow {
+ // Required for NSBorderlessWindowMask windows
+ for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) {
+ if (E->get().window_object == self) {
+ if (E->get().no_focus) {
+ return NO;
+ }
+ }
+ }
+ return YES;
+}
+
+@end
+
+/*************************************************************************/
+/* DisplayServerOSX */
+/*************************************************************************/
+
+bool DisplayServerOSX::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_GLOBAL_MENU:
+ case FEATURE_SUBWINDOWS:
+ //case FEATURE_TOUCHSCREEN:
+ case FEATURE_MOUSE:
+ case FEATURE_MOUSE_WARP:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
+ case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_CONSOLE_WINDOW:
+ case FEATURE_IME:
+ case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_HIDPI:
+ case FEATURE_ICON:
+ case FEATURE_NATIVE_ICON:
+ //case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_SWAP_BUFFERS:
+ return true;
+ default: {
+ }
+ }
+ return false;
+}
+
+String DisplayServerOSX::get_name() const {
+ return "OSX";
+}
+
+const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const {
+ const NSMenu *menu = NULL;
+ if (p_menu_root == "") {
+ // Main menu.x
+ menu = [NSApp mainMenu];
+ } else if (p_menu_root.to_lower() == "_dock") {
+ // macOS dock menu.
+ menu = dock_menu;
+ } else {
+ // Submenu.
+ if (submenu.has(p_menu_root)) {
+ menu = submenu[p_menu_root];
+ }
+ }
+ if (menu == apple_menu) {
+ // Do not allow to change Apple menu.
+ return NULL;
+ }
+ return menu;
+}
+
+NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) {
+ NSMenu *menu = NULL;
+ if (p_menu_root == "") {
+ // Main menu.
+ menu = [NSApp mainMenu];
+ } else if (p_menu_root.to_lower() == "_dock") {
+ // macOS dock menu.
+ menu = dock_menu;
+ } else {
+ // Submenu.
+ if (!submenu.has(p_menu_root)) {
+ NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ submenu[p_menu_root] = n_menu;
+ }
+ menu = submenu[p_menu_root];
+ }
+ if (menu == apple_menu) {
+ // Do not allow to change Apple menu.
+ return NULL;
+ }
+ return menu;
+}
+
+void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+ GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable = false;
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+ GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+ obj->callback = p_callback;
+ obj->meta = p_tag;
+ obj->checkable = true;
+ [menu_item setRepresentedObject:obj];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ NSMenu *sub_menu = _get_menu_root(p_submenu);
+ if (menu && sub_menu) {
+ if (sub_menu == menu) {
+ ERR_PRINT("Can't set submenu to self!");
+ return;
+ }
+ if ([sub_menu supermenu]) {
+ ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
+ return;
+ }
+ NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+ [menu setSubmenu:sub_menu forItem:menu_item];
+ }
+}
+
+void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+}
+
+bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return ([menu_item state] == NSControlStateValueOn);
+ }
+ }
+ return false;
+}
+
+bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checkable;
+ }
+ }
+ }
+ return false;
+}
+
+Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->callback;
+ }
+ }
+ }
+ return Callable();
+}
+
+Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->meta;
+ }
+ }
+ }
+ return Variant();
+}
+
+String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ char *utfs = strdup([[menu_item title] UTF8String]);
+ String ret;
+ ret.parse_utf8(utfs);
+ free(utfs);
+ return ret;
+ }
+ }
+ return String();
+}
+
+String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ const NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu) {
+ for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
+ if (E->get() == sub_menu)
+ return E->key();
+ }
+ }
+ }
+ }
+ return String();
+}
+
+void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if (p_checked) {
+ [menu_item setState:NSControlStateValueOn];
+ } else {
+ [menu_item setState:NSControlStateValueOff];
+ }
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ obj->checkable = p_checkable;
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ obj->callback = p_callback;
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GlobalMenuItem *obj = [menu_item representedObject];
+ obj->meta = p_tag;
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
+ }
+}
+
+void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ NSMenu *sub_menu = _get_menu_root(p_submenu);
+ if (menu && sub_menu) {
+ if (sub_menu == menu) {
+ ERR_PRINT("Can't set submenu to self!");
+ return;
+ }
+ if ([sub_menu supermenu]) {
+ ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
+ return;
+ }
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
+ return;
+ }
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu setSubmenu:sub_menu forItem:menu_item];
+ }
+ }
+}
+
+int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ return [menu numberOfItems];
+ } else {
+ return 0;
+ }
+}
+
+void DisplayServerOSX::global_menu_remove_item(const String &p_menu_root, int p_idx) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu.
+ return;
+ }
+ [menu removeItemAtIndex:p_idx];
+ }
+}
+
+void DisplayServerOSX::global_menu_clear(const String &p_menu_root) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ [menu removeAllItems];
+ // Restore Apple menu.
+ if (menu == [NSApp mainMenu]) {
+ NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
+ [menu setSubmenu:apple_menu forItem:menu_item];
+ }
+ }
+}
+
+void DisplayServerOSX::alert(const String &p_alert, const String &p_title) {
+ _THREAD_SAFE_METHOD_
+
+ NSAlert *window = [[NSAlert alloc] init];
+ NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+ NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
+
+ [window addButtonWithTitle:@"OK"];
+ [window setMessageText:ns_title];
+ [window setInformativeText:ns_alert];
+ [window setAlertStyle:NSAlertStyleWarning];
+
+ id key_window = [[NSApplication sharedApplication] keyWindow];
+ [window runModal];
+ [window release];
+ if (key_window) {
+ [key_window makeKeyAndOrderFront:nil];
+ }
+}
+
+Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSAlert *window = [[NSAlert alloc] init];
+ NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+ NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()];
+
+ for (int i = 0; i < p_buttons.size(); i++) {
+ NSString *ns_button = [NSString stringWithUTF8String:p_buttons[i].utf8().get_data()];
+ [window addButtonWithTitle:ns_button];
+ }
+ [window setMessageText:ns_title];
+ [window setInformativeText:ns_description];
+ [window setAlertStyle:NSAlertStyleInformational];
+
+ int button_pressed;
+ NSInteger ret = [window runModal];
+ if (ret == NSAlertFirstButtonReturn) {
+ button_pressed = 0;
+ } else if (ret == NSAlertSecondButtonReturn) {
+ button_pressed = 1;
+ } else if (ret == NSAlertThirdButtonReturn) {
+ button_pressed = 2;
+ } else {
+ button_pressed = 2 + (ret - NSAlertThirdButtonReturn);
+ }
+
+ if (!p_callback.is_null()) {
+ Variant button = button_pressed;
+ Variant *buttonp = &button;
+ Variant ret;
+ Callable::CallError ce;
+ p_callback.call((const Variant **)&buttonp, 1, ret, ce);
+ }
+
+ [window release];
+ return OK;
+}
+
+Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSAlert *window = [[NSAlert alloc] init];
+ NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+ NSString *ns_description = [NSString stringWithUTF8String:p_description.utf8().get_data()];
+ NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 30)];
+
+ [window addButtonWithTitle:@"OK"];
+ [window setMessageText:ns_title];
+ [window setInformativeText:ns_description];
+ [window setAlertStyle:NSAlertStyleInformational];
+
+ [input setStringValue:[NSString stringWithUTF8String:p_partial.utf8().get_data()]];
+ [window setAccessoryView:input];
+
+ [window runModal];
+
+ char *utfs = strdup([[input stringValue] UTF8String]);
+ String ret;
+ ret.parse_utf8(utfs);
+ free(utfs);
+
+ if (!p_callback.is_null()) {
+ Variant text = ret;
+ Variant *textp = &text;
+ Variant ret;
+ Callable::CallError ce;
+ p_callback.call((const Variant **)&textp, 1, ret, ce);
+ }
+
+ [window release];
+ return OK;
+}
+
+void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ if (p_mode == MOUSE_MODE_CAPTURED) {
+ // Apple Docs state that the display parameter is not used.
+ // "This parameter is not used. By default, you may pass kCGDirectMainDisplay."
+ // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ CGDisplayHideCursor(kCGDirectMainDisplay);
+ }
+ CGAssociateMouseAndMouseCursorPosition(false);
+ } else if (p_mode == MOUSE_MODE_HIDDEN) {
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ CGDisplayHideCursor(kCGDirectMainDisplay);
+ }
+ CGAssociateMouseAndMouseCursorPosition(true);
+ } else if (p_mode == MOUSE_MODE_CONFINED) {
+ CGDisplayShowCursor(kCGDirectMainDisplay);
+ CGAssociateMouseAndMouseCursorPosition(false);
+ } else {
+ CGDisplayShowCursor(kCGDirectMainDisplay);
+ CGAssociateMouseAndMouseCursorPosition(true);
+ }
+
+ warp_events.clear();
+ mouse_mode = p_mode;
+}
+
+DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const {
+ return mouse_mode;
+}
+
+void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) {
+ _THREAD_SAFE_METHOD_
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ last_mouse_pos = p_to;
+ } else {
+ WindowData &wd = windows[MAIN_WINDOW_ID];
+
+ //local point in window coords
+ const NSRect contentRect = [wd.window_view frame];
+ const float scale = screen_get_max_scale();
+ NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0);
+ NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
+
+ //point in scren coords
+ CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
+
+ //do the warping
+ CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
+ CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0);
+ CGAssociateMouseAndMouseCursorPosition(false);
+ CGWarpMouseCursorPosition(lMouseWarpPos);
+ if (mouse_mode != MOUSE_MODE_CONFINED) {
+ CGAssociateMouseAndMouseCursorPosition(true);
+ }
+ }
+}
+
+Point2i DisplayServerOSX::mouse_get_position() const {
+ return last_mouse_pos;
+}
+
+Point2i DisplayServerOSX::mouse_get_absolute_position() const {
+ _THREAD_SAFE_METHOD_
+
+ const NSPoint mouse_pos = [NSEvent mouseLocation];
+ const float scale = screen_get_max_scale();
+
+ for (NSScreen *screen in [NSScreen screens]) {
+ NSRect frame = [screen frame];
+ if (NSMouseInRect(mouse_pos, frame, NO)) {
+ return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin();
+ }
+ }
+ return Vector2i();
+}
+
+int DisplayServerOSX::mouse_get_button_state() const {
+ return last_button_state;
+}
+
+void DisplayServerOSX::clipboard_set(const String &p_text) {
+ _THREAD_SAFE_METHOD_
+
+ NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()];
+ NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString];
+
+ NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+ [pasteboard clearContents];
+ [pasteboard writeObjects:copiedStringArray];
+}
+
+String DisplayServerOSX::clipboard_get() const {
+ _THREAD_SAFE_METHOD_
+
+ NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+ NSArray *classArray = [NSArray arrayWithObject:[NSString class]];
+ NSDictionary *options = [NSDictionary dictionary];
+
+ BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
+
+ if (!ok) {
+ return "";
+ }
+
+ NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
+ NSString *string = [objectsToPaste objectAtIndex:0];
+
+ char *utfs = strdup([string UTF8String]);
+ String ret;
+ ret.parse_utf8(utfs);
+ free(utfs);
+
+ return ret;
+}
+
+int DisplayServerOSX::get_screen_count() const {
+ _THREAD_SAFE_METHOD_
+
+ NSArray *screenArray = [NSScreen screens];
+ return [screenArray count];
+}
+
+// Returns the native top-left screen coordinate of the smallest rectangle
+// that encompasses all screens. Needed in get_screen_position(),
+// window_get_position, and window_set_position()
+// to convert between OS X native screen coordinates and the ones expected by Godot
+
+static bool displays_arrangement_dirty = true;
+static bool displays_scale_dirty = true;
+static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) {
+ displays_arrangement_dirty = true;
+ displays_scale_dirty = true;
+}
+
+Point2i DisplayServerOSX::_get_screens_origin() const {
+ static Point2i origin;
+
+ if (displays_arrangement_dirty) {
+ origin = Point2i();
+
+ for (int i = 0; i < get_screen_count(); i++) {
+ Point2i position = _get_native_screen_position(i);
+ if (position.x < origin.x) {
+ origin.x = position.x;
+ }
+ if (position.y > origin.y) {
+ origin.y = position.y;
+ }
+ }
+ displays_arrangement_dirty = false;
+ }
+
+ return origin;
+}
+
+Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const {
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
+ // Return the top-left corner of the screen, for OS X the y starts at the bottom
+ return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale();
+ }
+
+ return Point2i();
+}
+
+Point2i DisplayServerOSX::screen_get_position(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin();
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot expects a positive value
+ position.y *= -1;
+ return position;
+}
+
+Size2i DisplayServerOSX::screen_get_size(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ // Note: Use frame to get the whole screen size
+ NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
+ return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale();
+ }
+
+ return Size2i();
+}
+
+int DisplayServerOSX::screen_get_dpi(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
+ NSSize displayDPI = [[description objectForKey:NSDeviceResolution] sizeValue];
+ return (displayDPI.width + displayDPI.height) / 2;
+ }
+
+ return 96;
+}
+
+float DisplayServerOSX::screen_get_scale(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+ if (OS::get_singleton()->is_hidpi_allowed()) {
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ if ([[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) {
+ return fmax(1.0, [[screenArray objectAtIndex:p_screen] backingScaleFactor]);
+ }
+ }
+ }
+
+ return 1.f;
+}
+
+float DisplayServerOSX::screen_get_max_scale() const {
+ _THREAD_SAFE_METHOD_
+
+ static float scale = 1.f;
+ if (displays_scale_dirty) {
+ int screen_count = get_screen_count();
+ for (int i = 0; i < screen_count; i++) {
+ scale = fmax(scale, screen_get_scale(i));
+ }
+ displays_scale_dirty = false;
+ }
+ return scale;
+}
+
+Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ const float scale = screen_get_max_scale();
+ NSRect nsrect = [[screenArray objectAtIndex:p_screen] visibleFrame];
+
+ Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * scale - _get_screens_origin();
+ position.y *= -1;
+ Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * scale;
+
+ return Rect2i(position, size);
+ }
+
+ return Rect2i();
+}
+
+Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const {
+ _THREAD_SAFE_METHOD_
+
+ Vector<int> ret;
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+
+DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ WindowID id = _create_window(p_mode, p_rect);
+ for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+ if (p_flags & (1 << i)) {
+ window_set_flag(WindowFlags(i), true, id);
+ }
+ }
+
+ return id;
+}
+
+void DisplayServerOSX::show_window(WindowID p_id) {
+ WindowData &wd = windows[p_id];
+
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
+}
+
+void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) {
+ _THREAD_SAFE_METHOD_
+
+ if (!wd.event_callback.is_null()) {
+ Variant event = int(p_event);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (E->get().window_object == p_window) {
+ return E->key();
+ }
+ }
+ return INVALID_WINDOW_ID;
+}
+
+void DisplayServerOSX::_update_window(WindowData p_wd) {
+ bool borderless_full = false;
+
+ if (p_wd.borderless) {
+ NSRect frameRect = [p_wd.window_object frame];
+ NSRect screenRect = [[p_wd.window_object screen] frame];
+
+ // Check if our window covers up the screen
+ if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y &&
+ frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) {
+ borderless_full = true;
+ }
+ }
+
+ if (borderless_full) {
+ // If the window covers up the screen set the level to above the main menu and hide on deactivate
+ [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
+ [p_wd.window_object setHidesOnDeactivate:YES];
+ } else {
+ // Reset these when our window is not a borderless window that covers up the screen
+ if (p_wd.on_top) {
+ [p_wd.window_object setLevel:NSFloatingWindowLevel];
+ } else {
+ [p_wd.window_object setLevel:NSNormalWindowLevel];
+ }
+ [p_wd.window_object setHidesOnDeactivate:NO];
+ }
+}
+
+void DisplayServerOSX::delete_sub_window(WindowID p_id) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_id));
+ ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");
+
+ WindowData &wd = windows[p_id];
+
+ [wd.window_object setContentView:nil];
+ [wd.window_object close];
+}
+
+void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
+}
+
+void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.mpath = p_region;
+}
+
+void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.rect_changed_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.event_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.input_event_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.input_text_callback = p_callable;
+}
+
+void DisplayServerOSX::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.drop_files_callback = p_callable;
+}
+
+int DisplayServerOSX::window_get_current_screen(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+ const WindowData &wd = windows[p_window];
+
+ const NSUInteger index = [[NSScreen screens] indexOfObject:[wd.window_object screen]];
+ return (index == NSNotFound) ? 0 : index;
+}
+
+void DisplayServerOSX::window_set_current_screen(int p_screen, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+ Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
+ window_set_position(wpos + screen_get_position(p_screen), p_window);
+}
+
+void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(p_window == p_parent);
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd_window = windows[p_window];
+
+ ERR_FAIL_COND(wd_window.transient_parent == p_parent);
+
+ ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
+ if (p_parent == INVALID_WINDOW_ID) {
+ //remove transient
+ ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
+ ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
+
+ WindowData &wd_parent = windows[wd_window.transient_parent];
+
+ wd_window.transient_parent = INVALID_WINDOW_ID;
+ wd_parent.transient_children.erase(p_window);
+
+ [wd_parent.window_object removeChildWindow:wd_window.window_object];
+ } else {
+ ERR_FAIL_COND(!windows.has(p_parent));
+ ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
+ WindowData &wd_parent = windows[p_parent];
+
+ wd_window.transient_parent = p_parent;
+ wd_parent.transient_children.insert(p_window);
+
+ [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove];
+ }
+}
+
+Point2i DisplayServerOSX::window_get_position(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
+ const WindowData &wd = windows[p_window];
+
+ NSRect nsrect = [wd.window_object frame];
+ Point2i pos;
+
+ // Return the position of the top-left corner, for OS X the y starts at the bottom
+ const float scale = screen_get_max_scale();
+ pos.x = nsrect.origin.x;
+ pos.y = (nsrect.origin.y + nsrect.size.height);
+ pos *= scale;
+ pos -= _get_screens_origin();
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot expects a positive value
+ pos.y *= -1;
+ return pos;
+}
+
+void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ Point2i position = p_position;
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot passes a positive value
+ position.y *= -1;
+ position += _get_screens_origin();
+ position /= screen_get_max_scale();
+
+ [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x, position.y)];
+
+ _update_window(wd);
+ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+}
+
+void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+ ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ wd.max_size = p_size;
+
+ if ((wd.max_size != Size2i()) && !wd.fullscreen) {
+ Size2i size = wd.max_size / screen_get_max_scale();
+ [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ } else {
+ [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ }
+}
+
+Size2i DisplayServerOSX::window_get_max_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ return wd.max_size;
+}
+
+void DisplayServerOSX::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+ ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ wd.min_size = p_size;
+
+ if ((wd.min_size != Size2i()) && !wd.fullscreen) {
+ Size2i size = wd.min_size / screen_get_max_scale();
+ [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ } else {
+ [wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+ }
+}
+
+Size2i DisplayServerOSX::window_get_min_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ return wd.min_size;
+}
+
+void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ Size2i size = p_size / screen_get_max_scale();
+
+ NSPoint top_left;
+ NSRect old_frame = [wd.window_object frame];
+ top_left.x = old_frame.origin.x;
+ top_left.y = NSMaxY(old_frame);
+
+ NSRect new_frame = NSMakeRect(0, 0, size.x, size.y);
+ new_frame = [wd.window_object frameRectForContentRect:new_frame];
+
+ new_frame.origin.x = top_left.x;
+ new_frame.origin.y = top_left.y - new_frame.size.height;
+
+ [wd.window_object setFrame:new_frame display:YES];
+
+ _update_window(wd);
+}
+
+Size2i DisplayServerOSX::window_get_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ return wd.size;
+}
+
+Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ NSRect frame = [wd.window_object frame];
+ return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale();
+}
+
+bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const {
+ return true;
+}
+
+void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) {
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (!OS_OSX::get_singleton()->is_layered_allowed()) {
+ return;
+ }
+ if (wd.layered_window != p_enabled) {
+ if (p_enabled) {
+ [wd.window_object setBackgroundColor:[NSColor clearColor]];
+ [wd.window_object setOpaque:NO];
+ [wd.window_object setHasShadow:NO];
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ [layer setOpaque:NO];
+ }
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ //TODO - implement transparency for Vulkan
+ }
+#endif
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+ wd.layered_window = true;
+ } else {
+ [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
+ [wd.window_object setOpaque:YES];
+ [wd.window_object setHasShadow:YES];
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ [layer setOpaque:YES];
+ }
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ //TODO - implement transparency for Vulkan
+ }
+#endif
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+ wd.layered_window = false;
+ }
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ //TODO - implement transparency for Vulkan
+ }
+#endif
+ NSRect frameRect = [wd.window_object frame];
+ [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES];
+ [wd.window_object setFrame:frameRect display:YES];
+ }
+}
+
+void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ WindowMode old_mode = window_get_mode(p_window);
+ if (old_mode == p_mode) {
+ return; // do nothing
+ }
+
+ switch (old_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ //do nothing
+ } break;
+ case WINDOW_MODE_MINIMIZED: {
+ [wd.window_object deminiaturize:nil];
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ if (wd.layered_window) {
+ _set_window_per_pixel_transparency_enabled(true, p_window);
+ }
+ if (wd.resize_disabled) { //restore resize disabled
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+ }
+ if (wd.min_size != Size2i()) {
+ Size2i size = wd.min_size / screen_get_max_scale();
+ [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ }
+ if (wd.max_size != Size2i()) {
+ Size2i size = wd.max_size / screen_get_max_scale();
+ [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ }
+ [wd.window_object toggleFullScreen:nil];
+ wd.fullscreen = false;
+ } break;
+ case WINDOW_MODE_MAXIMIZED: {
+ if ([wd.window_object isZoomed]) {
+ [wd.window_object zoom:nil];
+ }
+ } break;
+ }
+
+ switch (p_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ //do nothing
+ } break;
+ case WINDOW_MODE_MINIMIZED: {
+ [wd.window_object performMiniaturize:nil];
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ if (wd.layered_window)
+ _set_window_per_pixel_transparency_enabled(false, p_window);
+ if (wd.resize_disabled) //fullscreen window should be resizable to work
+ [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+ [wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+ [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ [wd.window_object toggleFullScreen:nil];
+ wd.fullscreen = true;
+ } break;
+ case WINDOW_MODE_MAXIMIZED: {
+ if (![wd.window_object isZoomed]) {
+ [wd.window_object zoom:nil];
+ }
+ } break;
+ }
+}
+
+DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
+ const WindowData &wd = windows[p_window];
+
+ if (wd.fullscreen) { //if fullscreen, it's not in another mode
+ return WINDOW_MODE_FULLSCREEN;
+ }
+ if ([wd.window_object isZoomed] && !wd.resize_disabled) {
+ return WINDOW_MODE_MAXIMIZED;
+ }
+ if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) {
+ if ([wd.window_object isMiniaturized]) {
+ return WINDOW_MODE_MINIMIZED;
+ }
+ }
+
+ // all other discarded, return windowed.
+ return WINDOW_MODE_WINDOWED;
+}
+
+void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ wd.resize_disabled = p_enabled;
+ if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs
+ return;
+ }
+ if (p_enabled) {
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+ } else {
+ [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+ }
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ // OrderOut prevents a lose focus bug with the window
+ if ([wd.window_object isVisible]) {
+ [wd.window_object orderOut:nil];
+ }
+ wd.borderless = p_enabled;
+ if (p_enabled) {
+ [wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
+ } else {
+ if (wd.layered_window)
+ _set_window_per_pixel_transparency_enabled(false, p_window);
+ [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
+ // Force update of the window styles
+ NSRect frameRect = [wd.window_object frame];
+ [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
+ [wd.window_object setFrame:frameRect display:NO];
+ }
+ _update_window(wd);
+ if ([wd.window_object isVisible]) {
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
+ }
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ wd.on_top = p_enabled;
+ if (p_enabled) {
+ [wd.window_object setLevel:NSFloatingWindowLevel];
+ } else {
+ [wd.window_object setLevel:NSNormalWindowLevel];
+ }
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ wd.layered_window = p_enabled;
+ if (p_enabled) {
+ [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless
+ } else if (!wd.borderless) {
+ [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
+ }
+ _set_window_per_pixel_transparency_enabled(p_enabled, p_window);
+ } break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ wd.no_focus = p_enabled;
+ } break;
+ default: {
+ }
+ }
+}
+
+bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+ const WindowData &wd = windows[p_window];
+
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ return wd.resize_disabled;
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ return [wd.window_object styleMask] == NSWindowStyleMaskBorderless;
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ return [wd.window_object level] == NSFloatingWindowLevel;
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ return wd.layered_window;
+ } break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ return wd.no_focus;
+ } break;
+ default: {
+ }
+ }
+
+ return false;
+}
+
+void DisplayServerOSX::window_request_attention(WindowID p_window) {
+ // It's app global, ignore window id.
+ [NSApp requestUserAttention:NSCriticalRequest];
+}
+
+void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ const WindowData &wd = windows[p_window];
+
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
+}
+
+bool DisplayServerOSX::window_can_draw(WindowID p_window) const {
+ return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;
+}
+
+bool DisplayServerOSX::can_any_window_draw() const {
+ _THREAD_SAFE_METHOD_
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void DisplayServerOSX::window_set_ime_active(const bool p_active, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.im_active = p_active;
+
+ if (!p_active) {
+ [wd.window_view cancelComposition];
+ }
+}
+
+void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.im_position = p_pos;
+}
+
+bool DisplayServerOSX::get_swap_cancel_ok() {
+ return false;
+}
+
+void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ if (cursor_shape == p_shape) {
+ return;
+ }
+
+ if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+ cursor_shape = p_shape;
+ return;
+ }
+
+ if (cursors[p_shape] != NULL) {
+ [cursors[p_shape] set];
+ } else {
+ switch (p_shape) {
+ case CURSOR_ARROW:
+ [[NSCursor arrowCursor] set];
+ break;
+ case CURSOR_IBEAM:
+ [[NSCursor IBeamCursor] set];
+ break;
+ case CURSOR_POINTING_HAND:
+ [[NSCursor pointingHandCursor] set];
+ break;
+ case CURSOR_CROSS:
+ [[NSCursor crosshairCursor] set];
+ break;
+ case CURSOR_WAIT:
+ [[NSCursor arrowCursor] set];
+ break;
+ case CURSOR_BUSY:
+ [[NSCursor arrowCursor] set];
+ break;
+ case CURSOR_DRAG:
+ [[NSCursor closedHandCursor] set];
+ break;
+ case CURSOR_CAN_DROP:
+ [[NSCursor openHandCursor] set];
+ break;
+ case CURSOR_FORBIDDEN:
+ [[NSCursor operationNotAllowedCursor] set];
+ break;
+ case CURSOR_VSIZE:
+ [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set];
+ break;
+ case CURSOR_HSIZE:
+ [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set];
+ break;
+ case CURSOR_BDIAGSIZE:
+ [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set];
+ break;
+ case CURSOR_FDIAGSIZE:
+ [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set];
+ break;
+ case CURSOR_MOVE:
+ [[NSCursor arrowCursor] set];
+ break;
+ case CURSOR_VSPLIT:
+ [[NSCursor resizeUpDownCursor] set];
+ break;
+ case CURSOR_HSPLIT:
+ [[NSCursor resizeLeftRightCursor] set];
+ break;
+ case CURSOR_HELP:
+ [_cursorFromSelector(@selector(_helpCursor)) set];
+ break;
+ default: {
+ }
+ }
+ }
+
+ cursor_shape = p_shape;
+}
+
+DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const {
+ return cursor_shape;
+}
+
+void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_cursor.is_valid()) {
+ Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
+ cursor_set_shape(p_shape);
+ return;
+ }
+ cursors_cache.erase(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ Ref<AtlasTexture> atlas_texture = p_cursor;
+ Ref<Image> image;
+ Size2 texture_size;
+ Rect2 atlas_rect;
+
+ if (texture.is_valid()) {
+ image = texture->get_data();
+ }
+
+ if (!image.is_valid() && atlas_texture.is_valid()) {
+ texture = atlas_texture->get_atlas();
+
+ atlas_rect.size.width = texture->get_width();
+ atlas_rect.size.height = texture->get_height();
+ atlas_rect.position.x = atlas_texture->get_region().position.x;
+ atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else if (image.is_valid()) {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(!texture.is_valid());
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+ image = texture->get_data();
+
+ ERR_FAIL_COND(!image.is_valid());
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:int(texture_size.width)
+ pixelsHigh:int(texture_size.height)
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:int(texture_size.width) * 4
+ bitsPerPixel:32];
+
+ ERR_FAIL_COND(imgrep == nil);
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = int(texture_size.width * texture_size.height);
+
+ for (int i = 0; i < len; i++) {
+ int row_index = floor(i / texture_size.width) + atlas_rect.position.y;
+ int column_index = (i % int(texture_size.width)) + atlas_rect.position.x;
+
+ if (atlas_texture.is_valid()) {
+ column_index = MIN(column_index, atlas_rect.size.width - 1);
+ row_index = MIN(row_index, atlas_rect.size.height - 1);
+ }
+
+ uint32_t color = image->get_pixel(column_index, row_index).to_argb32();
+
+ uint8_t alpha = (color >> 24) & 0xFF;
+ pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255;
+ pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255;
+ pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255;
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)];
+ [nsimage addRepresentation:imgrep];
+
+ NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)];
+
+ [cursors[p_shape] release];
+ cursors[p_shape] = cursor;
+
+ Vector<Variant> params;
+ params.push_back(p_cursor);
+ params.push_back(p_hotspot);
+ cursors_cache.insert(p_shape, params);
+
+ if (p_shape == cursor_shape) {
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ [cursor set];
+ }
+ }
+
+ [imgrep release];
+ [nsimage release];
+ } else {
+ // Reset to default system cursor
+ if (cursors[p_shape] != NULL) {
+ [cursors[p_shape] release];
+ cursors[p_shape] = NULL;
+ }
+
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+
+ cursors_cache.erase(p_shape);
+ }
+}
+
+struct LayoutInfo {
+ String name;
+ String code;
+};
+
+static Vector<LayoutInfo> kbd_layouts;
+static int current_layout = 0;
+static bool keyboard_layout_dirty = true;
+static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
+ kbd_layouts.clear();
+ current_layout = 0;
+ keyboard_layout_dirty = true;
+}
+
+void _update_keyboard_layouts() {
+ @autoreleasepool {
+ TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource();
+ NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName);
+ CFRelease(cur_source);
+
+ // Enum IME layouts
+ NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
+ NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
+ for (NSUInteger i = 0; i < [list_ime count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
+
+ NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
+
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
+ }
+ }
+ [list_ime release];
+
+ // Enum plain keyboard layouts
+ NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
+ NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
+ for (NSUInteger i = 0; i < [list_kbd count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
+
+ NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
+
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
+ }
+ }
+ [list_kbd release];
+ }
+
+ keyboard_layout_dirty = false;
+}
+
+int DisplayServerOSX::keyboard_get_layout_count() const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+ return kbd_layouts.size();
+}
+
+void DisplayServerOSX::keyboard_set_current_layout(int p_index) {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+
+ ERR_FAIL_INDEX(p_index, kbd_layouts.size());
+
+ NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()];
+
+ NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
+ NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
+ for (NSUInteger i = 0; i < [list_kbd count]; i++) {
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ if ([name isEqualToString:cur_name]) {
+ TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]);
+ break;
+ }
+ }
+ [list_kbd release];
+
+ NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
+ NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
+ for (NSUInteger i = 0; i < [list_ime count]; i++) {
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ if ([name isEqualToString:cur_name]) {
+ TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]);
+ break;
+ }
+ }
+ [list_ime release];
+}
+
+int DisplayServerOSX::keyboard_get_current_layout() const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+
+ return current_layout;
+}
+
+String DisplayServerOSX::keyboard_get_layout_language(int p_index) const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+
+ ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
+ return kbd_layouts[p_index].code;
+}
+
+String DisplayServerOSX::keyboard_get_layout_name(int p_index) const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+
+ ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
+ return kbd_layouts[p_index].name;
+}
+
+void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEvent> ev = p_event;
+ Input::get_singleton()->accumulate_input_event(ev);
+}
+
+void DisplayServerOSX::_release_pressed_events() {
+ _THREAD_SAFE_METHOD_
+ if (Input::get_singleton()) {
+ Input::get_singleton()->release_pressed_events();
+ }
+}
+
+void DisplayServerOSX::_process_key_events() {
+ Ref<InputEventKey> k;
+ for (int i = 0; i < key_event_pos; i++) {
+ const KeyEvent &ke = key_event_buffer[i];
+ if (ke.raw) {
+ // Non IME input - no composite characters, pass events as is
+ k.instance();
+
+ k->set_window_id(ke.window_id);
+ _get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode(ke.physical_keycode);
+ k->set_unicode(ke.unicode);
+
+ _push_input(k);
+ } else {
+ // IME input
+ if ((i == 0 && ke.keycode == 0) || (i > 0 && key_event_buffer[i - 1].keycode == 0)) {
+ k.instance();
+
+ k->set_window_id(ke.window_id);
+ _get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(0);
+ k->set_physical_keycode(0);
+ k->set_unicode(ke.unicode);
+
+ _push_input(k);
+ }
+ if (ke.keycode != 0) {
+ k.instance();
+
+ k->set_window_id(ke.window_id);
+ _get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode(ke.physical_keycode);
+
+ if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == 0) {
+ k->set_unicode(key_event_buffer[i + 1].unicode);
+ }
+
+ _push_input(k);
+ }
+ }
+ }
+
+ key_event_pos = 0;
+}
+
+void DisplayServerOSX::process_events() {
+ _THREAD_SAFE_METHOD_
+
+ while (true) {
+ NSEvent *event = [NSApp
+ nextEventMatchingMask:NSEventMaskAny
+ untilDate:[NSDate distantPast]
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+
+ if (event == nil) {
+ break;
+ }
+
+ [NSApp sendEvent:event];
+ }
+
+ if (!drop_events) {
+ _process_key_events();
+ Input::get_singleton()->flush_accumulated_events();
+ }
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ WindowData &wd = E->get();
+ if (wd.mpath.size() > 0) {
+ const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) {
+ if ([wd.window_object ignoresMouseEvents]) {
+ [wd.window_object setIgnoresMouseEvents:NO];
+ }
+ } else {
+ if (![wd.window_object ignoresMouseEvents]) {
+ [wd.window_object setIgnoresMouseEvents:YES];
+ }
+ }
+ } else {
+ if ([wd.window_object ignoresMouseEvents]) {
+ [wd.window_object setIgnoresMouseEvents:NO];
+ }
+ }
+ }
+
+ [autoreleasePool drain];
+ autoreleasePool = [[NSAutoreleasePool alloc] init];
+}
+
+void DisplayServerOSX::force_process_and_drop_events() {
+ _THREAD_SAFE_METHOD_
+
+ drop_events = true;
+ process_events();
+ drop_events = false;
+}
+
+void DisplayServerOSX::set_native_icon(const String &p_filename) {
+ _THREAD_SAFE_METHOD_
+
+ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND(!f);
+
+ Vector<uint8_t> data;
+ uint32_t len = f->get_len();
+ data.resize(len);
+ f->get_buffer((uint8_t *)&data.write[0], len);
+ memdelete(f);
+
+ NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
+ ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data.");
+
+ NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
+ ERR_FAIL_COND_MSG(!icon, "Error loading icon.");
+
+ [NSApp setApplicationIconImage:icon];
+}
+
+void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) {
+ _THREAD_SAFE_METHOD_
+
+ Ref<Image> img = p_icon;
+ img = img->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ ERR_FAIL_COND(imgrep == nil);
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ ERR_FAIL_COND(nsimg == nil);
+
+ [nsimg addRepresentation:imgrep];
+ [NSApp setApplicationIconImage:nsimg];
+
+ [imgrep release];
+ [nsimg release];
+}
+
+Vector<String> DisplayServerOSX::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+Point2i DisplayServerOSX::ime_get_selection() const {
+ return im_selection;
+}
+
+String DisplayServerOSX::ime_get_text() const {
+ return im_text;
+}
+
+DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const {
+ Point2i position = p_position;
+ position.y *= -1;
+ position += _get_screens_origin();
+ position /= screen_get_max_scale();
+
+ NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/];
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if ([E->get().window_object windowNumber] == wnum) {
+ return E->key();
+ }
+ }
+ return INVALID_WINDOW_ID;
+}
+
+void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].instance_id = p_instance;
+}
+
+ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
+ return windows[p_window].instance_id;
+}
+
+DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ds->alert("Your video card driver does not support any of the supported Metal versions.", "Unable to initialize Video driver");
+ }
+ return ds;
+}
+
+DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, const Rect2i &p_rect) {
+ WindowID id;
+ const float scale = screen_get_max_scale();
+ {
+ WindowData wd;
+
+ wd.window_delegate = [[GodotWindowDelegate alloc] init];
+ ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate");
+ [wd.window_delegate setWindowID:window_id_counter];
+
+ Point2i position = p_rect.position;
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot passes a positive value
+ position.y *= -1;
+ position += _get_screens_origin();
+
+ // initWithContentRect uses bottom-left corner of the window’s frame as origin.
+ wd.window_object = [[GodotWindow alloc]
+ initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale)
+ styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window");
+
+ wd.window_view = [[GodotContentView alloc] init];
+ ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
+ [wd.window_view setWindowID:window_id_counter];
+ [wd.window_view setWantsLayer:TRUE];
+
+ [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [wd.window_object setContentView:wd.window_view];
+ [wd.window_object setDelegate:wd.window_delegate];
+ [wd.window_object setAcceptsMouseMovedEvents:YES];
+ [wd.window_object setRestorable:NO];
+
+ if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) {
+ [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
+ }
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (context_vulkan) {
+ Error err = context_vulkan->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context");
+ }
+ }
+#endif
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+ id = window_id_counter++;
+ windows[id] = wd;
+ }
+
+ WindowData &wd = windows[id];
+ window_set_mode(p_mode, id);
+
+ const NSRect contentRect = [wd.window_view frame];
+ wd.size.width = contentRect.size.width * scale;
+ wd.size.height = contentRect.size.height * scale;
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_resize(id, wd.size.width, wd.size.height);
+ }
+#endif
+
+ return id;
+}
+
+void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ _THREAD_SAFE_METHOD_
+ if (!in_dispatch_input_event) {
+ in_dispatch_input_event = true;
+
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
+
+ Ref<InputEventFromWindow> event_from_window = p_event;
+ if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
+ //send to a window
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_null()) {
+ return;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ } else {
+ //send to all windows
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ Callable callable = E->get().input_event_callback;
+ if (callable.is_null()) {
+ continue;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+
+ in_dispatch_input_event = false;
+ }
+}
+
+void DisplayServerOSX::release_rendering_thread() {
+ //TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::make_rendering_thread() {
+ //TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::swap_buffers() {
+ //TODO - reimplement OpenGLES
+}
+
+void DisplayServerOSX::console_set_visible(bool p_enabled) {
+ //TODO - open terminal and redirect
+}
+
+bool DisplayServerOSX::is_console_visible() const {
+ return isatty(STDIN_FILENO);
+}
+
+DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+ drop_events = false;
+
+ memset(cursors, 0, sizeof(cursors));
+ cursor_shape = CURSOR_ARROW;
+
+ key_event_pos = 0;
+ mouse_mode = MOUSE_MODE_VISIBLE;
+ last_button_state = 0;
+
+ autoreleasePool = [[NSAutoreleasePool alloc] init];
+
+ eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ ERR_FAIL_COND(!eventSource);
+
+ CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0);
+
+ // Implicitly create shared NSApplication instance
+ [GodotApplication sharedApplication];
+
+ // In case we are unbundled, make us a proper UI application
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
+ keyboard_layout_dirty = true;
+ displays_arrangement_dirty = true;
+ displays_scale_dirty = true;
+
+ // Register to be notified on keyboard layout changes
+ CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),
+ NULL, keyboard_layout_changed,
+ kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ // Register to be notified on displays arrangement changes
+ CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL);
+
+ // Menu bar setup must go between sharedApplication above and
+ // finishLaunching below, in order to properly emulate the behavior
+ // of NSApplicationMain
+ NSMenuItem *menu_item;
+ NSString *title;
+
+ NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+ if (nsappname == nil) {
+ nsappname = [[NSProcessInfo processInfo] processName];
+ }
+
+ // Setup Dock menu
+ dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
+
+ // Setup Apple menu
+ apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
+ [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
+
+ [apple_menu addItem:[NSMenuItem separatorItem]];
+
+ NSMenu *services = [[NSMenu alloc] initWithTitle:@""];
+ menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
+ [apple_menu setSubmenu:services forItem:menu_item];
+ [NSApp setServicesMenu:services];
+ [services release];
+
+ [apple_menu addItem:[NSMenuItem separatorItem]];
+
+ title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname];
+ [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+ menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+ [menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)];
+
+ [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+ [apple_menu addItem:[NSMenuItem separatorItem]];
+
+ title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
+ [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+ // Setup menu bar
+ NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
+ [main_menu setSubmenu:apple_menu forItem:menu_item];
+ [NSApp setMainMenu:main_menu];
+
+ [NSApp finishLaunching];
+
+ delegate = [[GodotApplicationDelegate alloc] init];
+ ERR_FAIL_COND(!delegate);
+ [NSApp setDelegate:delegate];
+
+ //process application:openFile: event
+ while (true) {
+ NSEvent *event = [NSApp
+ nextEventMatchingMask:NSEventMaskAny
+ untilDate:[NSDate distantPast]
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+
+ if (event == nil) {
+ break;
+ }
+
+ [NSApp sendEvent:event];
+ }
+
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //TODO - do Vulkan and GLES2 support checks, driver selection and fallback
+ rendering_driver = p_rendering_driver;
+
+#ifndef _MSC_VER
+#warning Forcing vulkan rendering driver because OpenGL not implemented yet
+#endif
+ rendering_driver = "vulkan";
+
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextOSX);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = NULL;
+ r_error = ERR_CANT_CREATE;
+ ERR_FAIL_MSG("Could not initialize Vulkan");
+ }
+ }
+#endif
+
+ Point2i window_position(
+ screen_get_position(0).x + (screen_get_size(0).width - p_resolution.width) / 2,
+ screen_get_position(0).y + (screen_get_size(0).height - p_resolution.height) / 2);
+ WindowID main_window = _create_window(p_mode, Rect2i(window_position, p_resolution));
+ ERR_FAIL_COND(main_window == INVALID_WINDOW_ID);
+ for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+ if (p_flags & (1 << i)) {
+ window_set_flag(WindowFlags(i), true, main_window);
+ }
+ }
+ show_window(MAIN_WINDOW_ID);
+
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+DisplayServerOSX::~DisplayServerOSX() {
+ if (dock_menu) {
+ [dock_menu release];
+ }
+
+ for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
+ [E->get() release];
+ }
+
+ //destroy all windows
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) {
+ Map<WindowID, WindowData>::Element *F = E;
+ E = E->next();
+ [F->get().window_object setContentView:nil];
+ [F->get().window_object close];
+ }
+
+ //destroy drivers
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver == "opengl_es") {
+ //TODO - reimplement OpenGLES
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ }
+
+ if (context_vulkan) {
+ memdelete(context_vulkan);
+ }
+ }
+#endif
+
+ CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL);
+ CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL);
+
+ cursors_cache.clear();
+}
+
+void DisplayServerOSX::register_osx_driver() {
+ register_create_function("osx", create_func, get_rendering_drivers_func);
+}
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index cf38664022..9f2160dd9e 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -46,7 +46,6 @@
#include <sys/stat.h>
class EditorExportPlatformOSX : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformOSX, EditorExportPlatform);
int version_code;
@@ -56,8 +55,10 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary);
void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data);
+ Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
+ void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
#ifdef OSX_ENABLED
bool use_codesign() const { return true; }
@@ -66,17 +67,39 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
bool use_codesign() const { return false; }
bool use_dmg() const { return false; }
#endif
+ bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
+ String pname = p_package;
+
+ if (pname.length() == 0) {
+ if (r_error) {
+ *r_error = TTR("Identifier is missing.");
+ }
+ return false;
+ }
+
+ for (int i = 0; i < pname.length(); i++) {
+ char32_t c = pname[i];
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (r_error) {
+ *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
protected:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
+ virtual void get_export_options(List<ExportOption> *r_options) override;
public:
- virtual String get_name() const { return "Mac OSX"; }
- virtual String get_os_name() const { return "OSX"; }
- virtual Ref<Texture> get_logo() const { return logo; }
+ virtual String get_name() const override { return "macOS"; }
+ virtual String get_os_name() const override { return "macOS"; }
+ virtual Ref<Texture2D> get_logo() const override { return logo; }
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
if (use_dmg()) {
list.push_back("dmg");
@@ -84,18 +107,17 @@ public:
list.push_back("zip");
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
-
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual void get_platform_features(List<String> *r_features) {
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("pc");
r_features->push_back("s3tc");
r_features->push_back("OSX");
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
EditorExportPlatformOSX();
@@ -117,14 +139,13 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset>
}
void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) {
-
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
@@ -139,7 +160,12 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
#endif
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
@@ -147,8 +173,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false));
}
-void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_source, Vector<uint8_t> &p_dest) {
-
+void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, Vector<uint8_t> &p_dest) {
int src_len = p_size * p_size;
Vector<uint8_t> result;
@@ -160,11 +185,10 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour
int i = 0;
while (i < src_len) {
- uint8_t cur = p_source.read()[i * 4 + p_ch];
+ uint8_t cur = p_source.ptr()[i * 4 + p_ch];
if (i < src_len - 2) {
-
- if ((p_source.read()[(i + 1) * 4 + p_ch] == cur) && (p_source.read()[(i + 2) * 4 + p_ch] == cur)) {
+ if ((p_source.ptr()[(i + 1) * 4 + p_ch] == cur) && (p_source.ptr()[(i + 2) * 4 + p_ch] == cur)) {
if (buf_size > 0) {
result.write[res_size++] = (uint8_t)(buf_size - 1);
copymem(&result.write[res_size], &buf, buf_size);
@@ -176,7 +200,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour
bool hit_lim = true;
for (int j = 3; j <= lim; j++) {
- if (p_source.read()[(i + j) * 4 + p_ch] != cur) {
+ if (p_source.ptr()[(i + j) * 4 + p_ch] != cur) {
hit_lim = false;
i = i + j - 1;
result.write[res_size++] = (uint8_t)(j - 3 + 0x80);
@@ -215,7 +239,6 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector<uint8_t> &p_sour
}
void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data) {
-
Ref<ImageTexture> it = memnew(ImageTexture);
Vector<uint8_t> data;
@@ -278,7 +301,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
DirAccess::remove_file_or_error(path);
} else {
- PoolVector<uint8_t> src_data = copy->get_data();
+ Vector<uint8_t> src_data = copy->get_data();
//encode 24bit RGB RLE icon
{
@@ -302,7 +325,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
data.resize(data.size() + len + 8);
for (int j = 0; j < len; j++) {
- data.write[ofs + 8 + j] = src_data.read()[j * 4 + 3];
+ data.write[ofs + 8 + j] = src_data.ptr()[j * 4 + 3];
}
len += 8;
len = BSWAP32(len);
@@ -320,7 +343,6 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
}
void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary) {
-
String str;
String strnew;
str.parse_utf8((const char *)plist.ptr(), plist.size());
@@ -332,8 +354,8 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
strnew += lines[i].replace("$name", p_binary) + "\n";
} else if (lines[i].find("$info") != -1) {
strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
- } else if (lines[i].find("$identifier") != -1) {
- strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n";
+ } else if (lines[i].find("$bundle_identifier") != -1) {
+ strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
} else if (lines[i].find("$short_version") != -1) {
strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
@@ -369,7 +391,54 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
- and then wrap it up in a DMG
**/
+Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+#ifdef OSX_ENABLED
+ List<String> args;
+
+ args.push_back("altool");
+ args.push_back("--notarize-app");
+
+ args.push_back("--primary-bundle-id");
+ args.push_back(p_preset->get("application/bundle_identifier"));
+
+ args.push_back("--username");
+ args.push_back(p_preset->get("notarization/apple_id_name"));
+
+ args.push_back("--password");
+ args.push_back(p_preset->get("notarization/apple_id_password"));
+
+ args.push_back("--type");
+ args.push_back("osx");
+
+ if (p_preset->get("notarization/apple_team_id")) {
+ args.push_back("--asc-provider");
+ args.push_back(p_preset->get("notarization/apple_team_id"));
+ }
+
+ args.push_back("--file");
+ args.push_back(p_path);
+
+ String str;
+ Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true);
+ ERR_FAIL_COND_V(err != OK, err);
+
+ print_line("altool (" + p_path + "):\n" + str);
+ if (str.find("RequestUUID") == -1) {
+ EditorNode::add_io_error("altool: " + str);
+ return FAILED;
+ } else {
+ print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.");
+ print_line(" You can check progress manually by opening a Terminal and running the following command:");
+ print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+ }
+
+#endif
+
+ return OK;
+}
+
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+#ifdef OSX_ENABLED
List<String> args;
if (p_preset->get("codesign/timestamp")) {
@@ -380,13 +449,12 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
args.push_back("runtime");
}
- if (p_preset->get("codesign/entitlements") != "") {
- /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */
+ if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) {
args.push_back("--entitlements");
args.push_back(p_preset->get("codesign/entitlements"));
}
- PoolStringArray user_args = p_preset->get("codesign/custom_options");
+ PackedStringArray user_args = p_preset->get("codesign/custom_options");
for (int i = 0; i < user_args.size(); i++) {
String user_arg = user_args[i].strip_edges();
if (!user_arg.empty()) {
@@ -402,10 +470,10 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
args.push_back(p_path);
String str;
- Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true);
+ Error err = OS::get_singleton()->execute("codesign", args, true, nullptr, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
- print_line("codesign (" + p_path + "): " + str);
+ print_line("codesign (" + p_path + "):\n" + str);
if (str.find("no identity found") != -1) {
EditorNode::add_io_error("codesign: no identity found");
return FAILED;
@@ -414,6 +482,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
EditorNode::add_io_error("codesign: invalid entitlements file");
return FAILED;
}
+#endif
return OK;
}
@@ -435,7 +504,7 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
args.push_back(p_app_path_name);
String str;
- Error err = OS::get_singleton()->execute("hdiutil", args, true, NULL, &str, NULL, true);
+ Error err = OS::get_singleton()->execute("hdiutil", args, true, nullptr, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
print_line("hdiutil returned: " + str);
@@ -458,10 +527,11 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
EditorProgress ep("export", "Exporting for OSX", 3, true);
- if (p_debug)
+ if (p_debug) {
src_pkg_name = p_preset->get("custom_template/debug");
- else
+ } else {
src_pkg_name = p_preset->get("custom_template/release");
+ }
if (src_pkg_name == "") {
String err;
@@ -476,7 +546,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
return ERR_FILE_BAD_PATH;
}
- FileAccess *src_f = NULL;
+ FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
if (ep.step("Creating app", 0)) {
@@ -485,7 +555,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
if (!src_pkg_zip) {
-
EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg_name);
return ERR_FILE_NOT_FOUND;
}
@@ -495,80 +564,69 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64";
String pkg_name;
- if (p_preset->get("application/name") != "")
+ if (p_preset->get("application/name") != "") {
pkg_name = p_preset->get("application/name"); // app_name
- else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "")
+ } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
- else
+ } else {
pkg_name = "Unnamed";
+ }
String pkg_name_safe = OS::get_singleton()->get_safe_dir_name(pkg_name);
Error err = OK;
String tmp_app_path_name = "";
- zlib_filefunc_def io2 = io;
- FileAccess *dst_f = NULL;
- io2.opaque = &dst_f;
- zipFile dst_pkg_zip = NULL;
- DirAccess *tmp_app_path = NULL;
+ DirAccess *tmp_app_path = nullptr;
String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip";
- if (export_format == "dmg") {
- // We're on OSX so we can export to DMG, but first we create our application bundle
- tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app");
- print_line("Exporting to " + tmp_app_path_name);
- tmp_app_path = DirAccess::create_for_path(tmp_app_path_name);
- if (!tmp_app_path) {
- err = ERR_CANT_CREATE;
- }
- // Create our folder structure or rely on unzip?
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
- }
+ // Create our application bundle.
+ tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app");
+ print_line("Exporting to " + tmp_app_path_name);
+ tmp_app_path = DirAccess::create_for_path(tmp_app_path_name);
+ if (!tmp_app_path) {
+ err = ERR_CANT_CREATE;
+ }
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
- }
+ // Create our folder structure.
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
+ }
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
- }
- } else {
- // Open our destination zip file
- dst_pkg_zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
- if (!dst_pkg_zip) {
- err = ERR_CANT_CREATE;
- }
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
}
- // Now process our template
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
+ }
+
+ // Now process our template.
bool found_binary = false;
int total_size = 0;
while (ret == UNZ_OK && err == OK) {
bool is_execute = false;
- //get filename
+ // Get filename.
unz_file_info info;
char fname[16384];
- ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, NULL, 0, NULL, 0);
+ ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
String file = fname;
Vector<uint8_t> data;
data.resize(info.uncompressed_size);
- //read
+ // Read.
unzOpenCurrentFile(src_pkg_zip);
unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size());
unzCloseCurrentFile(src_pkg_zip);
- //write
-
+ // Write.
file = file.replace_first("osx_template.app/", "");
if (file == "Contents/Info.plist") {
@@ -578,7 +636,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (file.begins_with("Contents/MacOS/godot_")) {
if (file != "Contents/MacOS/" + binary_to_use) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //ignore!
+ continue; // skip
}
found_binary = true;
is_execute = true;
@@ -586,12 +644,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (file == "Contents/Resources/icon.icns") {
- //see if there is an icon
+ // See if there is an icon.
String iconpath;
- if (p_preset->get("application/icon") != "")
+ if (p_preset->get("application/icon") != "") {
iconpath = p_preset->get("application/icon");
- else
+ } else {
iconpath = ProjectSettings::get_singleton()->get("application/config/icon");
+ }
if (iconpath != "") {
if (iconpath.get_extension() == "icns") {
@@ -614,18 +673,17 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (data.size() > 0) {
-
if (file.find("/data.mono.osx.64.release_debug/") != -1) {
if (!p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //skip
+ continue; // skip
}
file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/");
}
if (file.find("/data.mono.osx.64.release/") != -1) {
if (p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //skip
+ continue; // skip
}
file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/");
}
@@ -633,66 +691,35 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
print_line("ADDING: " + file + " size: " + itos(data.size()));
total_size += data.size();
- if (export_format == "dmg") {
- // write it into our application bundle
- file = tmp_app_path_name.plus_file(file);
- if (err == OK) {
- err = tmp_app_path->make_dir_recursive(file.get_base_dir());
- }
- if (err == OK) {
- // write the file, need to add chmod
- FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
- if (f) {
- f->store_buffer(data.ptr(), data.size());
- f->close();
- if (is_execute) {
- // Chmod with 0755 if the file is executable
- FileAccess::set_unix_permissions(file, 0755);
- }
- memdelete(f);
- } else {
- err = ERR_CANT_CREATE;
+ // Write it into our application bundle.
+ file = tmp_app_path_name.plus_file(file);
+ if (err == OK) {
+ err = tmp_app_path->make_dir_recursive(file.get_base_dir());
+ }
+ if (err == OK) {
+ FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
+ if (f) {
+ f->store_buffer(data.ptr(), data.size());
+ f->close();
+ if (is_execute) {
+ // chmod with 0755 if the file is executable.
+ FileAccess::set_unix_permissions(file, 0755);
}
+ memdelete(f);
+ } else {
+ err = ERR_CANT_CREATE;
}
- } else {
- // add it to our zip file
- file = pkg_name + ".app/" + file;
-
- zip_fileinfo fi;
- fi.tmz_date.tm_hour = info.tmu_date.tm_hour;
- fi.tmz_date.tm_min = info.tmu_date.tm_min;
- fi.tmz_date.tm_sec = info.tmu_date.tm_sec;
- fi.tmz_date.tm_mon = info.tmu_date.tm_mon;
- fi.tmz_date.tm_mday = info.tmu_date.tm_mday;
- fi.tmz_date.tm_year = info.tmu_date.tm_year;
- fi.dosDate = info.dosDate;
- fi.internal_fa = info.internal_fa;
- fi.external_fa = info.external_fa;
-
- zipOpenNewFileInZip(dst_pkg_zip,
- file.utf8().get_data(),
- &fi,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(dst_pkg_zip, data.ptr(), data.size());
- zipCloseFileInZip(dst_pkg_zip);
}
}
ret = unzGoToNextFile(src_pkg_zip);
}
- // we're done with our source zip
+ // We're done with our source zip.
unzClose(src_pkg_zip);
if (!found_binary) {
- ERR_PRINTS("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
+ ERR_PRINT("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
err = ERR_FILE_NOT_FOUND;
}
@@ -701,124 +728,143 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
return ERR_SKIP;
}
- if (export_format == "dmg") {
- String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
- Vector<SharedObject> shared_objects;
- err = save_pack(p_preset, pack_path, &shared_objects);
+ String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
+ Vector<SharedObject> shared_objects;
+ err = save_pack(p_preset, pack_path, &shared_objects);
- // see if we can code sign our new package
- bool sign_enabled = p_preset->get("codesign/enable");
+ // See if we can code sign our new package.
+ bool sign_enabled = p_preset->get("codesign/enable");
- if (err == OK) {
- DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- for (int i = 0; i < shared_objects.size(); i++) {
- err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
- if (err == OK && sign_enabled) {
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
- }
+ if (err == OK) {
+ DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ for (int i = 0; i < shared_objects.size(); i++) {
+ err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
+ if (err == OK && sign_enabled) {
+ err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
}
- memdelete(da);
}
+ memdelete(da);
+ }
- if (err == OK && sign_enabled) {
- if (ep.step("Code signing bundle", 2)) {
- return ERR_SKIP;
- }
-
- // the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP
-
- // start with our application
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name);
-
- ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign
+ if (err == OK && sign_enabled) {
+ if (ep.step("Code signing bundle", 2)) {
+ return ERR_SKIP;
}
+ err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name);
+ }
- // and finally create a DMG
+ if (export_format == "dmg") {
+ // Create a DMG.
if (err == OK) {
if (ep.step("Making DMG", 3)) {
return ERR_SKIP;
}
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
}
-
- // Clean up temporary .app dir
- OS::get_singleton()->move_to_trash(tmp_app_path_name);
-
- } else { // pck
-
- String pack_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".pck");
-
- Vector<SharedObject> shared_objects;
- err = save_pack(p_preset, pack_path, &shared_objects);
-
- if (err == OK) {
- zipOpenNewFileInZip(dst_pkg_zip,
- (pkg_name + ".app/Contents/Resources/" + pkg_name + ".pck").utf8().get_data(),
- NULL,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- FileAccess *pf = FileAccess::open(pack_path, FileAccess::READ);
- if (pf) {
- const int BSIZE = 16384;
- uint8_t buf[BSIZE];
-
- while (true) {
-
- int r = pf->get_buffer(buf, BSIZE);
- if (r <= 0)
- break;
- zipWriteInFileInZip(dst_pkg_zip, buf, r);
- }
-
- zipCloseFileInZip(dst_pkg_zip);
- memdelete(pf);
- } else {
- err = ERR_CANT_OPEN;
+ // Sign DMG.
+ if (err == OK && sign_enabled) {
+ if (ep.step("Code signing DMG", 3)) {
+ return ERR_SKIP;
}
+ err = _code_sign(p_preset, p_path);
}
-
+ } else {
+ // Create ZIP.
if (err == OK) {
- //add shared objects
- for (int i = 0; i < shared_objects.size(); i++) {
- Vector<uint8_t> file = FileAccess::get_file_as_array(shared_objects[i].path);
- ERR_CONTINUE(file.empty());
-
- zipOpenNewFileInZip(dst_pkg_zip,
- (pkg_name + ".app/Contents/Frameworks/").plus_file(shared_objects[i].path.get_file()).utf8().get_data(),
- NULL,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(dst_pkg_zip, file.ptr(), file.size());
- zipCloseFileInZip(dst_pkg_zip);
+ if (ep.step("Making ZIP", 3)) {
+ return ERR_SKIP;
+ }
+ if (FileAccess::exists(p_path)) {
+ OS::get_singleton()->move_to_trash(p_path);
}
+
+ FileAccess *dst_f = nullptr;
+ zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f);
+ zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
+
+ _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
+
+ zipClose(zip, nullptr);
}
+ }
- // Clean up generated file.
- DirAccess::remove_file_or_error(pack_path);
+ bool noto_enabled = p_preset->get("notarization/enable");
+ if (err == OK && noto_enabled) {
+ if (ep.step("Sending archive for notarization", 4)) {
+ return ERR_SKIP;
+ }
+ err = _notarize(p_preset, p_path);
}
- }
- if (dst_pkg_zip) {
- zipClose(dst_pkg_zip, NULL);
+ // Clean up temporary .app dir.
+ OS::get_singleton()->move_to_trash(tmp_app_path_name);
}
return err;
}
-bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
+ String dir = p_root_path.plus_file(p_folder);
+
+ DirAccess *da = DirAccess::open(dir);
+ da->list_dir_begin();
+ String f;
+ while ((f = da->get_next()) != "") {
+ if (f == "." || f == "..") {
+ continue;
+ }
+ if (da->current_is_dir()) {
+ _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name);
+ } else {
+ bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name));
+
+ OS::Time time = OS::get_singleton()->get_time();
+ OS::Date date = OS::get_singleton()->get_date();
+
+ zip_fileinfo zipfi;
+ zipfi.tmz_date.tm_hour = time.hour;
+ zipfi.tmz_date.tm_mday = date.day;
+ zipfi.tmz_date.tm_min = time.min;
+ zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/
+ zipfi.tmz_date.tm_sec = time.sec;
+ zipfi.tmz_date.tm_year = date.year;
+ zipfi.dosDate = 0;
+ // 0100000: regular file type
+ // 0000755: permissions rwxr-xr-x
+ // 0000644: permissions rw-r--r--
+ uint32_t _mode = (is_executable ? 0100755 : 0100644);
+ zipfi.external_fa = (_mode << 16L) | !(_mode & 0200);
+ zipfi.internal_fa = 0;
+
+ zipOpenNewFileInZip4(p_zip,
+ p_folder.plus_file(f).utf8().get_data(),
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION,
+ 0,
+ -MAX_WBITS,
+ DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY,
+ nullptr,
+ 0,
+ 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions
+ 0);
+
+ Vector<uint8_t> array = FileAccess::get_file_as_array(dir.plus_file(f));
+ zipWriteInFileInZip(p_zip, array.ptr(), array.size());
+ zipCloseFileInZip(p_zip);
+ }
+ }
+ da->list_dir_end();
+ memdelete(da);
+}
+bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
@@ -843,13 +889,48 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
- if (!err.empty())
+ String identifier = p_preset->get("application/bundle_identifier");
+ String pn_err;
+ if (!is_package_name_valid(identifier, &pn_err)) {
+ err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n";
+ valid = false;
+ }
+
+ bool sign_enabled = p_preset->get("codesign/enable");
+ if (sign_enabled) {
+ if (p_preset->get("codesign/identity") == "") {
+ err += TTR("Codesign: identity not specified.") + "\n";
+ valid = false;
+ }
+ }
+ bool noto_enabled = p_preset->get("notarization/enable");
+ if (noto_enabled) {
+ if (!sign_enabled) {
+ err += TTR("Notarization: code signing required.") + "\n";
+ valid = false;
+ }
+ bool hr_enabled = p_preset->get("codesign/hardened_runtime");
+ if (!hr_enabled) {
+ err += TTR("Notarization: hardened runtime required.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/apple_id_name") == "") {
+ err += TTR("Notarization: Apple ID name not specified.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/apple_id_password") == "") {
+ err += TTR("Notarization: Apple ID password not specified.") + "\n";
+ valid = false;
+ }
+ }
+
+ if (!err.empty()) {
r_error = err;
+ }
return valid;
}
EditorExportPlatformOSX::EditorExportPlatformOSX() {
-
Ref<Image> img = memnew(Image(_osx_logo));
logo.instance();
logo->create_from_image(img);
@@ -859,7 +940,6 @@ EditorExportPlatformOSX::~EditorExportPlatformOSX() {
}
void register_osx_exporter() {
-
Ref<EditorExportPlatformOSX> platform;
platform.instance();
diff --git a/platform/osx/export/export.h b/platform/osx/export/export.h
index 7b8832cb01..4ddcec09fb 100644
--- a/platform/osx/export/export.h
+++ b/platform/osx/export/export.h
@@ -28,4 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef OSX_EXPORT_H
+#define OSX_EXPORT_H
+
void register_osx_exporter();
+
+#endif // OSX_EXPORT_H
diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm
index e6f8cbecf1..4e73d5441c 100644
--- a/platform/osx/godot_main_osx.mm
+++ b/platform/osx/godot_main_osx.mm
@@ -36,6 +36,11 @@
#include <unistd.h>
int main(int argc, char **argv) {
+#if defined(VULKAN_ENABLED)
+ // MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
int first_arg = 1;
const char *dbg_arg = "-NSDocumentRevisionsDebugMode";
printf("arguments\n");
@@ -55,6 +60,9 @@ int main(int argc, char **argv) {
OS_OSX os;
Error err;
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
if (os.open_with_filename != "") {
char *argv_c = (char *)malloc(os.open_with_filename.utf8().size());
memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size());
diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp
index 13ece678f3..cfc371710b 100644
--- a/platform/osx/joypad_osx.cpp
+++ b/platform/osx/joypad_osx.cpp
@@ -34,13 +34,13 @@
#define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad")
-static JoypadOSX *self = NULL;
+static JoypadOSX *self = nullptr;
joypad::joypad() {
- device_ref = NULL;
- ff_device = NULL;
- ff_axes = NULL;
- ff_directions = NULL;
+ device_ref = nullptr;
+ ff_device = nullptr;
+ ff_axes = nullptr;
+ ff_directions = nullptr;
ffservice = 0;
ff_timestamp = 0;
id = 0;
@@ -53,7 +53,7 @@ joypad::joypad() {
ff_effect.dwTriggerButton = FFEB_NOTRIGGER;
ff_effect.dwStartDelay = 0;
ff_effect.dwTriggerRepeatInterval = 0;
- ff_effect.lpEnvelope = NULL;
+ ff_effect.lpEnvelope = nullptr;
ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);
ff_effect.lpvTypeSpecificParams = &ff_constant_force;
ff_effect.dwSize = sizeof(ff_effect);
@@ -98,6 +98,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const {
}
return value;
}
+
void joypad::add_hid_element(IOHIDElementRef p_element) {
const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0;
@@ -105,7 +106,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) {
const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element);
const uint32_t usagePage = IOHIDElementGetUsagePage(p_element);
const uint32_t usage = IOHIDElementGetUsage(p_element);
- Vector<rec_element> *list = NULL;
+ Vector<rec_element> *list = nullptr;
switch (IOHIDElementGetType(p_element)) {
case kIOHIDElementTypeInput_Misc:
@@ -208,9 +209,8 @@ void joypad::add_hid_elements(CFArrayRef p_array) {
CFArrayApplyFunction(p_array, range, hid_element_added, this);
}
-static void joypad_removed_callback(void *ctx, IOReturn result, void *sender) {
- int id = (intptr_t)ctx;
- self->_device_removed(id);
+static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
+ self->_device_removed(res, ioHIDDeviceObject);
}
static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
@@ -241,7 +241,6 @@ static bool is_joypad(IOHIDDeviceRef p_device_ref) {
}
void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) {
-
if (p_res != kIOReturnSuccess || have_device(p_device)) {
return;
}
@@ -250,7 +249,7 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) {
if (is_joypad(p_device)) {
configure_joypad(p_device, &new_joypad);
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
- if (IOHIDDeviceGetService != NULL) {
+ if (IOHIDDeviceGetService != nullptr) {
#endif
const io_service_t ioservice = IOHIDDeviceGetService(p_device);
if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) {
@@ -261,22 +260,19 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) {
#endif
device_list.push_back(new_joypad);
}
- IOHIDDeviceRegisterRemovalCallback(p_device, joypad_removed_callback, (void *)(intptr_t)new_joypad.id);
IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
}
-void JoypadOSX::_device_removed(int p_id) {
-
- int device = get_joy_index(p_id);
+void JoypadOSX::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) {
+ int device = get_joy_ref(p_device);
ERR_FAIL_COND(device == -1);
- input->joy_connection_changed(p_id, false, "");
+ input->joy_connection_changed(device_list[device].id, false, "");
device_list.write[device].free();
device_list.remove(device);
}
static String _hex_str(uint8_t p_byte) {
-
static const char *dict = "0123456789abcdef";
char ret[3];
ret[2] = 0;
@@ -288,7 +284,6 @@ static String _hex_str(uint8_t p_byte) {
}
bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
-
p_joy->device_ref = p_device_ref;
/* get device name */
String name;
@@ -316,9 +311,16 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
if (refCF) {
CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id);
}
+
+ int version = 0;
+ refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey));
+ if (refCF) {
+ CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version);
+ }
+
if (vendor && product_id) {
char uid[128];
- sprintf(uid, "%04x%08x%04x%08x", OSSwapHostToBigInt32(vendor), 0, OSSwapHostToBigInt32(product_id), 0);
+ sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version));
input->joy_connection_changed(id, true, name, uid);
} else {
//bluetooth device
@@ -332,7 +334,7 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
input->joy_connection_changed(id, true, name, guid);
}
- CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, NULL, kIOHIDOptionsTypeNone);
+ CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone);
if (array) {
p_joy->add_hid_elements(array);
CFRelease(array);
@@ -348,7 +350,6 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
} \
}
bool joypad::config_force_feedback(io_service_t p_service) {
-
HRESULT ret = FFCreateDevice(p_service, &ff_device);
ERR_FAIL_COND_V(ret != FF_OK, false);
@@ -370,13 +371,13 @@ bool joypad::config_force_feedback(io_service_t p_service) {
#define TEST_FF(ff) (features.supportedEffects & (ff))
bool joypad::check_ff_features() {
-
FFCAPABILITIES features;
HRESULT ret = FFDeviceGetForceFeedbackCapabilities(ff_device, &features);
if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) {
uint32_t val;
ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val));
- if (ret != FF_OK) return false;
+ if (ret != FF_OK)
+ return false;
int num_axes = features.numFfAxes;
ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes);
ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes);
@@ -397,38 +398,38 @@ bool joypad::check_ff_features() {
static int process_hat_value(int p_min, int p_max, int p_value) {
int range = (p_max - p_min + 1);
int value = p_value - p_min;
- int hat_value = InputDefault::HAT_MASK_CENTER;
+ int hat_value = Input::HAT_MASK_CENTER;
if (range == 4) {
value *= 2;
}
switch (value) {
case 0:
- hat_value = InputDefault::HAT_MASK_UP;
+ hat_value = Input::HAT_MASK_UP;
break;
case 1:
- hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT;
+ hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_RIGHT;
break;
case 2:
- hat_value = InputDefault::HAT_MASK_RIGHT;
+ hat_value = Input::HAT_MASK_RIGHT;
break;
case 3:
- hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_RIGHT;
+ hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_RIGHT;
break;
case 4:
- hat_value = InputDefault::HAT_MASK_DOWN;
+ hat_value = Input::HAT_MASK_DOWN;
break;
case 5:
- hat_value = InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT;
+ hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_LEFT;
break;
case 6:
- hat_value = InputDefault::HAT_MASK_LEFT;
+ hat_value = Input::HAT_MASK_LEFT;
break;
case 7:
- hat_value = InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_LEFT;
+ hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_LEFT;
break;
default:
- hat_value = InputDefault::HAT_MASK_CENTER;
+ hat_value = Input::HAT_MASK_CENTER;
break;
}
return hat_value;
@@ -440,8 +441,8 @@ void JoypadOSX::poll_joypads() const {
}
}
-static const InputDefault::JoyAxis axis_correct(int p_value, int p_min, int p_max) {
- InputDefault::JoyAxis jx;
+static const Input::JoyAxis axis_correct(int p_value, int p_min, int p_max) {
+ Input::JoyAxis jx;
if (p_min < 0) {
jx.min = -1;
if (p_value < 0) {
@@ -511,7 +512,16 @@ void JoypadOSX::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
int JoypadOSX::get_joy_index(int p_id) const {
for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].id == p_id) return i;
+ if (device_list[i].id == p_id)
+ return i;
+ }
+ return -1;
+}
+
+int JoypadOSX::get_joy_ref(IOHIDDeviceRef p_device) const {
+ for (int i = 0; i < device_list.size(); i++) {
+ if (device_list[i].device_ref == p_device)
+ return i;
}
return -1;
}
@@ -526,7 +536,7 @@ bool JoypadOSX::have_device(IOHIDDeviceRef p_device) const {
}
static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) {
- CFDictionaryRef retval = NULL;
+ CFDictionaryRef retval = nullptr;
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
@@ -551,13 +561,13 @@ static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 u
}
void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
-
CFRunLoopRef runloop = CFRunLoopGetCurrent();
IOReturn ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
ERR_FAIL_COND(ret != kIOReturnSuccess);
IOHIDManagerSetDeviceMatchingMultiple(hid_manager, p_matching_array);
- IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, NULL);
+ IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, nullptr);
+ IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, joypad_removed_callback, nullptr);
IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE);
while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
@@ -565,9 +575,9 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
}
}
-JoypadOSX::JoypadOSX() {
+JoypadOSX::JoypadOSX(Input *in) {
self = this;
- input = (InputDefault *)Input::get_singleton();
+ input = in;
int okay = 1;
const void *vals[] = {
@@ -576,7 +586,7 @@ JoypadOSX::JoypadOSX() {
(void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
};
const size_t n_elements = sizeof(vals) / sizeof(vals[0]);
- CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : NULL;
+ CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : nullptr;
for (size_t i = 0; i < n_elements; i++) {
if (vals[i]) {
@@ -586,7 +596,7 @@ JoypadOSX::JoypadOSX() {
if (array) {
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
- if (hid_manager != NULL) {
+ if (hid_manager != nullptr) {
config_hid_manager(array);
}
CFRelease(array);
@@ -594,7 +604,6 @@ JoypadOSX::JoypadOSX() {
}
JoypadOSX::~JoypadOSX() {
-
for (int i = 0; i < device_list.size(); i++) {
device_list.write[i].free();
}
@@ -602,5 +611,5 @@ JoypadOSX::~JoypadOSX() {
IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone);
CFRelease(hid_manager);
- hid_manager = NULL;
+ hid_manager = nullptr;
}
diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h
index 388251016b..dc238e68e4 100644
--- a/platform/osx/joypad_osx.h
+++ b/platform/osx/joypad_osx.h
@@ -40,7 +40,7 @@
#include <ForceFeedback/ForceFeedbackConstants.h>
#include <IOKit/hid/IOHIDLib.h>
-#include "main/input_default.h"
+#include "core/input/input.h"
struct rec_element {
IOHIDElementRef ref;
@@ -88,13 +88,12 @@ struct joypad {
};
class JoypadOSX {
-
enum {
JOYPADS_MAX = 16,
};
private:
- InputDefault *input;
+ Input *input;
IOHIDManagerRef hid_manager;
Vector<joypad> device_list;
@@ -103,6 +102,7 @@ private:
bool configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy);
int get_joy_index(int p_id) const;
+ int get_joy_ref(IOHIDDeviceRef p_device) const;
void poll_joypads() const;
void setup_joypad_objects();
@@ -115,9 +115,9 @@ public:
void process_joypads();
void _device_added(IOReturn p_res, IOHIDDeviceRef p_device);
- void _device_removed(int p_id);
+ void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device);
- JoypadOSX();
+ JoypadOSX(Input *in);
~JoypadOSX();
};
diff --git a/platform/osx/logo.png b/platform/osx/logo.png
index 834bbf3ba6..b5a660b165 100644
--- a/platform/osx/logo.png
+++ b/platform/osx/logo.png
Binary files differ
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 190dbcf662..5a9e43450f 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -31,50 +31,20 @@
#ifndef OS_OSX_H
#define OS_OSX_H
-#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
-
-#include "core/os/input.h"
+#include "core/input/input.h"
#include "crash_handler_osx.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/coremidi/midi_driver_coremidi.h"
#include "drivers/unix/os_unix.h"
#include "joypad_osx.h"
-#include "main/input_default.h"
-#include "power_osx.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual/visual_server_wrap_mt.h"
-#include "servers/visual_server.h"
-
-#include <AppKit/AppKit.h>
-#include <AppKit/NSCursor.h>
-#include <ApplicationServices/ApplicationServices.h>
-#include <CoreVideo/CoreVideo.h>
-
-#undef BitMap
-#undef CursorShape
class OS_OSX : public OS_Unix {
-public:
- struct KeyEvent {
- unsigned int osx_state;
- bool pressed;
- bool echo;
- bool raw;
- uint32_t scancode;
- uint32_t unicode;
- };
-
- Vector<KeyEvent> key_event_buffer;
- int key_event_pos;
+ virtual void delete_main_loop();
bool force_quit;
- // rasterizer seems to no longer be given to visual server, its using GLES3 directly?
- //Rasterizer *rasterizer;
- VisualServer *visual_server;
- List<String> args;
- MainLoop *main_loop;
+ JoypadOSX *joypad_osx = nullptr;
#ifdef COREAUDIO_ENABLED
AudioDriverCoreAudio audio_driver;
@@ -83,139 +53,27 @@ public:
MIDIDriverCoreMidi midi_driver;
#endif
- InputDefault *input;
- JoypadOSX *joypad_osx;
-
- /* objc */
-
- CGEventSourceRef eventSource;
-
- void process_events();
- void process_key_events();
-
- void *framework;
- // pthread_key_t current;
- bool mouse_grab;
- Point2 mouse_pos;
-
- id delegate;
- id window_delegate;
- id window_object;
- id window_view;
- id autoreleasePool;
- id cursor;
- NSOpenGLPixelFormat *pixelFormat;
- NSOpenGLContext *context;
-
- bool layered_window;
-
- CursorShape cursor_shape;
- NSCursor *cursors[CURSOR_MAX];
- Map<CursorShape, Vector<Variant> > cursors_cache;
- MouseMode mouse_mode;
-
- String title;
- bool minimized;
- bool maximized;
- bool zoomed;
- bool resizable;
- bool window_focused;
-
- Size2 window_size;
- Rect2 restore_rect;
-
- String open_with_filename;
-
- Point2 im_position;
- bool im_active;
- String im_text;
- Point2 im_selection;
-
- Size2 min_size;
- Size2 max_size;
-
- PowerOSX *power_manager;
-
CrashHandler crash_handler;
- float _mouse_scale(float p_scale) {
- if (_display_scale() > 1.0)
- return p_scale;
- else
- return 1.0;
- }
-
- float _display_scale() const;
- float _display_scale(id screen) const;
-
- void _update_window();
-
- int video_driver_index;
- virtual int get_current_video_driver() const;
-
- struct GlobalMenuItem {
- String label;
- Variant signal;
- Variant meta;
-
- GlobalMenuItem() {
- //NOP
- }
-
- GlobalMenuItem(const String &p_label, const Variant &p_signal, const Variant &p_meta) {
- label = p_label;
- signal = p_signal;
- meta = p_meta;
- }
- };
-
- Map<String, Vector<GlobalMenuItem> > global_menus;
+ MainLoop *main_loop;
- void _update_global_menu();
+public:
+ String open_with_filename;
protected:
virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ virtual void initialize();
virtual void finalize();
+ virtual void initialize_joypads();
+
virtual void set_main_loop(MainLoop *p_main_loop);
- virtual void delete_main_loop();
public:
- static OS_OSX *singleton;
-
- void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
- void global_menu_add_separator(const String &p_menu);
- void global_menu_remove_item(const String &p_menu, int p_idx);
- void global_menu_clear(const String &p_menu);
-
- void wm_minimized(bool p_minimized);
-
virtual String get_name() const;
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual CursorShape get_cursor_shape() const;
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
-
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual void warp_mouse_position(const Point2 &p_to);
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- void update_real_mouse_position();
- virtual void set_window_title(const String &p_title);
-
- virtual Size2 get_window_size() const;
- virtual Size2 get_real_window_size() const;
-
- virtual void set_native_icon(const String &p_filename);
- virtual void set_icon(const Ref<Image> &p_icon);
-
virtual MainLoop *get_main_loop() const;
virtual String get_config_path() const;
@@ -226,99 +84,24 @@ public:
virtual String get_system_dir(SystemDir p_dir) const;
- virtual bool can_draw() const;
-
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
-
- virtual void release_rendering_thread();
- virtual void make_rendering_thread();
- virtual void swap_buffers();
-
Error shell_open(String p_uri);
- void push_input(const Ref<InputEvent> &p_event);
String get_locale() const;
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
virtual String get_executable_path() const;
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
-
- virtual void move_window_to_foreground();
-
- virtual int get_screen_count() const;
- virtual int get_current_screen() const;
- virtual void set_current_screen(int p_screen);
- virtual Point2 get_screen_position(int p_screen = -1) const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
- virtual int get_screen_dpi(int p_screen = -1) const;
-
- virtual Point2 get_window_position() const;
- virtual void set_window_position(const Point2 &p_position);
- virtual Size2 get_max_window_size() const;
- virtual Size2 get_min_window_size() const;
- virtual void set_min_window_size(const Size2 p_size);
- virtual void set_max_window_size(const Size2 p_size);
- virtual void set_window_size(const Size2 p_size);
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual void set_window_resizable(bool p_enabled);
- virtual bool is_window_resizable() const;
- virtual void set_window_minimized(bool p_enabled);
- virtual bool is_window_minimized() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
- virtual void set_window_always_on_top(bool p_enabled);
- virtual bool is_window_always_on_top() const;
- virtual bool is_window_focused() const;
- virtual void request_attention();
- virtual String get_joy_guid(int p_device) const;
-
- virtual void set_borderless_window(bool p_borderless);
- virtual bool get_borderless_window();
-
- virtual bool get_window_per_pixel_transparency_enabled() const;
- virtual void set_window_per_pixel_transparency_enabled(bool p_enabled);
-
- virtual void set_ime_active(const bool p_active);
- virtual void set_ime_position(const Point2 &p_pos);
- virtual Point2 get_ime_selection() const;
- virtual String get_ime_text() const;
-
- virtual String get_unique_id() const;
-
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
+ virtual String get_unique_id() const; //++
virtual bool _check_internal_feature_support(const String &p_feature);
- virtual void _set_use_vsync(bool p_enable);
- //virtual bool is_vsync_enabled() const;
-
void run();
- void set_mouse_mode(MouseMode p_mode);
- MouseMode get_mouse_mode() const;
-
void disable_crash_handler();
bool is_disable_crash_handler() const;
virtual Error move_to_trash(const String &p_path);
- void force_process_input();
-
OS_OSX();
-
-private:
- Point2 get_native_screen_position(int p_screen) const;
- Point2 get_native_window_position() const;
- void set_native_window_position(const Point2 &p_position);
- Point2 get_screens_origin() const;
};
#endif
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index 53fe11b3bb..399a29cbe0 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -30,1355 +30,73 @@
#include "os_osx.h"
-#include "core/os/keyboard.h"
-#include "core/print_string.h"
#include "core/version_generated.gen.h"
+
#include "dir_access_osx.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
+#include "display_server_osx.h"
#include "main/main.h"
-#include "semaphore_osx.h"
-#include "servers/visual/visual_server_raster.h"
-
-#include <mach-o/dyld.h>
-
-#include <Carbon/Carbon.h>
-#import <Cocoa/Cocoa.h>
-#include <IOKit/IOCFPlugIn.h>
-#include <IOKit/IOKitLib.h>
-#include <IOKit/hid/IOHIDKeys.h>
-#include <IOKit/hid/IOHIDLib.h>
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
-#include <os/log.h>
-#endif
#include <dlfcn.h>
-#include <fcntl.h>
#include <libproc.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
-#define NSEventMaskAny NSAnyEventMask
-#define NSEventTypeKeyDown NSKeyDown
-#define NSEventTypeKeyUp NSKeyUp
-#define NSEventModifierFlagShift NSShiftKeyMask
-#define NSEventModifierFlagCommand NSCommandKeyMask
-#define NSEventModifierFlagControl NSControlKeyMask
-#define NSEventModifierFlagOption NSAlternateKeyMask
-#define NSWindowStyleMaskTitled NSTitledWindowMask
-#define NSWindowStyleMaskResizable NSResizableWindowMask
-#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
-#define NSWindowStyleMaskClosable NSClosableWindowMask
-#define NSWindowStyleMaskBorderless NSBorderlessWindowMask
-#endif
-
-#ifndef NSAppKitVersionNumber10_12
-#define NSAppKitVersionNumber10_12 1504
-#endif
-#ifndef NSAppKitVersionNumber10_14
-#define NSAppKitVersionNumber10_14 1671
-#endif
-
-static void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> state) {
-
- state->set_shift((p_osx_state & NSEventModifierFlagShift));
- state->set_control((p_osx_state & NSEventModifierFlagControl));
- state->set_alt((p_osx_state & NSEventModifierFlagOption));
- state->set_metakey((p_osx_state & NSEventModifierFlagCommand));
-}
-
-static void push_to_key_event_buffer(const OS_OSX::KeyEvent &p_event) {
-
- Vector<OS_OSX::KeyEvent> &buffer = OS_OSX::singleton->key_event_buffer;
- if (OS_OSX::singleton->key_event_pos >= buffer.size()) {
- buffer.resize(1 + OS_OSX::singleton->key_event_pos);
- }
- buffer.write[OS_OSX::singleton->key_event_pos++] = p_event;
-}
-
-static int mouse_x = 0;
-static int mouse_y = 0;
-static int button_mask = 0;
-static bool mouse_down_control = false;
-
-static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFactor) {
-
- const NSRect contentRect = [OS_OSX::singleton->window_view frame];
- const NSPoint p = locationInWindow;
- const float s = OS_OSX::singleton->_mouse_scale(backingScaleFactor);
- mouse_x = p.x * s;
- mouse_y = (contentRect.size.height - p.y) * s;
- return Vector2(mouse_x, mouse_y);
-}
-
-static NSCursor *cursorFromSelector(SEL selector, SEL fallback = nil) {
- if ([NSCursor respondsToSelector:selector]) {
- id object = [NSCursor performSelector:selector];
- if ([object isKindOfClass:[NSCursor class]]) {
- return object;
- }
- }
- if (fallback) {
- // Fallback should be a reasonable default, no need to check.
- return [NSCursor performSelector:fallback];
- }
- return [NSCursor arrowCursor];
-}
-
-@interface GodotApplication : NSApplication
-@end
-
-@implementation GodotApplication
-
-- (void)sendEvent:(NSEvent *)event {
-
- // special case handling of command-period, which is traditionally a special
- // shortcut in macOS and doesn't arrive at our regular keyDown handler.
- if ([event type] == NSEventTypeKeyDown) {
- if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) {
-
- Ref<InputEventKey> k;
- k.instance();
-
- get_key_modifier_state([event modifierFlags], k);
- k->set_pressed(true);
- k->set_scancode(KEY_PERIOD);
- k->set_echo([event isARepeat]);
-
- OS_OSX::singleton->push_input(k);
- }
- }
-
- // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
- // This works around an AppKit bug, where key up events while holding
- // down the command key don't get sent to the key window.
- if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand))
- [[self keyWindow] sendEvent:event];
- else
- [super sendEvent:event];
-}
-
-@end
-
-@interface GodotApplicationDelegate : NSObject
-- (void)forceUnbundledWindowActivationHackStep1;
-- (void)forceUnbundledWindowActivationHackStep2;
-- (void)forceUnbundledWindowActivationHackStep3;
-@end
-
-@implementation GodotApplicationDelegate
-
-- (void)forceUnbundledWindowActivationHackStep1 {
- // Step1: Switch focus to macOS Dock.
- // Required to perform step 2, TransformProcessType will fail if app is already the in focus.
- for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
- [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
- break;
- }
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02];
-}
-
-- (void)forceUnbundledWindowActivationHackStep2 {
- // Step 2: Register app as foreground process.
- ProcessSerialNumber psn = { 0, kCurrentProcess };
- (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
-
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
-}
-
-- (void)forceUnbundledWindowActivationHackStep3 {
- // Step 3: Switch focus back to app window.
- [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
-}
-
-- (void)applicationDidFinishLaunching:(NSNotification *)notice {
- NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
- if (nsappname == nil) {
- // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
- }
-}
-
-- (void)globalMenuCallback:(id)sender {
-
- if (![sender representedObject])
- return;
-
- OS_OSX::GlobalMenuItem *item = (OS_OSX::GlobalMenuItem *)[[sender representedObject] pointerValue];
-
- if (!item)
- return;
-
- OS_OSX::singleton->main_loop->global_menu_action(item->signal, item->meta);
-}
-
-- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
-
- NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
-
- Vector<OS_OSX::GlobalMenuItem> &E = OS_OSX::singleton->global_menus["_dock"];
- for (int i = 0; i < E.size(); i++) {
- if (E[i].label == String()) {
- [menu addItem:[NSMenuItem separatorItem]];
- } else {
- NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
- [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E[i])]];
- }
- }
-
- return menu;
-}
-
-- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
- // Note: may be called called before main loop init!
- char *utfs = strdup([filename UTF8String]);
- OS_OSX::singleton->open_with_filename.parse_utf8(utfs);
- free(utfs);
-
-#ifdef TOOLS_ENABLED
- // Open new instance
- if (OS_OSX::singleton->get_main_loop()) {
- List<String> args;
- args.push_back(OS_OSX::singleton->open_with_filename);
- String exec = OS::get_singleton()->get_executable_path();
-
- OS::ProcessID pid = 0;
- OS::get_singleton()->execute(exec, args, false, &pid);
- }
-#endif
- return YES;
-}
-
-- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
-
- return NSTerminateCancel;
-}
-
-- (void)applicationDidHide:(NSNotification *)notification {
- /*
- _Godotwindow* window;
- for (window = _Godot.windowListHead; window; window = window->next)
- _GodotInputWindowVisibility(window, GL_FALSE);
-*/
-}
-
-- (void)applicationDidUnhide:(NSNotification *)notification {
- /*
- _Godotwindow* window;
-
- for (window = _Godot.windowListHead; window; window = window->next) {
- if ([window_object isVisible])
- _GodotInputWindowVisibility(window, GL_TRUE);
- }
-*/
-}
-
-- (void)applicationDidChangeScreenParameters:(NSNotification *)notification {
- //_GodotInputMonitorChange();
-}
-
-- (void)showAbout:(id)sender {
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
-}
-
-@end
-
-@interface GodotWindowDelegate : NSObject {
- //_Godotwindow* window;
-}
-
-@end
-
-@implementation GodotWindowDelegate
-
-- (BOOL)windowShouldClose:(id)sender {
- //_GodotInputWindowCloseRequest(window);
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
-
- return NO;
-}
-
-- (void)windowDidEnterFullScreen:(NSNotification *)notification {
- OS_OSX::singleton->zoomed = true;
-
- [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(0, 0)];
- [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
-}
-
-- (void)windowDidExitFullScreen:(NSNotification *)notification {
- OS_OSX::singleton->zoomed = false;
-
- if (OS_OSX::singleton->min_size != Size2()) {
- Size2 size = OS_OSX::singleton->min_size / OS_OSX::singleton->_display_scale();
- [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(size.x, size.y)];
- }
- if (OS_OSX::singleton->max_size != Size2()) {
- Size2 size = OS_OSX::singleton->max_size / OS_OSX::singleton->_display_scale();
- [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
- }
-
- if (!OS_OSX::singleton->resizable)
- [OS_OSX::singleton->window_object setStyleMask:[OS_OSX::singleton->window_object styleMask] & ~NSWindowStyleMaskResizable];
-}
-
-- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
- if (!OS_OSX::singleton)
- return;
-
- NSWindow *window = (NSWindow *)[notification object];
- CGFloat newBackingScaleFactor = [window backingScaleFactor];
- CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
- if (OS_OSX::singleton->is_hidpi_allowed()) {
- [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES];
- } else {
- [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:NO];
- }
-
- if (newBackingScaleFactor != oldBackingScaleFactor) {
- //Set new display scale and window size
- float newDisplayScale = OS_OSX::singleton->is_hidpi_allowed() ? newBackingScaleFactor : 1.0;
-
- const NSRect contentRect = [OS_OSX::singleton->window_view frame];
- const NSRect fbRect = contentRect;
-
- OS_OSX::singleton->window_size.width = fbRect.size.width * newDisplayScale;
- OS_OSX::singleton->window_size.height = fbRect.size.height * newDisplayScale;
-
- //Update context
- if (OS_OSX::singleton->main_loop) {
- //Force window resize event
- [self windowDidResize:notification];
- }
- }
-}
-
-- (void)windowDidResize:(NSNotification *)notification {
- [OS_OSX::singleton->context update];
-
- const NSRect contentRect = [OS_OSX::singleton->window_view frame];
- const NSRect fbRect = contentRect;
-
- float displayScale = OS_OSX::singleton->_display_scale();
- OS_OSX::singleton->window_size.width = fbRect.size.width * displayScale;
- OS_OSX::singleton->window_size.height = fbRect.size.height * displayScale;
-
- if (OS_OSX::singleton->main_loop) {
- Main::force_redraw();
- //Event retrieval blocks until resize is over. Call Main::iteration() directly.
- if (!Main::is_iterating()) { //avoid cyclic loop
- Main::iteration();
- }
- }
-
- /*
- _GodotInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
- _GodotInputWindowSize(window, contentRect.size.width, contentRect.size.height);
- _GodotInputWindowDamage(window);
-
- if (window->cursorMode == Godot_CURSOR_DISABLED)
- centerCursor(window);
-*/
-}
-
-- (void)windowDidMove:(NSNotification *)notification {
-
- if (OS_OSX::singleton->get_main_loop()) {
- OS_OSX::singleton->input->release_pressed_events();
- }
-
- /*
- [window->nsgl.context update];
-
- int x, y;
- _GodotPlatformGetWindowPos(window, &x, &y);
- _GodotInputWindowPos(window, x, y);
-
- if (window->cursorMode == Godot_CURSOR_DISABLED)
- centerCursor(window);
-*/
-}
-
-- (void)windowDidBecomeKey:(NSNotification *)notification {
- if (OS_OSX::singleton->get_main_loop()) {
- get_mouse_pos(
- [OS_OSX::singleton->window_object mouseLocationOutsideOfEventStream],
- [OS_OSX::singleton->window_view backingScaleFactor]);
- OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y));
-
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
- }
-
- OS_OSX::singleton->window_focused = true;
-}
-
-- (void)windowDidResignKey:(NSNotification *)notification {
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- OS_OSX::singleton->window_focused = false;
-}
-
-- (void)windowDidMiniaturize:(NSNotification *)notification {
- OS_OSX::singleton->wm_minimized(true);
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- OS_OSX::singleton->window_focused = false;
-};
-
-- (void)windowDidDeminiaturize:(NSNotification *)notification {
- OS_OSX::singleton->wm_minimized(false);
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
- OS_OSX::singleton->window_focused = true;
-};
-
-@end
-
-@interface GodotContentView : NSView <NSTextInputClient> {
- NSTrackingArea *trackingArea;
- NSMutableAttributedString *markedText;
- bool imeInputEventInProgress;
-}
-- (void)cancelComposition;
-- (BOOL)wantsUpdateLayer;
-- (void)updateLayer;
-@end
-
-@implementation GodotContentView
-
-+ (void)initialize {
- if (self == [GodotContentView class]) {
- // nothing left to do here at the moment..
- }
-}
-
-- (BOOL)wantsUpdateLayer {
- return YES;
-}
-
-- (void)updateLayer {
- [OS_OSX::singleton->context update];
-}
-
-- (id)init {
- self = [super init];
- trackingArea = nil;
- imeInputEventInProgress = false;
- [self updateTrackingAreas];
- [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
- markedText = [[NSMutableAttributedString alloc] init];
- return self;
-}
-
-- (void)dealloc {
- [trackingArea release];
- [markedText release];
- [super dealloc];
-}
-
-static const NSRange kEmptyRange = { NSNotFound, 0 };
-
-- (BOOL)hasMarkedText {
- return (markedText.length > 0);
-}
-
-- (NSRange)markedRange {
- return NSMakeRange(0, markedText.length);
-}
-
-- (NSRange)selectedRange {
- return kEmptyRange;
-}
-
-- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
- if ([aString isKindOfClass:[NSAttributedString class]]) {
- [markedText initWithAttributedString:aString];
- } else {
- [markedText initWithString:aString];
- }
- if (markedText.length == 0) {
- [self unmarkText];
- return;
- }
- if (OS_OSX::singleton->im_active) {
- imeInputEventInProgress = true;
- OS_OSX::singleton->im_text.parse_utf8([[markedText mutableString] UTF8String]);
- OS_OSX::singleton->im_selection = Point2(selectedRange.location, selectedRange.length);
-
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
- }
-}
-
-- (void)doCommandBySelector:(SEL)aSelector {
- if ([self respondsToSelector:aSelector])
- [self performSelector:aSelector];
-}
-
-- (void)unmarkText {
- imeInputEventInProgress = false;
- [[markedText mutableString] setString:@""];
- if (OS_OSX::singleton->im_active) {
- OS_OSX::singleton->im_text = String();
- OS_OSX::singleton->im_selection = Point2();
-
- if (OS_OSX::singleton->get_main_loop())
- OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
- }
-}
-
-- (NSArray *)validAttributesForMarkedText {
- return [NSArray array];
-}
-
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
- return nil;
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
- return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
- const NSRect contentRect = [OS_OSX::singleton->window_view frame];
- float displayScale = OS_OSX::singleton->_display_scale();
- NSRect pointInWindowRect = NSMakeRect(OS_OSX::singleton->im_position.x / displayScale, contentRect.size.height - (OS_OSX::singleton->im_position.y / displayScale) - 1, 0, 0);
- NSPoint pointOnScreen = [[OS_OSX::singleton->window_view window] convertRectToScreen:pointInWindowRect].origin;
-
- return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0);
-}
-
-- (void)cancelComposition {
- [self unmarkText];
- NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
- [currentInputContext discardMarkedText];
-}
-
-- (void)insertText:(id)aString {
- [self insertText:aString replacementRange:NSMakeRange(0, 0)];
-}
-
-- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
- NSEvent *event = [NSApp currentEvent];
-
- NSString *characters;
- if ([aString isKindOfClass:[NSAttributedString class]]) {
- characters = [aString string];
- } else {
- characters = (NSString *)aString;
- }
-
- NSUInteger i, length = [characters length];
-
- NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
- NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
- if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) {
- NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
- [currentInputContext discardMarkedText];
- [self cancelComposition];
- return;
- }
-
- for (i = 0; i < length; i++) {
- const unichar codepoint = [characters characterAtIndex:i];
- if ((codepoint & 0xFF00) == 0xF700)
- continue;
-
- OS_OSX::KeyEvent ke;
-
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = false;
- ke.raw = false; // IME input event
- ke.scancode = 0;
- ke.unicode = codepoint;
-
- push_to_key_event_buffer(ke);
- }
- [self cancelComposition];
-}
-
-- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
- return NSDragOperationCopy;
-}
-
-- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
- return NSDragOperationCopy;
-}
-
-- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
-
- NSPasteboard *pboard = [sender draggingPasteboard];
- NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
-
- Vector<String> files;
- for (NSUInteger i = 0; i < filenames.count; i++) {
- NSString *ns = [filenames objectAtIndex:i];
- char *utfs = strdup([ns UTF8String]);
- String ret;
- ret.parse_utf8(utfs);
- free(utfs);
- files.push_back(ret);
- }
-
- if (files.size()) {
- OS_OSX::singleton->main_loop->drop_files(files, 0);
- OS_OSX::singleton->move_window_to_foreground();
- }
-
- return NO;
-}
-
-- (BOOL)isOpaque {
- return YES;
-}
-
-- (BOOL)canBecomeKeyView {
- return YES;
-}
-
-- (BOOL)acceptsFirstResponder {
- return YES;
-}
-
-- (void)cursorUpdate:(NSEvent *)event {
- OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape;
- OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX;
- OS_OSX::singleton->set_cursor_shape(p_shape);
-}
-
-static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) {
- if (pressed) {
- button_mask |= mask;
- } else {
- button_mask &= ~mask;
- }
-
- Ref<InputEventMouseButton> mb;
- mb.instance();
- const CGFloat backingScaleFactor = [[event window] backingScaleFactor];
- const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor);
- get_key_modifier_state([event modifierFlags], mb);
- mb->set_button_index(index);
- mb->set_pressed(pressed);
- mb->set_position(pos);
- mb->set_global_position(pos);
- mb->set_button_mask(button_mask);
- if (index == BUTTON_LEFT && pressed) {
- mb->set_doubleclick([event clickCount] == 2);
- }
- OS_OSX::singleton->push_input(mb);
-}
-
-- (void)mouseDown:(NSEvent *)event {
- if (([event modifierFlags] & NSEventModifierFlagControl)) {
- mouse_down_control = true;
- _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
- } else {
- mouse_down_control = false;
- _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, true);
- }
-}
-
-- (void)mouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
-}
-
-- (void)mouseUp:(NSEvent *)event {
- if (mouse_down_control) {
- _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
- } else {
- _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, false);
- }
-}
-
-- (void)mouseMoved:(NSEvent *)event {
-
- Ref<InputEventMouseMotion> mm;
- mm.instance();
-
- mm->set_button_mask(button_mask);
- const CGFloat backingScaleFactor = [[event window] backingScaleFactor];
- const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor);
- mm->set_position(pos);
- mm->set_pressure([event pressure]);
- if ([event subtype] == NSTabletPointEventSubtype) {
- const NSPoint p = [event tilt];
- mm->set_tilt(Vector2(p.x, p.y));
- }
- mm->set_global_position(pos);
- mm->set_speed(OS_OSX::singleton->input->get_last_mouse_speed());
- Vector2 relativeMotion = Vector2();
- relativeMotion.x = [event deltaX] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor);
- relativeMotion.y = [event deltaY] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor);
- mm->set_relative(relativeMotion);
- get_key_modifier_state([event modifierFlags], mm);
-
- OS_OSX::singleton->input->set_mouse_position(Point2(mouse_x, mouse_y));
- OS_OSX::singleton->push_input(mm);
-}
-
-- (void)rightMouseDown:(NSEvent *)event {
- _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true);
-}
-
-- (void)rightMouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
-}
-
-- (void)rightMouseUp:(NSEvent *)event {
- _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false);
-}
-
-- (void)otherMouseDown:(NSEvent *)event {
-
- if ((int)[event buttonNumber] == 2) {
- _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true);
-
- } else if ((int)[event buttonNumber] == 3) {
- _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true);
-
- } else if ((int)[event buttonNumber] == 4) {
- _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true);
-
- } else {
- return;
- }
-}
-
-- (void)otherMouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
-}
-
-- (void)otherMouseUp:(NSEvent *)event {
-
- if ((int)[event buttonNumber] == 2) {
- _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false);
-
- } else if ((int)[event buttonNumber] == 3) {
- _mouseDownEvent(event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false);
-
- } else if ((int)[event buttonNumber] == 4) {
- _mouseDownEvent(event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false);
-
- } else {
- return;
- }
-}
-
-- (void)mouseExited:(NSEvent *)event {
- if (!OS_OSX::singleton)
- return;
-
- if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED)
- OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
-}
-
-- (void)mouseEntered:(NSEvent *)event {
- if (!OS_OSX::singleton)
- return;
- if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED)
- OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
-
- OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape;
- OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX;
- OS_OSX::singleton->set_cursor_shape(p_shape);
-}
-
-- (void)magnifyWithEvent:(NSEvent *)event {
- Ref<InputEventMagnifyGesture> ev;
- ev.instance();
- get_key_modifier_state([event modifierFlags], ev);
- ev->set_position(get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor]));
- ev->set_factor([event magnification] + 1.0);
- OS_OSX::singleton->push_input(ev);
-}
-
-- (void)viewDidChangeBackingProperties {
- // nothing left to do here
-}
-
-- (void)updateTrackingAreas {
- if (trackingArea != nil) {
- [self removeTrackingArea:trackingArea];
- [trackingArea release];
- }
-
- NSTrackingAreaOptions options =
- NSTrackingMouseEnteredAndExited |
- NSTrackingActiveInKeyWindow |
- NSTrackingCursorUpdate |
- NSTrackingInVisibleRect;
-
- trackingArea = [[NSTrackingArea alloc]
- initWithRect:[self bounds]
- options:options
- owner:self
- userInfo:nil];
-
- [self addTrackingArea:trackingArea];
- [super updateTrackingAreas];
-}
-
-static bool isNumpadKey(unsigned int key) {
-
- static const unsigned int table[] = {
- 0x41, /* kVK_ANSI_KeypadDecimal */
- 0x43, /* kVK_ANSI_KeypadMultiply */
- 0x45, /* kVK_ANSI_KeypadPlus */
- 0x47, /* kVK_ANSI_KeypadClear */
- 0x4b, /* kVK_ANSI_KeypadDivide */
- 0x4c, /* kVK_ANSI_KeypadEnter */
- 0x4e, /* kVK_ANSI_KeypadMinus */
- 0x51, /* kVK_ANSI_KeypadEquals */
- 0x52, /* kVK_ANSI_Keypad0 */
- 0x53, /* kVK_ANSI_Keypad1 */
- 0x54, /* kVK_ANSI_Keypad2 */
- 0x55, /* kVK_ANSI_Keypad3 */
- 0x56, /* kVK_ANSI_Keypad4 */
- 0x57, /* kVK_ANSI_Keypad5 */
- 0x58, /* kVK_ANSI_Keypad6 */
- 0x59, /* kVK_ANSI_Keypad7 */
- 0x5b, /* kVK_ANSI_Keypad8 */
- 0x5c, /* kVK_ANSI_Keypad9 */
- 0x5f, /* kVK_JIS_KeypadComma */
- 0x00
- };
- for (int i = 0; table[i] != 0; i++) {
- if (key == table[i])
- return true;
- }
- return false;
-}
-
-// Translates a OS X keycode to a Godot keycode
-//
-static int translateKey(unsigned int key) {
-
- // Keyboard symbol translation table
- static const unsigned int table[128] = {
- /* 00 */ KEY_A,
- /* 01 */ KEY_S,
- /* 02 */ KEY_D,
- /* 03 */ KEY_F,
- /* 04 */ KEY_H,
- /* 05 */ KEY_G,
- /* 06 */ KEY_Z,
- /* 07 */ KEY_X,
- /* 08 */ KEY_C,
- /* 09 */ KEY_V,
- /* 0a */ KEY_SECTION, /* ISO Section */
- /* 0b */ KEY_B,
- /* 0c */ KEY_Q,
- /* 0d */ KEY_W,
- /* 0e */ KEY_E,
- /* 0f */ KEY_R,
- /* 10 */ KEY_Y,
- /* 11 */ KEY_T,
- /* 12 */ KEY_1,
- /* 13 */ KEY_2,
- /* 14 */ KEY_3,
- /* 15 */ KEY_4,
- /* 16 */ KEY_6,
- /* 17 */ KEY_5,
- /* 18 */ KEY_EQUAL,
- /* 19 */ KEY_9,
- /* 1a */ KEY_7,
- /* 1b */ KEY_MINUS,
- /* 1c */ KEY_8,
- /* 1d */ KEY_0,
- /* 1e */ KEY_BRACERIGHT,
- /* 1f */ KEY_O,
- /* 20 */ KEY_U,
- /* 21 */ KEY_BRACELEFT,
- /* 22 */ KEY_I,
- /* 23 */ KEY_P,
- /* 24 */ KEY_ENTER,
- /* 25 */ KEY_L,
- /* 26 */ KEY_J,
- /* 27 */ KEY_APOSTROPHE,
- /* 28 */ KEY_K,
- /* 29 */ KEY_SEMICOLON,
- /* 2a */ KEY_BACKSLASH,
- /* 2b */ KEY_COMMA,
- /* 2c */ KEY_SLASH,
- /* 2d */ KEY_N,
- /* 2e */ KEY_M,
- /* 2f */ KEY_PERIOD,
- /* 30 */ KEY_TAB,
- /* 31 */ KEY_SPACE,
- /* 32 */ KEY_QUOTELEFT,
- /* 33 */ KEY_BACKSPACE,
- /* 34 */ KEY_UNKNOWN,
- /* 35 */ KEY_ESCAPE,
- /* 36 */ KEY_META,
- /* 37 */ KEY_META,
- /* 38 */ KEY_SHIFT,
- /* 39 */ KEY_CAPSLOCK,
- /* 3a */ KEY_ALT,
- /* 3b */ KEY_CONTROL,
- /* 3c */ KEY_SHIFT,
- /* 3d */ KEY_ALT,
- /* 3e */ KEY_CONTROL,
- /* 3f */ KEY_UNKNOWN, /* Function */
- /* 40 */ KEY_UNKNOWN, /* F17 */
- /* 41 */ KEY_KP_PERIOD,
- /* 42 */ KEY_UNKNOWN,
- /* 43 */ KEY_KP_MULTIPLY,
- /* 44 */ KEY_UNKNOWN,
- /* 45 */ KEY_KP_ADD,
- /* 46 */ KEY_UNKNOWN,
- /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */
- /* 48 */ KEY_VOLUMEUP, /* VolumeUp */
- /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */
- /* 4a */ KEY_VOLUMEMUTE, /* Mute */
- /* 4b */ KEY_KP_DIVIDE,
- /* 4c */ KEY_KP_ENTER,
- /* 4d */ KEY_UNKNOWN,
- /* 4e */ KEY_KP_SUBTRACT,
- /* 4f */ KEY_UNKNOWN, /* F18 */
- /* 50 */ KEY_UNKNOWN, /* F19 */
- /* 51 */ KEY_EQUAL, /* KeypadEqual */
- /* 52 */ KEY_KP_0,
- /* 53 */ KEY_KP_1,
- /* 54 */ KEY_KP_2,
- /* 55 */ KEY_KP_3,
- /* 56 */ KEY_KP_4,
- /* 57 */ KEY_KP_5,
- /* 58 */ KEY_KP_6,
- /* 59 */ KEY_KP_7,
- /* 5a */ KEY_UNKNOWN, /* F20 */
- /* 5b */ KEY_KP_8,
- /* 5c */ KEY_KP_9,
- /* 5d */ KEY_YEN, /* JIS Yen */
- /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */
- /* 5f */ KEY_COMMA, /* JIS KeypadComma */
- /* 60 */ KEY_F5,
- /* 61 */ KEY_F6,
- /* 62 */ KEY_F7,
- /* 63 */ KEY_F3,
- /* 64 */ KEY_F8,
- /* 65 */ KEY_F9,
- /* 66 */ KEY_UNKNOWN, /* JIS Eisu */
- /* 67 */ KEY_F11,
- /* 68 */ KEY_UNKNOWN, /* JIS Kana */
- /* 69 */ KEY_F13,
- /* 6a */ KEY_F16,
- /* 6b */ KEY_F14,
- /* 6c */ KEY_UNKNOWN,
- /* 6d */ KEY_F10,
- /* 6e */ KEY_MENU,
- /* 6f */ KEY_F12,
- /* 70 */ KEY_UNKNOWN,
- /* 71 */ KEY_F15,
- /* 72 */ KEY_INSERT, /* Really Help... */
- /* 73 */ KEY_HOME,
- /* 74 */ KEY_PAGEUP,
- /* 75 */ KEY_DELETE,
- /* 76 */ KEY_F4,
- /* 77 */ KEY_END,
- /* 78 */ KEY_F2,
- /* 79 */ KEY_PAGEDOWN,
- /* 7a */ KEY_F1,
- /* 7b */ KEY_LEFT,
- /* 7c */ KEY_RIGHT,
- /* 7d */ KEY_DOWN,
- /* 7e */ KEY_UP,
- /* 7f */ KEY_UNKNOWN,
- };
-
- if (key >= 128)
- return KEY_UNKNOWN;
-
- return table[key];
-}
-
-struct _KeyCodeMap {
- UniChar kchar;
- int kcode;
-};
-
-static const _KeyCodeMap _keycodes[55] = {
- { '`', KEY_QUOTELEFT },
- { '~', KEY_ASCIITILDE },
- { '0', KEY_0 },
- { '1', KEY_1 },
- { '2', KEY_2 },
- { '3', KEY_3 },
- { '4', KEY_4 },
- { '5', KEY_5 },
- { '6', KEY_6 },
- { '7', KEY_7 },
- { '8', KEY_8 },
- { '9', KEY_9 },
- { '-', KEY_MINUS },
- { '_', KEY_UNDERSCORE },
- { '=', KEY_EQUAL },
- { '+', KEY_PLUS },
- { 'q', KEY_Q },
- { 'w', KEY_W },
- { 'e', KEY_E },
- { 'r', KEY_R },
- { 't', KEY_T },
- { 'y', KEY_Y },
- { 'u', KEY_U },
- { 'i', KEY_I },
- { 'o', KEY_O },
- { 'p', KEY_P },
- { '[', KEY_BRACELEFT },
- { ']', KEY_BRACERIGHT },
- { '{', KEY_BRACELEFT },
- { '}', KEY_BRACERIGHT },
- { 'a', KEY_A },
- { 's', KEY_S },
- { 'd', KEY_D },
- { 'f', KEY_F },
- { 'g', KEY_G },
- { 'h', KEY_H },
- { 'j', KEY_J },
- { 'k', KEY_K },
- { 'l', KEY_L },
- { ';', KEY_SEMICOLON },
- { ':', KEY_COLON },
- { '\'', KEY_APOSTROPHE },
- { '\"', KEY_QUOTEDBL },
- { '\\', KEY_BACKSLASH },
- { '#', KEY_NUMBERSIGN },
- { 'z', KEY_Z },
- { 'x', KEY_X },
- { 'c', KEY_C },
- { 'v', KEY_V },
- { 'b', KEY_B },
- { 'n', KEY_N },
- { 'm', KEY_M },
- { ',', KEY_COMMA },
- { '.', KEY_PERIOD },
- { '/', KEY_SLASH }
-};
-
-static int remapKey(unsigned int key, unsigned int state) {
-
- if (isNumpadKey(key))
- return translateKey(key);
-
- TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
- if (!currentKeyboard)
- return translateKey(key);
-
- CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
- if (!layoutData)
- return translateKey(key);
-
- const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
-
- UInt32 keysDown = 0;
- UniChar chars[4];
- UniCharCount realLength;
-
- OSStatus err = UCKeyTranslate(keyboardLayout,
- key,
- kUCKeyActionDisplay,
- (state >> 8) & 0xFF,
- LMGetKbdType(),
- kUCKeyTranslateNoDeadKeysBit,
- &keysDown,
- sizeof(chars) / sizeof(chars[0]),
- &realLength,
- chars);
-
- if (err != noErr) {
- return translateKey(key);
- }
-
- for (unsigned int i = 0; i < 55; i++) {
- if (_keycodes[i].kchar == chars[0]) {
- return _keycodes[i].kcode;
- }
- }
- return translateKey(key);
-}
-
-- (void)keyDown:(NSEvent *)event {
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- NSString *characters = [event characters];
- NSUInteger length = [characters length];
-
- if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
- // Fallback unicode character handler used if IME is not active
- for (NSUInteger i = 0; i < length; i++) {
- OS_OSX::KeyEvent ke;
-
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
- ke.raw = true;
- ke.unicode = [characters characterAtIndex:i];
-
- push_to_key_event_buffer(ke);
- }
- } else {
- OS_OSX::KeyEvent ke;
-
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
- ke.raw = false;
- ke.unicode = 0;
-
- push_to_key_event_buffer(ke);
- }
- }
-
- // Pass events to IME handler
- if (OS_OSX::singleton->im_active)
- [self interpretKeyEvents:[NSArray arrayWithObject:event]];
-}
-
-- (void)flagsChanged:(NSEvent *)event {
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- OS_OSX::KeyEvent ke;
-
- ke.echo = false;
- ke.raw = true;
+#include <mach-o/dyld.h>
+#include <os/log.h>
- int key = [event keyCode];
- int mod = [event modifierFlags];
+/*************************************************************************/
+/* OSXTerminalLogger */
+/*************************************************************************/
- if (key == 0x36 || key == 0x37) {
- if (mod & NSEventModifierFlagCommand) {
- mod &= ~NSEventModifierFlagCommand;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x38 || key == 0x3c) {
- if (mod & NSEventModifierFlagShift) {
- mod &= ~NSEventModifierFlagShift;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x3a || key == 0x3d) {
- if (mod & NSEventModifierFlagOption) {
- mod &= ~NSEventModifierFlagOption;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x3b || key == 0x3e) {
- if (mod & NSEventModifierFlagControl) {
- mod &= ~NSEventModifierFlagControl;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else {
+class OSXTerminalLogger : public StdLogger {
+public:
+ virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR) {
+ if (!should_log(true)) {
return;
}
- ke.osx_state = mod;
- ke.scancode = remapKey(key, mod);
- ke.unicode = 0;
-
- push_to_key_event_buffer(ke);
- }
-}
-
-- (void)keyUp:(NSEvent *)event {
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- NSString *characters = [event characters];
- NSUInteger length = [characters length];
-
- // Fallback unicode character handler used if IME is not active
- if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
- for (NSUInteger i = 0; i < length; i++) {
- OS_OSX::KeyEvent ke;
-
- ke.osx_state = [event modifierFlags];
- ke.pressed = false;
- ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
- ke.raw = true;
- ke.unicode = [characters characterAtIndex:i];
-
- push_to_key_event_buffer(ke);
- }
- } else {
- OS_OSX::KeyEvent ke;
-
- ke.osx_state = [event modifierFlags];
- ke.pressed = false;
- ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
- ke.raw = true;
- ke.unicode = 0;
-
- push_to_key_event_buffer(ke);
- }
- }
-}
-
-inline void sendScrollEvent(int button, double factor, int modifierFlags) {
-
- unsigned int mask = 1 << (button - 1);
- Vector2 mouse_pos = Vector2(mouse_x, mouse_y);
-
- Ref<InputEventMouseButton> sc;
- sc.instance();
-
- get_key_modifier_state(modifierFlags, sc);
- sc->set_button_index(button);
- sc->set_factor(factor);
- sc->set_pressed(true);
- sc->set_position(mouse_pos);
- sc->set_global_position(mouse_pos);
- button_mask |= mask;
- sc->set_button_mask(button_mask);
- OS_OSX::singleton->push_input(sc);
-
- sc.instance();
- sc->set_button_index(button);
- sc->set_factor(factor);
- sc->set_pressed(false);
- sc->set_position(mouse_pos);
- sc->set_global_position(mouse_pos);
- button_mask &= ~mask;
- sc->set_button_mask(button_mask);
- OS_OSX::singleton->push_input(sc);
-}
-
-inline void sendPanEvent(double dx, double dy, int modifierFlags) {
-
- Ref<InputEventPanGesture> pg;
- pg.instance();
-
- get_key_modifier_state(modifierFlags, pg);
- Vector2 mouse_pos = Vector2(mouse_x, mouse_y);
- pg->set_position(mouse_pos);
- pg->set_delta(Vector2(-dx, -dy));
- OS_OSX::singleton->push_input(pg);
-}
-
-- (void)scrollWheel:(NSEvent *)event {
- double deltaX, deltaY;
-
- get_mouse_pos([event locationInWindow], [[event window] backingScaleFactor]);
-
- deltaX = [event scrollingDeltaX];
- deltaY = [event scrollingDeltaY];
-
- if ([event hasPreciseScrollingDeltas]) {
- deltaX *= 0.03;
- deltaY *= 0.03;
- }
-
- if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
- sendPanEvent(deltaX, deltaY, [event modifierFlags]);
- } else {
- if (fabs(deltaX)) {
- sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
- }
- if (fabs(deltaY)) {
- sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
- }
- }
-}
-
-@end
-
-@interface GodotWindow : NSWindow {
-}
-@end
-
-@implementation GodotWindow
-
-- (BOOL)canBecomeKeyWindow {
- // Required for NSBorderlessWindowMask windows
- return YES;
-}
-
-@end
-
-void OS_OSX::_update_global_menu() {
-
- NSMenu *main_menu = [NSApp mainMenu];
+ const char *err_details;
+ if (p_rationale && p_rationale[0])
+ err_details = p_rationale;
+ else
+ err_details = p_code;
- for (int i = 1; i < [main_menu numberOfItems]; i++) {
- [main_menu removeItemAtIndex:i];
- }
- for (Map<String, Vector<GlobalMenuItem> >::Element *E = global_menus.front(); E; E = E->next()) {
- if (E->key() != "_dock") {
- NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease];
- for (int i = 0; i < E->get().size(); i++) {
- if (E->get()[i].label == String()) {
- [menu addItem:[NSMenuItem separatorItem]];
- } else {
- NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E->get()[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
- [menu_item setRepresentedObject:[NSValue valueWithPointer:&(E->get()[i])]];
- }
- }
- NSMenuItem *menu_item = [main_menu addItemWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()] action:nil keyEquivalent:@""];
- [main_menu setSubmenu:menu forItem:menu_item];
+ switch (p_type) {
+ case ERR_WARNING:
+ os_log_info(OS_LOG_DEFAULT,
+ "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_SCRIPT:
+ os_log_error(OS_LOG_DEFAULT,
+ "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_SHADER:
+ os_log_error(OS_LOG_DEFAULT,
+ "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_ERROR:
+ default:
+ os_log_error(OS_LOG_DEFAULT,
+ "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
}
}
-}
-
-void OS_OSX::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
-
- global_menus[p_menu].push_back(GlobalMenuItem(p_label, p_signal, p_meta));
- _update_global_menu();
-}
-
-void OS_OSX::global_menu_add_separator(const String &p_menu) {
-
- global_menus[p_menu].push_back(GlobalMenuItem());
- _update_global_menu();
-}
-
-void OS_OSX::global_menu_remove_item(const String &p_menu, int p_idx) {
-
- ERR_FAIL_INDEX(p_idx, global_menus[p_menu].size());
-
- global_menus[p_menu].remove(p_idx);
- _update_global_menu();
-}
-
-void OS_OSX::global_menu_clear(const String &p_menu) {
-
- global_menus[p_menu].clear();
- _update_global_menu();
-}
-
-Point2 OS_OSX::get_ime_selection() const {
-
- return im_selection;
-}
-
-String OS_OSX::get_ime_text() const {
+};
- return im_text;
-}
+/*************************************************************************/
+/* OS_OSX */
+/*************************************************************************/
String OS_OSX::get_unique_id() const {
-
static String serial_number;
if (serial_number.empty()) {
@@ -1401,300 +119,42 @@ String OS_OSX::get_unique_id() const {
return serial_number;
}
-void OS_OSX::set_ime_active(const bool p_active) {
-
- im_active = p_active;
- if (!im_active)
- [window_view cancelComposition];
-}
-
-void OS_OSX::set_ime_position(const Point2 &p_pos) {
-
- im_position = p_pos;
-}
-
void OS_OSX::initialize_core() {
-
- crash_handler.initialize();
-
OS_Unix::initialize_core();
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM);
-
- SemaphoreOSX::make_default();
-}
-
-static bool keyboard_layout_dirty = true;
-static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
- keyboard_layout_dirty = true;
}
-static bool displays_arrangement_dirty = true;
-static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) {
- displays_arrangement_dirty = true;
+void OS_OSX::initialize_joypads() {
+ joypad_osx = memnew(JoypadOSX(Input::get_singleton()));
}
-int OS_OSX::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- /*** OSX INITIALIZATION ***/
- /*** OSX INITIALIZATION ***/
- /*** OSX INITIALIZATION ***/
-
- keyboard_layout_dirty = true;
- displays_arrangement_dirty = true;
-
- // Register to be notified on keyboard layout changes
- CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),
- NULL, keyboard_layout_changed,
- kTISNotifySelectedKeyboardInputSourceChanged, NULL,
- CFNotificationSuspensionBehaviorDeliverImmediately);
-
- // Register to be notified on displays arrangement changes
- CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL);
-
- window_delegate = [[GodotWindowDelegate alloc] init];
-
- // Don't use accumulation buffer support; it's not accelerated
- // Aux buffers probably aren't accelerated either
-
- unsigned int styleMask;
-
- if (p_desired.borderless_window) {
- styleMask = NSWindowStyleMaskBorderless;
- } else {
- resizable = p_desired.resizable;
- styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (p_desired.resizable ? NSWindowStyleMaskResizable : 0);
- }
-
- window_object = [[GodotWindow alloc]
- initWithContentRect:NSMakeRect(0, 0, p_desired.width, p_desired.height)
- styleMask:styleMask
- backing:NSBackingStoreBuffered
- defer:NO];
-
- ERR_FAIL_COND_V(window_object == nil, ERR_UNAVAILABLE);
-
- window_view = [[GodotContentView alloc] init];
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) {
- [window_view setWantsLayer:TRUE];
- }
-
- float displayScale = 1.0;
- if (is_hidpi_allowed()) {
- // note that mainScreen is not screen #0 but the one with the keyboard focus.
- NSScreen *screen = [NSScreen mainScreen];
- if ([screen respondsToSelector:@selector(backingScaleFactor)]) {
- displayScale = fmax(displayScale, [screen backingScaleFactor]);
- }
- }
-
- window_size.width = p_desired.width * displayScale;
- window_size.height = p_desired.height * displayScale;
-
- if (displayScale > 1.0) {
- [window_view setWantsBestResolutionOpenGLSurface:YES];
- //if (current_videomode.resizable)
- [window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
- } else {
- [window_view setWantsBestResolutionOpenGLSurface:NO];
- }
-
- //[window_object setTitle:[NSString stringWithUTF8String:"GodotEnginies"]];
- [window_object setContentView:window_view];
- [window_object setDelegate:window_delegate];
- [window_object setAcceptsMouseMovedEvents:YES];
- [(NSWindow *)window_object center];
-
- [window_object setRestorable:NO];
-
- unsigned int attributeCount = 0;
-
- // OS X needs non-zero color size, so set reasonable values
- int colorBits = 32;
-
- // Fail if a robustness strategy was requested
-
-#define ADD_ATTR(x) \
- { attributes[attributeCount++] = x; }
-#define ADD_ATTR2(x, y) \
- { \
- ADD_ATTR(x); \
- ADD_ATTR(y); \
- }
-
- // Arbitrary array size here
- NSOpenGLPixelFormatAttribute attributes[40];
-
- ADD_ATTR(NSOpenGLPFADoubleBuffer);
- ADD_ATTR(NSOpenGLPFAClosestPolicy);
-
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy);
- } else {
- //we now need OpenGL 3 or better, maybe even change this to 3_3Core ?
- ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core);
- }
-
- ADD_ATTR2(NSOpenGLPFAColorSize, colorBits);
-
- /*
- if (fbconfig->alphaBits > 0)
- ADD_ATTR2(NSOpenGLPFAAlphaSize, fbconfig->alphaBits);
-*/
-
- ADD_ATTR2(NSOpenGLPFADepthSize, 24);
-
- ADD_ATTR2(NSOpenGLPFAStencilSize, 8);
-
- /*
- if (fbconfig->stereo)
- ADD_ATTR(NSOpenGLPFAStereo);
-*/
-
- /*
- if (fbconfig->samples > 0) {
- ADD_ATTR2(NSOpenGLPFASampleBuffers, 1);
- ADD_ATTR2(NSOpenGLPFASamples, fbconfig->samples);
- }
-*/
-
- // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB
- // framebuffer, so there's no need (and no way) to request it
-
- ADD_ATTR(0);
-
-#undef ADD_ATTR
-#undef ADD_ATTR2
-
- pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
- ERR_FAIL_COND_V(pixelFormat == nil, ERR_UNAVAILABLE);
-
- context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
-
- ERR_FAIL_COND_V(context == nil, ERR_UNAVAILABLE);
-
- [context setView:window_view];
-
- [context makeCurrentContext];
-
- set_use_vsync(p_desired.use_vsync);
-
- [NSApp activateIgnoringOtherApps:YES];
-
- _update_window();
-
- [window_object makeKeyAndOrderFront:nil];
-
- if (p_desired.fullscreen)
- zoomed = true;
-
- /*** END OSX INITIALIZATION ***/
-
- bool gles3 = true;
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gles3 = false;
- }
-
- bool editor = Engine::get_singleton()->is_editor_hint();
- bool gl_initialization_error = false;
-
- while (true) {
- if (gles3) {
- if (RasterizerGLES3::is_viable() == OK) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- gles3 = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
- "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
-
- visual_server = memnew(VisualServerRaster);
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
- }
-
- visual_server->init();
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
- joypad_osx = memnew(JoypadOSX);
-
- power_manager = memnew(PowerOSX);
-
- _ensure_user_data_dir();
-
- restore_rect = Rect2(get_window_position(), get_window_size());
-
- if (p_desired.layered) {
- set_window_per_pixel_transparency_enabled(true);
- }
-
- update_real_mouse_position();
+void OS_OSX::initialize() {
+ crash_handler.initialize();
- return OK;
+ initialize_core();
+ //ensure_user_data_dir();
}
void OS_OSX::finalize() {
-
#ifdef COREMIDI_ENABLED
midi_driver.close();
#endif
- CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL);
- CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL);
-
delete_main_loop();
- memdelete(joypad_osx);
- memdelete(input);
-
- cursors_cache.clear();
- visual_server->finish();
- memdelete(visual_server);
- //memdelete(rasterizer);
+ if (joypad_osx) {
+ memdelete(joypad_osx);
+ }
}
void OS_OSX::set_main_loop(MainLoop *p_main_loop) {
-
main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
}
void OS_OSX::delete_main_loop() {
-
if (!main_loop)
return;
memdelete(main_loop);
@@ -1702,92 +162,10 @@ void OS_OSX::delete_main_loop() {
}
String OS_OSX::get_name() const {
-
- return "OSX";
-}
-
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
-class OSXTerminalLogger : public StdLogger {
-public:
- virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR) {
- if (!should_log(true)) {
- return;
- }
-
- const char *err_details;
- if (p_rationale && p_rationale[0])
- err_details = p_rationale;
- else
- err_details = p_code;
-
- switch (p_type) {
- case ERR_WARNING:
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) {
- os_log_info(OS_LOG_DEFAULT,
- "WARNING: %{public}s: %{public}s\nAt: %{public}s:%i.",
- p_function, err_details, p_file, p_line);
- }
- logf_error("\E[1;33mWARNING: %s: \E[0m\E[1m%s\n", p_function,
- err_details);
- logf_error("\E[0;33m At: %s:%i.\E[0m\n", p_file, p_line);
- break;
- case ERR_SCRIPT:
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) {
- os_log_error(OS_LOG_DEFAULT,
- "SCRIPT ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.",
- p_function, err_details, p_file, p_line);
- }
- logf_error("\E[1;35mSCRIPT ERROR: %s: \E[0m\E[1m%s\n", p_function,
- err_details);
- logf_error("\E[0;35m At: %s:%i.\E[0m\n", p_file, p_line);
- break;
- case ERR_SHADER:
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) {
- os_log_error(OS_LOG_DEFAULT,
- "SHADER ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.",
- p_function, err_details, p_file, p_line);
- }
- logf_error("\E[1;36mSHADER ERROR: %s: \E[0m\E[1m%s\n", p_function,
- err_details);
- logf_error("\E[0;36m At: %s:%i.\E[0m\n", p_file, p_line);
- break;
- case ERR_ERROR:
- default:
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) {
- os_log_error(OS_LOG_DEFAULT,
- "ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.",
- p_function, err_details, p_file, p_line);
- }
- logf_error("\E[1;31mERROR: %s: \E[0m\E[1m%s\n", p_function, err_details);
- logf_error("\E[0;31m At: %s:%i.\E[0m\n", p_file, p_line);
- break;
- }
- }
-};
-
-#else
-
-typedef UnixTerminalLogger OSXTerminalLogger;
-#endif
-
-void OS_OSX::alert(const String &p_alert, const String &p_title) {
- // Set OS X-compliant variables
- NSAlert *window = [[NSAlert alloc] init];
- NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
- NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
-
- [window addButtonWithTitle:@"OK"];
- [window setMessageText:ns_title];
- [window setInformativeText:ns_alert];
- [window setAlertStyle:NSWarningAlertStyle];
-
- // Display it, then release
- [window runModal];
- [window release];
+ return "macOS";
}
Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
-
String path = p_path;
if (!FileAccess::exists(path)) {
@@ -1805,301 +183,11 @@ Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle,
return OK;
}
-void OS_OSX::set_cursor_shape(CursorShape p_shape) {
-
- if (cursor_shape == p_shape)
- return;
-
- if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
- cursor_shape = p_shape;
- return;
- }
-
- if (cursors[p_shape] != NULL) {
- [cursors[p_shape] set];
- } else {
- switch (p_shape) {
- case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break;
- case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break;
- case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break;
- case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break;
- case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break;
- case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break;
- case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break;
- case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break;
- case CURSOR_FORBIDDEN: [[NSCursor operationNotAllowedCursor] set]; break;
- case CURSOR_VSIZE: [cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break;
- case CURSOR_HSIZE: [cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break;
- case CURSOR_BDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break;
- case CURSOR_FDIAGSIZE: [cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break;
- case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break;
- case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break;
- case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break;
- case CURSOR_HELP: [cursorFromSelector(@selector(_helpCursor)) set]; break;
- default: {
- };
- }
- }
-
- cursor_shape = p_shape;
-}
-
-OS::CursorShape OS_OSX::get_cursor_shape() const {
-
- return cursor_shape;
-}
-
-void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-
- if (p_cursor.is_valid()) {
-
- Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- set_cursor_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
- }
-
- Ref<Texture> texture = p_cursor;
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
- Size2 texture_size;
- Rect2 atlas_rect;
-
- if (texture.is_valid()) {
- image = texture->get_data();
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(!texture.is_valid());
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- image = texture->get_data();
-
- ERR_FAIL_COND(!image.is_valid());
-
- NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
- initWithBitmapDataPlanes:NULL
- pixelsWide:int(texture_size.width)
- pixelsHigh:int(texture_size.height)
- bitsPerSample:8
- samplesPerPixel:4
- hasAlpha:YES
- isPlanar:NO
- colorSpaceName:NSDeviceRGBColorSpace
- bytesPerRow:int(texture_size.width) * 4
- bitsPerPixel:32];
-
- ERR_FAIL_COND(imgrep == nil);
- uint8_t *pixels = [imgrep bitmapData];
-
- int len = int(texture_size.width * texture_size.height);
- PoolVector<uint8_t> data = image->get_data();
- PoolVector<uint8_t>::Read r = data.read();
-
- image->lock();
-
- /* Premultiply the alpha channel */
- for (int i = 0; i < len; i++) {
- int row_index = floor(i / texture_size.width) + atlas_rect.position.y;
- int column_index = (i % int(texture_size.width)) + atlas_rect.position.x;
-
- if (atlas_texture.is_valid()) {
- column_index = MIN(column_index, atlas_rect.size.width - 1);
- row_index = MIN(row_index, atlas_rect.size.height - 1);
- }
-
- uint32_t color = image->get_pixel(column_index, row_index).to_argb32();
-
- uint8_t alpha = (color >> 24) & 0xFF;
- pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255;
- pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255;
- pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255;
- pixels[i * 4 + 3] = alpha;
- }
-
- image->unlock();
-
- NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)];
- [nsimage addRepresentation:imgrep];
-
- NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)];
-
- [cursors[p_shape] release];
- cursors[p_shape] = cursor;
-
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- if (p_shape == cursor_shape) {
- if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
- [cursor set];
- }
- }
-
- [imgrep release];
- [nsimage release];
- } else {
- // Reset to default system cursor
- if (cursors[p_shape] != NULL) {
- [cursors[p_shape] release];
- cursors[p_shape] = NULL;
- }
-
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
-
- cursors_cache.erase(p_shape);
- }
-}
-
-void OS_OSX::set_mouse_show(bool p_show) {
-}
-
-void OS_OSX::set_mouse_grab(bool p_grab) {
-}
-
-bool OS_OSX::is_mouse_grab_enabled() const {
-
- return mouse_grab;
-}
-
-void OS_OSX::warp_mouse_position(const Point2 &p_to) {
-
- //copied from windows impl with osx native calls
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- mouse_x = p_to.x;
- mouse_y = p_to.y;
- } else { //set OS position
-
- //local point in window coords
- const NSRect contentRect = [window_view frame];
- float displayScale = _display_scale();
- NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0);
- NSPoint pointOnScreen = [[window_view window] convertRectToScreen:pointInWindowRect].origin;
-
- //point in scren coords
- CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
-
- //do the warping
- CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
- CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0);
- CGAssociateMouseAndMouseCursorPosition(false);
- CGWarpMouseCursorPosition(lMouseWarpPos);
- CGAssociateMouseAndMouseCursorPosition(true);
- }
-}
-
-void OS_OSX::update_real_mouse_position() {
-
- get_mouse_pos([window_object mouseLocationOutsideOfEventStream], [window_view backingScaleFactor]);
- input->set_mouse_position(Point2(mouse_x, mouse_y));
-}
-
-Point2 OS_OSX::get_mouse_position() const {
-
- return Vector2(mouse_x, mouse_y);
-}
-
-int OS_OSX::get_mouse_button_state() const {
- return button_mask;
-}
-
-void OS_OSX::set_window_title(const String &p_title) {
- title = p_title;
-
- [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
-}
-
-void OS_OSX::set_native_icon(const String &p_filename) {
-
- FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
- ERR_FAIL_COND(!f);
-
- Vector<uint8_t> data;
- uint32_t len = f->get_len();
- data.resize(len);
- f->get_buffer((uint8_t *)&data.write[0], len);
- memdelete(f);
-
- NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
- ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data.");
-
- NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
- ERR_FAIL_COND_MSG(!icon, "Error loading icon.");
-
- [NSApp setApplicationIconImage:icon];
-}
-
-void OS_OSX::set_icon(const Ref<Image> &p_icon) {
-
- Ref<Image> img = p_icon;
- img = img->duplicate();
- img->convert(Image::FORMAT_RGBA8);
- NSBitmapImageRep *imgrep = [[[NSBitmapImageRep alloc]
- initWithBitmapDataPlanes:NULL
- pixelsWide:img->get_width()
- pixelsHigh:img->get_height()
- bitsPerSample:8
- samplesPerPixel:4
- hasAlpha:YES
- isPlanar:NO
- colorSpaceName:NSDeviceRGBColorSpace
- bytesPerRow:img->get_width() * 4
- bitsPerPixel:32] autorelease];
- ERR_FAIL_COND(imgrep == nil);
- uint8_t *pixels = [imgrep bitmapData];
-
- int len = img->get_width() * img->get_height();
- PoolVector<uint8_t> data = img->get_data();
- PoolVector<uint8_t>::Read r = data.read();
-
- /* Premultiply the alpha channel */
- for (int i = 0; i < len; i++) {
- uint8_t alpha = r[i * 4 + 3];
- pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
- pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
- pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
- pixels[i * 4 + 3] = alpha;
- }
-
- NSImage *nsimg = [[[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())] autorelease];
- ERR_FAIL_COND(nsimg == nil);
- [nsimg addRepresentation:imgrep];
-
- [NSApp setApplicationIconImage:nsimg];
-}
-
MainLoop *OS_OSX::get_main_loop() const {
-
return main_loop;
}
String OS_OSX::get_config_path() const {
-
if (has_environment("XDG_CONFIG_HOME")) {
return get_environment("XDG_CONFIG_HOME");
} else if (has_environment("HOME")) {
@@ -2110,7 +198,6 @@ String OS_OSX::get_config_path() const {
}
String OS_OSX::get_data_path() const {
-
if (has_environment("XDG_DATA_HOME")) {
return get_environment("XDG_DATA_HOME");
} else {
@@ -2119,7 +206,6 @@ String OS_OSX::get_data_path() const {
}
String OS_OSX::get_cache_path() const {
-
if (has_environment("XDG_CACHE_HOME")) {
return get_environment("XDG_CACHE_HOME");
} else if (has_environment("HOME")) {
@@ -2130,7 +216,6 @@ String OS_OSX::get_cache_path() const {
}
String OS_OSX::get_bundle_resource_dir() const {
-
NSBundle *main = [NSBundle mainBundle];
NSString *resourcePath = [main resourcePath];
@@ -2144,12 +229,10 @@ String OS_OSX::get_bundle_resource_dir() const {
// Get properly capitalized engine name for system paths
String OS_OSX::get_godot_dir_name() const {
-
return String(VERSION_SHORT_NAME).capitalize();
}
String OS_OSX::get_system_dir(SystemDir p_dir) const {
-
NSSearchPathDirectory id;
bool found = true;
@@ -2179,10 +262,8 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const {
String ret;
if (found) {
-
NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
-
char *utfs = strdup([[paths firstObject] UTF8String]);
ret.parse_utf8(utfs);
free(utfs);
@@ -2192,57 +273,14 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const {
return ret;
}
-bool OS_OSX::can_draw() const {
-
- return true;
-}
-
-void OS_OSX::set_clipboard(const String &p_text) {
-
- NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()];
- NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString];
-
- NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
- [pasteboard clearContents];
- [pasteboard writeObjects:copiedStringArray];
-}
-
-String OS_OSX::get_clipboard() const {
-
- NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
- NSArray *classArray = [NSArray arrayWithObject:[NSString class]];
- NSDictionary *options = [NSDictionary dictionary];
-
- BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
-
- if (!ok) {
- return "";
- }
-
- NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
- NSString *string = [objectsToPaste objectAtIndex:0];
-
- char *utfs = strdup([string UTF8String]);
- String ret;
- ret.parse_utf8(utfs);
- free(utfs);
-
- return ret;
-}
-
-void OS_OSX::release_rendering_thread() {
-
- [NSOpenGLContext clearCurrentContext];
-}
-
-void OS_OSX::make_rendering_thread() {
-
- [context makeCurrentContext];
-}
-
Error OS_OSX::shell_open(String p_uri) {
-
- [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[[NSString stringWithUTF8String:p_uri.utf8().get_data()] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]];
+ NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
+ NSURL *uri = [[NSURL alloc] initWithString:string];
+ // Escape special characters in filenames
+ if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) {
+ uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
+ }
+ [[NSWorkspace sharedWorkspace] openURL:uri];
return OK;
}
@@ -2251,472 +289,7 @@ String OS_OSX::get_locale() const {
return [locale_code UTF8String];
}
-void OS_OSX::swap_buffers() {
- [context flushBuffer];
-}
-
-void OS_OSX::wm_minimized(bool p_minimized) {
-
- minimized = p_minimized;
-};
-
-void OS_OSX::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-}
-
-OS::VideoMode OS_OSX::get_video_mode(int p_screen) const {
-
- VideoMode vm;
- vm.width = window_size.width;
- vm.height = window_size.height;
-
- return vm;
-}
-
-void OS_OSX::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-}
-
-int OS_OSX::get_screen_count() const {
- NSArray *screenArray = [NSScreen screens];
- return [screenArray count];
-};
-
-// Returns the native top-left screen coordinate of the smallest rectangle
-// that encompasses all screens. Needed in get_screen_position(),
-// get_window_position, and set_window_position()
-// to convert between OS X native screen coordinates and the ones expected by Godot
-Point2 OS_OSX::get_screens_origin() const {
- static Point2 origin;
-
- if (displays_arrangement_dirty) {
- origin = Point2();
-
- for (int i = 0; i < get_screen_count(); i++) {
- Point2 position = get_native_screen_position(i);
- if (position.x < origin.x) {
- origin.x = position.x;
- }
- if (position.y > origin.y) {
- origin.y = position.y;
- }
- }
-
- displays_arrangement_dirty = false;
- }
-
- return origin;
-}
-
-static int get_screen_index(NSScreen *screen) {
- const NSUInteger index = [[NSScreen screens] indexOfObject:screen];
- return index == NSNotFound ? 0 : index;
-}
-
-int OS_OSX::get_current_screen() const {
- if (window_object) {
- return get_screen_index([window_object screen]);
- } else {
- return get_screen_index([NSScreen mainScreen]);
- }
-};
-
-void OS_OSX::set_current_screen(int p_screen) {
- Vector2 wpos = get_window_position() - get_screen_position(get_current_screen());
- set_window_position(wpos + get_screen_position(p_screen));
-};
-
-Point2 OS_OSX::get_native_screen_position(int p_screen) const {
- if (p_screen < 0) {
- p_screen = get_current_screen();
- }
-
- NSArray *screenArray = [NSScreen screens];
- if ((NSUInteger)p_screen < [screenArray count]) {
- float display_scale = _display_scale([screenArray objectAtIndex:p_screen]);
- NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
- // Return the top-left corner of the screen, for OS X the y starts at the bottom
- return Point2(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale;
- }
-
- return Point2();
-}
-
-Point2 OS_OSX::get_screen_position(int p_screen) const {
- Point2 position = get_native_screen_position(p_screen) - get_screens_origin();
- // OS X native y-coordinate relative to get_screens_origin() is negative,
- // Godot expects a positive value
- position.y *= -1;
- return position;
-}
-
-int OS_OSX::get_screen_dpi(int p_screen) const {
- if (p_screen < 0) {
- p_screen = get_current_screen();
- }
-
- NSArray *screenArray = [NSScreen screens];
- if ((NSUInteger)p_screen < [screenArray count]) {
- float displayScale = _display_scale([screenArray objectAtIndex:p_screen]);
- NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
- NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
- CGSize displayPhysicalSize = CGDisplayScreenSize(
- [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
-
- return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale;
- }
-
- return 72;
-}
-
-Size2 OS_OSX::get_screen_size(int p_screen) const {
- if (p_screen < 0) {
- p_screen = get_current_screen();
- }
-
- NSArray *screenArray = [NSScreen screens];
- if ((NSUInteger)p_screen < [screenArray count]) {
- float displayScale = _display_scale([screenArray objectAtIndex:p_screen]);
- // Note: Use frame to get the whole screen size
- NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
- return Size2(nsrect.size.width, nsrect.size.height) * displayScale;
- }
-
- return Size2();
-}
-
-void OS_OSX::_update_window() {
- bool borderless_full = false;
-
- if (get_borderless_window()) {
- NSRect frameRect = [window_object frame];
- NSRect screenRect = [[window_object screen] frame];
-
- // Check if our window covers up the screen
- if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y &&
- frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) {
- borderless_full = true;
- }
- }
-
- if (borderless_full) {
- // If the window covers up the screen set the level to above the main menu and hide on deactivate
- [window_object setLevel:NSMainMenuWindowLevel + 1];
- [window_object setHidesOnDeactivate:YES];
- } else {
- // Reset these when our window is not a borderless window that covers up the screen
- [window_object setLevel:NSNormalWindowLevel];
- [window_object setHidesOnDeactivate:NO];
- }
-}
-
-float OS_OSX::_display_scale() const {
- if (window_object) {
- return _display_scale([window_object screen]);
- } else {
- return _display_scale([NSScreen mainScreen]);
- }
-}
-
-float OS_OSX::_display_scale(id screen) const {
- if (is_hidpi_allowed()) {
- if ([screen respondsToSelector:@selector(backingScaleFactor)]) {
- return fmax(1.0, [screen backingScaleFactor]);
- }
- }
- return 1.0;
-}
-
-Point2 OS_OSX::get_native_window_position() const {
-
- NSRect nsrect = [window_object frame];
- Point2 pos;
- float display_scale = _display_scale();
-
- // Return the position of the top-left corner, for OS X the y starts at the bottom
- pos.x = nsrect.origin.x * display_scale;
- pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale;
-
- return pos;
-};
-
-Point2 OS_OSX::get_window_position() const {
- Point2 position = get_native_window_position() - get_screens_origin();
- // OS X native y-coordinate relative to get_screens_origin() is negative,
- // Godot expects a positive value
- position.y *= -1;
- return position;
-}
-
-void OS_OSX::set_native_window_position(const Point2 &p_position) {
-
- NSPoint pos;
- float displayScale = _display_scale();
-
- pos.x = p_position.x / displayScale;
- pos.y = p_position.y / displayScale;
-
- [window_object setFrameTopLeftPoint:pos];
-
- _update_window();
-};
-
-void OS_OSX::set_window_position(const Point2 &p_position) {
- Point2 position = p_position;
- // OS X native y-coordinate relative to get_screens_origin() is negative,
- // Godot passes a positive value
- position.y *= -1;
- set_native_window_position(get_screens_origin() + position);
-
- update_real_mouse_position();
-};
-
-Size2 OS_OSX::get_window_size() const {
-
- return window_size;
-};
-
-Size2 OS_OSX::get_real_window_size() const {
-
- NSRect frame = [window_object frame];
- return Size2(frame.size.width, frame.size.height) * _display_scale();
-}
-
-Size2 OS_OSX::get_max_window_size() const {
- return max_size;
-}
-
-Size2 OS_OSX::get_min_window_size() const {
- return min_size;
-}
-
-void OS_OSX::set_min_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
- ERR_PRINT("Minimum window size can't be larger than maximum window size!");
- return;
- }
- min_size = p_size;
-
- if ((min_size != Size2()) && !zoomed) {
- Size2 size = min_size / _display_scale();
- [window_object setContentMinSize:NSMakeSize(size.x, size.y)];
- } else {
- [window_object setContentMinSize:NSMakeSize(0, 0)];
- }
-}
-
-void OS_OSX::set_max_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
- ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
- return;
- }
- max_size = p_size;
-
- if ((max_size != Size2()) && !zoomed) {
- Size2 size = max_size / _display_scale();
- [window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
- } else {
- [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
- }
-}
-
-void OS_OSX::set_window_size(const Size2 p_size) {
-
- Size2 size = p_size / _display_scale();
-
- if (get_borderless_window() == false) {
- // NSRect used by setFrame includes the title bar, so add it to our size.y
- CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
- if (menuBarHeight != 0.f) {
- size.y += menuBarHeight;
- } else {
- if (floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_12) {
- size.y += [[NSStatusBar systemStatusBar] thickness];
- }
- }
- }
-
- NSRect frame = [window_object frame];
- [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES];
-
- _update_window();
-};
-
-void OS_OSX::set_window_fullscreen(bool p_enabled) {
-
- if (zoomed != p_enabled) {
- if (layered_window)
- set_window_per_pixel_transparency_enabled(false);
- if (!resizable)
- [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable];
- if (p_enabled) {
- [window_object setContentMinSize:NSMakeSize(0, 0)];
- [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
- } else {
- if (min_size != Size2()) {
- Size2 size = min_size / _display_scale();
- [window_object setContentMinSize:NSMakeSize(size.x, size.y)];
- }
- if (max_size != Size2()) {
- Size2 size = max_size / _display_scale();
- [window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
- }
- }
- [window_object toggleFullScreen:nil];
- }
- zoomed = p_enabled;
-};
-
-bool OS_OSX::is_window_fullscreen() const {
-
- return zoomed;
-};
-
-void OS_OSX::set_window_resizable(bool p_enabled) {
-
- if (p_enabled)
- [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable];
- else if (!zoomed)
- [window_object setStyleMask:[window_object styleMask] & ~NSWindowStyleMaskResizable];
-
- resizable = p_enabled;
-};
-
-bool OS_OSX::is_window_resizable() const {
-
- return [window_object styleMask] & NSWindowStyleMaskResizable;
-};
-
-void OS_OSX::set_window_minimized(bool p_enabled) {
-
- if (p_enabled)
- [window_object performMiniaturize:nil];
- else
- [window_object deminiaturize:nil];
-};
-
-bool OS_OSX::is_window_minimized() const {
-
- if ([window_object respondsToSelector:@selector(isMiniaturized)])
- return [window_object isMiniaturized];
-
- return minimized;
-};
-
-void OS_OSX::set_window_maximized(bool p_enabled) {
-
- if (p_enabled) {
- restore_rect = Rect2(get_window_position(), get_window_size());
- [window_object setFrame:[[[NSScreen screens] objectAtIndex:get_current_screen()] visibleFrame] display:YES];
- } else {
- set_window_size(restore_rect.size);
- set_window_position(restore_rect.position);
- };
- maximized = p_enabled;
-};
-
-bool OS_OSX::is_window_maximized() const {
-
- // don't know
- return maximized;
-};
-
-void OS_OSX::move_window_to_foreground() {
-
- [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- [window_object makeKeyAndOrderFront:nil];
-}
-
-void OS_OSX::set_window_always_on_top(bool p_enabled) {
- if (is_window_always_on_top() == p_enabled)
- return;
-
- if (p_enabled)
- [window_object setLevel:NSFloatingWindowLevel];
- else
- [window_object setLevel:NSNormalWindowLevel];
-}
-
-bool OS_OSX::is_window_always_on_top() const {
- return [window_object level] == NSFloatingWindowLevel;
-}
-
-bool OS_OSX::is_window_focused() const {
- return window_focused;
-}
-
-void OS_OSX::request_attention() {
-
- [NSApp requestUserAttention:NSCriticalRequest];
-}
-
-bool OS_OSX::get_window_per_pixel_transparency_enabled() const {
-
- if (!is_layered_allowed()) return false;
- return layered_window;
-}
-
-void OS_OSX::set_window_per_pixel_transparency_enabled(bool p_enabled) {
-
- if (!is_layered_allowed()) return;
- if (layered_window != p_enabled) {
- if (p_enabled) {
- set_borderless_window(true);
- GLint opacity = 0;
- [window_object setBackgroundColor:[NSColor clearColor]];
- [window_object setOpaque:NO];
- [window_object setHasShadow:NO];
- [context setValues:&opacity forParameter:NSOpenGLCPSurfaceOpacity];
- layered_window = true;
- } else {
- GLint opacity = 1;
- [window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
- [window_object setOpaque:YES];
- [window_object setHasShadow:YES];
- [context setValues:&opacity forParameter:NSOpenGLCPSurfaceOpacity];
- layered_window = false;
- }
- [context update];
- NSRect frame = [window_object frame];
- [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, 1, 1) display:YES];
- [window_object setFrame:frame display:YES];
- }
-}
-
-void OS_OSX::set_borderless_window(bool p_borderless) {
-
- // OrderOut prevents a lose focus bug with the window
- [window_object orderOut:nil];
-
- if (p_borderless) {
- [window_object setStyleMask:NSWindowStyleMaskBorderless];
- } else {
- if (layered_window)
- set_window_per_pixel_transparency_enabled(false);
-
- [window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (resizable ? NSWindowStyleMaskResizable : 0)];
-
- // Force update of the window styles
- NSRect frameRect = [window_object frame];
- [window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
- [window_object setFrame:frameRect display:NO];
-
- // Restore the window title
- [window_object setTitle:[NSString stringWithUTF8String:title.utf8().get_data()]];
- }
-
- _update_window();
-
- [window_object makeKeyAndOrderFront:nil];
-}
-
-bool OS_OSX::get_borderless_window() {
-
- return [window_object styleMask] == NSWindowStyleMaskBorderless;
-}
-
String OS_OSX::get_executable_path() const {
-
int ret;
pid_t pid;
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
@@ -2733,174 +306,7 @@ String OS_OSX::get_executable_path() const {
}
}
-// Returns string representation of keys, if they are printable.
-//
-static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) {
-
- TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
- if (!currentKeyboard)
- return nil;
-
- CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
- if (!layoutData)
- return nil;
-
- const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
-
- OSStatus err;
- CFMutableStringRef output = CFStringCreateMutable(NULL, 0);
-
- for (int i = 0; i < length; ++i) {
-
- UInt32 keysDown = 0;
- UniChar chars[4];
- UniCharCount realLength;
-
- err = UCKeyTranslate(keyboardLayout,
- keyCode[i],
- kUCKeyActionDisplay,
- 0,
- LMGetKbdType(),
- kUCKeyTranslateNoDeadKeysBit,
- &keysDown,
- sizeof(chars) / sizeof(chars[0]),
- &realLength,
- chars);
-
- if (err != noErr) {
- CFRelease(output);
- return nil;
- }
-
- CFStringAppendCharacters(output, chars, 1);
- }
-
- //CFStringUppercase(output, NULL);
-
- return (NSString *)output;
-}
-
-OS::LatinKeyboardVariant OS_OSX::get_latin_keyboard_variant() const {
-
- static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY;
-
- if (keyboard_layout_dirty) {
-
- layout = LATIN_KEYBOARD_QWERTY;
-
- CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y };
- NSString *test = createStringForKeys(keys, 6);
-
- if ([test isEqualToString:@"qwertz"]) {
- layout = LATIN_KEYBOARD_QWERTZ;
- } else if ([test isEqualToString:@"azerty"]) {
- layout = LATIN_KEYBOARD_AZERTY;
- } else if ([test isEqualToString:@"qzerty"]) {
- layout = LATIN_KEYBOARD_QZERTY;
- } else if ([test isEqualToString:@"',.pyf"]) {
- layout = LATIN_KEYBOARD_DVORAK;
- } else if ([test isEqualToString:@"xvlcwk"]) {
- layout = LATIN_KEYBOARD_NEO;
- } else if ([test isEqualToString:@"qwfpgj"]) {
- layout = LATIN_KEYBOARD_COLEMAK;
- }
-
- [test release];
-
- keyboard_layout_dirty = false;
- return layout;
- }
-
- return layout;
-}
-
-void OS_OSX::process_events() {
-
- while (true) {
- NSEvent *event = [NSApp
- nextEventMatchingMask:NSEventMaskAny
- untilDate:[NSDate distantPast]
- inMode:NSDefaultRunLoopMode
- dequeue:YES];
-
- if (event == nil)
- break;
-
- [NSApp sendEvent:event];
- }
- process_key_events();
-
- [autoreleasePool drain];
- autoreleasePool = [[NSAutoreleasePool alloc] init];
-
- input->flush_accumulated_events();
-}
-
-void OS_OSX::process_key_events() {
-
- Ref<InputEventKey> k;
- for (int i = 0; i < key_event_pos; i++) {
-
- const KeyEvent &ke = key_event_buffer[i];
-
- if (ke.raw) {
- // Non IME input - no composite characters, pass events as is
- k.instance();
-
- get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_scancode(ke.scancode);
- k->set_unicode(ke.unicode);
-
- push_input(k);
- } else {
- // IME input
- if ((i == 0 && ke.scancode == 0) || (i > 0 && key_event_buffer[i - 1].scancode == 0)) {
- k.instance();
-
- get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_scancode(0);
- k->set_unicode(ke.unicode);
-
- push_input(k);
- }
- if (ke.scancode != 0) {
- k.instance();
-
- get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_scancode(ke.scancode);
-
- if (i + 1 < key_event_pos && key_event_buffer[i + 1].scancode == 0) {
- k->set_unicode(key_event_buffer[i + 1].unicode);
- }
-
- push_input(k);
- }
- }
- }
-
- key_event_pos = 0;
-}
-
-void OS_OSX::push_input(const Ref<InputEvent> &p_event) {
-
- Ref<InputEvent> ev = p_event;
- input->accumulate_input_event(ev);
-}
-
-void OS_OSX::force_process_input() {
-
- process_events(); // get rid of pending events
- joypad_osx->process_joypads();
-}
-
void OS_OSX::run() {
-
force_quit = false;
if (!main_loop)
@@ -2908,218 +314,50 @@ void OS_OSX::run() {
main_loop->init();
- if (zoomed) {
- zoomed = false;
- set_window_fullscreen(true);
- }
-
- //uint64_t last_ticks=get_ticks_usec();
-
- //int frames=0;
- //uint64_t frame=0;
-
bool quit = false;
-
while (!force_quit && !quit) {
-
@try {
-
- process_events(); // get rid of pending events
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events(); // get rid of pending events
+ }
joypad_osx->process_joypads();
- if (Main::iteration() == true) {
+ if (Main::iteration()) {
quit = true;
}
} @catch (NSException *exception) {
- ERR_PRINTS("NSException: " + String([exception reason].UTF8String));
+ ERR_PRINT("NSException: " + String([exception reason].UTF8String));
}
};
-
main_loop->finish();
}
-void OS_OSX::set_mouse_mode(MouseMode p_mode) {
-
- if (p_mode == mouse_mode)
- return;
-
- if (p_mode == MOUSE_MODE_CAPTURED) {
- // Apple Docs state that the display parameter is not used.
- // "This parameter is not used. By default, you may pass kCGDirectMainDisplay."
- // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html
- CGDisplayHideCursor(kCGDirectMainDisplay);
- CGAssociateMouseAndMouseCursorPosition(false);
- } else if (p_mode == MOUSE_MODE_HIDDEN) {
- CGDisplayHideCursor(kCGDirectMainDisplay);
- CGAssociateMouseAndMouseCursorPosition(true);
- } else {
- CGDisplayShowCursor(kCGDirectMainDisplay);
- CGAssociateMouseAndMouseCursorPosition(true);
- }
-
- mouse_mode = p_mode;
-}
-
-OS::MouseMode OS_OSX::get_mouse_mode() const {
-
- return mouse_mode;
-}
-
-String OS_OSX::get_joy_guid(int p_device) const {
- return input->get_joy_guid_remapped(p_device);
-}
-
-OS::PowerState OS_OSX::get_power_state() {
- return power_manager->get_power_state();
-}
-
-int OS_OSX::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
-}
-
-int OS_OSX::get_power_percent_left() {
- return power_manager->get_power_percent_left();
-}
-
Error OS_OSX::move_to_trash(const String &p_path) {
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
NSError *err;
if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) {
- ERR_PRINTS("trashItemAtURL error: " + String(err.localizedDescription.UTF8String));
+ ERR_PRINT("trashItemAtURL error: " + String(err.localizedDescription.UTF8String));
return FAILED;
}
return OK;
}
-void OS_OSX::_set_use_vsync(bool p_enable) {
- CGLContextObj ctx = CGLGetCurrentContext();
- if (ctx) {
- GLint swapInterval = p_enable ? 1 : 0;
- CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval);
- }
-}
-
-OS_OSX *OS_OSX::singleton = NULL;
-
OS_OSX::OS_OSX() {
-
- memset(cursors, 0, sizeof(cursors));
- key_event_pos = 0;
- mouse_mode = OS::MOUSE_MODE_VISIBLE;
main_loop = NULL;
- singleton = this;
- im_active = false;
- im_position = Point2();
- layered_window = false;
- autoreleasePool = [[NSAutoreleasePool alloc] init];
-
- eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
- ERR_FAIL_COND(!eventSource);
-
- CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0);
-
- /*
- if (pthread_key_create(&_Godot.nsgl.current, NULL) != 0) {
- _GodotInputError(Godot_PLATFORM_ERROR, "NSGL: Failed to create context TLS");
- return GL_FALSE;
- }
-*/
-
- framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
- ERR_FAIL_COND(!framework);
-
- // Implicitly create shared NSApplication instance
- [GodotApplication sharedApplication];
-
- // In case we are unbundled, make us a proper UI application
- [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
-
- // Menu bar setup must go between sharedApplication above and
- // finishLaunching below, in order to properly emulate the behavior
- // of NSApplicationMain
- NSMenuItem *menu_item;
- NSString *title;
-
- NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
- if (nsappname == nil)
- nsappname = [[NSProcessInfo processInfo] processName];
-
- // Setup Apple menu
- NSMenu *apple_menu = [[NSMenu alloc] initWithTitle:@""];
- title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
-
- [apple_menu addItem:[NSMenuItem separatorItem]];
-
- NSMenu *services = [[NSMenu alloc] initWithTitle:@""];
- menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
- [apple_menu setSubmenu:services forItem:menu_item];
- [NSApp setServicesMenu:services];
- [services release];
-
- [apple_menu addItem:[NSMenuItem separatorItem]];
-
- title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
-
- menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
- [menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)];
-
- [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""];
-
- [apple_menu addItem:[NSMenuItem separatorItem]];
-
- title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
-
- // Setup menu bar
- NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
- menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
- [main_menu setSubmenu:apple_menu forItem:menu_item];
- [NSApp setMainMenu:main_menu];
-
- [main_menu release];
- [apple_menu release];
-
- [NSApp finishLaunching];
-
- delegate = [[GodotApplicationDelegate alloc] init];
- ERR_FAIL_COND(!delegate);
- [NSApp setDelegate:delegate];
-
- cursor_shape = CURSOR_ARROW;
-
- maximized = false;
- minimized = false;
- window_size = Vector2(1024, 600);
- zoomed = false;
- resizable = false;
- window_focused = true;
+ force_quit = false;
Vector<Logger *> loggers;
loggers.push_back(memnew(OSXTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
- //process application:openFile: event
- while (true) {
- NSEvent *event = [NSApp
- nextEventMatchingMask:NSEventMaskAny
- untilDate:[NSDate distantPast]
- inMode:NSDefaultRunLoopMode
- dequeue:YES];
-
- if (event == nil)
- break;
-
- [NSApp sendEvent:event];
- }
-
#ifdef COREAUDIO_ENABLED
AudioDriverManager::add_driver(&audio_driver);
#endif
+
+ DisplayServerOSX::register_osx_driver();
}
bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h
index 046512ae84..e657aca955 100644
--- a/platform/osx/platform_config.h
+++ b/platform/osx/platform_config.h
@@ -30,6 +30,4 @@
#include <alloca.h>
-#define GLES3_INCLUDE_H "thirdparty/glad/glad/glad.h"
-#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"
#define PTHREAD_RENAME_SELF
diff --git a/platform/osx/platform_osx_builders.py b/platform/osx/platform_osx_builders.py
index 81997f674b..953ed479db 100644
--- a/platform/osx/platform_osx_builders.py
+++ b/platform/osx/platform_osx_builders.py
@@ -8,14 +8,14 @@ from platform_methods import subprocess_main
def make_debug_osx(target, source, env):
- if (env["macports_clang"] != 'no'):
+ if env["macports_clang"] != "no":
mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local")
mpclangver = env["macports_clang"]
- os.system(mpprefix + '/libexec/llvm-' + mpclangver + '/bin/llvm-dsymutil {0} -o {0}.dSYM'.format(target[0]))
+ os.system(mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-dsymutil {0} -o {0}.dSYM".format(target[0]))
else:
- os.system('dsymutil {0} -o {0}.dSYM'.format(target[0]))
- os.system('strip -u -r {0}'.format(target[0]))
+ os.system("dsymutil {0} -o {0}.dSYM".format(target[0]))
+ os.system("strip -u -r {0}".format(target[0]))
-if __name__ == '__main__':
+if __name__ == "__main__":
subprocess_main(globals())
diff --git a/platform/osx/power_osx.cpp b/platform/osx/power_osx.cpp
deleted file mode 100644
index 6d7667c5e8..0000000000
--- a/platform/osx/power_osx.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
-/*************************************************************************/
-/* power_osx.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-/*
-Adapted from corresponding SDL 2.0 code.
-*/
-
-/*
- Simple DirectMedia Layer
- Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
-*/
-
-#include "power_osx.h"
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <IOKit/ps/IOPSKeys.h>
-#include <IOKit/ps/IOPowerSources.h>
-
-// CODE CHUNK IMPORTED FROM SDL 2.0
-
-/* CoreFoundation is so verbose... */
-#define STRMATCH(a, b) (CFStringCompare(a, b, 0) == kCFCompareEqualTo)
-#define GETVAL(k, v) \
- CFDictionaryGetValueIfPresent(dict, CFSTR(k), (const void **)v)
-
-/* Note that AC power sources also include a laptop battery it is charging. */
-void PowerOSX::checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging) {
- CFStringRef strval; /* don't CFRelease() this. */
- CFBooleanRef bval;
- CFNumberRef numval;
- bool charge = false;
- bool choose = false;
- bool is_ac = false;
- int secs = -1;
- int maxpct = -1;
- int pct = -1;
-
- if ((GETVAL(kIOPSIsPresentKey, &bval)) && (bval == kCFBooleanFalse)) {
- return; /* nothing to see here. */
- }
-
- if (!GETVAL(kIOPSPowerSourceStateKey, &strval)) {
- return;
- }
-
- if (STRMATCH(strval, CFSTR(kIOPSACPowerValue))) {
- is_ac = *have_ac = true;
- } else if (!STRMATCH(strval, CFSTR(kIOPSBatteryPowerValue))) {
- return; /* not a battery? */
- }
-
- if ((GETVAL(kIOPSIsChargingKey, &bval)) && (bval == kCFBooleanTrue)) {
- charge = true;
- }
-
- if (GETVAL(kIOPSMaxCapacityKey, &numval)) {
- SInt32 val = -1;
- CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
- if (val > 0) {
- *have_battery = true;
- maxpct = (int)val;
- }
- }
-
- if (GETVAL(kIOPSMaxCapacityKey, &numval)) {
- SInt32 val = -1;
- CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
- if (val > 0) {
- *have_battery = true;
- maxpct = (int)val;
- }
- }
-
- if (GETVAL(kIOPSTimeToEmptyKey, &numval)) {
- SInt32 val = -1;
- CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
-
- /* Mac OS X reports 0 minutes until empty if you're plugged in. :( */
- if ((val == 0) && (is_ac)) {
- val = -1; /* !!! FIXME: calc from timeToFull and capacity? */
- }
-
- secs = (int)val;
- if (secs > 0) {
- secs *= 60; /* value is in minutes, so convert to seconds. */
- }
- }
-
- if (GETVAL(kIOPSCurrentCapacityKey, &numval)) {
- SInt32 val = -1;
- CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
- pct = (int)val;
- }
-
- if ((pct > 0) && (maxpct > 0)) {
- pct = (int)((((double)pct) / ((double)maxpct)) * 100.0);
- }
-
- if (pct > 100) {
- pct = 100;
- }
-
- /*
- * We pick the battery that claims to have the most minutes left.
- * (failing a report of minutes, we'll take the highest percent.)
- */
- if ((secs < 0) && (nsecs_left < 0)) {
- if ((pct < 0) && (percent_left < 0)) {
- choose = true; /* at least we know there's a battery. */
- }
- if (pct > percent_left) {
- choose = true;
- }
- } else if (secs > nsecs_left) {
- choose = true;
- }
-
- if (choose) {
- nsecs_left = secs;
- percent_left = pct;
- *charging = charge;
- }
-}
-
-#undef GETVAL
-#undef STRMATCH
-
-// CODE CHUNK IMPORTED FROM SDL 2.0
-bool PowerOSX::GetPowerInfo_MacOSX() {
- CFTypeRef blob = IOPSCopyPowerSourcesInfo();
-
- nsecs_left = -1;
- percent_left = -1;
- power_state = OS::POWERSTATE_UNKNOWN;
-
- if (blob != NULL) {
- CFArrayRef list = IOPSCopyPowerSourcesList(blob);
- if (list != NULL) {
- /* don't CFRelease() the list items, or dictionaries! */
- bool have_ac = false;
- bool have_battery = false;
- bool charging = false;
- const CFIndex total = CFArrayGetCount(list);
- CFIndex i;
- for (i = 0; i < total; i++) {
- CFTypeRef ps = (CFTypeRef)CFArrayGetValueAtIndex(list, i);
- CFDictionaryRef dict = IOPSGetPowerSourceDescription(blob, ps);
- if (dict != NULL) {
- checkps(dict, &have_ac, &have_battery, &charging);
- }
- }
-
- if (!have_battery) {
- power_state = OS::POWERSTATE_NO_BATTERY;
- } else if (charging) {
- power_state = OS::POWERSTATE_CHARGING;
- } else if (have_ac) {
- power_state = OS::POWERSTATE_CHARGED;
- } else {
- power_state = OS::POWERSTATE_ON_BATTERY;
- }
-
- CFRelease(list);
- }
- CFRelease(blob);
- }
-
- return true; /* always the definitive answer on Mac OS X. */
-}
-
-bool PowerOSX::UpdatePowerInfo() {
- if (GetPowerInfo_MacOSX()) {
- return true;
- }
- return false;
-}
-
-OS::PowerState PowerOSX::get_power_state() {
- if (UpdatePowerInfo()) {
- return power_state;
- } else {
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerOSX::get_power_seconds_left() {
- if (UpdatePowerInfo()) {
- return nsecs_left;
- } else {
- return -1;
- }
-}
-
-int PowerOSX::get_power_percent_left() {
- if (UpdatePowerInfo()) {
- return percent_left;
- } else {
- return -1;
- }
-}
-
-PowerOSX::PowerOSX() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerOSX::~PowerOSX() {
-}
diff --git a/platform/osx/power_osx.h b/platform/osx/power_osx.h
deleted file mode 100644
index 6f9b213439..0000000000
--- a/platform/osx/power_osx.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*************************************************************************/
-/* power_osx.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef POWER_OSX_H
-#define POWER_OSX_H
-
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "dir_access_osx.h"
-
-#include <CoreFoundation/CoreFoundation.h>
-
-class PowerOSX {
-
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
- void checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging);
- bool GetPowerInfo_MacOSX(/*PowerState * state, int *seconds, int *percent*/);
- bool UpdatePowerInfo();
-
-public:
- PowerOSX();
- virtual ~PowerOSX();
-
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
-};
-
-#endif // POWER_OSX_H
diff --git a/platform/osx/semaphore_osx.cpp b/platform/osx/semaphore_osx.cpp
deleted file mode 100644
index e75f5103cc..0000000000
--- a/platform/osx/semaphore_osx.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*************************************************************************/
-/* semaphore_osx.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "semaphore_osx.h"
-
-#include <fcntl.h>
-#include <unistd.h>
-
-void cgsem_init(cgsem_t *cgsem) {
- int flags, fd, i;
-
- pipe(cgsem->pipefd);
-
- /* Make the pipes FD_CLOEXEC to allow them to close should we call
- * execv on restart. */
- for (i = 0; i < 2; i++) {
- fd = cgsem->pipefd[i];
- flags = fcntl(fd, F_GETFD, 0);
- flags |= FD_CLOEXEC;
- fcntl(fd, F_SETFD, flags);
- }
-}
-
-void cgsem_post(cgsem_t *cgsem) {
- const char buf = 1;
-
- write(cgsem->pipefd[1], &buf, 1);
-}
-
-void cgsem_wait(cgsem_t *cgsem) {
- char buf;
-
- read(cgsem->pipefd[0], &buf, 1);
-}
-
-void cgsem_destroy(cgsem_t *cgsem) {
- close(cgsem->pipefd[1]);
- close(cgsem->pipefd[0]);
-}
-
-#include "core/os/memory.h"
-
-#include <errno.h>
-
-Error SemaphoreOSX::wait() {
-
- cgsem_wait(&sem);
- return OK;
-}
-
-Error SemaphoreOSX::post() {
-
- cgsem_post(&sem);
-
- return OK;
-}
-int SemaphoreOSX::get() const {
-
- return 0;
-}
-
-Semaphore *SemaphoreOSX::create_semaphore_osx() {
-
- return memnew(SemaphoreOSX);
-}
-
-void SemaphoreOSX::make_default() {
-
- create_func = create_semaphore_osx;
-}
-
-SemaphoreOSX::SemaphoreOSX() {
-
- cgsem_init(&sem);
-}
-
-SemaphoreOSX::~SemaphoreOSX() {
-
- cgsem_destroy(&sem);
-}
diff --git a/platform/uwp/power_uwp.h b/platform/osx/vulkan_context_osx.h
index 5e28cf65e5..e996f176a9 100644
--- a/platform/uwp/power_uwp.h
+++ b/platform/osx/vulkan_context_osx.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* power_uwp.h */
+/* vulkan_context_osx.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef POWER_UWP_H
-#define POWER_UWP_H
+#ifndef VULKAN_DEVICE_OSX_H
+#define VULKAN_DEVICE_OSX_H
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
+#include "drivers/vulkan/vulkan_context.h"
+#include <AppKit/AppKit.h>
-class PowerUWP {
-
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
-
- bool UpdatePowerInfo();
+class VulkanContextOSX : public VulkanContext {
+ virtual const char *_get_platform_surface_extension() const;
public:
- PowerUWP();
- virtual ~PowerUWP();
+ Error window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height);
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
+ VulkanContextOSX();
+ ~VulkanContextOSX();
};
-#endif // POWER_UWP_H
+#endif // VULKAN_DEVICE_OSX_H
diff --git a/platform/iphone/power_iphone.cpp b/platform/osx/vulkan_context_osx.mm
index 36bac8da38..ec8745ff01 100644
--- a/platform/iphone/power_iphone.cpp
+++ b/platform/osx/vulkan_context_osx.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* power_iphone.cpp */
+/* vulkan_context_osx.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,43 +28,28 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "power_iphone.h"
+#include "vulkan_context_osx.h"
+#include <vulkan/vulkan_macos.h>
-bool PowerIphone::UpdatePowerInfo() {
- return false;
+const char *VulkanContextOSX::_get_platform_surface_extension() const {
+ return VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
}
-OS::PowerState PowerIphone::get_power_state() {
- if (UpdatePowerInfo()) {
- return power_state;
- } else {
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerIphone::get_power_seconds_left() {
- if (UpdatePowerInfo()) {
- return nsecs_left;
- } else {
- return -1;
- }
-}
+Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height) {
+ VkMacOSSurfaceCreateInfoMVK createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.pView = p_window;
-int PowerIphone::get_power_percent_left() {
- if (UpdatePowerInfo()) {
- return percent_left;
- } else {
- return -1;
- }
+ VkSurfaceKHR surface;
+ VkResult err = vkCreateMacOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+ return _window_create(p_window_id, surface, p_width, p_height);
}
-PowerIphone::PowerIphone() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
- // TODO Auto-generated constructor stub
+VulkanContextOSX::VulkanContextOSX() {
}
-PowerIphone::~PowerIphone() {
- // TODO Auto-generated destructor stub
+VulkanContextOSX::~VulkanContextOSX() {
}
diff --git a/platform/server/SCsub b/platform/server/SCsub
index f977275595..15b9af4d25 100644
--- a/platform/server/SCsub
+++ b/platform/server/SCsub
@@ -2,18 +2,15 @@
import sys
-Import('env')
+Import("env")
-common_server = [\
- "os_server.cpp",\
+common_server = [
+ "os_server.cpp",
]
if sys.platform == "darwin":
- common_server.append("#platform/osx/crash_handler_osx.mm")
- common_server.append("#platform/osx/power_osx.cpp")
- common_server.append("#platform/osx/semaphore_osx.cpp")
+ common_server.append("#platform/osx/crash_handler_osx.mm")
else:
- common_server.append("#platform/x11/crash_handler_x11.cpp")
- common_server.append("#platform/x11/power_x11.cpp")
+ common_server.append("#platform/x11/crash_handler_x11.cpp")
-prog = env.add_program('#bin/godot_server', ['godot_server.cpp'] + common_server)
+prog = env.add_program("#bin/godot_server", ["godot_server.cpp"] + common_server)
diff --git a/platform/server/detect.py b/platform/server/detect.py
index d82df77957..4c5a4527ae 100644
--- a/platform/server/detect.py
+++ b/platform/server/detect.py
@@ -5,6 +5,7 @@ import sys
# This file is mostly based on platform/x11/detect.py.
# If editing this file, make sure to apply relevant changes here too.
+
def is_active():
return True
@@ -14,14 +15,14 @@ def get_name():
def get_program_suffix():
- if (sys.platform == "darwin"):
+ if sys.platform == "darwin":
return "osx"
- return "x11"
+ return "linuxbsd"
def can_build():
- if (os.name != "posix"):
+ if os.name != "posix":
return False
return True
@@ -29,16 +30,18 @@ def can_build():
def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
+
return [
- BoolVariable('use_llvm', 'Use the LLVM compiler', False),
- BoolVariable('use_static_cpp', 'Link libgcc and libstdc++ statically for better portability', False),
- BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False),
- BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False),
- BoolVariable('use_lsan', 'Use LLVM/GCC compiler leak sanitizer (LSAN))', False),
- BoolVariable('use_tsan', 'Use LLVM/GCC compiler thread sanitizer (TSAN))', False),
- EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')),
- BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False),
- BoolVariable('execinfo', 'Use libexecinfo on systems where glibc is not available', False),
+ BoolVariable("use_llvm", "Use the LLVM compiler", False),
+ BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", False),
+ BoolVariable("use_coverage", "Test Godot coverage", False),
+ BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False),
+ BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False),
+ BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False),
+ BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False),
+ EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
+ BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
+ BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
]
@@ -51,86 +54,89 @@ def configure(env):
## Build type
- if (env["target"] == "release"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O3'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "release_debug"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "debug"):
- env.Prepend(CCFLAGS=['-g3'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
- env.Append(LINKFLAGS=['-rdynamic'])
+ if env["target"] == "release":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O3"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "release_debug":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Prepend(CCFLAGS=["-O2"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "debug":
+ env.Prepend(CCFLAGS=["-g3"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["-rdynamic"])
## Architecture
- is64 = sys.maxsize > 2**32
- if (env["bits"] == "default"):
+ is64 = sys.maxsize > 2 ** 32
+ if env["bits"] == "default":
env["bits"] = "64" if is64 else "32"
## Compiler configuration
- if 'CXX' in env and 'clang' in os.path.basename(env['CXX']):
+ if "CXX" in env and "clang" in os.path.basename(env["CXX"]):
# Convenience check to enforce the use_llvm overrides when CXX is clang(++)
- env['use_llvm'] = True
+ env["use_llvm"] = True
- if env['use_llvm']:
- if ('clang++' not in os.path.basename(env['CXX'])):
+ if env["use_llvm"]:
+ if "clang++" not in os.path.basename(env["CXX"]):
env["CC"] = "clang"
env["CXX"] = "clang++"
env["LINK"] = "clang++"
- env.Append(CPPDEFINES=['TYPED_METHOD_BIND'])
+ env.Append(CPPDEFINES=["TYPED_METHOD_BIND"])
env.extra_suffix = ".llvm" + env.extra_suffix
+ if env["use_coverage"]:
+ env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"])
+ env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"])
- if env['use_ubsan'] or env['use_asan'] or env['use_lsan'] or env['use_tsan']:
+ if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]:
env.extra_suffix += "s"
- if env['use_ubsan']:
- env.Append(CCFLAGS=['-fsanitize=undefined'])
- env.Append(LINKFLAGS=['-fsanitize=undefined'])
+ if env["use_ubsan"]:
+ env.Append(CCFLAGS=["-fsanitize=undefined"])
+ env.Append(LINKFLAGS=["-fsanitize=undefined"])
- if env['use_asan']:
- env.Append(CCFLAGS=['-fsanitize=address'])
- env.Append(LINKFLAGS=['-fsanitize=address'])
+ if env["use_asan"]:
+ env.Append(CCFLAGS=["-fsanitize=address"])
+ env.Append(LINKFLAGS=["-fsanitize=address"])
- if env['use_lsan']:
- env.Append(CCFLAGS=['-fsanitize=leak'])
- env.Append(LINKFLAGS=['-fsanitize=leak'])
+ if env["use_lsan"]:
+ env.Append(CCFLAGS=["-fsanitize=leak"])
+ env.Append(LINKFLAGS=["-fsanitize=leak"])
- if env['use_tsan']:
- env.Append(CCFLAGS=['-fsanitize=thread'])
- env.Append(LINKFLAGS=['-fsanitize=thread'])
+ if env["use_tsan"]:
+ env.Append(CCFLAGS=["-fsanitize=thread"])
+ env.Append(LINKFLAGS=["-fsanitize=thread"])
- if env['use_lto']:
- env.Append(CCFLAGS=['-flto'])
- if not env['use_llvm'] and env.GetOption("num_jobs") > 1:
- env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))])
+ if env["use_lto"]:
+ env.Append(CCFLAGS=["-flto"])
+ if not env["use_llvm"] and env.GetOption("num_jobs") > 1:
+ env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))])
else:
- env.Append(LINKFLAGS=['-flto'])
- if not env['use_llvm']:
- env['RANLIB'] = 'gcc-ranlib'
- env['AR'] = 'gcc-ar'
+ env.Append(LINKFLAGS=["-flto"])
+ if not env["use_llvm"]:
+ env["RANLIB"] = "gcc-ranlib"
+ env["AR"] = "gcc-ar"
- env.Append(CCFLAGS=['-pipe'])
- env.Append(LINKFLAGS=['-pipe'])
+ env.Append(CCFLAGS=["-pipe"])
+ env.Append(LINKFLAGS=["-pipe"])
## Dependencies
@@ -138,105 +144,114 @@ def configure(env):
# freetype depends on libpng and zlib, so bundling one of them while keeping others
# as shared libraries leads to weird issues
- if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']:
- env['builtin_freetype'] = True
- env['builtin_libpng'] = True
- env['builtin_zlib'] = True
+ if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]:
+ env["builtin_freetype"] = True
+ env["builtin_libpng"] = True
+ env["builtin_zlib"] = True
- if not env['builtin_freetype']:
- env.ParseConfig('pkg-config freetype2 --cflags --libs')
+ if not env["builtin_freetype"]:
+ env.ParseConfig("pkg-config freetype2 --cflags --libs")
- if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng16 --cflags --libs')
+ if not env["builtin_libpng"]:
+ env.ParseConfig("pkg-config libpng16 --cflags --libs")
- if not env['builtin_bullet']:
+ if not env["builtin_bullet"]:
# We need at least version 2.89
import subprocess
- bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip()
+
+ bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip()
if str(bullet_version) < "2.89":
# Abort as system bullet was requested but too old
- print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.89"))
+ print(
+ "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(
+ bullet_version, "2.89"
+ )
+ )
sys.exit(255)
- env.ParseConfig('pkg-config bullet --cflags --libs')
+ env.ParseConfig("pkg-config bullet --cflags --libs")
+
+ if False: # not env['builtin_assimp']:
+ # FIXME: Add min version check
+ env.ParseConfig("pkg-config assimp --cflags --libs")
- if not env['builtin_enet']:
- env.ParseConfig('pkg-config libenet --cflags --libs')
+ if not env["builtin_enet"]:
+ env.ParseConfig("pkg-config libenet --cflags --libs")
- if not env['builtin_squish']:
- env.ParseConfig('pkg-config libsquish --cflags --libs')
+ if not env["builtin_squish"]:
+ env.ParseConfig("pkg-config libsquish --cflags --libs")
- if not env['builtin_zstd']:
- env.ParseConfig('pkg-config libzstd --cflags --libs')
+ if not env["builtin_zstd"]:
+ env.ParseConfig("pkg-config libzstd --cflags --libs")
# Sound and video libraries
# Keep the order as it triggers chained dependencies (ogg needed by others, etc.)
- if not env['builtin_libtheora']:
- env['builtin_libogg'] = False # Needed to link against system libtheora
- env['builtin_libvorbis'] = False # Needed to link against system libtheora
- env.ParseConfig('pkg-config theora theoradec --cflags --libs')
+ if not env["builtin_libtheora"]:
+ env["builtin_libogg"] = False # Needed to link against system libtheora
+ env["builtin_libvorbis"] = False # Needed to link against system libtheora
+ env.ParseConfig("pkg-config theora theoradec --cflags --libs")
else:
- list_of_x86 = ['x86_64', 'x86', 'i386', 'i586']
+ list_of_x86 = ["x86_64", "x86", "i386", "i586"]
if any(platform.machine() in s for s in list_of_x86):
env["x86_libtheora_opt_gcc"] = True
- if not env['builtin_libvpx']:
- env.ParseConfig('pkg-config vpx --cflags --libs')
+ if not env["builtin_libvpx"]:
+ env.ParseConfig("pkg-config vpx --cflags --libs")
- if not env['builtin_libvorbis']:
- env['builtin_libogg'] = False # Needed to link against system libvorbis
- env.ParseConfig('pkg-config vorbis vorbisfile --cflags --libs')
+ if not env["builtin_libvorbis"]:
+ env["builtin_libogg"] = False # Needed to link against system libvorbis
+ env.ParseConfig("pkg-config vorbis vorbisfile --cflags --libs")
- if not env['builtin_opus']:
- env['builtin_libogg'] = False # Needed to link against system opus
- env.ParseConfig('pkg-config opus opusfile --cflags --libs')
+ if not env["builtin_opus"]:
+ env["builtin_libogg"] = False # Needed to link against system opus
+ env.ParseConfig("pkg-config opus opusfile --cflags --libs")
- if not env['builtin_libogg']:
- env.ParseConfig('pkg-config ogg --cflags --libs')
+ if not env["builtin_libogg"]:
+ env.ParseConfig("pkg-config ogg --cflags --libs")
- if not env['builtin_libwebp']:
- env.ParseConfig('pkg-config libwebp --cflags --libs')
+ if not env["builtin_libwebp"]:
+ env.ParseConfig("pkg-config libwebp --cflags --libs")
- if not env['builtin_mbedtls']:
+ if not env["builtin_mbedtls"]:
# mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228
- env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509'])
+ env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"])
- if not env['builtin_wslay']:
- env.ParseConfig('pkg-config libwslay --cflags --libs')
+ if not env["builtin_wslay"]:
+ env.ParseConfig("pkg-config libwslay --cflags --libs")
- if not env['builtin_miniupnpc']:
+ if not env["builtin_miniupnpc"]:
# No pkgconfig file so far, hardcode default paths.
env.Prepend(CPPPATH=["/usr/include/miniupnpc"])
env.Append(LIBS=["miniupnpc"])
# On Linux wchar_t should be 32-bits
# 16-bit library shouldn't be required due to compiler optimisations
- if not env['builtin_pcre2']:
- env.ParseConfig('pkg-config libpcre2-32 --cflags --libs')
+ if not env["builtin_pcre2"]:
+ env.ParseConfig("pkg-config libpcre2-32 --cflags --libs")
## Flags
# Linkflags below this line should typically stay the last ones
- if not env['builtin_zlib']:
- env.ParseConfig('pkg-config zlib --cflags --libs')
+ if not env["builtin_zlib"]:
+ env.ParseConfig("pkg-config zlib --cflags --libs")
- env.Prepend(CPPPATH=['#platform/server'])
- env.Append(CPPDEFINES=['SERVER_ENABLED', 'UNIX_ENABLED'])
+ env.Prepend(CPPPATH=["#platform/server"])
+ env.Append(CPPDEFINES=["SERVER_ENABLED", "UNIX_ENABLED"])
- if (platform.system() == "Darwin"):
- env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-lz', '-framework', 'IOKit'])
+ if platform.system() == "Darwin":
+ env.Append(LINKFLAGS=["-framework", "Cocoa", "-framework", "Carbon", "-lz", "-framework", "IOKit"])
- env.Append(LIBS=['pthread'])
+ env.Append(LIBS=["pthread"])
- if (platform.system() == "Linux"):
- env.Append(LIBS=['dl'])
+ if platform.system() == "Linux":
+ env.Append(LIBS=["dl"])
- if (platform.system().find("BSD") >= 0):
+ if platform.system().find("BSD") >= 0:
env["execinfo"] = True
if env["execinfo"]:
- env.Append(LIBS=['execinfo'])
+ env.Append(LIBS=["execinfo"])
# Link those statically for portability
- if env['use_static_cpp']:
- env.Append(LINKFLAGS=['-static-libgcc', '-static-libstdc++'])
+ if env["use_static_cpp"]:
+ env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"])
diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp
index df49bfaebf..9f22240a80 100644
--- a/platform/server/godot_server.cpp
+++ b/platform/server/godot_server.cpp
@@ -32,9 +32,11 @@
#include "os_server.h"
int main(int argc, char *argv[]) {
-
OS_Server os;
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
Error err = Main::setup(argv[0], argc - 1, &argv[1]);
if (err != OK)
return 255;
diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp
index 498fd01b5e..fbe526ef6d 100644
--- a/platform/server/os_server.cpp
+++ b/platform/server/os_server.cpp
@@ -31,10 +31,9 @@
#include "os_server.h"
#include "core/print_string.h"
-#include "drivers/dummy/audio_driver_dummy.h"
#include "drivers/dummy/rasterizer_dummy.h"
#include "drivers/dummy/texture_loader_dummy.h"
-#include "servers/visual/visual_server_raster.h"
+#include "servers/rendering/rendering_server_raster.h"
#include "main/main.h"
@@ -43,11 +42,10 @@
#include <unistd.h>
int OS_Server::get_video_driver_count() const {
-
return 1;
}
-const char *OS_Server::get_video_driver_name(int p_driver) const {
+const char *OS_Server::get_video_driver_name(int p_driver) const {
return "Dummy";
}
@@ -56,7 +54,6 @@ int OS_Server::get_audio_driver_count() const {
}
const char *OS_Server::get_audio_driver_name(int p_driver) const {
-
return "Dummy";
}
@@ -65,39 +62,27 @@ int OS_Server::get_current_video_driver() const {
}
void OS_Server::initialize_core() {
-
crash_handler.initialize();
OS_Unix::initialize_core();
-
-#ifdef __APPLE__
- SemaphoreOSX::make_default();
-#endif
}
Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
args = OS::get_singleton()->get_cmdline_args();
current_videomode = p_desired;
- main_loop = NULL;
+ main_loop = nullptr;
RasterizerDummy::make_current();
video_driver_index = p_video_driver; // unused in server platform, but should still be initialized
- visual_server = memnew(VisualServerRaster);
- visual_server->init();
+ rendering_server = memnew(RenderingServerRaster);
+ rendering_server->init();
AudioDriverManager::initialize(p_audio_driver);
input = memnew(InputDefault);
-#ifdef __APPLE__
- power_manager = memnew(PowerOSX);
-#else
- power_manager = memnew(PowerX11);
-#endif
-
_ensure_user_data_dir();
resource_loader_dummy.instance();
@@ -107,18 +92,15 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int
}
void OS_Server::finalize() {
-
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
+ main_loop = nullptr;
- visual_server->finish();
- memdelete(visual_server);
+ rendering_server->finish();
+ memdelete(rendering_server);
memdelete(input);
- memdelete(power_manager);
-
ResourceLoader::remove_resource_format_loader(resource_loader_dummy);
resource_loader_dummy.unref();
@@ -129,22 +111,18 @@ void OS_Server::set_mouse_show(bool p_show) {
}
void OS_Server::set_mouse_grab(bool p_grab) {
-
grab = p_grab;
}
bool OS_Server::is_mouse_grab_enabled() const {
-
return grab;
}
int OS_Server::get_mouse_button_state() const {
-
return 0;
}
Point2 OS_Server::get_mouse_position() const {
-
return Point2();
}
@@ -155,12 +133,10 @@ void OS_Server::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
}
OS::VideoMode OS_Server::get_video_mode(int p_screen) const {
-
return current_videomode;
}
Size2 OS_Server::get_window_size() const {
-
return Vector2(current_videomode.width, current_videomode.height);
}
@@ -168,54 +144,36 @@ void OS_Server::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen)
}
MainLoop *OS_Server::get_main_loop() const {
-
return main_loop;
}
void OS_Server::delete_main_loop() {
-
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
+ main_loop = nullptr;
}
void OS_Server::set_main_loop(MainLoop *p_main_loop) {
-
main_loop = p_main_loop;
input->set_main_loop(p_main_loop);
}
bool OS_Server::can_draw() const {
-
return false; //can never draw
};
String OS_Server::get_name() const {
-
return "Server";
}
void OS_Server::move_window_to_foreground() {
}
-OS::PowerState OS_Server::get_power_state() {
- return power_manager->get_power_state();
-}
-
-int OS_Server::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
-}
-
-int OS_Server::get_power_percent_left() {
- return power_manager->get_power_percent_left();
-}
-
bool OS_Server::_check_internal_feature_support(const String &p_feature) {
return p_feature == "pc";
}
void OS_Server::run() {
-
force_quit = false;
if (!main_loop)
@@ -224,7 +182,6 @@ void OS_Server::run() {
main_loop->init();
while (!force_quit) {
-
if (Main::iteration())
break;
};
@@ -233,7 +190,6 @@ void OS_Server::run() {
}
String OS_Server::get_config_path() const {
-
if (has_environment("XDG_CONFIG_HOME")) {
return get_environment("XDG_CONFIG_HOME");
} else if (has_environment("HOME")) {
@@ -244,7 +200,6 @@ String OS_Server::get_config_path() const {
}
String OS_Server::get_data_path() const {
-
if (has_environment("XDG_DATA_HOME")) {
return get_environment("XDG_DATA_HOME");
} else if (has_environment("HOME")) {
@@ -255,7 +210,6 @@ String OS_Server::get_data_path() const {
}
String OS_Server::get_cache_path() const {
-
if (has_environment("XDG_CACHE_HOME")) {
return get_environment("XDG_CACHE_HOME");
} else if (has_environment("HOME")) {
@@ -266,46 +220,37 @@ String OS_Server::get_cache_path() const {
}
String OS_Server::get_system_dir(SystemDir p_dir) const {
-
String xdgparam;
switch (p_dir) {
case SYSTEM_DIR_DESKTOP: {
-
xdgparam = "DESKTOP";
} break;
case SYSTEM_DIR_DCIM: {
-
xdgparam = "PICTURES";
} break;
case SYSTEM_DIR_DOCUMENTS: {
-
xdgparam = "DOCUMENTS";
} break;
case SYSTEM_DIR_DOWNLOADS: {
-
xdgparam = "DOWNLOAD";
} break;
case SYSTEM_DIR_MOVIES: {
-
xdgparam = "VIDEOS";
} break;
case SYSTEM_DIR_MUSIC: {
-
xdgparam = "MUSIC";
} break;
case SYSTEM_DIR_PICTURES: {
-
xdgparam = "PICTURES";
} break;
case SYSTEM_DIR_RINGTONES: {
-
xdgparam = "MUSIC";
} break;
@@ -314,7 +259,7 @@ String OS_Server::get_system_dir(SystemDir p_dir) const {
String pipe;
List<String> arg;
arg.push_back(xdgparam);
- Error err = const_cast<OS_Server *>(this)->execute("xdg-user-dir", arg, true, NULL, &pipe);
+ Error err = const_cast<OS_Server *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe);
if (err != OK)
return ".";
return pipe.strip_edges();
@@ -329,7 +274,6 @@ bool OS_Server::is_disable_crash_handler() const {
}
OS_Server::OS_Server() {
-
//adriver here
grab = false;
};
diff --git a/platform/server/os_server.h b/platform/server/os_server.h
index 46ca9cb6d1..06ea483fd4 100644
--- a/platform/server/os_server.h
+++ b/platform/server/os_server.h
@@ -31,26 +31,23 @@
#ifndef OS_SERVER_H
#define OS_SERVER_H
+#include "core/input/input.h"
#include "drivers/dummy/texture_loader_dummy.h"
#include "drivers/unix/os_unix.h"
-#include "main/input_default.h"
#ifdef __APPLE__
#include "platform/osx/crash_handler_osx.h"
-#include "platform/osx/power_osx.h"
#include "platform/osx/semaphore_osx.h"
#else
-#include "platform/x11/crash_handler_x11.h"
-#include "platform/x11/power_x11.h"
+#include "platform/x11/crash_handler_linuxbsd.h"
#endif
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering_server.h"
#undef CursorShape
class OS_Server : public OS_Unix {
-
- VisualServer *visual_server;
+ RenderingServer *rendering_server;
VideoMode current_videomode;
List<String> args;
MainLoop *main_loop;
@@ -63,12 +60,6 @@ class OS_Server : public OS_Unix {
InputDefault *input;
-#ifdef __APPLE__
- PowerOSX *power_manager;
-#else
- PowerX11 *power_manager;
-#endif
-
CrashHandler crash_handler;
int video_driver_index;
@@ -112,9 +103,6 @@ public:
void run();
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
virtual bool _check_internal_feature_support(const String &p_feature);
virtual String get_config_path() const;
diff --git a/platform/server/platform_config.h b/platform/server/platform_config.h
index bdff93f02b..73136ec81b 100644
--- a/platform/server/platform_config.h
+++ b/platform/server/platform_config.h
@@ -31,10 +31,19 @@
#if defined(__linux__) || defined(__APPLE__)
#include <alloca.h>
#endif
-#if defined(__FreeBSD__) || defined(__OpenBSD__)
-#include <stdlib.h>
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+#include <stdlib.h> // alloca
+// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms,
+// include NetBSD, use pthread_setname_np. NetBSD's version however requires
+// a different format, we handle this directly in thread_posix.
+#ifdef __NetBSD__
+#define PTHREAD_NETBSD_SET_NAME
+#else
#define PTHREAD_BSD_SET_NAME
#endif
+#endif
+
#ifdef __APPLE__
#define PTHREAD_RENAME_SELF
#endif
diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub
index c14290f0c4..4358b0eead 100644
--- a/platform/uwp/SCsub
+++ b/platform/uwp/SCsub
@@ -1,22 +1,21 @@
#!/usr/bin/env python
-Import('env')
+Import("env")
files = [
- 'thread_uwp.cpp',
- '#platform/windows/key_mapping_windows.cpp',
- '#platform/windows/windows_terminal_logger.cpp',
- 'joypad_uwp.cpp',
- 'power_uwp.cpp',
- 'context_egl_uwp.cpp',
- 'app.cpp',
- 'os_uwp.cpp',
+ "thread_uwp.cpp",
+ "#platform/windows/key_mapping_windows.cpp",
+ "#platform/windows/windows_terminal_logger.cpp",
+ "joypad_uwp.cpp",
+ "context_egl_uwp.cpp",
+ "app.cpp",
+ "os_uwp.cpp",
]
if "build_angle" in env and env["build_angle"]:
- cmd = env.AlwaysBuild(env.ANGLE('libANGLE.lib', None))
+ cmd = env.AlwaysBuild(env.ANGLE("libANGLE.lib", None))
-prog = env.add_program('#bin/godot', files)
+prog = env.add_program("#bin/godot", files)
if "build_angle" in env and env["build_angle"]:
env.Depends(prog, [cmd])
diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp
index a47fe96c1b..6090d13854 100644
--- a/platform/uwp/app.cpp
+++ b/platform/uwp/app.cpp
@@ -78,16 +78,6 @@ public:
return 0;
}
-App::App() :
- mWindowClosed(false),
- mWindowVisible(true),
- mWindowWidth(0),
- mWindowHeight(0),
- mEglDisplay(EGL_NO_DISPLAY),
- mEglContext(EGL_NO_CONTEXT),
- mEglSurface(EGL_NO_SURFACE) {
-}
-
// The first method called when the IFrameworkView is being created.
void App::Initialize(CoreApplicationView ^ applicationView) {
// Register event handlers for app lifecycle. This example includes Activated, so that we
@@ -156,7 +146,6 @@ void App::SetWindow(CoreWindow ^ p_window) {
}
static int _get_button(Windows::UI::Input::PointerPoint ^ pt) {
-
using namespace Windows::UI::Input;
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
@@ -207,7 +196,6 @@ static bool _is_touch(Windows::UI::Input::PointerPoint ^ pointerPoint) {
}
static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windows::Foundation::Point rawPosition, OS *os) {
-
Windows::Foundation::Point outputPosition;
// Compute coordinates normalized from 0..1.
@@ -247,17 +235,14 @@ static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windo
};
static int _get_finger(uint32_t p_touch_id) {
-
return p_touch_id % 31; // for now
};
void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args, bool p_pressed, bool p_is_wheel) {
-
Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint;
Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os);
int but = _get_button(point);
if (_is_touch(point)) {
-
Ref<InputEventScreenTouch> screen_touch;
screen_touch.instance();
screen_touch->set_device(0);
@@ -270,7 +255,6 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor
os->input_event(screen_touch);
} else {
-
Ref<InputEventMouseButton> mouse_button;
mouse_button.instance();
mouse_button->set_device(0);
@@ -301,22 +285,18 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor
};
void App::OnPointerPressed(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
-
pointer_event(sender, args, true);
};
void App::OnPointerReleased(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
-
pointer_event(sender, args, false);
};
void App::OnPointerWheelChanged(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
-
pointer_event(sender, args, true, true);
}
void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^ signalNotifier, bool timedOut) {
-
OS::MouseMode mode = os->get_mouse_mode();
SignalNotifier ^ notifier = mouseChangedNotifier;
@@ -325,12 +305,10 @@ void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^
ref new DispatchedHandler(
[mode, notifier, this]() {
if (mode == OS::MOUSE_MODE_CAPTURED) {
-
this->MouseMovedToken = MouseDevice::GetForCurrentView()->MouseMoved +=
ref new TypedEventHandler<MouseDevice ^, MouseEventArgs ^>(this, &App::OnMouseMoved);
} else {
-
MouseDevice::GetForCurrentView()->MouseMoved -= MouseMovedToken;
}
@@ -341,12 +319,10 @@ void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^
}
void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
-
Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint;
Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os);
if (_is_touch(point)) {
-
Ref<InputEventScreenDrag> screen_drag;
screen_drag.instance();
screen_drag->set_device(0);
@@ -356,7 +332,6 @@ void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Co
os->input_event(screen_drag);
} else {
-
// In case the mouse grabbed, MouseMoved will handle this
if (os->get_mouse_mode() == OS::MouseMode::MOUSE_MODE_CAPTURED)
return;
@@ -375,7 +350,6 @@ void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Co
}
void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) {
-
// In case the mouse isn't grabbed, PointerMoved will handle this
if (os->get_mouse_mode() != OS::MouseMode::MOUSE_MODE_CAPTURED)
return;
@@ -397,7 +371,6 @@ void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) {
}
void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Windows::UI::Core::KeyEventArgs ^ key_args, Windows::UI::Core::CharacterReceivedEventArgs ^ char_args) {
-
OS_UWP::KeyEvent ke;
ke.control = sender->GetAsyncKeyState(VirtualKey::Control) == CoreVirtualKeyStates::Down;
@@ -407,22 +380,23 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind
ke.pressed = p_pressed;
if (key_args != nullptr) {
-
ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE;
ke.unicode = 0;
- ke.scancode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey);
+ ke.keycode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey);
+ ke.physical_keycode = KeyMappingWindows::get_scansym((unsigned int)key_args->KeyStatus.ScanCode);
ke.echo = (!p_pressed && !key_args->KeyStatus.IsKeyReleased) || (p_pressed && key_args->KeyStatus.WasKeyDown);
} else {
-
ke.type = OS_UWP::KeyEvent::MessageType::CHAR_EVENT_MESSAGE;
ke.unicode = char_args->KeyCode;
- ke.scancode = 0;
+ ke.keycode = 0;
+ ke.physical_keycode = 0;
ke.echo = (!p_pressed && !char_args->KeyStatus.IsKeyReleased) || (p_pressed && char_args->KeyStatus.WasKeyDown);
}
os->queue_key_event(ke);
}
+
void App::OnKeyDown(CoreWindow ^ sender, KeyEventArgs ^ args) {
key_event(sender, true, args);
}
@@ -504,14 +478,12 @@ void App::UpdateWindowSize(Size size) {
}
char **App::get_command_line(unsigned int *out_argc) {
-
- static char *fail_cl[] = { "--path", "game", NULL };
+ static char *fail_cl[] = { "--path", "game", nullptr };
*out_argc = 2;
FILE *f = _wfopen(L"__cl__.cl", L"rb");
- if (f == NULL) {
-
+ if (f == nullptr) {
wprintf(L"Couldn't open command line file.\n");
return fail_cl;
}
@@ -533,7 +505,6 @@ char **App::get_command_line(unsigned int *out_argc) {
int argc = READ_LE_4(len);
for (int i = 0; i < argc; i++) {
-
r = fread(len, sizeof(uint8_t), 4, f);
if (r < 4) {
@@ -555,8 +526,7 @@ char **App::get_command_line(unsigned int *out_argc) {
arg[strlen] = '\0';
if (r == strlen) {
-
- int warg_size = MultiByteToWideChar(CP_UTF8, 0, arg, -1, NULL, 0);
+ int warg_size = MultiByteToWideChar(CP_UTF8, 0, arg, -1, nullptr, 0);
wchar_t *warg = new wchar_t[warg_size];
MultiByteToWideChar(CP_UTF8, 0, arg, -1, warg, warg_size);
@@ -564,7 +534,6 @@ char **App::get_command_line(unsigned int *out_argc) {
cl.Append(ref new Platform::String(warg, warg_size));
} else {
-
delete[] arg;
fclose(f);
wprintf(L"Error reading command.\n");
@@ -580,15 +549,14 @@ char **App::get_command_line(unsigned int *out_argc) {
char **ret = new char *[cl.Size + 1];
for (int i = 0; i < cl.Size; i++) {
-
- int arg_size = WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, NULL, 0, NULL, NULL);
+ int arg_size = WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, nullptr, 0, nullptr, nullptr);
char *arg = new char[arg_size];
- WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, arg, arg_size, NULL, NULL);
+ WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, arg, arg_size, nullptr, nullptr);
ret[i] = arg;
}
- ret[cl.Size] = NULL;
+ ret[cl.Size] = nullptr;
*out_argc = cl.Size;
return ret;
diff --git a/platform/uwp/app.h b/platform/uwp/app.h
index 302d9759b3..5cffe378b1 100644
--- a/platform/uwp/app.h
+++ b/platform/uwp/app.h
@@ -34,7 +34,6 @@
#include <wrl.h>
-// ANGLE doesn't provide a specific lib for GLES3, so we keep using GLES2
#include "GLES2/gl2.h"
#include "os_uwp.h"
@@ -46,7 +45,7 @@ namespace GodotUWP
ref class App sealed : public Windows::ApplicationModel::Core::IFrameworkView
{
public:
- App();
+ App() {}
// IFrameworkView Methods.
virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
@@ -93,14 +92,14 @@ namespace GodotUWP
char** get_command_line(unsigned int* out_argc);
- bool mWindowClosed;
- bool mWindowVisible;
- GLsizei mWindowWidth;
- GLsizei mWindowHeight;
+ bool mWindowClosed = false;
+ bool mWindowVisible = true;
+ GLsizei mWindowWidth = 0;
+ GLsizei mWindowHeight = 0;
- EGLDisplay mEglDisplay;
- EGLContext mEglContext;
- EGLSurface mEglSurface;
+ EGLDisplay mEglDisplay = EGL_NO_DISPLAY;
+ EGLContext mEglContext = EGL_NO_CONTEXT;
+ EGLSurface mEglSurface = EGL_NO_SURFACE;
CoreWindow^ window;
OS_UWP* os;
diff --git a/platform/uwp/context_egl_uwp.cpp b/platform/uwp/context_egl_uwp.cpp
index 7ac9489bb4..2da6c5897a 100644
--- a/platform/uwp/context_egl_uwp.cpp
+++ b/platform/uwp/context_egl_uwp.cpp
@@ -35,27 +35,22 @@
using Platform::Exception;
void ContextEGL_UWP::release_current() {
-
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext);
};
void ContextEGL_UWP::make_current() {
-
eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
};
int ContextEGL_UWP::get_window_width() {
-
return width;
};
int ContextEGL_UWP::get_window_height() {
-
return height;
};
void ContextEGL_UWP::reset() {
-
cleanup();
window = CoreWindow::GetForCurrentThread();
@@ -63,7 +58,6 @@ void ContextEGL_UWP::reset() {
};
void ContextEGL_UWP::swap_buffers() {
-
if (eglSwapBuffers(mEglDisplay, mEglSurface) != EGL_TRUE) {
cleanup();
@@ -75,7 +69,6 @@ void ContextEGL_UWP::swap_buffers() {
};
Error ContextEGL_UWP::initialize() {
-
EGLint configAttribList[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
@@ -115,7 +108,6 @@ Error ContextEGL_UWP::initialize() {
}
try {
-
const EGLint displayAttributes[] = {
/*EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9,
@@ -155,7 +147,7 @@ Error ContextEGL_UWP::initialize() {
throw Exception::CreateException(E_FAIL, L"Failed to initialize EGL");
}
- if (eglGetConfigs(display, NULL, 0, &numConfigs) == EGL_FALSE) {
+ if (eglGetConfigs(display, nullptr, 0, &numConfigs) == EGL_FALSE) {
throw Exception::CreateException(E_FAIL, L"Failed to get EGLConfig count");
}
@@ -191,7 +183,6 @@ Error ContextEGL_UWP::initialize() {
};
void ContextEGL_UWP::cleanup() {
-
if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) {
eglDestroySurface(mEglDisplay, mEglSurface);
mEglSurface = EGL_NO_SURFACE;
@@ -216,6 +207,5 @@ ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) :
window(p_window) {}
ContextEGL_UWP::~ContextEGL_UWP() {
-
cleanup();
};
diff --git a/platform/uwp/context_egl_uwp.h b/platform/uwp/context_egl_uwp.h
index 7a41685867..6f333b8e6a 100644
--- a/platform/uwp/context_egl_uwp.h
+++ b/platform/uwp/context_egl_uwp.h
@@ -41,11 +41,10 @@
using namespace Windows::UI::Core;
class ContextEGL_UWP {
-
public:
enum Driver {
GLES_2_0,
- GLES_3_0,
+ VULKAN, // FIXME: Add Vulkan support.
};
private:
diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py
index 000bd18e7d..a7ca26c16c 100644
--- a/platform/uwp/detect.py
+++ b/platform/uwp/detect.py
@@ -12,11 +12,11 @@ def get_name():
def can_build():
- if (os.name == "nt"):
+ if os.name == "nt":
# building natively on windows!
- if (os.getenv("VSINSTALLDIR")):
+ if os.getenv("VSINSTALLDIR"):
- if (os.getenv("ANGLE_SRC_PATH") is None):
+ if os.getenv("ANGLE_SRC_PATH") is None:
return False
return True
@@ -25,16 +25,16 @@ def can_build():
def get_opts():
return [
- ('msvc_version', 'MSVC version to use (ignored if the VCINSTALLDIR environment variable is set)', None),
+ ("msvc_version", "MSVC version to use (ignored if the VCINSTALLDIR environment variable is set)", None),
]
def get_flags():
return [
- ('tools', False),
- ('xaudio2', True),
- ('builtin_pcre2_with_jit', False),
+ ("tools", False),
+ ("xaudio2", True),
+ ("builtin_pcre2_with_jit", False),
]
@@ -42,45 +42,55 @@ def configure(env):
env.msvc = True
- if (env["bits"] != "default"):
+ if env["bits"] != "default":
print("Error: bits argument is disabled for MSVC")
- print("""
+ print(
+ """
Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console
(or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits
argument (example: scons p=uwp) and SCons will attempt to detect what MSVC compiler will be executed and inform you.
- """)
+ """
+ )
sys.exit()
## Build type
- if (env["target"] == "release"):
- env.Append(CCFLAGS=['/O2', '/GL'])
- env.Append(CCFLAGS=['/MD'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:WINDOWS', '/LTCG'])
-
- elif (env["target"] == "release_debug"):
- env.Append(CCFLAGS=['/O2', '/Zi'])
- env.Append(CCFLAGS=['/MD'])
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
-
- elif (env["target"] == "debug"):
- env.Append(CCFLAGS=['/Zi'])
- env.Append(CCFLAGS=['/MDd'])
- env.Append(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
- env.Append(LINKFLAGS=['/DEBUG'])
+ if env["target"] == "release":
+ env.Append(CCFLAGS=["/O2", "/GL"])
+ env.Append(CCFLAGS=["/MD"])
+ env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS", "/LTCG"])
+
+ elif env["target"] == "release_debug":
+ env.Append(CCFLAGS=["/O2", "/Zi"])
+ env.Append(CCFLAGS=["/MD"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
+ env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
+
+ elif env["target"] == "debug":
+ env.Append(CCFLAGS=["/Zi"])
+ env.Append(CCFLAGS=["/MDd"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
+ env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
+ env.Append(LINKFLAGS=["/DEBUG"])
## Compiler configuration
- env['ENV'] = os.environ
- vc_base_path = os.environ['VCTOOLSINSTALLDIR'] if "VCTOOLSINSTALLDIR" in os.environ else os.environ['VCINSTALLDIR']
+ env["ENV"] = os.environ
+ vc_base_path = os.environ["VCTOOLSINSTALLDIR"] if "VCTOOLSINSTALLDIR" in os.environ else os.environ["VCINSTALLDIR"]
# ANGLE
angle_root = os.getenv("ANGLE_SRC_PATH")
- env.Prepend(CPPPATH=[angle_root + '/include'])
+ env.Prepend(CPPPATH=[angle_root + "/include"])
jobs = str(env.GetOption("num_jobs"))
- angle_build_cmd = "msbuild.exe " + angle_root + "/winrt/10/src/angle.sln /nologo /v:m /m:" + jobs + " /p:Configuration=Release /p:Platform="
+ angle_build_cmd = (
+ "msbuild.exe "
+ + angle_root
+ + "/winrt/10/src/angle.sln /nologo /v:m /m:"
+ + jobs
+ + " /p:Configuration=Release /p:Platform="
+ )
if os.path.isfile(str(os.getenv("ANGLE_SRC_PATH")) + "/winrt/10/src/angle.sln"):
env["build_angle"] = True
@@ -88,49 +98,53 @@ def configure(env):
## Architecture
arch = ""
- if str(os.getenv('Platform')).lower() == "arm":
+ if str(os.getenv("Platform")).lower() == "arm":
print("Compiled program architecture will be an ARM executable. (forcing bits=32).")
arch = "arm"
env["bits"] = "32"
- env.Append(LINKFLAGS=['/MACHINE:ARM'])
- env.Append(LIBPATH=[vc_base_path + 'lib/store/arm'])
+ env.Append(LINKFLAGS=["/MACHINE:ARM"])
+ env.Append(LIBPATH=[vc_base_path + "lib/store/arm"])
angle_build_cmd += "ARM"
- env.Append(LIBPATH=[angle_root + '/winrt/10/src/Release_ARM/lib'])
+ env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_ARM/lib"])
else:
- compiler_version_str = methods.detect_visual_c_compiler_version(env['ENV'])
+ compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"])
- if(compiler_version_str == "amd64" or compiler_version_str == "x86_amd64"):
+ if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64":
env["bits"] = "64"
print("Compiled program architecture will be a x64 executable (forcing bits=64).")
- elif (compiler_version_str == "x86" or compiler_version_str == "amd64_x86"):
+ elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86":
env["bits"] = "32"
print("Compiled program architecture will be a x86 executable. (forcing bits=32).")
else:
- print("Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup.")
+ print(
+ "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings"
+ " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture"
+ " this build is compiled for. You should check your settings/compilation setup."
+ )
env["bits"] = "32"
- if (env["bits"] == "32"):
+ if env["bits"] == "32":
arch = "x86"
angle_build_cmd += "Win32"
- env.Append(LINKFLAGS=['/MACHINE:X86'])
- env.Append(LIBPATH=[vc_base_path + 'lib/store'])
- env.Append(LIBPATH=[angle_root + '/winrt/10/src/Release_Win32/lib'])
+ env.Append(LINKFLAGS=["/MACHINE:X86"])
+ env.Append(LIBPATH=[vc_base_path + "lib/store"])
+ env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_Win32/lib"])
else:
arch = "x64"
angle_build_cmd += "x64"
- env.Append(LINKFLAGS=['/MACHINE:X64'])
- env.Append(LIBPATH=[os.environ['VCINSTALLDIR'] + 'lib/store/amd64'])
- env.Append(LIBPATH=[angle_root + '/winrt/10/src/Release_x64/lib'])
+ env.Append(LINKFLAGS=["/MACHINE:X64"])
+ env.Append(LIBPATH=[os.environ["VCINSTALLDIR"] + "lib/store/amd64"])
+ env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_x64/lib"])
env["PROGSUFFIX"] = "." + arch + env["PROGSUFFIX"]
env["OBJSUFFIX"] = "." + arch + env["OBJSUFFIX"]
@@ -138,39 +152,64 @@ def configure(env):
## Compile flags
- env.Prepend(CPPPATH=['#platform/uwp', '#drivers/windows'])
- env.Append(CPPDEFINES=['UWP_ENABLED', 'WINDOWS_ENABLED', 'TYPED_METHOD_BIND'])
- env.Append(CPPDEFINES=['GLES_ENABLED', 'GL_GLEXT_PROTOTYPES', 'EGL_EGLEXT_PROTOTYPES', 'ANGLE_ENABLED'])
- winver = "0x0602" # Windows 8 is the minimum target for UWP build
- env.Append(CPPDEFINES=[('WINVER', winver), ('_WIN32_WINNT', winver), 'WIN32'])
-
- env.Append(CPPDEFINES=['__WRL_NO_DEFAULT_LIB__', ('PNG_ABORT', 'abort')])
-
- env.Append(CPPFLAGS=['/AI', vc_base_path + 'lib/store/references'])
- env.Append(CPPFLAGS=['/AI', vc_base_path + 'lib/x86/store/references'])
-
- env.Append(CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split())
- env.Append(CPPDEFINES=['_UNICODE', 'UNICODE', ('WINAPI_FAMILY', 'WINAPI_FAMILY_APP')])
- env.Append(CXXFLAGS=['/ZW'])
- env.Append(CCFLAGS=['/AI', vc_base_path + '\\vcpackages', '/AI', os.environ['WINDOWSSDKDIR'] + '\\References\\CommonConfiguration\\Neutral'])
+ env.Prepend(CPPPATH=["#platform/uwp", "#drivers/windows"])
+ env.Append(CPPDEFINES=["UWP_ENABLED", "WINDOWS_ENABLED", "TYPED_METHOD_BIND"])
+ env.Append(CPPDEFINES=["GLES_ENABLED", "GL_GLEXT_PROTOTYPES", "EGL_EGLEXT_PROTOTYPES", "ANGLE_ENABLED"])
+ winver = "0x0602" # Windows 8 is the minimum target for UWP build
+ env.Append(CPPDEFINES=[("WINVER", winver), ("_WIN32_WINNT", winver), "WIN32"])
+
+ env.Append(CPPDEFINES=["__WRL_NO_DEFAULT_LIB__", ("PNG_ABORT", "abort")])
+
+ env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/store/references"])
+ env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/x86/store/references"])
+
+ env.Append(
+ CCFLAGS=(
+ '/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX-'
+ " /Zc:forScope /Gd /EHsc /nologo".split()
+ )
+ )
+ env.Append(CPPDEFINES=["_UNICODE", "UNICODE", ("WINAPI_FAMILY", "WINAPI_FAMILY_APP")])
+ env.Append(CXXFLAGS=["/ZW"])
+ env.Append(
+ CCFLAGS=[
+ "/AI",
+ vc_base_path + "\\vcpackages",
+ "/AI",
+ os.environ["WINDOWSSDKDIR"] + "\\References\\CommonConfiguration\\Neutral",
+ ]
+ )
## Link flags
- env.Append(LINKFLAGS=['/MANIFEST:NO', '/NXCOMPAT', '/DYNAMICBASE', '/WINMD', '/APPCONTAINER', '/ERRORREPORT:PROMPT', '/NOLOGO', '/TLBID:1', '/NODEFAULTLIB:"kernel32.lib"', '/NODEFAULTLIB:"ole32.lib"'])
+ env.Append(
+ LINKFLAGS=[
+ "/MANIFEST:NO",
+ "/NXCOMPAT",
+ "/DYNAMICBASE",
+ "/WINMD",
+ "/APPCONTAINER",
+ "/ERRORREPORT:PROMPT",
+ "/NOLOGO",
+ "/TLBID:1",
+ '/NODEFAULTLIB:"kernel32.lib"',
+ '/NODEFAULTLIB:"ole32.lib"',
+ ]
+ )
LIBS = [
- 'WindowsApp',
- 'mincore',
- 'ws2_32',
- 'libANGLE',
- 'libEGL',
- 'libGLESv2',
- 'bcrypt',
+ "WindowsApp",
+ "mincore",
+ "ws2_32",
+ "libANGLE",
+ "libEGL",
+ "libGLESv2",
+ "bcrypt",
]
env.Append(LINKFLAGS=[p + ".lib" for p in LIBS])
# Incremental linking fix
- env['BUILDERS']['ProgramOriginal'] = env['BUILDERS']['Program']
- env['BUILDERS']['Program'] = methods.precious_program
+ env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"]
+ env["BUILDERS"]["Program"] = methods.precious_program
- env.Append(BUILDERS={'ANGLE': env.Builder(action=angle_build_cmd)})
+ env.Append(BUILDERS={"ANGLE": env.Builder(action=angle_build_cmd)})
diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index 57fb9004b8..5679ec3eac 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -54,7 +54,7 @@ static const char *uwp_capabilities[] = {
"internetClient",
"internetClientServer",
"privateNetworkClientServer",
- NULL
+ nullptr
};
static const char *uwp_uap_capabilities[] = {
"appointments",
@@ -71,7 +71,7 @@ static const char *uwp_uap_capabilities[] = {
"userAccountInformation",
"videosLibrary",
"voipCall",
- NULL
+ nullptr
};
static const char *uwp_device_capabilities[] = {
"bluetooth",
@@ -79,11 +79,10 @@ static const char *uwp_device_capabilities[] = {
"microphone",
"proximity",
"webcam",
- NULL
+ nullptr
};
class AppxPackager {
-
enum {
FILE_HEADER_MAGIC = 0x04034b50,
DATA_DESCRIPTOR_MAGIC = 0x08074b50,
@@ -107,29 +106,21 @@ class AppxPackager {
};
struct BlockHash {
-
String base64_hash;
size_t compressed_size;
};
struct FileMeta {
-
String name;
- int lfh_size;
- bool compressed;
- size_t compressed_size;
- size_t uncompressed_size;
+ int lfh_size = 0;
+ bool compressed = false;
+ size_t compressed_size = 0;
+ size_t uncompressed_size = 0;
Vector<BlockHash> hashes;
- uLong file_crc32;
- ZPOS64_T zip_offset;
-
- FileMeta() :
- lfh_size(0),
- compressed(false),
- compressed_size(0),
- uncompressed_size(0),
- file_crc32(0),
- zip_offset(0) {}
+ uLong file_crc32 = 0;
+ ZPOS64_T zip_offset = 0;
+
+ FileMeta() {}
};
String progress_task;
@@ -195,7 +186,6 @@ public:
///////////////////////////////////////////////////////////////////////////
String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) {
-
unsigned char hash[32];
char base64[45];
@@ -208,24 +198,22 @@ String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len)
}
void AppxPackager::make_block_map(const String &p_path) {
-
FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
tmp_file->store_string("<BlockMap xmlns=\"http://schemas.microsoft.com/appx/2010/blockmap\" HashMethod=\"http://www.w3.org/2001/04/xmlenc#sha256\">");
for (int i = 0; i < file_metadata.size(); i++) {
-
FileMeta file = file_metadata[i];
tmp_file->store_string(
"<File Name=\"" + file.name.replace("/", "\\") + "\" Size=\"" + itos(file.uncompressed_size) + "\" LfhSize=\"" + itos(file.lfh_size) + "\">");
for (int j = 0; j < file.hashes.size(); j++) {
-
tmp_file->store_string("<Block Hash=\"" + file.hashes[j].base64_hash + "\" ");
- if (file.compressed)
+ if (file.compressed) {
tmp_file->store_string("Size=\"" + itos(file.hashes[j].compressed_size) + "\" ");
+ }
tmp_file->store_string("/>");
}
@@ -239,21 +227,20 @@ void AppxPackager::make_block_map(const String &p_path) {
}
String AppxPackager::content_type(String p_extension) {
-
- if (p_extension == "png")
+ if (p_extension == "png") {
return "image/png";
- else if (p_extension == "jpg")
+ } else if (p_extension == "jpg") {
return "image/jpg";
- else if (p_extension == "xml")
+ } else if (p_extension == "xml") {
return "application/xml";
- else if (p_extension == "exe" || p_extension == "dll")
+ } else if (p_extension == "exe" || p_extension == "dll") {
return "application/x-msdownload";
- else
+ } else {
return "application/octet-stream";
+ }
}
void AppxPackager::make_content_types(const String &p_path) {
-
FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
@@ -262,10 +249,11 @@ void AppxPackager::make_content_types(const String &p_path) {
Map<String, String> types;
for (int i = 0; i < file_metadata.size(); i++) {
-
String ext = file_metadata[i].name.get_extension();
- if (types.has(ext)) continue;
+ if (types.has(ext)) {
+ continue;
+ }
types[ext] = content_type(ext);
@@ -288,7 +276,6 @@ void AppxPackager::make_content_types(const String &p_path) {
}
Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) {
-
Vector<uint8_t> buf;
buf.resize(BASE_FILE_HEADER_SIZE + p_file_meta.name.length());
@@ -331,7 +318,6 @@ Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) {
}
void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_hash) {
-
Vector<uint8_t> &buf = central_dir_data;
int offs = buf.size();
buf.resize(buf.size() + BASE_CENTRAL_DIR_SIZE + p_file.name.length());
@@ -383,7 +369,6 @@ void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_ha
}
Vector<uint8_t> AppxPackager::make_end_of_central_record() {
-
Vector<uint8_t> buf;
buf.resize(ZIP64_END_OF_CENTRAL_DIR_SIZE + 12 + END_OF_CENTRAL_DIR_SIZE); // Size plus magic
@@ -453,14 +438,12 @@ Vector<uint8_t> AppxPackager::make_end_of_central_record() {
}
void AppxPackager::init(FileAccess *p_fa) {
-
package = p_fa;
central_dir_offset = 0;
end_of_central_dir_offset = 0;
}
Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) {
-
if (p_file_no >= 1 && p_total_files >= 1) {
if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) {
return ERR_SKIP;
@@ -478,13 +461,12 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
// Data for compression
z_stream strm;
- FileAccess *strm_f = NULL;
+ FileAccess *strm_f = nullptr;
Vector<uint8_t> strm_in;
strm_in.resize(BLOCK_SIZE);
Vector<uint8_t> strm_out;
if (p_compress) {
-
strm.zalloc = zipio_alloc;
strm.zfree = zipio_free;
strm.opaque = &strm_f;
@@ -497,7 +479,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
int step = 0;
while (p_len - step > 0) {
-
size_t block_size = (p_len - step) > BLOCK_SIZE ? (size_t)BLOCK_SIZE : (p_len - step);
for (uint64_t i = 0; i < block_size; i++) {
@@ -508,7 +489,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
bh.base64_hash = hash_block(strm_in.ptr(), block_size);
if (p_compress) {
-
strm.avail_in = block_size;
strm.avail_out = strm_out.size();
strm.next_in = (uint8_t *)strm_in.ptr();
@@ -524,15 +504,17 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
//package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before);
int start = file_buffer.size();
file_buffer.resize(file_buffer.size() + bh.compressed_size);
- for (uint64_t i = 0; i < bh.compressed_size; i++)
+ for (uint64_t i = 0; i < bh.compressed_size; i++) {
file_buffer.write[start + i] = strm_out[i];
+ }
} else {
bh.compressed_size = block_size;
//package->store_buffer(strm_in.ptr(), block_size);
int start = file_buffer.size();
file_buffer.resize(file_buffer.size() + block_size);
- for (uint64_t i = 0; i < bh.compressed_size; i++)
+ for (uint64_t i = 0; i < bh.compressed_size; i++) {
file_buffer.write[start + i] = strm_in[i];
+ }
}
meta.hashes.push_back(bh);
@@ -541,7 +523,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
}
if (p_compress) {
-
strm.avail_in = 0;
strm.avail_out = strm_out.size();
strm.next_in = (uint8_t *)strm_in.ptr();
@@ -554,14 +535,14 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
//package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before);
int start = file_buffer.size();
file_buffer.resize(file_buffer.size() + (strm.total_out - total_out_before));
- for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++)
+ for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++) {
file_buffer.write[start + i] = strm_out[i];
+ }
deflateEnd(&strm);
meta.compressed_size = strm.total_out;
} else {
-
meta.compressed_size = p_len;
}
@@ -584,7 +565,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
}
void AppxPackager::finish() {
-
// Create and add block map file
EditorNode::progress_task_step("export", "Creating block map...", 4);
@@ -641,7 +621,7 @@ void AppxPackager::finish() {
package->close();
memdelete(package);
- package = NULL;
+ package = nullptr;
}
AppxPackager::AppxPackager() {}
@@ -651,7 +631,6 @@ AppxPackager::~AppxPackager() {}
////////////////////////////////////////////////////////////////////
class EditorExportPlatformUWP : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformUWP, EditorExportPlatform);
Ref<ImageTexture> logo;
@@ -663,19 +642,24 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
};
bool _valid_resource_name(const String &p_name) const {
-
- if (p_name.empty()) return false;
- if (p_name.ends_with(".")) return false;
+ if (p_name.empty()) {
+ return false;
+ }
+ if (p_name.ends_with(".")) {
+ return false;
+ }
static const char *invalid_names[] = {
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
"COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
- NULL
+ nullptr
};
const char **t = invalid_names;
while (*t) {
- if (p_name == *t) return false;
+ if (p_name == *t) {
+ return false;
+ }
t++;
}
@@ -683,22 +667,33 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
}
bool _valid_guid(const String &p_guid) const {
-
Vector<String> parts = p_guid.split("-");
- if (parts.size() != 5) return false;
- if (parts[0].length() != 8) return false;
- for (int i = 1; i < 4; i++)
- if (parts[i].length() != 4) return false;
- if (parts[4].length() != 12) return false;
+ if (parts.size() != 5) {
+ return false;
+ }
+ if (parts[0].length() != 8) {
+ return false;
+ }
+ for (int i = 1; i < 4; i++) {
+ if (parts[i].length() != 4) {
+ return false;
+ }
+ }
+ if (parts[4].length() != 12) {
+ return false;
+ }
return true;
}
bool _valid_bgcolor(const String &p_color) const {
-
- if (p_color.empty()) return true;
- if (p_color.begins_with("#") && p_color.is_valid_html_color()) return true;
+ if (p_color.empty()) {
+ return true;
+ }
+ if (p_color.begins_with("#") && p_color.is_valid_html_color()) {
+ return true;
+ }
// Colors from https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
static const char *valid_colors[] = {
@@ -726,47 +721,32 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
"snow", "springGreen", "steelBlue", "tan", "teal", "thistle",
"tomato", "transparent", "turquoise", "violet", "wheat", "white",
"whiteSmoke", "yellow", "yellowGreen",
- NULL
+ nullptr
};
const char **color = valid_colors;
while (*color) {
- if (p_color == *color) return true;
+ if (p_color == *color) {
+ return true;
+ }
color++;
}
return false;
}
- bool _valid_image(const StreamTexture *p_image, int p_width, int p_height) const {
-
+ bool _valid_image(const StreamTexture2D *p_image, int p_width, int p_height) const {
if (!p_image) {
return false;
}
// TODO: Add resource creation or image rescaling to enable other scales:
// 1.25, 1.5, 2.0
- real_t scales[] = { 1.0 };
- bool valid_w = false;
- bool valid_h = false;
-
- for (int i = 0; i < 1; i++) {
-
- int w = ceil(p_width * scales[i]);
- int h = ceil(p_height * scales[i]);
-
- if (w == p_image->get_width())
- valid_w = true;
- if (h == p_image->get_height())
- valid_h = true;
- }
-
- return valid_w && valid_h;
+ return p_width == p_image->get_width() && p_height == p_image->get_height();
}
Vector<uint8_t> _fix_manifest(const Ref<EditorExportPreset> &p_preset, const Vector<uint8_t> &p_template, bool p_give_internet) const {
-
String result = String::utf8((const char *)p_template.ptr(), p_template.size());
result = result.replace("$godot_version$", VERSION_FULL_NAME);
@@ -867,43 +847,44 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
Vector<uint8_t> r_ret;
r_ret.resize(result.length());
- for (int i = 0; i < result.length(); i++)
+ for (int i = 0; i < result.length(); i++) {
r_ret.write[i] = result.utf8().get(i);
+ }
return r_ret;
}
Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
-
Vector<uint8_t> data;
- StreamTexture *image = NULL;
+ StreamTexture2D *image = nullptr;
if (p_path.find("StoreLogo") != -1) {
- image = p_preset->get("images/store_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/store_logo")));
+ image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/store_logo")));
} else if (p_path.find("Square44x44Logo") != -1) {
- image = p_preset->get("images/square44x44_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square44x44_logo")));
+ image = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square44x44_logo")));
} else if (p_path.find("Square71x71Logo") != -1) {
- image = p_preset->get("images/square71x71_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square71x71_logo")));
+ image = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square71x71_logo")));
} else if (p_path.find("Square150x150Logo") != -1) {
- image = p_preset->get("images/square150x150_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square150x150_logo")));
+ image = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square150x150_logo")));
} else if (p_path.find("Square310x310Logo") != -1) {
- image = p_preset->get("images/square310x310_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square310x310_logo")));
+ image = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square310x310_logo")));
} else if (p_path.find("Wide310x150Logo") != -1) {
- image = p_preset->get("images/wide310x150_logo").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/wide310x150_logo")));
+ image = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/wide310x150_logo")));
} else if (p_path.find("SplashScreen") != -1) {
- image = p_preset->get("images/splash_screen").is_zero() ? NULL : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/splash_screen")));
+ image = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/splash_screen")));
} else {
ERR_PRINT("Unable to load logo");
}
- if (!image) return data;
+ if (!image) {
+ return data;
+ }
String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png");
Error err = image->get_data()->save_png(tmp_path);
if (err != OK) {
-
String err_string = "Couldn't save temp logo file.";
EditorNode::add_io_error(err_string);
@@ -913,7 +894,6 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
FileAccess *f = FileAccess::open(tmp_path, FileAccess::READ, &err);
if (err != OK) {
-
String err_string = "Couldn't open temp logo file.";
// Cleanup generated file.
DirAccess::remove_file_or_error(tmp_path);
@@ -932,17 +912,16 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
}
static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
-
/* TODO: This was copied verbatim from Android export. It should be
- * refactored to the parent class and also be used for .zip export.
- */
+ * refactored to the parent class and also be used for .zip export.
+ */
/*
- * By not compressing files with little or not benefit in doing so,
- * a performance gain is expected at runtime. Moreover, if the APK is
- * zip-aligned, assets stored as they are can be efficiently read by
- * Android by memory-mapping them.
- */
+ * By not compressing files with little or not benefit in doing so,
+ * a performance gain is expected at runtime. Moreover, if the APK is
+ * zip-aligned, assets stored as they are can be efficiently read by
+ * Android by memory-mapping them.
+ */
// -- Unconditional uncompress to mimic AAPT plus some other
@@ -961,7 +940,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
".scn", // Binary scenes are usually already compressed
".stex", // Streamable textures are usually already compressed
// Trailer for easier processing
- NULL
+ nullptr
};
for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
@@ -982,8 +961,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
return true;
}
- static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
-
+ static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
AppxPackager *packager = (AppxPackager *)p_userdata;
String dst_path = p_path.replace_first("res://", "game/");
@@ -991,24 +969,24 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
}
public:
- virtual String get_name() const {
- return "Windows Universal";
+ virtual String get_name() const override {
+ return "UWP";
}
- virtual String get_os_name() const {
+ virtual String get_os_name() const override {
return "UWP";
}
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
list.push_back("appx");
return list;
}
- virtual Ref<Texture> get_logo() const {
+ virtual Ref<Texture2D> get_logo() const override {
return logo;
}
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override {
r_features->push_back("s3tc");
r_features->push_back("etc");
switch ((int)p_preset->get("architecture/target")) {
@@ -1024,7 +1002,7 @@ public:
}
}
- virtual void get_export_options(List<ExportOption> *r_options) {
+ virtual void get_export_options(List<ExportOption> *r_options) override {
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "architecture/target", PROPERTY_HINT_ENUM, "arm,x86,x64"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
@@ -1054,13 +1032,13 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_flipped"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "images/background_color"), "transparent"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square150x150"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_wide310x150"), false));
@@ -1089,8 +1067,7 @@ public:
}
}
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
-
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override {
String err;
bool valid = false;
@@ -1161,37 +1138,37 @@ public:
err += TTR("Invalid background color.") + "\n";
}
- if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/store_logo"))), 50, 50)) {
+ if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/store_logo"))), 50, 50)) {
valid = false;
err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n";
}
- if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) {
+ if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) {
valid = false;
err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n";
}
- if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) {
+ if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) {
valid = false;
err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n";
}
- if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square150x150_logo"))), 150, 0)) {
+ if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) {
valid = false;
err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n";
}
- if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) {
+ if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) {
valid = false;
err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n";
}
- if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) {
+ if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) {
valid = false;
err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n";
}
- if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) {
+ if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) {
valid = false;
err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n";
}
@@ -1200,16 +1177,16 @@ public:
return valid;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
-
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override {
String src_appx;
- EditorProgress ep("export", "Exporting for Windows Universal", 7, true);
+ EditorProgress ep("export", "Exporting for UWP", 7, true);
- if (p_debug)
+ if (p_debug) {
src_appx = p_preset->get("custom_template/debug");
- else
+ } else {
src_appx = p_preset->get("custom_template/release");
+ }
src_appx = src_appx.strip_edges();
@@ -1251,7 +1228,7 @@ public:
AppxPackager packager;
packager.init(fa_pack);
- FileAccess *src_f = NULL;
+ FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
if (ep.step("Creating package...", 0)) {
@@ -1261,7 +1238,6 @@ public:
unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io);
if (!pkg) {
-
EditorNode::add_io_error("Could not find template appx to export:\n" + src_appx);
return ERR_FILE_NOT_FOUND;
}
@@ -1279,11 +1255,10 @@ public:
int template_file_no = 1;
while (ret == UNZ_OK) {
-
// get file name
unz_file_info info;
char fname[16834];
- ret = unzGetCurrentFileInfo(pkg, &info, fname, 16834, NULL, 0, NULL, 0);
+ ret = unzGetCurrentFileInfo(pkg, &info, fname, 16834, nullptr, 0, nullptr, 0);
String path = fname;
@@ -1297,11 +1272,12 @@ public:
bool do_read = true;
if (path.begins_with("Assets/")) {
-
path = path.replace(".scale-100", "");
data = _get_image_data(p_preset, path);
- if (data.size() > 0) do_read = false;
+ if (data.size() > 0) {
+ do_read = false;
+ }
}
//read
@@ -1313,7 +1289,6 @@ public:
}
if (path == "AppxManifest.xml") {
-
data = _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG));
}
@@ -1356,7 +1331,6 @@ public:
encode_uint32(cl.size(), clf.ptrw());
for (int i = 0; i < cl.size(); i++) {
-
CharString txt = cl[i].utf8();
int base = clf.size();
clf.resize(base + 4 + txt.length());
@@ -1397,7 +1371,7 @@ public:
}
if (!FileAccess::exists(signtool_path)) {
- ERR_PRINTS("Could not find signtool executable at " + signtool_path + ", aborting.");
+ ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting.");
return ERR_FILE_NOT_FOUND;
}
@@ -1418,12 +1392,12 @@ public:
}
if (!FileAccess::exists(cert_path)) {
- ERR_PRINTS("Could not find certificate file at " + cert_path + ", aborting.");
+ ERR_PRINT("Could not find certificate file at " + cert_path + ", aborting.");
return ERR_FILE_NOT_FOUND;
}
if (cert_alg < 0 || cert_alg > 2) {
- ERR_PRINTS("Invalid certificate algorithm " + itos(cert_alg) + ", aborting.");
+ ERR_PRINT("Invalid certificate algorithm " + itos(cert_alg) + ", aborting.");
return ERR_INVALID_DATA;
}
@@ -1444,13 +1418,12 @@ public:
return OK;
}
- virtual void get_platform_features(List<String> *r_features) {
-
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("pc");
r_features->push_back("UWP");
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
EditorExportPlatformUWP() {
@@ -1461,7 +1434,6 @@ public:
};
void register_uwp_exporter() {
-
#ifdef WINDOWS_ENABLED
EDITOR_DEF("export/uwp/signtool", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/uwp/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
diff --git a/platform/uwp/export/export.h b/platform/uwp/export/export.h
index ce03bc0aeb..1a1555d8ee 100644
--- a/platform/uwp/export/export.h
+++ b/platform/uwp/export/export.h
@@ -28,4 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef UWP_EXPORT_H
+#define UWP_EXPORT_H
+
void register_uwp_exporter();
+
+#endif // UWP_EXPORT_H
diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp
index 90df6fe5d7..4fdfde9673 100644
--- a/platform/uwp/joypad_uwp.cpp
+++ b/platform/uwp/joypad_uwp.cpp
@@ -35,7 +35,6 @@ using namespace Windows::Gaming::Input;
using namespace Windows::Foundation;
void JoypadUWP::register_events() {
-
Gamepad::GamepadAdded +=
ref new EventHandler<Gamepad ^>(this, &JoypadUWP::OnGamepadAdded);
Gamepad::GamepadRemoved +=
@@ -43,32 +42,28 @@ void JoypadUWP::register_events() {
}
void JoypadUWP::process_controllers() {
-
for (int i = 0; i < MAX_CONTROLLERS; i++) {
-
ControllerDevice &joy = controllers[i];
- if (!joy.connected) break;
+ if (!joy.connected)
+ break;
switch (joy.type) {
-
case ControllerType::GAMEPAD_CONTROLLER: {
-
GamepadReading reading = ((Gamepad ^) joy.controller_reference)->GetCurrentReading();
int button_mask = (int)GamepadButtons::Menu;
for (int j = 0; j < 14; j++) {
-
input->joy_button(joy.id, j, (int)reading.Buttons & button_mask);
button_mask *= 2;
}
- input->joy_axis(joy.id, JOY_AXIS_0, axis_correct(reading.LeftThumbstickX));
- input->joy_axis(joy.id, JOY_AXIS_1, axis_correct(reading.LeftThumbstickY, true));
- input->joy_axis(joy.id, JOY_AXIS_2, axis_correct(reading.RightThumbstickX));
- input->joy_axis(joy.id, JOY_AXIS_3, axis_correct(reading.RightThumbstickY, true));
- input->joy_axis(joy.id, JOY_AXIS_4, axis_correct(reading.LeftTrigger, false, true));
- input->joy_axis(joy.id, JOY_AXIS_5, axis_correct(reading.RightTrigger, false, true));
+ input->joy_axis(joy.id, JOY_AXIS_LEFT_X, axis_correct(reading.LeftThumbstickX));
+ input->joy_axis(joy.id, JOY_AXIS_LEFT_Y, axis_correct(reading.LeftThumbstickY, true));
+ input->joy_axis(joy.id, JOY_AXIS_RIGHT_X, axis_correct(reading.RightThumbstickX));
+ input->joy_axis(joy.id, JOY_AXIS_RIGHT_Y, axis_correct(reading.RightThumbstickY, true));
+ input->joy_axis(joy.id, JOY_AXIS_TRIGGER_LEFT, axis_correct(reading.LeftTrigger, false, true));
+ input->joy_axis(joy.id, JOY_AXIS_TRIGGER_RIGHT, axis_correct(reading.RightTrigger, false, true));
uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id);
if (timestamp > joy.ff_timestamp) {
@@ -92,24 +87,20 @@ void JoypadUWP::process_controllers() {
}
JoypadUWP::JoypadUWP() {
-
for (int i = 0; i < MAX_CONTROLLERS; i++)
controllers[i].id = i;
}
JoypadUWP::JoypadUWP(InputDefault *p_input) {
-
input = p_input;
JoypadUWP();
}
void JoypadUWP::OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) {
-
short idx = -1;
for (int i = 0; i < MAX_CONTROLLERS; i++) {
-
if (!controllers[i].connected) {
idx = i;
break;
@@ -127,11 +118,9 @@ void JoypadUWP::OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input
}
void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) {
-
short idx = -1;
for (int i = 0; i < MAX_CONTROLLERS; i++) {
-
if (controllers[i].controller_reference == value) {
idx = i;
break;
@@ -146,7 +135,6 @@ void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Inp
}
InputDefault::JoyAxis JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const {
-
InputDefault::JoyAxis jx;
jx.min = p_trigger ? 0 : -1;
diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h
index f2a721f3dd..13f246a438 100644
--- a/platform/uwp/joypad_uwp.h
+++ b/platform/uwp/joypad_uwp.h
@@ -31,10 +31,9 @@
#ifndef JOYPAD_UWP_H
#define JOYPAD_UWP_H
-#include "main/input_default.h"
+#include "core/input/input.h"
ref class JoypadUWP sealed {
-
/** clang-format breaks this, it does not understand this token. */
/* clang-format off */
internal:
@@ -57,7 +56,6 @@ private:
};
struct ControllerDevice {
-
Windows::Gaming::Input::IGameController ^ controller_reference;
int id;
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index d5047b53ab..44ab075816 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -35,8 +35,6 @@
#include "core/io/marshalls.h"
#include "core/project_settings.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/unix/ip_unix.h"
#include "drivers/windows/dir_access_windows.h"
#include "drivers/windows/file_access_windows.h"
@@ -46,8 +44,8 @@
#include "main/main.h"
#include "platform/windows/windows_terminal_logger.h"
#include "servers/audio_server.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
+#include "servers/rendering/rendering_server_raster.h"
+#include "servers/rendering/rendering_server_wrap_mt.h"
#include "thread_uwp.h"
#include <ppltasks.h>
@@ -83,7 +81,6 @@ int OS_UWP::get_current_video_driver() const {
}
void OS_UWP::set_window_size(const Size2 p_size) {
-
Windows::Foundation::Size new_size;
new_size.Width = p_size.width;
new_size.Height = p_size.height;
@@ -91,14 +88,12 @@ void OS_UWP::set_window_size(const Size2 p_size) {
ApplicationView ^ view = ApplicationView::GetForCurrentView();
if (view->TryResizeView(new_size)) {
-
video_mode.width = p_size.width;
video_mode.height = p_size.height;
}
}
void OS_UWP::set_window_fullscreen(bool p_enabled) {
-
ApplicationView ^ view = ApplicationView::GetForCurrentView();
video_mode.fullscreen = view->IsFullScreenMode;
@@ -107,24 +102,21 @@ void OS_UWP::set_window_fullscreen(bool p_enabled) {
return;
if (p_enabled) {
-
video_mode.fullscreen = view->TryEnterFullScreenMode();
} else {
-
view->ExitFullScreenMode();
video_mode.fullscreen = false;
}
}
bool OS_UWP::is_window_fullscreen() const {
-
return ApplicationView::GetForCurrentView()->IsFullScreenMode;
}
void OS_UWP::set_keep_screen_on(bool p_enabled) {
-
- if (is_keep_screen_on() == p_enabled) return;
+ if (is_keep_screen_on() == p_enabled)
+ return;
if (p_enabled)
display_request->RequestActive();
@@ -135,14 +127,11 @@ void OS_UWP::set_keep_screen_on(bool p_enabled) {
}
void OS_UWP::initialize_core() {
-
last_button_state = 0;
//RedirectIOToConsole();
ThreadUWP::make_default();
- SemaphoreWindows::make_default();
- MutexWindows::make_default();
RWLockWindows::make_default();
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
@@ -168,7 +157,6 @@ void OS_UWP::initialize_core() {
}
bool OS_UWP::can_draw() const {
-
return !minimized;
};
@@ -177,80 +165,40 @@ void OS_UWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) {
}
void OS_UWP::screen_size_changed() {
-
gl_context->reset();
};
Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- main_loop = NULL;
+ main_loop = nullptr;
outside = true;
+ // FIXME: Hardcoded for now, add Vulkan support.
+ p_video_driver = VIDEO_DRIVER_GLES2;
ContextEGL_UWP::Driver opengl_api_type = ContextEGL_UWP::GLES_2_0;
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- opengl_api_type = ContextEGL_UWP::GLES_2_0;
- }
-
bool gl_initialization_error = false;
- gl_context = NULL;
- while (!gl_context) {
- gl_context = memnew(ContextEGL_UWP(window, opengl_api_type));
-
- if (gl_context->initialize() != OK) {
- memdelete(gl_context);
- gl_context = NULL;
-
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gl_initialization_error = true;
- break;
- }
-
- p_video_driver = VIDEO_DRIVER_GLES2;
- opengl_api_type = ContextEGL_UWP::GLES_2_0;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
+ gl_context = memnew(ContextEGL_UWP(window, opengl_api_type));
- while (true) {
- if (opengl_api_type == ContextEGL_UWP::GLES_3_0) {
- if (RasterizerGLES3::is_viable() == OK) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- opengl_api_type = ContextEGL_UWP::GLES_2_0;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
+ if (gl_context->initialize() != OK) {
+ memdelete(gl_context);
+ gl_context = nullptr;
+ gl_initialization_error = true;
+ }
- if (opengl_api_type == ContextEGL_UWP::GLES_2_0) {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
+ if (opengl_api_type == ContextEGL_UWP::GLES_2_0) {
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
}
}
if (gl_initialization_error) {
OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
"Please update your drivers or if you have a very old or integrated GPU upgrade it.",
- "Unable to initialize Video driver");
+ "Unable to initialize video driver");
return ERR_UNAVAILABLE;
}
@@ -271,11 +219,9 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
if (p_desired.fullscreen != view->IsFullScreenMode) {
if (p_desired.fullscreen) {
-
vm.fullscreen = view->TryEnterFullScreenMode();
} else {
-
view->ExitFullScreenMode();
vm.fullscreen = false;
}
@@ -288,20 +234,19 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
view->PreferredLaunchViewSize = desired;
if (view->TryResizeView(desired)) {
-
vm.width = view->VisibleBounds.Width;
vm.height = view->VisibleBounds.Height;
}
set_video_mode(vm);
- visual_server = memnew(VisualServerRaster);
+ rendering_server = memnew(RenderingServerRaster);
// FIXME: Reimplement threaded rendering
if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, false));
+ rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
}
- visual_server->init();
+ rendering_server->init();
input = memnew(InputDefault);
@@ -310,8 +255,6 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
AudioDriverManager::initialize(p_audio_driver);
- power_manager = memnew(PowerUWP);
-
managed_object->update_clipboard();
Clipboard::ContentChanged += ref new EventHandler<Platform::Object ^>(managed_object, &ManagedType::on_clipboard_changed);
@@ -351,16 +294,14 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
}
void OS_UWP::set_clipboard(const String &p_text) {
-
DataPackage ^ clip = ref new DataPackage();
clip->RequestedOperation = DataPackageOperation::Copy;
- clip->SetText(ref new Platform::String((const wchar_t *)p_text.c_str()));
+ clip->SetText(ref new Platform::String((LPCWSTR)(p_text.utf16().get_data())));
Clipboard::SetContent(clip);
};
String OS_UWP::get_clipboard() const {
-
if (managed_object->clipboard != nullptr)
return managed_object->clipboard->Data();
else
@@ -368,32 +309,28 @@ String OS_UWP::get_clipboard() const {
};
void OS_UWP::input_event(const Ref<InputEvent> &p_event) {
-
input->parse_input_event(p_event);
};
void OS_UWP::delete_main_loop() {
-
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
+ main_loop = nullptr;
}
void OS_UWP::set_main_loop(MainLoop *p_main_loop) {
-
input->set_main_loop(p_main_loop);
main_loop = p_main_loop;
}
void OS_UWP::finalize() {
-
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
+ main_loop = nullptr;
- visual_server->finish();
- memdelete(visual_server);
+ rendering_server->finish();
+ memdelete(rendering_server);
#ifdef OPENGL_ENABLED
if (gl_context)
memdelete(gl_context);
@@ -405,14 +342,12 @@ void OS_UWP::finalize() {
}
void OS_UWP::finalize_core() {
-
NetSocketPosix::cleanup();
}
void OS_UWP::alert(const String &p_alert, const String &p_title) {
-
- Platform::String ^ alert = ref new Platform::String(p_alert.c_str());
- Platform::String ^ title = ref new Platform::String(p_title.c_str());
+ Platform::String ^ alert = ref new Platform::String((LPCWSTR)(p_alert.utf16().get_data()));
+ Platform::String ^ title = ref new Platform::String((LPCWSTR)(p_title.utf16().get_data()));
MessageDialog ^ msg = ref new MessageDialog(alert, title);
@@ -426,21 +361,17 @@ void OS_UWP::alert(const String &p_alert, const String &p_title) {
}
void OS_UWP::ManagedType::alert_close(IUICommand ^ command) {
-
alert_close_handle = false;
}
void OS_UWP::ManagedType::on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev) {
-
update_clipboard();
}
void OS_UWP::ManagedType::update_clipboard() {
-
DataPackageView ^ data = Clipboard::GetContent();
if (data->Contains(StandardDataFormats::Text)) {
-
create_task(data->GetTextAsync()).then([this](Platform::String ^ clipboard_content) {
this->clipboard = clipboard_content;
});
@@ -448,7 +379,6 @@ void OS_UWP::ManagedType::update_clipboard() {
}
void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender, AccelerometerReadingChangedEventArgs ^ args) {
-
AccelerometerReading ^ reading = args->Reading;
os->input->set_accelerometer(Vector3(
@@ -458,7 +388,6 @@ void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sende
}
void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) {
-
MagnetometerReading ^ reading = args->Reading;
os->input->set_magnetometer(Vector3(
@@ -468,7 +397,6 @@ void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender,
}
void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, GyrometerReadingChangedEventArgs ^ args) {
-
GyrometerReading ^ reading = args->Reading;
os->input->set_magnetometer(Vector3(
@@ -478,22 +406,17 @@ void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, Gyrom
}
void OS_UWP::set_mouse_mode(MouseMode p_mode) {
-
if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) {
-
CoreWindow::GetForCurrentThread()->SetPointerCapture();
} else {
-
CoreWindow::GetForCurrentThread()->ReleasePointerCapture();
}
if (p_mode == MouseMode::MOUSE_MODE_CAPTURED || p_mode == MouseMode::MOUSE_MODE_HIDDEN) {
-
CoreWindow::GetForCurrentThread()->PointerCursor = nullptr;
} else {
-
CoreWindow::GetForCurrentThread()->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0);
}
@@ -503,17 +426,14 @@ void OS_UWP::set_mouse_mode(MouseMode p_mode) {
}
OS_UWP::MouseMode OS_UWP::get_mouse_mode() const {
-
return mouse_mode;
}
Point2 OS_UWP::get_mouse_position() const {
-
return Point2(old_x, old_y);
}
int OS_UWP::get_mouse_button_state() const {
-
return last_button_state;
}
@@ -521,23 +441,21 @@ void OS_UWP::set_window_title(const String &p_title) {
}
void OS_UWP::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-
video_mode = p_video_mode;
}
-OS::VideoMode OS_UWP::get_video_mode(int p_screen) const {
+OS::VideoMode OS_UWP::get_video_mode(int p_screen) const {
return video_mode;
}
+
void OS_UWP::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
}
String OS_UWP::get_name() const {
-
return "UWP";
}
OS::Date OS_UWP::get_date(bool utc) const {
-
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
@@ -552,8 +470,8 @@ OS::Date OS_UWP::get_date(bool utc) const {
date.dst = false;
return date;
}
-OS::Time OS_UWP::get_time(bool utc) const {
+OS::Time OS_UWP::get_time(bool utc) const {
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
@@ -587,7 +505,6 @@ OS::TimeZoneInfo OS_UWP::get_time_zone_info() const {
}
uint64_t OS_UWP::get_unix_time() const {
-
FILETIME ft;
SYSTEMTIME st;
GetSystemTime(&st);
@@ -609,20 +526,36 @@ uint64_t OS_UWP::get_unix_time() const {
};
void OS_UWP::delay_usec(uint32_t p_usec) const {
-
int msec = p_usec < 1000 ? 1 : p_usec / 1000;
// no Sleep()
WaitForSingleObjectEx(GetCurrentThread(), msec, false);
}
-uint64_t OS_UWP::get_ticks_usec() const {
+uint64_t OS_UWP::get_ticks_usec() const {
uint64_t ticks;
- uint64_t time;
+
// This is the number of clock ticks since start
QueryPerformanceCounter((LARGE_INTEGER *)&ticks);
+
// Divide by frequency to get the time in seconds
- time = ticks * 1000000L / ticks_per_second;
+ // original calculation shown below is subject to overflow
+ // with high ticks_per_second and a number of days since the last reboot.
+ // time = ticks * 1000000L / ticks_per_second;
+
+ // we can prevent this by either using 128 bit math
+ // or separating into a calculation for seconds, and the fraction
+ uint64_t seconds = ticks / ticks_per_second;
+
+ // compiler will optimize these two into one divide
+ uint64_t leftover = ticks % ticks_per_second;
+
+ // remainder
+ uint64_t time = (leftover * 1000000L) / ticks_per_second;
+
+ // seconds
+ time += seconds * 1000000L;
+
// Subtract the time at game start to get
// the time since the game started
time -= ticks_start;
@@ -630,15 +563,12 @@ uint64_t OS_UWP::get_ticks_usec() const {
}
void OS_UWP::process_events() {
-
joypad->process_controllers();
process_key_events();
}
void OS_UWP::process_key_events() {
-
for (int i = 0; i < key_event_pos; i++) {
-
KeyEvent &kev = key_event_buffer[i];
Ref<InputEventKey> key_event;
@@ -647,7 +577,8 @@ void OS_UWP::process_key_events() {
key_event->set_shift(kev.shift);
key_event->set_control(kev.control);
key_event->set_echo(kev.echo);
- key_event->set_scancode(kev.scancode);
+ key_event->set_keycode(kev.keycode);
+ key_event->set_physical_keycode(kev.physical_keycode);
key_event->set_unicode(kev.unicode);
key_event->set_pressed(kev.pressed);
@@ -660,7 +591,6 @@ void OS_UWP::queue_key_event(KeyEvent &p_event) {
// This merges Char events with the previous Key event, so
// the unicode can be retrieved without sending duplicate events.
if (p_event.type == KeyEvent::MessageType::CHAR_EVENT_MESSAGE && key_event_pos > 0) {
-
KeyEvent &old = key_event_buffer[key_event_pos - 1];
ERR_FAIL_COND(old.type != KeyEvent::MessageType::KEY_EVENT_MESSAGE);
@@ -674,7 +604,6 @@ void OS_UWP::queue_key_event(KeyEvent &p_event) {
}
void OS_UWP::set_cursor_shape(CursorShape p_shape) {
-
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (cursor_shape == p_shape)
@@ -706,7 +635,6 @@ void OS_UWP::set_cursor_shape(CursorShape p_shape) {
}
OS::CursorShape OS_UWP::get_cursor_shape() const {
-
return cursor_shape;
}
@@ -715,22 +643,18 @@ void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c
}
Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
-
return FAILED;
};
Error OS_UWP::kill(const ProcessID &p_pid) {
-
return FAILED;
};
Error OS_UWP::set_cwd(const String &p_cwd) {
-
return FAILED;
}
String OS_UWP::get_executable_path() const {
-
return "";
}
@@ -738,22 +662,18 @@ void OS_UWP::set_icon(const Ref<Image> &p_icon) {
}
bool OS_UWP::has_environment(const String &p_var) const {
-
return false;
};
String OS_UWP::get_environment(const String &p_var) const {
-
return "";
};
bool OS_UWP::set_environment(const String &p_var, const String &p_value) const {
-
return false;
}
String OS_UWP::get_stdin_string(bool p_block) {
-
return String();
}
@@ -761,12 +681,10 @@ void OS_UWP::move_window_to_foreground() {
}
Error OS_UWP::shell_open(String p_uri) {
-
return FAILED;
}
String OS_UWP::get_locale() const {
-
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // this should work on phone 8.1, but it doesn't
return "en";
#else
@@ -776,48 +694,40 @@ String OS_UWP::get_locale() const {
}
void OS_UWP::release_rendering_thread() {
-
gl_context->release_current();
}
void OS_UWP::make_rendering_thread() {
-
gl_context->make_current();
}
void OS_UWP::swap_buffers() {
-
gl_context->swap_buffers();
}
bool OS_UWP::has_touchscreen_ui_hint() const {
-
TouchCapabilities ^ tc = ref new TouchCapabilities();
return tc->TouchPresent != 0 || UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;
}
bool OS_UWP::has_virtual_keyboard() const {
-
return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;
}
-void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) {
-
+void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
InputPane ^ pane = InputPane::GetForCurrentView();
pane->TryShow();
}
void OS_UWP::hide_virtual_keyboard() {
-
InputPane ^ pane = InputPane::GetForCurrentView();
pane->TryHide();
}
static String format_error_message(DWORD id) {
-
- LPWSTR messageBuffer = NULL;
+ LPWSTR messageBuffer = nullptr;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL);
+ nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr);
String msg = "Error " + itos(id) + ": " + String(messageBuffer, size);
@@ -827,9 +737,8 @@ static String format_error_message(DWORD id) {
}
Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
-
String full_path = "game/" + p_path;
- p_library_handle = (void *)LoadPackagedLibrary(full_path.c_str(), 0);
+ p_library_handle = (void *)LoadPackagedLibrary((LPCWSTR)(full_path.utf16().get_data()), 0);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + ".");
return OK;
}
@@ -854,7 +763,6 @@ Error OS_UWP::get_dynamic_library_symbol_handle(void *p_library_handle, const St
}
void OS_UWP::run() {
-
if (!main_loop)
return;
@@ -866,9 +774,9 @@ void OS_UWP::run() {
uint64_t frame = 0;
while (!force_quit) {
-
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
- if (managed_object->alert_close_handle) continue;
+ if (managed_object->alert_close_handle)
+ continue;
process_events(); // get rid of pending events
if (Main::iteration())
break;
@@ -878,12 +786,10 @@ void OS_UWP::run() {
}
MainLoop *OS_UWP::get_main_loop() const {
-
return main_loop;
}
String OS_UWP::get_user_data_dir() const {
-
Windows::Storage::StorageFolder ^ data_folder = Windows::Storage::ApplicationData::Current->LocalFolder;
return String(data_folder->Path->Data()).replace("\\", "/");
@@ -893,20 +799,7 @@ bool OS_UWP::_check_internal_feature_support(const String &p_feature) {
return p_feature == "pc";
}
-OS::PowerState OS_UWP::get_power_state() {
- return power_manager->get_power_state();
-}
-
-int OS_UWP::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
-}
-
-int OS_UWP::get_power_percent_left() {
- return power_manager->get_power_percent_left();
-}
-
OS_UWP::OS_UWP() {
-
key_event_pos = 0;
force_quit = false;
alt_mem = false;
@@ -923,14 +816,14 @@ OS_UWP::OS_UWP() {
stdo = fopen("stdout.txt", "wb");
#endif
- gl_context = NULL;
+ gl_context = nullptr;
display_request = ref new Windows::System::Display::DisplayRequest();
managed_object = ref new ManagedType;
managed_object->os = this;
- mouse_mode_changed = CreateEvent(NULL, TRUE, FALSE, L"os_mouse_mode_changed");
+ mouse_mode_changed = CreateEvent(nullptr, TRUE, FALSE, L"os_mouse_mode_changed");
AudioDriverManager::add_driver(&audio_driver);
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index fb43ab382e..892327bac5 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -32,17 +32,15 @@
#define OS_UWP_H
#include "context_egl_uwp.h"
+#include "core/input/input.h"
#include "core/math/transform_2d.h"
-#include "core/os/input.h"
#include "core/os/os.h"
#include "core/ustring.h"
#include "drivers/xaudio2/audio_driver_xaudio2.h"
#include "joypad_uwp.h"
-#include "main/input_default.h"
-#include "power_uwp.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering_server.h"
#include <fcntl.h>
#include <io.h>
@@ -50,10 +48,8 @@
#include <windows.h>
class OS_UWP : public OS {
-
public:
struct KeyEvent {
-
enum MessageType {
KEY_EVENT_MESSAGE,
CHAR_EVENT_MESSAGE
@@ -62,7 +58,8 @@ public:
bool alt, shift, control;
MessageType type;
bool pressed;
- unsigned int scancode;
+ unsigned int keycode;
+ unsigned int physical_keycode;
unsigned int unicode;
bool echo;
CorePhysicalKeyStatus status;
@@ -89,7 +86,7 @@ private:
bool outside;
int old_x, old_y;
Point2i center;
- VisualServer *visual_server;
+ RenderingServer *rendering_server;
int pressrc;
ContextEGL_UWP *gl_context;
@@ -102,8 +99,6 @@ private:
AudioDriverXAudio2 audio_driver;
- PowerUWP *power_manager;
-
MouseMode mouse_mode;
bool alt_mem;
bool gr_mem;
@@ -205,7 +200,7 @@ public:
virtual void delay_usec(uint32_t p_usec) const;
virtual uint64_t get_ticks_usec() const;
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr);
virtual Error kill(const ProcessID &p_pid);
virtual bool has_environment(const String &p_var) const;
@@ -239,7 +234,7 @@ public:
virtual bool has_touchscreen_ui_hint() const;
virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1);
+ virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
@@ -250,14 +245,10 @@ public:
void run();
- virtual bool get_swap_ok_cancel() { return true; }
+ virtual bool get_swap_cancel_ok() { return true; }
void input_event(const Ref<InputEvent> &p_event);
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
-
void queue_key_event(KeyEvent &p_event);
OS_UWP();
diff --git a/platform/uwp/power_uwp.cpp b/platform/uwp/power_uwp.cpp
deleted file mode 100644
index c6b4359392..0000000000
--- a/platform/uwp/power_uwp.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*************************************************************************/
-/* power_uwp.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "power_uwp.h"
-
-PowerUWP::PowerUWP() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerUWP::~PowerUWP() {
-}
-
-bool PowerUWP::UpdatePowerInfo() {
- // TODO, WinRT: Battery info is available on at least one WinRT platform (Windows Phone 8). Implement UpdatePowerInfo as appropriate. */
- /* Notes from SDL:
- - the Win32 function, GetSystemPowerStatus, is not available for use on WinRT
- - Windows Phone 8 has a 'Battery' class, which is documented as available for C++
- - More info on WP8's Battery class can be found at http://msdn.microsoft.com/library/windowsphone/develop/jj207231
- */
- return false;
-}
-
-OS::PowerState PowerUWP::get_power_state() {
- if (UpdatePowerInfo()) {
- return power_state;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN");
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerUWP::get_power_seconds_left() {
- if (UpdatePowerInfo()) {
- return nsecs_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
-
-int PowerUWP::get_power_percent_left() {
- if (UpdatePowerInfo()) {
- return percent_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
diff --git a/platform/uwp/thread_uwp.cpp b/platform/uwp/thread_uwp.cpp
index 9dc20a74e8..8e7bb144be 100644
--- a/platform/uwp/thread_uwp.cpp
+++ b/platform/uwp/thread_uwp.cpp
@@ -33,7 +33,6 @@
#include "core/os/memory.h"
Thread *ThreadUWP::create_func_uwp(ThreadCreateCallback p_callback, void *p_user, const Settings &) {
-
ThreadUWP *thread = memnew(ThreadUWP);
std::thread new_thread(p_callback, p_user);
@@ -43,18 +42,15 @@ Thread *ThreadUWP::create_func_uwp(ThreadCreateCallback p_callback, void *p_user
};
Thread::ID ThreadUWP::get_thread_id_func_uwp() {
-
return std::hash<std::thread::id>()(std::this_thread::get_id());
};
void ThreadUWP::wait_to_finish_func_uwp(Thread *p_thread) {
-
ThreadUWP *tp = static_cast<ThreadUWP *>(p_thread);
tp->thread.join();
};
Thread::ID ThreadUWP::get_id() const {
-
return std::hash<std::thread::id>()(thread.get_id());
};
@@ -63,11 +59,3 @@ void ThreadUWP::make_default() {
get_thread_id_func = get_thread_id_func_uwp;
wait_to_finish_func = wait_to_finish_func_uwp;
};
-
-ThreadUWP::ThreadUWP(){
-
-};
-
-ThreadUWP::~ThreadUWP(){
-
-};
diff --git a/platform/uwp/thread_uwp.h b/platform/uwp/thread_uwp.h
index a2d367ae2f..9b2a2590a8 100644
--- a/platform/uwp/thread_uwp.h
+++ b/platform/uwp/thread_uwp.h
@@ -38,23 +38,22 @@
#include <thread>
class ThreadUWP : public Thread {
-
std::thread thread;
static Thread *create_func_uwp(ThreadCreateCallback p_callback, void *, const Settings &);
static ID get_thread_id_func_uwp();
static void wait_to_finish_func_uwp(Thread *p_thread);
- ThreadUWP();
+ ThreadUWP() {}
public:
virtual ID get_id() const;
static void make_default();
- ~ThreadUWP();
+ ~ThreadUWP() {}
};
-#endif
+#endif // UWP_ENABLED
-#endif
+#endif // THREAD_UWP_H
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 892d734734..daffe59f34 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-Import('env')
+Import("env")
import os
from platform_methods import run_in_subprocess
@@ -8,23 +8,24 @@ import platform_windows_builders
common_win = [
"godot_windows.cpp",
- "context_gl_windows.cpp",
"crash_handler_windows.cpp",
"os_windows.cpp",
+ "display_server_windows.cpp",
"key_mapping_windows.cpp",
"joypad_windows.cpp",
- "power_windows.cpp",
- "windows_terminal_logger.cpp"
+ "windows_terminal_logger.cpp",
+ "vulkan_context_win.cpp",
+ "context_gl_windows.cpp",
]
-res_file = 'godot_res.rc'
+res_file = "godot_res.rc"
res_target = "godot_res" + env["OBJSUFFIX"]
res_obj = env.RES(res_target, res_file)
-prog = env.add_program('#bin/godot', common_win + res_obj, PROGSUFFIX=env["PROGSUFFIX"])
+prog = env.add_program("#bin/godot", common_win + res_obj, PROGSUFFIX=env["PROGSUFFIX"])
# Microsoft Visual Studio Project Generation
-if env['vsproj']:
+if env["vsproj"]:
env.vs_srcs = env.vs_srcs + ["platform/windows/" + res_file]
env.vs_srcs = env.vs_srcs + ["platform/windows/godot.natvis"]
for x in common_win:
diff --git a/platform/windows/context_gl_windows.cpp b/platform/windows/context_gl_windows.cpp
index ad62e3a306..1c32639a38 100644
--- a/platform/windows/context_gl_windows.cpp
+++ b/platform/windows/context_gl_windows.cpp
@@ -51,27 +51,22 @@
typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *);
void ContextGL_Windows::release_current() {
-
- wglMakeCurrent(hDC, NULL);
+ wglMakeCurrent(hDC, nullptr);
}
void ContextGL_Windows::make_current() {
-
wglMakeCurrent(hDC, hRC);
}
int ContextGL_Windows::get_window_width() {
-
return OS::get_singleton()->get_video_mode().width;
}
int ContextGL_Windows::get_window_height() {
-
return OS::get_singleton()->get_video_mode().height;
}
bool ContextGL_Windows::should_vsync_via_compositor() {
-
if (OS::get_singleton()->is_window_fullscreen() || !OS::get_singleton()->is_vsync_via_compositor_enabled()) {
return false;
}
@@ -88,7 +83,6 @@ bool ContextGL_Windows::should_vsync_via_compositor() {
}
void ContextGL_Windows::swap_buffers() {
-
SwapBuffers(hDC);
if (use_vsync) {
@@ -108,7 +102,6 @@ void ContextGL_Windows::swap_buffers() {
}
void ContextGL_Windows::set_use_vsync(bool p_use) {
-
vsync_via_compositor = p_use && should_vsync_via_compositor();
if (wglSwapIntervalEXT) {
@@ -120,14 +113,12 @@ void ContextGL_Windows::set_use_vsync(bool p_use) {
}
bool ContextGL_Windows::is_using_vsync() const {
-
return use_vsync;
}
#define _WGL_CONTEXT_DEBUG_BIT_ARB 0x0001
Error ContextGL_Windows::initialize() {
-
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1,
@@ -175,7 +166,6 @@ Error ContextGL_Windows::initialize() {
wglMakeCurrent(hDC, hRC);
if (opengl_3_context) {
-
int attribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3, //we want a 3.3 context
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
@@ -185,10 +175,10 @@ Error ContextGL_Windows::initialize() {
0
}; //zero indicates the end of the array
- PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; //pointer to the method
+ PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr; //pointer to the method
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
- if (wglCreateContextAttribsARB == NULL) //OpenGL 3.0 is not supported
+ if (wglCreateContextAttribsARB == nullptr) //OpenGL 3.0 is not supported
{
wglDeleteContext(hRC);
return ERR_CANT_CREATE;
@@ -199,7 +189,7 @@ Error ContextGL_Windows::initialize() {
wglDeleteContext(hRC);
return ERR_CANT_CREATE; // Return false
}
- wglMakeCurrent(hDC, NULL);
+ wglMakeCurrent(hDC, nullptr);
wglDeleteContext(hRC);
hRC = new_hRC;
@@ -217,7 +207,6 @@ Error ContextGL_Windows::initialize() {
}
ContextGL_Windows::ContextGL_Windows(HWND hwnd, bool p_opengl_3_context) {
-
opengl_3_context = p_opengl_3_context;
hWnd = hwnd;
use_vsync = false;
diff --git a/platform/windows/context_gl_windows.h b/platform/windows/context_gl_windows.h
index 280c5a1e3c..046e3437ea 100644
--- a/platform/windows/context_gl_windows.h
+++ b/platform/windows/context_gl_windows.h
@@ -44,7 +44,6 @@ typedef bool(APIENTRY *PFNWGLSWAPINTERVALEXTPROC)(int interval);
typedef int(APIENTRY *PFNWGLGETSWAPINTERVALEXTPROC)(void);
class ContextGL_Windows {
-
HDC hDC;
HGLRC hRC;
unsigned int pixel_format;
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index 6145751e00..02031ef6bb 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -38,11 +38,13 @@
// Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app
-#include <psapi.h>
#include <algorithm>
#include <iterator>
+#include <string>
#include <vector>
+#include <psapi.h>
+
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dbghelp.lib")
@@ -121,7 +123,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
DWORD cbNeeded;
std::vector<HMODULE> module_handles(1);
- if (OS::get_singleton() == NULL || OS::get_singleton()->is_disable_crash_handler() || IsDebuggerPresent()) {
+ if (OS::get_singleton() == nullptr || OS::get_singleton()->is_disable_crash_handler() || IsDebuggerPresent()) {
return EXCEPTION_CONTINUE_SEARCH;
}
@@ -131,7 +133,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
// Load the symbols:
- if (!SymInitialize(process, NULL, false))
+ if (!SymInitialize(process, nullptr, false))
return EXCEPTION_CONTINUE_SEARCH;
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
@@ -173,7 +175,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
msg = proj_settings->get("debug/settings/crash_handler/message");
}
- fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
+ fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
int n = 0;
do {
@@ -193,7 +195,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
n++;
}
- if (!StackWalk64(image_type, process, hThread, &frame, context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+ if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
break;
} while (frame.AddrReturn.Offset != 0 && n < 256);
diff --git a/platform/windows/crash_handler_windows.h b/platform/windows/crash_handler_windows.h
index adc548073c..66a4cac296 100644
--- a/platform/windows/crash_handler_windows.h
+++ b/platform/windows/crash_handler_windows.h
@@ -41,7 +41,6 @@ extern DWORD CrashHandlerException(EXCEPTION_POINTERS *ep);
#endif
class CrashHandler {
-
bool disabled;
public:
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 500736bd3f..6b503c1561 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -1,6 +1,9 @@
import methods
import os
+# To match other platforms
+STACK_SIZE = 8388608
+
def is_active():
return True
@@ -11,10 +14,10 @@ def get_name():
def can_build():
- if (os.name == "nt"):
+ if os.name == "nt":
# Building natively on Windows
# If VCINSTALLDIR is set in the OS environ, use traditional Godot logic to set up MSVC
- if (os.getenv("VCINSTALLDIR")): # MSVC, manual setup
+ if os.getenv("VCINSTALLDIR"): # MSVC, manual setup
return True
# Otherwise, let SCons find MSVC if installed, or else Mingw.
@@ -23,18 +26,18 @@ def can_build():
# null compiler.
return True
- if (os.name == "posix"):
+ if os.name == "posix":
# Cross-compiling with MinGW-w64 (old MinGW32 is not supported)
mingw32 = "i686-w64-mingw32-"
mingw64 = "x86_64-w64-mingw32-"
- if (os.getenv("MINGW32_PREFIX")):
+ if os.getenv("MINGW32_PREFIX"):
mingw32 = os.getenv("MINGW32_PREFIX")
- if (os.getenv("MINGW64_PREFIX")):
+ if os.getenv("MINGW64_PREFIX"):
mingw64 = os.getenv("MINGW64_PREFIX")
test = "gcc --version > /dev/null 2>&1"
- if (os.system(mingw64 + test) == 0 or os.system(mingw32 + test) == 0):
+ if os.system(mingw64 + test) == 0 or os.system(mingw32 + test) == 0:
return True
return False
@@ -45,47 +48,48 @@ def get_opts():
mingw32 = ""
mingw64 = ""
- if (os.name == "posix"):
+ if os.name == "posix":
mingw32 = "i686-w64-mingw32-"
mingw64 = "x86_64-w64-mingw32-"
- if (os.getenv("MINGW32_PREFIX")):
+ if os.getenv("MINGW32_PREFIX"):
mingw32 = os.getenv("MINGW32_PREFIX")
- if (os.getenv("MINGW64_PREFIX")):
+ if os.getenv("MINGW64_PREFIX"):
mingw64 = os.getenv("MINGW64_PREFIX")
return [
- ('mingw_prefix_32', 'MinGW prefix (Win32)', mingw32),
- ('mingw_prefix_64', 'MinGW prefix (Win64)', mingw64),
+ ("mingw_prefix_32", "MinGW prefix (Win32)", mingw32),
+ ("mingw_prefix_64", "MinGW prefix (Win64)", mingw64),
# Targeted Windows version: 7 (and later), minimum supported version
# XP support dropped after EOL due to missing API for IPv6 and other issues
# Vista support dropped after EOL due to GH-10243
- ('target_win_version', 'Targeted Windows version, >= 0x0601 (Windows 7)', '0x0601'),
- EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')),
- BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False),
- ('msvc_version', 'MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.', None),
- BoolVariable('use_mingw', 'Use the Mingw compiler, even if MSVC is installed. Only used on Windows.', False),
- BoolVariable('use_llvm', 'Use the LLVM compiler', False),
- BoolVariable('use_thinlto', 'Use ThinLTO', False),
+ ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"),
+ EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
+ EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("console", "gui")),
+ BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
+ ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None),
+ BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed. Only used on Windows.", False),
+ BoolVariable("use_llvm", "Use the LLVM compiler", False),
+ BoolVariable("use_thinlto", "Use ThinLTO", False),
]
def get_flags():
- return [
- ]
+ return []
def build_res_file(target, source, env):
- if (env["bits"] == "32"):
- cmdbase = env['mingw_prefix_32']
+ if env["bits"] == "32":
+ cmdbase = env["mingw_prefix_32"]
else:
- cmdbase = env['mingw_prefix_64']
- cmdbase = cmdbase + 'windres --include-dir . '
+ cmdbase = env["mingw_prefix_64"]
+ cmdbase = cmdbase + "windres --include-dir . "
import subprocess
+
for x in range(len(source)):
- cmd = cmdbase + '-i ' + str(source[x]) + ' -o ' + str(target[x])
+ cmd = cmdbase + "-i " + str(source[x]) + " -o " + str(target[x])
try:
out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
if len(out[1]):
@@ -97,12 +101,14 @@ def build_res_file(target, source, env):
def setup_msvc_manual(env):
"""Set up env to use MSVC manually, using VCINSTALLDIR"""
- if (env["bits"] != "default"):
- print("""
+ if env["bits"] != "default":
+ print(
+ """
Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console
(or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits
argument (example: scons p=windows) and SCons will attempt to detect what MSVC compiler will be executed and inform you.
- """)
+ """
+ )
raise SCons.Errors.UserError("Bits argument should not be used when using VCINSTALLDIR")
# Force bits arg
@@ -111,18 +117,23 @@ def setup_msvc_manual(env):
env["x86_libtheora_opt_vc"] = True
# find compiler manually
- compiler_version_str = methods.detect_visual_c_compiler_version(env['ENV'])
+ compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"])
print("Found MSVC compiler: " + compiler_version_str)
# If building for 64bit architecture, disable assembly optimisations for 32 bit builds (theora as of writing)... vc compiler for 64bit can not compile _asm
- if(compiler_version_str == "amd64" or compiler_version_str == "x86_amd64"):
+ if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64":
env["bits"] = "64"
env["x86_libtheora_opt_vc"] = False
print("Compiled program architecture will be a 64 bit executable (forcing bits=64).")
- elif (compiler_version_str == "x86" or compiler_version_str == "amd64_x86"):
+ elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86":
print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).")
else:
- print("Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR.")
+ print(
+ "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings"
+ " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this"
+ " build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."
+ )
+
def setup_msvc_auto(env):
"""Set up MSVC using SCons's auto-detection logic"""
@@ -135,94 +146,133 @@ def setup_msvc_auto(env):
# (Ideally we'd decide on the tool config before configuring any
# environment, and just set the env up once, but this function runs
# on an existing env so this is the simplest way.)
- env['MSVC_SETUP_RUN'] = False # Need to set this to re-run the tool
- env['MSVS_VERSION'] = None
- env['MSVC_VERSION'] = None
- env['TARGET_ARCH'] = None
- if env['bits'] != 'default':
- env['TARGET_ARCH'] = {'32': 'x86', '64': 'x86_64'}[env['bits']]
- if env.has_key('msvc_version'):
- env['MSVC_VERSION'] = env['msvc_version']
- env.Tool('msvc')
- env.Tool('mssdk') # we want the MS SDK
+ env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool
+ env["MSVS_VERSION"] = None
+ env["MSVC_VERSION"] = None
+ env["TARGET_ARCH"] = None
+ if env["bits"] != "default":
+ env["TARGET_ARCH"] = {"32": "x86", "64": "x86_64"}[env["bits"]]
+ if env.has_key("msvc_version"):
+ env["MSVC_VERSION"] = env["msvc_version"]
+ env.Tool("msvc")
+ env.Tool("mssdk") # we want the MS SDK
# Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015
# Get actual target arch into bits (it may be "default" at this point):
- if env['TARGET_ARCH'] in ('amd64', 'x86_64'):
- env['bits'] = '64'
+ if env["TARGET_ARCH"] in ("amd64", "x86_64"):
+ env["bits"] = "64"
else:
- env['bits'] = '32'
- print("Found MSVC version %s, arch %s, bits=%s" % (env['MSVC_VERSION'], env['TARGET_ARCH'], env['bits']))
- if env['TARGET_ARCH'] in ('amd64', 'x86_64'):
+ env["bits"] = "32"
+ print("Found MSVC version %s, arch %s, bits=%s" % (env["MSVC_VERSION"], env["TARGET_ARCH"], env["bits"]))
+ if env["TARGET_ARCH"] in ("amd64", "x86_64"):
env["x86_libtheora_opt_vc"] = False
+
def setup_mingw(env):
"""Set up env for use with mingw"""
# Nothing to do here
print("Using MinGW")
pass
+
def configure_msvc(env, manual_msvc_config):
"""Configure env to work with MSVC"""
# Build type
-
- if (env["target"] == "release"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Append(CCFLAGS=['/O2'])
- else: # optimize for size
- env.Append(CCFLAGS=['/O1'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:WINDOWS'])
- env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup'])
- env.Append(LINKFLAGS=['/OPT:REF'])
-
- elif (env["target"] == "release_debug"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Append(CCFLAGS=['/O2'])
- else: # optimize for size
- env.Append(CCFLAGS=['/O1'])
- env.AppendUnique(CPPDEFINES = ['DEBUG_ENABLED'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
- env.Append(LINKFLAGS=['/OPT:REF'])
-
- elif (env["target"] == "debug"):
- env.AppendUnique(CCFLAGS=['/Z7', '/Od', '/EHsc'])
- env.AppendUnique(CPPDEFINES = ['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED',
- 'D3D_DEBUG_INFO'])
- env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
- env.Append(LINKFLAGS=['/DEBUG'])
-
- if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes"):
- env.AppendUnique(CCFLAGS=['/Z7'])
- env.AppendUnique(LINKFLAGS=['/DEBUG'])
+ if env["tests"]:
+ env["windows_subsystem"] = "console"
+
+ if env["target"] == "release":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(CCFLAGS=["/O2"])
+ else: # optimize for size
+ env.Append(CCFLAGS=["/O1"])
+ env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"])
+ env.Append(LINKFLAGS=["/OPT:REF"])
+
+ elif env["target"] == "release_debug":
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(CCFLAGS=["/O2"])
+ else: # optimize for size
+ env.Append(CCFLAGS=["/O1"])
+ env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["/OPT:REF"])
+
+ elif env["target"] == "debug":
+ env.AppendUnique(CCFLAGS=["/Z7", "/Od", "/EHsc"])
+ env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"])
+ env.Append(LINKFLAGS=["/DEBUG"])
+
+ if env["debug_symbols"] == "full" or env["debug_symbols"] == "yes":
+ env.AppendUnique(CCFLAGS=["/Z7"])
+ env.AppendUnique(LINKFLAGS=["/DEBUG"])
+
+ if env["windows_subsystem"] == "gui":
+ env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"])
+ else:
+ env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
+ env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
## Compile/link flags
- env.AppendUnique(CCFLAGS=['/MT', '/Gd', '/GR', '/nologo'])
- if int(env['MSVC_VERSION'].split('.')[0]) >= 14: #vs2015 and later
- env.AppendUnique(CCFLAGS=['/utf-8'])
- env.AppendUnique(CXXFLAGS=['/TP']) # assume all sources are C++
- if manual_msvc_config: # should be automatic if SCons found it
+ env.AppendUnique(CCFLAGS=["/MT", "/Gd", "/GR", "/nologo"])
+ if int(env["MSVC_VERSION"].split(".")[0]) >= 14: # vs2015 and later
+ env.AppendUnique(CCFLAGS=["/utf-8"])
+ env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
+ if manual_msvc_config: # should be automatic if SCons found it
if os.getenv("WindowsSdkDir") is not None:
env.Prepend(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"])
else:
print("Missing environment variable: WindowsSdkDir")
- env.AppendUnique(CPPDEFINES = ['WINDOWS_ENABLED', 'OPENGL_ENABLED',
- 'WASAPI_ENABLED', 'WINMIDI_ENABLED',
- 'TYPED_METHOD_BIND',
- 'WIN32', 'MSVC',
- 'WINVER=%s' % env["target_win_version"],
- '_WIN32_WINNT=%s' % env["target_win_version"]])
- env.AppendUnique(CPPDEFINES=['NOMINMAX']) # disable bogus min/max WinDef.h macros
+ env.AppendUnique(
+ CPPDEFINES=[
+ "WINDOWS_ENABLED",
+ "WASAPI_ENABLED",
+ "WINMIDI_ENABLED",
+ "TYPED_METHOD_BIND",
+ "WIN32",
+ "MSVC",
+ "WINVER=%s" % env["target_win_version"],
+ "_WIN32_WINNT=%s" % env["target_win_version"],
+ ]
+ )
+ env.AppendUnique(CPPDEFINES=["NOMINMAX"]) # disable bogus min/max WinDef.h macros
if env["bits"] == "64":
- env.AppendUnique(CPPDEFINES=['_WIN64'])
+ env.AppendUnique(CPPDEFINES=["_WIN64"])
## Libs
- LIBS = ['winmm', 'opengl32', 'dsound', 'kernel32', 'ole32', 'oleaut32',
- 'user32', 'gdi32', 'IPHLPAPI', 'Shlwapi', 'wsock32', 'Ws2_32',
- 'shell32', 'advapi32', 'dinput8', 'dxguid', 'imm32', 'bcrypt','Avrt',
- 'dwmapi']
+ LIBS = [
+ "winmm",
+ "dsound",
+ "kernel32",
+ "ole32",
+ "oleaut32",
+ "user32",
+ "gdi32",
+ "IPHLPAPI",
+ "Shlwapi",
+ "wsock32",
+ "Ws2_32",
+ "shell32",
+ "advapi32",
+ "dinput8",
+ "dxguid",
+ "imm32",
+ "bcrypt",
+ "Avrt",
+ "dwmapi",
+ ]
+
+ env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"])
+ if not env["builtin_vulkan"]:
+ LIBS += ["vulkan"]
+ else:
+ LIBS += ["cfgmgr32"]
+
+ # env.AppendUnique(CPPDEFINES = ['OPENGL_ENABLED'])
+ LIBS += ["opengl32"]
+
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
if manual_msvc_config:
@@ -233,21 +283,24 @@ def configure_msvc(env, manual_msvc_config):
## LTO
- if (env["use_lto"]):
- env.AppendUnique(CCFLAGS=['/GL'])
- env.AppendUnique(ARFLAGS=['/LTCG'])
+ if env["use_lto"]:
+ env.AppendUnique(CCFLAGS=["/GL"])
+ env.AppendUnique(ARFLAGS=["/LTCG"])
if env["progress"]:
- env.AppendUnique(LINKFLAGS=['/LTCG:STATUS'])
+ env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
else:
- env.AppendUnique(LINKFLAGS=['/LTCG'])
+ env.AppendUnique(LINKFLAGS=["/LTCG"])
if manual_msvc_config:
env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")])
env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")])
# Incremental linking fix
- env['BUILDERS']['ProgramOriginal'] = env['BUILDERS']['Program']
- env['BUILDERS']['Program'] = methods.precious_program
+ env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"]
+ env["BUILDERS"]["Program"] = methods.precious_program
+
+ env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+
def configure_mingw(env):
# Workaround for MinGW. See:
@@ -256,118 +309,155 @@ def configure_mingw(env):
## Build type
- if (env["target"] == "release"):
- env.Append(CCFLAGS=['-msse2'])
+ if env["tests"]:
+ env["windows_subsystem"] = "console"
+
+ if env["target"] == "release":
+ env.Append(CCFLAGS=["-msse2"])
- if (env["optimize"] == "speed"): #optimize for speed (default)
- if (env["bits"] == "64"):
- env.Append(CCFLAGS=['-O3'])
+ if env["optimize"] == "speed": # optimize for speed (default)
+ if env["bits"] == "64":
+ env.Append(CCFLAGS=["-O3"])
else:
- env.Append(CCFLAGS=['-O2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
-
-
- env.Append(LINKFLAGS=['-Wl,--subsystem,windows'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "release_debug"):
- env.Append(CCFLAGS=['-O2'])
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Append(CCFLAGS=['-O2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
-
- elif (env["target"] == "debug"):
- env.Append(CCFLAGS=['-g3'])
- env.Append(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
+ env.Append(CCFLAGS=["-O2"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+
+ elif env["target"] == "release_debug":
+ env.Append(CCFLAGS=["-O2"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+ if env["debug_symbols"] == "yes":
+ env.Prepend(CCFLAGS=["-g1"])
+ if env["debug_symbols"] == "full":
+ env.Prepend(CCFLAGS=["-g2"])
+ if env["optimize"] == "speed": # optimize for speed (default)
+ env.Append(CCFLAGS=["-O2"])
+ else: # optimize for size
+ env.Prepend(CCFLAGS=["-Os"])
+
+ elif env["target"] == "debug":
+ env.Append(CCFLAGS=["-g3"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
+
+ if env["windows_subsystem"] == "gui":
+ env.Append(LINKFLAGS=["-Wl,--subsystem,windows"])
+ else:
+ env.Append(LINKFLAGS=["-Wl,--subsystem,console"])
+ env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
## Compiler configuration
- if (os.name == "nt"):
- # Force splitting libmodules.a in multiple chunks to work around
- # issues reaching the linker command line size limit, which also
- # seem to induce huge slowdown for 'ar' (GH-30892).
- env['split_libmodules'] = True
- else:
+ if os.name != "nt":
env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation
- if (env["bits"] == "default"):
- if (os.name == "nt"):
+ if env["bits"] == "default":
+ if os.name == "nt":
env["bits"] = "64" if "PROGRAMFILES(X86)" in os.environ else "32"
- else: # default to 64-bit on Linux
+ else: # default to 64-bit on Linux
env["bits"] = "64"
mingw_prefix = ""
- if (env["bits"] == "32"):
- env.Append(LINKFLAGS=['-static'])
- env.Append(LINKFLAGS=['-static-libgcc'])
- env.Append(LINKFLAGS=['-static-libstdc++'])
+ if env["bits"] == "32":
+ env.Append(LINKFLAGS=["-static"])
+ env.Append(LINKFLAGS=["-static-libgcc"])
+ env.Append(LINKFLAGS=["-static-libstdc++"])
mingw_prefix = env["mingw_prefix_32"]
else:
- env.Append(LINKFLAGS=['-static'])
+ env.Append(LINKFLAGS=["-static"])
mingw_prefix = env["mingw_prefix_64"]
- if env['use_llvm']:
+ if env["use_llvm"]:
env["CC"] = mingw_prefix + "clang"
- env['AS'] = mingw_prefix + "as"
+ env["AS"] = mingw_prefix + "as"
env["CXX"] = mingw_prefix + "clang++"
- env['AR'] = mingw_prefix + "ar"
- env['RANLIB'] = mingw_prefix + "ranlib"
+ env["AR"] = mingw_prefix + "ar"
+ env["RANLIB"] = mingw_prefix + "ranlib"
env["LINK"] = mingw_prefix + "clang++"
else:
env["CC"] = mingw_prefix + "gcc"
- env['AS'] = mingw_prefix + "as"
- env['CXX'] = mingw_prefix + "g++"
- env['AR'] = mingw_prefix + "gcc-ar"
- env['RANLIB'] = mingw_prefix + "gcc-ranlib"
- env['LINK'] = mingw_prefix + "g++"
+ env["AS"] = mingw_prefix + "as"
+ env["CXX"] = mingw_prefix + "g++"
+ env["AR"] = mingw_prefix + "gcc-ar"
+ env["RANLIB"] = mingw_prefix + "gcc-ranlib"
+ env["LINK"] = mingw_prefix + "g++"
env["x86_libtheora_opt_gcc"] = True
- if env['use_lto']:
- if not env['use_llvm'] and env.GetOption("num_jobs") > 1:
- env.Append(CCFLAGS=['-flto'])
- env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))])
+ if env["use_lto"]:
+ if not env["use_llvm"] and env.GetOption("num_jobs") > 1:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))])
else:
- if env['use_thinlto']:
- env.Append(CCFLAGS=['-flto=thin'])
- env.Append(LINKFLAGS=['-flto=thin'])
+ if env["use_thinlto"]:
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
else:
- env.Append(CCFLAGS=['-flto'])
- env.Append(LINKFLAGS=['-flto'])
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
## Compile flags
- env.Append(CCFLAGS=['-mwindows'])
- env.Append(CPPDEFINES=['WINDOWS_ENABLED', 'OPENGL_ENABLED', 'WASAPI_ENABLED', 'WINMIDI_ENABLED'])
- env.Append(CPPDEFINES=[('WINVER', env['target_win_version']), ('_WIN32_WINNT', env['target_win_version'])])
- env.Append(LIBS=['mingw32', 'opengl32', 'dsound', 'ole32', 'd3d9', 'winmm', 'gdi32', 'iphlpapi', 'shlwapi', 'wsock32', 'ws2_32', 'kernel32', 'oleaut32', 'dinput8', 'dxguid', 'ksuser', 'imm32', 'bcrypt', 'avrt', 'uuid', 'dwmapi'])
+ env.Append(CCFLAGS=["-mwindows"])
+
+ env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"])
+ env.Append(CPPDEFINES=[("WINVER", env["target_win_version"]), ("_WIN32_WINNT", env["target_win_version"])])
+ env.Append(
+ LIBS=[
+ "mingw32",
+ "dsound",
+ "ole32",
+ "d3d9",
+ "winmm",
+ "gdi32",
+ "iphlpapi",
+ "shlwapi",
+ "wsock32",
+ "ws2_32",
+ "kernel32",
+ "oleaut32",
+ "dinput8",
+ "dxguid",
+ "ksuser",
+ "imm32",
+ "bcrypt",
+ "avrt",
+ "uuid",
+ "dwmapi",
+ ]
+ )
+
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ if not env["builtin_vulkan"]:
+ env.Append(LIBS=["vulkan"])
+ else:
+ env.Append(LIBS=["cfgmgr32"])
- env.Append(CPPDEFINES=['MINGW_ENABLED', ('MINGW_HAS_SECURE_API', 1)])
+ ## TODO !!! Re-enable when OpenGLES Rendering Device is implemented !!!
+ # env.Append(CPPDEFINES=['OPENGL_ENABLED'])
+ env.Append(LIBS=["opengl32"])
+
+ env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)])
# resrc
- env.Append(BUILDERS={'RES': env.Builder(action=build_res_file, suffix='.o', src_suffix='.rc')})
+ env.Append(BUILDERS={"RES": env.Builder(action=build_res_file, suffix=".o", src_suffix=".rc")})
+
def configure(env):
# At this point the env has been set up with basic tools/compilers.
- env.Prepend(CPPPATH=['#platform/windows'])
+ env.Prepend(CPPPATH=["#platform/windows"])
- print("Configuring for Windows: target=%s, bits=%s" % (env['target'], env['bits']))
+ print("Configuring for Windows: target=%s, bits=%s" % (env["target"], env["bits"]))
- if (os.name == "nt"):
- env['ENV'] = os.environ # this makes build less repeatable, but simplifies some things
- env['ENV']['TMP'] = os.environ['TMP']
+ if os.name == "nt":
+ env["ENV"] = os.environ # this makes build less repeatable, but simplifies some things
+ env["ENV"]["TMP"] = os.environ["TMP"]
# First figure out which compiler, version, and target arch we're using
if os.getenv("VCINSTALLDIR") and not env["use_mingw"]:
@@ -375,7 +465,7 @@ def configure(env):
setup_msvc_manual(env)
env.msvc = True
manual_msvc_config = True
- elif env.get('MSVC_VERSION', '') and not env["use_mingw"]:
+ elif env.get("MSVC_VERSION", "") and not env["use_mingw"]:
setup_msvc_auto(env)
env.msvc = True
manual_msvc_config = False
@@ -387,5 +477,5 @@ def configure(env):
if env.msvc:
configure_msvc(env, manual_msvc_config)
- else: # MinGW
+ else: # MinGW
configure_mingw(env)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
new file mode 100644
index 0000000000..dfbb734ee4
--- /dev/null
+++ b/platform/windows/display_server_windows.cpp
@@ -0,0 +1,3266 @@
+/*************************************************************************/
+/* display_server_windows.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_windows.h"
+
+#include "core/io/marshalls.h"
+#include "core/math/geometry_2d.h"
+#include "main/main.h"
+#include "os_windows.h"
+#include "scene/resources/texture.h"
+
+#include <avrt.h>
+
+#ifdef DEBUG_ENABLED
+static String format_error_message(DWORD id) {
+ LPWSTR messageBuffer = nullptr;
+ size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr);
+
+ String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size);
+
+ LocalFree(messageBuffer);
+
+ return msg;
+}
+#endif // DEBUG_ENABLED
+
+bool DisplayServerWindows::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_SUBWINDOWS:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_MOUSE:
+ case FEATURE_MOUSE_WARP:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
+ case FEATURE_CONSOLE_WINDOW:
+ case FEATURE_IME:
+ case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_HIDPI:
+ case FEATURE_ICON:
+ case FEATURE_NATIVE_ICON:
+ case FEATURE_SWAP_BUFFERS:
+ case FEATURE_KEEP_SCREEN_ON:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerWindows::get_name() const {
+ return "Windows";
+}
+
+void DisplayServerWindows::alert(const String &p_alert, const String &p_title) {
+ MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
+}
+
+void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
+ if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) {
+ WindowData &wd = windows[MAIN_WINDOW_ID];
+
+ RECT clipRect;
+ GetClientRect(wd.hWnd, &clipRect);
+ ClientToScreen(wd.hWnd, (POINT *)&clipRect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&clipRect.right);
+ ClipCursor(&clipRect);
+ if (p_mode == MOUSE_MODE_CAPTURED) {
+ center = window_get_size() / 2;
+ POINT pos = { (int)center.x, (int)center.y };
+ ClientToScreen(wd.hWnd, &pos);
+ SetCursorPos(pos.x, pos.y);
+ SetCapture(wd.hWnd);
+ }
+ } else {
+ ReleaseCapture();
+ ClipCursor(nullptr);
+ }
+
+ if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) {
+ if (hCursor == nullptr) {
+ hCursor = SetCursor(nullptr);
+ } else {
+ SetCursor(nullptr);
+ }
+ } else {
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+ }
+}
+
+void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
+ _THREAD_SAFE_METHOD_
+
+ if (mouse_mode == p_mode)
+ return;
+
+ mouse_mode = p_mode;
+
+ _set_mouse_mode_impl(p_mode);
+}
+
+DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const {
+ return mouse_mode;
+}
+
+void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) {
+ _THREAD_SAFE_METHOD_
+
+ if (!windows.has(last_focused_window)) {
+ return; //no window focused?
+ }
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ old_x = p_to.x;
+ old_y = p_to.y;
+ } else {
+ POINT p;
+ p.x = p_to.x;
+ p.y = p_to.y;
+ ClientToScreen(windows[last_focused_window].hWnd, &p);
+
+ SetCursorPos(p.x, p.y);
+ }
+}
+
+Point2i DisplayServerWindows::mouse_get_position() const {
+ POINT p;
+ GetCursorPos(&p);
+ return Point2i(p.x, p.y);
+ //return Point2(old_x, old_y);
+}
+
+int DisplayServerWindows::mouse_get_button_state() const {
+ return last_button_state;
+}
+
+void DisplayServerWindows::clipboard_set(const String &p_text) {
+ _THREAD_SAFE_METHOD_
+
+ if (!windows.has(last_focused_window)) {
+ return; //no window focused?
+ }
+
+ // Convert LF line endings to CRLF in clipboard content
+ // Otherwise, line endings won't be visible when pasted in other software
+ String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n
+
+ if (!OpenClipboard(windows[last_focused_window].hWnd)) {
+ ERR_FAIL_MSG("Unable to open clipboard.");
+ }
+ EmptyClipboard();
+
+ Char16String utf16 = text.utf16();
+ HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (utf16.length() + 1) * sizeof(WCHAR));
+ ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents.");
+
+ LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem);
+ memcpy(lptstrCopy, utf16.get_data(), (utf16.length() + 1) * sizeof(WCHAR));
+ GlobalUnlock(mem);
+
+ SetClipboardData(CF_UNICODETEXT, mem);
+
+ // set the CF_TEXT version (not needed?)
+ CharString utf8 = text.utf8();
+ mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1);
+ ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents.");
+
+ LPTSTR ptr = (LPTSTR)GlobalLock(mem);
+ memcpy(ptr, utf8.get_data(), utf8.length());
+ ptr[utf8.length()] = 0;
+ GlobalUnlock(mem);
+
+ SetClipboardData(CF_TEXT, mem);
+
+ CloseClipboard();
+}
+
+String DisplayServerWindows::clipboard_get() const {
+ _THREAD_SAFE_METHOD_
+
+ if (!windows.has(last_focused_window)) {
+ return String(); //no window focused?
+ }
+
+ String ret;
+ if (!OpenClipboard(windows[last_focused_window].hWnd)) {
+ ERR_FAIL_V_MSG("", "Unable to open clipboard.");
+ };
+
+ if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
+ HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
+ if (mem != nullptr) {
+ LPWSTR ptr = (LPWSTR)GlobalLock(mem);
+ if (ptr != nullptr) {
+ ret = String::utf16((const char16_t *)ptr);
+ GlobalUnlock(mem);
+ };
+ };
+
+ } else if (IsClipboardFormatAvailable(CF_TEXT)) {
+ HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
+ if (mem != nullptr) {
+ LPTSTR ptr = (LPTSTR)GlobalLock(mem);
+ if (ptr != nullptr) {
+ ret.parse_utf8((const char *)ptr);
+ GlobalUnlock(mem);
+ };
+ };
+ };
+
+ CloseClipboard();
+
+ return ret;
+}
+
+typedef struct {
+ int count;
+ int screen;
+ HMONITOR monitor;
+} EnumScreenData;
+
+static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumScreenData *data = (EnumScreenData *)dwData;
+ if (data->monitor == hMonitor) {
+ data->screen = data->count;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ int *data = (int *)dwData;
+ (*data)++;
+ return TRUE;
+}
+
+int DisplayServerWindows::get_screen_count() const {
+ _THREAD_SAFE_METHOD_
+
+ int data = 0;
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcCount, (LPARAM)&data);
+ return data;
+}
+
+typedef struct {
+ int count;
+ int screen;
+ Point2 pos;
+} EnumPosData;
+
+static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumPosData *data = (EnumPosData *)dwData;
+ if (data->count == data->screen) {
+ data->pos.x = lprcMonitor->left;
+ data->pos.y = lprcMonitor->top;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+Point2i DisplayServerWindows::screen_get_position(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumPosData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Point2() };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcPos, (LPARAM)&data);
+ return data.pos;
+}
+
+typedef struct {
+ int count;
+ int screen;
+ Size2 size;
+} EnumSizeData;
+
+typedef struct {
+ int count;
+ int screen;
+ Rect2i rect;
+} EnumRectData;
+
+static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumSizeData *data = (EnumSizeData *)dwData;
+ if (data->count == data->screen) {
+ data->size.x = lprcMonitor->right - lprcMonitor->left;
+ data->size.y = lprcMonitor->bottom - lprcMonitor->top;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+Size2i DisplayServerWindows::screen_get_size(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumSizeData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Size2() };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcSize, (LPARAM)&data);
+ return data.size;
+}
+
+static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumRectData *data = (EnumRectData *)dwData;
+ if (data->count == data->screen) {
+ MONITORINFO minfo;
+ zeromem(&minfo, sizeof(MONITORINFO));
+ minfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoA(hMonitor, &minfo);
+
+ data->rect.position.x = minfo.rcWork.left;
+ data->rect.position.y = minfo.rcWork.top;
+ data->rect.size.x = minfo.rcWork.right - minfo.rcWork.left;
+ data->rect.size.y = minfo.rcWork.bottom - minfo.rcWork.top;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumRectData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Rect2i() };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcUsableSize, (LPARAM)&data);
+ return data.rect;
+}
+
+typedef struct {
+ int count;
+ int screen;
+ int dpi;
+} EnumDpiData;
+
+enum _MonitorDpiType {
+ MDT_Effective_DPI = 0,
+ MDT_Angular_DPI = 1,
+ MDT_Raw_DPI = 2,
+ MDT_Default = MDT_Effective_DPI
+};
+
+static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) {
+ int dpiX = 96, dpiY = 96;
+
+ static HMODULE Shcore = nullptr;
+ typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY);
+ static GetDPIForMonitor_t getDPIForMonitor = nullptr;
+
+ if (Shcore == nullptr) {
+ Shcore = LoadLibraryW(L"Shcore.dll");
+ getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : nullptr;
+
+ if ((Shcore == nullptr) || (getDPIForMonitor == nullptr)) {
+ if (Shcore)
+ FreeLibrary(Shcore);
+ Shcore = (HMODULE)INVALID_HANDLE_VALUE;
+ }
+ }
+
+ UINT x = 0, y = 0;
+ HRESULT hr = E_FAIL;
+ if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) {
+ hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y);
+ if (SUCCEEDED(hr) && (x > 0) && (y > 0)) {
+ dpiX = (int)x;
+ dpiY = (int)y;
+ }
+ } else {
+ static int overallX = 0, overallY = 0;
+ if (overallX <= 0 || overallY <= 0) {
+ HDC hdc = GetDC(nullptr);
+ if (hdc) {
+ overallX = GetDeviceCaps(hdc, LOGPIXELSX);
+ overallY = GetDeviceCaps(hdc, LOGPIXELSY);
+ ReleaseDC(nullptr, hdc);
+ }
+ }
+ if (overallX > 0 && overallY > 0) {
+ dpiX = overallX;
+ dpiY = overallY;
+ }
+ }
+
+ return (dpiX + dpiY) / 2;
+}
+
+static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumDpiData *data = (EnumDpiData *)dwData;
+ if (data->count == data->screen) {
+ data->dpi = QueryDpiForMonitor(hMonitor);
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+int DisplayServerWindows::screen_get_dpi(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumDpiData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, 72 };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
+ return data.dpi;
+}
+
+bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const {
+#ifndef _MSC_VER
+#warning touchscreen not working
+#endif
+ return false;
+}
+
+void DisplayServerWindows::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) {
+}
+
+DisplayServer::ScreenOrientation DisplayServerWindows::screen_get_orientation(int p_screen) const {
+ return SCREEN_LANDSCAPE;
+}
+
+void DisplayServerWindows::screen_set_keep_on(bool p_enable) {
+}
+
+bool DisplayServerWindows::screen_is_kept_on() const {
+ return false;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const {
+ _THREAD_SAFE_METHOD_
+
+ Vector<DisplayServer::WindowID> ret;
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+
+DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(const Point2i &p_position) const {
+ POINT p;
+ p.x = p_position.x;
+ p.y = p_position.y;
+ HWND hwnd = WindowFromPoint(p);
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (E->get().hWnd == hwnd) {
+ return E->key();
+ }
+ }
+
+ return INVALID_WINDOW_ID;
+}
+
+DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ WindowID window_id = _create_window(p_mode, p_flags, p_rect);
+ ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window.");
+
+ WindowData &wd = windows[window_id];
+
+ if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) {
+ wd.resizable = false;
+ }
+ if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
+ wd.borderless = true;
+ }
+ if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) {
+ wd.always_on_top = true;
+ }
+ if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
+ wd.no_focus = true;
+ }
+
+ return window_id;
+}
+
+void DisplayServerWindows::show_window(WindowID p_id) {
+ WindowData &wd = windows[p_id];
+
+ if (p_id != MAIN_WINDOW_ID) {
+ _update_window_style(p_id);
+ }
+
+ ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window
+ if (!wd.no_focus) {
+ SetForegroundWindow(wd.hWnd); // Slightly Higher Priority
+ SetFocus(wd.hWnd); // Sets Keyboard Focus To
+ }
+}
+
+void DisplayServerWindows::delete_sub_window(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted.");
+
+ WindowData &wd = windows[p_window];
+
+ while (wd.transient_children.size()) {
+ window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);
+ }
+
+ if (wd.transient_parent != INVALID_WINDOW_ID) {
+ window_set_transient(p_window, INVALID_WINDOW_ID);
+ }
+
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_destroy(p_window);
+ }
+#endif
+
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
+ wintab_WTClose(windows[p_window].wtctx);
+ windows[p_window].wtctx = 0;
+ }
+ DestroyWindow(windows[p_window].hWnd);
+ windows.erase(p_window);
+}
+
+void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].instance_id = p_instance;
+}
+
+ObjectID DisplayServerWindows::window_get_attached_instance_id(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
+ return windows[p_window].instance_id;
+}
+
+void DisplayServerWindows::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].rect_changed_callback = p_callable;
+}
+
+void DisplayServerWindows::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].event_callback = p_callable;
+}
+
+void DisplayServerWindows::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].input_event_callback = p_callable;
+}
+
+void DisplayServerWindows::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].input_text_callback = p_callable;
+}
+
+void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].drop_files_callback = p_callable;
+}
+
+void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data()));
+}
+
+void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].mpath = p_region;
+ _update_window_mouse_passthrough(p_window);
+}
+
+void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) {
+ if (windows[p_window].mpath.size() == 0) {
+ SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE);
+ } else {
+ POINT *points = (POINT *)memalloc(sizeof(POINT) * windows[p_window].mpath.size());
+ for (int i = 0; i < windows[p_window].mpath.size(); i++) {
+ if (windows[p_window].borderless) {
+ points[i].x = windows[p_window].mpath[i].x;
+ points[i].y = windows[p_window].mpath[i].y;
+ } else {
+ points[i].x = windows[p_window].mpath[i].x + GetSystemMetrics(SM_CXSIZEFRAME);
+ points[i].y = windows[p_window].mpath[i].y + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION);
+ }
+ }
+
+ HRGN region = CreatePolygonRgn(points, windows[p_window].mpath.size(), ALTERNATE);
+ SetWindowRgn(windows[p_window].hWnd, region, TRUE);
+ DeleteObject(region);
+ memfree(points);
+ }
+}
+
+int DisplayServerWindows::window_get_current_screen(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+
+ EnumScreenData data = { 0, 0, MonitorFromWindow(windows[p_window].hWnd, MONITOR_DEFAULTTONEAREST) };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcScreen, (LPARAM)&data);
+ return data.screen;
+}
+
+void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ ERR_FAIL_INDEX(p_screen, get_screen_count());
+
+ Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
+ window_set_position(ofs + screen_get_position(p_screen), p_window);
+}
+
+Point2i DisplayServerWindows::window_get_position(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
+ const WindowData &wd = windows[p_window];
+
+ if (wd.minimized) {
+ return wd.last_pos;
+ }
+
+ POINT point;
+ point.x = 0;
+ point.y = 0;
+
+ ClientToScreen(wd.hWnd, &point);
+
+ return Point2i(point.x, point.y);
+
+#if 0
+ //do not use this method, as it includes windows decorations
+ RECT r;
+ GetWindowRect(wd.hWnd, &r);
+ return Point2(r.left, r.top);
+#endif
+}
+
+void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) {
+ POINT mouse_pos;
+ if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) {
+ if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) {
+ old_x = mouse_pos.x;
+ old_y = mouse_pos.y;
+ old_invalid = false;
+ Input::get_singleton()->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y));
+ }
+ }
+}
+
+void DisplayServerWindows::window_set_position(const Point2i &p_position, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.fullscreen)
+ return;
+#if 0
+ //wrong needs to account properly for decorations
+ RECT r;
+ GetWindowRect(wd.hWnd, &r);
+ MoveWindow(wd.hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE);
+#else
+
+ RECT rc;
+ rc.left = p_position.x;
+ rc.right = p_position.x + wd.width;
+ rc.bottom = p_position.y + wd.height;
+ rc.top = p_position.y;
+
+ const DWORD style = GetWindowLongPtr(wd.hWnd, GWL_STYLE);
+ const DWORD exStyle = GetWindowLongPtr(wd.hWnd, GWL_EXSTYLE);
+
+ AdjustWindowRectEx(&rc, style, false, exStyle);
+ MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
+#endif
+ // Don't let the mouse leave the window when moved
+ if (mouse_mode == MOUSE_MODE_CONFINED) {
+ RECT rect;
+ GetClientRect(wd.hWnd, &rect);
+ ClientToScreen(wd.hWnd, (POINT *)&rect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&rect.right);
+ ClipCursor(&rect);
+ }
+
+ wd.last_pos = p_position;
+ _update_real_mouse_position(p_window);
+}
+
+void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_parent) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(p_window == p_parent);
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd_window = windows[p_window];
+
+ ERR_FAIL_COND(wd_window.transient_parent == p_parent);
+
+ ERR_FAIL_COND_MSG(wd_window.always_on_top, "Windows with the 'on top' can't become transient.");
+
+ if (p_parent == INVALID_WINDOW_ID) {
+ //remove transient
+
+ ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
+ ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
+
+ WindowData &wd_parent = windows[wd_window.transient_parent];
+
+ wd_window.transient_parent = INVALID_WINDOW_ID;
+ wd_parent.transient_children.erase(p_window);
+
+ SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
+ } else {
+ ERR_FAIL_COND(!windows.has(p_parent));
+ ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
+ WindowData &wd_parent = windows[p_parent];
+
+ wd_window.transient_parent = p_parent;
+ wd_parent.transient_children.insert(p_window);
+
+ SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
+ }
+}
+
+void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+ ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ wd.max_size = p_size;
+}
+
+Size2i DisplayServerWindows::window_get_max_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ return wd.max_size;
+}
+
+void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if ((p_size != Size2()) && (wd.max_size != Size2()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+ ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ wd.min_size = p_size;
+}
+
+Size2i DisplayServerWindows::window_get_min_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+ return wd.min_size;
+}
+
+void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ int w = p_size.width;
+ int h = p_size.height;
+
+ wd.width = w;
+ wd.height = h;
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_resize(p_window, w, h);
+ }
+#endif
+
+ if (wd.fullscreen) {
+ return;
+ }
+
+ RECT rect;
+ GetWindowRect(wd.hWnd, &rect);
+
+ if (!wd.borderless) {
+ RECT crect;
+ GetClientRect(wd.hWnd, &crect);
+
+ w += (rect.right - rect.left) - (crect.right - crect.left);
+ h += (rect.bottom - rect.top) - (crect.bottom - crect.top);
+ }
+
+ MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE);
+
+ // Don't let the mouse leave the window when resizing to a smaller resolution
+ if (mouse_mode == MOUSE_MODE_CONFINED) {
+ RECT crect;
+ GetClientRect(wd.hWnd, &crect);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.right);
+ ClipCursor(&crect);
+ }
+}
+
+Size2i DisplayServerWindows::window_get_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ if (wd.minimized) {
+ return Size2(wd.width, wd.height);
+ }
+
+ RECT r;
+ if (GetClientRect(wd.hWnd, &r)) { // Only area inside of window border
+ return Size2(r.right - r.left, r.bottom - r.top);
+ }
+ return Size2();
+}
+
+Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
+ const WindowData &wd = windows[p_window];
+
+ RECT r;
+ if (GetWindowRect(wd.hWnd, &r)) { // Includes area of the window border
+ return Size2(r.right - r.left, r.bottom - r.top);
+ }
+ return Size2();
+}
+
+void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
+ r_style = 0;
+ r_style_ex = WS_EX_WINDOWEDGE;
+ if (p_main_window) {
+ r_style_ex |= WS_EX_APPWINDOW;
+ }
+
+ if (p_fullscreen || p_borderless) {
+ r_style |= WS_POPUP;
+ //if (p_borderless) {
+ // r_style_ex |= WS_EX_TOOLWINDOW;
+ //}
+ } else {
+ if (p_resizable) {
+ if (p_maximized) {
+ r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE;
+ } else {
+ r_style = WS_OVERLAPPEDWINDOW;
+ }
+ } else {
+ r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
+ }
+ }
+
+ if (p_no_activate_focus) {
+ r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
+ }
+ r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+}
+
+void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint, bool p_maximized) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ DWORD style = 0;
+ DWORD style_ex = 0;
+
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
+
+ SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
+ SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
+
+ SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | (wd.no_focus ? SWP_NOACTIVATE : 0));
+
+ if (p_repaint) {
+ RECT rect;
+ GetWindowRect(wd.hWnd, &rect);
+ MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
+ }
+}
+
+void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) {
+ RECT rect;
+
+ wd.fullscreen = false;
+
+ if (wd.pre_fs_valid) {
+ rect = wd.pre_fs_rect;
+ } else {
+ rect.left = 0;
+ rect.right = wd.width;
+ rect.top = 0;
+ rect.bottom = wd.height;
+ }
+
+ _update_window_style(p_window, false, wd.was_maximized);
+
+ MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
+
+ wd.pre_fs_valid = true;
+ }
+
+ if (p_mode == WINDOW_MODE_MAXIMIZED) {
+ ShowWindow(wd.hWnd, SW_MAXIMIZE);
+ wd.maximized = true;
+ wd.minimized = false;
+ }
+
+ if (p_mode == WINDOW_MODE_WINDOWED) {
+ ShowWindow(wd.hWnd, SW_RESTORE);
+ wd.maximized = false;
+ wd.minimized = false;
+ }
+
+ if (p_mode == WINDOW_MODE_MINIMIZED) {
+ ShowWindow(wd.hWnd, SW_MINIMIZE);
+ wd.maximized = false;
+ wd.minimized = true;
+ }
+
+ if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) {
+ if (wd.minimized) {
+ ShowWindow(wd.hWnd, SW_RESTORE);
+ }
+ wd.was_maximized = wd.maximized;
+
+ if (wd.pre_fs_valid) {
+ GetWindowRect(wd.hWnd, &wd.pre_fs_rect);
+ }
+
+ int cs = window_get_current_screen(p_window);
+ Point2 pos = screen_get_position(cs);
+ Size2 size = screen_get_size(cs);
+
+ wd.fullscreen = true;
+ wd.maximized = false;
+ wd.minimized = false;
+
+ _update_window_style(false);
+
+ MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
+ }
+}
+
+DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
+ const WindowData &wd = windows[p_window];
+
+ if (wd.fullscreen) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else if (wd.minimized) {
+ return WINDOW_MODE_MINIMIZED;
+ } else if (wd.maximized) {
+ return WINDOW_MODE_MAXIMIZED;
+ } else {
+ return WINDOW_MODE_WINDOWED;
+ }
+}
+
+bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+
+ // FIXME: Implement this, or confirm that it should always be true.
+
+ return true; //no idea
+}
+
+void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ wd.resizable = !p_enabled;
+ _update_window_style(p_window);
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ wd.borderless = p_enabled;
+ _update_window_style(p_window);
+ _update_window_mouse_passthrough(p_window);
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top");
+ wd.always_on_top = p_enabled;
+ _update_window_style(p_window);
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ // FIXME: Implement.
+ } break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ wd.no_focus = p_enabled;
+ _update_window_style(p_window);
+ } break;
+ case WINDOW_FLAG_MAX:
+ break;
+ }
+}
+
+bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+ const WindowData &wd = windows[p_window];
+ switch (p_flag) {
+ case WINDOW_FLAG_RESIZE_DISABLED: {
+ return !wd.resizable;
+ } break;
+ case WINDOW_FLAG_BORDERLESS: {
+ return wd.borderless;
+ } break;
+ case WINDOW_FLAG_ALWAYS_ON_TOP: {
+ return wd.always_on_top;
+ } break;
+ case WINDOW_FLAG_TRANSPARENT: {
+ // FIXME: Implement.
+ } break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ return wd.no_focus;
+ } break;
+ case WINDOW_FLAG_MAX:
+ break;
+ }
+
+ return false;
+}
+
+void DisplayServerWindows::window_request_attention(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ FLASHWINFO info;
+ info.cbSize = sizeof(FLASHWINFO);
+ info.hwnd = wd.hWnd;
+ info.dwFlags = FLASHW_TRAY;
+ info.dwTimeout = 0;
+ info.uCount = 2;
+ FlashWindowEx(&info);
+}
+
+void DisplayServerWindows::window_move_to_foreground(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ SetForegroundWindow(wd.hWnd);
+}
+
+bool DisplayServerWindows::window_can_draw(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), false);
+ const WindowData &wd = windows[p_window];
+ return wd.minimized;
+}
+
+bool DisplayServerWindows::can_any_window_draw() const {
+ _THREAD_SAFE_METHOD_
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (!E->get().minimized) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (p_active) {
+ ImmAssociateContext(wd.hWnd, wd.im_himc);
+
+ window_set_ime_position(wd.im_position, p_window);
+ } else {
+ ImmAssociateContext(wd.hWnd, (HIMC)0);
+ }
+}
+
+void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.im_position = p_pos;
+
+ HIMC himc = ImmGetContext(wd.hWnd);
+ if (himc == (HIMC)0)
+ return;
+
+ COMPOSITIONFORM cps;
+ cps.dwStyle = CFS_FORCE_POSITION;
+ cps.ptCurrentPos.x = wd.im_position.x;
+ cps.ptCurrentPos.y = wd.im_position.y;
+ ImmSetCompositionWindow(himc, &cps);
+ ImmReleaseContext(wd.hWnd, himc);
+}
+
+void DisplayServerWindows::console_set_visible(bool p_enabled) {
+ _THREAD_SAFE_METHOD_
+
+ if (console_visible == p_enabled)
+ return;
+ ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE);
+ console_visible = p_enabled;
+}
+
+bool DisplayServerWindows::is_console_visible() const {
+ return console_visible;
+}
+
+void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ if (cursor_shape == p_shape)
+ return;
+
+ if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+ cursor_shape = p_shape;
+ return;
+ }
+
+ static const LPCTSTR win_cursors[CURSOR_MAX] = {
+ IDC_ARROW,
+ IDC_IBEAM,
+ IDC_HAND, //finger
+ IDC_CROSS,
+ IDC_WAIT,
+ IDC_APPSTARTING,
+ IDC_ARROW,
+ IDC_ARROW,
+ IDC_NO,
+ IDC_SIZENS,
+ IDC_SIZEWE,
+ IDC_SIZENESW,
+ IDC_SIZENWSE,
+ IDC_SIZEALL,
+ IDC_SIZENS,
+ IDC_SIZEWE,
+ IDC_HELP
+ };
+
+ if (cursors[p_shape] != nullptr) {
+ SetCursor(cursors[p_shape]);
+ } else {
+ SetCursor(LoadCursor(hInstance, win_cursors[p_shape]));
+ }
+
+ cursor_shape = p_shape;
+}
+
+DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const {
+ return cursor_shape;
+}
+
+void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) {
+ // Get the system display DC
+ HDC hDC = GetDC(nullptr);
+
+ // Create helper DC
+ HDC hMainDC = CreateCompatibleDC(hDC);
+ HDC hAndMaskDC = CreateCompatibleDC(hDC);
+ HDC hXorMaskDC = CreateCompatibleDC(hDC);
+
+ // Get the dimensions of the source bitmap
+ BITMAP bm;
+ GetObject(hSourceBitmap, sizeof(BITMAP), &bm);
+
+ // Create the mask bitmaps
+ hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
+ hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
+
+ // Release the system display DC
+ ReleaseDC(nullptr, hDC);
+
+ // Select the bitmaps to helper DC
+ HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap);
+ HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap);
+ HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap);
+
+ // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap
+ // with 'clrTransparent' will be white pixels of the monochrome bitmap
+ SetBkColor(hMainDC, clrTransparent);
+ BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY);
+
+ // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap
+ // with 'clrTransparent' will be black and rest the pixels same as corresponding
+ // pixels of the source bitmap
+ SetBkColor(hXorMaskDC, RGB(0, 0, 0));
+ SetTextColor(hXorMaskDC, RGB(255, 255, 255));
+ BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY);
+ BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND);
+
+ // Deselect bitmaps from the helper DC
+ SelectObject(hMainDC, hOldMainBitmap);
+ SelectObject(hAndMaskDC, hOldAndMaskBitmap);
+ SelectObject(hXorMaskDC, hOldXorMaskBitmap);
+
+ // Delete the helper DC
+ DeleteDC(hXorMaskDC);
+ DeleteDC(hAndMaskDC);
+ DeleteDC(hMainDC);
+}
+
+void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_cursor.is_valid()) {
+ Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
+ cursor_set_shape(p_shape);
+ return;
+ }
+
+ cursors_cache.erase(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ Ref<AtlasTexture> atlas_texture = p_cursor;
+ Ref<Image> image;
+ Size2 texture_size;
+ Rect2 atlas_rect;
+
+ if (texture.is_valid()) {
+ image = texture->get_data();
+ }
+
+ if (!image.is_valid() && atlas_texture.is_valid()) {
+ texture = atlas_texture->get_atlas();
+
+ atlas_rect.size.width = texture->get_width();
+ atlas_rect.size.height = texture->get_height();
+ atlas_rect.position.x = atlas_texture->get_region().position.x;
+ atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else if (image.is_valid()) {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(!texture.is_valid());
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+ image = texture->get_data();
+
+ ERR_FAIL_COND(!image.is_valid());
+
+ UINT image_size = texture_size.width * texture_size.height;
+
+ // Create the BITMAP with alpha channel
+ COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size);
+
+ for (UINT index = 0; index < image_size; index++) {
+ int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
+ int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
+
+ if (atlas_texture.is_valid()) {
+ column_index = MIN(column_index, atlas_rect.size.width - 1);
+ row_index = MIN(row_index, atlas_rect.size.height - 1);
+ }
+
+ *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32();
+ }
+
+ // Using 4 channels, so 4 * 8 bits
+ HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer);
+ COLORREF clrTransparent = -1;
+
+ // Create the AND and XOR masks for the bitmap
+ HBITMAP hAndMask = nullptr;
+ HBITMAP hXorMask = nullptr;
+
+ GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask);
+
+ if (nullptr == hAndMask || nullptr == hXorMask) {
+ memfree(buffer);
+ DeleteObject(bitmap);
+ return;
+ }
+
+ // Finally, create the icon
+ ICONINFO iconinfo;
+ iconinfo.fIcon = FALSE;
+ iconinfo.xHotspot = p_hotspot.x;
+ iconinfo.yHotspot = p_hotspot.y;
+ iconinfo.hbmMask = hAndMask;
+ iconinfo.hbmColor = hXorMask;
+
+ if (cursors[p_shape])
+ DestroyIcon(cursors[p_shape]);
+
+ cursors[p_shape] = CreateIconIndirect(&iconinfo);
+
+ Vector<Variant> params;
+ params.push_back(p_cursor);
+ params.push_back(p_hotspot);
+ cursors_cache.insert(p_shape, params);
+
+ if (p_shape == cursor_shape) {
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ SetCursor(cursors[p_shape]);
+ }
+ }
+
+ if (hAndMask != nullptr) {
+ DeleteObject(hAndMask);
+ }
+
+ if (hXorMask != nullptr) {
+ DeleteObject(hXorMask);
+ }
+
+ memfree(buffer);
+ DeleteObject(bitmap);
+ } else {
+ // Reset to default system cursor
+ if (cursors[p_shape]) {
+ DestroyIcon(cursors[p_shape]);
+ cursors[p_shape] = nullptr;
+ }
+
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+
+ cursors_cache.erase(p_shape);
+ }
+}
+
+bool DisplayServerWindows::get_swap_cancel_ok() {
+ return true;
+}
+
+void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
+ _THREAD_SAFE_METHOD_
+
+ AllowSetForegroundWindow(pid);
+}
+
+int DisplayServerWindows::keyboard_get_layout_count() const {
+ return GetKeyboardLayoutList(0, NULL);
+}
+
+int DisplayServerWindows::keyboard_get_current_layout() const {
+ HKL cur_layout = GetKeyboardLayout(0);
+
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+
+ for (int i = 0; i < layout_count; i++) {
+ if (cur_layout == layouts[i]) {
+ memfree(layouts);
+ return i;
+ }
+ }
+ memfree(layouts);
+ return -1;
+}
+
+void DisplayServerWindows::keyboard_set_current_layout(int p_index) {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+
+ ERR_FAIL_INDEX(p_index, layout_count);
+
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+ ActivateKeyboardLayout(layouts[p_index], KLF_SETFORPROCESS);
+ memfree(layouts);
+}
+
+String DisplayServerWindows::keyboard_get_layout_language(int p_index) const {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+
+ ERR_FAIL_INDEX_V(p_index, layout_count, "");
+
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+
+ WCHAR buf[LOCALE_NAME_MAX_LENGTH];
+ memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));
+ LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
+
+ memfree(layouts);
+
+ return String::utf16((const char16_t *)buf).substr(0, 2);
+}
+
+String _get_full_layout_name_from_registry(HKL p_layout) {
+ String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0");
+ String ret;
+
+ HKEY hkey;
+ WCHAR layout_text[1024];
+ memset(layout_text, 0, 1024 * sizeof(WCHAR));
+
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
+ return ret;
+ }
+
+ DWORD buffer = 1024;
+ DWORD vtype = REG_SZ;
+ if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) {
+ ret = String::utf16((const char16_t *)layout_text);
+ }
+ RegCloseKey(hkey);
+ return ret;
+}
+
+String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+
+ ERR_FAIL_INDEX_V(p_index, layout_count, "");
+
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+
+ String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).
+ if (ret == String()) {
+ WCHAR buf[LOCALE_NAME_MAX_LENGTH];
+ memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));
+ LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
+
+ WCHAR name[1024];
+ memset(name, 0, 1024 * sizeof(WCHAR));
+ GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024);
+
+ ret = String::utf16((const char16_t *)name);
+ }
+ memfree(layouts);
+
+ return ret;
+}
+
+void DisplayServerWindows::process_events() {
+ _THREAD_SAFE_METHOD_
+
+ MSG msg;
+
+ if (!drop_events) {
+ joypad->process_joypads();
+ }
+
+ while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+
+ if (!drop_events) {
+ _process_key_events();
+ Input::get_singleton()->flush_accumulated_events();
+ }
+}
+
+void DisplayServerWindows::force_process_and_drop_events() {
+ _THREAD_SAFE_METHOD_
+
+ drop_events = true;
+ process_events();
+ drop_events = false;
+}
+
+void DisplayServerWindows::release_rendering_thread() {
+}
+
+void DisplayServerWindows::make_rendering_thread() {
+}
+
+void DisplayServerWindows::swap_buffers() {
+}
+
+void DisplayServerWindows::set_native_icon(const String &p_filename) {
+ _THREAD_SAFE_METHOD_
+
+ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'.");
+
+ ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
+ int pos = 0;
+
+ icon_dir->idReserved = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ icon_dir->idType = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ ERR_FAIL_COND_MSG(icon_dir->idType != 1, "Invalid icon file format!");
+
+ icon_dir->idCount = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY));
+ f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY));
+
+ int small_icon_index = -1; // Select 16x16 with largest color count
+ int small_icon_cc = 0;
+ int big_icon_index = -1; // Select largest
+ int big_icon_width = 16;
+ int big_icon_cc = 0;
+
+ for (int i = 0; i < icon_dir->idCount; i++) {
+ int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount;
+ int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth;
+ if (width == 16) {
+ if (colors >= small_icon_cc) {
+ small_icon_index = i;
+ small_icon_cc = colors;
+ }
+ }
+ if (width >= big_icon_width) {
+ if (colors >= big_icon_cc) {
+ big_icon_index = i;
+ big_icon_width = width;
+ big_icon_cc = colors;
+ }
+ }
+ }
+
+ ERR_FAIL_COND_MSG(big_icon_index == -1, "No valid icons found!");
+
+ if (small_icon_index == -1) {
+ WARN_PRINT("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!");
+ small_icon_index = big_icon_index;
+ small_icon_cc = big_icon_cc;
+ }
+
+ // Read the big icon
+ DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes;
+ Vector<uint8_t> data_big;
+ data_big.resize(bytecount_big);
+ pos = icon_dir->idEntries[big_icon_index].dwImageOffset;
+ f->seek(pos);
+ f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big);
+ HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000);
+ ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
+
+ // Read the small icon
+ DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes;
+ Vector<uint8_t> data_small;
+ data_small.resize(bytecount_small);
+ pos = icon_dir->idEntries[small_icon_index].dwImageOffset;
+ f->seek(pos);
+ f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small);
+ HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000);
+ ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
+
+ // Online tradition says to be sure last error is cleared and set the small icon first
+ int err = 0;
+ SetLastError(err);
+
+ SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);
+ err = GetLastError();
+ ERR_FAIL_COND_MSG(err, "Error setting ICON_SMALL: " + format_error_message(err) + ".");
+
+ SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big);
+ err = GetLastError();
+ ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + ".");
+
+ memdelete(f);
+ memdelete(icon_dir);
+}
+
+void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!p_icon.is_valid());
+ Ref<Image> icon = p_icon->duplicate();
+ if (icon->get_format() != Image::FORMAT_RGBA8)
+ icon->convert(Image::FORMAT_RGBA8);
+ int w = icon->get_width();
+ int h = icon->get_height();
+
+ /* Create temporary bitmap buffer */
+ int icon_len = 40 + h * w * 4;
+ Vector<BYTE> v;
+ v.resize(icon_len);
+ BYTE *icon_bmp = v.ptrw();
+
+ encode_uint32(40, &icon_bmp[0]);
+ encode_uint32(w, &icon_bmp[4]);
+ encode_uint32(h * 2, &icon_bmp[8]);
+ encode_uint16(1, &icon_bmp[12]);
+ encode_uint16(32, &icon_bmp[14]);
+ encode_uint32(BI_RGB, &icon_bmp[16]);
+ encode_uint32(w * h * 4, &icon_bmp[20]);
+ encode_uint32(0, &icon_bmp[24]);
+ encode_uint32(0, &icon_bmp[28]);
+ encode_uint32(0, &icon_bmp[32]);
+ encode_uint32(0, &icon_bmp[36]);
+
+ uint8_t *wr = &icon_bmp[40];
+ const uint8_t *r = icon->get_data().ptr();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
+ uint8_t *wpx = &wr[(i * w + j) * 4];
+ wpx[0] = rpx[2];
+ wpx[1] = rpx[1];
+ wpx[2] = rpx[0];
+ wpx[3] = rpx[3];
+ }
+ }
+
+ HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
+
+ /* Set the icon for the window */
+ SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
+
+ /* Set the icon in the task manager (should we do this?) */
+ SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
+}
+
+void DisplayServerWindows::vsync_set_use_via_compositor(bool p_enable) {
+}
+
+bool DisplayServerWindows::vsync_is_using_via_compositor() const {
+ return false;
+}
+
+void DisplayServerWindows::set_context(Context p_context) {
+}
+
+#define MI_WP_SIGNATURE 0xFF515700
+#define SIGNATURE_MASK 0xFFFFFF00
+// Keeping the name suggested by Microsoft, but this macro really answers:
+// Is this mouse event emulated from touch or pen input?
+#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE)
+// This one tells whether the event comes from touchscreen (and not from pen)
+#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80))
+
+void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) {
+ // Defensive
+ if (touch_state.has(idx) == p_pressed)
+ return;
+
+ if (p_pressed) {
+ touch_state.insert(idx, Vector2(p_x, p_y));
+ } else {
+ touch_state.erase(idx);
+ }
+
+ Ref<InputEventScreenTouch> event;
+ event.instance();
+ event->set_index(idx);
+ event->set_window_id(p_window);
+ event->set_pressed(p_pressed);
+ event->set_position(Vector2(p_x, p_y));
+
+ Input::get_singleton()->accumulate_input_event(event);
+}
+
+void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) {
+ Map<int, Vector2>::Element *curr = touch_state.find(idx);
+ // Defensive
+ if (!curr)
+ return;
+
+ if (curr->get() == Vector2(p_x, p_y))
+ return;
+
+ Ref<InputEventScreenDrag> event;
+ event.instance();
+ event->set_window_id(p_window);
+ event->set_index(idx);
+ event->set_position(Vector2(p_x, p_y));
+ event->set_relative(Vector2(p_x, p_y) - curr->get());
+
+ Input::get_singleton()->accumulate_input_event(event);
+
+ curr->get() = Vector2(p_x, p_y);
+}
+
+void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent p_event) {
+ if (!wd.event_callback.is_null()) {
+ Variant event = int(p_event);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerWindows *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ _THREAD_SAFE_METHOD_
+ if (in_dispatch_input_event) {
+ return;
+ }
+
+ in_dispatch_input_event = true;
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
+
+ Ref<InputEventFromWindow> event_from_window = p_event;
+ if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
+ //send to a window
+ ERR_FAIL_COND(!windows.has(event_from_window->get_window_id()));
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_null()) {
+ in_dispatch_input_event = false;
+ return;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ } else {
+ //send to all windows
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ Callable callable = E->get().input_event_callback;
+ if (callable.is_null()) {
+ continue;
+ }
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+
+ in_dispatch_input_event = false;
+}
+
+LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (drop_events) {
+ if (user_proc) {
+ return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
+ } else {
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
+ };
+
+ WindowID window_id = INVALID_WINDOW_ID;
+ bool window_created = false;
+
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if (E->get().hWnd == hWnd) {
+ window_id = E->key();
+ window_created = true;
+ break;
+ }
+ }
+
+ if (!window_created) {
+ // Window creation in progress.
+ window_id = window_id_counter;
+ ERR_FAIL_COND_V(!windows.has(window_id), 0);
+ }
+
+ switch (uMsg) // Check For Windows Messages
+ {
+ case WM_SETFOCUS: {
+ windows[window_id].window_has_focus = true;
+ last_focused_window = window_id;
+
+ // Restore mouse mode
+ _set_mouse_mode_impl(mouse_mode);
+
+ if (!app_focused) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+ app_focused = true;
+ }
+ break;
+ }
+ case WM_KILLFOCUS: {
+ windows[window_id].window_has_focus = false;
+ last_focused_window = window_id;
+
+ // Release capture unconditionally because it can be set due to dragging, in addition to captured mode
+ ReleaseCapture();
+
+ // Release every touch to avoid sticky points
+ for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) {
+ _touch_event(window_id, false, E->get().x, E->get().y, E->key());
+ }
+ touch_state.clear();
+
+ bool self_steal = false;
+ HWND new_hwnd = (HWND)wParam;
+ if (IsWindow(new_hwnd)) {
+ self_steal = true;
+ }
+
+ if (!self_steal) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+ app_focused = false;
+ }
+
+ break;
+ }
+ case WM_ACTIVATE: // Watch For Window Activate Message
+ {
+ windows[window_id].minimized = HIWORD(wParam) != 0;
+
+ if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN);
+ windows[window_id].window_focused = true;
+ alt_mem = false;
+ control_mem = false;
+ shift_mem = false;
+ } else { // WM_INACTIVE
+ Input::get_singleton()->release_pressed_events();
+ _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT);
+ windows[window_id].window_focused = false;
+ alt_mem = false;
+ };
+
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
+ wintab_WTEnable(windows[window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam));
+ }
+
+ return 0; // Return To The Message Loop
+ }
+ case WM_GETMINMAXINFO: {
+ if (windows[window_id].resizable && !windows[window_id].fullscreen) {
+ Size2 decor = window_get_size(window_id) - window_get_real_size(window_id); // Size of window decorations
+ MINMAXINFO *min_max_info = (MINMAXINFO *)lParam;
+ if (windows[window_id].min_size != Size2()) {
+ min_max_info->ptMinTrackSize.x = windows[window_id].min_size.x + decor.x;
+ min_max_info->ptMinTrackSize.y = windows[window_id].min_size.y + decor.y;
+ }
+ if (windows[window_id].max_size != Size2()) {
+ min_max_info->ptMaxTrackSize.x = windows[window_id].max_size.x + decor.x;
+ min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y;
+ }
+ return 0;
+ } else {
+ break;
+ }
+ }
+ case WM_PAINT:
+
+ Main::force_redraw();
+ break;
+
+ case WM_SYSCOMMAND: // Intercept System Commands
+ {
+ switch (wParam) // Check System Calls
+ {
+ case SC_SCREENSAVE: // Screensaver Trying To Start?
+ case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
+ return 0; // Prevent From Happening
+ case SC_KEYMENU:
+ if ((lParam >> 16) <= 0)
+ return 0;
+ }
+ break; // Exit
+ }
+
+ case WM_CLOSE: // Did We Receive A Close Message?
+ {
+ _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
+
+ return 0; // Jump Back
+ }
+ case WM_MOUSELEAVE: {
+ old_invalid = true;
+ outside = true;
+
+ _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);
+
+ } break;
+ case WM_INPUT: {
+ if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
+ break;
+ }
+
+ UINT dwSize;
+
+ GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER));
+ LPBYTE lpb = new BYTE[dwSize];
+ if (lpb == nullptr) {
+ return 0;
+ }
+
+ if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
+ OutputDebugString(TEXT("GetRawInputData does not return correct size !\n"));
+
+ RAWINPUT *raw = (RAWINPUT *)lpb;
+
+ if (raw->header.dwType == RIM_TYPEMOUSE) {
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+
+ mm->set_window_id(window_id);
+ mm->set_control(control_mem);
+ mm->set_shift(shift_mem);
+ mm->set_alt(alt_mem);
+
+ mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f);
+
+ mm->set_button_mask(last_button_state);
+
+ Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
+
+ // centering just so it works as before
+ POINT pos = { (int)c.x, (int)c.y };
+ ClientToScreen(windows[window_id].hWnd, &pos);
+ SetCursorPos(pos.x, pos.y);
+
+ mm->set_position(c);
+ mm->set_global_position(c);
+ Input::get_singleton()->set_mouse_position(c);
+ mm->set_speed(Vector2(0, 0));
+
+ if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
+ mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY));
+
+ } else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
+ int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
+
+ Vector2 abs_pos(
+ (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft,
+ (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop);
+
+ POINT coords; //client coords
+ coords.x = abs_pos.x;
+ coords.y = abs_pos.y;
+
+ ScreenToClient(hWnd, &coords);
+
+ mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y));
+ old_x = coords.x;
+ old_y = coords.y;
+
+ /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth));
+ Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight));
+ */
+ }
+
+ if (windows[window_id].window_has_focus && mm->get_relative() != Vector2())
+ Input::get_singleton()->accumulate_input_event(mm);
+ }
+ delete[] lpb;
+ } break;
+ case WT_CSRCHANGE:
+ case WT_PROXIMITY: {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
+ AXIS pressure;
+ if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+ windows[window_id].min_pressure = int(pressure.axMin);
+ windows[window_id].max_pressure = int(pressure.axMax);
+ }
+ AXIS orientation[3];
+ if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+ windows[window_id].tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+ }
+ return 0;
+ }
+ } break;
+ case WT_PACKET: {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
+ PACKET packet;
+ if (wintab_WTPacket(windows[window_id].wtctx, wParam, &packet)) {
+ float pressure = float(packet.pkNormalPressure - windows[window_id].min_pressure) / float(windows[window_id].max_pressure - windows[window_id].min_pressure);
+ windows[window_id].last_pressure = pressure;
+ windows[window_id].last_pressure_update = 0;
+
+ double azim = (packet.pkOrientation.orAzimuth / 10.0f) * (Math_PI / 180);
+ double alt = Math::tan((Math::abs(packet.pkOrientation.orAltitude / 10.0f)) * (Math_PI / 180));
+
+ if (windows[window_id].tilt_supported) {
+ windows[window_id].last_tilt = Vector2(Math::atan(Math::sin(azim) / alt), Math::atan(Math::cos(azim) / alt));
+ } else {
+ windows[window_id].last_tilt = Vector2();
+ }
+
+ POINT coords;
+ GetCursorPos(&coords);
+ ScreenToClient(windows[window_id].hWnd, &coords);
+
+ // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
+ if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
+ break;
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+ mm->set_window_id(window_id);
+ mm->set_control(GetKeyState(VK_CONTROL) < 0);
+ mm->set_shift(GetKeyState(VK_SHIFT) < 0);
+ mm->set_alt(alt_mem);
+
+ mm->set_pressure(windows[window_id].last_pressure);
+ mm->set_tilt(windows[window_id].last_tilt);
+
+ mm->set_button_mask(last_button_state);
+
+ mm->set_position(Vector2(coords.x, coords.y));
+ mm->set_global_position(Vector2(coords.x, coords.y));
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
+ old_x = c.x;
+ old_y = c.y;
+
+ if (mm->get_position() == c) {
+ center = c;
+ return 0;
+ }
+
+ Point2i ncenter = mm->get_position();
+ center = ncenter;
+ POINT pos = { (int)c.x, (int)c.y };
+ ClientToScreen(windows[window_id].hWnd, &pos);
+ SetCursorPos(pos.x, pos.y);
+ }
+
+ Input::get_singleton()->set_mouse_position(mm->get_position());
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+
+ if (old_invalid) {
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ old_invalid = false;
+ }
+
+ mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ if (windows[window_id].window_has_focus)
+ Input::get_singleton()->accumulate_input_event(mm);
+ }
+ return 0;
+ }
+ } break;
+ case WM_POINTERENTER: {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
+ break;
+ }
+
+ if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) {
+ break;
+ }
+
+ uint32_t pointer_id = LOWORD(wParam);
+ POINTER_INPUT_TYPE pointer_type = PT_POINTER;
+ if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
+ break;
+ }
+
+ if (pointer_type != PT_PEN) {
+ break;
+ }
+
+ windows[window_id].block_mm = true;
+ return 0;
+ } break;
+ case WM_POINTERLEAVE: {
+ windows[window_id].block_mm = false;
+ return 0;
+ } break;
+ case WM_POINTERUPDATE: {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
+ break;
+ }
+
+ if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) {
+ break;
+ }
+
+ uint32_t pointer_id = LOWORD(wParam);
+ POINTER_INPUT_TYPE pointer_type = PT_POINTER;
+ if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
+ break;
+ }
+
+ if (pointer_type != PT_PEN) {
+ break;
+ }
+
+ POINTER_PEN_INFO pen_info;
+ if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) {
+ break;
+ }
+
+ if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
+ // Universal translation enabled; ignore OS translation
+ LPARAM extra = GetMessageExtraInfo();
+ if (IsTouchEvent(extra)) {
+ break;
+ }
+ }
+
+ if (outside) {
+ //mouse enter
+
+ if (mouse_mode != MOUSE_MODE_CAPTURED) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
+ }
+
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+ outside = false;
+
+ //Once-Off notification, must call again....
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = hWnd;
+ tme.dwHoverTime = HOVER_DEFAULT;
+ TrackMouseEvent(&tme);
+ }
+
+ // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
+ if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
+ break;
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+
+ mm->set_window_id(window_id);
+ if (pen_info.penMask & PEN_MASK_PRESSURE) {
+ mm->set_pressure((float)pen_info.pressure / 1024);
+ } else {
+ mm->set_pressure((HIWORD(wParam) & POINTER_MESSAGE_FLAG_FIRSTBUTTON) ? 1.0f : 0.0f);
+ }
+ if ((pen_info.penMask & PEN_MASK_TILT_X) && (pen_info.penMask & PEN_MASK_TILT_Y)) {
+ mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90));
+ }
+
+ mm->set_control(GetKeyState(VK_CONTROL) < 0);
+ mm->set_shift(GetKeyState(VK_SHIFT) < 0);
+ mm->set_alt(alt_mem);
+
+ mm->set_button_mask(last_button_state);
+
+ POINT coords; //client coords
+ coords.x = GET_X_LPARAM(lParam);
+ coords.y = GET_Y_LPARAM(lParam);
+
+ ScreenToClient(windows[window_id].hWnd, &coords);
+
+ mm->set_position(Vector2(coords.x, coords.y));
+ mm->set_global_position(Vector2(coords.x, coords.y));
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
+ old_x = c.x;
+ old_y = c.y;
+
+ if (mm->get_position() == c) {
+ center = c;
+ return 0;
+ }
+
+ Point2i ncenter = mm->get_position();
+ center = ncenter;
+ POINT pos = { (int)c.x, (int)c.y };
+ ClientToScreen(hWnd, &pos);
+ SetCursorPos(pos.x, pos.y);
+ }
+
+ Input::get_singleton()->set_mouse_position(mm->get_position());
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+
+ if (old_invalid) {
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ old_invalid = false;
+ }
+
+ mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ if (windows[window_id].window_has_focus) {
+ Input::get_singleton()->accumulate_input_event(mm);
+ }
+
+ return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event
+ } break;
+ case WM_MOUSEMOVE: {
+ if (windows[window_id].block_mm) {
+ break;
+ }
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
+ break;
+ }
+
+ if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
+ // Universal translation enabled; ignore OS translation
+ LPARAM extra = GetMessageExtraInfo();
+ if (IsTouchEvent(extra)) {
+ break;
+ }
+ }
+
+ if (outside) {
+ //mouse enter
+
+ if (mouse_mode != MOUSE_MODE_CAPTURED) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
+ }
+
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+ outside = false;
+
+ //Once-Off notification, must call again....
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = hWnd;
+ tme.dwHoverTime = HOVER_DEFAULT;
+ TrackMouseEvent(&tme);
+ }
+
+ // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
+ if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
+ break;
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instance();
+ mm->set_window_id(window_id);
+ mm->set_control((wParam & MK_CONTROL) != 0);
+ mm->set_shift((wParam & MK_SHIFT) != 0);
+ mm->set_alt(alt_mem);
+
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
+ // Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not update recently.
+ if (windows[window_id].last_pressure_update < 10) {
+ windows[window_id].last_pressure_update++;
+ } else {
+ windows[window_id].last_tilt = Vector2();
+ windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+ }
+ } else {
+ windows[window_id].last_tilt = Vector2();
+ windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+ }
+
+ mm->set_pressure(windows[window_id].last_pressure);
+ mm->set_tilt(windows[window_id].last_tilt);
+
+ mm->set_button_mask(last_button_state);
+
+ mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+ mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
+ old_x = c.x;
+ old_y = c.y;
+
+ if (mm->get_position() == c) {
+ center = c;
+ return 0;
+ }
+
+ Point2i ncenter = mm->get_position();
+ center = ncenter;
+ POINT pos = { (int)c.x, (int)c.y };
+ ClientToScreen(windows[window_id].hWnd, &pos);
+ SetCursorPos(pos.x, pos.y);
+ }
+
+ Input::get_singleton()->set_mouse_position(mm->get_position());
+ mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+
+ if (old_invalid) {
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ old_invalid = false;
+ }
+
+ mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
+ old_x = mm->get_position().x;
+ old_y = mm->get_position().y;
+ if (windows[window_id].window_has_focus)
+ Input::get_singleton()->accumulate_input_event(mm);
+
+ } break;
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
+ // Universal translation enabled; ignore OS translations for left button
+ LPARAM extra = GetMessageExtraInfo();
+ if (IsTouchEvent(extra)) {
+ break;
+ }
+ }
+ [[fallthrough]];
+ case WM_MBUTTONDOWN:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONUP:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_LBUTTONDBLCLK:
+ case WM_MBUTTONDBLCLK:
+ case WM_RBUTTONDBLCLK:
+ case WM_XBUTTONDBLCLK:
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONUP: {
+ Ref<InputEventMouseButton> mb;
+ mb.instance();
+ mb->set_window_id(window_id);
+
+ switch (uMsg) {
+ case WM_LBUTTONDOWN: {
+ mb->set_pressed(true);
+ mb->set_button_index(1);
+ } break;
+ case WM_LBUTTONUP: {
+ mb->set_pressed(false);
+ mb->set_button_index(1);
+ } break;
+ case WM_MBUTTONDOWN: {
+ mb->set_pressed(true);
+ mb->set_button_index(3);
+ } break;
+ case WM_MBUTTONUP: {
+ mb->set_pressed(false);
+ mb->set_button_index(3);
+ } break;
+ case WM_RBUTTONDOWN: {
+ mb->set_pressed(true);
+ mb->set_button_index(2);
+ } break;
+ case WM_RBUTTONUP: {
+ mb->set_pressed(false);
+ mb->set_button_index(2);
+ } break;
+ case WM_LBUTTONDBLCLK: {
+ mb->set_pressed(true);
+ mb->set_button_index(1);
+ mb->set_doubleclick(true);
+ } break;
+ case WM_RBUTTONDBLCLK: {
+ mb->set_pressed(true);
+ mb->set_button_index(2);
+ mb->set_doubleclick(true);
+ } break;
+ case WM_MBUTTONDBLCLK: {
+ mb->set_pressed(true);
+ mb->set_button_index(3);
+ mb->set_doubleclick(true);
+ } break;
+ case WM_MOUSEWHEEL: {
+ mb->set_pressed(true);
+ int motion = (short)HIWORD(wParam);
+ if (!motion)
+ return 0;
+
+ if (motion > 0)
+ mb->set_button_index(BUTTON_WHEEL_UP);
+ else
+ mb->set_button_index(BUTTON_WHEEL_DOWN);
+
+ } break;
+ case WM_MOUSEHWHEEL: {
+ mb->set_pressed(true);
+ int motion = (short)HIWORD(wParam);
+ if (!motion)
+ return 0;
+
+ if (motion < 0) {
+ mb->set_button_index(BUTTON_WHEEL_LEFT);
+ mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
+ } else {
+ mb->set_button_index(BUTTON_WHEEL_RIGHT);
+ mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
+ }
+ } break;
+ case WM_XBUTTONDOWN: {
+ mb->set_pressed(true);
+ if (HIWORD(wParam) == XBUTTON1)
+ mb->set_button_index(BUTTON_XBUTTON1);
+ else
+ mb->set_button_index(BUTTON_XBUTTON2);
+ } break;
+ case WM_XBUTTONUP: {
+ mb->set_pressed(false);
+ if (HIWORD(wParam) == XBUTTON1)
+ mb->set_button_index(BUTTON_XBUTTON1);
+ else
+ mb->set_button_index(BUTTON_XBUTTON2);
+ } break;
+ case WM_XBUTTONDBLCLK: {
+ mb->set_pressed(true);
+ if (HIWORD(wParam) == XBUTTON1)
+ mb->set_button_index(BUTTON_XBUTTON1);
+ else
+ mb->set_button_index(BUTTON_XBUTTON2);
+ mb->set_doubleclick(true);
+ } break;
+ default: {
+ return 0;
+ }
+ }
+
+ mb->set_control((wParam & MK_CONTROL) != 0);
+ mb->set_shift((wParam & MK_SHIFT) != 0);
+ mb->set_alt(alt_mem);
+ //mb->get_alt()=(wParam&MK_MENU)!=0;
+ if (mb->is_pressed())
+ last_button_state |= (1 << (mb->get_button_index() - 1));
+ else
+ last_button_state &= ~(1 << (mb->get_button_index() - 1));
+ mb->set_button_mask(last_button_state);
+
+ mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) {
+ mb->set_position(Vector2(old_x, old_y));
+ }
+
+ if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) {
+ if (mb->is_pressed()) {
+ if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED)
+ SetCapture(hWnd);
+ } else {
+ if (--pressrc <= 0) {
+ if (mouse_mode != MOUSE_MODE_CAPTURED) {
+ ReleaseCapture();
+ }
+ pressrc = 0;
+ }
+ }
+ } else {
+ // for reasons unknown to mankind, wheel comes in screen coordinates
+ POINT coords;
+ coords.x = mb->get_position().x;
+ coords.y = mb->get_position().y;
+
+ ScreenToClient(hWnd, &coords);
+
+ mb->set_position(Vector2(coords.x, coords.y));
+ }
+
+ mb->set_global_position(mb->get_position());
+
+ Input::get_singleton()->accumulate_input_event(mb);
+ if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) {
+ //send release for mouse wheel
+ Ref<InputEventMouseButton> mbd = mb->duplicate();
+ mbd->set_window_id(window_id);
+ last_button_state &= ~(1 << (mbd->get_button_index() - 1));
+ mbd->set_button_mask(last_button_state);
+ mbd->set_pressed(false);
+ Input::get_singleton()->accumulate_input_event(mbd);
+ }
+
+ } break;
+
+ case WM_MOVE: {
+ if (!IsIconic(windows[window_id].hWnd)) {
+ int x = int16_t(LOWORD(lParam));
+ int y = int16_t(HIWORD(lParam));
+ windows[window_id].last_pos = Point2(x, y);
+
+ if (!windows[window_id].rect_changed_callback.is_null()) {
+ Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+ }
+ } break;
+
+ case WM_SIZE: {
+ // Ignore size when a SIZE_MINIMIZED event is triggered
+ if (wParam != SIZE_MINIMIZED) {
+ int window_w = LOWORD(lParam);
+ int window_h = HIWORD(lParam);
+ if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) {
+ windows[window_id].width = window_w;
+ windows[window_id].height = window_h;
+
+#if defined(VULKAN_ENABLED)
+ if ((rendering_driver == "vulkan") && window_created) {
+ context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height);
+ }
+#endif
+
+ } else {
+ windows[window_id].preserve_window_size = false;
+ window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id);
+ }
+ }
+
+ if (!windows[window_id].rect_changed_callback.is_null()) {
+ Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+
+ if (wParam == SIZE_MAXIMIZED) {
+ windows[window_id].maximized = true;
+ windows[window_id].minimized = false;
+ } else if (wParam == SIZE_MINIMIZED) {
+ windows[window_id].maximized = false;
+ windows[window_id].minimized = true;
+ } else if (wParam == SIZE_RESTORED) {
+ windows[window_id].maximized = false;
+ windows[window_id].minimized = false;
+ }
+#if 0
+ if (is_layered_allowed() && layered_window) {
+ DeleteObject(hBitmap);
+
+ RECT r;
+ GetWindowRect(hWnd, &r);
+ dib_size = Size2i(r.right - r.left, r.bottom - r.top);
+
+ BITMAPINFO bmi;
+ ZeroMemory(&bmi, sizeof(BITMAPINFO));
+ bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.bmiHeader.biWidth = dib_size.x;
+ bmi.bmiHeader.biHeight = dib_size.y;
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+ bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
+ hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0);
+ SelectObject(hDC_dib, hBitmap);
+
+ ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
+ }
+#endif
+ //return 0; // Jump Back
+ } break;
+
+ case WM_ENTERSIZEMOVE: {
+ Input::get_singleton()->release_pressed_events();
+ move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
+ } break;
+ case WM_EXITSIZEMOVE: {
+ KillTimer(windows[window_id].hWnd, move_timer_id);
+ } break;
+ case WM_TIMER: {
+ if (wParam == move_timer_id) {
+ _process_key_events();
+ if (!Main::is_iterating()) {
+ Main::iteration();
+ }
+ }
+ } break;
+
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ case WM_KEYUP:
+ case WM_KEYDOWN: {
+ if (wParam == VK_SHIFT)
+ shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
+ if (wParam == VK_CONTROL)
+ control_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
+ if (wParam == VK_MENU) {
+ alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
+ if (lParam & (1 << 24))
+ gr_mem = alt_mem;
+ }
+
+ if (mouse_mode == MOUSE_MODE_CAPTURED) {
+ // When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves
+ if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) {
+ _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
+ }
+ }
+ /*
+ if (wParam==VK_WIN) TODO wtf is this?
+ meta_mem=uMsg==WM_KEYDOWN;
+ */
+ [[fallthrough]];
+ }
+ case WM_CHAR: {
+ ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
+
+ // Make sure we don't include modifiers for the modifier key itself.
+ KeyEvent ke;
+ ke.shift = (wParam != VK_SHIFT) ? shift_mem : false;
+ ke.alt = (!(wParam == VK_MENU && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN))) ? alt_mem : false;
+ ke.control = (wParam != VK_CONTROL) ? control_mem : false;
+ ke.meta = meta_mem;
+ ke.uMsg = uMsg;
+ ke.window_id = window_id;
+
+ if (ke.uMsg == WM_SYSKEYDOWN)
+ ke.uMsg = WM_KEYDOWN;
+ if (ke.uMsg == WM_SYSKEYUP)
+ ke.uMsg = WM_KEYUP;
+
+ ke.wParam = wParam;
+ ke.lParam = lParam;
+ key_event_buffer[key_event_pos++] = ke;
+
+ } break;
+ case WM_INPUTLANGCHANGEREQUEST: {
+ // FIXME: Do something?
+ } break;
+
+ case WM_TOUCH: {
+ BOOL bHandled = FALSE;
+ UINT cInputs = LOWORD(wParam);
+ PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs);
+ if (pInputs) {
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) {
+ for (UINT i = 0; i < cInputs; i++) {
+ TOUCHINPUT ti = pInputs[i];
+ POINT touch_pos = {
+ TOUCH_COORD_TO_PIXEL(ti.x),
+ TOUCH_COORD_TO_PIXEL(ti.y),
+ };
+ ScreenToClient(hWnd, &touch_pos);
+ //do something with each touch input entry
+ if (ti.dwFlags & TOUCHEVENTF_MOVE) {
+ _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID);
+ } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
+ _touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID);
+ };
+ }
+ bHandled = TRUE;
+ } else {
+ /* handle the error here */
+ }
+ memdelete_arr(pInputs);
+ } else {
+ /* handle the error here, probably out of memory */
+ }
+ if (bHandled) {
+ CloseTouchInputHandle((HTOUCHINPUT)lParam);
+ return 0;
+ };
+
+ } break;
+
+ case WM_DEVICECHANGE: {
+ joypad->probe_joypads();
+ } break;
+ case WM_SETCURSOR: {
+ if (LOWORD(lParam) == HTCLIENT) {
+ if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) {
+ //Hide the cursor
+ if (hCursor == nullptr) {
+ hCursor = SetCursor(nullptr);
+ } else {
+ SetCursor(nullptr);
+ }
+ } else {
+ if (hCursor != nullptr) {
+ CursorShape c = cursor_shape;
+ cursor_shape = CURSOR_MAX;
+ cursor_set_shape(c);
+ hCursor = nullptr;
+ }
+ }
+ }
+
+ } break;
+ case WM_DROPFILES: {
+ HDROP hDropInfo = (HDROP)wParam;
+ const int buffsize = 4096;
+ WCHAR buf[buffsize];
+
+ int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
+
+ Vector<String> files;
+
+ for (int i = 0; i < fcount; i++) {
+ DragQueryFileW(hDropInfo, i, buf, buffsize);
+ String file = String::utf16((const char16_t *)buf);
+ files.push_back(file);
+ }
+
+ if (files.size() && !windows[window_id].drop_files_callback.is_null()) {
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+ }
+
+ } break;
+
+ default: {
+ if (user_proc) {
+ return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
+ };
+ };
+ }
+
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+}
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
+ if (ds_win)
+ return ds_win->WndProc(hWnd, uMsg, wParam, lParam);
+ else
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+}
+
+void DisplayServerWindows::_process_key_events() {
+ for (int i = 0; i < key_event_pos; i++) {
+ KeyEvent &ke = key_event_buffer[i];
+ switch (ke.uMsg) {
+ case WM_CHAR: {
+ // extended keys should only be processed as WM_KEYDOWN message.
+ if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) {
+ Ref<InputEventKey> k;
+ k.instance();
+
+ k->set_window_id(ke.window_id);
+ k->set_shift(ke.shift);
+ k->set_alt(ke.alt);
+ k->set_control(ke.control);
+ k->set_metakey(ke.meta);
+ k->set_pressed(true);
+ k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam));
+ k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)));
+ k->set_unicode(ke.wParam);
+ if (k->get_unicode() && gr_mem) {
+ k->set_alt(false);
+ k->set_control(false);
+ }
+
+ if (k->get_unicode() < 32)
+ k->set_unicode(0);
+
+ Input::get_singleton()->accumulate_input_event(k);
+ }
+
+ //do nothing
+ } break;
+ case WM_KEYUP:
+ case WM_KEYDOWN: {
+ Ref<InputEventKey> k;
+ k.instance();
+
+ k->set_window_id(ke.window_id);
+ k->set_shift(ke.shift);
+ k->set_alt(ke.alt);
+ k->set_control(ke.control);
+ k->set_metakey(ke.meta);
+
+ k->set_pressed(ke.uMsg == WM_KEYDOWN);
+
+ if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) {
+ // Special case for Numpad Enter key
+ k->set_keycode(KEY_KP_ENTER);
+ } else {
+ k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam));
+ }
+
+ k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)));
+
+ if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
+ k->set_unicode(key_event_buffer[i + 1].wParam);
+ }
+ if (k->get_unicode() && gr_mem) {
+ k->set_alt(false);
+ k->set_control(false);
+ }
+
+ if (k->get_unicode() < 32)
+ k->set_unicode(0);
+
+ k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30))));
+
+ Input::get_singleton()->accumulate_input_event(k);
+
+ } break;
+ }
+ }
+
+ key_event_pos = 0;
+}
+
+void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const String &p_new_driver) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ WindowData &wd = E->get();
+ wd.block_mm = false;
+ if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) {
+ wintab_WTEnable(wd.wtctx, false);
+ wintab_WTClose(wd.wtctx);
+ wd.wtctx = 0;
+ }
+ if ((p_new_driver == "wintab") && wintab_available) {
+ wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
+ wd.wtlc.lcOptions |= CXO_MESSAGES;
+ wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+ wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+ wd.wtlc.lcPktMode = 0;
+ wd.wtlc.lcOutOrgX = 0;
+ wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
+ wd.wtlc.lcOutOrgY = 0;
+ wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
+ wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
+ if (wd.wtctx) {
+ wintab_WTEnable(wd.wtctx, true);
+ AXIS pressure;
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+ wd.min_pressure = int(pressure.axMin);
+ wd.max_pressure = int(pressure.axMax);
+ }
+ AXIS orientation[3];
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+ wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+ }
+ wintab_WTEnable(wd.wtctx, true);
+ } else {
+ print_verbose("WinTab context creation failed.");
+ }
+ }
+ }
+}
+
+DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
+ DWORD dwExStyle;
+ DWORD dwStyle;
+
+ _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
+
+ RECT WindowRect;
+
+ WindowRect.left = p_rect.position.x;
+ WindowRect.right = p_rect.position.x + p_rect.size.x;
+ WindowRect.top = p_rect.position.y;
+ WindowRect.bottom = p_rect.position.y + p_rect.size.y;
+
+ if (p_mode == WINDOW_MODE_FULLSCREEN) {
+ int nearest_area = 0;
+ Rect2i screen_rect;
+ for (int i = 0; i < get_screen_count(); i++) {
+ Rect2i r;
+ r.position = screen_get_position(i);
+ r.size = screen_get_size(i);
+ Rect2 inters = r.clip(p_rect);
+ int area = inters.size.width * inters.size.height;
+ if (area >= nearest_area) {
+ screen_rect = r;
+ nearest_area = area;
+ }
+ }
+
+ WindowRect.left = screen_rect.position.x;
+ WindowRect.right = screen_rect.position.x + screen_rect.size.x;
+ WindowRect.top = screen_rect.position.y;
+ WindowRect.bottom = screen_rect.position.y + screen_rect.size.y;
+ }
+
+ AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
+
+ WindowID id = window_id_counter;
+ {
+ WindowData &wd = windows[id];
+
+ wd.hWnd = CreateWindowExW(
+ dwExStyle,
+ L"Engine", L"",
+ dwStyle,
+ // (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2,
+ // (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2,
+ WindowRect.left,
+ WindowRect.top,
+ WindowRect.right - WindowRect.left,
+ WindowRect.bottom - WindowRect.top,
+ nullptr, nullptr, hInstance, nullptr);
+ if (!wd.hWnd) {
+ MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
+ windows.erase(id);
+ return INVALID_WINDOW_ID;
+ }
+#ifdef VULKAN_ENABLED
+
+ if (rendering_driver == "vulkan") {
+ if (context_vulkan->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ windows.erase(id);
+ ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window.");
+ }
+ }
+#endif
+
+ RegisterTouchWindow(wd.hWnd, 0);
+
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = wd.hWnd;
+ tme.dwHoverTime = HOVER_DEFAULT;
+ TrackMouseEvent(&tme);
+
+ DragAcceptFiles(wd.hWnd, true);
+
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available) {
+ wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
+ wd.wtlc.lcOptions |= CXO_MESSAGES;
+ wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+ wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+ wd.wtlc.lcPktMode = 0;
+ wd.wtlc.lcOutOrgX = 0;
+ wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
+ wd.wtlc.lcOutOrgY = 0;
+ wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
+ wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
+ if (wd.wtctx) {
+ wintab_WTEnable(wd.wtctx, true);
+ AXIS pressure;
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+ wd.min_pressure = int(pressure.axMin);
+ wd.max_pressure = int(pressure.axMax);
+ }
+ AXIS orientation[3];
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+ wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+ }
+ } else {
+ print_verbose("WinTab context creation failed.");
+ }
+ } else {
+ wd.wtctx = 0;
+ }
+
+ wd.last_pressure = 0;
+ wd.last_pressure_update = 0;
+ wd.last_tilt = Vector2();
+
+ // IME
+ wd.im_himc = ImmGetContext(wd.hWnd);
+ ImmReleaseContext(wd.hWnd, wd.im_himc);
+
+ wd.im_position = Vector2();
+ wd.last_pos = p_rect.position;
+ wd.width = p_rect.size.width;
+ wd.height = p_rect.size.height;
+
+ window_id_counter++;
+ }
+
+ return id;
+}
+
+// WinTab API
+bool DisplayServerWindows::wintab_available = false;
+WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr;
+WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr;
+WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr;
+WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr;
+WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr;
+
+// Windows Ink API
+bool DisplayServerWindows::winink_available = false;
+GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
+GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
+
+typedef enum _SHC_PROCESS_DPI_AWARENESS {
+ SHC_PROCESS_DPI_UNAWARE = 0,
+ SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
+ SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2
+} SHC_PROCESS_DPI_AWARENESS;
+
+DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ drop_events = false;
+ key_event_pos = 0;
+
+ alt_mem = false;
+ gr_mem = false;
+ shift_mem = false;
+ control_mem = false;
+ meta_mem = false;
+ console_visible = IsWindowVisible(GetConsoleWindow());
+ hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance();
+
+ pressrc = 0;
+ old_invalid = true;
+ mouse_mode = MOUSE_MODE_VISIBLE;
+
+ outside = true;
+
+ if (OS::get_singleton()->is_hidpi_allowed()) {
+ HMODULE Shcore = LoadLibraryW(L"Shcore.dll");
+
+ if (Shcore != nullptr) {
+ typedef HRESULT(WINAPI * SetProcessDpiAwareness_t)(SHC_PROCESS_DPI_AWARENESS);
+
+ SetProcessDpiAwareness_t SetProcessDpiAwareness = (SetProcessDpiAwareness_t)GetProcAddress(Shcore, "SetProcessDpiAwareness");
+
+ if (SetProcessDpiAwareness) {
+ SetProcessDpiAwareness(SHC_PROCESS_SYSTEM_DPI_AWARE);
+ }
+ }
+ }
+
+ memset(&wc, 0, sizeof(WNDCLASSEXW));
+ wc.cbSize = sizeof(WNDCLASSEXW);
+ wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
+ wc.lpfnWndProc = (WNDPROC)::WndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ //wc.hInstance = hInstance;
+ wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr);
+ wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
+ wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW);
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = L"Engine";
+
+ if (!RegisterClassExW(&wc)) {
+ MessageBox(nullptr, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
+
+ use_raw_input = true;
+
+ RAWINPUTDEVICE Rid[1];
+
+ Rid[0].usUsagePage = 0x01;
+ Rid[0].usUsage = 0x02;
+ Rid[0].dwFlags = 0;
+ Rid[0].hwndTarget = 0;
+
+ if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) {
+ //registration failed.
+ use_raw_input = false;
+ }
+
+ rendering_driver = "vulkan";
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextWindows);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
+ }
+#endif
+#if defined(OPENGL_ENABLED)
+ if (rendering_driver_index == VIDEO_DRIVER_GLES2) {
+ context_gles2 = memnew(ContextGL_Windows(hWnd, false));
+
+ if (context_gles2->initialize() != OK) {
+ memdelete(context_gles2);
+ context_gles2 = nullptr;
+ ERR_FAIL_V(ERR_UNAVAILABLE);
+ }
+
+ context_gles2->set_use_vsync(video_mode.use_vsync);
+ set_vsync_via_compositor(video_mode.vsync_via_compositor);
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ memdelete(context_gles2);
+ context_gles2 = nullptr;
+ ERR_FAIL_V(ERR_UNAVAILABLE);
+ }
+ }
+#endif
+ Point2i window_position(
+ (screen_get_size(0).width - p_resolution.width) / 2,
+ (screen_get_size(0).height - p_resolution.height) / 2);
+
+ WindowID main_window = _create_window(p_mode, 0, Rect2i(window_position, p_resolution));
+ ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window.");
+
+ for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
+ if (p_flags & (1 << i)) {
+ window_set_flag(WindowFlags(i), true, main_window);
+ }
+ }
+
+ show_window(MAIN_WINDOW_ID);
+
+#if defined(VULKAN_ENABLED)
+
+ if (rendering_driver == "vulkan") {
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ move_timer_id = 1;
+
+ //set_ime_active(false);
+
+ if (!OS::get_singleton()->is_in_low_processor_usage_mode()) {
+ //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
+ SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
+ DWORD index = 0;
+ HANDLE handle = AvSetMmThreadCharacteristics("Games", &index);
+ if (handle)
+ AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL);
+
+ // This is needed to make sure that background work does not starve the main thread.
+ // This is only setting priority of this thread, not the whole process.
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+ }
+
+ cursor_shape = CURSOR_ARROW;
+
+ _update_real_mouse_position(MAIN_WINDOW_ID);
+
+ joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
+
+ r_error = OK;
+
+ ((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd);
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+}
+
+Vector<String> DisplayServerWindows::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#ifdef VULKAN_ENABLED
+ drivers.push_back("vulkan");
+#endif
+#ifdef OPENGL_ENABLED
+ drivers.push_back("opengl");
+#endif
+
+ return drivers;
+}
+
+DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n"
+ "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
+ "Unable to initialize Video driver");
+ }
+ return ds;
+}
+
+void DisplayServerWindows::register_windows_driver() {
+ register_create_function("windows", create_func, get_rendering_drivers_func);
+}
+
+DisplayServerWindows::~DisplayServerWindows() {
+ delete joypad;
+ touch_state.clear();
+
+ cursors_cache.clear();
+
+ if (user_proc) {
+ SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
+ };
+
+ if (windows.has(MAIN_WINDOW_ID)) {
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+ }
+#endif
+ if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) {
+ wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx);
+ windows[MAIN_WINDOW_ID].wtctx = 0;
+ }
+ DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
+ }
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ }
+
+ if (context_vulkan)
+ memdelete(context_vulkan);
+ }
+#endif
+}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
new file mode 100644
index 0000000000..0fca2589ae
--- /dev/null
+++ b/platform/windows/display_server_windows.h
@@ -0,0 +1,562 @@
+/*************************************************************************/
+/* display_server_windows.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_WINDOWS_H
+#define DISPLAY_SERVER_WINDOWS_H
+
+#include "servers/display_server.h"
+
+#include "core/input/input.h"
+#include "core/os/os.h"
+#include "core/project_settings.h"
+#include "crash_handler_windows.h"
+#include "drivers/unix/ip_unix.h"
+#include "drivers/wasapi/audio_driver_wasapi.h"
+#include "drivers/winmidi/midi_driver_winmidi.h"
+#include "joypad_windows.h"
+#include "key_mapping_windows.h"
+#include "servers/audio_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#include "servers/rendering_server.h"
+
+#ifdef XAUDIO2_ENABLED
+#include "drivers/xaudio2/audio_driver_xaudio2.h"
+#endif
+
+#if defined(OPENGL_ENABLED)
+#include "context_gl_windows.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/windows/vulkan_context_win.h"
+#endif
+
+#include <fcntl.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+#include <windowsx.h>
+
+// WinTab API
+#define WT_PACKET 0x7FF0
+#define WT_PROXIMITY 0x7FF5
+#define WT_INFOCHANGE 0x7FF6
+#define WT_CSRCHANGE 0x7FF7
+
+#define WTI_DEFSYSCTX 4
+#define WTI_DEVICES 100
+#define DVC_NPRESSURE 15
+#define DVC_TPRESSURE 16
+#define DVC_ORIENTATION 17
+#define DVC_ROTATION 18
+
+#define CXO_MESSAGES 0x0004
+#define PK_NORMAL_PRESSURE 0x0400
+#define PK_TANGENT_PRESSURE 0x0800
+#define PK_ORIENTATION 0x1000
+
+typedef struct tagLOGCONTEXTW {
+ WCHAR lcName[40];
+ UINT lcOptions;
+ UINT lcStatus;
+ UINT lcLocks;
+ UINT lcMsgBase;
+ UINT lcDevice;
+ UINT lcPktRate;
+ DWORD lcPktData;
+ DWORD lcPktMode;
+ DWORD lcMoveMask;
+ DWORD lcBtnDnMask;
+ DWORD lcBtnUpMask;
+ LONG lcInOrgX;
+ LONG lcInOrgY;
+ LONG lcInOrgZ;
+ LONG lcInExtX;
+ LONG lcInExtY;
+ LONG lcInExtZ;
+ LONG lcOutOrgX;
+ LONG lcOutOrgY;
+ LONG lcOutOrgZ;
+ LONG lcOutExtX;
+ LONG lcOutExtY;
+ LONG lcOutExtZ;
+ DWORD lcSensX;
+ DWORD lcSensY;
+ DWORD lcSensZ;
+ BOOL lcSysMode;
+ int lcSysOrgX;
+ int lcSysOrgY;
+ int lcSysExtX;
+ int lcSysExtY;
+ DWORD lcSysSensX;
+ DWORD lcSysSensY;
+} LOGCONTEXTW;
+
+typedef struct tagAXIS {
+ LONG axMin;
+ LONG axMax;
+ UINT axUnits;
+ DWORD axResolution;
+} AXIS;
+
+typedef struct tagORIENTATION {
+ int orAzimuth;
+ int orAltitude;
+ int orTwist;
+} ORIENTATION;
+
+typedef struct tagPACKET {
+ int pkNormalPressure;
+ int pkTangentPressure;
+ ORIENTATION pkOrientation;
+} PACKET;
+
+typedef HANDLE(WINAPI *WTOpenPtr)(HWND p_window, LOGCONTEXTW *p_ctx, BOOL p_enable);
+typedef BOOL(WINAPI *WTClosePtr)(HANDLE p_ctx);
+typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output);
+typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets);
+typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable);
+
+// Windows Ink API
+#ifndef POINTER_STRUCTURES
+
+#define POINTER_STRUCTURES
+
+typedef DWORD POINTER_INPUT_TYPE;
+typedef UINT32 POINTER_FLAGS;
+typedef UINT32 PEN_FLAGS;
+typedef UINT32 PEN_MASK;
+
+#ifndef PEN_MASK_PRESSURE
+#define PEN_MASK_PRESSURE 0x00000001
+#endif
+
+#ifndef PEN_MASK_TILT_X
+#define PEN_MASK_TILT_X 0x00000004
+#endif
+
+#ifndef PEN_MASK_TILT_Y
+#define PEN_MASK_TILT_Y 0x00000008
+#endif
+
+#ifndef POINTER_MESSAGE_FLAG_FIRSTBUTTON
+#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010
+#endif
+
+enum tagPOINTER_INPUT_TYPE {
+ PT_POINTER = 0x00000001,
+ PT_TOUCH = 0x00000002,
+ PT_PEN = 0x00000003,
+ PT_MOUSE = 0x00000004,
+ PT_TOUCHPAD = 0x00000005
+};
+
+typedef enum tagPOINTER_BUTTON_CHANGE_TYPE {
+ POINTER_CHANGE_NONE,
+ POINTER_CHANGE_FIRSTBUTTON_DOWN,
+ POINTER_CHANGE_FIRSTBUTTON_UP,
+ POINTER_CHANGE_SECONDBUTTON_DOWN,
+ POINTER_CHANGE_SECONDBUTTON_UP,
+ POINTER_CHANGE_THIRDBUTTON_DOWN,
+ POINTER_CHANGE_THIRDBUTTON_UP,
+ POINTER_CHANGE_FOURTHBUTTON_DOWN,
+ POINTER_CHANGE_FOURTHBUTTON_UP,
+ POINTER_CHANGE_FIFTHBUTTON_DOWN,
+ POINTER_CHANGE_FIFTHBUTTON_UP,
+} POINTER_BUTTON_CHANGE_TYPE;
+
+typedef struct tagPOINTER_INFO {
+ POINTER_INPUT_TYPE pointerType;
+ UINT32 pointerId;
+ UINT32 frameId;
+ POINTER_FLAGS pointerFlags;
+ HANDLE sourceDevice;
+ HWND hwndTarget;
+ POINT ptPixelLocation;
+ POINT ptHimetricLocation;
+ POINT ptPixelLocationRaw;
+ POINT ptHimetricLocationRaw;
+ DWORD dwTime;
+ UINT32 historyCount;
+ INT32 InputData;
+ DWORD dwKeyStates;
+ UINT64 PerformanceCount;
+ POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
+} POINTER_INFO;
+
+typedef struct tagPOINTER_PEN_INFO {
+ POINTER_INFO pointerInfo;
+ PEN_FLAGS penFlags;
+ PEN_MASK penMask;
+ UINT32 pressure;
+ UINT32 rotation;
+ INT32 tiltX;
+ INT32 tiltY;
+} POINTER_PEN_INFO;
+
+#endif //POINTER_STRUCTURES
+
+#ifndef WM_POINTERUPDATE
+#define WM_POINTERUPDATE 0x0245
+#endif
+
+#ifndef WM_POINTERENTER
+#define WM_POINTERENTER 0x0249
+#endif
+
+#ifndef WM_POINTERLEAVE
+#define WM_POINTERLEAVE 0x024A
+#endif
+
+typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
+typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
+
+typedef struct {
+ BYTE bWidth; // Width, in pixels, of the image
+ BYTE bHeight; // Height, in pixels, of the image
+ BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
+ BYTE bReserved; // Reserved ( must be 0)
+ WORD wPlanes; // Color Planes
+ WORD wBitCount; // Bits per pixel
+ DWORD dwBytesInRes; // How many bytes in this resource?
+ DWORD dwImageOffset; // Where in the file is this image?
+} ICONDIRENTRY, *LPICONDIRENTRY;
+
+typedef struct {
+ WORD idReserved; // Reserved (must be 0)
+ WORD idType; // Resource Type (1 for icons)
+ WORD idCount; // How many images?
+ ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
+} ICONDIR, *LPICONDIR;
+
+class DisplayServerWindows : public DisplayServer {
+ //No need to register, it's platform-specific and nothing is added
+ //GDCLASS(DisplayServerWindows, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+public:
+ // WinTab API
+ static bool wintab_available;
+ static WTOpenPtr wintab_WTOpen;
+ static WTClosePtr wintab_WTClose;
+ static WTInfoPtr wintab_WTInfo;
+ static WTPacketPtr wintab_WTPacket;
+ static WTEnablePtr wintab_WTEnable;
+
+ // Windows Ink API
+ static bool winink_available;
+ static GetPointerTypePtr win8p_GetPointerType;
+ static GetPointerPenInfoPtr win8p_GetPointerPenInfo;
+
+ void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
+
+private:
+ void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap);
+
+ enum {
+ KEY_EVENT_BUFFER_SIZE = 512
+ };
+
+ struct KeyEvent {
+ WindowID window_id;
+ bool alt, shift, control, meta;
+ UINT uMsg;
+ WPARAM wParam;
+ LPARAM lParam;
+ };
+
+ KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE];
+ int key_event_pos;
+
+ bool old_invalid;
+ bool outside;
+ int old_x, old_y;
+ Point2i center;
+
+#if defined(OPENGL_ENABLED)
+ ContextGL_Windows *context_gles2;
+#endif
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextWindows *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ Map<int, Vector2> touch_state;
+
+ int pressrc;
+ HINSTANCE hInstance; // Holds The Instance Of The Application
+ String rendering_driver;
+ bool app_focused = false;
+
+ struct WindowData {
+ HWND hWnd;
+ //layered window
+
+ Vector<Vector2> mpath;
+
+ bool preserve_window_size = false;
+ bool pre_fs_valid = false;
+ RECT pre_fs_rect;
+ bool maximized = false;
+ bool minimized = false;
+ bool fullscreen = false;
+ bool borderless = false;
+ bool resizable = true;
+ bool window_focused = false;
+ bool was_maximized = false;
+ bool always_on_top = false;
+ bool no_focus = false;
+ bool window_has_focus = false;
+
+ HANDLE wtctx;
+ LOGCONTEXTW wtlc;
+ int min_pressure;
+ int max_pressure;
+ bool tilt_supported;
+ bool block_mm = false;
+
+ int last_pressure_update;
+ float last_pressure;
+ Vector2 last_tilt;
+
+ HBITMAP hBitmap; //DIB section for layered window
+ uint8_t *dib_data = nullptr;
+ Size2 dib_size;
+ HDC hDC_dib;
+ Size2 min_size;
+ Size2 max_size;
+ int width = 0, height = 0;
+
+ Size2 window_rect;
+ Point2 last_pos;
+
+ ObjectID instance_id;
+
+ // IME
+ HIMC im_himc;
+ Vector2 im_position;
+
+ bool layered_window = false;
+
+ Callable rect_changed_callback;
+ Callable event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ WindowID transient_parent = INVALID_WINDOW_ID;
+ Set<WindowID> transient_children;
+ };
+
+ JoypadWindows *joypad;
+
+ WindowID _create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect);
+ WindowID window_id_counter = MAIN_WINDOW_ID;
+ Map<WindowID, WindowData> windows;
+
+ WindowID last_focused_window = INVALID_WINDOW_ID;
+
+ uint32_t move_timer_id;
+
+ HCURSOR hCursor;
+
+ WNDPROC user_proc = nullptr;
+
+ void _send_window_event(const WindowData &wd, WindowEvent p_event);
+ void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
+
+ MouseMode mouse_mode;
+ bool alt_mem = false;
+ bool gr_mem = false;
+ bool shift_mem = false;
+ bool control_mem = false;
+ bool meta_mem = false;
+ uint32_t last_button_state = 0;
+ bool use_raw_input = false;
+ bool drop_events = false;
+ bool in_dispatch_input_event = false;
+ bool console_visible = false;
+
+ WNDCLASSEXW wc;
+
+ HCURSOR cursors[CURSOR_MAX] = { nullptr };
+ CursorShape cursor_shape;
+ Map<CursorShape, Vector<Variant>> cursors_cache;
+
+ void _drag_event(WindowID p_window, float p_x, float p_y, int idx);
+ void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx);
+
+ void _update_window_style(WindowID p_window, bool p_repaint = true, bool p_maximized = false);
+ void _update_window_mouse_passthrough(WindowID p_window);
+
+ void _update_real_mouse_position(WindowID p_window);
+
+ void _set_mouse_mode_impl(MouseMode p_mode);
+
+ void _process_key_events();
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+public:
+ LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+ virtual bool has_feature(Feature p_feature) const;
+ virtual String get_name() const;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+
+ virtual void mouse_set_mode(MouseMode p_mode);
+ virtual MouseMode mouse_get_mode() const;
+
+ virtual void mouse_warp_to_position(const Point2i &p_to);
+ virtual Point2i mouse_get_position() const;
+ virtual int mouse_get_button_state() const;
+
+ virtual void clipboard_set(const String &p_text);
+ virtual String clipboard_get() const;
+
+ virtual int get_screen_count() const;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+ virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW);
+ ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+
+ virtual void screen_set_keep_on(bool p_enable); //disable screensaver
+ virtual bool screen_is_kept_on() const;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const;
+
+ virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
+ virtual void show_window(WindowID p_window);
+ virtual void delete_sub_window(WindowID p_window);
+
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent);
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const; //wtf is this? should probable use proper name
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
+
+ virtual bool can_any_window_draw() const;
+
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID);
+
+ virtual void console_set_visible(bool p_enabled);
+ virtual bool is_console_visible() const;
+
+ virtual void cursor_set_shape(CursorShape p_shape);
+ virtual CursorShape cursor_get_shape() const;
+ virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2());
+
+ virtual bool get_swap_cancel_ok();
+
+ virtual void enable_for_stealing_focus(OS::ProcessID pid);
+
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
+
+ virtual void process_events();
+
+ virtual void force_process_and_drop_events();
+
+ virtual void release_rendering_thread();
+ virtual void make_rendering_thread();
+ virtual void swap_buffers();
+
+ virtual void set_native_icon(const String &p_filename);
+ virtual void set_icon(const Ref<Image> &p_icon);
+
+ virtual void vsync_set_use_via_compositor(bool p_enable);
+ virtual bool vsync_is_using_via_compositor() const;
+
+ virtual void set_context(Context p_context);
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+ static void register_windows_driver();
+
+ DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerWindows();
+};
+
+#endif // DISPLAY_SERVER_WINDOWS_H
diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp
index 34d66ecd79..c2436e8b64 100644
--- a/platform/windows/export/export.cpp
+++ b/platform/windows/export/export.cpp
@@ -38,7 +38,6 @@
static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size);
class EditorExportPlatformWindows : public EditorExportPlatformPC {
-
void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
@@ -85,7 +84,7 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
@@ -105,7 +104,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
}
if (!FileAccess::exists(rcedit_path)) {
- ERR_PRINTS("Could not find rcedit executable at " + rcedit_path + ", no icon or app information data will be included.");
+ ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", no icon or app information data will be included.");
return;
}
@@ -114,7 +113,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
String wine_path = EditorSettings::get_singleton()->get("export/windows/wine");
if (wine_path != String() && !FileAccess::exists(wine_path)) {
- ERR_PRINTS("Could not find wine executable at " + wine_path + ", no icon or app information data will be included.");
+ ERR_PRINT("Could not find wine executable at " + wine_path + ", no icon or app information data will be included.");
return;
}
@@ -188,7 +187,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#ifdef WINDOWS_ENABLED
String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool");
if (signtool_path != String() && !FileAccess::exists(signtool_path)) {
- ERR_PRINTS("Could not find signtool executable at " + signtool_path + ", aborting.");
+ ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting.");
return ERR_FILE_NOT_FOUND;
}
if (signtool_path == String()) {
@@ -197,7 +196,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#else
String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode");
if (signtool_path != String() && !FileAccess::exists(signtool_path)) {
- ERR_PRINTS("Could not find osslsigncode executable at " + signtool_path + ", aborting.");
+ ERR_PRINT("Could not find osslsigncode executable at " + signtool_path + ", aborting.");
return ERR_FILE_NOT_FOUND;
}
if (signtool_path == String()) {
@@ -297,7 +296,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
}
//user options
- PoolStringArray user_args = p_preset->get("codesign/custom_options");
+ PackedStringArray user_args = p_preset->get("codesign/custom_options");
for (int i = 0; i < user_args.size(); i++) {
String user_arg = user_args[i].strip_edges();
if (!user_arg.empty()) {
@@ -315,7 +314,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#endif
String str;
- Error err = OS::get_singleton()->execute(signtool_path, args, true, NULL, &str, NULL, true);
+ Error err = OS::get_singleton()->execute(signtool_path, args, true, nullptr, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
print_line("codesign (" + p_path + "): " + str);
@@ -331,7 +330,6 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
}
void register_windows_exporter() {
-
EDITOR_DEF("export/windows/rcedit", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
#ifdef WINDOWS_ENABLED
@@ -366,7 +364,6 @@ void register_windows_exporter() {
}
static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
-
// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data
FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE);
@@ -408,7 +405,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start,
bool found = false;
for (int i = 0; i < num_sections; ++i) {
-
int64_t section_header_pos = section_table_pos + i * 40;
f->seek(section_header_pos);
diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis
index 593557cc69..90f0b55d0a 100644
--- a/platform/windows/godot.natvis
+++ b/platform/windows/godot.natvis
@@ -19,7 +19,7 @@
</ArrayItems>
</Expand>
</Type>
-
+
<Type Name="List&lt;*&gt;">
<Expand>
<Item Name="[size]">_data ? (_data->size_cache) : 0</Item>
@@ -62,7 +62,7 @@
<DisplayString Condition="type == Variant::POOL_COLOR_ARRAY">{*(PoolColorArray *)_data._mem}</DisplayString>
<StringView Condition="type == Variant::STRING &amp;&amp; ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,su</StringView>
-
+
<Expand>
<Item Name="[value]" Condition="type == Variant::BOOL">_data._bool</Item>
<Item Name="[value]" Condition="type == Variant::INT">_data._int</Item>
@@ -143,7 +143,7 @@
<Item Name="alpha">a</Item>
</Expand>
</Type>
-
+
<Type Name="Node" Inheritable="false">
<Expand>
<Item Name="Object">(Object*)this</Item>
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index dcc12b7649..add559a717 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -121,23 +121,22 @@ CommandLineToArgvA(
i++;
}
_argv[j] = '\0';
- argv[argc] = NULL;
+ argv[argc] = nullptr;
(*_argc) = argc;
return argv;
}
char *wc_to_utf8(const wchar_t *wc) {
- int ulen = WideCharToMultiByte(CP_UTF8, 0, wc, -1, NULL, 0, NULL, NULL);
+ int ulen = WideCharToMultiByte(CP_UTF8, 0, wc, -1, nullptr, 0, nullptr, nullptr);
char *ubuf = new char[ulen + 1];
- WideCharToMultiByte(CP_UTF8, 0, wc, -1, ubuf, ulen, NULL, NULL);
+ WideCharToMultiByte(CP_UTF8, 0, wc, -1, ubuf, ulen, nullptr, nullptr);
ubuf[ulen] = 0;
return ubuf;
}
int widechar_main(int argc, wchar_t **argv) {
-
- OS_Windows os(NULL);
+ OS_Windows os(nullptr);
setlocale(LC_CTYPE, "");
@@ -147,6 +146,8 @@ int widechar_main(int argc, wchar_t **argv) {
argv_utf8[i] = wc_to_utf8(argv[i]);
}
+ TEST_MAIN_PARAM_OVERRIDE(argc, argv_utf8)
+
Error err = Main::setup(argv_utf8[0], argc - 1, &argv_utf8[1]);
if (err != OK) {
@@ -176,7 +177,7 @@ int _main() {
wc_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
- if (NULL == wc_argv) {
+ if (nullptr == wc_argv) {
wprintf(L"CommandLineToArgvW failed\n");
return 0;
}
@@ -187,10 +188,12 @@ int _main() {
return result;
}
-int main(int _argc, char **_argv) {
+int main(int argc, char **argv) {
+ // override the arguments for the test handler / if symbol is provided
+ // TEST_MAIN_OVERRIDE
+
// _argc and _argv are ignored
// we are going to use the WideChar version of them instead
-
#ifdef CRASH_HANDLER_EXCEPTION
__try {
return _main();
@@ -202,9 +205,9 @@ int main(int _argc, char **_argv) {
#endif
}
-HINSTANCE godot_hinstance = NULL;
+HINSTANCE godot_hinstance = nullptr;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
godot_hinstance = hInstance;
- return main(0, NULL);
+ return main(0, nullptr);
}
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 49432435b9..d1454c9096 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -33,10 +33,6 @@
#include <oleauto.h>
#include <wbemidl.h>
-#ifndef __GNUC__
-#define __builtin_bswap32 _byteswap_ulong
-#endif
-
#if defined(__GNUC__)
// Workaround GCC warning from -Wcast-function-type.
#define GetProcAddress (void *)GetProcAddress
@@ -45,6 +41,7 @@
DWORD WINAPI _xinput_get_state(DWORD dwUserIndex, XINPUT_STATE *pState) {
return ERROR_DEVICE_NOT_CONNECTED;
}
+
DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) {
return ERROR_DEVICE_NOT_CONNECTED;
}
@@ -52,42 +49,45 @@ DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
JoypadWindows::JoypadWindows() {
}
-JoypadWindows::JoypadWindows(InputDefault *_input, HWND *hwnd) {
-
- input = _input;
+JoypadWindows::JoypadWindows(HWND *hwnd) {
+ input = Input::get_singleton();
hWnd = hwnd;
joypad_count = 0;
- dinput = NULL;
- xinput_dll = NULL;
- xinput_get_state = NULL;
- xinput_set_state = NULL;
+ dinput = nullptr;
+ xinput_dll = nullptr;
+ xinput_get_state = nullptr;
+ xinput_set_state = nullptr;
load_xinput();
for (int i = 0; i < JOYPADS_MAX; i++)
attached_joypads[i] = false;
- HRESULT result;
- result = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, NULL);
- if (FAILED(result)) {
- printf("failed init DINPUT: %ld\n", result);
+ HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr);
+ if (result == DI_OK) {
+ probe_joypads();
+ } else {
+ ERR_PRINT("Couldn't initialize DirectInput. Error: " + itos(result));
+ if (result == DIERR_OUTOFMEMORY) {
+ ERR_PRINT("The Windows DirectInput subsystem could not allocate sufficient memory.");
+ ERR_PRINT("Rebooting your PC may solve this issue.");
+ }
+ // Ensure dinput is still a nullptr.
+ dinput = nullptr;
}
- probe_joypads();
}
JoypadWindows::~JoypadWindows() {
-
close_joypad();
- dinput->Release();
+ if (dinput) {
+ dinput->Release();
+ }
unload_xinput();
}
bool JoypadWindows::have_device(const GUID &p_guid) {
-
for (int i = 0; i < JOYPADS_MAX; i++) {
-
if (d_joypads[i].guid == p_guid) {
-
d_joypads[i].confirmed = true;
return true;
}
@@ -97,7 +97,6 @@ bool JoypadWindows::have_device(const GUID &p_guid) {
// adapted from SDL2, works a lot better than the MSDN version
bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
-
static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
@@ -105,21 +104,21 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
if (p_guid == &IID_ValveStreamingGamepad || p_guid == &IID_X360WiredGamepad || p_guid == &IID_X360WirelessGamepad)
return true;
- PRAWINPUTDEVICELIST dev_list = NULL;
+ PRAWINPUTDEVICELIST dev_list = nullptr;
unsigned int dev_list_count = 0;
- if (GetRawInputDeviceList(NULL, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
+ if (GetRawInputDeviceList(nullptr, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
return false;
}
dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count);
- if (!dev_list) return false;
+ if (!dev_list)
+ return false;
if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
free(dev_list);
return false;
}
for (unsigned int i = 0; i < dev_list_count; i++) {
-
RID_DEVICE_INFO rdi;
char dev_name[128];
UINT rdiSize = sizeof(rdi);
@@ -130,8 +129,7 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
(GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1) &&
(MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)p_guid->Data1) &&
(GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICENAME, &dev_name, &nameSize) != (UINT)-1) &&
- (strstr(dev_name, "IG_") != NULL)) {
-
+ (strstr(dev_name, "IG_") != nullptr)) {
free(dev_list);
return true;
}
@@ -141,15 +139,15 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
}
bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
-
+ ERR_FAIL_NULL_V_MSG(dinput, false, "DirectInput not initialized. Rebooting your PC may solve this issue.");
HRESULT hr;
int num = input->get_unused_joy_id();
if (have_device(instance->guidInstance) || num == -1)
return false;
- d_joypads[joypad_count] = dinput_gamepad();
- dinput_gamepad *joy = &d_joypads[joypad_count];
+ d_joypads[num] = dinput_gamepad();
+ dinput_gamepad *joy = &d_joypads[num];
const DWORD devtype = (instance->dwDevType & 0xFF);
@@ -157,7 +155,7 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
return false;
}
- hr = dinput->CreateDevice(instance->guidInstance, &joy->di_joy, NULL);
+ hr = dinput->CreateDevice(instance->guidInstance, &joy->di_joy, nullptr);
if (FAILED(hr)) {
return false;
@@ -165,12 +163,16 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
const GUID &guid = instance->guidProduct;
char uid[128];
- sprintf_s(uid, "%08lx%04hx%04hx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
- __builtin_bswap32(guid.Data1), guid.Data2, guid.Data3,
- guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
- guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
- id_to_change = joypad_count;
+ ERR_FAIL_COND_V_MSG(memcmp(&guid.Data4[2], "PIDVID", 6), false, "DirectInput device not recognised.");
+ WORD type = BSWAP16(0x03);
+ WORD vendor = BSWAP16(LOWORD(guid.Data1));
+ WORD product = BSWAP16(HIWORD(guid.Data1));
+ WORD version = 0;
+ sprintf_s(uid, "%04x%04x%04x%04x%04x%04x%04x%04x", type, 0, vendor, 0, product, 0, version, 0);
+
+ id_to_change = num;
+ slider_count = 0;
joy->di_joy->SetDataFormat(&c_dfDIJoystick2);
joy->di_joy->SetCooperativeLevel(*hWnd, DISCL_FOREGROUND);
@@ -188,9 +190,7 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
}
void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) {
-
if (ob->dwType & DIDFT_AXIS) {
-
HRESULT res;
DIPROPRANGE prop_range;
DIPROPDWORD dilong;
@@ -207,9 +207,14 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
ofs = DIJOFS_RY;
else if (ob->guidType == GUID_RzAxis)
ofs = DIJOFS_RZ;
- else if (ob->guidType == GUID_Slider)
- ofs = DIJOFS_SLIDER(0);
- else
+ else if (ob->guidType == GUID_Slider) {
+ if (slider_count < 2) {
+ ofs = DIJOFS_SLIDER(slider_count);
+ slider_count++;
+ } else {
+ return;
+ }
+ } else
return;
prop_range.diph.dwSize = sizeof(DIPROPRANGE);
prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
@@ -239,7 +244,6 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
}
BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) {
-
JoypadWindows *self = (JoypadWindows *)p_context;
if (self->is_xinput_device(&p_instance->guidProduct)) {
return DIENUM_CONTINUE;
@@ -249,7 +253,6 @@ BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, vo
}
BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context) {
-
JoypadWindows *self = (JoypadWindows *)context;
self->setup_joypad_object(instance, self->id_to_change);
@@ -257,17 +260,15 @@ BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *insta
}
void JoypadWindows::close_joypad(int id) {
-
if (id == -1) {
-
for (int i = 0; i < JOYPADS_MAX; i++) {
-
close_joypad(i);
}
return;
}
- if (!d_joypads[id].attached) return;
+ if (!d_joypads[id].attached)
+ return;
d_joypads[id].di_joy->Unacquire();
d_joypads[id].di_joy->Release();
@@ -279,18 +280,15 @@ void JoypadWindows::close_joypad(int id) {
}
void JoypadWindows::probe_joypads() {
-
+ ERR_FAIL_NULL_MSG(dinput, "DirectInput not initialized. Rebooting your PC may solve this issue.");
DWORD dwResult;
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
-
ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE));
dwResult = xinput_get_state(i, &x_joypads[i].state);
if (dwResult == ERROR_SUCCESS) {
-
int id = input->get_unused_joy_id();
if (id != -1 && !x_joypads[i].attached) {
-
x_joypads[i].attached = true;
x_joypads[i].id = id;
x_joypads[i].ff_timestamp = 0;
@@ -300,7 +298,6 @@ void JoypadWindows::probe_joypads() {
input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__");
}
} else if (x_joypads[i].attached) {
-
x_joypads[i].attached = false;
attached_joypads[x_joypads[i].id] = false;
input->joy_connection_changed(x_joypads[i].id, false, "");
@@ -308,27 +305,22 @@ void JoypadWindows::probe_joypads() {
}
for (int i = 0; i < joypad_count; i++) {
-
d_joypads[i].confirmed = false;
}
dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY);
for (int i = 0; i < joypad_count; i++) {
-
if (!d_joypads[i].confirmed) {
-
close_joypad(i);
}
}
}
void JoypadWindows::process_joypads() {
-
HRESULT hr;
for (int i = 0; i < XUSER_MAX_COUNT; i++) {
-
xinput_gamepad &joy = x_joypads[i];
if (!joy.attached) {
continue;
@@ -337,20 +329,18 @@ void JoypadWindows::process_joypads() {
xinput_get_state(i, &joy.state);
if (joy.state.dwPacketNumber != joy.last_packet) {
-
int button_mask = XINPUT_GAMEPAD_DPAD_UP;
for (int j = 0; j <= 16; j++) {
-
input->joy_button(joy.id, j, joy.state.Gamepad.wButtons & button_mask);
button_mask = button_mask * 2;
}
- input->joy_axis(joy.id, JOY_AXIS_0, axis_correct(joy.state.Gamepad.sThumbLX, true));
- input->joy_axis(joy.id, JOY_AXIS_1, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true));
- input->joy_axis(joy.id, JOY_AXIS_2, axis_correct(joy.state.Gamepad.sThumbRX, true));
- input->joy_axis(joy.id, JOY_AXIS_3, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true));
- input->joy_axis(joy.id, JOY_AXIS_4, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true));
- input->joy_axis(joy.id, JOY_AXIS_5, axis_correct(joy.state.Gamepad.bRightTrigger, true, true));
+ input->joy_axis(joy.id, JOY_AXIS_LEFT_X, axis_correct(joy.state.Gamepad.sThumbLX, true));
+ input->joy_axis(joy.id, JOY_AXIS_LEFT_Y, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true));
+ input->joy_axis(joy.id, JOY_AXIS_RIGHT_X, axis_correct(joy.state.Gamepad.sThumbRX, true));
+ input->joy_axis(joy.id, JOY_AXIS_RIGHT_Y, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true));
+ input->joy_axis(joy.id, JOY_AXIS_TRIGGER_LEFT, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true));
+ input->joy_axis(joy.id, JOY_AXIS_TRIGGER_RIGHT, axis_correct(joy.state.Gamepad.bRightTrigger, true, true));
joy.last_packet = joy.state.dwPacketNumber;
}
uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id);
@@ -370,7 +360,6 @@ void JoypadWindows::process_joypads() {
}
for (int i = 0; i < JOYPADS_MAX; i++) {
-
dinput_gamepad *joy = &d_joypads[i];
if (!joy->attached)
@@ -391,18 +380,13 @@ void JoypadWindows::process_joypads() {
post_hat(joy->id, js.rgdwPOV[0]);
for (int j = 0; j < 128; j++) {
-
if (js.rgbButtons[j] & 0x80) {
-
if (!joy->last_buttons[j]) {
-
input->joy_button(joy->id, j, true);
joy->last_buttons[j] = true;
}
} else {
-
if (joy->last_buttons[j]) {
-
input->joy_button(joy->id, j, false);
joy->last_buttons[j] = false;
}
@@ -410,12 +394,11 @@ void JoypadWindows::process_joypads() {
}
// on mingw, these constants are not constants
- int count = 6;
- unsigned int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ };
- int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz };
+ int count = 8;
+ unsigned int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, DIJOFS_SLIDER(0), DIJOFS_SLIDER(1) };
+ int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] };
for (int j = 0; j < joy->joy_axis.size(); j++) {
-
for (int k = 0; k < count; k++) {
if (joy->joy_axis[j] == axes[k]) {
input->joy_axis(joy->id, j, axis_correct(values[k]));
@@ -428,7 +411,6 @@ void JoypadWindows::process_joypads() {
}
void JoypadWindows::post_hat(int p_device, DWORD p_dpad) {
-
int dpad_val = 0;
// Should be -1 when centered, but according to docs:
@@ -436,53 +418,43 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) {
// BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);"
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v%3Dvs.85)#remarks
if (LOWORD(p_dpad) == 0xFFFF) {
- dpad_val = InputDefault::HAT_MASK_CENTER;
+ dpad_val = Input::HAT_MASK_CENTER;
}
if (p_dpad == 0) {
-
- dpad_val = InputDefault::HAT_MASK_UP;
+ dpad_val = Input::HAT_MASK_UP;
} else if (p_dpad == 4500) {
-
- dpad_val = (InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT);
+ dpad_val = (Input::HAT_MASK_UP | Input::HAT_MASK_RIGHT);
} else if (p_dpad == 9000) {
-
- dpad_val = InputDefault::HAT_MASK_RIGHT;
+ dpad_val = Input::HAT_MASK_RIGHT;
} else if (p_dpad == 13500) {
-
- dpad_val = (InputDefault::HAT_MASK_RIGHT | InputDefault::HAT_MASK_DOWN);
+ dpad_val = (Input::HAT_MASK_RIGHT | Input::HAT_MASK_DOWN);
} else if (p_dpad == 18000) {
-
- dpad_val = InputDefault::HAT_MASK_DOWN;
+ dpad_val = Input::HAT_MASK_DOWN;
} else if (p_dpad == 22500) {
-
- dpad_val = (InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT);
+ dpad_val = (Input::HAT_MASK_DOWN | Input::HAT_MASK_LEFT);
} else if (p_dpad == 27000) {
-
- dpad_val = InputDefault::HAT_MASK_LEFT;
+ dpad_val = Input::HAT_MASK_LEFT;
} else if (p_dpad == 31500) {
-
- dpad_val = (InputDefault::HAT_MASK_LEFT | InputDefault::HAT_MASK_UP);
+ dpad_val = (Input::HAT_MASK_LEFT | Input::HAT_MASK_UP);
}
input->joy_hat(p_device, dpad_val);
};
-InputDefault::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const {
-
- InputDefault::JoyAxis jx;
+Input::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const {
+ Input::JoyAxis jx;
if (Math::abs(p_val) < MIN_JOY_AXIS) {
jx.min = p_trigger ? 0 : -1;
jx.value = 0.0f;
return jx;
}
if (p_xinput) {
-
if (p_trigger) {
jx.min = 0;
jx.value = (float)p_val / MAX_TRIGGER;
@@ -532,7 +504,6 @@ void JoypadWindows::joypad_vibration_stop_xinput(int p_device, uint64_t p_timest
}
void JoypadWindows::load_xinput() {
-
xinput_get_state = &_xinput_get_state;
xinput_set_state = &_xinput_set_state;
xinput_dll = LoadLibrary("XInput1_4.dll");
@@ -559,9 +530,7 @@ void JoypadWindows::load_xinput() {
}
void JoypadWindows::unload_xinput() {
-
if (xinput_dll) {
-
FreeLibrary((HMODULE)xinput_dll);
}
}
diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h
index ab85bc60ac..c961abf0a5 100644
--- a/platform/windows/joypad_windows.h
+++ b/platform/windows/joypad_windows.h
@@ -39,9 +39,9 @@
#ifndef SAFE_RELEASE // when Windows Media Device M? is not present
#define SAFE_RELEASE(x) \
- if (x != NULL) { \
+ if (x != nullptr) { \
x->Release(); \
- x = NULL; \
+ x = nullptr; \
}
#endif
@@ -52,7 +52,7 @@
class JoypadWindows {
public:
JoypadWindows();
- JoypadWindows(InputDefault *_input, HWND *hwnd);
+ JoypadWindows(HWND *hwnd);
~JoypadWindows();
void probe_joypads();
@@ -70,7 +70,6 @@ private:
};
struct dinput_gamepad {
-
int id;
bool attached;
bool confirmed;
@@ -93,7 +92,6 @@ private:
};
struct xinput_gamepad {
-
int id;
bool attached;
bool vibrating;
@@ -117,9 +115,10 @@ private:
HWND *hWnd;
HANDLE xinput_dll;
LPDIRECTINPUT8 dinput;
- InputDefault *input;
+ Input *input;
int id_to_change;
+ int slider_count;
int joypad_count;
bool attached_joypads[JOYPADS_MAX];
dinput_gamepad d_joypads[JOYPADS_MAX];
@@ -141,7 +140,7 @@ private:
void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp);
- InputDefault::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const;
+ Input::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const;
XInputGetState_t xinput_get_state;
XInputSetState_t xinput_set_state;
};
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index c76b31ca9c..d8d0b13068 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -33,7 +33,6 @@
#include <stdio.h>
struct _WinTranslatePair {
-
unsigned int keysym;
unsigned int keycode;
};
@@ -130,7 +129,7 @@ static _WinTranslatePair _vk_to_keycode[] = {
{ KEY_MASK_META, VK_LWIN }, //(0x5B)
{ KEY_MASK_META, VK_RWIN }, //(0x5C)
- //VK_APPS (0x5D)
+ { KEY_MENU, VK_APPS }, //(0x5D)
{ KEY_STANDBY, VK_SLEEP }, //(0x5F)
{ KEY_KP_0, VK_NUMPAD0 }, //(0x60)
{ KEY_KP_1, VK_NUMPAD1 }, //(0x61)
@@ -238,10 +237,106 @@ VK_PA1 (0xFD)
VK_OEM_CLEAR (0xFE)
*/
-unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) {
+static _WinTranslatePair _scancode_to_keycode[] = {
+
+ { KEY_ESCAPE, 0x01 },
+ { KEY_1, 0x02 },
+ { KEY_2, 0x03 },
+ { KEY_3, 0x04 },
+ { KEY_4, 0x05 },
+ { KEY_5, 0x06 },
+ { KEY_6, 0x07 },
+ { KEY_7, 0x08 },
+ { KEY_8, 0x09 },
+ { KEY_9, 0x0A },
+ { KEY_0, 0x0B },
+ { KEY_MINUS, 0x0C },
+ { KEY_EQUAL, 0x0D },
+ { KEY_BACKSPACE, 0x0E },
+ { KEY_TAB, 0x0F },
+ { KEY_Q, 0x10 },
+ { KEY_W, 0x11 },
+ { KEY_E, 0x12 },
+ { KEY_R, 0x13 },
+ { KEY_T, 0x14 },
+ { KEY_Y, 0x15 },
+ { KEY_U, 0x16 },
+ { KEY_I, 0x17 },
+ { KEY_O, 0x18 },
+ { KEY_P, 0x19 },
+ { KEY_BRACELEFT, 0x1A },
+ { KEY_BRACERIGHT, 0x1B },
+ { KEY_ENTER, 0x1C },
+ { KEY_CONTROL, 0x1D },
+ { KEY_A, 0x1E },
+ { KEY_S, 0x1F },
+ { KEY_D, 0x20 },
+ { KEY_F, 0x21 },
+ { KEY_G, 0x22 },
+ { KEY_H, 0x23 },
+ { KEY_J, 0x24 },
+ { KEY_K, 0x25 },
+ { KEY_L, 0x26 },
+ { KEY_SEMICOLON, 0x27 },
+ { KEY_APOSTROPHE, 0x28 },
+ { KEY_QUOTELEFT, 0x29 },
+ { KEY_SHIFT, 0x2A },
+ { KEY_BACKSLASH, 0x2B },
+ { KEY_Z, 0x2C },
+ { KEY_X, 0x2D },
+ { KEY_C, 0x2E },
+ { KEY_V, 0x2F },
+ { KEY_B, 0x30 },
+ { KEY_N, 0x31 },
+ { KEY_M, 0x32 },
+ { KEY_COMMA, 0x33 },
+ { KEY_PERIOD, 0x34 },
+ { KEY_SLASH, 0x35 },
+ { KEY_SHIFT, 0x36 },
+ { KEY_PRINT, 0x37 },
+ { KEY_ALT, 0x38 },
+ { KEY_SPACE, 0x39 },
+ { KEY_CAPSLOCK, 0x3A },
+ { KEY_F1, 0x3B },
+ { KEY_F2, 0x3C },
+ { KEY_F3, 0x3D },
+ { KEY_F4, 0x3E },
+ { KEY_F5, 0x3F },
+ { KEY_F6, 0x40 },
+ { KEY_F7, 0x41 },
+ { KEY_F8, 0x42 },
+ { KEY_F9, 0x43 },
+ { KEY_F10, 0x44 },
+ { KEY_NUMLOCK, 0x45 },
+ { KEY_SCROLLLOCK, 0x46 },
+ { KEY_HOME, 0x47 },
+ { KEY_UP, 0x48 },
+ { KEY_PAGEUP, 0x49 },
+ { KEY_KP_SUBTRACT, 0x4A },
+ { KEY_LEFT, 0x4B },
+ { KEY_KP_5, 0x4C },
+ { KEY_RIGHT, 0x4D },
+ { KEY_KP_ADD, 0x4E },
+ { KEY_END, 0x4F },
+ { KEY_DOWN, 0x50 },
+ { KEY_PAGEDOWN, 0x51 },
+ { KEY_INSERT, 0x52 },
+ { KEY_DELETE, 0x53 },
+ //{ KEY_???, 0x56 }, //NON US BACKSLASH
+ { KEY_F11, 0x57 },
+ { KEY_F12, 0x58 },
+ { KEY_META, 0x5B },
+ { KEY_META, 0x5C },
+ { KEY_MENU, 0x5D },
+ { KEY_F13, 0x64 },
+ { KEY_F14, 0x65 },
+ { KEY_F15, 0x66 },
+ { KEY_F16, 0x67 },
+ { KEY_UNKNOWN, 0 }
+};
+unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) {
for (int i = 0; _vk_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
-
if (_vk_to_keycode[i].keycode == p_code) {
//printf("outcode: %x\n",_vk_to_keycode[i].keysym);
@@ -251,3 +346,81 @@ unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) {
return KEY_UNKNOWN;
}
+
+unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended) {
+ unsigned int keycode = KEY_UNKNOWN;
+ for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+ if (_scancode_to_keycode[i].keycode == p_code) {
+ keycode = _scancode_to_keycode[i].keysym;
+ break;
+ }
+ }
+
+ if (p_extended) {
+ switch (keycode) {
+ case KEY_ENTER: {
+ keycode = KEY_KP_ENTER;
+ } break;
+ case KEY_SLASH: {
+ keycode = KEY_KP_DIVIDE;
+ } break;
+ case KEY_CAPSLOCK: {
+ keycode = KEY_KP_ADD;
+ } break;
+ }
+ } else {
+ switch (keycode) {
+ case KEY_NUMLOCK: {
+ keycode = KEY_PAUSE;
+ } break;
+ case KEY_HOME: {
+ keycode = KEY_KP_7;
+ } break;
+ case KEY_UP: {
+ keycode = KEY_KP_8;
+ } break;
+ case KEY_PAGEUP: {
+ keycode = KEY_KP_9;
+ } break;
+ case KEY_LEFT: {
+ keycode = KEY_KP_4;
+ } break;
+ case KEY_RIGHT: {
+ keycode = KEY_KP_6;
+ } break;
+ case KEY_END: {
+ keycode = KEY_KP_1;
+ } break;
+ case KEY_DOWN: {
+ keycode = KEY_KP_2;
+ } break;
+ case KEY_PAGEDOWN: {
+ keycode = KEY_KP_3;
+ } break;
+ case KEY_INSERT: {
+ keycode = KEY_KP_0;
+ } break;
+ case KEY_DELETE: {
+ keycode = KEY_KP_PERIOD;
+ } break;
+ case KEY_PRINT: {
+ keycode = KEY_KP_MULTIPLY;
+ } break;
+ }
+ }
+
+ return keycode;
+}
+
+bool KeyMappingWindows::is_extended_key(unsigned int p_code) {
+ return p_code == VK_INSERT ||
+ p_code == VK_DELETE ||
+ p_code == VK_HOME ||
+ p_code == VK_END ||
+ p_code == VK_PRIOR ||
+ p_code == VK_NEXT ||
+ p_code == VK_LEFT ||
+ p_code == VK_UP ||
+ p_code == VK_RIGHT ||
+ p_code == VK_DOWN;
+}
diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h
index 0f9bdecde1..f64f1feb9f 100644
--- a/platform/windows/key_mapping_windows.h
+++ b/platform/windows/key_mapping_windows.h
@@ -38,11 +38,12 @@
#include <winuser.h>
class KeyMappingWindows {
-
- KeyMappingWindows(){};
+ KeyMappingWindows() {}
public:
static unsigned int get_keysym(unsigned int p_code);
+ static unsigned int get_scansym(unsigned int p_code, bool p_extended);
+ static bool is_extended_key(unsigned int p_code);
};
#endif // KEY_MAPPING_WINDOWS_H
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index a6977a7a86..f73516b370 100755..100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -33,22 +33,21 @@
#include "os_windows.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
#include "core/io/marshalls.h"
#include "core/version_generated.gen.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/windows/dir_access_windows.h"
#include "drivers/windows/file_access_windows.h"
-#include "drivers/windows/mutex_windows.h"
#include "drivers/windows/rw_lock_windows.h"
-#include "drivers/windows/semaphore_windows.h"
#include "drivers/windows/thread_windows.h"
#include "joypad_windows.h"
#include "lang_table.h"
#include "main/main.h"
+#include "platform/windows/display_server_windows.h"
#include "servers/audio_server.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
+#include "servers/rendering/rendering_server_raster.h"
+#include "servers/rendering/rendering_server_wrap_mt.h"
#include "windows_terminal_logger.h"
#include <avrt.h>
@@ -79,38 +78,13 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#define GetProcAddress (void *)GetProcAddress
#endif
-typedef struct {
- int count;
- int screen;
- Size2 size;
-} EnumSizeData;
-
-typedef struct {
- int count;
- int screen;
- Point2 pos;
-} EnumPosData;
-
-static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
-
- EnumSizeData *data = (EnumSizeData *)dwData;
- if (data->count == data->screen) {
- data->size.x = lprcMonitor->right - lprcMonitor->left;
- data->size.y = lprcMonitor->bottom - lprcMonitor->top;
- }
-
- data->count++;
- return TRUE;
-}
-
#ifdef DEBUG_ENABLED
static String format_error_message(DWORD id) {
-
- LPWSTR messageBuffer = NULL;
+ LPWSTR messageBuffer = nullptr;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL);
+ nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr);
- String msg = "Error " + itos(id) + ": " + String(messageBuffer, size);
+ String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size);
LocalFree(messageBuffer);
@@ -118,10 +92,7 @@ static String format_error_message(DWORD id) {
}
#endif // DEBUG_ENABLED
-extern HINSTANCE godot_hinstance;
-
void RedirectIOToConsole() {
-
int hConHandle;
intptr_t lStdHandle;
@@ -136,15 +107,11 @@ void RedirectIOToConsole() {
// set the screen buffer to be big enough to let us scroll text
- GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
-
- &coninfo);
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
- SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
-
- coninfo.dwSize);
+ SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// redirect unbuffered STDOUT to the console
@@ -156,7 +123,7 @@ void RedirectIOToConsole() {
*stdout = *fp;
- setvbuf(stdout, NULL, _IONBF, 0);
+ setvbuf(stdout, nullptr, _IONBF, 0);
// redirect unbuffered STDIN to the console
@@ -168,7 +135,7 @@ void RedirectIOToConsole() {
*stdin = *fp;
- setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdin, nullptr, _IONBF, 0);
// redirect unbuffered STDERR to the console
@@ -180,7 +147,7 @@ void RedirectIOToConsole() {
*stderr = *fp;
- setvbuf(stderr, NULL, _IONBF, 0);
+ setvbuf(stderr, nullptr, _IONBF, 0);
// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
@@ -188,41 +155,29 @@ void RedirectIOToConsole() {
}
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
- if (ScriptDebugger::get_singleton() == NULL)
+ if (!EngineDebugger::is_active())
return FALSE;
switch (dwCtrlType) {
case CTRL_C_EVENT:
- ScriptDebugger::get_singleton()->set_depth(-1);
- ScriptDebugger::get_singleton()->set_lines_left(1);
+ EngineDebugger::get_script_debugger()->set_depth(-1);
+ EngineDebugger::get_script_debugger()->set_lines_left(1);
return TRUE;
default:
return FALSE;
}
}
-GetPointerTypePtr OS_Windows::win8p_GetPointerType = NULL;
-GetPointerPenInfoPtr OS_Windows::win8p_GetPointerPenInfo = NULL;
-
void OS_Windows::initialize_debugging() {
-
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
}
-void OS_Windows::initialize_core() {
-
+void OS_Windows::initialize() {
crash_handler.initialize();
- last_button_state = 0;
-
//RedirectIOToConsole();
- maximized = false;
- minimized = false;
- borderless = false;
ThreadWindows::make_default();
- SemaphoreWindows::make_default();
- MutexWindows::make_default();
RWLockWindows::make_default();
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
@@ -249,1401 +204,28 @@ void OS_Windows::initialize_core() {
process_map = memnew((Map<ProcessID, ProcessInfo>));
- IP_Unix::make_default();
-
- cursor_shape = CURSOR_ARROW;
-}
-
-bool OS_Windows::can_draw() const {
-
- return !minimized;
-};
-
-#define MI_WP_SIGNATURE 0xFF515700
-#define SIGNATURE_MASK 0xFFFFFF00
-// Keeping the name suggested by Microsoft, but this macro really answers:
-// Is this mouse event emulated from touch or pen input?
-#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE)
-// This one tells whether the event comes from touchscreen (and not from pen)
-#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80))
-
-void OS_Windows::_touch_event(bool p_pressed, float p_x, float p_y, int idx) {
-
- // Defensive
- if (touch_state.has(idx) == p_pressed)
- return;
-
- if (p_pressed) {
- touch_state.insert(idx, Vector2(p_x, p_y));
- } else {
- touch_state.erase(idx);
- }
-
- Ref<InputEventScreenTouch> event;
- event.instance();
- event->set_index(idx);
- event->set_pressed(p_pressed);
- event->set_position(Vector2(p_x, p_y));
-
- if (main_loop) {
- input->accumulate_input_event(event);
- }
-};
-
-void OS_Windows::_drag_event(float p_x, float p_y, int idx) {
-
- Map<int, Vector2>::Element *curr = touch_state.find(idx);
- // Defensive
- if (!curr)
- return;
-
- if (curr->get() == Vector2(p_x, p_y))
- return;
-
- Ref<InputEventScreenDrag> event;
- event.instance();
- event->set_index(idx);
- event->set_position(Vector2(p_x, p_y));
- event->set_relative(Vector2(p_x, p_y) - curr->get());
-
- if (main_loop)
- input->accumulate_input_event(event);
-
- curr->get() = Vector2(p_x, p_y);
-};
-
-LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
-
- if (drop_events) {
-
- if (user_proc) {
-
- return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
- } else {
- return DefWindowProcW(hWnd, uMsg, wParam, lParam);
- }
- };
-
- switch (uMsg) // Check For Windows Messages
- {
- case WM_SETFOCUS: {
- window_has_focus = true;
-
- // Restore mouse mode
- _set_mouse_mode_impl(mouse_mode);
-
- break;
- }
- case WM_KILLFOCUS: {
- window_has_focus = false;
-
- // Release capture unconditionally because it can be set due to dragging, in addition to captured mode
- ReleaseCapture();
-
- // Release every touch to avoid sticky points
- for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) {
- _touch_event(false, E->get().x, E->get().y, E->key());
- }
- touch_state.clear();
-
- break;
- }
- case WM_ACTIVATE: // Watch For Window Activate Message
- {
- minimized = HIWORD(wParam) != 0;
- if (!main_loop) {
- return 0;
- };
- if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
-
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
- window_focused = true;
- alt_mem = false;
- control_mem = false;
- shift_mem = false;
- } else { // WM_INACTIVE
- input->release_pressed_events();
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
- window_focused = false;
- alt_mem = false;
- };
-
- return 0; // Return To The Message Loop
- }
- case WM_GETMINMAXINFO: {
- if (video_mode.resizable && !video_mode.fullscreen) {
- Size2 decor = get_real_window_size() - get_window_size(); // Size of window decorations
- MINMAXINFO *min_max_info = (MINMAXINFO *)lParam;
- if (min_size != Size2()) {
- min_max_info->ptMinTrackSize.x = min_size.x + decor.x;
- min_max_info->ptMinTrackSize.y = min_size.y + decor.y;
- }
- if (max_size != Size2()) {
- min_max_info->ptMaxTrackSize.x = max_size.x + decor.x;
- min_max_info->ptMaxTrackSize.y = max_size.y + decor.y;
- }
- return 0;
- } else {
- break;
- }
- }
- case WM_PAINT:
-
- Main::force_redraw();
- break;
-
- case WM_SYSCOMMAND: // Intercept System Commands
- {
- switch (wParam) // Check System Calls
- {
- case SC_SCREENSAVE: // Screensaver Trying To Start?
- case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
- return 0; // Prevent From Happening
- case SC_KEYMENU:
- if ((lParam >> 16) <= 0)
- return 0;
- }
- break; // Exit
- }
-
- case WM_CLOSE: // Did We Receive A Close Message?
- {
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
- //force_quit=true;
- return 0; // Jump Back
- }
- case WM_MOUSELEAVE: {
-
- old_invalid = true;
- outside = true;
- if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
- main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
-
- } break;
- case WM_INPUT: {
- if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
- break;
- }
-
- UINT dwSize;
-
- GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
- LPBYTE lpb = new BYTE[dwSize];
- if (lpb == NULL) {
- return 0;
- }
-
- if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
- OutputDebugString(TEXT("GetRawInputData does not return correct size !\n"));
-
- RAWINPUT *raw = (RAWINPUT *)lpb;
-
- if (raw->header.dwType == RIM_TYPEMOUSE) {
- Ref<InputEventMouseMotion> mm;
- mm.instance();
-
- mm->set_control(control_mem);
- mm->set_shift(shift_mem);
- mm->set_alt(alt_mem);
-
- mm->set_button_mask(last_button_state);
-
- Point2i c(video_mode.width / 2, video_mode.height / 2);
-
- // centering just so it works as before
- POINT pos = { (int)c.x, (int)c.y };
- ClientToScreen(hWnd, &pos);
- SetCursorPos(pos.x, pos.y);
-
- mm->set_position(c);
- mm->set_global_position(c);
- input->set_mouse_position(c);
- mm->set_speed(Vector2(0, 0));
-
- if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
- mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY));
-
- } else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
-
- int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
- int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
- int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
- int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
-
- Vector2 abs_pos(
- (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft,
- (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop);
-
- POINT coords; //client coords
- coords.x = abs_pos.x;
- coords.y = abs_pos.y;
-
- ScreenToClient(hWnd, &coords);
-
- mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y));
- old_x = coords.x;
- old_y = coords.y;
-
- /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth));
- Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight));
- */
- }
-
- if (window_has_focus && main_loop && mm->get_relative() != Vector2())
- input->accumulate_input_event(mm);
- }
- delete[] lpb;
- } break;
- case WM_POINTERUPDATE: {
- if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
- break;
- }
-
- if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) {
- break;
- }
-
- uint32_t pointer_id = LOWORD(wParam);
- POINTER_INPUT_TYPE pointer_type = PT_POINTER;
- if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
- break;
- }
-
- if (pointer_type != PT_PEN) {
- break;
- }
-
- POINTER_PEN_INFO pen_info;
- if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) {
- break;
- }
-
- if (input->is_emulating_mouse_from_touch()) {
- // Universal translation enabled; ignore OS translation
- LPARAM extra = GetMessageExtraInfo();
- if (IsTouchEvent(extra)) {
- break;
- }
- }
-
- if (outside) {
- //mouse enter
-
- if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
- main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
-
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
- outside = false;
-
- //Once-Off notification, must call again....
- TRACKMOUSEEVENT tme;
- tme.cbSize = sizeof(TRACKMOUSEEVENT);
- tme.dwFlags = TME_LEAVE;
- tme.hwndTrack = hWnd;
- tme.dwHoverTime = HOVER_DEFAULT;
- TrackMouseEvent(&tme);
- }
-
- // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
- break;
-
- Ref<InputEventMouseMotion> mm;
- mm.instance();
-
- mm->set_pressure(pen_info.pressure ? (float)pen_info.pressure / 1024 : 0);
- mm->set_tilt(Vector2(pen_info.tiltX ? (float)pen_info.tiltX / 90 : 0, pen_info.tiltY ? (float)pen_info.tiltY / 90 : 0));
-
- mm->set_control((wParam & MK_CONTROL) != 0);
- mm->set_shift((wParam & MK_SHIFT) != 0);
- mm->set_alt(alt_mem);
-
- mm->set_button_mask(last_button_state);
-
- POINT coords; //client coords
- coords.x = GET_X_LPARAM(lParam);
- coords.y = GET_Y_LPARAM(lParam);
-
- ScreenToClient(hWnd, &coords);
-
- mm->set_position(Vector2(coords.x, coords.y));
- mm->set_global_position(Vector2(coords.x, coords.y));
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
-
- Point2i c(video_mode.width / 2, video_mode.height / 2);
- old_x = c.x;
- old_y = c.y;
-
- if (mm->get_position() == c) {
- center = c;
- return 0;
- }
-
- Point2i ncenter = mm->get_position();
- center = ncenter;
- POINT pos = { (int)c.x, (int)c.y };
- ClientToScreen(hWnd, &pos);
- SetCursorPos(pos.x, pos.y);
- }
-
- input->set_mouse_position(mm->get_position());
- mm->set_speed(input->get_last_mouse_speed());
-
- if (old_invalid) {
-
- old_x = mm->get_position().x;
- old_y = mm->get_position().y;
- old_invalid = false;
- }
-
- mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
- old_x = mm->get_position().x;
- old_y = mm->get_position().y;
- if (window_has_focus && main_loop)
- input->parse_input_event(mm);
-
- return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event
- } break;
- case WM_MOUSEMOVE: {
- if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
- break;
- }
-
- if (input->is_emulating_mouse_from_touch()) {
- // Universal translation enabled; ignore OS translation
- LPARAM extra = GetMessageExtraInfo();
- if (IsTouchEvent(extra)) {
- break;
- }
- }
-
- if (outside) {
- //mouse enter
-
- if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
- main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
-
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
- outside = false;
-
- //Once-Off notification, must call again....
- TRACKMOUSEEVENT tme;
- tme.cbSize = sizeof(TRACKMOUSEEVENT);
- tme.dwFlags = TME_LEAVE;
- tme.hwndTrack = hWnd;
- tme.dwHoverTime = HOVER_DEFAULT;
- TrackMouseEvent(&tme);
- }
-
- // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
- break;
-
- Ref<InputEventMouseMotion> mm;
- mm.instance();
-
- mm->set_control((wParam & MK_CONTROL) != 0);
- mm->set_shift((wParam & MK_SHIFT) != 0);
- mm->set_alt(alt_mem);
-
- mm->set_button_mask(last_button_state);
-
- mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
- mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
-
- Point2i c(video_mode.width / 2, video_mode.height / 2);
- old_x = c.x;
- old_y = c.y;
-
- if (mm->get_position() == c) {
- center = c;
- return 0;
- }
-
- Point2i ncenter = mm->get_position();
- center = ncenter;
- POINT pos = { (int)c.x, (int)c.y };
- ClientToScreen(hWnd, &pos);
- SetCursorPos(pos.x, pos.y);
- }
-
- input->set_mouse_position(mm->get_position());
- mm->set_speed(input->get_last_mouse_speed());
-
- if (old_invalid) {
-
- old_x = mm->get_position().x;
- old_y = mm->get_position().y;
- old_invalid = false;
- }
-
- mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
- old_x = mm->get_position().x;
- old_y = mm->get_position().y;
- if (window_has_focus && main_loop)
- input->accumulate_input_event(mm);
-
- } break;
- case WM_LBUTTONDOWN:
- case WM_LBUTTONUP:
- if (input->is_emulating_mouse_from_touch()) {
- // Universal translation enabled; ignore OS translations for left button
- LPARAM extra = GetMessageExtraInfo();
- if (IsTouchEvent(extra)) {
- break;
- }
- }
- FALLTHROUGH;
- case WM_MBUTTONDOWN:
- case WM_MBUTTONUP:
- case WM_RBUTTONDOWN:
- case WM_RBUTTONUP:
- case WM_MOUSEWHEEL:
- case WM_MOUSEHWHEEL:
- case WM_LBUTTONDBLCLK:
- case WM_MBUTTONDBLCLK:
- case WM_RBUTTONDBLCLK:
- case WM_XBUTTONDBLCLK:
- case WM_XBUTTONDOWN:
- case WM_XBUTTONUP: {
-
- Ref<InputEventMouseButton> mb;
- mb.instance();
-
- switch (uMsg) {
- case WM_LBUTTONDOWN: {
- mb->set_pressed(true);
- mb->set_button_index(1);
- } break;
- case WM_LBUTTONUP: {
- mb->set_pressed(false);
- mb->set_button_index(1);
- } break;
- case WM_MBUTTONDOWN: {
- mb->set_pressed(true);
- mb->set_button_index(3);
- } break;
- case WM_MBUTTONUP: {
- mb->set_pressed(false);
- mb->set_button_index(3);
- } break;
- case WM_RBUTTONDOWN: {
- mb->set_pressed(true);
- mb->set_button_index(2);
- } break;
- case WM_RBUTTONUP: {
- mb->set_pressed(false);
- mb->set_button_index(2);
- } break;
- case WM_LBUTTONDBLCLK: {
- mb->set_pressed(true);
- mb->set_button_index(1);
- mb->set_doubleclick(true);
- } break;
- case WM_RBUTTONDBLCLK: {
- mb->set_pressed(true);
- mb->set_button_index(2);
- mb->set_doubleclick(true);
- } break;
- case WM_MBUTTONDBLCLK: {
- mb->set_pressed(true);
- mb->set_button_index(3);
- mb->set_doubleclick(true);
- } break;
- case WM_MOUSEWHEEL: {
-
- mb->set_pressed(true);
- int motion = (short)HIWORD(wParam);
- if (!motion)
- return 0;
-
- if (motion > 0)
- mb->set_button_index(BUTTON_WHEEL_UP);
- else
- mb->set_button_index(BUTTON_WHEEL_DOWN);
-
- } break;
- case WM_MOUSEHWHEEL: {
-
- mb->set_pressed(true);
- int motion = (short)HIWORD(wParam);
- if (!motion)
- return 0;
-
- if (motion < 0) {
- mb->set_button_index(BUTTON_WHEEL_LEFT);
- mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
- } else {
- mb->set_button_index(BUTTON_WHEEL_RIGHT);
- mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
- }
- } break;
- case WM_XBUTTONDOWN: {
-
- mb->set_pressed(true);
- if (HIWORD(wParam) == XBUTTON1)
- mb->set_button_index(BUTTON_XBUTTON1);
- else
- mb->set_button_index(BUTTON_XBUTTON2);
- } break;
- case WM_XBUTTONUP: {
-
- mb->set_pressed(false);
- if (HIWORD(wParam) == XBUTTON1)
- mb->set_button_index(BUTTON_XBUTTON1);
- else
- mb->set_button_index(BUTTON_XBUTTON2);
- } break;
- case WM_XBUTTONDBLCLK: {
-
- mb->set_pressed(true);
- if (HIWORD(wParam) == XBUTTON1)
- mb->set_button_index(BUTTON_XBUTTON1);
- else
- mb->set_button_index(BUTTON_XBUTTON2);
- mb->set_doubleclick(true);
- } break;
- default: {
- return 0;
- }
- }
-
- mb->set_control((wParam & MK_CONTROL) != 0);
- mb->set_shift((wParam & MK_SHIFT) != 0);
- mb->set_alt(alt_mem);
- //mb->get_alt()=(wParam&MK_MENU)!=0;
- if (mb->is_pressed())
- last_button_state |= (1 << (mb->get_button_index() - 1));
- else
- last_button_state &= ~(1 << (mb->get_button_index() - 1));
- mb->set_button_mask(last_button_state);
-
- mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
-
- if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) {
-
- mb->set_position(Vector2(old_x, old_y));
- }
-
- if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) {
- if (mb->is_pressed()) {
-
- if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED)
- SetCapture(hWnd);
- } else {
-
- if (--pressrc <= 0) {
- if (mouse_mode != MOUSE_MODE_CAPTURED) {
- ReleaseCapture();
- }
- pressrc = 0;
- }
- }
- } else {
- // for reasons unknown to mankind, wheel comes in screen coordinates
- POINT coords;
- coords.x = mb->get_position().x;
- coords.y = mb->get_position().y;
-
- ScreenToClient(hWnd, &coords);
-
- mb->set_position(Vector2(coords.x, coords.y));
- }
-
- mb->set_global_position(mb->get_position());
-
- if (main_loop) {
- input->accumulate_input_event(mb);
- if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) {
- //send release for mouse wheel
- Ref<InputEventMouseButton> mbd = mb->duplicate();
- last_button_state &= ~(1 << (mbd->get_button_index() - 1));
- mbd->set_button_mask(last_button_state);
- mbd->set_pressed(false);
- input->accumulate_input_event(mbd);
- }
- }
- } break;
+ // Add current Godot PID to the list of known PIDs
+ ProcessInfo current_pi = {};
+ PROCESS_INFORMATION current_pi_pi = {};
+ current_pi.pi = current_pi_pi;
+ current_pi.pi.hProcess = GetCurrentProcess();
+ process_map->insert(GetCurrentProcessId(), current_pi);
- case WM_MOVE: {
- if (!IsIconic(hWnd)) {
- int x = LOWORD(lParam);
- int y = HIWORD(lParam);
- last_pos = Point2(x, y);
- }
- } break;
-
- case WM_SIZE: {
- // Ignore size when a SIZE_MINIMIZED event is triggered
- if (wParam != SIZE_MINIMIZED) {
- int window_w = LOWORD(lParam);
- int window_h = HIWORD(lParam);
- if (window_w > 0 && window_h > 0 && !preserve_window_size) {
- video_mode.width = window_w;
- video_mode.height = window_h;
- } else {
- preserve_window_size = false;
- set_window_size(Size2(video_mode.width, video_mode.height));
- }
- }
-
- if (wParam == SIZE_MAXIMIZED) {
- maximized = true;
- minimized = false;
- } else if (wParam == SIZE_MINIMIZED) {
- maximized = false;
- minimized = true;
- } else if (wParam == SIZE_RESTORED) {
- maximized = false;
- minimized = false;
- }
- if (is_layered_allowed() && layered_window) {
- DeleteObject(hBitmap);
-
- RECT r;
- GetWindowRect(hWnd, &r);
- dib_size = Size2i(r.right - r.left, r.bottom - r.top);
-
- BITMAPINFO bmi;
- ZeroMemory(&bmi, sizeof(BITMAPINFO));
- bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmi.bmiHeader.biWidth = dib_size.x;
- bmi.bmiHeader.biHeight = dib_size.y;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
- bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
- hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, NULL, 0x0);
- SelectObject(hDC_dib, hBitmap);
-
- ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
- }
- //return 0; // Jump Back
- } break;
-
- case WM_ENTERSIZEMOVE: {
- input->release_pressed_events();
- move_timer_id = SetTimer(hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC)NULL);
- } break;
- case WM_EXITSIZEMOVE: {
- KillTimer(hWnd, move_timer_id);
- } break;
- case WM_TIMER: {
- if (wParam == move_timer_id) {
- process_key_events();
- if (!Main::is_iterating()) {
- Main::iteration();
- }
- }
- } break;
-
- case WM_SYSKEYDOWN:
- case WM_SYSKEYUP:
- case WM_KEYUP:
- case WM_KEYDOWN: {
-
- if (wParam == VK_SHIFT)
- shift_mem = uMsg == WM_KEYDOWN;
- if (wParam == VK_CONTROL)
- control_mem = uMsg == WM_KEYDOWN;
- if (wParam == VK_MENU) {
- alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
- if (lParam & (1 << 24))
- gr_mem = alt_mem;
- }
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- // When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves
- if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) {
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
- }
- }
- /*
- if (wParam==VK_WIN) TODO wtf is this?
- meta_mem=uMsg==WM_KEYDOWN;
- */
- FALLTHROUGH;
- }
- case WM_CHAR: {
-
- ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
-
- // Make sure we don't include modifiers for the modifier key itself.
- KeyEvent ke;
- ke.shift = (wParam != VK_SHIFT) ? shift_mem : false;
- ke.alt = (!(wParam == VK_MENU && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN))) ? alt_mem : false;
- ke.control = (wParam != VK_CONTROL) ? control_mem : false;
- ke.meta = meta_mem;
- ke.uMsg = uMsg;
-
- if (ke.uMsg == WM_SYSKEYDOWN)
- ke.uMsg = WM_KEYDOWN;
- if (ke.uMsg == WM_SYSKEYUP)
- ke.uMsg = WM_KEYUP;
-
- ke.wParam = wParam;
- ke.lParam = lParam;
- key_event_buffer[key_event_pos++] = ke;
-
- } break;
- case WM_INPUTLANGCHANGEREQUEST: {
-
- // FIXME: Do something?
- } break;
-
- case WM_TOUCH: {
-
- BOOL bHandled = FALSE;
- UINT cInputs = LOWORD(wParam);
- PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs);
- if (pInputs) {
- if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) {
- for (UINT i = 0; i < cInputs; i++) {
- TOUCHINPUT ti = pInputs[i];
- POINT touch_pos = {
- TOUCH_COORD_TO_PIXEL(ti.x),
- TOUCH_COORD_TO_PIXEL(ti.y),
- };
- ScreenToClient(hWnd, &touch_pos);
- //do something with each touch input entry
- if (ti.dwFlags & TOUCHEVENTF_MOVE) {
-
- _drag_event(touch_pos.x, touch_pos.y, ti.dwID);
- } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
-
- _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID);
- };
- }
- bHandled = TRUE;
- } else {
- /* handle the error here */
- }
- memdelete_arr(pInputs);
- } else {
- /* handle the error here, probably out of memory */
- }
- if (bHandled) {
- CloseTouchInputHandle((HTOUCHINPUT)lParam);
- return 0;
- };
-
- } break;
-
- case WM_DEVICECHANGE: {
-
- joypad->probe_joypads();
- } break;
- case WM_SETCURSOR: {
- if (LOWORD(lParam) == HTCLIENT) {
- if (window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) {
- //Hide the cursor
- if (hCursor == NULL)
- hCursor = SetCursor(NULL);
- else
- SetCursor(NULL);
- } else {
- if (hCursor != NULL) {
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
- hCursor = NULL;
- }
- }
- }
-
- } break;
- case WM_DROPFILES: {
-
- HDROP hDropInfo = (HDROP)wParam;
- const int buffsize = 4096;
- wchar_t buf[buffsize];
-
- int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0);
-
- Vector<String> files;
-
- for (int i = 0; i < fcount; i++) {
-
- DragQueryFileW(hDropInfo, i, buf, buffsize);
- String file = buf;
- files.push_back(file);
- }
-
- if (files.size() && main_loop) {
- main_loop->drop_files(files, 0);
- }
-
- } break;
-
- default: {
-
- if (user_proc) {
-
- return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
- };
- };
- }
-
- return DefWindowProcW(hWnd, uMsg, wParam, lParam);
-}
-
-LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
-
- OS_Windows *os_win = static_cast<OS_Windows *>(OS::get_singleton());
- if (os_win)
- return os_win->WndProc(hWnd, uMsg, wParam, lParam);
- else
- return DefWindowProcW(hWnd, uMsg, wParam, lParam);
-}
-
-void OS_Windows::process_key_events() {
-
- for (int i = 0; i < key_event_pos; i++) {
-
- KeyEvent &ke = key_event_buffer[i];
- switch (ke.uMsg) {
-
- case WM_CHAR: {
- if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) {
- Ref<InputEventKey> k;
- k.instance();
-
- k->set_shift(ke.shift);
- k->set_alt(ke.alt);
- k->set_control(ke.control);
- k->set_metakey(ke.meta);
- k->set_pressed(true);
- k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
- k->set_unicode(ke.wParam);
- if (k->get_unicode() && gr_mem) {
- k->set_alt(false);
- k->set_control(false);
- }
-
- if (k->get_unicode() < 32)
- k->set_unicode(0);
-
- input->accumulate_input_event(k);
- }
-
- //do nothing
- } break;
- case WM_KEYUP:
- case WM_KEYDOWN: {
-
- Ref<InputEventKey> k;
- k.instance();
-
- k->set_shift(ke.shift);
- k->set_alt(ke.alt);
- k->set_control(ke.control);
- k->set_metakey(ke.meta);
-
- k->set_pressed(ke.uMsg == WM_KEYDOWN);
-
- if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) {
- // Special case for Numpad Enter key
- k->set_scancode(KEY_KP_ENTER);
- } else {
- k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
- }
-
- if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
- k->set_unicode(key_event_buffer[i + 1].wParam);
- }
- if (k->get_unicode() && gr_mem) {
- k->set_alt(false);
- k->set_control(false);
- }
-
- if (k->get_unicode() < 32)
- k->set_unicode(0);
-
- k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30))));
-
- input->accumulate_input_event(k);
-
- } break;
- }
- }
-
- key_event_pos = 0;
-}
-
-enum _MonitorDpiType {
- MDT_Effective_DPI = 0,
- MDT_Angular_DPI = 1,
- MDT_Raw_DPI = 2,
- MDT_Default = MDT_Effective_DPI
-};
-
-static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) {
-
- int dpiX = 96, dpiY = 96;
-
- static HMODULE Shcore = NULL;
- typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY);
- static GetDPIForMonitor_t getDPIForMonitor = NULL;
-
- if (Shcore == NULL) {
- Shcore = LoadLibraryW(L"Shcore.dll");
- getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : NULL;
-
- if ((Shcore == NULL) || (getDPIForMonitor == NULL)) {
- if (Shcore)
- FreeLibrary(Shcore);
- Shcore = (HMODULE)INVALID_HANDLE_VALUE;
- }
- }
-
- UINT x = 0, y = 0;
- HRESULT hr = E_FAIL;
- if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) {
- hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y);
- if (SUCCEEDED(hr) && (x > 0) && (y > 0)) {
-
- dpiX = (int)x;
- dpiY = (int)y;
- }
- } else {
- static int overallX = 0, overallY = 0;
- if (overallX <= 0 || overallY <= 0) {
- HDC hdc = GetDC(NULL);
- if (hdc) {
- overallX = GetDeviceCaps(hdc, LOGPIXELSX);
- overallY = GetDeviceCaps(hdc, LOGPIXELSY);
- ReleaseDC(NULL, hdc);
- }
- }
- if (overallX > 0 && overallY > 0) {
- dpiX = overallX;
- dpiY = overallY;
- }
- }
-
- return (dpiX + dpiY) / 2;
-}
-
-typedef enum _SHC_PROCESS_DPI_AWARENESS {
- SHC_PROCESS_DPI_UNAWARE = 0,
- SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
- SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2
-} SHC_PROCESS_DPI_AWARENESS;
-
-int OS_Windows::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- main_loop = NULL;
- outside = true;
- window_has_focus = true;
- WNDCLASSEXW wc;
-
- if (is_hidpi_allowed()) {
- HMODULE Shcore = LoadLibraryW(L"Shcore.dll");
-
- if (Shcore != NULL) {
- typedef HRESULT(WINAPI * SetProcessDpiAwareness_t)(SHC_PROCESS_DPI_AWARENESS);
-
- SetProcessDpiAwareness_t SetProcessDpiAwareness = (SetProcessDpiAwareness_t)GetProcAddress(Shcore, "SetProcessDpiAwareness");
-
- if (SetProcessDpiAwareness) {
- SetProcessDpiAwareness(SHC_PROCESS_SYSTEM_DPI_AWARE);
- }
- }
- }
-
- video_mode = p_desired;
- //printf("**************** desired %s, mode %s\n", p_desired.fullscreen?"true":"false", video_mode.fullscreen?"true":"false");
- RECT WindowRect;
-
- WindowRect.left = 0;
- WindowRect.right = video_mode.width;
- WindowRect.top = 0;
- WindowRect.bottom = video_mode.height;
-
- memset(&wc, 0, sizeof(WNDCLASSEXW));
- wc.cbSize = sizeof(WNDCLASSEXW);
- wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
- wc.lpfnWndProc = (WNDPROC)::WndProc;
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- //wc.hInstance = hInstance;
- wc.hInstance = godot_hinstance ? godot_hinstance : GetModuleHandle(NULL);
- wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
- wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW);
- wc.hbrBackground = NULL;
- wc.lpszMenuName = NULL;
- wc.lpszClassName = L"Engine";
-
- if (!RegisterClassExW(&wc)) {
- MessageBox(NULL, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
- return ERR_UNAVAILABLE;
- }
-
- use_raw_input = true;
-
- RAWINPUTDEVICE Rid[1];
-
- Rid[0].usUsagePage = 0x01;
- Rid[0].usUsage = 0x02;
- Rid[0].dwFlags = 0;
- Rid[0].hwndTarget = 0;
-
- if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) {
- //registration failed.
- use_raw_input = false;
- }
-
- pre_fs_valid = true;
- if (video_mode.fullscreen) {
-
- /* this returns DPI unaware size, commenting
- DEVMODE current;
- memset(&current, 0, sizeof(current));
- EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &current);
-
- WindowRect.right = current.dmPelsWidth;
- WindowRect.bottom = current.dmPelsHeight;
-
- */
-
- EnumSizeData data = { 0, 0, Size2() };
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data);
-
- WindowRect.right = data.size.width;
- WindowRect.bottom = data.size.height;
-
- /* DEVMODE dmScreenSettings;
- memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
- dmScreenSettings.dmSize=sizeof(dmScreenSettings);
- dmScreenSettings.dmPelsWidth = video_mode.width;
- dmScreenSettings.dmPelsHeight = video_mode.height;
- dmScreenSettings.dmBitsPerPel = current.dmBitsPerPel;
- dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
-
- LONG err = ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN);
- if (err!=DISP_CHANGE_SUCCESSFUL) {
-
- video_mode.fullscreen=false;
- }*/
- pre_fs_valid = false;
- }
-
- DWORD dwExStyle;
- DWORD dwStyle;
-
- if (video_mode.fullscreen || video_mode.borderless_window) {
-
- dwExStyle = WS_EX_APPWINDOW;
- dwStyle = WS_POPUP;
-
- } else {
- dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
- dwStyle = WS_OVERLAPPEDWINDOW;
- if (!video_mode.resizable) {
- dwStyle &= ~WS_THICKFRAME;
- dwStyle &= ~WS_MAXIMIZEBOX;
- }
- }
-
- AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
-
- char *windowid;
-#ifdef MINGW_ENABLED
- windowid = getenv("GODOT_WINDOWID");
-#else
- size_t len;
- _dupenv_s(&windowid, &len, "GODOT_WINDOWID");
-#endif
-
- if (windowid) {
-
-// strtoull on mingw
-#ifdef MINGW_ENABLED
- hWnd = (HWND)strtoull(windowid, NULL, 0);
-#else
- hWnd = (HWND)_strtoui64(windowid, NULL, 0);
-#endif
- free(windowid);
- SetLastError(0);
- user_proc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
- SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)::WndProc);
- DWORD le = GetLastError();
- if (user_proc == 0 && le != 0) {
-
- printf("Error setting WNDPROC: %li\n", le);
- };
- GetWindowLongPtr(hWnd, GWLP_WNDPROC);
-
- RECT rect;
- if (!GetClientRect(hWnd, &rect)) {
- MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
- return ERR_UNAVAILABLE;
- };
- video_mode.width = rect.right;
- video_mode.height = rect.bottom;
- video_mode.fullscreen = false;
- } else {
-
- hWnd = CreateWindowExW(
- dwExStyle,
- L"Engine", L"",
- dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
- (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2,
- (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2,
- WindowRect.right - WindowRect.left,
- WindowRect.bottom - WindowRect.top,
- NULL, NULL, hInstance, NULL);
- if (!hWnd) {
- MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
- return ERR_UNAVAILABLE;
- }
- };
-
- if (video_mode.always_on_top) {
- SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
- }
-
-#if defined(OPENGL_ENABLED)
-
- bool gles3_context = true;
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gles3_context = false;
- }
-
- bool editor = Engine::get_singleton()->is_editor_hint();
- bool gl_initialization_error = false;
-
- gl_context = NULL;
- while (!gl_context) {
- gl_context = memnew(ContextGL_Windows(hWnd, gles3_context));
-
- if (gl_context->initialize() != OK) {
- memdelete(gl_context);
- gl_context = NULL;
-
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gl_initialization_error = true;
- break;
- }
-
- p_video_driver = VIDEO_DRIVER_GLES2;
- gles3_context = false;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- while (true) {
- if (gles3_context) {
- if (RasterizerGLES3::is_viable() == OK) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- gles3_context = false;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- } else {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
- "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
-
- gl_context->set_use_vsync(video_mode.use_vsync);
- set_vsync_via_compositor(video_mode.vsync_via_compositor);
-#endif
-
- visual_server = memnew(VisualServerRaster);
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
- }
-
- visual_server->init();
-
- input = memnew(InputDefault);
- joypad = memnew(JoypadWindows(input, &hWnd));
-
- power_manager = memnew(PowerWindows);
-
- AudioDriverManager::initialize(p_audio_driver);
-
- TRACKMOUSEEVENT tme;
- tme.cbSize = sizeof(TRACKMOUSEEVENT);
- tme.dwFlags = TME_LEAVE;
- tme.hwndTrack = hWnd;
- tme.dwHoverTime = HOVER_DEFAULT;
- TrackMouseEvent(&tme);
-
- RegisterTouchWindow(hWnd, 0);
-
- _ensure_user_data_dir();
-
- DragAcceptFiles(hWnd, true);
-
- move_timer_id = 1;
-
- if (!is_no_window_mode_enabled()) {
- ShowWindow(hWnd, SW_SHOW); // Show The Window
- SetForegroundWindow(hWnd); // Slightly Higher Priority
- SetFocus(hWnd); // Sets Keyboard Focus To
- }
-
- if (p_desired.layered) {
- set_window_per_pixel_transparency_enabled(true);
- }
-
- // IME
- im_himc = ImmGetContext(hWnd);
- ImmReleaseContext(hWnd, im_himc);
-
- im_position = Vector2();
-
- set_ime_active(false);
-
- if (!OS::get_singleton()->is_in_low_processor_usage_mode()) {
- //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
- SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
- DWORD index = 0;
- HANDLE handle = AvSetMmThreadCharacteristics("Games", &index);
- if (handle)
- AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL);
-
- // This is needed to make sure that background work does not starve the main thread.
- // This is only setting priority of this thread, not the whole process.
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
- }
-
- update_real_mouse_position();
-
- return OK;
+ IP_Unix::make_default();
+ main_loop = nullptr;
}
-void OS_Windows::set_clipboard(const String &p_text) {
-
- // Convert LF line endings to CRLF in clipboard content
- // Otherwise, line endings won't be visible when pasted in other software
- String text = p_text.replace("\n", "\r\n");
-
- if (!OpenClipboard(hWnd)) {
- ERR_FAIL_MSG("Unable to open clipboard.");
- }
- EmptyClipboard();
-
- HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType));
- ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents.");
-
- LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem);
- memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType));
- GlobalUnlock(mem);
-
- SetClipboardData(CF_UNICODETEXT, mem);
-
- // set the CF_TEXT version (not needed?)
- CharString utf8 = text.utf8();
- mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1);
- ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents.");
-
- LPTSTR ptr = (LPTSTR)GlobalLock(mem);
- memcpy(ptr, utf8.get_data(), utf8.length());
- ptr[utf8.length()] = 0;
- GlobalUnlock(mem);
-
- SetClipboardData(CF_TEXT, mem);
-
- CloseClipboard();
-};
-
-String OS_Windows::get_clipboard() const {
-
- String ret;
- if (!OpenClipboard(hWnd)) {
- ERR_FAIL_V_MSG("", "Unable to open clipboard.");
- };
-
- if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
-
- HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
- if (mem != NULL) {
-
- LPWSTR ptr = (LPWSTR)GlobalLock(mem);
- if (ptr != NULL) {
-
- ret = String((CharType *)ptr);
- GlobalUnlock(mem);
- };
- };
-
- } else if (IsClipboardFormatAvailable(CF_TEXT)) {
-
- HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
- if (mem != NULL) {
-
- LPTSTR ptr = (LPTSTR)GlobalLock(mem);
- if (ptr != NULL) {
-
- ret.parse_utf8((const char *)ptr);
- GlobalUnlock(mem);
- };
- };
- };
-
- CloseClipboard();
-
- return ret;
-};
-
void OS_Windows::delete_main_loop() {
-
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
+ main_loop = nullptr;
}
void OS_Windows::set_main_loop(MainLoop *p_main_loop) {
-
- input->set_main_loop(p_main_loop);
main_loop = p_main_loop;
}
void OS_Windows::finalize() {
-
#ifdef WINMIDI_ENABLED
driver_midi.close();
#endif
@@ -1651,600 +233,17 @@ void OS_Windows::finalize() {
if (main_loop)
memdelete(main_loop);
- main_loop = NULL;
-
- memdelete(joypad);
- memdelete(input);
- touch_state.clear();
-
- cursors_cache.clear();
- visual_server->finish();
- memdelete(visual_server);
-#ifdef OPENGL_ENABLED
- if (gl_context)
- memdelete(gl_context);
-#endif
-
- if (user_proc) {
- SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
- };
+ main_loop = nullptr;
}
void OS_Windows::finalize_core() {
-
timeEndPeriod(1);
memdelete(process_map);
NetSocketPosix::cleanup();
}
-void OS_Windows::alert(const String &p_alert, const String &p_title) {
-
- if (!is_no_window_mode_enabled())
- MessageBoxW(NULL, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
- else
- print_line("ALERT: " + p_alert);
-}
-
-void OS_Windows::set_mouse_mode(MouseMode p_mode) {
-
- if (mouse_mode == p_mode)
- return;
-
- _set_mouse_mode_impl(p_mode);
-
- mouse_mode = p_mode;
-}
-
-void OS_Windows::_set_mouse_mode_impl(MouseMode p_mode) {
-
- if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) {
- RECT clipRect;
- GetClientRect(hWnd, &clipRect);
- ClientToScreen(hWnd, (POINT *)&clipRect.left);
- ClientToScreen(hWnd, (POINT *)&clipRect.right);
- ClipCursor(&clipRect);
- if (p_mode == MOUSE_MODE_CAPTURED) {
- center = Point2i(video_mode.width / 2, video_mode.height / 2);
- POINT pos = { (int)center.x, (int)center.y };
- ClientToScreen(hWnd, &pos);
- SetCursorPos(pos.x, pos.y);
- SetCapture(hWnd);
- }
- } else {
- ReleaseCapture();
- ClipCursor(NULL);
- }
-
- if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) {
- hCursor = SetCursor(NULL);
- } else {
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
- }
-}
-OS_Windows::MouseMode OS_Windows::get_mouse_mode() const {
-
- return mouse_mode;
-}
-
-void OS_Windows::warp_mouse_position(const Point2 &p_to) {
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
-
- old_x = p_to.x;
- old_y = p_to.y;
- } else {
-
- POINT p;
- p.x = p_to.x;
- p.y = p_to.y;
- ClientToScreen(hWnd, &p);
-
- SetCursorPos(p.x, p.y);
- }
-}
-
-Point2 OS_Windows::get_mouse_position() const {
-
- return Point2(old_x, old_y);
-}
-
-void OS_Windows::update_real_mouse_position() {
-
- POINT mouse_pos;
- if (GetCursorPos(&mouse_pos) && ScreenToClient(hWnd, &mouse_pos)) {
- if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= video_mode.width && mouse_pos.y <= video_mode.height) {
- old_x = mouse_pos.x;
- old_y = mouse_pos.y;
- old_invalid = false;
- input->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y));
- }
- }
-}
-
-int OS_Windows::get_mouse_button_state() const {
-
- return last_button_state;
-}
-
-void OS_Windows::set_window_title(const String &p_title) {
-
- SetWindowTextW(hWnd, p_title.c_str());
-}
-
-void OS_Windows::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-}
-
-OS::VideoMode OS_Windows::get_video_mode(int p_screen) const {
-
- return video_mode;
-}
-void OS_Windows::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-}
-
-static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
-
- int *data = (int *)dwData;
- (*data)++;
- return TRUE;
-}
-
-int OS_Windows::get_screen_count() const {
-
- int data = 0;
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcCount, (LPARAM)&data);
- return data;
-}
-
-typedef struct {
- int count;
- int screen;
- HMONITOR monitor;
-} EnumScreenData;
-
-static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
-
- EnumScreenData *data = (EnumScreenData *)dwData;
- if (data->monitor == hMonitor) {
- data->screen = data->count;
- }
-
- data->count++;
- return TRUE;
-}
-
-int OS_Windows::get_current_screen() const {
-
- EnumScreenData data = { 0, 0, MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) };
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcScreen, (LPARAM)&data);
- return data.screen;
-}
-
-void OS_Windows::set_current_screen(int p_screen) {
-
- Vector2 ofs = get_window_position() - get_screen_position(get_current_screen());
- set_window_position(ofs + get_screen_position(p_screen));
-}
-
-static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
-
- EnumPosData *data = (EnumPosData *)dwData;
- if (data->count == data->screen) {
- data->pos.x = lprcMonitor->left;
- data->pos.y = lprcMonitor->top;
- }
-
- data->count++;
- return TRUE;
-}
-
-Point2 OS_Windows::get_screen_position(int p_screen) const {
-
- EnumPosData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Point2() };
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcPos, (LPARAM)&data);
- return data.pos;
-}
-
-Size2 OS_Windows::get_screen_size(int p_screen) const {
-
- EnumSizeData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Size2() };
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data);
- return data.size;
-}
-
-typedef struct {
- int count;
- int screen;
- int dpi;
-} EnumDpiData;
-
-static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
-
- EnumDpiData *data = (EnumDpiData *)dwData;
- if (data->count == data->screen) {
- data->dpi = QueryDpiForMonitor(hMonitor);
- }
-
- data->count++;
- return TRUE;
-}
-
-int OS_Windows::get_screen_dpi(int p_screen) const {
-
- EnumDpiData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, 72 };
- EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcDpi, (LPARAM)&data);
- return data.dpi;
-}
-
-Point2 OS_Windows::get_window_position() const {
-
- if (minimized) {
- return last_pos;
- }
-
- RECT r;
- GetWindowRect(hWnd, &r);
- return Point2(r.left, r.top);
-}
-
-void OS_Windows::set_window_position(const Point2 &p_position) {
-
- if (video_mode.fullscreen) return;
- RECT r;
- GetWindowRect(hWnd, &r);
- MoveWindow(hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE);
-
- // Don't let the mouse leave the window when moved
- if (mouse_mode == MOUSE_MODE_CONFINED) {
- RECT rect;
- GetClientRect(hWnd, &rect);
- ClientToScreen(hWnd, (POINT *)&rect.left);
- ClientToScreen(hWnd, (POINT *)&rect.right);
- ClipCursor(&rect);
- }
-
- last_pos = p_position;
- update_real_mouse_position();
-}
-
-Size2 OS_Windows::get_window_size() const {
-
- if (minimized) {
- return Size2(video_mode.width, video_mode.height);
- }
-
- RECT r;
- if (GetClientRect(hWnd, &r)) { // Only area inside of window border
- return Size2(r.right - r.left, r.bottom - r.top);
- }
- return Size2();
-}
-
-Size2 OS_Windows::get_max_window_size() const {
- return max_size;
-}
-
-Size2 OS_Windows::get_min_window_size() const {
- return min_size;
-}
-
-void OS_Windows::set_min_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
- ERR_PRINT("Minimum window size can't be larger than maximum window size!");
- return;
- }
- min_size = p_size;
-}
-
-void OS_Windows::set_max_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
- ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
- return;
- }
- max_size = p_size;
-}
-
-Size2 OS_Windows::get_real_window_size() const {
-
- RECT r;
- if (GetWindowRect(hWnd, &r)) { // Includes area of the window border
- return Size2(r.right - r.left, r.bottom - r.top);
- }
- return Size2();
-}
-
-void OS_Windows::set_window_size(const Size2 p_size) {
-
- int w = p_size.width;
- int h = p_size.height;
-
- video_mode.width = w;
- video_mode.height = h;
-
- if (video_mode.fullscreen) {
- return;
- }
-
- RECT rect;
- GetWindowRect(hWnd, &rect);
-
- if (!video_mode.borderless_window) {
- RECT crect;
- GetClientRect(hWnd, &crect);
-
- w += (rect.right - rect.left) - (crect.right - crect.left);
- h += (rect.bottom - rect.top) - (crect.bottom - crect.top);
- }
-
- MoveWindow(hWnd, rect.left, rect.top, w, h, TRUE);
-
- // Don't let the mouse leave the window when resizing to a smaller resolution
- if (mouse_mode == MOUSE_MODE_CONFINED) {
- RECT crect;
- GetClientRect(hWnd, &crect);
- ClientToScreen(hWnd, (POINT *)&crect.left);
- ClientToScreen(hWnd, (POINT *)&crect.right);
- ClipCursor(&crect);
- }
-}
-void OS_Windows::set_window_fullscreen(bool p_enabled) {
-
- if (video_mode.fullscreen == p_enabled)
- return;
-
- if (layered_window)
- set_window_per_pixel_transparency_enabled(false);
-
- if (p_enabled) {
-
- was_maximized = maximized;
-
- if (pre_fs_valid) {
- GetWindowRect(hWnd, &pre_fs_rect);
- }
-
- int cs = get_current_screen();
- Point2 pos = get_screen_position(cs);
- Size2 size = get_screen_size(cs);
-
- video_mode.fullscreen = true;
-
- _update_window_style(false);
-
- MoveWindow(hWnd, pos.x, pos.y, size.width, size.height, TRUE);
-
- } else {
-
- RECT rect;
-
- video_mode.fullscreen = false;
-
- if (pre_fs_valid) {
- rect = pre_fs_rect;
- } else {
- rect.left = 0;
- rect.right = video_mode.width;
- rect.top = 0;
- rect.bottom = video_mode.height;
- }
-
- _update_window_style(false, was_maximized);
-
- MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
-
- pre_fs_valid = true;
- }
-}
-bool OS_Windows::is_window_fullscreen() const {
-
- return video_mode.fullscreen;
-}
-void OS_Windows::set_window_resizable(bool p_enabled) {
-
- if (video_mode.resizable == p_enabled)
- return;
-
- video_mode.resizable = p_enabled;
-
- _update_window_style();
-}
-bool OS_Windows::is_window_resizable() const {
-
- return video_mode.resizable;
-}
-void OS_Windows::set_window_minimized(bool p_enabled) {
-
- if (p_enabled) {
- maximized = false;
- minimized = true;
- ShowWindow(hWnd, SW_MINIMIZE);
- } else {
- ShowWindow(hWnd, SW_RESTORE);
- maximized = false;
- minimized = false;
- }
-}
-bool OS_Windows::is_window_minimized() const {
-
- return minimized;
-}
-void OS_Windows::set_window_maximized(bool p_enabled) {
-
- if (p_enabled) {
- maximized = true;
- minimized = false;
- ShowWindow(hWnd, SW_MAXIMIZE);
- } else {
- ShowWindow(hWnd, SW_RESTORE);
- maximized = false;
- minimized = false;
- }
-}
-bool OS_Windows::is_window_maximized() const {
-
- return maximized;
-}
-
-void OS_Windows::set_window_always_on_top(bool p_enabled) {
- if (video_mode.always_on_top == p_enabled)
- return;
-
- video_mode.always_on_top = p_enabled;
-
- _update_window_style();
-}
-
-bool OS_Windows::is_window_always_on_top() const {
- return video_mode.always_on_top;
-}
-
-bool OS_Windows::is_window_focused() const {
-
- return window_focused;
-}
-
-void OS_Windows::set_console_visible(bool p_enabled) {
- if (console_visible == p_enabled)
- return;
- ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE);
- console_visible = p_enabled;
-}
-
-bool OS_Windows::is_console_visible() const {
- return console_visible;
-}
-
-bool OS_Windows::get_window_per_pixel_transparency_enabled() const {
-
- if (!is_layered_allowed()) return false;
- return layered_window;
-}
-
-void OS_Windows::set_window_per_pixel_transparency_enabled(bool p_enabled) {
-
- if (!is_layered_allowed()) return;
- if (layered_window != p_enabled) {
- if (p_enabled) {
- set_borderless_window(true);
- //enable per-pixel alpha
- hDC_dib = CreateCompatibleDC(GetDC(hWnd));
-
- SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
-
- RECT r;
- GetWindowRect(hWnd, &r);
- dib_size = Size2(r.right - r.left, r.bottom - r.top);
-
- BITMAPINFO bmi;
- ZeroMemory(&bmi, sizeof(BITMAPINFO));
- bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmi.bmiHeader.biWidth = dib_size.x;
- bmi.bmiHeader.biHeight = dib_size.y;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
- bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
- hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, NULL, 0x0);
- SelectObject(hDC_dib, hBitmap);
-
- ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
-
- layered_window = true;
- } else {
- //disable per-pixel alpha
- layered_window = false;
-
- SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
-
- //cleanup
- DeleteObject(hBitmap);
- DeleteDC(hDC_dib);
- }
- }
-}
-
-uint8_t *OS_Windows::get_layered_buffer_data() {
-
- return (is_layered_allowed() && layered_window) ? dib_data : NULL;
-}
-
-Size2 OS_Windows::get_layered_buffer_size() {
-
- return (is_layered_allowed() && layered_window) ? dib_size : Size2();
-}
-
-void OS_Windows::swap_layered_buffer() {
-
- if (is_layered_allowed() && layered_window) {
-
- //premultiply alpha
- for (int y = 0; y < dib_size.y; y++) {
- for (int x = 0; x < dib_size.x; x++) {
- float alpha = (float)dib_data[y * (int)dib_size.x * 4 + x * 4 + 3] / (float)0xFF;
- dib_data[y * (int)dib_size.x * 4 + x * 4 + 0] *= alpha;
- dib_data[y * (int)dib_size.x * 4 + x * 4 + 1] *= alpha;
- dib_data[y * (int)dib_size.x * 4 + x * 4 + 2] *= alpha;
- }
- }
- //swap layered window buffer
- POINT ptSrc = { 0, 0 };
- SIZE sizeWnd = { (long)dib_size.x, (long)dib_size.y };
- BLENDFUNCTION bf;
- bf.BlendOp = AC_SRC_OVER;
- bf.BlendFlags = 0;
- bf.AlphaFormat = AC_SRC_ALPHA;
- bf.SourceConstantAlpha = 0xFF;
- UpdateLayeredWindow(hWnd, NULL, NULL, &sizeWnd, hDC_dib, &ptSrc, 0, &bf, ULW_ALPHA);
- }
-}
-
-void OS_Windows::set_borderless_window(bool p_borderless) {
- if (video_mode.borderless_window == p_borderless)
- return;
-
- if (!p_borderless && layered_window)
- set_window_per_pixel_transparency_enabled(false);
-
- video_mode.borderless_window = p_borderless;
-
- preserve_window_size = true;
- _update_window_style();
-}
-
-bool OS_Windows::get_borderless_window() {
- return video_mode.borderless_window;
-}
-
-void OS_Windows::_update_window_style(bool p_repaint, bool p_maximized) {
- if (video_mode.fullscreen || video_mode.borderless_window) {
- SetWindowLongPtr(hWnd, GWL_STYLE, WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE);
- } else {
- if (video_mode.resizable) {
- if (p_maximized) {
- SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MAXIMIZE);
- } else {
- SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
- }
- } else {
- SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_MINIMIZEBOX | WS_POPUPWINDOW | WS_VISIBLE);
- }
- }
-
- SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
-
- if (p_repaint) {
- RECT rect;
- GetWindowRect(hWnd, &rect);
- MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
- }
-}
-
Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
-
String path = p_path;
if (!FileAccess::exists(path)) {
@@ -2258,14 +257,14 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han
PAddDllDirectory add_dll_directory = (PAddDllDirectory)GetProcAddress(GetModuleHandle("kernel32.dll"), "AddDllDirectory");
PRemoveDllDirectory remove_dll_directory = (PRemoveDllDirectory)GetProcAddress(GetModuleHandle("kernel32.dll"), "RemoveDllDirectory");
- bool has_dll_directory_api = ((add_dll_directory != NULL) && (remove_dll_directory != NULL));
- DLL_DIRECTORY_COOKIE cookie = NULL;
+ bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr));
+ DLL_DIRECTORY_COOKIE cookie = nullptr;
if (p_also_set_library_path && has_dll_directory_api) {
- cookie = add_dll_directory(path.get_base_dir().c_str());
+ cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data()));
}
- p_library_handle = (void *)LoadLibraryExW(path.c_str(), NULL, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + format_error_message(GetLastError()) + ".");
if (cookie) {
@@ -2294,24 +293,11 @@ Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, cons
return OK;
}
-void OS_Windows::request_attention() {
-
- FLASHWINFO info;
- info.cbSize = sizeof(FLASHWINFO);
- info.hwnd = hWnd;
- info.dwFlags = FLASHW_TRAY;
- info.dwTimeout = 0;
- info.uCount = 2;
- FlashWindowEx(&info);
-}
-
String OS_Windows::get_name() const {
-
return "Windows";
}
OS::Date OS_Windows::get_date(bool utc) const {
-
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
@@ -2326,8 +312,8 @@ OS::Date OS_Windows::get_date(bool utc) const {
date.dst = false;
return date;
}
-OS::Time OS_Windows::get_time(bool utc) const {
+OS::Time OS_Windows::get_time(bool utc) const {
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
@@ -2360,380 +346,112 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const {
return ret;
}
-uint64_t OS_Windows::get_unix_time() const {
-
- FILETIME ft;
- SYSTEMTIME st;
- GetSystemTime(&st);
- SystemTimeToFileTime(&st, &ft);
-
- SYSTEMTIME ep;
- ep.wYear = 1970;
- ep.wMonth = 1;
- ep.wDayOfWeek = 4;
- ep.wDay = 1;
- ep.wHour = 0;
- ep.wMinute = 0;
- ep.wSecond = 0;
- ep.wMilliseconds = 0;
- FILETIME fep;
- SystemTimeToFileTime(&ep, &fep);
-
- // Type punning through unions (rather than pointer cast) as per:
- // https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime#remarks
- ULARGE_INTEGER ft_punning;
- ft_punning.LowPart = ft.dwLowDateTime;
- ft_punning.HighPart = ft.dwHighDateTime;
-
- ULARGE_INTEGER fep_punning;
- fep_punning.LowPart = fep.dwLowDateTime;
- fep_punning.HighPart = fep.dwHighDateTime;
-
- return (ft_punning.QuadPart - fep_punning.QuadPart) / 10000000;
-};
-
-uint64_t OS_Windows::get_system_time_secs() const {
-
- return get_system_time_msecs() / 1000;
-}
-
-uint64_t OS_Windows::get_system_time_msecs() const {
-
- const uint64_t WINDOWS_TICK = 10000;
- const uint64_t MSEC_TO_UNIX_EPOCH = 11644473600000LL;
+double OS_Windows::get_unix_time() const {
+ // 1 Windows tick is 100ns
+ const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
+ const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
SYSTEMTIME st;
GetSystemTime(&st);
FILETIME ft;
SystemTimeToFileTime(&st, &ft);
- uint64_t ret;
- ret = ft.dwHighDateTime;
- ret <<= 32;
- ret |= ft.dwLowDateTime;
+ uint64_t ticks_time;
+ ticks_time = ft.dwHighDateTime;
+ ticks_time <<= 32;
+ ticks_time |= ft.dwLowDateTime;
- return (uint64_t)(ret / WINDOWS_TICK - MSEC_TO_UNIX_EPOCH);
+ return (double)(ticks_time - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
}
void OS_Windows::delay_usec(uint32_t p_usec) const {
-
if (p_usec < 1000)
Sleep(1);
else
Sleep(p_usec / 1000);
}
-uint64_t OS_Windows::get_ticks_usec() const {
+uint64_t OS_Windows::get_ticks_usec() const {
uint64_t ticks;
- uint64_t time;
+
// This is the number of clock ticks since start
if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks))
ticks = (UINT64)timeGetTime();
- // Divide by frequency to get the time in seconds
- time = ticks * 1000000L / ticks_per_second;
- // Subtract the time at game start to get
- // the time since the game started
- time -= ticks_start;
- return time;
-}
-void OS_Windows::process_events() {
-
- MSG msg;
-
- if (!drop_events) {
- joypad->process_joypads();
- }
-
- while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
-
- TranslateMessage(&msg);
- DispatchMessageW(&msg);
- }
-
- if (!drop_events) {
- process_key_events();
- input->flush_accumulated_events();
- }
-}
-
-void OS_Windows::set_cursor_shape(CursorShape p_shape) {
-
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+ // Divide by frequency to get the time in seconds
+ // original calculation shown below is subject to overflow
+ // with high ticks_per_second and a number of days since the last reboot.
+ // time = ticks * 1000000L / ticks_per_second;
- if (cursor_shape == p_shape)
- return;
+ // we can prevent this by either using 128 bit math
+ // or separating into a calculation for seconds, and the fraction
+ uint64_t seconds = ticks / ticks_per_second;
- if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
- cursor_shape = p_shape;
- return;
- }
+ // compiler will optimize these two into one divide
+ uint64_t leftover = ticks % ticks_per_second;
- static const LPCTSTR win_cursors[CURSOR_MAX] = {
- IDC_ARROW,
- IDC_IBEAM,
- IDC_HAND, //finger
- IDC_CROSS,
- IDC_WAIT,
- IDC_APPSTARTING,
- IDC_ARROW,
- IDC_ARROW,
- IDC_NO,
- IDC_SIZENS,
- IDC_SIZEWE,
- IDC_SIZENESW,
- IDC_SIZENWSE,
- IDC_SIZEALL,
- IDC_SIZENS,
- IDC_SIZEWE,
- IDC_HELP
- };
+ // remainder
+ uint64_t time = (leftover * 1000000L) / ticks_per_second;
- if (cursors[p_shape] != NULL) {
- SetCursor(cursors[p_shape]);
- } else {
- SetCursor(LoadCursor(hInstance, win_cursors[p_shape]));
- }
+ // seconds
+ time += seconds * 1000000L;
- cursor_shape = p_shape;
-}
-
-OS::CursorShape OS_Windows::get_cursor_shape() const {
-
- return cursor_shape;
+ // Subtract the time at game start to get
+ // the time since the game started
+ time -= ticks_start;
+ return time;
}
-void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-
- if (p_cursor.is_valid()) {
-
- Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- set_cursor_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
+String OS_Windows::_quote_command_line_argument(const String &p_text) const {
+ for (int i = 0; i < p_text.size(); i++) {
+ char32_t c = p_text[i];
+ if (c == ' ' || c == '&' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '=' || c == ';' || c == '!' || c == '\'' || c == '+' || c == ',' || c == '`' || c == '~') {
+ return "\"" + p_text + "\"";
}
-
- Ref<Texture> texture = p_cursor;
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
- Size2 texture_size;
- Rect2 atlas_rect;
-
- if (texture.is_valid()) {
- image = texture->get_data();
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(!texture.is_valid());
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- image = texture->get_data();
-
- ERR_FAIL_COND(!image.is_valid());
-
- UINT image_size = texture_size.width * texture_size.height;
-
- // Create the BITMAP with alpha channel
- COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size);
-
- image->lock();
- for (UINT index = 0; index < image_size; index++) {
- int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
- int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
-
- if (atlas_texture.is_valid()) {
- column_index = MIN(column_index, atlas_rect.size.width - 1);
- row_index = MIN(row_index, atlas_rect.size.height - 1);
- }
-
- *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32();
- }
- image->unlock();
-
- // Using 4 channels, so 4 * 8 bits
- HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer);
- COLORREF clrTransparent = -1;
-
- // Create the AND and XOR masks for the bitmap
- HBITMAP hAndMask = NULL;
- HBITMAP hXorMask = NULL;
-
- GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask);
-
- if (NULL == hAndMask || NULL == hXorMask) {
- memfree(buffer);
- DeleteObject(bitmap);
- return;
- }
-
- // Finally, create the icon
- ICONINFO iconinfo;
- iconinfo.fIcon = FALSE;
- iconinfo.xHotspot = p_hotspot.x;
- iconinfo.yHotspot = p_hotspot.y;
- iconinfo.hbmMask = hAndMask;
- iconinfo.hbmColor = hXorMask;
-
- if (cursors[p_shape])
- DestroyIcon(cursors[p_shape]);
-
- cursors[p_shape] = CreateIconIndirect(&iconinfo);
-
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- if (p_shape == cursor_shape) {
- if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
- SetCursor(cursors[p_shape]);
- }
- }
-
- if (hAndMask != NULL) {
- DeleteObject(hAndMask);
- }
-
- if (hXorMask != NULL) {
- DeleteObject(hXorMask);
- }
-
- memfree(buffer);
- DeleteObject(bitmap);
- } else {
- // Reset to default system cursor
- if (cursors[p_shape]) {
- DestroyIcon(cursors[p_shape]);
- cursors[p_shape] = NULL;
- }
-
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- set_cursor_shape(c);
-
- cursors_cache.erase(p_shape);
}
-}
-
-void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) {
-
- // Get the system display DC
- HDC hDC = GetDC(NULL);
-
- // Create helper DC
- HDC hMainDC = CreateCompatibleDC(hDC);
- HDC hAndMaskDC = CreateCompatibleDC(hDC);
- HDC hXorMaskDC = CreateCompatibleDC(hDC);
-
- // Get the dimensions of the source bitmap
- BITMAP bm;
- GetObject(hSourceBitmap, sizeof(BITMAP), &bm);
-
- // Create the mask bitmaps
- hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
- hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
-
- // Release the system display DC
- ReleaseDC(NULL, hDC);
-
- // Select the bitmaps to helper DC
- HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap);
- HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap);
- HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap);
-
- // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap
- // with 'clrTransparent' will be white pixels of the monochrome bitmap
- SetBkColor(hMainDC, clrTransparent);
- BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY);
-
- // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap
- // with 'clrTransparent' will be black and rest the pixels same as corresponding
- // pixels of the source bitmap
- SetBkColor(hXorMaskDC, RGB(0, 0, 0));
- SetTextColor(hXorMaskDC, RGB(255, 255, 255));
- BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY);
- BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND);
-
- // Deselect bitmaps from the helper DC
- SelectObject(hMainDC, hOldMainBitmap);
- SelectObject(hAndMaskDC, hOldAndMaskBitmap);
- SelectObject(hXorMaskDC, hOldXorMaskBitmap);
-
- // Delete the helper DC
- DeleteDC(hXorMaskDC);
- DeleteDC(hAndMaskDC);
- DeleteDC(hMainDC);
+ return p_text;
}
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
-
if (p_blocking && r_pipe) {
-
- String argss;
- argss = "\"\"" + p_path + "\"";
-
+ String argss = _quote_command_line_argument(p_path);
for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
-
- argss += " \"" + E->get() + "\"";
+ argss += " " + _quote_command_line_argument(E->get());
}
- argss += "\"";
-
if (read_stderr) {
argss += " 2>&1"; // Read stderr too
}
+ // Note: _wpopen is calling command as "cmd.exe /c argss", instead of executing it directly, add extra quotes around full command, to prevent it from stripping quotes in the command.
+ argss = _quote_command_line_argument(argss);
- FILE *f = _wpopen(argss.c_str(), L"r");
-
+ FILE *f = _wpopen((LPCWSTR)(argss.utf16().get_data()), L"r");
ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
char buf[65535];
while (fgets(buf, 65535, f)) {
-
if (p_pipe_mutex) {
p_pipe_mutex->lock();
}
- (*r_pipe) += buf;
+ (*r_pipe) += String::utf8(buf);
if (p_pipe_mutex) {
p_pipe_mutex->unlock();
}
}
int rv = _pclose(f);
- if (r_exitcode)
+ if (r_exitcode) {
*r_exitcode = rv;
+ }
return OK;
}
- String cmdline = "\"" + p_path + "\"";
+ String cmdline = _quote_command_line_argument(p_path);
const List<String>::Element *I = p_arguments.front();
while (I) {
-
- cmdline += " \"" + I->get() + "\"";
-
+ cmdline += " " + _quote_command_line_argument(I->get());
I = I->next();
- };
+ }
ProcessInfo pi;
ZeroMemory(&pi.si, sizeof(pi.si));
@@ -2741,34 +459,29 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
ZeroMemory(&pi.pi, sizeof(pi.pi));
LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
- Vector<CharType> modstr; //windows wants to change this no idea why
- modstr.resize(cmdline.size());
- for (int i = 0; i < cmdline.size(); i++)
- modstr.write[i] = cmdline[i];
- int ret = CreateProcessW(NULL, modstr.ptrw(), NULL, NULL, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, NULL, NULL, si_w, &pi.pi);
+ Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why.
+ int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi);
ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK);
if (p_blocking) {
-
DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE);
- if (r_exitcode)
+ if (r_exitcode) {
*r_exitcode = ret2;
+ }
CloseHandle(pi.pi.hProcess);
CloseHandle(pi.pi.hThread);
} else {
-
ProcessID pid = pi.pi.dwProcessId;
if (r_child_id) {
*r_child_id = pid;
- };
+ }
process_map->insert(pid, pi);
- };
+ }
return OK;
};
Error OS_Windows::kill(const ProcessID &p_pid) {
-
ERR_FAIL_COND_V(!process_map->has(p_pid), FAILED);
const PROCESS_INFORMATION pi = (*process_map)[p_pid].pi;
@@ -2787,197 +500,46 @@ int OS_Windows::get_process_id() const {
}
Error OS_Windows::set_cwd(const String &p_cwd) {
-
- if (_wchdir(p_cwd.c_str()) != 0)
+ if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0)
return ERR_CANT_OPEN;
return OK;
}
String OS_Windows::get_executable_path() const {
-
- wchar_t bufname[4096];
- GetModuleFileNameW(NULL, bufname, 4096);
- String s = bufname;
+ WCHAR bufname[4096];
+ GetModuleFileNameW(nullptr, bufname, 4096);
+ String s = String::utf16((const char16_t *)bufname);
return s;
}
-void OS_Windows::set_native_icon(const String &p_filename) {
-
- FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
- ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'.");
-
- ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
- int pos = 0;
-
- icon_dir->idReserved = f->get_32();
- pos += sizeof(WORD);
- f->seek(pos);
-
- icon_dir->idType = f->get_32();
- pos += sizeof(WORD);
- f->seek(pos);
-
- ERR_FAIL_COND_MSG(icon_dir->idType != 1, "Invalid icon file format!");
-
- icon_dir->idCount = f->get_32();
- pos += sizeof(WORD);
- f->seek(pos);
-
- icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY));
- f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY));
-
- int small_icon_index = -1; // Select 16x16 with largest color count
- int small_icon_cc = 0;
- int big_icon_index = -1; // Select largest
- int big_icon_width = 16;
- int big_icon_cc = 0;
-
- for (int i = 0; i < icon_dir->idCount; i++) {
- int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount;
- int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth;
- if (width == 16) {
- if (colors >= small_icon_cc) {
- small_icon_index = i;
- small_icon_cc = colors;
- }
- }
- if (width >= big_icon_width) {
- if (colors >= big_icon_cc) {
- big_icon_index = i;
- big_icon_width = width;
- big_icon_cc = colors;
- }
- }
- }
-
- ERR_FAIL_COND_MSG(big_icon_index == -1, "No valid icons found!");
-
- if (small_icon_index == -1) {
- WARN_PRINTS("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!");
- small_icon_index = big_icon_index;
- small_icon_cc = big_icon_cc;
- }
-
- // Read the big icon
- DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes;
- Vector<uint8_t> data_big;
- data_big.resize(bytecount_big);
- pos = icon_dir->idEntries[big_icon_index].dwImageOffset;
- f->seek(pos);
- f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big);
- HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000);
- ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
-
- // Read the small icon
- DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes;
- Vector<uint8_t> data_small;
- data_small.resize(bytecount_small);
- pos = icon_dir->idEntries[small_icon_index].dwImageOffset;
- f->seek(pos);
- f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small);
- HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000);
- ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
-
- // Online tradition says to be sure last error is cleared and set the small icon first
- int err = 0;
- SetLastError(err);
-
- SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);
- err = GetLastError();
- ERR_FAIL_COND_MSG(err, "Error setting ICON_SMALL: " + format_error_message(err) + ".");
-
- SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big);
- err = GetLastError();
- ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + ".");
-
- memdelete(f);
- memdelete(icon_dir);
-}
-
-void OS_Windows::set_icon(const Ref<Image> &p_icon) {
-
- ERR_FAIL_COND(!p_icon.is_valid());
- Ref<Image> icon = p_icon->duplicate();
- if (icon->get_format() != Image::FORMAT_RGBA8)
- icon->convert(Image::FORMAT_RGBA8);
- int w = icon->get_width();
- int h = icon->get_height();
-
- /* Create temporary bitmap buffer */
- int icon_len = 40 + h * w * 4;
- Vector<BYTE> v;
- v.resize(icon_len);
- BYTE *icon_bmp = v.ptrw();
-
- encode_uint32(40, &icon_bmp[0]);
- encode_uint32(w, &icon_bmp[4]);
- encode_uint32(h * 2, &icon_bmp[8]);
- encode_uint16(1, &icon_bmp[12]);
- encode_uint16(32, &icon_bmp[14]);
- encode_uint32(BI_RGB, &icon_bmp[16]);
- encode_uint32(w * h * 4, &icon_bmp[20]);
- encode_uint32(0, &icon_bmp[24]);
- encode_uint32(0, &icon_bmp[28]);
- encode_uint32(0, &icon_bmp[32]);
- encode_uint32(0, &icon_bmp[36]);
-
- uint8_t *wr = &icon_bmp[40];
- PoolVector<uint8_t>::Read r = icon->get_data().read();
-
- for (int i = 0; i < h; i++) {
-
- for (int j = 0; j < w; j++) {
-
- const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
- uint8_t *wpx = &wr[(i * w + j) * 4];
- wpx[0] = rpx[2];
- wpx[1] = rpx[1];
- wpx[2] = rpx[0];
- wpx[3] = rpx[3];
- }
- }
-
- HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
-
- /* Set the icon for the window */
- SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
-
- /* Set the icon in the task manager (should we do this?) */
- SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
-}
-
bool OS_Windows::has_environment(const String &p_var) const {
-
#ifdef MINGW_ENABLED
- return _wgetenv(p_var.c_str()) != NULL;
+ return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr;
#else
- wchar_t *env;
+ WCHAR *env;
size_t len;
- _wdupenv_s(&env, &len, p_var.c_str());
- const bool has_env = env != NULL;
+ _wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data()));
+ const bool has_env = env != nullptr;
free(env);
return has_env;
#endif
};
String OS_Windows::get_environment(const String &p_var) const {
-
- wchar_t wval[0x7Fff]; // MSDN says 32767 char is the maximum
- int wlen = GetEnvironmentVariableW(p_var.c_str(), wval, 0x7Fff);
+ WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum
+ int wlen = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), wval, 0x7fff);
if (wlen > 0) {
- return wval;
+ return String::utf16((const char16_t *)wval);
}
return "";
}
bool OS_Windows::set_environment(const String &p_var, const String &p_value) const {
-
- return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str());
+ return (bool)SetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), (LPCWSTR)(p_value.utf16().get_data()));
}
String OS_Windows::get_stdin_string(bool p_block) {
-
if (p_block) {
char buff[1024];
return fgets(buff, 1024, stdin);
@@ -2986,24 +548,12 @@ String OS_Windows::get_stdin_string(bool p_block) {
return String();
}
-void OS_Windows::enable_for_stealing_focus(ProcessID pid) {
-
- AllowSetForegroundWindow(pid);
-}
-
-void OS_Windows::move_window_to_foreground() {
-
- SetForegroundWindow(hWnd);
-}
-
Error OS_Windows::shell_open(String p_uri) {
-
- ShellExecuteW(NULL, NULL, p_uri.c_str(), NULL, NULL, SW_SHOWNORMAL);
+ ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL);
return OK;
}
String OS_Windows::get_locale() const {
-
const _WinLocale *wl = &_win_locales[0];
LANGID langid = GetUserDefaultUILanguage();
@@ -3012,7 +562,6 @@ String OS_Windows::get_locale() const {
int sublang = langid & ~((1 << 9) - 1);
while (wl->locale) {
-
if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL)
neutral = wl->locale;
@@ -3058,97 +607,14 @@ int OS_Windows::get_processor_count() const {
return sysinfo.dwNumberOfProcessors;
}
-OS::LatinKeyboardVariant OS_Windows::get_latin_keyboard_variant() const {
-
- unsigned long azerty[] = {
- 0x00020401, // Arabic (102) AZERTY
- 0x0001080c, // Belgian (Comma)
- 0x0000080c, // Belgian French
- 0x0000040c, // French
- 0 // <--- STOP MARK
- };
- unsigned long qwertz[] = {
- 0x0000041a, // Croation
- 0x00000405, // Czech
- 0x00000407, // German
- 0x00010407, // German (IBM)
- 0x0000040e, // Hungarian
- 0x0000046e, // Luxembourgish
- 0x00010415, // Polish (214)
- 0x00000418, // Romanian (Legacy)
- 0x0000081a, // Serbian (Latin)
- 0x0000041b, // Slovak
- 0x00000424, // Slovenian
- 0x0001042e, // Sorbian Extended
- 0x0002042e, // Sorbian Standard
- 0x0000042e, // Sorbian Standard (Legacy)
- 0x0000100c, // Swiss French
- 0x00000807, // Swiss German
- 0 // <--- STOP MARK
- };
- unsigned long dvorak[] = {
- 0x00010409, // US-Dvorak
- 0x00030409, // US-Dvorak for left hand
- 0x00040409, // US-Dvorak for right hand
- 0 // <--- STOP MARK
- };
-
- char name[KL_NAMELENGTH + 1];
- name[0] = 0;
- GetKeyboardLayoutNameA(name);
-
- unsigned long hex = strtoul(name, NULL, 16);
-
- int i = 0;
- while (azerty[i] != 0) {
- if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY;
- i++;
- }
-
- i = 0;
- while (qwertz[i] != 0) {
- if (qwertz[i] == hex) return LATIN_KEYBOARD_QWERTZ;
- i++;
- }
-
- i = 0;
- while (dvorak[i] != 0) {
- if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK;
- i++;
- }
-
- return LATIN_KEYBOARD_QWERTY;
-}
-
-void OS_Windows::release_rendering_thread() {
-
- gl_context->release_current();
-}
-
-void OS_Windows::make_rendering_thread() {
-
- gl_context->make_current();
-}
-
-void OS_Windows::swap_buffers() {
-
- gl_context->swap_buffers();
-}
-
-void OS_Windows::force_process_input() {
- process_events(); // get rid of pending events
-}
-
void OS_Windows::run() {
-
if (!main_loop)
return;
main_loop->init();
while (!force_quit) {
-
- process_events(); // get rid of pending events
+ DisplayServer::get_singleton()->process_events(); // get rid of pending events
if (Main::iteration())
break;
};
@@ -3157,12 +623,10 @@ void OS_Windows::run() {
}
MainLoop *OS_Windows::get_main_loop() const {
-
return main_loop;
}
String OS_Windows::get_config_path() const {
-
if (has_environment("XDG_CONFIG_HOME")) { // unlikely, but after all why not?
return get_environment("XDG_CONFIG_HOME");
} else if (has_environment("APPDATA")) {
@@ -3173,7 +637,6 @@ String OS_Windows::get_config_path() const {
}
String OS_Windows::get_data_path() const {
-
if (has_environment("XDG_DATA_HOME")) {
return get_environment("XDG_DATA_HOME");
} else {
@@ -3182,7 +645,6 @@ String OS_Windows::get_data_path() const {
}
String OS_Windows::get_cache_path() const {
-
if (has_environment("XDG_CACHE_HOME")) {
return get_environment("XDG_CACHE_HOME");
} else if (has_environment("TEMP")) {
@@ -3194,12 +656,10 @@ String OS_Windows::get_cache_path() const {
// Get properly capitalized engine name for system paths
String OS_Windows::get_godot_dir_name() const {
-
return String(VERSION_SHORT_NAME).capitalize();
}
String OS_Windows::get_system_dir(SystemDir p_dir) const {
-
KNOWNFOLDERID id;
switch (p_dir) {
@@ -3230,15 +690,14 @@ String OS_Windows::get_system_dir(SystemDir p_dir) const {
}
PWSTR szPath;
- HRESULT res = SHGetKnownFolderPath(id, 0, NULL, &szPath);
+ HRESULT res = SHGetKnownFolderPath(id, 0, nullptr, &szPath);
ERR_FAIL_COND_V(res != S_OK, String());
- String path = String(szPath);
+ String path = String::utf16((const char16_t *)szPath);
CoTaskMemFree(szPath);
return path;
}
String OS_Windows::get_user_data_dir() const {
-
String appname = get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name"));
if (appname != "") {
bool use_custom_dir = ProjectSettings::get_singleton()->get("application/config/use_custom_user_dir");
@@ -3257,75 +716,12 @@ String OS_Windows::get_user_data_dir() const {
}
String OS_Windows::get_unique_id() const {
-
HW_PROFILE_INFO HwProfInfo;
ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), "");
- return String(HwProfInfo.szHwProfileGuid);
-}
-
-void OS_Windows::set_ime_active(const bool p_active) {
-
- if (p_active) {
- ImmAssociateContext(hWnd, im_himc);
-
- set_ime_position(im_position);
- } else {
- ImmAssociateContext(hWnd, (HIMC)0);
- }
-}
-
-void OS_Windows::set_ime_position(const Point2 &p_pos) {
-
- im_position = p_pos;
-
- HIMC himc = ImmGetContext(hWnd);
- if (himc == (HIMC)0)
- return;
-
- COMPOSITIONFORM cps;
- cps.dwStyle = CFS_FORCE_POSITION;
- cps.ptCurrentPos.x = im_position.x;
- cps.ptCurrentPos.y = im_position.y;
- ImmSetCompositionWindow(himc, &cps);
- ImmReleaseContext(hWnd, himc);
-}
-
-bool OS_Windows::is_joy_known(int p_device) {
- return input->is_joy_mapped(p_device);
-}
-
-String OS_Windows::get_joy_guid(int p_device) const {
- return input->get_joy_guid_remapped(p_device);
-}
-
-void OS_Windows::_set_use_vsync(bool p_enable) {
-
- if (gl_context)
- gl_context->set_use_vsync(p_enable);
-}
-/*
-bool OS_Windows::is_vsync_enabled() const {
-
- if (gl_context)
- return gl_context->is_using_vsync();
-
- return true;
-}*/
-
-OS::PowerState OS_Windows::get_power_state() {
- return power_manager->get_power_state();
-}
-
-int OS_Windows::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
-}
-
-int OS_Windows::get_power_percent_left() {
- return power_manager->get_power_percent_left();
+ return String::utf16((const char16_t *)(HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN);
}
bool OS_Windows::_check_internal_feature_support(const String &p_feature) {
-
return p_feature == "pc";
}
@@ -3337,71 +733,106 @@ bool OS_Windows::is_disable_crash_handler() const {
return crash_handler.is_disabled();
}
-void OS_Windows::process_and_drop_events() {
-
- drop_events = true;
- process_events();
- drop_events = false;
-}
-
Error OS_Windows::move_to_trash(const String &p_path) {
SHFILEOPSTRUCTW sf;
- WCHAR *from = new WCHAR[p_path.length() + 2];
- wcscpy_s(from, p_path.length() + 1, p_path.c_str());
- from[p_path.length() + 1] = 0;
- sf.hwnd = hWnd;
+ Char16String utf16 = p_path.utf16();
+ WCHAR *from = new WCHAR[utf16.length() + 2];
+ wcscpy_s(from, utf16.length() + 1, (LPCWSTR)(utf16.get_data()));
+ from[utf16.length() + 1] = 0;
+
+ sf.hwnd = main_window;
sf.wFunc = FO_DELETE;
sf.pFrom = from;
- sf.pTo = NULL;
+ sf.pTo = nullptr;
sf.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
sf.fAnyOperationsAborted = FALSE;
- sf.hNameMappings = NULL;
- sf.lpszProgressTitle = NULL;
+ sf.hNameMappings = nullptr;
+ sf.lpszProgressTitle = nullptr;
int ret = SHFileOperationW(&sf);
delete[] from;
if (ret) {
- ERR_PRINTS("SHFileOperation error: " + itos(ret));
+ ERR_PRINT("SHFileOperation error: " + itos(ret));
return FAILED;
}
return OK;
}
+int OS_Windows::get_tablet_driver_count() const {
+ return tablet_drivers.size();
+}
+
+String OS_Windows::get_tablet_driver_name(int p_driver) const {
+ if (p_driver < 0 || p_driver >= tablet_drivers.size()) {
+ return "";
+ } else {
+ return tablet_drivers[p_driver];
+ }
+}
+
+String OS_Windows::get_current_tablet_driver() const {
+ return tablet_driver;
+}
+
+void OS_Windows::set_current_tablet_driver(const String &p_driver) {
+ if (get_tablet_driver_count() == 0) {
+ return;
+ }
+ bool found = false;
+ for (int i = 0; i < get_tablet_driver_count(); i++) {
+ if (p_driver == get_tablet_driver_name(i)) {
+ found = true;
+ }
+ }
+ if (found) {
+ if (DisplayServerWindows::get_singleton()) {
+ ((DisplayServerWindows *)DisplayServerWindows::get_singleton())->_update_tablet_ctx(tablet_driver, p_driver);
+ }
+ tablet_driver = p_driver;
+ } else {
+ ERR_PRINT("Unknown tablet driver " + p_driver + ".");
+ }
+}
+
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
+ //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
+ HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
+ if (wintab_lib) {
+ DisplayServerWindows::wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW");
+ DisplayServerWindows::wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose");
+ DisplayServerWindows::wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW");
+ DisplayServerWindows::wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket");
+ DisplayServerWindows::wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable");
- drop_events = false;
- key_event_pos = 0;
- layered_window = false;
- hBitmap = NULL;
- force_quit = false;
- alt_mem = false;
- gr_mem = false;
- shift_mem = false;
- control_mem = false;
- meta_mem = false;
- minimized = false;
- was_maximized = false;
- window_focused = true;
- console_visible = IsWindowVisible(GetConsoleWindow());
-
- //Note: Functions for pen input, available on Windows 8+
+ DisplayServerWindows::wintab_available = DisplayServerWindows::wintab_WTOpen && DisplayServerWindows::wintab_WTClose && DisplayServerWindows::wintab_WTInfo && DisplayServerWindows::wintab_WTPacket && DisplayServerWindows::wintab_WTEnable;
+ }
+
+ if (DisplayServerWindows::wintab_available) {
+ tablet_drivers.push_back("wintab");
+ }
+
+ //Note: Windows Ink API for pen input, available on Windows 8+ only.
HMODULE user32_lib = LoadLibraryW(L"user32.dll");
if (user32_lib) {
- win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
- win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
+ DisplayServerWindows::win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
+ DisplayServerWindows::win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
+
+ DisplayServerWindows::winink_available = DisplayServerWindows::win8p_GetPointerType && DisplayServerWindows::win8p_GetPointerPenInfo;
+ }
+
+ if (DisplayServerWindows::winink_available) {
+ tablet_drivers.push_back("winink");
}
+ force_quit = false;
+
hInstance = _hInstance;
- pressrc = 0;
- old_invalid = true;
- mouse_mode = MOUSE_MODE_VISIBLE;
#ifdef STDOUT_FILE
stdo = fopen("stdout.txt", "wb");
#endif
- user_proc = NULL;
#ifdef WASAPI_ENABLED
AudioDriverManager::add_driver(&driver_wasapi);
@@ -3410,16 +841,14 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) {
AudioDriverManager::add_driver(&driver_xaudio2);
#endif
+ DisplayServerWindows::register_windows_driver();
+
Vector<Logger *> loggers;
loggers.push_back(memnew(WindowsTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
}
OS_Windows::~OS_Windows() {
- if (is_layered_allowed() && layered_window) {
- DeleteObject(hBitmap);
- DeleteDC(hDC_dib);
- }
#ifdef STDOUT_FILE
fclose(stdo);
#endif
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index cf16295a70..910a83539a 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -31,8 +31,7 @@
#ifndef OS_WINDOWS_H
#define OS_WINDOWS_H
-#include "context_gl_windows.h"
-#include "core/os/input.h"
+#include "core/input/input.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "crash_handler_windows.h"
@@ -40,193 +39,43 @@
#include "drivers/wasapi/audio_driver_wasapi.h"
#include "drivers/winmidi/midi_driver_winmidi.h"
#include "key_mapping_windows.h"
-#include "main/input_default.h"
-#include "power_windows.h"
#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
+#include "servers/rendering/rasterizer.h"
+#include "servers/rendering_server.h"
#ifdef XAUDIO2_ENABLED
#include "drivers/xaudio2/audio_driver_xaudio2.h"
#endif
+#if defined(OPENGL_ENABLED)
+#include "context_gl_windows.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "platform/windows/vulkan_context_win.h"
+#endif
+
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <windowsx.h>
-#ifndef POINTER_STRUCTURES
-
-#define POINTER_STRUCTURES
-
-typedef DWORD POINTER_INPUT_TYPE;
-typedef UINT32 POINTER_FLAGS;
-typedef UINT32 PEN_FLAGS;
-typedef UINT32 PEN_MASK;
-
-enum tagPOINTER_INPUT_TYPE {
- PT_POINTER = 0x00000001,
- PT_TOUCH = 0x00000002,
- PT_PEN = 0x00000003,
- PT_MOUSE = 0x00000004,
- PT_TOUCHPAD = 0x00000005
-};
-
-typedef enum tagPOINTER_BUTTON_CHANGE_TYPE {
- POINTER_CHANGE_NONE,
- POINTER_CHANGE_FIRSTBUTTON_DOWN,
- POINTER_CHANGE_FIRSTBUTTON_UP,
- POINTER_CHANGE_SECONDBUTTON_DOWN,
- POINTER_CHANGE_SECONDBUTTON_UP,
- POINTER_CHANGE_THIRDBUTTON_DOWN,
- POINTER_CHANGE_THIRDBUTTON_UP,
- POINTER_CHANGE_FOURTHBUTTON_DOWN,
- POINTER_CHANGE_FOURTHBUTTON_UP,
- POINTER_CHANGE_FIFTHBUTTON_DOWN,
- POINTER_CHANGE_FIFTHBUTTON_UP,
-} POINTER_BUTTON_CHANGE_TYPE;
-
-typedef struct tagPOINTER_INFO {
- POINTER_INPUT_TYPE pointerType;
- UINT32 pointerId;
- UINT32 frameId;
- POINTER_FLAGS pointerFlags;
- HANDLE sourceDevice;
- HWND hwndTarget;
- POINT ptPixelLocation;
- POINT ptHimetricLocation;
- POINT ptPixelLocationRaw;
- POINT ptHimetricLocationRaw;
- DWORD dwTime;
- UINT32 historyCount;
- INT32 InputData;
- DWORD dwKeyStates;
- UINT64 PerformanceCount;
- POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
-} POINTER_INFO;
-
-typedef struct tagPOINTER_PEN_INFO {
- POINTER_INFO pointerInfo;
- PEN_FLAGS penFlags;
- PEN_MASK penMask;
- UINT32 pressure;
- UINT32 rotation;
- INT32 tiltX;
- INT32 tiltY;
-} POINTER_PEN_INFO;
-
-#endif
-
-typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
-typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
-
-typedef struct {
- BYTE bWidth; // Width, in pixels, of the image
- BYTE bHeight; // Height, in pixels, of the image
- BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
- BYTE bReserved; // Reserved ( must be 0)
- WORD wPlanes; // Color Planes
- WORD wBitCount; // Bits per pixel
- DWORD dwBytesInRes; // How many bytes in this resource?
- DWORD dwImageOffset; // Where in the file is this image?
-} ICONDIRENTRY, *LPICONDIRENTRY;
-
-typedef struct {
- WORD idReserved; // Reserved (must be 0)
- WORD idType; // Resource Type (1 for icons)
- WORD idCount; // How many images?
- ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
-} ICONDIR, *LPICONDIR;
-
class JoypadWindows;
class OS_Windows : public OS {
-
- static GetPointerTypePtr win8p_GetPointerType;
- static GetPointerPenInfoPtr win8p_GetPointerPenInfo;
-
- enum {
- KEY_EVENT_BUFFER_SIZE = 512
- };
-
#ifdef STDOUT_FILE
FILE *stdo;
#endif
- struct KeyEvent {
-
- bool alt, shift, control, meta;
- UINT uMsg;
- WPARAM wParam;
- LPARAM lParam;
- };
-
- KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE];
- int key_event_pos;
-
uint64_t ticks_start;
uint64_t ticks_per_second;
- bool old_invalid;
- bool outside;
- int old_x, old_y;
- Point2i center;
-#if defined(OPENGL_ENABLED)
- ContextGL_Windows *gl_context;
-#endif
- VisualServer *visual_server;
- int pressrc;
- HINSTANCE hInstance; // Holds The Instance Of The Application
- HWND hWnd;
- Point2 last_pos;
-
- HBITMAP hBitmap; //DIB section for layered window
- uint8_t *dib_data;
- Size2 dib_size;
- HDC hDC_dib;
- bool layered_window;
-
- uint32_t move_timer_id;
-
- HCURSOR hCursor;
-
- Size2 min_size;
- Size2 max_size;
-
- Size2 window_rect;
- VideoMode video_mode;
- bool preserve_window_size = false;
-
+ HINSTANCE hInstance;
MainLoop *main_loop;
- WNDPROC user_proc;
-
- // IME
- HIMC im_himc;
- Vector2 im_position;
-
- MouseMode mouse_mode;
- bool alt_mem;
- bool gr_mem;
- bool shift_mem;
- bool control_mem;
- bool meta_mem;
- bool force_quit;
- bool window_has_focus;
- uint32_t last_button_state;
- bool use_raw_input;
- bool drop_events;
-
- HCURSOR cursors[CURSOR_MAX] = { NULL };
- CursorShape cursor_shape;
- Map<CursorShape, Vector<Variant> > cursors_cache;
-
- InputDefault *input;
- JoypadWindows *joypad;
- Map<int, Vector2> touch_state;
-
- PowerWindows *power_manager;
+ String tablet_driver;
+ Vector<String> tablet_drivers;
- int video_driver_index;
#ifdef WASAPI_ENABLED
AudioDriverWASAPI driver_wasapi;
#endif
@@ -239,105 +88,29 @@ class OS_Windows : public OS {
CrashHandler crash_handler;
- void _drag_event(float p_x, float p_y, int idx);
- void _touch_event(bool p_pressed, float p_x, float p_y, int idx);
-
- void _update_window_style(bool p_repaint = true, bool p_maximized = false);
-
- void _set_mouse_mode_impl(MouseMode p_mode);
+ bool force_quit;
+ HWND main_window;
// functions used by main to initialize/deinitialize the OS
protected:
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ virtual void initialize();
virtual void set_main_loop(MainLoop *p_main_loop);
virtual void delete_main_loop();
virtual void finalize();
virtual void finalize_core();
+ virtual String get_stdin_string(bool p_block);
- void process_events();
- void process_key_events();
+ String _quote_command_line_argument(const String &p_text) const;
struct ProcessInfo {
-
STARTUPINFO si;
PROCESS_INFORMATION pi;
};
Map<ProcessID, ProcessInfo> *process_map;
- bool pre_fs_valid;
- RECT pre_fs_rect;
- bool maximized;
- bool minimized;
- bool borderless;
- bool window_focused;
- bool console_visible;
- bool was_maximized;
-
public:
- LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
- String get_stdin_string(bool p_block);
-
- void set_mouse_mode(MouseMode p_mode);
- MouseMode get_mouse_mode() const;
-
- virtual void warp_mouse_position(const Point2 &p_to);
- virtual Point2 get_mouse_position() const;
- void update_real_mouse_position();
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
- virtual int get_screen_count() const;
- virtual int get_current_screen() const;
- virtual void set_current_screen(int p_screen);
- virtual Point2 get_screen_position(int p_screen = -1) const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
- virtual int get_screen_dpi(int p_screen = -1) const;
-
- virtual Point2 get_window_position() const;
- virtual void set_window_position(const Point2 &p_position);
- virtual Size2 get_window_size() const;
- virtual Size2 get_real_window_size() const;
- virtual Size2 get_max_window_size() const;
- virtual Size2 get_min_window_size() const;
- virtual void set_min_window_size(const Size2 p_size);
- virtual void set_max_window_size(const Size2 p_size);
- virtual void set_window_size(const Size2 p_size);
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual void set_window_resizable(bool p_enabled);
- virtual bool is_window_resizable() const;
- virtual void set_window_minimized(bool p_enabled);
- virtual bool is_window_minimized() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
- virtual void set_window_always_on_top(bool p_enabled);
- virtual bool is_window_always_on_top() const;
- virtual bool is_window_focused() const;
- virtual void set_console_visible(bool p_enabled);
- virtual bool is_console_visible() const;
- virtual void request_attention();
-
- virtual void set_borderless_window(bool p_borderless);
- virtual bool get_borderless_window();
-
- virtual bool get_window_per_pixel_transparency_enabled() const;
- virtual void set_window_per_pixel_transparency_enabled(bool p_enabled);
-
- virtual uint8_t *get_layered_buffer_data();
- virtual Size2 get_layered_buffer_size();
- virtual void swap_layered_buffer();
-
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
virtual Error close_dynamic_library(void *p_library_handle);
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
@@ -346,20 +119,24 @@ public:
virtual String get_name() const;
+ virtual int get_tablet_driver_count() const;
+ virtual String get_tablet_driver_name(int p_driver) const;
+ virtual String get_current_tablet_driver() const;
+ virtual void set_current_tablet_driver(const String &p_driver);
+
+ virtual void initialize_joypads() {}
+
virtual Date get_date(bool utc) const;
virtual Time get_time(bool utc) const;
virtual TimeZoneInfo get_time_zone_info() const;
- virtual uint64_t get_unix_time() const;
- virtual uint64_t get_system_time_secs() const;
- virtual uint64_t get_system_time_msecs() const;
+ virtual double get_unix_time() const;
- virtual bool can_draw() const;
virtual Error set_cwd(const String &p_cwd);
virtual void delay_usec(uint32_t p_usec) const;
virtual uint64_t get_ticks_usec() const;
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr);
virtual Error kill(const ProcessID &p_pid);
virtual int get_process_id() const;
@@ -367,28 +144,12 @@ public:
virtual String get_environment(const String &p_var) const;
virtual bool set_environment(const String &p_var, const String &p_value) const;
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
-
- void set_cursor_shape(CursorShape p_shape);
- CursorShape get_cursor_shape() const;
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
- void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap);
-
- void set_native_icon(const String &p_filename);
- void set_icon(const Ref<Image> &p_icon);
-
virtual String get_executable_path() const;
virtual String get_locale() const;
virtual int get_processor_count() const;
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
-
- virtual void enable_for_stealing_focus(ProcessID pid);
- virtual void move_window_to_foreground();
-
virtual String get_config_path() const;
virtual String get_data_path() const;
virtual String get_cache_path() const;
@@ -399,41 +160,21 @@ public:
virtual String get_unique_id() const;
- virtual void set_ime_active(const bool p_active);
- virtual void set_ime_position(const Point2 &p_pos);
-
- virtual void release_rendering_thread();
- virtual void make_rendering_thread();
- virtual void swap_buffers();
-
virtual Error shell_open(String p_uri);
void run();
- virtual bool get_swap_ok_cancel() { return true; }
-
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
-
- virtual void _set_use_vsync(bool p_enable);
- //virtual bool is_vsync_enabled() const;
-
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
-
virtual bool _check_internal_feature_support(const String &p_feature);
void disable_crash_handler();
bool is_disable_crash_handler() const;
virtual void initialize_debugging();
- void force_process_input();
-
virtual Error move_to_trash(const String &p_path);
- virtual void process_and_drop_events();
+ void set_main_window(HWND p_main_window) { main_window = p_main_window; }
+ HINSTANCE get_hinstance() { return hInstance; }
OS_Windows(HINSTANCE _hInstance);
~OS_Windows();
};
diff --git a/platform/windows/platform_config.h b/platform/windows/platform_config.h
index 04653ee56c..09a16614e0 100644
--- a/platform/windows/platform_config.h
+++ b/platform/windows/platform_config.h
@@ -29,8 +29,3 @@
/*************************************************************************/
#include <malloc.h>
-//#else
-//#include <alloca.h>
-//#endif
-#define GLES3_INCLUDE_H "thirdparty/glad/glad/glad.h"
-#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index a1ad3b8b50..22e33b51b4 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -9,14 +9,14 @@ from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
mingw_prefix = ""
- if (env["bits"] == "32"):
+ if env["bits"] == "32":
mingw_prefix = env["mingw_prefix_32"]
else:
mingw_prefix = env["mingw_prefix_64"]
- os.system(mingw_prefix + 'objcopy --only-keep-debug {0} {0}.debugsymbols'.format(target[0]))
- os.system(mingw_prefix + 'strip --strip-debug --strip-unneeded {0}'.format(target[0]))
- os.system(mingw_prefix + 'objcopy --add-gnu-debuglink={0}.debugsymbols {0}'.format(target[0]))
+ os.system(mingw_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ os.system(mingw_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ os.system(mingw_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
-if __name__ == '__main__':
+if __name__ == "__main__":
subprocess_main(globals())
diff --git a/platform/windows/power_windows.cpp b/platform/windows/power_windows.cpp
deleted file mode 100644
index aea06da413..0000000000
--- a/platform/windows/power_windows.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/*************************************************************************/
-/* power_windows.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-/*
-Adapted from corresponding SDL 2.0 code.
-*/
-
-/*
- Simple DirectMedia Layer
- Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
-*/
-
-#include "power_windows.h"
-
-// CODE CHUNK IMPORTED FROM SDL 2.0
-
-bool PowerWindows::GetPowerInfo_Windows() {
- SYSTEM_POWER_STATUS status;
- bool need_details = FALSE;
-
- /* This API should exist back to Win95. */
- if (!GetSystemPowerStatus(&status)) {
- /* !!! FIXME: push GetLastError() into GetError() */
- power_state = OS::POWERSTATE_UNKNOWN;
- } else if (status.BatteryFlag == 0xFF) { /* unknown state */
- power_state = OS::POWERSTATE_UNKNOWN;
- } else if (status.BatteryFlag & (1 << 7)) { /* no battery */
- power_state = OS::POWERSTATE_NO_BATTERY;
- } else if (status.BatteryFlag & (1 << 3)) { /* charging */
- power_state = OS::POWERSTATE_CHARGING;
- need_details = TRUE;
- } else if (status.ACLineStatus == 1) {
- power_state = OS::POWERSTATE_CHARGED; /* on AC, not charging. */
- need_details = TRUE;
- } else {
- power_state = OS::POWERSTATE_ON_BATTERY; /* not on AC. */
- need_details = TRUE;
- }
-
- percent_left = -1;
- nsecs_left = -1;
- if (need_details) {
- const int pct = (int)status.BatteryLifePercent;
- const int secs = (int)status.BatteryLifeTime;
-
- if (pct != 255) { /* 255 == unknown */
- percent_left = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
- }
- if (secs != (int)0xFFFFFFFF) { /* ((DWORD)-1) == unknown */
- nsecs_left = secs;
- }
- }
-
- return TRUE; /* always the definitive answer on Windows. */
-}
-
-OS::PowerState PowerWindows::get_power_state() {
- if (GetPowerInfo_Windows()) {
- return power_state;
- } else {
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerWindows::get_power_seconds_left() {
- if (GetPowerInfo_Windows()) {
- return nsecs_left;
- } else {
- return -1;
- }
-}
-
-int PowerWindows::get_power_percent_left() {
- if (GetPowerInfo_Windows()) {
- return percent_left;
- } else {
- return -1;
- }
-}
-
-PowerWindows::PowerWindows() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerWindows::~PowerWindows() {
-}
diff --git a/platform/windows/vulkan_context_win.cpp b/platform/windows/vulkan_context_win.cpp
new file mode 100644
index 0000000000..2c63281c49
--- /dev/null
+++ b/platform/windows/vulkan_context_win.cpp
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* vulkan_context_win.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "vulkan_context_win.h"
+#include <vulkan/vulkan_win32.h>
+
+const char *VulkanContextWindows::_get_platform_surface_extension() const {
+ return VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
+}
+
+int VulkanContextWindows::window_create(DisplayServer::WindowID p_window_id, HWND p_window, HINSTANCE p_instance, int p_width, int p_height) {
+ VkWin32SurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = nullptr;
+ createInfo.flags = 0;
+ createInfo.hinstance = p_instance;
+ createInfo.hwnd = p_window;
+
+ VkSurfaceKHR surface;
+ VkResult err = vkCreateWin32SurfaceKHR(_get_instance(), &createInfo, nullptr, &surface);
+ ERR_FAIL_COND_V(err, -1);
+ return _window_create(p_window_id, surface, p_width, p_height);
+}
+
+VulkanContextWindows::VulkanContextWindows() {
+}
+
+VulkanContextWindows::~VulkanContextWindows() {
+}
diff --git a/platform/windows/power_windows.h b/platform/windows/vulkan_context_win.h
index 80d86a12c5..6e80db0286 100644
--- a/platform/windows/power_windows.h
+++ b/platform/windows/vulkan_context_win.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* power_windows.h */
+/* vulkan_context_win.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,31 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef POWER_WINDOWS_H
-#define POWER_WINDOWS_H
-
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
+#ifndef VULKAN_DEVICE_WIN_H
+#define VULKAN_DEVICE_WIN_H
+#include "drivers/vulkan/vulkan_context.h"
#include <windows.h>
-class PowerWindows {
-
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
-
- bool GetPowerInfo_Windows();
+class VulkanContextWindows : public VulkanContext {
+ virtual const char *_get_platform_surface_extension() const;
public:
- PowerWindows();
- virtual ~PowerWindows();
+ int window_create(DisplayServer::WindowID p_window_id, HWND p_window, HINSTANCE p_instance, int p_width, int p_height);
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
+ VulkanContextWindows();
+ ~VulkanContextWindows();
};
-#endif // POWER_WINDOWS_H
+#endif // VULKAN_DEVICE_WIN_H
diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp
index 8eb6adc27b..0938b65b04 100644
--- a/platform/windows/windows_terminal_logger.cpp
+++ b/platform/windows/windows_terminal_logger.cpp
@@ -49,7 +49,7 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er
len = BUFFER_SIZE; // Output is too big, will be truncated
buf[len] = 0;
- int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0);
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, len, nullptr, 0);
if (wlen < 0)
return;
@@ -81,70 +81,73 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file
StdLogger::log_error(p_function, p_file, p_line, p_code, p_rationale, p_type);
#ifndef UWP_ENABLED
} else {
-
CONSOLE_SCREEN_BUFFER_INFO sbi; //original
GetConsoleScreenBufferInfo(hCon, &sbi);
- WORD current_fg = sbi.wAttributes & (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
WORD current_bg = sbi.wAttributes & (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
uint32_t basecol = 0;
switch (p_type) {
- case ERR_ERROR: basecol = FOREGROUND_RED; break;
- case ERR_WARNING: basecol = FOREGROUND_RED | FOREGROUND_GREEN; break;
- case ERR_SCRIPT: basecol = FOREGROUND_RED | FOREGROUND_BLUE; break;
- case ERR_SHADER: basecol = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
+ case ERR_ERROR:
+ basecol = FOREGROUND_RED;
+ break;
+ case ERR_WARNING:
+ basecol = FOREGROUND_RED | FOREGROUND_GREEN;
+ break;
+ case ERR_SCRIPT:
+ basecol = FOREGROUND_RED | FOREGROUND_BLUE;
+ break;
+ case ERR_SHADER:
+ basecol = FOREGROUND_GREEN | FOREGROUND_BLUE;
+ break;
}
basecol |= current_bg;
- if (p_rationale && p_rationale[0]) {
-
- SetConsoleTextAttribute(hCon, basecol | FOREGROUND_INTENSITY);
- switch (p_type) {
- case ERR_ERROR: logf("ERROR: "); break;
- case ERR_WARNING: logf("WARNING: "); break;
- case ERR_SCRIPT: logf("SCRIPT ERROR: "); break;
- case ERR_SHADER: logf("SHADER ERROR: "); break;
- }
-
- SetConsoleTextAttribute(hCon, current_fg | current_bg | FOREGROUND_INTENSITY);
- logf("%s\n", p_rationale);
+ SetConsoleTextAttribute(hCon, basecol | FOREGROUND_INTENSITY);
+ switch (p_type) {
+ case ERR_ERROR:
+ logf("ERROR:");
+ break;
+ case ERR_WARNING:
+ logf("WARNING:");
+ break;
+ case ERR_SCRIPT:
+ logf("SCRIPT ERROR:");
+ break;
+ case ERR_SHADER:
+ logf("SHADER ERROR:");
+ break;
+ }
- SetConsoleTextAttribute(hCon, basecol);
- switch (p_type) {
- case ERR_ERROR: logf(" At: "); break;
- case ERR_WARNING: logf(" At: "); break;
- case ERR_SCRIPT: logf(" At: "); break;
- case ERR_SHADER: logf(" At: "); break;
- }
+ SetConsoleTextAttribute(hCon, basecol);
+ if (p_rationale && p_rationale[0]) {
+ logf(" %s\n", p_rationale);
+ } else {
+ logf(" %s\n", p_code);
+ }
- SetConsoleTextAttribute(hCon, current_fg | current_bg);
- logf("%s:%i\n", p_file, p_line);
+ // `FOREGROUND_INTENSITY` alone results in gray text.
+ SetConsoleTextAttribute(hCon, FOREGROUND_INTENSITY);
+ switch (p_type) {
+ case ERR_ERROR:
+ logf(" at: ");
+ break;
+ case ERR_WARNING:
+ logf(" at: ");
+ break;
+ case ERR_SCRIPT:
+ logf(" at: ");
+ break;
+ case ERR_SHADER:
+ logf(" at: ");
+ break;
+ }
+ if (p_rationale && p_rationale[0]) {
+ logf("(%s:%i)\n", p_file, p_line);
} else {
-
- SetConsoleTextAttribute(hCon, basecol | FOREGROUND_INTENSITY);
- switch (p_type) {
- case ERR_ERROR: logf("ERROR: %s: ", p_function); break;
- case ERR_WARNING: logf("WARNING: %s: ", p_function); break;
- case ERR_SCRIPT: logf("SCRIPT ERROR: %s: ", p_function); break;
- case ERR_SHADER: logf("SCRIPT ERROR: %s: ", p_function); break;
- }
-
- SetConsoleTextAttribute(hCon, current_fg | current_bg | FOREGROUND_INTENSITY);
- logf("%s\n", p_code);
-
- SetConsoleTextAttribute(hCon, basecol);
- switch (p_type) {
- case ERR_ERROR: logf(" At: "); break;
- case ERR_WARNING: logf(" At: "); break;
- case ERR_SCRIPT: logf(" At: "); break;
- case ERR_SHADER: logf(" At: "); break;
- }
-
- SetConsoleTextAttribute(hCon, current_fg | current_bg);
- logf("%s:%i\n", p_file, p_line);
+ logf("%s (%s:%i)\n", p_function, p_file, p_line);
}
SetConsoleTextAttribute(hCon, sbi.wAttributes);
diff --git a/platform/x11/SCsub b/platform/x11/SCsub
deleted file mode 100644
index 3d5aa15208..0000000000
--- a/platform/x11/SCsub
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-
-Import('env')
-
-from platform_methods import run_in_subprocess
-import platform_x11_builders
-
-common_x11 = [
- "context_gl_x11.cpp",
- "crash_handler_x11.cpp",
- "os_x11.cpp",
- "key_mapping_x11.cpp",
- "joypad_linux.cpp",
- "power_x11.cpp",
- "detect_prime.cpp"
-]
-
-prog = env.add_program('#bin/godot', ['godot_x11.cpp'] + common_x11)
-
-if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]:
- env.AddPostAction(prog, run_in_subprocess(platform_x11_builders.make_debug_x11))
diff --git a/platform/x11/detect.py b/platform/x11/detect.py
deleted file mode 100644
index bd5e5e0812..0000000000
--- a/platform/x11/detect.py
+++ /dev/null
@@ -1,357 +0,0 @@
-import os
-import platform
-import sys
-from methods import get_compiler_version, using_gcc, using_clang
-
-
-def is_active():
- return True
-
-
-def get_name():
- return "X11"
-
-
-def can_build():
-
- if (os.name != "posix" or sys.platform == "darwin"):
- return False
-
- # Check the minimal dependencies
- x11_error = os.system("pkg-config --version > /dev/null")
- if (x11_error):
- return False
-
- x11_error = os.system("pkg-config x11 --modversion > /dev/null ")
- if (x11_error):
- return False
-
- x11_error = os.system("pkg-config xcursor --modversion > /dev/null ")
- if (x11_error):
- print("xcursor not found.. x11 disabled.")
- return False
-
- x11_error = os.system("pkg-config xinerama --modversion > /dev/null ")
- if (x11_error):
- print("xinerama not found.. x11 disabled.")
- return False
-
- x11_error = os.system("pkg-config xrandr --modversion > /dev/null ")
- if (x11_error):
- print("xrandr not found.. x11 disabled.")
- return False
-
- x11_error = os.system("pkg-config xrender --modversion > /dev/null ")
- if (x11_error):
- print("xrender not found.. x11 disabled.")
- return False
-
- x11_error = os.system("pkg-config xi --modversion > /dev/null ")
- if (x11_error):
- print("xi not found.. Aborting.")
- return False
-
- return True
-
-def get_opts():
- from SCons.Variables import BoolVariable, EnumVariable
-
- return [
- BoolVariable('use_llvm', 'Use the LLVM compiler', False),
- BoolVariable('use_lld', 'Use the LLD linker', False),
- BoolVariable('use_thinlto', 'Use ThinLTO', False),
- BoolVariable('use_static_cpp', 'Link libgcc and libstdc++ statically for better portability', False),
- BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False),
- BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False),
- BoolVariable('use_lsan', 'Use LLVM/GCC compiler leak sanitizer (LSAN))', False),
- BoolVariable('use_tsan', 'Use LLVM/GCC compiler thread sanitizer (TSAN))', False),
- BoolVariable('pulseaudio', 'Detect and use PulseAudio', True),
- BoolVariable('udev', 'Use udev for gamepad connection callbacks', False),
- EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')),
- BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False),
- BoolVariable('touch', 'Enable touch events', True),
- BoolVariable('execinfo', 'Use libexecinfo on systems where glibc is not available', False),
- ]
-
-
-def get_flags():
-
- return []
-
-
-def configure(env):
-
- ## Build type
-
- if (env["target"] == "release"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O3'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "release_debug"):
- if (env["optimize"] == "speed"): #optimize for speed (default)
- env.Prepend(CCFLAGS=['-O2'])
- else: #optimize for size
- env.Prepend(CCFLAGS=['-Os'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED'])
-
- if (env["debug_symbols"] == "yes"):
- env.Prepend(CCFLAGS=['-g1'])
- if (env["debug_symbols"] == "full"):
- env.Prepend(CCFLAGS=['-g2'])
-
- elif (env["target"] == "debug"):
- env.Prepend(CCFLAGS=['-g3'])
- env.Prepend(CPPDEFINES=['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED'])
- env.Append(LINKFLAGS=['-rdynamic'])
-
- ## Architecture
-
- is64 = sys.maxsize > 2**32
- if (env["bits"] == "default"):
- env["bits"] = "64" if is64 else "32"
-
- ## Compiler configuration
-
- if 'CXX' in env and 'clang' in os.path.basename(env['CXX']):
- # Convenience check to enforce the use_llvm overrides when CXX is clang(++)
- env['use_llvm'] = True
-
- if env['use_llvm']:
- if ('clang++' not in os.path.basename(env['CXX'])):
- env["CC"] = "clang"
- env["CXX"] = "clang++"
- env["LINK"] = "clang++"
- env.Append(CPPDEFINES=['TYPED_METHOD_BIND'])
- env.extra_suffix = ".llvm" + env.extra_suffix
-
- if env['use_lld']:
- if env['use_llvm']:
- env.Append(LINKFLAGS=['-fuse-ld=lld'])
- if env['use_thinlto']:
- # A convenience so you don't need to write use_lto too when using SCons
- env['use_lto'] = True
- else:
- print("Using LLD with GCC is not supported yet, try compiling with 'use_llvm=yes'.")
- sys.exit(255)
-
- if env['use_ubsan'] or env['use_asan'] or env['use_lsan'] or env['use_tsan']:
- env.extra_suffix += "s"
-
- if env['use_ubsan']:
- env.Append(CCFLAGS=['-fsanitize=undefined'])
- env.Append(LINKFLAGS=['-fsanitize=undefined'])
-
- if env['use_asan']:
- env.Append(CCFLAGS=['-fsanitize=address'])
- env.Append(LINKFLAGS=['-fsanitize=address'])
-
- if env['use_lsan']:
- env.Append(CCFLAGS=['-fsanitize=leak'])
- env.Append(LINKFLAGS=['-fsanitize=leak'])
-
- if env['use_tsan']:
- env.Append(CCFLAGS=['-fsanitize=thread'])
- env.Append(LINKFLAGS=['-fsanitize=thread'])
-
- if env['use_lto']:
- if not env['use_llvm'] and env.GetOption("num_jobs") > 1:
- env.Append(CCFLAGS=['-flto'])
- env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))])
- else:
- if env['use_lld'] and env['use_thinlto']:
- env.Append(CCFLAGS=['-flto=thin'])
- env.Append(LINKFLAGS=['-flto=thin'])
- else:
- env.Append(CCFLAGS=['-flto'])
- env.Append(LINKFLAGS=['-flto'])
-
- if not env['use_llvm']:
- env['RANLIB'] = 'gcc-ranlib'
- env['AR'] = 'gcc-ar'
-
- env.Append(CCFLAGS=['-pipe'])
- env.Append(LINKFLAGS=['-pipe'])
-
- # Check for gcc version >= 6 before adding -no-pie
- if using_gcc(env):
- version = get_compiler_version(env)
- if version != None and version[0] >= '6':
- env.Append(CCFLAGS=['-fpie'])
- env.Append(LINKFLAGS=['-no-pie'])
- # Do the same for clang should be fine with Clang 4 and higher
- if using_clang(env):
- version = get_compiler_version(env)
- if version != None and version[0] >= '4':
- env.Append(CCFLAGS=['-fpie'])
- env.Append(LINKFLAGS=['-no-pie'])
-
- ## Dependencies
-
- env.ParseConfig('pkg-config x11 --cflags --libs')
- env.ParseConfig('pkg-config xcursor --cflags --libs')
- env.ParseConfig('pkg-config xinerama --cflags --libs')
- env.ParseConfig('pkg-config xrandr --cflags --libs')
- env.ParseConfig('pkg-config xrender --cflags --libs')
- env.ParseConfig('pkg-config xi --cflags --libs')
-
- if (env['touch']):
- env.Append(CPPDEFINES=['TOUCH_ENABLED'])
-
- # FIXME: Check for existence of the libs before parsing their flags with pkg-config
-
- # freetype depends on libpng and zlib, so bundling one of them while keeping others
- # as shared libraries leads to weird issues
- if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']:
- env['builtin_freetype'] = True
- env['builtin_libpng'] = True
- env['builtin_zlib'] = True
-
- if not env['builtin_freetype']:
- env.ParseConfig('pkg-config freetype2 --cflags --libs')
-
- if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng16 --cflags --libs')
-
- if not env['builtin_bullet']:
- # We need at least version 2.89
- import subprocess
- bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip()
- if str(bullet_version) < "2.89":
- # Abort as system bullet was requested but too old
- print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.89"))
- sys.exit(255)
- env.ParseConfig('pkg-config bullet --cflags --libs')
-
- if not env['builtin_enet']:
- env.ParseConfig('pkg-config libenet --cflags --libs')
-
- if not env['builtin_squish']:
- env.ParseConfig('pkg-config libsquish --cflags --libs')
-
- if not env['builtin_zstd']:
- env.ParseConfig('pkg-config libzstd --cflags --libs')
-
- # Sound and video libraries
- # Keep the order as it triggers chained dependencies (ogg needed by others, etc.)
-
- if not env['builtin_libtheora']:
- env['builtin_libogg'] = False # Needed to link against system libtheora
- env['builtin_libvorbis'] = False # Needed to link against system libtheora
- env.ParseConfig('pkg-config theora theoradec --cflags --libs')
- else:
- list_of_x86 = ['x86_64', 'x86', 'i386', 'i586']
- if any(platform.machine() in s for s in list_of_x86):
- env["x86_libtheora_opt_gcc"] = True
-
- if not env['builtin_libvpx']:
- env.ParseConfig('pkg-config vpx --cflags --libs')
-
- if not env['builtin_libvorbis']:
- env['builtin_libogg'] = False # Needed to link against system libvorbis
- env.ParseConfig('pkg-config vorbis vorbisfile --cflags --libs')
-
- if not env['builtin_opus']:
- env['builtin_libogg'] = False # Needed to link against system opus
- env.ParseConfig('pkg-config opus opusfile --cflags --libs')
-
- if not env['builtin_libogg']:
- env.ParseConfig('pkg-config ogg --cflags --libs')
-
- if not env['builtin_libwebp']:
- env.ParseConfig('pkg-config libwebp --cflags --libs')
-
- if not env['builtin_mbedtls']:
- # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228
- env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509'])
-
- if not env['builtin_wslay']:
- env.ParseConfig('pkg-config libwslay --cflags --libs')
-
- if not env['builtin_miniupnpc']:
- # No pkgconfig file so far, hardcode default paths.
- env.Prepend(CPPPATH=["/usr/include/miniupnpc"])
- env.Append(LIBS=["miniupnpc"])
-
- # On Linux wchar_t should be 32-bits
- # 16-bit library shouldn't be required due to compiler optimisations
- if not env['builtin_pcre2']:
- env.ParseConfig('pkg-config libpcre2-32 --cflags --libs')
-
- ## Flags
-
- if (os.system("pkg-config --exists alsa") == 0): # 0 means found
- print("Enabling ALSA")
- env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
- # Don't parse --cflags, we don't need to add /usr/include/alsa to include path
- env.ParseConfig('pkg-config alsa --libs')
- else:
- print("ALSA libraries not found, disabling driver")
-
- if env['pulseaudio']:
- if (os.system("pkg-config --exists libpulse") == 0): # 0 means found
- print("Enabling PulseAudio")
- env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
- env.ParseConfig('pkg-config --cflags --libs libpulse')
- else:
- print("PulseAudio development libraries not found, disabling driver")
-
- if (platform.system() == "Linux"):
- env.Append(CPPDEFINES=["JOYDEV_ENABLED"])
-
- if env['udev']:
- if (os.system("pkg-config --exists libudev") == 0): # 0 means found
- print("Enabling udev support")
- env.Append(CPPDEFINES=["UDEV_ENABLED"])
- env.ParseConfig('pkg-config libudev --cflags --libs')
- else:
- print("libudev development libraries not found, disabling udev support")
-
- # Linkflags below this line should typically stay the last ones
- if not env['builtin_zlib']:
- env.ParseConfig('pkg-config zlib --cflags --libs')
-
- env.Prepend(CPPPATH=['#platform/x11'])
- env.Append(CPPDEFINES=['X11_ENABLED', 'UNIX_ENABLED', 'OPENGL_ENABLED', 'GLES_ENABLED'])
- env.Append(LIBS=['GL', 'pthread'])
-
- if (platform.system() == "Linux"):
- env.Append(LIBS=['dl'])
-
- if (platform.system().find("BSD") >= 0):
- env["execinfo"] = True
-
- if env["execinfo"]:
- env.Append(LIBS=['execinfo'])
-
- if not env['tools']:
- import subprocess
- import re
- linker_version_str = subprocess.check_output([env.subst(env["LINK"]), '-Wl,--version']).decode("utf-8")
- gnu_ld_version = re.search('^GNU ld [^$]*(\d+\.\d+)$', linker_version_str, re.MULTILINE)
- if not gnu_ld_version:
- print("Warning: Creating template binaries enabled for PCK embedding is currently only supported with GNU ld")
- else:
- if float(gnu_ld_version.group(1)) >= 2.30:
- env.Append(LINKFLAGS=['-T', 'platform/x11/pck_embed.ld'])
- else:
- env.Append(LINKFLAGS=['-T', 'platform/x11/pck_embed.legacy.ld'])
-
- ## Cross-compilation
-
- if (is64 and env["bits"] == "32"):
- env.Append(CCFLAGS=['-m32'])
- env.Append(LINKFLAGS=['-m32', '-L/usr/lib/i386-linux-gnu'])
- elif (not is64 and env["bits"] == "64"):
- env.Append(CCFLAGS=['-m64'])
- env.Append(LINKFLAGS=['-m64', '-L/usr/lib/i686-linux-gnu'])
-
- # Link those statically for portability
- if env['use_static_cpp']:
- env.Append(LINKFLAGS=['-static-libgcc', '-static-libstdc++'])
diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp
deleted file mode 100644
index e40aa867f8..0000000000
--- a/platform/x11/os_x11.cpp
+++ /dev/null
@@ -1,3510 +0,0 @@
-/*************************************************************************/
-/* os_x11.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "os_x11.h"
-#include "detect_prime.h"
-
-#include "core/os/dir_access.h"
-#include "core/print_string.h"
-#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/gles3/rasterizer_gles3.h"
-#include "errno.h"
-#include "key_mapping_x11.h"
-#include "servers/visual/visual_server_raster.h"
-#include "servers/visual/visual_server_wrap_mt.h"
-
-#ifdef HAVE_MNTENT
-#include <mntent.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "X11/Xutil.h"
-
-#include "X11/Xatom.h"
-#include "X11/extensions/Xinerama.h"
-// ICCCM
-#define WM_NormalState 1L // window normal state
-#define WM_IconicState 3L // window minimized
-// EWMH
-#define _NET_WM_STATE_REMOVE 0L // remove/unset property
-#define _NET_WM_STATE_ADD 1L // add/set property
-#define _NET_WM_STATE_TOGGLE 2L // toggle property
-
-#include "main/main.h"
-
-#include <dlfcn.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-//stupid linux.h
-#ifdef KEY_TAB
-#undef KEY_TAB
-#endif
-
-#include <X11/Xatom.h>
-
-#undef CursorShape
-
-#include <X11/XKBlib.h>
-
-// 2.2 is the first release with multitouch
-#define XINPUT_CLIENT_VERSION_MAJOR 2
-#define XINPUT_CLIENT_VERSION_MINOR 2
-
-#define VALUATOR_ABSX 0
-#define VALUATOR_ABSY 1
-#define VALUATOR_PRESSURE 2
-#define VALUATOR_TILTX 3
-#define VALUATOR_TILTY 4
-
-static const double abs_resolution_mult = 10000.0;
-static const double abs_resolution_range_mult = 10.0;
-
-void OS_X11::initialize_core() {
-
- crash_handler.initialize();
-
- OS_Unix::initialize_core();
-}
-
-int OS_X11::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
- long im_event_mask = 0;
- last_button_state = 0;
-
- xmbstring = NULL;
- x11_window = 0;
- last_click_ms = 0;
- last_click_button_index = -1;
- last_click_pos = Point2(-100, -100);
- args = OS::get_singleton()->get_cmdline_args();
- current_videomode = p_desired;
- main_loop = NULL;
- last_timestamp = 0;
- last_mouse_pos_valid = false;
- last_keyrelease_time = 0;
- xdnd_version = 0;
-
- if (get_render_thread_mode() == RENDER_SEPARATE_THREAD) {
- XInitThreads();
- }
-
- /** XLIB INITIALIZATION **/
- x11_display = XOpenDisplay(NULL);
-
- if (!x11_display) {
- ERR_PRINT("X11 Display is not available");
- return ERR_UNAVAILABLE;
- }
-
- char *modifiers = NULL;
- Bool xkb_dar = False;
- XAutoRepeatOn(x11_display);
- xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, NULL);
-
- // Try to support IME if detectable auto-repeat is supported
- if (xkb_dar == True) {
-
-#ifdef X_HAVE_UTF8_STRING
- // Xutf8LookupString will be used later instead of XmbLookupString before
- // the multibyte sequences can be converted to unicode string.
- modifiers = XSetLocaleModifiers("");
-#endif
- }
-
- if (modifiers == NULL) {
- if (is_stdout_verbose()) {
- WARN_PRINT("IME is disabled");
- }
- XSetLocaleModifiers("@im=none");
- WARN_PRINT("Error setting locale modifiers");
- }
-
- const char *err;
- xrr_get_monitors = NULL;
- xrr_free_monitors = NULL;
- int xrandr_major = 0;
- int xrandr_minor = 0;
- int event_base, error_base;
- xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);
- xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);
- if (!xrandr_handle) {
- err = dlerror();
- fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);
- } else {
- XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
- if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
- xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");
- if (!xrr_get_monitors) {
- err = dlerror();
- fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);
- } else {
- xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");
- if (!xrr_free_monitors) {
- err = dlerror();
- fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);
- xrr_get_monitors = NULL;
- }
- }
- }
- }
-
- if (!refresh_device_info()) {
- OS::get_singleton()->alert("Your system does not support XInput 2.\n"
- "Please upgrade your distribution.",
- "Unable to initialize XInput");
- return ERR_UNAVAILABLE;
- }
-
- xim = XOpenIM(x11_display, NULL, NULL, NULL);
-
- if (xim == NULL) {
- WARN_PRINT("XOpenIM failed");
- xim_style = 0L;
- } else {
- ::XIMCallback im_destroy_callback;
- im_destroy_callback.client_data = (::XPointer)(this);
- im_destroy_callback.callback = (::XIMProc)(xim_destroy_callback);
- if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
- NULL) != NULL) {
- WARN_PRINT("Error setting XIM destroy callback");
- }
-
- ::XIMStyles *xim_styles = NULL;
- xim_style = 0L;
- char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL);
- if (imvalret != NULL || xim_styles == NULL) {
- fprintf(stderr, "Input method doesn't support any styles\n");
- }
-
- if (xim_styles) {
- xim_style = 0L;
- for (int i = 0; i < xim_styles->count_styles; i++) {
-
- if (xim_styles->supported_styles[i] ==
- (XIMPreeditNothing | XIMStatusNothing)) {
-
- xim_style = xim_styles->supported_styles[i];
- break;
- }
- }
-
- XFree(xim_styles);
- }
- XFree(imvalret);
- }
-
-/*
- char* windowid = getenv("GODOT_WINDOWID");
- if (windowid) {
-
- //freopen("/home/punto/stdout", "w", stdout);
- //reopen("/home/punto/stderr", "w", stderr);
- x11_window = atol(windowid);
-
- XWindowAttributes xwa;
- XGetWindowAttributes(x11_display,x11_window,&xwa);
-
- current_videomode.width = xwa.width;
- current_videomode.height = xwa.height;
- };
- */
-
-// maybe contextgl wants to be in charge of creating the window
-#if defined(OPENGL_ENABLED)
- if (getenv("DRI_PRIME") == NULL) {
- int use_prime = -1;
-
- if (getenv("PRIMUS_DISPLAY") ||
- getenv("PRIMUS_libGLd") ||
- getenv("PRIMUS_libGLa") ||
- getenv("PRIMUS_libGL") ||
- getenv("PRIMUS_LOAD_GLOBAL") ||
- getenv("BUMBLEBEE_SOCKET")) {
-
- print_verbose("Optirun/primusrun detected. Skipping GPU detection");
- use_prime = 0;
- }
-
- if (getenv("LD_LIBRARY_PATH")) {
- String ld_library_path(getenv("LD_LIBRARY_PATH"));
- Vector<String> libraries = ld_library_path.split(":");
-
- for (int i = 0; i < libraries.size(); ++i) {
- if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
- FileAccess::exists(libraries[i] + "/libGL.so")) {
-
- print_verbose("Custom libGL override detected. Skipping GPU detection");
- use_prime = 0;
- }
- }
- }
-
- if (use_prime == -1) {
- print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
- use_prime = detect_prime();
- }
-
- if (use_prime) {
- print_line("Found discrete GPU, setting DRI_PRIME=1 to use it.");
- print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
- setenv("DRI_PRIME", "1", 1);
- }
- }
-
- ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_3_0_COMPATIBLE;
-
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
- }
-
- bool editor = Engine::get_singleton()->is_editor_hint();
- bool gl_initialization_error = false;
-
- context_gl = NULL;
- while (!context_gl) {
- context_gl = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, opengl_api_type));
-
- if (context_gl->initialize() != OK) {
- memdelete(context_gl);
- context_gl = NULL;
-
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
- if (p_video_driver == VIDEO_DRIVER_GLES2) {
- gl_initialization_error = true;
- break;
- }
-
- p_video_driver = VIDEO_DRIVER_GLES2;
- opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- while (true) {
- if (opengl_api_type == ContextGL_X11::GLES_3_0_COMPATIBLE) {
- if (RasterizerGLES3::is_viable() == OK) {
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
- break;
- } else {
- if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
- p_video_driver = VIDEO_DRIVER_GLES2;
- opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE;
- continue;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (opengl_api_type == ContextGL_X11::GLES_2_0_COMPATIBLE) {
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- break;
- } else {
- gl_initialization_error = true;
- break;
- }
- }
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
- "Please update your drivers or if you have a very old or integrated GPU upgrade it.",
- "Unable to initialize Video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
-
- context_gl->set_use_vsync(current_videomode.use_vsync);
-
-#endif
-
- visual_server = memnew(VisualServerRaster);
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
- }
-
- if (current_videomode.maximized) {
- current_videomode.maximized = false;
- set_window_maximized(true);
- // borderless fullscreen window mode
- } else if (current_videomode.fullscreen) {
- current_videomode.fullscreen = false;
- set_window_fullscreen(true);
- } else if (current_videomode.borderless_window) {
- Hints hints;
- Atom property;
- hints.flags = 2;
- hints.decorations = 0;
- property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
- }
-
- // make PID known to X11
- {
- const long pid = this->get_process_id();
- Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);
- XChangeProperty(x11_display, x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
- }
-
- // disable resizable window
- if (!current_videomode.resizable && !current_videomode.fullscreen) {
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- xsh->flags = PMinSize | PMaxSize;
- XWindowAttributes xwa;
- if (current_videomode.fullscreen) {
- XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa);
- } else {
- XGetWindowAttributes(x11_display, x11_window, &xwa);
- }
- xsh->min_width = xwa.width;
- xsh->max_width = xwa.width;
- xsh->min_height = xwa.height;
- xsh->max_height = xwa.height;
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
- }
-
- if (current_videomode.always_on_top) {
- current_videomode.always_on_top = false;
- set_window_always_on_top(true);
- }
-
- ERR_FAIL_COND_V(!visual_server, ERR_UNAVAILABLE);
- ERR_FAIL_COND_V(x11_window == 0, ERR_UNAVAILABLE);
-
- XSetWindowAttributes new_attr;
-
- new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
- ButtonReleaseMask | EnterWindowMask |
- LeaveWindowMask | PointerMotionMask |
- Button1MotionMask |
- Button2MotionMask | Button3MotionMask |
- Button4MotionMask | Button5MotionMask |
- ButtonMotionMask | KeymapStateMask |
- ExposureMask | VisibilityChangeMask |
- StructureNotifyMask |
- SubstructureNotifyMask | SubstructureRedirectMask |
- FocusChangeMask | PropertyChangeMask |
- ColormapChangeMask | OwnerGrabButtonMask |
- im_event_mask;
-
- XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);
-
- static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
- static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
-
- xi.all_event_mask.deviceid = XIAllDevices;
- xi.all_event_mask.mask_len = sizeof(all_mask_data);
- xi.all_event_mask.mask = all_mask_data;
-
- xi.all_master_event_mask.deviceid = XIAllMasterDevices;
- xi.all_master_event_mask.mask_len = sizeof(all_master_mask_data);
- xi.all_master_event_mask.mask = all_master_mask_data;
-
- XISetMask(xi.all_event_mask.mask, XI_HierarchyChanged);
- XISetMask(xi.all_master_event_mask.mask, XI_DeviceChanged);
- XISetMask(xi.all_master_event_mask.mask, XI_RawMotion);
-
-#ifdef TOUCH_ENABLED
- if (xi.touch_devices.size()) {
- XISetMask(xi.all_event_mask.mask, XI_TouchBegin);
- XISetMask(xi.all_event_mask.mask, XI_TouchUpdate);
- XISetMask(xi.all_event_mask.mask, XI_TouchEnd);
- XISetMask(xi.all_event_mask.mask, XI_TouchOwnership);
- }
-#endif
-
- XISelectEvents(x11_display, x11_window, &xi.all_event_mask, 1);
- XISelectEvents(x11_display, DefaultRootWindow(x11_display), &xi.all_master_event_mask, 1);
-
- // Disabled by now since grabbing also blocks mouse events
- // (they are received as extended events instead of standard events)
- /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership);
-
- // Grab touch devices to avoid OS gesture interference
- for (int i = 0; i < xi.touch_devices.size(); ++i) {
- XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
- }*/
-
- /* set the titlebar name */
- XStoreName(x11_display, x11_window, "Godot");
-
- wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
- XSetWMProtocols(x11_display, x11_window, &wm_delete, 1);
-
- im_active = false;
- im_position = Vector2();
-
- if (xim && xim_style) {
-
- xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL);
- if (XGetICValues(xic, XNFilterEvents, &im_event_mask, NULL) != NULL) {
- WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
- XDestroyIC(xic);
- xic = NULL;
- }
- if (xic) {
- XUnsetICFocus(xic);
- } else {
- WARN_PRINT("XCreateIC couldn't create xic");
- }
- } else {
-
- xic = NULL;
- WARN_PRINT("XCreateIC couldn't create xic");
- }
-
- cursor_size = XcursorGetDefaultSize(x11_display);
- cursor_theme = XcursorGetTheme(x11_display);
-
- if (!cursor_theme) {
- print_verbose("XcursorGetTheme could not get cursor theme");
- cursor_theme = "default";
- }
-
- for (int i = 0; i < CURSOR_MAX; i++) {
-
- cursors[i] = None;
- img[i] = NULL;
- }
-
- current_cursor = CURSOR_ARROW;
-
- for (int i = 0; i < CURSOR_MAX; i++) {
-
- static const char *cursor_file[] = {
- "left_ptr",
- "xterm",
- "hand2",
- "cross",
- "watch",
- "left_ptr_watch",
- "fleur",
- "hand1",
- "X_cursor",
- "sb_v_double_arrow",
- "sb_h_double_arrow",
- "size_bdiag",
- "size_fdiag",
- "hand1",
- "sb_v_double_arrow",
- "sb_h_double_arrow",
- "question_arrow"
- };
-
- img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);
- if (img[i]) {
- cursors[i] = XcursorImageLoadCursor(x11_display, img[i]);
- } else {
- print_verbose("Failed loading custom cursor: " + String(cursor_file[i]));
- }
- }
-
- {
- // Creating an empty/transparent cursor
-
- // Create 1x1 bitmap
- Pixmap cursormask = XCreatePixmap(x11_display,
- RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
-
- // Fill with zero
- XGCValues xgc;
- xgc.function = GXclear;
- GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
- XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
-
- // Color value doesn't matter. Mask zero means no foreground or background will be drawn
- XColor col = {};
-
- Cursor cursor = XCreatePixmapCursor(x11_display,
- cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)
- cursormask, // mask
- &col, &col, 0, 0);
-
- XFreePixmap(x11_display, cursormask);
- XFreeGC(x11_display, gc);
-
- if (cursor == None) {
- ERR_PRINT("FAILED CREATING CURSOR");
- }
-
- null_cursor = cursor;
- }
- set_cursor_shape(CURSOR_BUSY);
-
- //Set Xdnd (drag & drop) support
- Atom XdndAware = XInternAtom(x11_display, "XdndAware", False);
- Atom version = 5;
- XChangeProperty(x11_display, x11_window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&version, 1);
-
- xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
- xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
- xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
- xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
- xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
- xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
- xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
- requested = None;
-
- visual_server->init();
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
- window_has_focus = true; // Set focus to true at init
-#ifdef JOYDEV_ENABLED
- joypad = memnew(JoypadLinux(input));
-#endif
- _ensure_user_data_dir();
-
- power_manager = memnew(PowerX11);
-
- if (p_desired.layered) {
- set_window_per_pixel_transparency_enabled(true);
- }
-
- XEvent xevent;
- while (XPending(x11_display) > 0) {
- XNextEvent(x11_display, &xevent);
- if (xevent.type == ConfigureNotify) {
- _window_changed(&xevent);
- }
- }
-
- update_real_mouse_position();
-
- return OK;
-}
-
-bool OS_X11::refresh_device_info() {
- int event_base, error_base;
-
- print_verbose("XInput: Refreshing devices.");
-
- if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {
- print_verbose("XInput extension not available. Please upgrade your distribution.");
- return false;
- }
-
- int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;
- int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;
-
- if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {
- print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));
- xi.opcode = 0;
- return false;
- }
-
- if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {
- print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",
- XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));
- }
-
- xi.absolute_devices.clear();
- xi.touch_devices.clear();
-
- int dev_count;
- XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
-
- for (int i = 0; i < dev_count; i++) {
- XIDeviceInfo *dev = &info[i];
- if (!dev->enabled)
- continue;
- if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave))
- continue;
-
- bool direct_touch = false;
- bool absolute_mode = false;
- int resolution_x = 0;
- int resolution_y = 0;
- int range_min_x = 0;
- int range_min_y = 0;
- int range_max_x = 0;
- int range_max_y = 0;
- int pressure_resolution = 0;
- int pressure_min = 0;
- int pressure_max = 0;
- int tilt_resolution_x = 0;
- int tilt_resolution_y = 0;
- int tilt_range_min_x = 0;
- int tilt_range_min_y = 0;
- int tilt_range_max_x = 0;
- int tilt_range_max_y = 0;
- for (int j = 0; j < dev->num_classes; j++) {
-#ifdef TOUCH_ENABLED
- if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
- direct_touch = true;
- }
-#endif
- if (dev->classes[j]->type == XIValuatorClass) {
- XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];
-
- if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) {
- resolution_x = class_info->resolution;
- range_min_x = class_info->min;
- range_max_x = class_info->max;
- absolute_mode = true;
- } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) {
- resolution_y = class_info->resolution;
- range_min_y = class_info->min;
- range_max_y = class_info->max;
- absolute_mode = true;
- } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) {
- pressure_resolution = class_info->resolution;
- pressure_min = class_info->min;
- pressure_max = class_info->max;
- } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) {
- tilt_resolution_x = class_info->resolution;
- tilt_range_min_x = class_info->min;
- tilt_range_max_x = class_info->max;
- } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) {
- tilt_resolution_y = class_info->resolution;
- tilt_range_min_y = class_info->min;
- tilt_range_max_y = class_info->max;
- }
- }
- }
- if (direct_touch) {
- xi.touch_devices.push_back(dev->deviceid);
- print_verbose("XInput: Using touch device: " + String(dev->name));
- }
- if (absolute_mode) {
- // If no resolution was reported, use the min/max ranges.
- if (resolution_x <= 0) {
- resolution_x = (range_max_x - range_min_x) * abs_resolution_range_mult;
- }
- if (resolution_y <= 0) {
- resolution_y = (range_max_y - range_min_y) * abs_resolution_range_mult;
- }
-
- xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);
- print_verbose("XInput: Absolute pointing device: " + String(dev->name));
- }
-
- if (pressure_resolution <= 0) {
- pressure_resolution = (pressure_max - pressure_min);
- }
- if (tilt_resolution_x <= 0) {
- tilt_resolution_x = (tilt_range_max_x - tilt_range_min_x);
- }
- if (tilt_resolution_y <= 0) {
- tilt_resolution_y = (tilt_range_max_y - tilt_range_min_y);
- }
- xi.pressure = 0;
- xi.pen_devices[dev->deviceid] = Vector3(pressure_resolution, tilt_resolution_x, tilt_resolution_y);
- }
-
- XIFreeDeviceInfo(info);
-#ifdef TOUCH_ENABLED
- if (!xi.touch_devices.size()) {
- print_verbose("XInput: No touch devices found.");
- }
-#endif
-
- return true;
-}
-
-void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data,
- ::XPointer call_data) {
-
- WARN_PRINT("Input method stopped");
- OS_X11 *os = reinterpret_cast<OS_X11 *>(client_data);
- os->xim = NULL;
- os->xic = NULL;
-}
-
-void OS_X11::set_ime_active(const bool p_active) {
-
- im_active = p_active;
-
- if (!xic)
- return;
-
- if (p_active) {
- XSetICFocus(xic);
- set_ime_position(im_position);
- } else {
- XUnsetICFocus(xic);
- }
-}
-
-void OS_X11::set_ime_position(const Point2 &p_pos) {
-
- im_position = p_pos;
-
- if (!xic)
- return;
-
- ::XPoint spot;
- spot.x = short(p_pos.x);
- spot.y = short(p_pos.y);
- XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
- XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL);
- XFree(preedit_attr);
-}
-
-String OS_X11::get_unique_id() const {
-
- static String machine_id;
- if (machine_id.empty()) {
- if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) {
- while (machine_id.empty() && !f->eof_reached()) {
- machine_id = f->get_line().strip_edges();
- }
- f->close();
- memdelete(f);
- }
- }
- return machine_id;
-}
-
-void OS_X11::finalize() {
-
- if (main_loop)
- memdelete(main_loop);
- main_loop = NULL;
-
- /*
- if (debugger_connection_console) {
- memdelete(debugger_connection_console);
- }
- */
-#ifdef ALSAMIDI_ENABLED
- driver_alsamidi.close();
-#endif
-
-#ifdef JOYDEV_ENABLED
- memdelete(joypad);
-#endif
-
- xi.touch_devices.clear();
- xi.state.clear();
-
- memdelete(input);
-
- cursors_cache.clear();
- visual_server->finish();
- memdelete(visual_server);
- //memdelete(rasterizer);
-
- memdelete(power_manager);
-
- if (xrandr_handle)
- dlclose(xrandr_handle);
-
- XUnmapWindow(x11_display, x11_window);
- XDestroyWindow(x11_display, x11_window);
-
-#if defined(OPENGL_ENABLED)
- memdelete(context_gl);
-#endif
- for (int i = 0; i < CURSOR_MAX; i++) {
- if (cursors[i] != None)
- XFreeCursor(x11_display, cursors[i]);
- if (img[i] != NULL)
- XcursorImageDestroy(img[i]);
- };
-
- if (xic) {
- XDestroyIC(xic);
- }
- if (xim) {
- XCloseIM(xim);
- }
-
- XCloseDisplay(x11_display);
- if (xmbstring)
- memfree(xmbstring);
-
- args.clear();
-}
-
-void OS_X11::set_mouse_mode(MouseMode p_mode) {
-
- if (p_mode == mouse_mode)
- return;
-
- if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED)
- XUngrabPointer(x11_display, CurrentTime);
-
- // The only modes that show a cursor are VISIBLE and CONFINED
- bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
-
- if (showCursor) {
- XDefineCursor(x11_display, x11_window, cursors[current_cursor]); // show cursor
- } else {
- XDefineCursor(x11_display, x11_window, null_cursor); // hide cursor
- }
-
- mouse_mode = p_mode;
-
- if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) {
-
- //flush pending motion events
- flush_mouse_motion();
-
- if (XGrabPointer(
- x11_display, x11_window, True,
- ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime) != GrabSuccess) {
- ERR_PRINT("NO GRAB");
- }
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- center.x = current_videomode.width / 2;
- center.y = current_videomode.height / 2;
-
- XWarpPointer(x11_display, None, x11_window,
- 0, 0, 0, 0, (int)center.x, (int)center.y);
-
- input->set_mouse_position(center);
- }
- } else {
- do_mouse_warp = false;
- }
-
- XFlush(x11_display);
-}
-
-void OS_X11::warp_mouse_position(const Point2 &p_to) {
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
-
- last_mouse_pos = p_to;
- } else {
-
- /*XWindowAttributes xwa;
- XGetWindowAttributes(x11_display, x11_window, &xwa);
- printf("%d %d\n", xwa.x, xwa.y); needed? */
-
- XWarpPointer(x11_display, None, x11_window,
- 0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
- }
-}
-
-void OS_X11::flush_mouse_motion() {
- while (true) {
- if (XPending(x11_display) > 0) {
- XEvent event;
- XPeekEvent(x11_display, &event);
-
- if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
- XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
-
- if (event_data->evtype == XI_RawMotion) {
- XNextEvent(x11_display, &event);
- } else {
- break;
- }
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- xi.relative_motion.x = 0;
- xi.relative_motion.y = 0;
-}
-
-OS::MouseMode OS_X11::get_mouse_mode() const {
- return mouse_mode;
-}
-
-int OS_X11::get_mouse_button_state() const {
- return last_button_state;
-}
-
-Point2 OS_X11::get_mouse_position() const {
- return last_mouse_pos;
-}
-
-bool OS_X11::get_window_per_pixel_transparency_enabled() const {
-
- if (!is_layered_allowed()) return false;
- return layered_window;
-}
-
-void OS_X11::set_window_per_pixel_transparency_enabled(bool p_enabled) {
-
- if (!is_layered_allowed()) return;
- if (layered_window != p_enabled) {
- if (p_enabled) {
- set_borderless_window(true);
- layered_window = true;
- } else {
- layered_window = false;
- }
- }
-}
-
-void OS_X11::set_window_title(const String &p_title) {
- XStoreName(x11_display, x11_window, p_title.utf8().get_data());
-
- Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
- Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
- XChangeProperty(x11_display, x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
-}
-
-void OS_X11::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-}
-
-OS::VideoMode OS_X11::get_video_mode(int p_screen) const {
- return current_videomode;
-}
-
-void OS_X11::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-}
-
-void OS_X11::set_wm_fullscreen(bool p_enabled) {
- if (p_enabled && !get_borderless_window()) {
- // remove decorations if the window is not already borderless
- Hints hints;
- Atom property;
- hints.flags = 2;
- hints.decorations = 0;
- property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
- }
-
- if (p_enabled && !is_window_resizable()) {
- // Set the window as resizable to prevent window managers to ignore the fullscreen state flag.
- XSizeHints *xsh;
-
- xsh = XAllocSizeHints();
- xsh->flags = 0L;
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
- }
-
- // Using EWMH -- Extended Window Manager Hints
- XEvent xev;
- Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = wm_state;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
- xev.xclient.data.l[1] = wm_fullscreen;
- xev.xclient.data.l[2] = 0;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
-
- // set bypass compositor hint
- Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
- unsigned long compositing_disable_on = p_enabled ? 1 : 0;
- XChangeProperty(x11_display, x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
-
- XFlush(x11_display);
-
- if (!p_enabled) {
- // Reset the non-resizable flags if we un-set these before.
- Size2 size = get_window_size();
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- if (!is_window_resizable()) {
- xsh->flags = PMinSize | PMaxSize;
- xsh->min_width = size.x;
- xsh->max_width = size.x;
- xsh->min_height = size.y;
- xsh->max_height = size.y;
- } else {
- xsh->flags = 0L;
- if (min_size != Size2()) {
- xsh->flags |= PMinSize;
- xsh->min_width = min_size.x;
- xsh->min_height = min_size.y;
- }
- if (max_size != Size2()) {
- xsh->flags |= PMaxSize;
- xsh->max_width = max_size.x;
- xsh->max_height = max_size.y;
- }
- }
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
-
- // put back or remove decorations according to the last set borderless state
- Hints hints;
- Atom property;
- hints.flags = 2;
- hints.decorations = current_videomode.borderless_window ? 0 : 1;
- property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
- }
-}
-
-void OS_X11::set_wm_above(bool p_enabled) {
- Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
-
- XClientMessageEvent xev;
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.window = x11_window;
- xev.message_type = wm_state;
- xev.format = 32;
- xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
- xev.data.l[1] = wm_above;
- xev.data.l[3] = 1;
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
-}
-
-int OS_X11::get_screen_count() const {
- // Using Xinerama Extension
- int event_base, error_base;
- const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
- if (!ext_okay) return 0;
-
- int count;
- XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
- XFree(xsi);
- return count;
-}
-
-int OS_X11::get_current_screen() const {
- int x, y;
- Window child;
- XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
-
- int count = get_screen_count();
- for (int i = 0; i < count; i++) {
- Point2i pos = get_screen_position(i);
- Size2i size = get_screen_size(i);
- if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height))
- return i;
- }
- return 0;
-}
-
-void OS_X11::set_current_screen(int p_screen) {
- int count = get_screen_count();
- if (p_screen >= count) return;
-
- if (current_videomode.fullscreen) {
- Point2i position = get_screen_position(p_screen);
- Size2i size = get_screen_size(p_screen);
-
- XMoveResizeWindow(x11_display, x11_window, position.x, position.y, size.x, size.y);
- } else {
- if (p_screen != get_current_screen()) {
- Point2i position = get_screen_position(p_screen);
- XMoveWindow(x11_display, x11_window, position.x, position.y);
- }
- }
-}
-
-Point2 OS_X11::get_screen_position(int p_screen) const {
- if (p_screen == -1) {
- p_screen = get_current_screen();
- }
-
- // Using Xinerama Extension
- int event_base, error_base;
- const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
- if (!ext_okay) {
- return Point2i(0, 0);
- }
-
- int count;
- XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
- if (p_screen >= count) {
- return Point2i(0, 0);
- }
-
- Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org);
-
- XFree(xsi);
-
- return position;
-}
-
-Size2 OS_X11::get_screen_size(int p_screen) const {
- if (p_screen == -1) {
- p_screen = get_current_screen();
- }
-
- // Using Xinerama Extension
- int event_base, error_base;
- const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
- if (!ext_okay) return Size2i(0, 0);
-
- int count;
- XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
- if (p_screen >= count) return Size2i(0, 0);
-
- Size2i size = Point2i(xsi[p_screen].width, xsi[p_screen].height);
- XFree(xsi);
- return size;
-}
-
-int OS_X11::get_screen_dpi(int p_screen) const {
- if (p_screen == -1) {
- p_screen = get_current_screen();
- }
-
- //invalid screen?
- ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0);
-
- //Get physical monitor Dimensions through XRandR and calculate dpi
- Size2 sc = get_screen_size(p_screen);
- if (xrandr_ext_ok) {
- int count = 0;
- if (xrr_get_monitors) {
- xrr_monitor_info *monitors = xrr_get_monitors(x11_display, x11_window, true, &count);
- if (p_screen < count) {
- double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;
- double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;
- xrr_free_monitors(monitors);
- return (xdpi + ydpi) / 2;
- }
- xrr_free_monitors(monitors);
- } else if (p_screen == 0) {
- XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);
- if (sizes) {
- double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;
- double ydpi = sc.height / (double)sizes[0].mheight * 25.4;
- return (xdpi + ydpi) / 2;
- }
- }
- }
-
- int width_mm = DisplayWidthMM(x11_display, p_screen);
- int height_mm = DisplayHeightMM(x11_display, p_screen);
- double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);
- double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);
- if (xdpi || ydpi)
- return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
-
- //could not get dpi
- return 96;
-}
-
-Point2 OS_X11::get_window_position() const {
- int x, y;
- Window child;
- XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
- return Point2i(x, y);
-}
-
-void OS_X11::set_window_position(const Point2 &p_position) {
- int x = 0;
- int y = 0;
- if (!get_borderless_window()) {
- //exclude window decorations
- XSync(x11_display, False);
- Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
- if (prop != None) {
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
- if (format == 32 && len == 4) {
- long *extents = (long *)data;
- x = extents[0];
- y = extents[2];
- }
- XFree(data);
- }
- }
- }
- XMoveWindow(x11_display, x11_window, p_position.x - x, p_position.y - y);
- update_real_mouse_position();
-}
-
-Size2 OS_X11::get_window_size() const {
- // Use current_videomode width and height instead of XGetWindowAttributes
- // since right after a XResizeWindow the attributes may not be updated yet
- return Size2i(current_videomode.width, current_videomode.height);
-}
-
-Size2 OS_X11::get_real_window_size() const {
- XWindowAttributes xwa;
- XSync(x11_display, False);
- XGetWindowAttributes(x11_display, x11_window, &xwa);
- int w = xwa.width;
- int h = xwa.height;
- Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
- if (prop != None) {
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
- if (format == 32 && len == 4) {
- long *extents = (long *)data;
- w += extents[0] + extents[1]; // left, right
- h += extents[2] + extents[3]; // top, bottom
- }
- XFree(data);
- }
- }
- return Size2(w, h);
-}
-
-Size2 OS_X11::get_max_window_size() const {
- return max_size;
-}
-
-Size2 OS_X11::get_min_window_size() const {
- return min_size;
-}
-
-void OS_X11::set_min_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
- ERR_PRINT("Minimum window size can't be larger than maximum window size!");
- return;
- }
- min_size = p_size;
-
- if (is_window_resizable()) {
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- xsh->flags = 0L;
- if (min_size != Size2()) {
- xsh->flags |= PMinSize;
- xsh->min_width = min_size.x;
- xsh->min_height = min_size.y;
- }
- if (max_size != Size2()) {
- xsh->flags |= PMaxSize;
- xsh->max_width = max_size.x;
- xsh->max_height = max_size.y;
- }
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
-
- XFlush(x11_display);
- }
-}
-
-void OS_X11::set_max_window_size(const Size2 p_size) {
-
- if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
- ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
- return;
- }
- max_size = p_size;
-
- if (is_window_resizable()) {
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- xsh->flags = 0L;
- if (min_size != Size2()) {
- xsh->flags |= PMinSize;
- xsh->min_width = min_size.x;
- xsh->min_height = min_size.y;
- }
- if (max_size != Size2()) {
- xsh->flags |= PMaxSize;
- xsh->max_width = max_size.x;
- xsh->max_height = max_size.y;
- }
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
-
- XFlush(x11_display);
- }
-}
-
-void OS_X11::set_window_size(const Size2 p_size) {
-
- if (current_videomode.width == p_size.width && current_videomode.height == p_size.height)
- return;
-
- XWindowAttributes xwa;
- XSync(x11_display, False);
- XGetWindowAttributes(x11_display, x11_window, &xwa);
- int old_w = xwa.width;
- int old_h = xwa.height;
-
- // If window resizable is disabled we need to update the attributes first
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- if (!is_window_resizable()) {
- xsh->flags = PMinSize | PMaxSize;
- xsh->min_width = p_size.x;
- xsh->max_width = p_size.x;
- xsh->min_height = p_size.y;
- xsh->max_height = p_size.y;
- } else {
- xsh->flags = 0L;
- if (min_size != Size2()) {
- xsh->flags |= PMinSize;
- xsh->min_width = min_size.x;
- xsh->min_height = min_size.y;
- }
- if (max_size != Size2()) {
- xsh->flags |= PMaxSize;
- xsh->max_width = max_size.x;
- xsh->max_height = max_size.y;
- }
- }
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
-
- // Resize the window
- XResizeWindow(x11_display, x11_window, p_size.x, p_size.y);
-
- // Update our videomode width and height
- current_videomode.width = p_size.x;
- current_videomode.height = p_size.y;
-
- for (int timeout = 0; timeout < 50; ++timeout) {
- XSync(x11_display, False);
- XGetWindowAttributes(x11_display, x11_window, &xwa);
-
- if (old_w != xwa.width || old_h != xwa.height)
- break;
-
- usleep(10000);
- }
-}
-
-void OS_X11::set_window_fullscreen(bool p_enabled) {
-
- if (current_videomode.fullscreen == p_enabled)
- return;
-
- if (layered_window)
- set_window_per_pixel_transparency_enabled(false);
-
- if (p_enabled && current_videomode.always_on_top) {
- // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
- set_window_maximized(true);
- }
- set_wm_fullscreen(p_enabled);
- if (!p_enabled && current_videomode.always_on_top) {
- // Restore
- set_window_maximized(false);
- }
- if (!p_enabled) {
- set_window_position(last_position_before_fs);
- } else {
- last_position_before_fs = get_window_position();
- }
- current_videomode.fullscreen = p_enabled;
-}
-
-bool OS_X11::is_window_fullscreen() const {
- return current_videomode.fullscreen;
-}
-
-void OS_X11::set_window_resizable(bool p_enabled) {
-
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
- if (!p_enabled) {
- Size2 size = get_window_size();
-
- xsh->flags = PMinSize | PMaxSize;
- xsh->min_width = size.x;
- xsh->max_width = size.x;
- xsh->min_height = size.y;
- xsh->max_height = size.y;
- } else {
- xsh->flags = 0L;
- if (min_size != Size2()) {
- xsh->flags |= PMinSize;
- xsh->min_width = min_size.x;
- xsh->min_height = min_size.y;
- }
- if (max_size != Size2()) {
- xsh->flags |= PMaxSize;
- xsh->max_width = max_size.x;
- xsh->max_height = max_size.y;
- }
- }
-
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
-
- current_videomode.resizable = p_enabled;
-
- XFlush(x11_display);
-}
-
-bool OS_X11::is_window_resizable() const {
- return current_videomode.resizable;
-}
-
-void OS_X11::set_window_minimized(bool p_enabled) {
- // Using ICCCM -- Inter-Client Communication Conventions Manual
- XEvent xev;
- Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = wm_change;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
-
- Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = wm_state;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
- xev.xclient.data.l[1] = wm_hidden;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
-}
-
-bool OS_X11::is_window_minimized() const {
- // Using ICCCM -- Inter-Client Communication Conventions Manual
- Atom property = XInternAtom(x11_display, "WM_STATE", True);
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
-
- int result = XGetWindowProperty(
- x11_display,
- x11_window,
- property,
- 0,
- 32,
- False,
- AnyPropertyType,
- &type,
- &format,
- &len,
- &remaining,
- &data);
-
- if (result == Success) {
- long *state = (long *)data;
- if (state[0] == WM_IconicState)
- return true;
- }
- return false;
-}
-
-void OS_X11::set_window_maximized(bool p_enabled) {
- if (is_window_maximized() == p_enabled)
- return;
-
- // Using EWMH -- Extended Window Manager Hints
- XEvent xev;
- Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
- Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = wm_state;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
- xev.xclient.data.l[1] = wm_max_horz;
- xev.xclient.data.l[2] = wm_max_vert;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
-
- if (p_enabled && is_window_maximize_allowed()) {
- // Wait for effective resizing (so the GLX context is too).
- // Give up after 0.5s, it's not going to happen on this WM.
- // https://github.com/godotengine/godot/issues/19978
- for (int attempt = 0; !is_window_maximized() && attempt < 50; attempt++) {
- usleep(10000);
- }
- }
-
- maximized = p_enabled;
-}
-
-bool OS_X11::is_window_maximize_allowed() {
- Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False);
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
-
- int result = XGetWindowProperty(
- x11_display,
- x11_window,
- property,
- 0,
- 1024,
- False,
- XA_ATOM,
- &type,
- &format,
- &len,
- &remaining,
- &data);
-
- if (result == Success) {
- Atom *atoms = (Atom *)data;
- Atom wm_act_max_horz = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False);
- Atom wm_act_max_vert = XInternAtom(x11_display, "_NET_WM_ACTION_MAXIMIZE_VERT", False);
- bool found_wm_act_max_horz = false;
- bool found_wm_act_max_vert = false;
-
- for (uint64_t i = 0; i < len; i++) {
- if (atoms[i] == wm_act_max_horz)
- found_wm_act_max_horz = true;
- if (atoms[i] == wm_act_max_vert)
- found_wm_act_max_vert = true;
-
- if (found_wm_act_max_horz || found_wm_act_max_vert)
- return true;
- }
- XFree(atoms);
- }
-
- return false;
-}
-
-bool OS_X11::is_window_maximized() const {
- // Using EWMH -- Extended Window Manager Hints
- Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- bool retval = false;
-
- int result = XGetWindowProperty(
- x11_display,
- x11_window,
- property,
- 0,
- 1024,
- False,
- XA_ATOM,
- &type,
- &format,
- &len,
- &remaining,
- &data);
-
- if (result == Success) {
- Atom *atoms = (Atom *)data;
- Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
- Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
- bool found_wm_max_horz = false;
- bool found_wm_max_vert = false;
-
- for (uint64_t i = 0; i < len; i++) {
- if (atoms[i] == wm_max_horz)
- found_wm_max_horz = true;
- if (atoms[i] == wm_max_vert)
- found_wm_max_vert = true;
-
- if (found_wm_max_horz && found_wm_max_vert) {
- retval = true;
- break;
- }
- }
- }
-
- XFree(data);
- return retval;
-}
-
-void OS_X11::set_window_always_on_top(bool p_enabled) {
- if (is_window_always_on_top() == p_enabled)
- return;
-
- if (p_enabled && current_videomode.fullscreen) {
- // Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
- set_window_maximized(true);
- }
- set_wm_above(p_enabled);
- if (!p_enabled && !current_videomode.fullscreen) {
- // Restore
- set_window_maximized(false);
- }
-
- current_videomode.always_on_top = p_enabled;
-}
-
-bool OS_X11::is_window_always_on_top() const {
- return current_videomode.always_on_top;
-}
-
-bool OS_X11::is_window_focused() const {
- return window_focused;
-}
-
-void OS_X11::set_borderless_window(bool p_borderless) {
-
- if (get_borderless_window() == p_borderless)
- return;
-
- if (!p_borderless && layered_window)
- set_window_per_pixel_transparency_enabled(false);
-
- current_videomode.borderless_window = p_borderless;
-
- Hints hints;
- Atom property;
- hints.flags = 2;
- hints.decorations = current_videomode.borderless_window ? 0 : 1;
- property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
-
- // Preserve window size
- set_window_size(Size2(current_videomode.width, current_videomode.height));
-}
-
-bool OS_X11::get_borderless_window() {
-
- bool borderless = current_videomode.borderless_window;
- Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- if (prop != None) {
-
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- if (XGetWindowProperty(x11_display, x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
- if (data && (format == 32) && (len >= 5)) {
- borderless = !((Hints *)data)->decorations;
- }
- XFree(data);
- }
- }
- return borderless;
-}
-
-void OS_X11::request_attention() {
- // Using EWMH -- Extended Window Manager Hints
- //
- // Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
- // Will be unset by the window manager after user react on the request for attention
-
- XEvent xev;
- Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
- Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = wm_state;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
- xev.xclient.data.l[1] = wm_attention;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
- XFlush(x11_display);
-}
-
-void OS_X11::get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) {
-
- state->set_shift((p_x11_state & ShiftMask));
- state->set_control((p_x11_state & ControlMask));
- state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt
- state->set_metakey((p_x11_state & Mod4Mask));
-}
-
-unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) {
-
- unsigned int mask = 1 << (p_x11_button - 1);
-
- if (p_x11_type == ButtonPress) {
- last_button_state |= mask;
- } else {
- last_button_state &= ~mask;
- }
-
- return last_button_state;
-}
-
-void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
-
- // X11 functions don't know what const is
- XKeyEvent *xkeyevent = p_event;
-
- // This code was pretty difficult to write.
- // The docs stink and every toolkit seems to
- // do it in a different way.
-
- /* Phase 1, obtain a proper keysym */
-
- // This was also very difficult to figure out.
- // You'd expect you could just use Keysym provided by
- // XKeycodeToKeysym to obtain internationalized
- // input.. WRONG!!
- // you must use XLookupString (???) which not only wastes
- // cycles generating an unnecessary string, but also
- // still works in half the cases. (won't handle deadkeys)
- // For more complex input methods (deadkeys and more advanced)
- // you have to use XmbLookupString (??).
- // So.. then you have to chosse which of both results
- // you want to keep.
- // This is a real bizarreness and cpu waster.
-
- KeySym keysym_keycode = 0; // keysym used to find a keycode
- KeySym keysym_unicode = 0; // keysym used to find unicode
-
- // XLookupString returns keysyms usable as nice scancodes/
- char str[256 + 1];
- XKeyEvent xkeyevent_no_mod = *xkeyevent;
- xkeyevent_no_mod.state &= ~ShiftMask;
- xkeyevent_no_mod.state &= ~ControlMask;
- XLookupString(xkeyevent, str, 256, &keysym_unicode, NULL);
- XLookupString(&xkeyevent_no_mod, NULL, 0, &keysym_keycode, NULL);
-
- // Meanwhile, XLookupString returns keysyms useful for unicode.
-
- if (!xmbstring) {
- // keep a temporary buffer for the string
- xmbstring = (char *)memalloc(sizeof(char) * 8);
- xmblen = 8;
- }
-
- if (xkeyevent->type == KeyPress && xic) {
-
- Status status;
-#ifdef X_HAVE_UTF8_STRING
- int utf8len = 8;
- char *utf8string = (char *)memalloc(sizeof(char) * utf8len);
- int utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
- utf8len - 1, &keysym_unicode, &status);
- if (status == XBufferOverflow) {
- utf8len = utf8bytes + 1;
- utf8string = (char *)memrealloc(utf8string, utf8len);
- utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
- utf8len - 1, &keysym_unicode, &status);
- }
- utf8string[utf8bytes] = '\0';
-
- if (status == XLookupChars) {
- bool keypress = xkeyevent->type == KeyPress;
- unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
- if (keycode >= 'a' && keycode <= 'z')
- keycode -= 'a' - 'A';
-
- String tmp;
- tmp.parse_utf8(utf8string, utf8bytes);
- for (int i = 0; i < tmp.length(); i++) {
- Ref<InputEventKey> k;
- k.instance();
- if (keycode == 0 && tmp[i] == 0) {
- continue;
- }
-
- get_key_modifier_state(xkeyevent->state, k);
-
- k->set_unicode(tmp[i]);
-
- k->set_pressed(keypress);
-
- k->set_scancode(keycode);
-
- k->set_echo(false);
-
- if (k->get_scancode() == KEY_BACKTAB) {
- //make it consistent across platforms.
- k->set_scancode(KEY_TAB);
- k->set_shift(true);
- }
-
- input->accumulate_input_event(k);
- }
- memfree(utf8string);
- return;
- }
- memfree(utf8string);
-#else
- do {
-
- int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
- xmbstring[mnbytes] = '\0';
-
- if (status == XBufferOverflow) {
- xmblen = mnbytes + 1;
- xmbstring = (char *)memrealloc(xmbstring, xmblen);
- }
- } while (status == XBufferOverflow);
-#endif
- }
-
- /* Phase 2, obtain a pigui keycode from the keysym */
-
- // KeyMappingX11 just translated the X11 keysym to a PIGUI
- // keysym, so it works in all platforms the same.
-
- unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
-
- /* Phase 3, obtain a unicode character from the keysym */
-
- // KeyMappingX11 also translates keysym to unicode.
- // It does a binary search on a table to translate
- // most properly.
- unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;
-
- /* Phase 4, determine if event must be filtered */
-
- // This seems to be a side-effect of using XIM.
- // XEventFilter looks like a core X11 function,
- // but it's actually just used to see if we must
- // ignore a deadkey, or events XIM determines
- // must not reach the actual gui.
- // Guess it was a design problem of the extension
-
- bool keypress = xkeyevent->type == KeyPress;
-
- if (keycode == 0 && unicode == 0)
- return;
-
- /* Phase 5, determine modifier mask */
-
- // No problems here, except I had no way to
- // know Mod1 was ALT and Mod4 was META (applekey/winkey)
- // just tried Mods until i found them.
-
- //print_verbose("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
-
- Ref<InputEventKey> k;
- k.instance();
-
- get_key_modifier_state(xkeyevent->state, k);
-
- /* Phase 6, determine echo character */
-
- // Echo characters in X11 are a keyrelease and a keypress
- // one after the other with the (almot) same timestamp.
- // To detect them, i use XPeekEvent and check that their
- // difference in time is below a threshold.
-
- if (xkeyevent->type != KeyPress) {
-
- p_echo = false;
-
- // make sure there are events pending,
- // so this call won't block.
- if (XPending(x11_display) > 0) {
- XEvent peek_event;
- XPeekEvent(x11_display, &peek_event);
-
- // I'm using a threshold of 5 msecs,
- // since sometimes there seems to be a little
- // jitter. I'm still not convinced that all this approach
- // is correct, but the xorg developers are
- // not very helpful today.
-
- ::Time tresh = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
- if (peek_event.type == KeyPress && tresh < 5) {
- KeySym rk;
- XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL);
- if (rk == keysym_keycode) {
- XEvent event;
- XNextEvent(x11_display, &event); //erase next event
- handle_key_event((XKeyEvent *)&event, true);
- return; //ignore current, echo next
- }
- }
-
- // use the time from peek_event so it always works
- }
-
- // save the time to check for echo when keypress happens
- }
-
- /* Phase 7, send event to Window */
-
- k->set_pressed(keypress);
-
- if (keycode >= 'a' && keycode <= 'z')
- keycode -= 'a' - 'A';
-
- k->set_scancode(keycode);
- k->set_unicode(unicode);
- k->set_echo(p_echo);
-
- if (k->get_scancode() == KEY_BACKTAB) {
- //make it consistent across platforms.
- k->set_scancode(KEY_TAB);
- k->set_shift(true);
- }
-
- //don't set mod state if modifier keys are released by themselves
- //else event.is_action() will not work correctly here
- if (!k->is_pressed()) {
- if (k->get_scancode() == KEY_SHIFT)
- k->set_shift(false);
- else if (k->get_scancode() == KEY_CONTROL)
- k->set_control(false);
- else if (k->get_scancode() == KEY_ALT)
- k->set_alt(false);
- else if (k->get_scancode() == KEY_META)
- k->set_metakey(false);
- }
-
- bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_scancode());
- if (k->is_pressed()) {
- if (last_is_pressed) {
- k->set_echo(true);
- }
- }
-
- //printf("key: %x\n",k->get_scancode());
- input->accumulate_input_event(k);
-}
-
-struct Property {
- unsigned char *data;
- int format, nitems;
- Atom type;
-};
-
-static Property read_property(Display *p_display, Window p_window, Atom p_property) {
-
- Atom actual_type;
- int actual_format;
- unsigned long nitems;
- unsigned long bytes_after;
- unsigned char *ret = 0;
-
- int read_bytes = 1024;
-
- //Keep trying to read the property until there are no
- //bytes unread.
- do {
- if (ret != 0)
- XFree(ret);
-
- XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
- &actual_type, &actual_format, &nitems, &bytes_after,
- &ret);
-
- read_bytes *= 2;
-
- } while (bytes_after != 0);
-
- Property p = { ret, actual_format, (int)nitems, actual_type };
-
- return p;
-}
-
-static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) {
-
- static const char *target_type = "text/uri-list";
-
- for (int i = 0; i < p_count; i++) {
-
- Atom atom = p_list[i];
-
- if (atom != None && String(XGetAtomName(p_display, atom)) == target_type)
- return atom;
- }
- return None;
-}
-
-static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
-
- static const char *target_type = "text/uri-list";
- if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type)
- return p_t1;
-
- if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type)
- return p_t2;
-
- if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type)
- return p_t3;
-
- return None;
-}
-
-void OS_X11::_window_changed(XEvent *event) {
-
- if (xic) {
- // Not portable.
- set_ime_position(Point2(0, 1));
- }
- if ((event->xconfigure.width == current_videomode.width) &&
- (event->xconfigure.height == current_videomode.height))
- return;
-
- current_videomode.width = event->xconfigure.width;
- current_videomode.height = event->xconfigure.height;
-}
-
-void OS_X11::process_xevents() {
-
- //printf("checking events %i\n", XPending(x11_display));
-
- do_mouse_warp = false;
-
- // Is the current mouse mode one where it needs to be grabbed.
- bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED;
-
- while (XPending(x11_display) > 0) {
- XEvent event;
- XNextEvent(x11_display, &event);
-
- if (XFilterEvent(&event, None)) {
- continue;
- }
-
- if (XGetEventData(x11_display, &event.xcookie)) {
-
- if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
-
- XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
- int index = event_data->detail;
- Vector2 pos = Vector2(event_data->event_x, event_data->event_y);
-
- switch (event_data->evtype) {
- case XI_HierarchyChanged:
- case XI_DeviceChanged: {
- refresh_device_info();
- } break;
- case XI_RawMotion: {
- XIRawEvent *raw_event = (XIRawEvent *)event_data;
- int device_id = raw_event->deviceid;
-
- // Determine the axis used (called valuators in XInput for some forsaken reason)
- // Mask is a bitmask indicating which axes are involved.
- // We are interested in the values of axes 0 and 1.
- if (raw_event->valuators.mask_len <= 0) {
- break;
- }
-
- const double *values = raw_event->raw_values;
-
- double rel_x = 0.0;
- double rel_y = 0.0;
- double pressure = 0.0;
- double tilt_x = 0.0;
- double tilt_y = 0.0;
-
- if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) {
- rel_x = *values;
- values++;
- }
-
- if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) {
- rel_y = *values;
- values++;
- }
-
- if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) {
- pressure = *values;
- values++;
- }
-
- if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) {
- tilt_x = *values;
- values++;
- }
-
- if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) {
- tilt_y = *values;
- }
-
- Map<int, Vector3>::Element *pen_info = xi.pen_devices.find(device_id);
- if (pen_info) {
- Vector3 mult = pen_info->value();
- if (mult.x != 0.0) xi.pressure = pressure / mult.x;
- if ((mult.y != 0.0) && (mult.z != 0.0)) xi.tilt = Vector2(tilt_x / mult.y, tilt_y / mult.z);
- }
-
- // https://bugs.freedesktop.org/show_bug.cgi?id=71609
- // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html
- if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {
- break; // Flush duplicate to avoid overly fast motion
- }
-
- xi.old_raw_pos.x = xi.raw_pos.x;
- xi.old_raw_pos.y = xi.raw_pos.y;
- xi.raw_pos.x = rel_x;
- xi.raw_pos.y = rel_y;
-
- Map<int, Vector2>::Element *abs_info = xi.absolute_devices.find(device_id);
-
- if (abs_info) {
- // Absolute mode device
- Vector2 mult = abs_info->value();
-
- xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;
- xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;
- } else {
- // Relative mode device
- xi.relative_motion.x = xi.raw_pos.x;
- xi.relative_motion.y = xi.raw_pos.y;
- }
-
- xi.last_relative_time = raw_event->time;
- } break;
-#ifdef TOUCH_ENABLED
- case XI_TouchBegin: // Fall-through
- // Disabled hand-in-hand with the grabbing
- //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
-
- case XI_TouchEnd: {
-
- bool is_begin = event_data->evtype == XI_TouchBegin;
-
- Ref<InputEventScreenTouch> st;
- st.instance();
- st->set_index(index);
- st->set_position(pos);
- st->set_pressed(is_begin);
-
- if (is_begin) {
- if (xi.state.has(index)) // Defensive
- break;
- xi.state[index] = pos;
- if (xi.state.size() == 1) {
- // X11 may send a motion event when a touch gesture begins, that would result
- // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out
- xi.mouse_pos_to_filter = pos;
- }
- input->accumulate_input_event(st);
- } else {
- if (!xi.state.has(index)) // Defensive
- break;
- xi.state.erase(index);
- input->accumulate_input_event(st);
- }
- } break;
-
- case XI_TouchUpdate: {
-
- Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index);
- if (!curr_pos_elem) { // Defensive
- break;
- }
-
- if (curr_pos_elem->value() != pos) {
-
- Ref<InputEventScreenDrag> sd;
- sd.instance();
- sd->set_index(index);
- sd->set_position(pos);
- sd->set_relative(pos - curr_pos_elem->value());
- input->accumulate_input_event(sd);
-
- curr_pos_elem->value() = pos;
- }
- } break;
-#endif
- }
- }
- }
- XFreeEventData(x11_display, &event.xcookie);
-
- switch (event.type) {
- case Expose:
- Main::force_redraw();
- break;
-
- case NoExpose:
- minimized = true;
- break;
-
- case VisibilityNotify: {
- XVisibilityEvent *visibility = (XVisibilityEvent *)&event;
- minimized = (visibility->state == VisibilityFullyObscured);
- } break;
- case LeaveNotify: {
- if (main_loop && !mouse_mode_grab)
- main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
-
- } break;
- case EnterNotify: {
- if (main_loop && !mouse_mode_grab)
- main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
- } break;
- case FocusIn:
- minimized = false;
- window_has_focus = true;
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
- window_focused = true;
-
- if (mouse_mode_grab) {
- // Show and update the cursor if confined and the window regained focus.
- if (mouse_mode == MOUSE_MODE_CONFINED)
- XUndefineCursor(x11_display, x11_window);
- else if (mouse_mode == MOUSE_MODE_CAPTURED) // or re-hide it in captured mode
- XDefineCursor(x11_display, x11_window, null_cursor);
-
- XGrabPointer(
- x11_display, x11_window, True,
- ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime);
- }
-#ifdef TOUCH_ENABLED
- // Grab touch devices to avoid OS gesture interference
- /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
- XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
- }*/
-#endif
- if (xic) {
- XSetICFocus(xic);
- }
- break;
-
- case FocusOut:
- window_has_focus = false;
- input->release_pressed_events();
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
- window_focused = false;
-
- if (mouse_mode_grab) {
- //dear X11, I try, I really try, but you never work, you do whathever you want.
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- // Show the cursor if we're in captured mode so it doesn't look weird.
- XUndefineCursor(x11_display, x11_window);
- }
- XUngrabPointer(x11_display, CurrentTime);
- }
-#ifdef TOUCH_ENABLED
- // Ungrab touch devices so input works as usual while we are unfocused
- /*for (int i = 0; i < xi.touch_devices.size(); ++i) {
- XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);
- }*/
-
- // Release every pointer to avoid sticky points
- for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) {
-
- Ref<InputEventScreenTouch> st;
- st.instance();
- st->set_index(E->key());
- st->set_position(E->get());
- input->accumulate_input_event(st);
- }
- xi.state.clear();
-#endif
- if (xic) {
- XUnsetICFocus(xic);
- }
- break;
-
- case ConfigureNotify:
- _window_changed(&event);
- break;
- case ButtonPress:
- case ButtonRelease: {
-
- /* exit in case of a mouse button press */
- last_timestamp = event.xbutton.time;
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- event.xbutton.x = last_mouse_pos.x;
- event.xbutton.y = last_mouse_pos.y;
- }
-
- Ref<InputEventMouseButton> mb;
- mb.instance();
-
- get_key_modifier_state(event.xbutton.state, mb);
- mb->set_button_index(event.xbutton.button);
- if (mb->get_button_index() == 2)
- mb->set_button_index(3);
- else if (mb->get_button_index() == 3)
- mb->set_button_index(2);
- mb->set_button_mask(get_mouse_button_state(mb->get_button_index(), event.xbutton.type));
- mb->set_position(Vector2(event.xbutton.x, event.xbutton.y));
- mb->set_global_position(mb->get_position());
-
- mb->set_pressed((event.type == ButtonPress));
-
- if (event.type == ButtonPress) {
-
- uint64_t diff = get_ticks_usec() / 1000 - last_click_ms;
-
- if (mb->get_button_index() == last_click_button_index) {
-
- if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) {
-
- last_click_ms = 0;
- last_click_pos = Point2(-100, -100);
- last_click_button_index = -1;
- mb->set_doubleclick(true);
- }
-
- } else if (mb->get_button_index() < 4 || mb->get_button_index() > 7) {
- last_click_button_index = mb->get_button_index();
- }
-
- if (!mb->is_doubleclick()) {
- last_click_ms += diff;
- last_click_pos = Point2(event.xbutton.x, event.xbutton.y);
- }
- }
-
- input->accumulate_input_event(mb);
-
- } break;
- case MotionNotify: {
-
- // The X11 API requires filtering one-by-one through the motion
- // notify events, in order to figure out which event is the one
- // generated by warping the mouse pointer.
-
- while (true) {
- if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == current_videomode.width / 2 && event.xmotion.y == current_videomode.height / 2) {
- //this is likely the warp event since it was warped here
- center = Vector2(event.xmotion.x, event.xmotion.y);
- break;
- }
-
- if (XPending(x11_display) > 0) {
- XEvent tevent;
- XPeekEvent(x11_display, &tevent);
- if (tevent.type == MotionNotify) {
- XNextEvent(x11_display, &event);
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- last_timestamp = event.xmotion.time;
-
- // Motion is also simple.
- // A little hack is in order
- // to be able to send relative motion events.
- Point2 pos(event.xmotion.x, event.xmotion.y);
-
- // Avoidance of spurious mouse motion (see handling of touch)
- bool filter = false;
- // Adding some tolerance to match better Point2i to Vector2
- if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {
- filter = true;
- }
- // Invalidate to avoid filtering a possible legitimate similar event coming later
- xi.mouse_pos_to_filter = Vector2(1e10, 1e10);
- if (filter) {
- break;
- }
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
- break;
- }
-
- Point2i new_center = pos;
- pos = last_mouse_pos + xi.relative_motion;
- center = new_center;
- do_mouse_warp = window_has_focus; // warp the cursor if we're focused in
- }
-
- if (!last_mouse_pos_valid) {
-
- last_mouse_pos = pos;
- last_mouse_pos_valid = true;
- }
-
- // Hackish but relative mouse motion is already handled in the RawMotion event.
- // RawMotion does not provide the absolute mouse position (whereas MotionNotify does).
- // Therefore, RawMotion cannot be the authority on absolute mouse position.
- // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.
- // Therefore, MotionNotify cannot be the authority on relative mouse motion.
- // This means we need to take a combined approach...
- Point2 rel;
-
- // Only use raw input if in capture mode. Otherwise use the classic behavior.
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- rel = xi.relative_motion;
- } else {
- rel = pos - last_mouse_pos;
- }
-
- // Reset to prevent lingering motion
- xi.relative_motion.x = 0;
- xi.relative_motion.y = 0;
-
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- pos = Point2i(current_videomode.width / 2, current_videomode.height / 2);
- }
-
- Ref<InputEventMouseMotion> mm;
- mm.instance();
-
- mm->set_pressure(xi.pressure);
- mm->set_tilt(xi.tilt);
-
- // Make the absolute position integral so it doesn't look _too_ weird :)
- Point2i posi(pos);
-
- get_key_modifier_state(event.xmotion.state, mm);
- mm->set_button_mask(get_mouse_button_state());
- mm->set_position(posi);
- mm->set_global_position(posi);
- input->set_mouse_position(posi);
- mm->set_speed(input->get_last_mouse_speed());
-
- mm->set_relative(rel);
-
- last_mouse_pos = pos;
-
- // printf("rel: %d,%d\n", rel.x, rel.y );
- // Don't propagate the motion event unless we have focus
- // this is so that the relative motion doesn't get messed up
- // after we regain focus.
- if (window_has_focus || !mouse_mode_grab)
- input->accumulate_input_event(mm);
-
- } break;
- case KeyPress:
- case KeyRelease: {
-
- last_timestamp = event.xkey.time;
-
- // key event is a little complex, so
- // it will be handled in its own function.
- handle_key_event((XKeyEvent *)&event);
- } break;
- case SelectionRequest: {
-
- XSelectionRequestEvent *req;
- XEvent e, respond;
- e = event;
-
- req = &(e.xselectionrequest);
- if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
- req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
- req->target == XInternAtom(x11_display, "TEXT", 0) ||
- req->target == XA_STRING ||
- req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
- req->target == XInternAtom(x11_display, "text/plain", 0)) {
- CharString clip = OS::get_clipboard().utf8();
- XChangeProperty(x11_display,
- req->requestor,
- req->property,
- req->target,
- 8,
- PropModeReplace,
- (unsigned char *)clip.get_data(),
- clip.length());
- respond.xselection.property = req->property;
- } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {
-
- Atom data[7];
- data[0] = XInternAtom(x11_display, "TARGETS", 0);
- data[1] = XInternAtom(x11_display, "UTF8_STRING", 0);
- data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
- data[3] = XInternAtom(x11_display, "TEXT", 0);
- data[4] = XA_STRING;
- data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
- data[6] = XInternAtom(x11_display, "text/plain", 0);
-
- XChangeProperty(x11_display,
- req->requestor,
- req->property,
- XA_ATOM,
- 32,
- PropModeReplace,
- (unsigned char *)&data,
- sizeof(data) / sizeof(data[0]));
- respond.xselection.property = req->property;
-
- } else {
- char *targetname = XGetAtomName(x11_display, req->target);
- printf("No Target '%s'\n", targetname);
- if (targetname)
- XFree(targetname);
- respond.xselection.property = None;
- }
-
- respond.xselection.type = SelectionNotify;
- respond.xselection.display = req->display;
- respond.xselection.requestor = req->requestor;
- respond.xselection.selection = req->selection;
- respond.xselection.target = req->target;
- respond.xselection.time = req->time;
- XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond);
- XFlush(x11_display);
- } break;
-
- case SelectionNotify:
-
- if (event.xselection.target == requested) {
-
- Property p = read_property(x11_display, x11_window, XInternAtom(x11_display, "PRIMARY", 0));
-
- Vector<String> files = String((char *)p.data).split("\n", false);
- for (int i = 0; i < files.size(); i++) {
- files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges();
- }
- main_loop->drop_files(files);
-
- //Reply that all is well.
- XClientMessageEvent m;
- memset(&m, 0, sizeof(m));
- m.type = ClientMessage;
- m.display = x11_display;
- m.window = xdnd_source_window;
- m.message_type = xdnd_finished;
- m.format = 32;
- m.data.l[0] = x11_window;
- m.data.l[1] = 1;
- m.data.l[2] = xdnd_action_copy; //We only ever copy.
-
- XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);
- }
- break;
-
- case ClientMessage:
-
- if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete)
- main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
-
- else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
-
- //File(s) have been dragged over the window, check for supported target (text/uri-list)
- xdnd_version = (event.xclient.data.l[1] >> 24);
- Window source = event.xclient.data.l[0];
- bool more_than_3 = event.xclient.data.l[1] & 1;
- if (more_than_3) {
- Property p = read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
- requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
- } else
- requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
- } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {
-
- //xdnd position event, reply with an XDND status message
- //just depending on type of data for now
- XClientMessageEvent m;
- memset(&m, 0, sizeof(m));
- m.type = ClientMessage;
- m.display = event.xclient.display;
- m.window = event.xclient.data.l[0];
- m.message_type = xdnd_status;
- m.format = 32;
- m.data.l[0] = x11_window;
- m.data.l[1] = (requested != None);
- m.data.l[2] = 0; //empty rectangle
- m.data.l[3] = 0;
- m.data.l[4] = xdnd_action_copy;
-
- XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
- XFlush(x11_display);
- } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
-
- if (requested != None) {
- xdnd_source_window = event.xclient.data.l[0];
- if (xdnd_version >= 1)
- XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, event.xclient.data.l[2]);
- else
- XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime);
- } else {
- //Reply that we're not interested.
- XClientMessageEvent m;
- memset(&m, 0, sizeof(m));
- m.type = ClientMessage;
- m.display = event.xclient.display;
- m.window = event.xclient.data.l[0];
- m.message_type = xdnd_finished;
- m.format = 32;
- m.data.l[0] = x11_window;
- m.data.l[1] = 0;
- m.data.l[2] = None; //Failed.
- XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
- }
- }
- break;
- default:
- break;
- }
- }
-
- XFlush(x11_display);
-
- if (do_mouse_warp) {
-
- XWarpPointer(x11_display, None, x11_window,
- 0, 0, 0, 0, (int)current_videomode.width / 2, (int)current_videomode.height / 2);
-
- /*
- Window root, child;
- int root_x, root_y;
- int win_x, win_y;
- unsigned int mask;
- XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );
-
- printf("Root: %d,%d\n", root_x, root_y);
- printf("Win: %d,%d\n", win_x, win_y);
- */
- }
-
- input->flush_accumulated_events();
-}
-
-MainLoop *OS_X11::get_main_loop() const {
-
- return main_loop;
-}
-
-void OS_X11::delete_main_loop() {
-
- if (main_loop)
- memdelete(main_loop);
- main_loop = NULL;
-}
-
-void OS_X11::set_main_loop(MainLoop *p_main_loop) {
-
- main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
-}
-
-bool OS_X11::can_draw() const {
-
- return !minimized;
-};
-
-void OS_X11::set_clipboard(const String &p_text) {
-
- OS::set_clipboard(p_text);
-
- XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime);
- XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, CurrentTime);
-};
-
-static String _get_clipboard_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) {
-
- String ret;
-
- Atom type;
- Atom selection = XA_PRIMARY;
- int format, result;
- unsigned long len, bytes_left, dummy;
- unsigned char *data;
- Window Sown = XGetSelectionOwner(x11_display, p_source);
-
- if (Sown == x11_window) {
-
- return p_internal_clipboard;
- };
-
- if (Sown != None) {
- XConvertSelection(x11_display, p_source, target, selection,
- x11_window, CurrentTime);
- XFlush(x11_display);
- while (true) {
- XEvent event;
- XNextEvent(x11_display, &event);
- if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
- break;
- };
- };
-
- //
- // Do not get any data, see how much data is there
- //
- XGetWindowProperty(x11_display, x11_window,
- selection, // Tricky..
- 0, 0, // offset - len
- 0, // Delete 0==FALSE
- AnyPropertyType, //flag
- &type, // return type
- &format, // return format
- &len, &bytes_left, //that
- &data);
- // DATA is There
- if (bytes_left > 0) {
- result = XGetWindowProperty(x11_display, x11_window,
- selection, 0, bytes_left, 0,
- AnyPropertyType, &type, &format,
- &len, &dummy, &data);
- if (result == Success) {
- ret.parse_utf8((const char *)data);
- } else
- printf("FAIL\n");
- XFree(data);
- }
- }
-
- return ret;
-}
-
-static String _get_clipboard(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) {
- String ret;
- Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
- if (utf8_atom != None) {
- ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom);
- }
- if (ret == "") {
- ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING);
- }
- return ret;
-}
-
-String OS_X11::get_clipboard() const {
-
- String ret;
- ret = _get_clipboard(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, x11_display, OS::get_clipboard());
-
- if (ret == "") {
- ret = _get_clipboard(XA_PRIMARY, x11_window, x11_display, OS::get_clipboard());
- };
-
- return ret;
-}
-
-String OS_X11::get_name() const {
-
- return "X11";
-}
-
-Error OS_X11::shell_open(String p_uri) {
-
- Error ok;
- List<String> args;
- args.push_back(p_uri);
- ok = execute("xdg-open", args, false);
- if (ok == OK)
- return OK;
- ok = execute("gnome-open", args, false);
- if (ok == OK)
- return OK;
- ok = execute("kde-open", args, false);
- return ok;
-}
-
-bool OS_X11::_check_internal_feature_support(const String &p_feature) {
-
- return p_feature == "pc";
-}
-
-String OS_X11::get_config_path() const {
-
- if (has_environment("XDG_CONFIG_HOME")) {
- return get_environment("XDG_CONFIG_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file(".config");
- } else {
- return ".";
- }
-}
-
-String OS_X11::get_data_path() const {
-
- if (has_environment("XDG_DATA_HOME")) {
- return get_environment("XDG_DATA_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file(".local/share");
- } else {
- return get_config_path();
- }
-}
-
-String OS_X11::get_cache_path() const {
-
- if (has_environment("XDG_CACHE_HOME")) {
- return get_environment("XDG_CACHE_HOME");
- } else if (has_environment("HOME")) {
- return get_environment("HOME").plus_file(".cache");
- } else {
- return get_config_path();
- }
-}
-
-String OS_X11::get_system_dir(SystemDir p_dir) const {
-
- String xdgparam;
-
- switch (p_dir) {
- case SYSTEM_DIR_DESKTOP: {
-
- xdgparam = "DESKTOP";
- } break;
- case SYSTEM_DIR_DCIM: {
-
- xdgparam = "PICTURES";
-
- } break;
- case SYSTEM_DIR_DOCUMENTS: {
-
- xdgparam = "DOCUMENTS";
-
- } break;
- case SYSTEM_DIR_DOWNLOADS: {
-
- xdgparam = "DOWNLOAD";
-
- } break;
- case SYSTEM_DIR_MOVIES: {
-
- xdgparam = "VIDEOS";
-
- } break;
- case SYSTEM_DIR_MUSIC: {
-
- xdgparam = "MUSIC";
-
- } break;
- case SYSTEM_DIR_PICTURES: {
-
- xdgparam = "PICTURES";
-
- } break;
- case SYSTEM_DIR_RINGTONES: {
-
- xdgparam = "MUSIC";
-
- } break;
- }
-
- String pipe;
- List<String> arg;
- arg.push_back(xdgparam);
- Error err = const_cast<OS_X11 *>(this)->execute("xdg-user-dir", arg, true, NULL, &pipe);
- if (err != OK)
- return ".";
- return pipe.strip_edges();
-}
-
-void OS_X11::move_window_to_foreground() {
-
- XEvent xev;
- Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);
-
- memset(&xev, 0, sizeof(xev));
- xev.type = ClientMessage;
- xev.xclient.window = x11_window;
- xev.xclient.message_type = net_active_window;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = 1;
- xev.xclient.data.l[1] = CurrentTime;
-
- XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
- XFlush(x11_display);
-}
-
-void OS_X11::set_cursor_shape(CursorShape p_shape) {
-
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
-
- if (p_shape == current_cursor) {
- return;
- }
-
- if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
- if (cursors[p_shape] != None) {
- XDefineCursor(x11_display, x11_window, cursors[p_shape]);
- } else if (cursors[CURSOR_ARROW] != None) {
- XDefineCursor(x11_display, x11_window, cursors[CURSOR_ARROW]);
- }
- }
-
- current_cursor = p_shape;
-}
-
-OS::CursorShape OS_X11::get_cursor_shape() const {
-
- return current_cursor;
-}
-
-void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-
- if (p_cursor.is_valid()) {
-
- Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- set_cursor_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
- }
-
- Ref<Texture> texture = p_cursor;
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
- Size2 texture_size;
- Rect2 atlas_rect;
-
- if (texture.is_valid()) {
- image = texture->get_data();
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(!texture.is_valid());
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- image = texture->get_data();
-
- ERR_FAIL_COND(!image.is_valid());
-
- // Create the cursor structure
- XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
- XcursorUInt image_size = texture_size.width * texture_size.height;
- XcursorDim size = sizeof(XcursorPixel) * image_size;
-
- cursor_image->version = 1;
- cursor_image->size = size;
- cursor_image->xhot = p_hotspot.x;
- cursor_image->yhot = p_hotspot.y;
-
- // allocate memory to contain the whole file
- cursor_image->pixels = (XcursorPixel *)memalloc(size);
-
- image->lock();
-
- for (XcursorPixel index = 0; index < image_size; index++) {
- int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
- int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
-
- if (atlas_texture.is_valid()) {
- column_index = MIN(column_index, atlas_rect.size.width - 1);
- row_index = MIN(row_index, atlas_rect.size.height - 1);
- }
-
- *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();
- }
-
- image->unlock();
-
- ERR_FAIL_COND(cursor_image->pixels == NULL);
-
- // Save it for a further usage
- cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
-
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- if (p_shape == current_cursor) {
- if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
- XDefineCursor(x11_display, x11_window, cursors[p_shape]);
- }
- }
-
- memfree(cursor_image->pixels);
- XcursorImageDestroy(cursor_image);
- } else {
- // Reset to default system cursor
- if (img[p_shape]) {
- cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]);
- }
-
- CursorShape c = current_cursor;
- current_cursor = CURSOR_MAX;
- set_cursor_shape(c);
-
- cursors_cache.erase(p_shape);
- }
-}
-
-void OS_X11::release_rendering_thread() {
-
-#if defined(OPENGL_ENABLED)
- context_gl->release_current();
-#endif
-}
-
-void OS_X11::make_rendering_thread() {
-
-#if defined(OPENGL_ENABLED)
- context_gl->make_current();
-#endif
-}
-
-void OS_X11::swap_buffers() {
-
-#if defined(OPENGL_ENABLED)
- context_gl->swap_buffers();
-#endif
-}
-
-void OS_X11::alert(const String &p_alert, const String &p_title) {
- const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" };
-
- String path = get_environment("PATH");
- Vector<String> path_elems = path.split(":", false);
- String program;
-
- for (int i = 0; i < path_elems.size(); i++) {
- for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) {
- String tested_path = path_elems[i].plus_file(message_programs[k]);
-
- if (FileAccess::exists(tested_path)) {
- program = tested_path;
- break;
- }
- }
-
- if (program.length())
- break;
- }
-
- List<String> args;
-
- if (program.ends_with("zenity")) {
- args.push_back("--error");
- args.push_back("--width");
- args.push_back("500");
- args.push_back("--title");
- args.push_back(p_title);
- args.push_back("--text");
- args.push_back(p_alert);
- }
-
- if (program.ends_with("kdialog")) {
- args.push_back("--error");
- args.push_back(p_alert);
- args.push_back("--title");
- args.push_back(p_title);
- }
-
- if (program.ends_with("Xdialog")) {
- args.push_back("--title");
- args.push_back(p_title);
- args.push_back("--msgbox");
- args.push_back(p_alert);
- args.push_back("0");
- args.push_back("0");
- }
-
- if (program.ends_with("xmessage")) {
- args.push_back("-center");
- args.push_back("-title");
- args.push_back(p_title);
- args.push_back(p_alert);
- }
-
- if (program.length()) {
- execute(program, args, true);
- } else {
- print_line(p_alert);
- }
-}
-
-bool g_set_icon_error = false;
-int set_icon_errorhandler(Display *dpy, XErrorEvent *ev) {
- g_set_icon_error = true;
- return 0;
-}
-
-void OS_X11::set_icon(const Ref<Image> &p_icon) {
- int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&set_icon_errorhandler);
-
- Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);
-
- if (p_icon.is_valid()) {
- Ref<Image> img = p_icon->duplicate();
- img->convert(Image::FORMAT_RGBA8);
-
- while (true) {
- int w = img->get_width();
- int h = img->get_height();
-
- if (g_set_icon_error) {
- g_set_icon_error = false;
-
- WARN_PRINT("Icon too large, attempting to resize icon.");
-
- int new_width, new_height;
- if (w > h) {
- new_width = w / 2;
- new_height = h * new_width / w;
- } else {
- new_height = h / 2;
- new_width = w * new_height / h;
- }
-
- w = new_width;
- h = new_height;
-
- if (!w || !h) {
- WARN_PRINT("Unable to set icon.");
- break;
- }
-
- img->resize(w, h, Image::INTERPOLATE_CUBIC);
- }
-
- // We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
- Vector<long> pd;
-
- pd.resize(2 + w * h);
-
- pd.write[0] = w;
- pd.write[1] = h;
-
- PoolVector<uint8_t>::Read r = img->get_data().read();
-
- long *wr = &pd.write[2];
- uint8_t const *pr = r.ptr();
-
- for (int i = 0; i < w * h; i++) {
- long v = 0;
- // A R G B
- v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
- *wr++ = v;
- pr += 4;
- }
-
- XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
-
- if (!g_set_icon_error)
- break;
- }
- } else {
- XDeleteProperty(x11_display, x11_window, net_wm_icon);
- }
-
- XFlush(x11_display);
- XSetErrorHandler(oldHandler);
-}
-
-void OS_X11::force_process_input() {
- process_xevents(); // get rid of pending events
-#ifdef JOYDEV_ENABLED
- joypad->process_joypads();
-#endif
-}
-
-void OS_X11::run() {
-
- force_quit = false;
-
- if (!main_loop)
- return;
-
- main_loop->init();
-
- //uint64_t last_ticks=get_ticks_usec();
-
- //int frames=0;
- //uint64_t frame=0;
-
- while (!force_quit) {
-
- process_xevents(); // get rid of pending events
-#ifdef JOYDEV_ENABLED
- joypad->process_joypads();
-#endif
- if (Main::iteration())
- break;
- };
-
- main_loop->finish();
-}
-
-bool OS_X11::is_joy_known(int p_device) {
- return input->is_joy_mapped(p_device);
-}
-
-String OS_X11::get_joy_guid(int p_device) const {
- return input->get_joy_guid_remapped(p_device);
-}
-
-void OS_X11::_set_use_vsync(bool p_enable) {
-#if defined(OPENGL_ENABLED)
- if (context_gl)
- context_gl->set_use_vsync(p_enable);
-#endif
-}
-/*
-bool OS_X11::is_vsync_enabled() const {
-
- if (context_gl)
- return context_gl->is_using_vsync();
-
- return true;
-}
-*/
-void OS_X11::set_context(int p_context) {
-
- XClassHint *classHint = XAllocClassHint();
-
- if (classHint) {
-
- CharString name_str;
- switch (p_context) {
- case CONTEXT_EDITOR:
- name_str = "Godot_Editor";
- break;
- case CONTEXT_PROJECTMAN:
- name_str = "Godot_ProjectList";
- break;
- case CONTEXT_ENGINE:
- name_str = "Godot_Engine";
- break;
- }
-
- CharString class_str;
- if (p_context == CONTEXT_ENGINE) {
- String config_name = GLOBAL_GET("application/config/name");
- if (config_name.length() == 0) {
- class_str = "Godot_Engine";
- } else {
- class_str = config_name.utf8();
- }
- } else {
- class_str = "Godot";
- }
-
- classHint->res_class = class_str.ptrw();
- classHint->res_name = name_str.ptrw();
-
- XSetClassHint(x11_display, x11_window, classHint);
- XFree(classHint);
- }
-}
-
-OS::PowerState OS_X11::get_power_state() {
- return power_manager->get_power_state();
-}
-
-int OS_X11::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
-}
-
-int OS_X11::get_power_percent_left() {
- return power_manager->get_power_percent_left();
-}
-
-void OS_X11::disable_crash_handler() {
- crash_handler.disable();
-}
-
-bool OS_X11::is_disable_crash_handler() const {
- return crash_handler.is_disabled();
-}
-
-static String get_mountpoint(const String &p_path) {
- struct stat s;
- if (stat(p_path.utf8().get_data(), &s)) {
- return "";
- }
-
-#ifdef HAVE_MNTENT
- dev_t dev = s.st_dev;
- FILE *fd = setmntent("/proc/mounts", "r");
- if (!fd) {
- return "";
- }
-
- struct mntent mnt;
- char buf[1024];
- size_t buflen = 1024;
- while (getmntent_r(fd, &mnt, buf, buflen)) {
- if (!stat(mnt.mnt_dir, &s) && s.st_dev == dev) {
- endmntent(fd);
- return String(mnt.mnt_dir);
- }
- }
-
- endmntent(fd);
-#endif
- return "";
-}
-
-Error OS_X11::move_to_trash(const String &p_path) {
- String trash_can = "";
- String mnt = get_mountpoint(p_path);
-
- // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can.
- if (mnt != "") {
- String path(mnt + "/.Trash-" + itos(getuid()) + "/files");
- struct stat s;
- if (!stat(path.utf8().get_data(), &s)) {
- trash_can = path;
- }
- }
-
- // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can.
- if (trash_can == "") {
- char *dhome = getenv("XDG_DATA_HOME");
- if (dhome) {
- trash_can = String(dhome) + "/Trash/files";
- }
- }
-
- // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can.
- if (trash_can == "") {
- char *home = getenv("HOME");
- if (home) {
- trash_can = String(home) + "/.local/share/Trash/files";
- }
- }
-
- // Issue an error if none of the previous locations is appropriate for the trash can.
- if (trash_can == "") {
- ERR_PRINTS("move_to_trash: Could not determine the trash can location");
- return FAILED;
- }
-
- // Create needed directories for decided trash can location.
- DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- Error err = dir_access->make_dir_recursive(trash_can);
- memdelete(dir_access);
-
- // Issue an error if trash can is not created proprely.
- if (err != OK) {
- ERR_PRINTS("move_to_trash: Could not create the trash can \"" + trash_can + "\"");
- return err;
- }
-
- // The trash can is successfully created, now move the given resource to it.
- // Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
- List<String> mv_args;
- mv_args.push_back(p_path);
- mv_args.push_back(trash_can);
- int retval;
- err = execute("mv", mv_args, true, NULL, NULL, &retval);
-
- // Issue an error if "mv" failed to move the given resource to the trash can.
- if (err != OK || retval != 0) {
- ERR_PRINTS("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\"");
- return FAILED;
- }
-
- return OK;
-}
-
-OS::LatinKeyboardVariant OS_X11::get_latin_keyboard_variant() const {
-
- XkbDescRec *xkbdesc = XkbAllocKeyboard();
- ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY);
-
- XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc);
- ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY);
- ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY);
-
- char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols);
- ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY);
-
- Vector<String> info = String(layout).split("+");
- ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY);
-
- if (info[1].find("colemak") != -1) {
- return LATIN_KEYBOARD_COLEMAK;
- } else if (info[1].find("qwertz") != -1) {
- return LATIN_KEYBOARD_QWERTZ;
- } else if (info[1].find("azerty") != -1) {
- return LATIN_KEYBOARD_AZERTY;
- } else if (info[1].find("qzerty") != -1) {
- return LATIN_KEYBOARD_QZERTY;
- } else if (info[1].find("dvorak") != -1) {
- return LATIN_KEYBOARD_DVORAK;
- } else if (info[1].find("neo") != -1) {
- return LATIN_KEYBOARD_NEO;
- }
-
- return LATIN_KEYBOARD_QWERTY;
-}
-
-void OS_X11::update_real_mouse_position() {
- Window root_return, child_return;
- int root_x, root_y, win_x, win_y;
- unsigned int mask_return;
-
- Bool xquerypointer_result = XQueryPointer(x11_display, x11_window, &root_return, &child_return, &root_x, &root_y,
- &win_x, &win_y, &mask_return);
-
- if (xquerypointer_result) {
- if (win_x > 0 && win_y > 0 && win_x <= current_videomode.width && win_y <= current_videomode.height) {
-
- last_mouse_pos.x = win_x;
- last_mouse_pos.y = win_y;
- last_mouse_pos_valid = true;
- input->set_mouse_position(last_mouse_pos);
- }
- }
-}
-
-OS_X11::OS_X11() {
-
-#ifdef PULSEAUDIO_ENABLED
- AudioDriverManager::add_driver(&driver_pulseaudio);
-#endif
-
-#ifdef ALSA_ENABLED
- AudioDriverManager::add_driver(&driver_alsa);
-#endif
-
- xi.opcode = 0;
- xi.last_relative_time = 0;
- layered_window = false;
- minimized = false;
- window_focused = true;
- xim_style = 0L;
- mouse_mode = MOUSE_MODE_VISIBLE;
- last_position_before_fs = Vector2();
-}
diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h
deleted file mode 100644
index 25b406743b..0000000000
--- a/platform/x11/os_x11.h
+++ /dev/null
@@ -1,334 +0,0 @@
-/*************************************************************************/
-/* os_x11.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef OS_X11_H
-#define OS_X11_H
-
-#include "context_gl_x11.h"
-#include "core/os/input.h"
-#include "crash_handler_x11.h"
-#include "drivers/alsa/audio_driver_alsa.h"
-#include "drivers/alsamidi/midi_driver_alsamidi.h"
-#include "drivers/pulseaudio/audio_driver_pulseaudio.h"
-#include "drivers/unix/os_unix.h"
-#include "joypad_linux.h"
-#include "main/input_default.h"
-#include "power_x11.h"
-#include "servers/audio_server.h"
-#include "servers/visual/rasterizer.h"
-#include "servers/visual_server.h"
-//#include "servers/visual/visual_server_wrap_mt.h"
-
-#include <X11/Xcursor/Xcursor.h>
-#include <X11/Xlib.h>
-#include <X11/extensions/XInput2.h>
-#include <X11/extensions/Xrandr.h>
-#include <X11/keysym.h>
-
-// Hints for X11 fullscreen
-typedef struct {
- unsigned long flags;
- unsigned long functions;
- unsigned long decorations;
- long inputMode;
- unsigned long status;
-} Hints;
-
-typedef struct _xrr_monitor_info {
- Atom name;
- Bool primary;
- Bool automatic;
- int noutput;
- int x;
- int y;
- int width;
- int height;
- int mwidth;
- int mheight;
- RROutput *outputs;
-} xrr_monitor_info;
-
-#undef CursorShape
-
-class OS_X11 : public OS_Unix {
-
- Atom wm_delete;
- Atom xdnd_enter;
- Atom xdnd_position;
- Atom xdnd_status;
- Atom xdnd_action_copy;
- Atom xdnd_drop;
- Atom xdnd_finished;
- Atom xdnd_selection;
- Atom requested;
-
- int xdnd_version;
-
-#if defined(OPENGL_ENABLED)
- ContextGL_X11 *context_gl;
-#endif
- //Rasterizer *rasterizer;
- VisualServer *visual_server;
- VideoMode current_videomode;
- List<String> args;
- Window x11_window;
- Window xdnd_source_window;
- MainLoop *main_loop;
- ::Display *x11_display;
- char *xmbstring;
- int xmblen;
- unsigned long last_timestamp;
- ::Time last_keyrelease_time;
- ::XIC xic;
- ::XIM xim;
- ::XIMStyle xim_style;
- static void xim_destroy_callback(::XIM im, ::XPointer client_data,
- ::XPointer call_data);
-
- // IME
- bool im_active;
- Vector2 im_position;
- Vector2 last_position_before_fs;
-
- Size2 min_size;
- Size2 max_size;
-
- Point2 last_mouse_pos;
- bool last_mouse_pos_valid;
- Point2i last_click_pos;
- uint64_t last_click_ms;
- int last_click_button_index;
- uint32_t last_button_state;
-
- struct {
- int opcode;
- Vector<int> touch_devices;
- Map<int, Vector2> absolute_devices;
- Map<int, Vector3> pen_devices;
- XIEventMask all_event_mask;
- XIEventMask all_master_event_mask;
- Map<int, Vector2> state;
- double pressure;
- Vector2 tilt;
- Vector2 mouse_pos_to_filter;
- Vector2 relative_motion;
- Vector2 raw_pos;
- Vector2 old_raw_pos;
- ::Time last_relative_time;
- } xi;
-
- bool refresh_device_info();
-
- unsigned int get_mouse_button_state(unsigned int p_x11_button, int p_x11_type);
- void get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state);
- void flush_mouse_motion();
-
- MouseMode mouse_mode;
- Point2i center;
-
- void handle_key_event(XKeyEvent *p_event, bool p_echo = false);
- void process_xevents();
- virtual void delete_main_loop();
-
- bool force_quit;
- bool minimized;
- bool window_has_focus;
- bool do_mouse_warp;
-
- const char *cursor_theme;
- int cursor_size;
- XcursorImage *img[CURSOR_MAX];
- Cursor cursors[CURSOR_MAX];
- Cursor null_cursor;
- CursorShape current_cursor;
- Map<CursorShape, Vector<Variant> > cursors_cache;
-
- InputDefault *input;
-
-#ifdef JOYDEV_ENABLED
- JoypadLinux *joypad;
-#endif
-
-#ifdef ALSA_ENABLED
- AudioDriverALSA driver_alsa;
-#endif
-
-#ifdef ALSAMIDI_ENABLED
- MIDIDriverALSAMidi driver_alsamidi;
-#endif
-
-#ifdef PULSEAUDIO_ENABLED
- AudioDriverPulseAudio driver_pulseaudio;
-#endif
-
- PowerX11 *power_manager;
-
- bool layered_window;
-
- CrashHandler crash_handler;
-
- int video_driver_index;
- bool maximized;
- bool window_focused;
- //void set_wm_border(bool p_enabled);
- void set_wm_fullscreen(bool p_enabled);
- void set_wm_above(bool p_enabled);
-
- typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors);
- typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors);
- xrr_get_monitors_t xrr_get_monitors;
- xrr_free_monitors_t xrr_free_monitors;
- void *xrandr_handle;
- Bool xrandr_ext_ok;
-
-protected:
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
- virtual void finalize();
-
- virtual void set_main_loop(MainLoop *p_main_loop);
-
- void _window_changed(XEvent *event);
-
- bool is_window_maximize_allowed();
-
-public:
- virtual String get_name() const;
-
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual CursorShape get_cursor_shape() const;
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
-
- void set_mouse_mode(MouseMode p_mode);
- MouseMode get_mouse_mode() const;
-
- virtual void warp_mouse_position(const Point2 &p_to);
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void set_icon(const Ref<Image> &p_icon);
-
- virtual MainLoop *get_main_loop() const;
-
- virtual bool can_draw() const;
-
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
-
- virtual void release_rendering_thread();
- virtual void make_rendering_thread();
- virtual void swap_buffers();
-
- virtual String get_config_path() const;
- virtual String get_data_path() const;
- virtual String get_cache_path() const;
-
- virtual String get_system_dir(SystemDir p_dir) const;
-
- virtual Error shell_open(String p_uri);
-
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
- virtual int get_screen_count() const;
- virtual int get_current_screen() const;
- virtual void set_current_screen(int p_screen);
- virtual Point2 get_screen_position(int p_screen = -1) const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
- virtual int get_screen_dpi(int p_screen = -1) const;
- virtual Point2 get_window_position() const;
- virtual void set_window_position(const Point2 &p_position);
- virtual Size2 get_window_size() const;
- virtual Size2 get_real_window_size() const;
- virtual Size2 get_max_window_size() const;
- virtual Size2 get_min_window_size() const;
- virtual void set_min_window_size(const Size2 p_size);
- virtual void set_max_window_size(const Size2 p_size);
- virtual void set_window_size(const Size2 p_size);
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual void set_window_resizable(bool p_enabled);
- virtual bool is_window_resizable() const;
- virtual void set_window_minimized(bool p_enabled);
- virtual bool is_window_minimized() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
- virtual void set_window_always_on_top(bool p_enabled);
- virtual bool is_window_always_on_top() const;
- virtual bool is_window_focused() const;
- virtual void request_attention();
-
- virtual void set_borderless_window(bool p_borderless);
- virtual bool get_borderless_window();
-
- virtual bool get_window_per_pixel_transparency_enabled() const;
- virtual void set_window_per_pixel_transparency_enabled(bool p_enabled);
-
- virtual void set_ime_active(const bool p_active);
- virtual void set_ime_position(const Point2 &p_pos);
-
- virtual String get_unique_id() const;
-
- virtual void move_window_to_foreground();
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
-
- virtual void set_context(int p_context);
-
- virtual void _set_use_vsync(bool p_enable);
- //virtual bool is_vsync_enabled() const;
-
- virtual OS::PowerState get_power_state();
- virtual int get_power_seconds_left();
- virtual int get_power_percent_left();
-
- virtual bool _check_internal_feature_support(const String &p_feature);
-
- virtual void force_process_input();
- void run();
-
- void disable_crash_handler();
- bool is_disable_crash_handler() const;
-
- virtual Error move_to_trash(const String &p_path);
-
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
-
- void update_real_mouse_position();
- OS_X11();
-};
-
-#endif
diff --git a/platform/x11/power_x11.cpp b/platform/x11/power_x11.cpp
deleted file mode 100644
index 5ac5e8e87b..0000000000
--- a/platform/x11/power_x11.cpp
+++ /dev/null
@@ -1,577 +0,0 @@
-/*************************************************************************/
-/* power_x11.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-/*
-Adapted from corresponding SDL 2.0 code.
-*/
-
-/*
- Simple DirectMedia Layer
- Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
-*/
-
-#include "power_x11.h"
-
-#include <stdio.h>
-#include <unistd.h>
-
-#include "core/error_macros.h"
-#include <dirent.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-// CODE CHUNK IMPORTED FROM SDL 2.0
-
-static const char *proc_apm_path = "/proc/apm";
-static const char *proc_acpi_battery_path = "/proc/acpi/battery";
-static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
-static const char *sys_class_power_supply_path = "/sys/class/power_supply";
-
-FileAccessRef PowerX11::open_power_file(const char *base, const char *node, const char *key) {
- String path = String(base) + String("/") + String(node) + String("/") + String(key);
- FileAccessRef f = FileAccess::open(path, FileAccess::READ);
- return f;
-}
-
-bool PowerX11::read_power_file(const char *base, const char *node, const char *key, char *buf, size_t buflen) {
- ssize_t br = 0;
- FileAccessRef fd = open_power_file(base, node, key);
- if (!fd) {
- return false;
- }
- br = fd->get_buffer(reinterpret_cast<uint8_t *>(buf), buflen - 1);
- fd->close();
- if (br < 0) {
- return false;
- }
- buf[br] = '\0'; // null-terminate the string
- return true;
-}
-
-bool PowerX11::make_proc_acpi_key_val(char **_ptr, char **_key, char **_val) {
- char *ptr = *_ptr;
-
- while (*ptr == ' ') {
- ptr++; /* skip whitespace. */
- }
-
- if (*ptr == '\0') {
- return false; /* EOF. */
- }
-
- *_key = ptr;
-
- while ((*ptr != ':') && (*ptr != '\0')) {
- ptr++;
- }
-
- if (*ptr == '\0') {
- return false; /* (unexpected) EOF. */
- }
-
- *(ptr++) = '\0'; /* terminate the key. */
-
- while (*ptr == ' ') {
- ptr++; /* skip whitespace. */
- }
-
- if (*ptr == '\0') {
- return false; /* (unexpected) EOF. */
- }
-
- *_val = ptr;
-
- while ((*ptr != '\n') && (*ptr != '\0')) {
- ptr++;
- }
-
- if (*ptr != '\0') {
- *(ptr++) = '\0'; /* terminate the value. */
- }
-
- *_ptr = ptr; /* store for next time. */
- return true;
-}
-
-void PowerX11::check_proc_acpi_battery(const char *node, bool *have_battery, bool *charging) {
- const char *base = proc_acpi_battery_path;
- char info[1024];
- char state[1024];
- char *ptr = NULL;
- char *key = NULL;
- char *val = NULL;
- bool charge = false;
- bool choose = false;
- int maximum = -1;
- int remaining = -1;
- int secs = -1;
- int pct = -1;
-
- if (!read_power_file(base, node, "state", state, sizeof(state))) {
- return;
- } else {
- if (!read_power_file(base, node, "info", info, sizeof(info)))
- return;
- }
-
- ptr = &state[0];
- while (make_proc_acpi_key_val(&ptr, &key, &val)) {
- if (String(key) == "present") {
- if (String(val) == "yes") {
- *have_battery = true;
- }
- } else if (String(key) == "charging state") {
- /* !!! FIXME: what exactly _does_ charging/discharging mean? */
- if (String(val) == "charging/discharging") {
- charge = true;
- } else if (String(val) == "charging") {
- charge = true;
- }
- } else if (String(key) == "remaining capacity") {
- String sval = val;
- const int cvt = sval.to_int();
- remaining = cvt;
- }
- }
-
- ptr = &info[0];
- while (make_proc_acpi_key_val(&ptr, &key, &val)) {
- if (String(key) == "design capacity") {
- String sval = val;
- const int cvt = sval.to_int();
- maximum = cvt;
- }
- }
-
- if ((maximum >= 0) && (remaining >= 0)) {
- pct = (int)((((float)remaining) / ((float)maximum)) * 100.0f);
- if (pct < 0) {
- pct = 0;
- } else if (pct > 100) {
- pct = 100;
- }
- }
-
- /* !!! FIXME: calculate (secs). */
-
- /*
- * We pick the battery that claims to have the most minutes left.
- * (failing a report of minutes, we'll take the highest percent.)
- */
- // -- GODOT start --
- //if ((secs < 0) && (this->nsecs_left < 0)) {
- if (this->nsecs_left < 0) {
- // -- GODOT end --
- if ((pct < 0) && (this->percent_left < 0)) {
- choose = true; /* at least we know there's a battery. */
- }
- if (pct > this->percent_left) {
- choose = true;
- }
- } else if (secs > this->nsecs_left) {
- choose = true;
- }
-
- if (choose) {
- this->nsecs_left = secs;
- this->percent_left = pct;
- *charging = charge;
- }
-}
-
-void PowerX11::check_proc_acpi_ac_adapter(const char *node, bool *have_ac) {
- const char *base = proc_acpi_ac_adapter_path;
- char state[256];
- char *ptr = NULL;
- char *key = NULL;
- char *val = NULL;
-
- if (!read_power_file(base, node, "state", state, sizeof(state))) {
- return;
- }
-
- ptr = &state[0];
- while (make_proc_acpi_key_val(&ptr, &key, &val)) {
- String skey = key;
- if (skey == "state") {
- String sval = val;
- if (sval == "on-line") {
- *have_ac = true;
- }
- }
- }
-}
-
-bool PowerX11::GetPowerInfo_Linux_proc_acpi() {
- String node;
- DirAccess *dirp = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- bool have_battery = false;
- bool have_ac = false;
- bool charging = false;
-
- this->nsecs_left = -1;
- this->percent_left = -1;
- this->power_state = OS::POWERSTATE_UNKNOWN;
-
- dirp->change_dir(proc_acpi_battery_path);
- Error err = dirp->list_dir_begin();
-
- if (err != OK) {
- return false; /* can't use this interface. */
- } else {
- node = dirp->get_next();
- while (node != "") {
- check_proc_acpi_battery(node.utf8().get_data(), &have_battery, &charging /*, seconds, percent*/);
- node = dirp->get_next();
- }
- }
- dirp->change_dir(proc_acpi_ac_adapter_path);
- err = dirp->list_dir_begin();
- if (err != OK) {
- return false; /* can't use this interface. */
- } else {
- node = dirp->get_next();
- while (node != "") {
- check_proc_acpi_ac_adapter(node.utf8().get_data(), &have_ac);
- node = dirp->get_next();
- }
- }
-
- if (!have_battery) {
- this->power_state = OS::POWERSTATE_NO_BATTERY;
- } else if (charging) {
- this->power_state = OS::POWERSTATE_CHARGING;
- } else if (have_ac) {
- this->power_state = OS::POWERSTATE_CHARGED;
- } else {
- this->power_state = OS::POWERSTATE_ON_BATTERY;
- }
-
- memdelete(dirp);
- return true; /* definitive answer. */
-}
-
-bool PowerX11::next_string(char **_ptr, char **_str) {
- char *ptr = *_ptr;
- char *str = *_str;
-
- while (*ptr == ' ') { /* skip any spaces... */
- ptr++;
- }
-
- if (*ptr == '\0') {
- return false;
- }
-
- str = ptr;
- while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
- ptr++;
-
- if (*ptr != '\0')
- *(ptr++) = '\0';
-
- *_str = str;
- *_ptr = ptr;
- return true;
-}
-
-bool PowerX11::int_string(char *str, int *val) {
- String sval = str;
- *val = sval.to_int();
- return (*str != '\0');
-}
-
-/* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
-bool PowerX11::GetPowerInfo_Linux_proc_apm() {
- bool need_details = false;
- int ac_status = 0;
- int battery_status = 0;
- int battery_flag = 0;
- int battery_percent = 0;
- int battery_time = 0;
- FileAccessRef fd = FileAccess::open(proc_apm_path, FileAccess::READ);
- char buf[128];
- char *ptr = &buf[0];
- char *str = NULL;
- ssize_t br;
-
- if (!fd) {
- return false; /* can't use this interface. */
- }
-
- br = fd->get_buffer(reinterpret_cast<uint8_t *>(buf), sizeof(buf) - 1);
- fd->close();
-
- if (br < 0) {
- return false;
- }
-
- buf[br] = '\0'; /* null-terminate the string. */
- if (!next_string(&ptr, &str)) { /* driver version */
- return false;
- }
- if (!next_string(&ptr, &str)) { /* BIOS version */
- return false;
- }
- if (!next_string(&ptr, &str)) { /* APM flags */
- return false;
- }
-
- if (!next_string(&ptr, &str)) { /* AC line status */
- return false;
- } else if (!int_string(str, &ac_status)) {
- return false;
- }
-
- if (!next_string(&ptr, &str)) { /* battery status */
- return false;
- } else if (!int_string(str, &battery_status)) {
- return false;
- }
- if (!next_string(&ptr, &str)) { /* battery flag */
- return false;
- } else if (!int_string(str, &battery_flag)) {
- return false;
- }
- if (!next_string(&ptr, &str)) { /* remaining battery life percent */
- return false;
- }
- String sstr = str;
- if (sstr[sstr.length() - 1] == '%') {
- sstr[sstr.length() - 1] = '\0';
- }
- if (!int_string(str, &battery_percent)) {
- return false;
- }
-
- if (!next_string(&ptr, &str)) { /* remaining battery life time */
- return false;
- } else if (!int_string(str, &battery_time)) {
- return false;
- }
-
- if (!next_string(&ptr, &str)) { /* remaining battery life time units */
- return false;
- } else if (String(str) == "min") {
- battery_time *= 60;
- }
-
- if (battery_flag == 0xFF) { /* unknown state */
- this->power_state = OS::POWERSTATE_UNKNOWN;
- } else if (battery_flag & (1 << 7)) { /* no battery */
- this->power_state = OS::POWERSTATE_NO_BATTERY;
- } else if (battery_flag & (1 << 3)) { /* charging */
- this->power_state = OS::POWERSTATE_CHARGING;
- need_details = true;
- } else if (ac_status == 1) {
- this->power_state = OS::POWERSTATE_CHARGED; /* on AC, not charging. */
- need_details = true;
- } else {
- this->power_state = OS::POWERSTATE_ON_BATTERY;
- need_details = true;
- }
-
- this->percent_left = -1;
- this->nsecs_left = -1;
- if (need_details) {
- const int pct = battery_percent;
- const int secs = battery_time;
-
- if (pct >= 0) { /* -1 == unknown */
- this->percent_left = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
- }
- if (secs >= 0) { /* -1 == unknown */
- this->nsecs_left = secs;
- }
- }
-
- return true;
-}
-
-/* !!! FIXME: implement d-bus queries to org.freedesktop.UPower. */
-
-bool PowerX11::GetPowerInfo_Linux_sys_class_power_supply(/*PowerState *state, int *seconds, int *percent*/) {
- const char *base = sys_class_power_supply_path;
- String name;
-
- DirAccess *dirp = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- dirp->change_dir(base);
- Error err = dirp->list_dir_begin();
-
- if (err != OK) {
- return false;
- }
-
- this->power_state = OS::POWERSTATE_NO_BATTERY; /* assume we're just plugged in. */
- this->nsecs_left = -1;
- this->percent_left = -1;
-
- name = dirp->get_next();
-
- while (name != "") {
- bool choose = false;
- char str[64];
- OS::PowerState st;
- int secs;
- int pct;
-
- if ((name == ".") || (name == "..")) {
- name = dirp->get_next();
- continue; //skip these, of course.
- } else {
- if (!read_power_file(base, name.utf8().get_data(), "type", str, sizeof(str))) {
- name = dirp->get_next();
- continue; // Don't know _what_ we're looking at. Give up on it.
- } else {
- if (String(str) != "Battery\n") {
- name = dirp->get_next();
- continue; // we don't care about UPS and such.
- }
- }
- }
-
- /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */
- if (read_power_file(base, name.utf8().get_data(), "present", str, sizeof(str)) && (String(str) == "0\n")) {
- st = OS::POWERSTATE_NO_BATTERY;
- } else if (!read_power_file(base, name.utf8().get_data(), "status", str, sizeof(str))) {
- st = OS::POWERSTATE_UNKNOWN; /* uh oh */
- } else if (String(str) == "Charging\n") {
- st = OS::POWERSTATE_CHARGING;
- } else if (String(str) == "Discharging\n") {
- st = OS::POWERSTATE_ON_BATTERY;
- } else if ((String(str) == "Full\n") || (String(str) == "Not charging\n")) {
- st = OS::POWERSTATE_CHARGED;
- } else {
- st = OS::POWERSTATE_UNKNOWN; /* uh oh */
- }
-
- if (!read_power_file(base, name.utf8().get_data(), "capacity", str, sizeof(str))) {
- pct = -1;
- } else {
- pct = String(str).to_int();
- pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
- }
-
- if (!read_power_file(base, name.utf8().get_data(), "time_to_empty_now", str, sizeof(str))) {
- secs = -1;
- } else {
- secs = String(str).to_int();
- secs = (secs <= 0) ? -1 : secs; /* 0 == unknown */
- }
-
- /*
- * We pick the battery that claims to have the most minutes left.
- * (failing a report of minutes, we'll take the highest percent.)
- */
- if ((secs < 0) && (this->nsecs_left < 0)) {
- if ((pct < 0) && (this->percent_left < 0)) {
- choose = true; /* at least we know there's a battery. */
- } else if (pct > this->percent_left) {
- choose = true;
- }
- } else if (secs > this->nsecs_left) {
- choose = true;
- }
-
- if (choose) {
- this->nsecs_left = secs;
- this->percent_left = pct;
- this->power_state = st;
- }
-
- name = dirp->get_next();
- }
-
- memdelete(dirp);
- return true; /* don't look any further*/
-}
-
-bool PowerX11::UpdatePowerInfo() {
- if (GetPowerInfo_Linux_sys_class_power_supply()) { // try method 1
- return true;
- }
- if (GetPowerInfo_Linux_proc_acpi()) { // try further
- return true;
- }
- if (GetPowerInfo_Linux_proc_apm()) { // try even further
- return true;
- }
- return false;
-}
-
-PowerX11::PowerX11() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerX11::~PowerX11() {
-}
-
-OS::PowerState PowerX11::get_power_state() {
- if (UpdatePowerInfo()) {
- return power_state;
- } else {
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerX11::get_power_seconds_left() {
- if (UpdatePowerInfo()) {
- return nsecs_left;
- } else {
- return -1;
- }
-}
-
-int PowerX11::get_power_percent_left() {
- if (UpdatePowerInfo()) {
- return percent_left;
- } else {
- return -1;
- }
-}
diff --git a/platform/x11/power_x11.h b/platform/x11/power_x11.h
deleted file mode 100644
index 76f20c68e8..0000000000
--- a/platform/x11/power_x11.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*************************************************************************/
-/* power_x11.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef POWER_X11_H
-#define POWER_X11_H
-
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-
-class PowerX11 {
-
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
-
- FileAccessRef open_power_file(const char *base, const char *node, const char *key);
- bool read_power_file(const char *base, const char *node, const char *key, char *buf, size_t buflen);
- bool make_proc_acpi_key_val(char **_ptr, char **_key, char **_val);
- void check_proc_acpi_battery(const char *node, bool *have_battery, bool *charging);
- void check_proc_acpi_ac_adapter(const char *node, bool *have_ac);
- bool GetPowerInfo_Linux_proc_acpi();
- bool next_string(char **_ptr, char **_str);
- bool int_string(char *str, int *val);
- bool GetPowerInfo_Linux_proc_apm();
- bool GetPowerInfo_Linux_sys_class_power_supply();
- bool UpdatePowerInfo();
-
-public:
- PowerX11();
- virtual ~PowerX11();
-
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
-};
-
-#endif // POWER_X11_H