From 8823eae328547991def3b13ee2919291d29a278b Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 20 Jul 2022 09:28:22 +0300 Subject: Rename OSX to macOS and iPhoneOS to iOS. --- .github/CODEOWNERS | 4 +- .github/workflows/ios_builds.yml | 2 +- .github/workflows/macos_builds.yml | 4 +- SConstruct | 18 +- core/SCsub | 2 +- core/config/project_settings.cpp | 2 +- core/input/input_builders.py | 4 +- core/os/keyboard.cpp | 2 +- doc/classes/EditorExportPlugin.xml | 2 +- drivers/coreaudio/audio_driver_coreaudio.cpp | 20 +- drivers/coreaudio/audio_driver_coreaudio.h | 6 +- drivers/gl_context/SCsub | 2 +- drivers/gles3/rasterizer_gles3.cpp | 2 +- drivers/unix/os_unix.cpp | 2 +- drivers/vulkan/SCsub | 6 +- editor/editor_export.cpp | 10 +- editor/editor_export.h | 8 +- editor/editor_fonts.cpp | 2 +- editor/editor_settings.cpp | 8 +- editor/editor_spin_slider.cpp | 2 +- editor/project_manager.cpp | 2 +- main/main.cpp | 4 +- methods.py | 10 +- .../libgodot.ios.debug.xcframework/Info.plist | 40 + .../libgodot.ios.debug.xcframework/ios-arm64/empty | 1 + .../ios-arm64_x86_64-simulator/empty | 1 + .../libgodot.ios.release.xcframework/Info.plist | 40 + .../ios-arm64/empty | 1 + .../ios-arm64_x86_64-simulator/empty | 1 + .../libgodot.iphone.debug.xcframework/Info.plist | 40 - .../ios-arm64/empty | 1 - .../ios-arm64_x86_64-simulator/empty | 1 - .../libgodot.iphone.release.xcframework/Info.plist | 40 - .../ios-arm64/empty | 1 - .../ios-arm64_x86_64-simulator/empty | 1 - misc/dist/macos/editor.entitlements | 20 + misc/dist/macos_template.app/Contents/Info.plist | 48 + misc/dist/macos_template.app/Contents/PkgInfo | 1 + .../Contents/Resources/icon.icns | Bin 0 -> 67500 bytes misc/dist/macos_tools.app/Contents/Info.plist | 199 ++ misc/dist/macos_tools.app/Contents/PkgInfo | 1 + .../Contents/Resources/GDScript.icns | Bin 0 -> 185973 bytes .../macos_tools.app/Contents/Resources/Godot.icns | Bin 0 -> 271163 bytes .../Contents/Resources/Project.icns | Bin 0 -> 208221 bytes .../Contents/Resources/Resource.icns | Bin 0 -> 176973 bytes .../macos_tools.app/Contents/Resources/Scene.icns | Bin 0 -> 150612 bytes .../macos_tools.app/Contents/Resources/Shader.icns | Bin 0 -> 162062 bytes .../Contents/Resources/af.lproj/InfoPlist.strings | 0 .../Contents/Resources/ar.lproj/InfoPlist.strings | 0 .../Contents/Resources/az.lproj/InfoPlist.strings | 0 .../Contents/Resources/bg.lproj/InfoPlist.strings | 0 .../Contents/Resources/bn.lproj/InfoPlist.strings | 0 .../Contents/Resources/br.lproj/InfoPlist.strings | 0 .../Contents/Resources/ca.lproj/InfoPlist.strings | 0 .../Contents/Resources/cs.lproj/InfoPlist.strings | 0 .../Contents/Resources/da.lproj/InfoPlist.strings | 0 .../Contents/Resources/de.lproj/InfoPlist.strings | 0 .../Contents/Resources/el.lproj/InfoPlist.strings | 0 .../Contents/Resources/en.lproj/InfoPlist.strings | 0 .../Contents/Resources/eo.lproj/InfoPlist.strings | 0 .../Contents/Resources/es.lproj/InfoPlist.strings | 0 .../Resources/es_AR.lproj/InfoPlist.strings | 0 .../Contents/Resources/et.lproj/InfoPlist.strings | 0 .../Contents/Resources/eu.lproj/InfoPlist.strings | 0 .../Contents/Resources/fa.lproj/InfoPlist.strings | 0 .../Contents/Resources/fi.lproj/InfoPlist.strings | 0 .../Contents/Resources/fil.lproj/InfoPlist.strings | 0 .../Contents/Resources/fr.lproj/InfoPlist.strings | 0 .../Contents/Resources/ga.lproj/InfoPlist.strings | 0 .../Contents/Resources/gl.lproj/InfoPlist.strings | 0 .../Contents/Resources/he.lproj/InfoPlist.strings | 0 .../Contents/Resources/hi.lproj/InfoPlist.strings | 0 .../Contents/Resources/hr.lproj/InfoPlist.strings | 0 .../Contents/Resources/hu.lproj/InfoPlist.strings | 0 .../Contents/Resources/id.lproj/InfoPlist.strings | 0 .../Contents/Resources/is.lproj/InfoPlist.strings | 0 .../Contents/Resources/it.lproj/InfoPlist.strings | 0 .../Contents/Resources/ja.lproj/InfoPlist.strings | 0 .../Contents/Resources/ka.lproj/InfoPlist.strings | 0 .../Contents/Resources/km.lproj/InfoPlist.strings | 0 .../Contents/Resources/ko.lproj/InfoPlist.strings | 0 .../Contents/Resources/lt.lproj/InfoPlist.strings | 0 .../Contents/Resources/lv.lproj/InfoPlist.strings | 0 .../Contents/Resources/mi.lproj/InfoPlist.strings | 0 .../Contents/Resources/mk.lproj/InfoPlist.strings | 0 .../Contents/Resources/ml.lproj/InfoPlist.strings | 0 .../Contents/Resources/mr.lproj/InfoPlist.strings | 0 .../Contents/Resources/ms.lproj/InfoPlist.strings | 0 .../Contents/Resources/nb.lproj/InfoPlist.strings | 0 .../Contents/Resources/nl.lproj/InfoPlist.strings | 0 .../Contents/Resources/or.lproj/InfoPlist.strings | 0 .../Contents/Resources/pl.lproj/InfoPlist.strings | 0 .../Contents/Resources/pt.lproj/InfoPlist.strings | 0 .../Resources/pt_BR.lproj/InfoPlist.strings | 0 .../Contents/Resources/ro.lproj/InfoPlist.strings | 0 .../Contents/Resources/ru.lproj/InfoPlist.strings | 0 .../Contents/Resources/si.lproj/InfoPlist.strings | 0 .../Contents/Resources/sk.lproj/InfoPlist.strings | 0 .../Contents/Resources/sl.lproj/InfoPlist.strings | 0 .../Contents/Resources/sq.lproj/InfoPlist.strings | 0 .../Resources/sr-Cyrl.lproj/InfoPlist.strings | 0 .../Resources/sr-Latn.lproj/InfoPlist.strings | 0 .../Contents/Resources/sv.lproj/InfoPlist.strings | 0 .../Contents/Resources/ta.lproj/InfoPlist.strings | 0 .../Contents/Resources/te.lproj/InfoPlist.strings | 0 .../Contents/Resources/th.lproj/InfoPlist.strings | 0 .../Contents/Resources/tr.lproj/InfoPlist.strings | 0 .../Contents/Resources/tt.lproj/InfoPlist.strings | 0 .../Contents/Resources/tzm.lproj/InfoPlist.strings | 0 .../Contents/Resources/uk.lproj/InfoPlist.strings | 0 .../Resources/ur_PK.lproj/InfoPlist.strings | 0 .../Contents/Resources/vi.lproj/InfoPlist.strings | 0 .../Resources/zh_CN.lproj/InfoPlist.strings | 0 .../Resources/zh_HK.lproj/InfoPlist.strings | 0 .../Resources/zh_TW.lproj/InfoPlist.strings | 0 misc/dist/osx/editor.entitlements | 20 - misc/dist/osx_template.app/Contents/Info.plist | 48 - misc/dist/osx_template.app/Contents/PkgInfo | 1 - .../osx_template.app/Contents/Resources/icon.icns | Bin 67500 -> 0 bytes .../Resources/vulkan/icd.d/MoltenVK_icd.json | 7 - misc/dist/osx_tools.app/Contents/Info.plist | 199 -- misc/dist/osx_tools.app/Contents/PkgInfo | 1 - .../osx_tools.app/Contents/Resources/GDScript.icns | Bin 185973 -> 0 bytes .../osx_tools.app/Contents/Resources/Godot.icns | Bin 271163 -> 0 bytes .../osx_tools.app/Contents/Resources/Project.icns | Bin 208221 -> 0 bytes .../osx_tools.app/Contents/Resources/Resource.icns | Bin 176973 -> 0 bytes .../osx_tools.app/Contents/Resources/Scene.icns | Bin 150612 -> 0 bytes .../osx_tools.app/Contents/Resources/Shader.icns | Bin 162062 -> 0 bytes .../Contents/Resources/af.lproj/InfoPlist.strings | 0 .../Contents/Resources/ar.lproj/InfoPlist.strings | 0 .../Contents/Resources/az.lproj/InfoPlist.strings | 0 .../Contents/Resources/bg.lproj/InfoPlist.strings | 0 .../Contents/Resources/bn.lproj/InfoPlist.strings | 0 .../Contents/Resources/br.lproj/InfoPlist.strings | 0 .../Contents/Resources/ca.lproj/InfoPlist.strings | 0 .../Contents/Resources/cs.lproj/InfoPlist.strings | 0 .../Contents/Resources/da.lproj/InfoPlist.strings | 0 .../Contents/Resources/de.lproj/InfoPlist.strings | 0 .../Contents/Resources/el.lproj/InfoPlist.strings | 0 .../Contents/Resources/en.lproj/InfoPlist.strings | 0 .../Contents/Resources/eo.lproj/InfoPlist.strings | 0 .../Contents/Resources/es.lproj/InfoPlist.strings | 0 .../Resources/es_AR.lproj/InfoPlist.strings | 0 .../Contents/Resources/et.lproj/InfoPlist.strings | 0 .../Contents/Resources/eu.lproj/InfoPlist.strings | 0 .../Contents/Resources/fa.lproj/InfoPlist.strings | 0 .../Contents/Resources/fi.lproj/InfoPlist.strings | 0 .../Contents/Resources/fil.lproj/InfoPlist.strings | 0 .../Contents/Resources/fr.lproj/InfoPlist.strings | 0 .../Contents/Resources/ga.lproj/InfoPlist.strings | 0 .../Contents/Resources/gl.lproj/InfoPlist.strings | 0 .../Contents/Resources/he.lproj/InfoPlist.strings | 0 .../Contents/Resources/hi.lproj/InfoPlist.strings | 0 .../Contents/Resources/hr.lproj/InfoPlist.strings | 0 .../Contents/Resources/hu.lproj/InfoPlist.strings | 0 .../Contents/Resources/id.lproj/InfoPlist.strings | 0 .../Contents/Resources/is.lproj/InfoPlist.strings | 0 .../Contents/Resources/it.lproj/InfoPlist.strings | 0 .../Contents/Resources/ja.lproj/InfoPlist.strings | 0 .../Contents/Resources/ka.lproj/InfoPlist.strings | 0 .../Contents/Resources/km.lproj/InfoPlist.strings | 0 .../Contents/Resources/ko.lproj/InfoPlist.strings | 0 .../Contents/Resources/lt.lproj/InfoPlist.strings | 0 .../Contents/Resources/lv.lproj/InfoPlist.strings | 0 .../Contents/Resources/mi.lproj/InfoPlist.strings | 0 .../Contents/Resources/mk.lproj/InfoPlist.strings | 0 .../Contents/Resources/ml.lproj/InfoPlist.strings | 0 .../Contents/Resources/mr.lproj/InfoPlist.strings | 0 .../Contents/Resources/ms.lproj/InfoPlist.strings | 0 .../Contents/Resources/nb.lproj/InfoPlist.strings | 0 .../Contents/Resources/nl.lproj/InfoPlist.strings | 0 .../Contents/Resources/or.lproj/InfoPlist.strings | 0 .../Contents/Resources/pl.lproj/InfoPlist.strings | 0 .../Contents/Resources/pt.lproj/InfoPlist.strings | 0 .../Resources/pt_BR.lproj/InfoPlist.strings | 0 .../Contents/Resources/ro.lproj/InfoPlist.strings | 0 .../Contents/Resources/ru.lproj/InfoPlist.strings | 0 .../Contents/Resources/si.lproj/InfoPlist.strings | 0 .../Contents/Resources/sk.lproj/InfoPlist.strings | 0 .../Contents/Resources/sl.lproj/InfoPlist.strings | 0 .../Contents/Resources/sq.lproj/InfoPlist.strings | 0 .../Resources/sr-Cyrl.lproj/InfoPlist.strings | 0 .../Resources/sr-Latn.lproj/InfoPlist.strings | 0 .../Contents/Resources/sv.lproj/InfoPlist.strings | 0 .../Contents/Resources/ta.lproj/InfoPlist.strings | 0 .../Contents/Resources/te.lproj/InfoPlist.strings | 0 .../Contents/Resources/th.lproj/InfoPlist.strings | 0 .../Contents/Resources/tr.lproj/InfoPlist.strings | 0 .../Contents/Resources/tt.lproj/InfoPlist.strings | 0 .../Contents/Resources/tzm.lproj/InfoPlist.strings | 0 .../Contents/Resources/uk.lproj/InfoPlist.strings | 0 .../Resources/ur_PK.lproj/InfoPlist.strings | 0 .../Contents/Resources/vi.lproj/InfoPlist.strings | 0 .../Resources/vulkan/icd.d/MoltenVK_icd.json | 7 - .../Resources/zh_CN.lproj/InfoPlist.strings | 0 .../Resources/zh_HK.lproj/InfoPlist.strings | 0 .../Resources/zh_TW.lproj/InfoPlist.strings | 0 modules/camera/SCsub | 4 +- modules/camera/camera_macos.h | 46 + modules/camera/camera_macos.mm | 355 +++ modules/camera/camera_osx.h | 46 - modules/camera/camera_osx.mm | 355 --- modules/camera/config.py | 2 +- modules/camera/register_types.cpp | 8 +- modules/denoise/config.py | 2 +- .../gltf/editor/editor_scene_importer_blend.cpp | 4 +- modules/mobile_vr/mobile_vr_interface.cpp | 2 +- modules/mono/SCsub | 2 +- modules/mono/build_scripts/mono_configure.py | 16 +- modules/mono/config.py | 4 +- .../Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props | 6 +- .../GodotTools/GodotTools/Export/AotBuilder.cs | 4 +- .../GodotTools/GodotTools/Export/ExportPlugin.cs | 2 +- .../GodotTools/GodotTools/GodotSharpEditor.cs | 10 +- .../GodotTools/Ides/MonoDevelop/Instance.cs | 2 +- .../GodotTools/GodotTools/Internals/Internal.cs | 4 +- .../mono/editor/GodotTools/GodotTools/Utils/OS.cs | 4 +- modules/mono/editor/editor_internal_calls.cpp | 10 +- modules/mono/godotsharp_dirs.cpp | 4 +- modules/mono/mono_gd/gd_mono.cpp | 8 +- modules/mono/mono_gd/gd_mono_log.h | 2 +- modules/mono/mono_gd/gd_mono_method_thunk.h | 2 +- modules/mono/mono_gd/support/ios_support.h | 4 +- modules/mono/mono_gd/support/ios_support.mm | 4 +- modules/mono/utils/macos_utils.cpp | 58 + modules/mono/utils/macos_utils.h | 42 + modules/mono/utils/osx_utils.cpp | 58 - modules/mono/utils/osx_utils.h | 42 - .../text_server_adv/gdextension_build/SConstruct | 10 +- .../text_server_adv/gdextension_build/methods.py | 2 +- .../gdextension_build/text_server_adv.gdextension | 4 +- .../text_server_fb/gdextension_build/SConstruct | 10 +- .../text_server_fb/gdextension_build/methods.py | 2 +- .../gdextension_build/text_server_fb.gdextension | 4 +- .../visual_script/editor/visual_script_editor.cpp | 18 +- platform/ios/SCsub | 43 + platform/ios/api/api.cpp | 48 + platform/ios/api/api.h | 42 + platform/ios/app_delegate.h | 47 + platform/ios/app_delegate.mm | 149 + platform/ios/detect.py | 152 + platform/ios/device_metrics.h | 37 + platform/ios/device_metrics.m | 152 + platform/ios/display_layer.h | 58 + platform/ios/display_layer.mm | 173 + platform/ios/display_server_ios.h | 217 ++ platform/ios/display_server_ios.mm | 655 ++++ platform/ios/export/export.cpp | 40 + platform/ios/export/export.h | 36 + platform/ios/export/export_plugin.cpp | 1849 +++++++++++ platform/ios/export/export_plugin.h | 293 ++ platform/ios/export/godot_plugin_config.cpp | 285 ++ platform/ios/export/godot_plugin_config.h | 132 + platform/ios/godot_app_delegate.h | 41 + platform/ios/godot_app_delegate.m | 467 +++ platform/ios/godot_ios.mm | 131 + platform/ios/godot_view.h | 67 + platform/ios/godot_view.mm | 481 +++ platform/ios/godot_view_gesture_recognizer.h | 46 + platform/ios/godot_view_gesture_recognizer.mm | 186 ++ platform/ios/godot_view_renderer.h | 44 + platform/ios/godot_view_renderer.mm | 118 + platform/ios/ios.h | 61 + platform/ios/ios.mm | 180 ++ platform/ios/joypad_ios.h | 50 + platform/ios/joypad_ios.mm | 344 ++ platform/ios/keyboard_input_view.h | 37 + platform/ios/keyboard_input_view.mm | 197 ++ platform/ios/logo.png | Bin 0 -> 1297 bytes platform/ios/main.m | 56 + platform/ios/os_ios.h | 124 + platform/ios/os_ios.mm | 346 ++ platform/ios/platform_config.h | 44 + platform/ios/tts_ios.h | 63 + platform/ios/tts_ios.mm | 164 + platform/ios/view_controller.h | 42 + platform/ios/view_controller.mm | 240 ++ platform/ios/vulkan_context_ios.h | 48 + platform/ios/vulkan_context_ios.mm | 59 + platform/iphone/SCsub | 43 - platform/iphone/api/api.cpp | 48 - platform/iphone/api/api.h | 42 - platform/iphone/app_delegate.h | 47 - platform/iphone/app_delegate.mm | 149 - platform/iphone/detect.py | 152 - platform/iphone/device_metrics.h | 37 - platform/iphone/device_metrics.m | 152 - platform/iphone/display_layer.h | 58 - platform/iphone/display_layer.mm | 173 - platform/iphone/display_server_iphone.h | 217 -- platform/iphone/display_server_iphone.mm | 655 ---- platform/iphone/export/export.cpp | 40 - platform/iphone/export/export.h | 36 - platform/iphone/export/export_plugin.cpp | 1849 ----------- platform/iphone/export/export_plugin.h | 293 -- platform/iphone/export/godot_plugin_config.cpp | 285 -- platform/iphone/export/godot_plugin_config.h | 132 - platform/iphone/godot_app_delegate.h | 41 - platform/iphone/godot_app_delegate.m | 467 --- platform/iphone/godot_iphone.mm | 131 - platform/iphone/godot_view.h | 67 - platform/iphone/godot_view.mm | 481 --- platform/iphone/godot_view_gesture_recognizer.h | 46 - platform/iphone/godot_view_gesture_recognizer.mm | 186 -- platform/iphone/godot_view_renderer.h | 44 - platform/iphone/godot_view_renderer.mm | 118 - platform/iphone/ios.h | 61 - platform/iphone/ios.mm | 180 -- platform/iphone/joypad_iphone.h | 50 - platform/iphone/joypad_iphone.mm | 344 -- platform/iphone/keyboard_input_view.h | 37 - platform/iphone/keyboard_input_view.mm | 197 -- platform/iphone/logo.png | Bin 1297 -> 0 bytes platform/iphone/main.m | 56 - platform/iphone/os_iphone.h | 124 - platform/iphone/os_iphone.mm | 346 -- platform/iphone/platform_config.h | 44 - platform/iphone/tts_ios.h | 63 - platform/iphone/tts_ios.mm | 164 - platform/iphone/view_controller.h | 42 - platform/iphone/view_controller.mm | 240 -- platform/iphone/vulkan_context_iphone.h | 48 - platform/iphone/vulkan_context_iphone.mm | 59 - platform/macos/SCsub | 30 + platform/macos/crash_handler_macos.h | 47 + platform/macos/crash_handler_macos.mm | 203 ++ platform/macos/detect.py | 253 ++ platform/macos/dir_access_macos.h | 55 + platform/macos/dir_access_macos.mm | 81 + platform/macos/display_server_macos.h | 406 +++ platform/macos/display_server_macos.mm | 3304 ++++++++++++++++++++ platform/macos/export/codesign.cpp | 1564 +++++++++ platform/macos/export/codesign.h | 368 +++ platform/macos/export/export.cpp | 43 + platform/macos/export/export.h | 36 + platform/macos/export/export_plugin.cpp | 1673 ++++++++++ platform/macos/export/export_plugin.h | 137 + platform/macos/export/lipo.cpp | 236 ++ platform/macos/export/lipo.h | 76 + platform/macos/export/macho.cpp | 548 ++++ platform/macos/export/macho.h | 215 ++ platform/macos/export/plist.cpp | 570 ++++ platform/macos/export/plist.h | 116 + platform/macos/gl_manager_macos_legacy.h | 97 + platform/macos/gl_manager_macos_legacy.mm | 228 ++ platform/macos/godot_application.h | 42 + platform/macos/godot_application.mm | 58 + platform/macos/godot_application_delegate.h | 46 + platform/macos/godot_application_delegate.mm | 163 + platform/macos/godot_content_view.h | 66 + platform/macos/godot_content_view.mm | 771 +++++ platform/macos/godot_main_macos.mm | 92 + platform/macos/godot_menu_item.h | 61 + platform/macos/godot_window.h | 47 + platform/macos/godot_window.mm | 69 + platform/macos/godot_window_delegate.h | 47 + platform/macos/godot_window_delegate.mm | 270 ++ platform/macos/joypad_macos.cpp | 616 ++++ platform/macos/joypad_macos.h | 124 + platform/macos/key_mapping_macos.h | 52 + platform/macos/key_mapping_macos.mm | 496 +++ platform/macos/logo.png | Bin 0 -> 7195 bytes platform/macos/macos_terminal_logger.h | 44 + platform/macos/macos_terminal_logger.mm | 82 + platform/macos/os_macos.h | 120 + platform/macos/os_macos.mm | 524 ++++ platform/macos/platform_config.h | 34 + platform/macos/platform_macos_builders.py | 21 + platform/macos/tts_macos.h | 71 + platform/macos/tts_macos.mm | 266 ++ platform/macos/vulkan_context_macos.h | 47 + platform/macos/vulkan_context_macos.mm | 59 + platform/osx/SCsub | 30 - platform/osx/crash_handler_osx.h | 47 - platform/osx/crash_handler_osx.mm | 203 -- platform/osx/detect.py | 251 -- platform/osx/dir_access_osx.h | 55 - platform/osx/dir_access_osx.mm | 81 - platform/osx/display_server_osx.h | 406 --- platform/osx/display_server_osx.mm | 3304 -------------------- platform/osx/export/codesign.cpp | 1564 --------- platform/osx/export/codesign.h | 368 --- platform/osx/export/export.cpp | 43 - platform/osx/export/export.h | 36 - platform/osx/export/export_plugin.cpp | 1673 ---------- platform/osx/export/export_plugin.h | 137 - platform/osx/export/lipo.cpp | 236 -- platform/osx/export/lipo.h | 76 - platform/osx/export/macho.cpp | 548 ---- platform/osx/export/macho.h | 215 -- platform/osx/export/plist.cpp | 570 ---- platform/osx/export/plist.h | 116 - platform/osx/gl_manager_osx_legacy.h | 97 - platform/osx/gl_manager_osx_legacy.mm | 228 -- platform/osx/godot_application.h | 42 - platform/osx/godot_application.mm | 58 - platform/osx/godot_application_delegate.h | 46 - platform/osx/godot_application_delegate.mm | 163 - platform/osx/godot_content_view.h | 66 - platform/osx/godot_content_view.mm | 771 ----- platform/osx/godot_main_osx.mm | 92 - platform/osx/godot_menu_item.h | 61 - platform/osx/godot_window.h | 47 - platform/osx/godot_window.mm | 69 - platform/osx/godot_window_delegate.h | 47 - platform/osx/godot_window_delegate.mm | 270 -- platform/osx/joypad_osx.cpp | 616 ---- platform/osx/joypad_osx.h | 124 - platform/osx/key_mapping_osx.h | 52 - platform/osx/key_mapping_osx.mm | 496 --- platform/osx/logo.png | Bin 7195 -> 0 bytes platform/osx/os_osx.h | 120 - platform/osx/os_osx.mm | 524 ---- platform/osx/osx_terminal_logger.h | 44 - platform/osx/osx_terminal_logger.mm | 82 - platform/osx/platform_config.h | 34 - platform/osx/platform_osx_builders.py | 21 - platform/osx/tts_osx.h | 71 - platform/osx/tts_osx.mm | 266 -- platform/osx/vulkan_context_osx.h | 47 - platform/osx/vulkan_context_osx.mm | 59 - scene/gui/code_edit.cpp | 2 +- servers/rendering/renderer_rd/effects_rd.cpp | 2 +- .../renderer_rd/renderer_scene_render_rd.cpp | 14 +- servers/rendering/renderer_rd/shader_rd.cpp | 2 +- tests/scene/test_code_edit.h | 2 +- tests/scene/test_text_edit.h | 16 +- 427 files changed, 23640 insertions(+), 23638 deletions(-) create mode 100644 misc/dist/ios_xcode/libgodot.ios.debug.xcframework/Info.plist create mode 100644 misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64/empty create mode 100644 misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/empty create mode 100644 misc/dist/ios_xcode/libgodot.ios.release.xcframework/Info.plist create mode 100644 misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64/empty create mode 100644 misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/empty delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/Info.plist delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64/empty delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64_x86_64-simulator/empty delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.release.xcframework/Info.plist delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64/empty delete mode 100644 misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64_x86_64-simulator/empty create mode 100644 misc/dist/macos/editor.entitlements create mode 100644 misc/dist/macos_template.app/Contents/Info.plist create mode 100644 misc/dist/macos_template.app/Contents/PkgInfo create mode 100644 misc/dist/macos_template.app/Contents/Resources/icon.icns create mode 100644 misc/dist/macos_tools.app/Contents/Info.plist create mode 100644 misc/dist/macos_tools.app/Contents/PkgInfo create mode 100644 misc/dist/macos_tools.app/Contents/Resources/GDScript.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/Godot.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/Project.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/Resource.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/Scene.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/Shader.icns create mode 100644 misc/dist/macos_tools.app/Contents/Resources/af.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/az.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/br.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/da.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/de.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/el.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/en.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/es.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/et.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/he.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/id.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/is.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/it.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/km.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/or.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/si.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/te.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/th.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings create mode 100644 misc/dist/macos_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx/editor.entitlements delete mode 100644 misc/dist/osx_template.app/Contents/Info.plist delete mode 100644 misc/dist/osx_template.app/Contents/PkgInfo delete mode 100644 misc/dist/osx_template.app/Contents/Resources/icon.icns delete mode 100644 misc/dist/osx_template.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json delete mode 100644 misc/dist/osx_tools.app/Contents/Info.plist delete mode 100644 misc/dist/osx_tools.app/Contents/PkgInfo delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/GDScript.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/Godot.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/Project.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/Resource.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/Scene.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/Shader.icns delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/af.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/az.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/br.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/da.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/de.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/el.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/en.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/es.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/et.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/he.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/id.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/is.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/it.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/km.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/or.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/si.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/te.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/th.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings delete mode 100644 misc/dist/osx_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings create mode 100644 modules/camera/camera_macos.h create mode 100644 modules/camera/camera_macos.mm delete mode 100644 modules/camera/camera_osx.h delete mode 100644 modules/camera/camera_osx.mm create mode 100644 modules/mono/utils/macos_utils.cpp create mode 100644 modules/mono/utils/macos_utils.h delete mode 100644 modules/mono/utils/osx_utils.cpp delete mode 100644 modules/mono/utils/osx_utils.h create mode 100644 platform/ios/SCsub create mode 100644 platform/ios/api/api.cpp create mode 100644 platform/ios/api/api.h create mode 100644 platform/ios/app_delegate.h create mode 100644 platform/ios/app_delegate.mm create mode 100644 platform/ios/detect.py create mode 100644 platform/ios/device_metrics.h create mode 100644 platform/ios/device_metrics.m create mode 100644 platform/ios/display_layer.h create mode 100644 platform/ios/display_layer.mm create mode 100644 platform/ios/display_server_ios.h create mode 100644 platform/ios/display_server_ios.mm create mode 100644 platform/ios/export/export.cpp create mode 100644 platform/ios/export/export.h create mode 100644 platform/ios/export/export_plugin.cpp create mode 100644 platform/ios/export/export_plugin.h create mode 100644 platform/ios/export/godot_plugin_config.cpp create mode 100644 platform/ios/export/godot_plugin_config.h create mode 100644 platform/ios/godot_app_delegate.h create mode 100644 platform/ios/godot_app_delegate.m create mode 100644 platform/ios/godot_ios.mm create mode 100644 platform/ios/godot_view.h create mode 100644 platform/ios/godot_view.mm create mode 100644 platform/ios/godot_view_gesture_recognizer.h create mode 100644 platform/ios/godot_view_gesture_recognizer.mm create mode 100644 platform/ios/godot_view_renderer.h create mode 100644 platform/ios/godot_view_renderer.mm create mode 100644 platform/ios/ios.h create mode 100644 platform/ios/ios.mm create mode 100644 platform/ios/joypad_ios.h create mode 100644 platform/ios/joypad_ios.mm create mode 100644 platform/ios/keyboard_input_view.h create mode 100644 platform/ios/keyboard_input_view.mm create mode 100644 platform/ios/logo.png create mode 100644 platform/ios/main.m create mode 100644 platform/ios/os_ios.h create mode 100644 platform/ios/os_ios.mm create mode 100644 platform/ios/platform_config.h create mode 100644 platform/ios/tts_ios.h create mode 100644 platform/ios/tts_ios.mm create mode 100644 platform/ios/view_controller.h create mode 100644 platform/ios/view_controller.mm create mode 100644 platform/ios/vulkan_context_ios.h create mode 100644 platform/ios/vulkan_context_ios.mm delete mode 100644 platform/iphone/SCsub delete mode 100644 platform/iphone/api/api.cpp delete mode 100644 platform/iphone/api/api.h delete mode 100644 platform/iphone/app_delegate.h delete mode 100644 platform/iphone/app_delegate.mm delete mode 100644 platform/iphone/detect.py delete mode 100644 platform/iphone/device_metrics.h delete mode 100644 platform/iphone/device_metrics.m delete mode 100644 platform/iphone/display_layer.h delete mode 100644 platform/iphone/display_layer.mm delete mode 100644 platform/iphone/display_server_iphone.h delete mode 100644 platform/iphone/display_server_iphone.mm delete mode 100644 platform/iphone/export/export.cpp delete mode 100644 platform/iphone/export/export.h delete mode 100644 platform/iphone/export/export_plugin.cpp delete mode 100644 platform/iphone/export/export_plugin.h delete mode 100644 platform/iphone/export/godot_plugin_config.cpp delete mode 100644 platform/iphone/export/godot_plugin_config.h delete mode 100644 platform/iphone/godot_app_delegate.h delete mode 100644 platform/iphone/godot_app_delegate.m delete mode 100644 platform/iphone/godot_iphone.mm delete mode 100644 platform/iphone/godot_view.h delete mode 100644 platform/iphone/godot_view.mm delete mode 100644 platform/iphone/godot_view_gesture_recognizer.h delete mode 100644 platform/iphone/godot_view_gesture_recognizer.mm delete mode 100644 platform/iphone/godot_view_renderer.h delete mode 100644 platform/iphone/godot_view_renderer.mm delete mode 100644 platform/iphone/ios.h delete mode 100644 platform/iphone/ios.mm delete mode 100644 platform/iphone/joypad_iphone.h delete mode 100644 platform/iphone/joypad_iphone.mm delete mode 100644 platform/iphone/keyboard_input_view.h delete mode 100644 platform/iphone/keyboard_input_view.mm delete mode 100644 platform/iphone/logo.png delete mode 100644 platform/iphone/main.m delete mode 100644 platform/iphone/os_iphone.h delete mode 100644 platform/iphone/os_iphone.mm delete mode 100644 platform/iphone/platform_config.h delete mode 100644 platform/iphone/tts_ios.h delete mode 100644 platform/iphone/tts_ios.mm delete mode 100644 platform/iphone/view_controller.h delete mode 100644 platform/iphone/view_controller.mm delete mode 100644 platform/iphone/vulkan_context_iphone.h delete mode 100644 platform/iphone/vulkan_context_iphone.mm create mode 100644 platform/macos/SCsub create mode 100644 platform/macos/crash_handler_macos.h create mode 100644 platform/macos/crash_handler_macos.mm create mode 100644 platform/macos/detect.py create mode 100644 platform/macos/dir_access_macos.h create mode 100644 platform/macos/dir_access_macos.mm create mode 100644 platform/macos/display_server_macos.h create mode 100644 platform/macos/display_server_macos.mm create mode 100644 platform/macos/export/codesign.cpp create mode 100644 platform/macos/export/codesign.h create mode 100644 platform/macos/export/export.cpp create mode 100644 platform/macos/export/export.h create mode 100644 platform/macos/export/export_plugin.cpp create mode 100644 platform/macos/export/export_plugin.h create mode 100644 platform/macos/export/lipo.cpp create mode 100644 platform/macos/export/lipo.h create mode 100644 platform/macos/export/macho.cpp create mode 100644 platform/macos/export/macho.h create mode 100644 platform/macos/export/plist.cpp create mode 100644 platform/macos/export/plist.h create mode 100644 platform/macos/gl_manager_macos_legacy.h create mode 100644 platform/macos/gl_manager_macos_legacy.mm create mode 100644 platform/macos/godot_application.h create mode 100644 platform/macos/godot_application.mm create mode 100644 platform/macos/godot_application_delegate.h create mode 100644 platform/macos/godot_application_delegate.mm create mode 100644 platform/macos/godot_content_view.h create mode 100644 platform/macos/godot_content_view.mm create mode 100644 platform/macos/godot_main_macos.mm create mode 100644 platform/macos/godot_menu_item.h create mode 100644 platform/macos/godot_window.h create mode 100644 platform/macos/godot_window.mm create mode 100644 platform/macos/godot_window_delegate.h create mode 100644 platform/macos/godot_window_delegate.mm create mode 100644 platform/macos/joypad_macos.cpp create mode 100644 platform/macos/joypad_macos.h create mode 100644 platform/macos/key_mapping_macos.h create mode 100644 platform/macos/key_mapping_macos.mm create mode 100644 platform/macos/logo.png create mode 100644 platform/macos/macos_terminal_logger.h create mode 100644 platform/macos/macos_terminal_logger.mm create mode 100644 platform/macos/os_macos.h create mode 100644 platform/macos/os_macos.mm create mode 100644 platform/macos/platform_config.h create mode 100644 platform/macos/platform_macos_builders.py create mode 100644 platform/macos/tts_macos.h create mode 100644 platform/macos/tts_macos.mm create mode 100644 platform/macos/vulkan_context_macos.h create mode 100644 platform/macos/vulkan_context_macos.mm delete mode 100644 platform/osx/SCsub delete mode 100644 platform/osx/crash_handler_osx.h delete mode 100644 platform/osx/crash_handler_osx.mm delete mode 100644 platform/osx/detect.py delete mode 100644 platform/osx/dir_access_osx.h delete mode 100644 platform/osx/dir_access_osx.mm delete mode 100644 platform/osx/display_server_osx.h delete mode 100644 platform/osx/display_server_osx.mm delete mode 100644 platform/osx/export/codesign.cpp delete mode 100644 platform/osx/export/codesign.h delete mode 100644 platform/osx/export/export.cpp delete mode 100644 platform/osx/export/export.h delete mode 100644 platform/osx/export/export_plugin.cpp delete mode 100644 platform/osx/export/export_plugin.h delete mode 100644 platform/osx/export/lipo.cpp delete mode 100644 platform/osx/export/lipo.h delete mode 100644 platform/osx/export/macho.cpp delete mode 100644 platform/osx/export/macho.h delete mode 100644 platform/osx/export/plist.cpp delete mode 100644 platform/osx/export/plist.h delete mode 100644 platform/osx/gl_manager_osx_legacy.h delete mode 100644 platform/osx/gl_manager_osx_legacy.mm delete mode 100644 platform/osx/godot_application.h delete mode 100644 platform/osx/godot_application.mm delete mode 100644 platform/osx/godot_application_delegate.h delete mode 100644 platform/osx/godot_application_delegate.mm delete mode 100644 platform/osx/godot_content_view.h delete mode 100644 platform/osx/godot_content_view.mm delete mode 100644 platform/osx/godot_main_osx.mm delete mode 100644 platform/osx/godot_menu_item.h delete mode 100644 platform/osx/godot_window.h delete mode 100644 platform/osx/godot_window.mm delete mode 100644 platform/osx/godot_window_delegate.h delete mode 100644 platform/osx/godot_window_delegate.mm delete mode 100644 platform/osx/joypad_osx.cpp delete mode 100644 platform/osx/joypad_osx.h delete mode 100644 platform/osx/key_mapping_osx.h delete mode 100644 platform/osx/key_mapping_osx.mm delete mode 100644 platform/osx/logo.png delete mode 100644 platform/osx/os_osx.h delete mode 100644 platform/osx/os_osx.mm delete mode 100644 platform/osx/osx_terminal_logger.h delete mode 100644 platform/osx/osx_terminal_logger.mm delete mode 100644 platform/osx/platform_config.h delete mode 100644 platform/osx/platform_osx_builders.py delete mode 100644 platform/osx/tts_osx.h delete mode 100644 platform/osx/tts_osx.mm delete mode 100644 platform/osx/vulkan_context_osx.h delete mode 100644 platform/osx/vulkan_context_osx.mm diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ebbbe345fd..e7e88e95d7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -139,10 +139,10 @@ doc_classes/* @godotengine/documentation # Platform /platform/android/ @godotengine/android -/platform/iphone/ @godotengine/ios +/platform/ios/ @godotengine/ios /platform/javascript/ @godotengine/html5 /platform/linuxbsd/ @godotengine/linux-bsd -/platform/osx/ @godotengine/macos +/platform/macos/ @godotengine/macos /platform/uwp/ @godotengine/uwp /platform/windows/ @godotengine/windows diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 40f091e234..03277edc1d 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/actions/godot-build with: sconsflags: ${{ env.SCONSFLAGS }} - platform: iphone + platform: ios target: release tools: false tests: false diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 9b8ffc45a7..0cb037bfae 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -24,7 +24,7 @@ jobs: target: release_debug tools: true tests: true - bin: "./bin/godot.osx.opt.tools.64" + bin: "./bin/godot.macos.opt.tools.64" - name: Template (target=release, tools=no) cache-name: macos-template @@ -49,7 +49,7 @@ jobs: uses: ./.github/actions/godot-build with: sconsflags: ${{ env.SCONSFLAGS }} - platform: osx + platform: macos target: ${{ matrix.target }} tools: ${{ matrix.tools }} tests: ${{ matrix.tests }} diff --git a/SConstruct b/SConstruct index 0eba93e4ff..74f9d1158c 100644 --- a/SConstruct +++ b/SConstruct @@ -260,7 +260,7 @@ else: ): selected_platform = "linuxbsd" elif sys.platform == "darwin": - selected_platform = "osx" + selected_platform = "macos" elif sys.platform == "win32": selected_platform = "windows" else: @@ -272,6 +272,20 @@ else: if selected_platform != "": print("Automatically detected platform: " + selected_platform) +if selected_platform in ["macos", "osx"]: + if selected_platform == "osx": + # Deprecated alias kept for compatibility. + print('Platform "osx" has been renamed to "macos" in Godot 4.0. Building for platform "macos".') + # Alias for convenience. + selected_platform = "macos" + +if selected_platform in ["ios", "iphone"]: + if selected_platform == "iphone": + # Deprecated alias kept for compatibility. + print('Platform "iphone" has been renamed to "ios" in Godot 4.0. Building for platform "ios".') + # Alias for convenience. + selected_platform = "ios" + if selected_platform in ["linux", "bsd", "x11"]: if selected_platform == "x11": # Deprecated alias kept for compatibility. @@ -554,7 +568,7 @@ if selected_platform in platform_list: ) # Apple LLVM versions differ from upstream LLVM version \o/, compare # in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions - elif env["platform"] == "osx" or env["platform"] == "iphone": + elif env["platform"] == "macos" or env["platform"] == "ios": vanilla = methods.is_vanilla_clang(env) if vanilla and cc_version_major < 6: print( diff --git a/core/SCsub b/core/SCsub index df3e7a547a..97080b8710 100644 --- a/core/SCsub +++ b/core/SCsub @@ -129,7 +129,7 @@ if env["builtin_zstd"]: "decompress/zstd_decompress_block.c", "decompress/zstd_decompress.c", ] - if env["platform"] in ["android", "iphone", "linuxbsd", "osx"]: + if env["platform"] in ["android", "ios", "linuxbsd", "macos"]: # Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S") thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources] diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 7145e628c1..f9bac58ffa 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -488,7 +488,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b // We need to test both possibilities as extensions for Linux binaries are optional // (so both 'mygame.bin' and 'mygame' should be able to find 'mygame.pck'). -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (!found) { // Attempt to load PCK from macOS .app bundle resources. found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_filename + ".pck")); diff --git a/core/input/input_builders.py b/core/input/input_builders.py index 748ec06133..c0dac26f02 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -47,9 +47,9 @@ def make_default_controller_mappings(target, source, env): platform_variables = { "Linux": "#if X11_ENABLED", "Windows": "#ifdef WINDOWS_ENABLED", - "Mac OS X": "#ifdef OSX_ENABLED", + "Mac OS X": "#ifdef MACOS_ENABLED", "Android": "#if defined(__ANDROID__)", - "iOS": "#ifdef IPHONE_ENABLED", + "iOS": "#ifdef IOS_ENABLED", "Javascript": "#ifdef JAVASCRIPT_ENABLED", "UWP": "#ifdef UWP_ENABLED", } diff --git a/core/os/keyboard.cpp b/core/os/keyboard.cpp index 3e690991d9..a592791d06 100644 --- a/core/os/keyboard.cpp +++ b/core/os/keyboard.cpp @@ -61,7 +61,7 @@ static const _KeyCodeText _keycodes[] = { {Key::PAGEDOWN ,"PageDown"}, {Key::SHIFT ,"Shift"}, {Key::CTRL ,"Ctrl"}, -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED {Key::META ,"Command"}, #else {Key::META ,"Meta"}, diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 8aa2db2cf8..f217fbaf48 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -96,7 +96,7 @@ Adds a static lib from the given [code]path[/code] to the iOS project. - + diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp index 276e69e470..cc38c2352f 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.cpp +++ b/drivers/coreaudio/audio_driver_coreaudio.cpp @@ -38,7 +38,7 @@ #define kOutputBus 0 #define kInputBus 1 -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED OSStatus AudioDriverCoreAudio::input_device_address_cb(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData) { @@ -72,7 +72,7 @@ Error AudioDriverCoreAudio::init() { AudioComponentDescription desc; memset(&desc, 0, sizeof(desc)); desc.componentType = kAudioUnitType_Output; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED desc.componentSubType = kAudioUnitSubType_HALOutput; #else desc.componentSubType = kAudioUnitSubType_RemoteIO; @@ -85,7 +85,7 @@ Error AudioDriverCoreAudio::init() { OSStatus result = AudioComponentInstanceNew(comp, &audio_unit); ERR_FAIL_COND_V(result != noErr, FAILED); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED AudioObjectPropertyAddress prop; prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice; prop.mScope = kAudioObjectPropertyScopeGlobal; @@ -135,7 +135,7 @@ Error AudioDriverCoreAudio::init() { // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) buffer_frames = closest_power_of_2(latency * mix_rate / 1000); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED result = AudioUnitSetProperty(audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, kOutputBus, &buffer_frames, sizeof(UInt32)); ERR_FAIL_COND_V(result != noErr, FAILED); #endif @@ -313,7 +313,7 @@ void AudioDriverCoreAudio::finish() { ERR_PRINT("AudioUnitUninitialize failed"); } -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED AudioObjectPropertyAddress prop; prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice; prop.mScope = kAudioObjectPropertyScopeGlobal; @@ -339,7 +339,7 @@ Error AudioDriverCoreAudio::capture_init() { AudioComponentDescription desc; memset(&desc, 0, sizeof(desc)); desc.componentType = kAudioUnitType_Output; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED desc.componentSubType = kAudioUnitSubType_HALOutput; #else desc.componentSubType = kAudioUnitSubType_RemoteIO; @@ -352,7 +352,7 @@ Error AudioDriverCoreAudio::capture_init() { OSStatus result = AudioComponentInstanceNew(comp, &input_unit); ERR_FAIL_COND_V(result != noErr, FAILED); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED AudioObjectPropertyAddress prop; prop.mSelector = kAudioHardwarePropertyDefaultInputDevice; prop.mScope = kAudioObjectPropertyScopeGlobal; @@ -370,7 +370,7 @@ Error AudioDriverCoreAudio::capture_init() { ERR_FAIL_COND_V(result != noErr, FAILED); UInt32 size; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED AudioDeviceID deviceId; size = sizeof(AudioDeviceID); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; @@ -447,7 +447,7 @@ void AudioDriverCoreAudio::capture_finish() { ERR_PRINT("AudioUnitUninitialize failed"); } -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED AudioObjectPropertyAddress prop; prop.mSelector = kAudioHardwarePropertyDefaultInputDevice; prop.mScope = kAudioObjectPropertyScopeGlobal; @@ -491,7 +491,7 @@ Error AudioDriverCoreAudio::capture_stop() { return OK; } -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED Array AudioDriverCoreAudio::_get_device_list(bool capture) { Array list; diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h index f86037f092..0a4bbd662d 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.h +++ b/drivers/coreaudio/audio_driver_coreaudio.h @@ -36,7 +36,7 @@ #include "servers/audio_server.h" #import -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED #import #endif @@ -58,7 +58,7 @@ class AudioDriverCoreAudio : public AudioDriver { Vector samples_in; Vector input_buf; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED Array _get_device_list(bool capture = false); void _set_device(const String &device, bool capture = false); @@ -106,7 +106,7 @@ public: bool try_lock(); void stop(); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED virtual Array get_device_list(); virtual String get_device(); virtual void set_device(String device); diff --git a/drivers/gl_context/SCsub b/drivers/gl_context/SCsub index ddeec6f4c6..7e8bd22960 100644 --- a/drivers/gl_context/SCsub +++ b/drivers/gl_context/SCsub @@ -2,7 +2,7 @@ Import("env") -if env["platform"] in ["haiku", "osx", "windows", "linuxbsd"]: +if env["platform"] in ["haiku", "macos", "windows", "linuxbsd"]: # Thirdparty source files thirdparty_dir = "#thirdparty/glad/" thirdparty_sources = [ diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 613a7f37d9..33303b1e38 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -69,7 +69,7 @@ #endif #endif -#if !defined(IPHONE_ENABLED) && !defined(JAVASCRIPT_ENABLED) +#if !defined(IOS_ENABLED) && !defined(JAVASCRIPT_ENABLED) // We include EGL below to get debug callback on GLES2 platforms, // but EGL is not available on iOS. #define CAN_DEBUG diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 091287c652..5bf14056ab 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -65,7 +65,7 @@ #include #include -#if defined(OSX_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) +#if defined(MACOS_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) // Random location for getentropy. Fitting. #include #define UNIX_GET_ENTROPY diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub index f7de2c4a7e..a076c0ac54 100644 --- a/drivers/vulkan/SCsub +++ b/drivers/vulkan/SCsub @@ -15,11 +15,11 @@ if env["use_volk"]: if env["platform"] == "android": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_ANDROID_KHR"]) -elif env["platform"] == "iphone": +elif env["platform"] == "ios": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK"]) elif env["platform"] == "linuxbsd" and env["x11"]: env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_XLIB_KHR"]) -elif env["platform"] == "osx": +elif env["platform"] == "macos": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_MACOS_MVK"]) elif env["platform"] == "windows": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_WIN32_KHR"]) @@ -40,7 +40,7 @@ elif env["platform"] == "android": # Our current NDK version only provides old Vulkan headers, # so we have to limit VMA. env_thirdparty_vma.AppendUnique(CPPDEFINES=["VMA_VULKAN_VERSION=1000000"]) -elif env["platform"] == "osx" or env["platform"] == "iphone": +elif env["platform"] == "macos" or env["platform"] == "ios": # MoltenVK supports only Vulkan 1.1 API, limit VMA to the same version. env_thirdparty_vma.AppendUnique(CPPDEFINES=["VMA_VULKAN_VERSION=1001000"]) diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index bb9d930cf5..873bb0e1e5 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -698,12 +698,12 @@ String EditorExportPlugin::get_ios_cpp_code() const { return ios_cpp_code; } -void EditorExportPlugin::add_osx_plugin_file(const String &p_path) { - osx_plugin_files.push_back(p_path); +void EditorExportPlugin::add_macos_plugin_file(const String &p_path) { + macos_plugin_files.push_back(p_path); } -const Vector &EditorExportPlugin::get_osx_plugin_files() const { - return osx_plugin_files; +const Vector &EditorExportPlugin::get_macos_plugin_files() const { + return macos_plugin_files; } void EditorExportPlugin::add_ios_project_static_lib(const String &p_path) { @@ -746,7 +746,7 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags); ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file); ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code); - ClassDB::bind_method(D_METHOD("add_osx_plugin_file", "path"), &EditorExportPlugin::add_osx_plugin_file); + ClassDB::bind_method(D_METHOD("add_macos_plugin_file", "path"), &EditorExportPlugin::add_macos_plugin_file); ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); GDVIRTUAL_BIND(_export_file, "path", "type", "features"); diff --git a/editor/editor_export.h b/editor/editor_export.h index 6f41736d2d..9179a3e2b0 100644 --- a/editor/editor_export.h +++ b/editor/editor_export.h @@ -366,7 +366,7 @@ class EditorExportPlugin : public RefCounted { Vector ios_bundle_files; String ios_cpp_code; - Vector osx_plugin_files; + Vector macos_plugin_files; _FORCE_INLINE_ void _clear() { shared_objects.clear(); @@ -381,7 +381,7 @@ class EditorExportPlugin : public RefCounted { ios_plist_content = ""; ios_linker_flags = ""; ios_cpp_code = ""; - osx_plugin_files.clear(); + macos_plugin_files.clear(); } void _export_file_script(const String &p_path, const String &p_type, const Vector &p_features); @@ -402,7 +402,7 @@ protected: void add_ios_linker_flags(const String &p_flags); void add_ios_bundle_file(const String &p_path); void add_ios_cpp_code(const String &p_code); - void add_osx_plugin_file(const String &p_path); + void add_macos_plugin_file(const String &p_path); void skip(); @@ -423,7 +423,7 @@ public: String get_ios_linker_flags() const; Vector get_ios_bundle_files() const; String get_ios_cpp_code() const; - const Vector &get_osx_plugin_files() const; + const Vector &get_macos_plugin_files() const; EditorExportPlugin(); }; diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index d58dc98f07..a02051c8ee 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -102,7 +102,7 @@ void editor_register_fonts(Ref p_theme) { // - macOS doesn't use font hinting. // - Windows uses ClearType, which is in between "Light" and "Normal" hinting. // - Linux has configurable font hinting, but most distributions including Ubuntu default to "Light". -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED font_hinting = TextServer::HINTING_NONE; #else font_hinting = TextServer::HINTING_LIGHT; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index fa8643af86..abb1b73a18 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -414,7 +414,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("interface/editor/code_font_custom_opentype_features", ""); _initial_set("interface/editor/code_font_custom_variations", ""); _initial_set("interface/editor/font_antialiased", true); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_hinting", 0, "Auto (None),None,Light,Normal") #else EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_hinting", 0, "Auto (Light),None,Light,Normal") @@ -1370,7 +1370,7 @@ String EditorSettings::get_editor_layouts_config() const { } float EditorSettings::get_auto_display_scale() const { -#if defined(OSX_ENABLED) || defined(ANDROID_ENABLED) +#if defined(MACOS_ENABLED) || defined(ANDROID_ENABLED) return DisplayServer::get_singleton()->screen_get_max_scale(); #else const int screen = DisplayServer::get_singleton()->window_get_current_screen(); @@ -1489,7 +1489,7 @@ void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, c for (int i = 0; i < p_keycodes.size(); i++) { Key keycode = (Key)p_keycodes[i]; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS if (keycode == Key::KEY_DELETE) { keycode = KeyModifierMask::CMD | Key::BACKSPACE; @@ -1519,7 +1519,7 @@ Ref ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, cons for (int i = 0; i < p_keycodes.size(); i++) { Key keycode = (Key)p_keycodes[i]; -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS if (keycode == Key::KEY_DELETE) { keycode = KeyModifierMask::CMD | Key::BACKSPACE; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 5c0ccd11f0..20e9d7a3df 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -37,7 +37,7 @@ String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const { if (grabber->is_visible()) { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED Key key = Key::META; #else Key key = Key::CTRL; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index a98578ad7d..327ff6bb2d 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1998,7 +1998,7 @@ void ProjectManager::shortcut_input(const Ref &p_ev) { // Pressing Command + Q quits the Project Manager // This is handled by the platform implementation on macOS, // so only define the shortcut on other platforms -#ifndef OSX_ENABLED +#ifndef MACOS_ENABLED if (k->get_keycode_with_modifiers() == (KeyModifierMask::CMD | Key::Q)) { _dim_window(); get_tree()->quit(); diff --git a/main/main.cpp b/main/main.cpp index 8f4f348dba..12d1196cdf 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -685,7 +685,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph I = args.front(); while (I) { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED // Ignore the process serial number argument passed by macOS Gatekeeper. // Otherwise, Godot would try to open a non-existent project on the first start and abort. if (I->get().begins_with("-psn_")) { @@ -2668,7 +2668,7 @@ bool Main::start() { ERR_FAIL_COND_V_MSG(!scene, false, "Failed loading scene: " + local_game_path); sml->add_current_scene(scene); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED String mac_iconpath = GLOBAL_DEF("application/config/macos_native_icon", "Variant()"); if (!mac_iconpath.is_empty()) { DisplayServer::get_singleton()->set_native_icon(mac_iconpath); diff --git a/methods.py b/methods.py index b4a55cab79..1db3a8aa01 100644 --- a/methods.py +++ b/methods.py @@ -833,15 +833,15 @@ def Run(env, function, short_message, subprocess=True): def detect_darwin_sdk_path(platform, env): sdk_name = "" - if platform == "osx": + if platform == "macos": sdk_name = "macosx" var_name = "MACOS_SDK_PATH" - elif platform == "iphone": + elif platform == "ios": sdk_name = "iphoneos" - var_name = "IPHONESDK" - elif platform == "iphonesimulator": + var_name = "IOS_SDK_PATH" + elif platform == "iossimulator": sdk_name = "iphonesimulator" - var_name = "IPHONESDK" + var_name = "IOS_SDK_PATH" else: raise Exception("Invalid platform argument passed to detect_darwin_sdk_path") diff --git a/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/Info.plist b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/Info.plist new file mode 100644 index 0000000000..846533594f --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64/empty b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64/empty new file mode 100644 index 0000000000..bd3e894333 --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/empty b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/empty new file mode 100644 index 0000000000..bd3e894333 --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.ios.release.xcframework/Info.plist b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/Info.plist new file mode 100644 index 0000000000..846533594f --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgodot.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64/empty b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64/empty new file mode 100644 index 0000000000..bd3e894333 --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/empty b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/empty new file mode 100644 index 0000000000..bd3e894333 --- /dev/null +++ b/misc/dist/ios_xcode/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/empty @@ -0,0 +1 @@ +Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/Info.plist b/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/Info.plist deleted file mode 100644 index 846533594f..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - AvailableLibraries - - - LibraryIdentifier - ios-arm64 - LibraryPath - libgodot.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - LibraryIdentifier - ios-arm64_x86_64-simulator - LibraryPath - libgodot.a - SupportedArchitectures - - arm64 - x86_64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - - diff --git a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64/empty b/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64/empty deleted file mode 100644 index bd3e894333..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64/empty +++ /dev/null @@ -1 +0,0 @@ -Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64_x86_64-simulator/empty b/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64_x86_64-simulator/empty deleted file mode 100644 index bd3e894333..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.debug.xcframework/ios-arm64_x86_64-simulator/empty +++ /dev/null @@ -1 +0,0 @@ -Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/Info.plist b/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/Info.plist deleted file mode 100644 index 846533594f..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - AvailableLibraries - - - LibraryIdentifier - ios-arm64 - LibraryPath - libgodot.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - LibraryIdentifier - ios-arm64_x86_64-simulator - LibraryPath - libgodot.a - SupportedArchitectures - - arm64 - x86_64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - - diff --git a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64/empty b/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64/empty deleted file mode 100644 index bd3e894333..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64/empty +++ /dev/null @@ -1 +0,0 @@ -Dummy file to make dylibs folder exported diff --git a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64_x86_64-simulator/empty b/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64_x86_64-simulator/empty deleted file mode 100644 index bd3e894333..0000000000 --- a/misc/dist/ios_xcode/libgodot.iphone.release.xcframework/ios-arm64_x86_64-simulator/empty +++ /dev/null @@ -1 +0,0 @@ -Dummy file to make dylibs folder exported diff --git a/misc/dist/macos/editor.entitlements b/misc/dist/macos/editor.entitlements new file mode 100644 index 0000000000..d0137910a3 --- /dev/null +++ b/misc/dist/macos/editor.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.disable-library-validation + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + + diff --git a/misc/dist/macos_template.app/Contents/Info.plist b/misc/dist/macos_template.app/Contents/Info.plist new file mode 100644 index 0000000000..542146cdb8 --- /dev/null +++ b/misc/dist/macos_template.app/Contents/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + $binary + CFBundleName + $name + CFBundleDisplayName + $name + CFBundleIconFile + icon.icns + CFBundleIdentifier + $bundle_identifier + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + $short_version + CFBundleSignature + $signature + CFBundleVersion + $version +$usage_descriptions + NSHumanReadableCopyright + $copyright + CFBundleSupportedPlatforms + + MacOSX + + NSPrincipalClass + NSApplication + LSApplicationCategoryType + public.app-category.$app_category + LSMinimumSystemVersion + 10.12 + LSMinimumSystemVersionByArchitecture + + x86_64 + 10.12 + + NSHighResolutionCapable +$highres + + diff --git a/misc/dist/macos_template.app/Contents/PkgInfo b/misc/dist/macos_template.app/Contents/PkgInfo new file mode 100644 index 0000000000..6f749b0f37 --- /dev/null +++ b/misc/dist/macos_template.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? diff --git a/misc/dist/macos_template.app/Contents/Resources/icon.icns b/misc/dist/macos_template.app/Contents/Resources/icon.icns new file mode 100644 index 0000000000..be9254630c Binary files /dev/null and b/misc/dist/macos_template.app/Contents/Resources/icon.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Info.plist b/misc/dist/macos_tools.app/Contents/Info.plist new file mode 100644 index 0000000000..886df87cc6 --- /dev/null +++ b/misc/dist/macos_tools.app/Contents/Info.plist @@ -0,0 +1,199 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Godot + CFBundleName + Godot + CFBundleIconFile + Godot.icns + CFBundleIdentifier + org.godotengine.godot + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 4.0 + CFBundleSignature + godot + CFBundleVersion + 4.0 + NSMicrophoneUsageDescription + Microphone access is required to capture audio. + NSCameraUsageDescription + Camera access is required to capture video. + NSRequiresAquaSystemAppearance + + NSHumanReadableCopyright + © 2007-2022 Juan Linietsky, Ariel Manzur & Godot Engine contributors + CFBundleSupportedPlatforms + + MacOSX + + NSPrincipalClass + NSApplication + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 10.12 + LSMinimumSystemVersionByArchitecture + + x86_64 + 10.12 + + NSHighResolutionCapable + + CFBundleDocumentTypes + + + CFBundleTypeRole + Editor + LSItemContentTypes + + public.tscn + + NSExportableTypes + + public.tscn + + + + CFBundleTypeRole + Editor + LSItemContentTypes + + public.godot + + NSExportableTypes + + public.godot + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + public.tscn + UTTypeReferenceURL + + UTTypeDescription + Godot Engine scene + UTTypeIconFile + Scene.icns + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + scn + tscn + escn + + public.mime-type + application/x-godot-scene + + + + UTTypeIdentifier + public.gd + UTTypeReferenceURL + + UTTypeDescription + GDScript script + UTTypeIconFile + GDScript.icns + UTTypeConformsTo + + public.script + + UTTypeTagSpecification + + public.filename-extension + + gd + + public.mime-type + application/x-gdscript + + + + UTTypeIdentifier + public.res + UTTypeReferenceURL + + UTTypeDescription + Godot Engine resource + UTTypeIconFile + Resource.icns + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + res + tres + + public.mime-type + application/x-godot-resource + + + + UTTypeIdentifier + public.gdshader + UTTypeReferenceURL + + UTTypeDescription + Godot Engine shader + UTTypeIconFile + Shader.icns + UTTypeConformsTo + + public.script + + UTTypeTagSpecification + + public.filename-extension + + gdshader + + public.mime-type + application/x-godot-shader + + + + UTTypeIdentifier + public.godot + UTTypeReferenceURL + + UTTypeDescription + Godot Engine project + UTTypeIconFile + Project.icns + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + godot + + public.mime-type + application/x-godot-project + + + + + diff --git a/misc/dist/macos_tools.app/Contents/PkgInfo b/misc/dist/macos_tools.app/Contents/PkgInfo new file mode 100644 index 0000000000..6f749b0f37 --- /dev/null +++ b/misc/dist/macos_tools.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? diff --git a/misc/dist/macos_tools.app/Contents/Resources/GDScript.icns b/misc/dist/macos_tools.app/Contents/Resources/GDScript.icns new file mode 100644 index 0000000000..b08e8df339 Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/GDScript.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/Godot.icns b/misc/dist/macos_tools.app/Contents/Resources/Godot.icns new file mode 100644 index 0000000000..61697976c6 Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/Godot.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/Project.icns b/misc/dist/macos_tools.app/Contents/Resources/Project.icns new file mode 100644 index 0000000000..10e31528e4 Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/Project.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/Resource.icns b/misc/dist/macos_tools.app/Contents/Resources/Resource.icns new file mode 100644 index 0000000000..9648cb616e Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/Resource.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/Scene.icns b/misc/dist/macos_tools.app/Contents/Resources/Scene.icns new file mode 100644 index 0000000000..c8c3dee07e Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/Scene.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/Shader.icns b/misc/dist/macos_tools.app/Contents/Resources/Shader.icns new file mode 100644 index 0000000000..a76e648a1a Binary files /dev/null and b/misc/dist/macos_tools.app/Contents/Resources/Shader.icns differ diff --git a/misc/dist/macos_tools.app/Contents/Resources/af.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/af.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/az.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/az.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/br.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/br.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/da.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/da.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/de.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/de.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/el.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/el.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/en.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/en.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/es.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/es.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/et.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/et.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/he.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/he.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/id.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/id.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/is.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/is.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/it.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/it.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/km.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/km.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/or.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/or.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/si.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/si.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/te.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/te.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/th.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/th.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/macos_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings b/misc/dist/macos_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/dist/osx/editor.entitlements b/misc/dist/osx/editor.entitlements deleted file mode 100644 index d0137910a3..0000000000 --- a/misc/dist/osx/editor.entitlements +++ /dev/null @@ -1,20 +0,0 @@ - - - - - com.apple.security.cs.allow-dyld-environment-variables - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-executable-page-protection - - com.apple.security.cs.disable-library-validation - - com.apple.security.device.audio-input - - com.apple.security.device.camera - - - diff --git a/misc/dist/osx_template.app/Contents/Info.plist b/misc/dist/osx_template.app/Contents/Info.plist deleted file mode 100644 index 542146cdb8..0000000000 --- a/misc/dist/osx_template.app/Contents/Info.plist +++ /dev/null @@ -1,48 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - $binary - CFBundleName - $name - CFBundleDisplayName - $name - CFBundleIconFile - icon.icns - CFBundleIdentifier - $bundle_identifier - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleShortVersionString - $short_version - CFBundleSignature - $signature - CFBundleVersion - $version -$usage_descriptions - NSHumanReadableCopyright - $copyright - CFBundleSupportedPlatforms - - MacOSX - - NSPrincipalClass - NSApplication - LSApplicationCategoryType - public.app-category.$app_category - LSMinimumSystemVersion - 10.12 - LSMinimumSystemVersionByArchitecture - - x86_64 - 10.12 - - NSHighResolutionCapable -$highres - - diff --git a/misc/dist/osx_template.app/Contents/PkgInfo b/misc/dist/osx_template.app/Contents/PkgInfo deleted file mode 100644 index 6f749b0f37..0000000000 --- a/misc/dist/osx_template.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? diff --git a/misc/dist/osx_template.app/Contents/Resources/icon.icns b/misc/dist/osx_template.app/Contents/Resources/icon.icns deleted file mode 100644 index be9254630c..0000000000 Binary files a/misc/dist/osx_template.app/Contents/Resources/icon.icns and /dev/null differ diff --git a/misc/dist/osx_template.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json b/misc/dist/osx_template.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json deleted file mode 100644 index c4f8f71d0e..0000000000 --- a/misc/dist/osx_template.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "file_format_version" : "1.0.0", - "ICD": { - "library_path": "../../../Frameworks/libMoltenVK.dylib", - "api_version" : "1.1.0" - } -} diff --git a/misc/dist/osx_tools.app/Contents/Info.plist b/misc/dist/osx_tools.app/Contents/Info.plist deleted file mode 100644 index 886df87cc6..0000000000 --- a/misc/dist/osx_tools.app/Contents/Info.plist +++ /dev/null @@ -1,199 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Godot - CFBundleName - Godot - CFBundleIconFile - Godot.icns - CFBundleIdentifier - org.godotengine.godot - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleShortVersionString - 4.0 - CFBundleSignature - godot - CFBundleVersion - 4.0 - NSMicrophoneUsageDescription - Microphone access is required to capture audio. - NSCameraUsageDescription - Camera access is required to capture video. - NSRequiresAquaSystemAppearance - - NSHumanReadableCopyright - © 2007-2022 Juan Linietsky, Ariel Manzur & Godot Engine contributors - CFBundleSupportedPlatforms - - MacOSX - - NSPrincipalClass - NSApplication - LSApplicationCategoryType - public.app-category.developer-tools - LSMinimumSystemVersion - 10.12 - LSMinimumSystemVersionByArchitecture - - x86_64 - 10.12 - - NSHighResolutionCapable - - CFBundleDocumentTypes - - - CFBundleTypeRole - Editor - LSItemContentTypes - - public.tscn - - NSExportableTypes - - public.tscn - - - - CFBundleTypeRole - Editor - LSItemContentTypes - - public.godot - - NSExportableTypes - - public.godot - - - - UTExportedTypeDeclarations - - - UTTypeIdentifier - public.tscn - UTTypeReferenceURL - - UTTypeDescription - Godot Engine scene - UTTypeIconFile - Scene.icns - UTTypeConformsTo - - public.data - - UTTypeTagSpecification - - public.filename-extension - - scn - tscn - escn - - public.mime-type - application/x-godot-scene - - - - UTTypeIdentifier - public.gd - UTTypeReferenceURL - - UTTypeDescription - GDScript script - UTTypeIconFile - GDScript.icns - UTTypeConformsTo - - public.script - - UTTypeTagSpecification - - public.filename-extension - - gd - - public.mime-type - application/x-gdscript - - - - UTTypeIdentifier - public.res - UTTypeReferenceURL - - UTTypeDescription - Godot Engine resource - UTTypeIconFile - Resource.icns - UTTypeConformsTo - - public.data - - UTTypeTagSpecification - - public.filename-extension - - res - tres - - public.mime-type - application/x-godot-resource - - - - UTTypeIdentifier - public.gdshader - UTTypeReferenceURL - - UTTypeDescription - Godot Engine shader - UTTypeIconFile - Shader.icns - UTTypeConformsTo - - public.script - - UTTypeTagSpecification - - public.filename-extension - - gdshader - - public.mime-type - application/x-godot-shader - - - - UTTypeIdentifier - public.godot - UTTypeReferenceURL - - UTTypeDescription - Godot Engine project - UTTypeIconFile - Project.icns - UTTypeConformsTo - - public.data - - UTTypeTagSpecification - - public.filename-extension - - godot - - public.mime-type - application/x-godot-project - - - - - diff --git a/misc/dist/osx_tools.app/Contents/PkgInfo b/misc/dist/osx_tools.app/Contents/PkgInfo deleted file mode 100644 index 6f749b0f37..0000000000 --- a/misc/dist/osx_tools.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? diff --git a/misc/dist/osx_tools.app/Contents/Resources/GDScript.icns b/misc/dist/osx_tools.app/Contents/Resources/GDScript.icns deleted file mode 100644 index b08e8df339..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/GDScript.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/Godot.icns b/misc/dist/osx_tools.app/Contents/Resources/Godot.icns deleted file mode 100644 index 61697976c6..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/Godot.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/Project.icns b/misc/dist/osx_tools.app/Contents/Resources/Project.icns deleted file mode 100644 index 10e31528e4..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/Project.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/Resource.icns b/misc/dist/osx_tools.app/Contents/Resources/Resource.icns deleted file mode 100644 index 9648cb616e..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/Resource.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/Scene.icns b/misc/dist/osx_tools.app/Contents/Resources/Scene.icns deleted file mode 100644 index c8c3dee07e..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/Scene.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/Shader.icns b/misc/dist/osx_tools.app/Contents/Resources/Shader.icns deleted file mode 100644 index a76e648a1a..0000000000 Binary files a/misc/dist/osx_tools.app/Contents/Resources/Shader.icns and /dev/null differ diff --git a/misc/dist/osx_tools.app/Contents/Resources/af.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/af.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ar.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/az.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/az.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/bg.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/bn.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/br.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/br.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ca.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/cs.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/da.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/da.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/de.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/de.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/el.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/el.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/en.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/en.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/eo.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/es.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/es.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/es_AR.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/et.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/et.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/eu.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/fa.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/fi.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/fil.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/fr.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ga.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/gl.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/he.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/he.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/hi.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/hr.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/hu.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/id.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/id.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/is.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/is.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/it.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/it.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ja.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ka.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/km.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/km.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ko.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/lt.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/lv.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/mi.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/mk.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ml.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/mr.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ms.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/nb.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/nl.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/or.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/or.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/pl.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/pt.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/pt_BR.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ro.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ru.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/si.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/si.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sk.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sl.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sq.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sr-Cyrl.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sr-Latn.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/sv.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ta.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/te.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/te.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/th.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/th.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/tr.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/tt.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/tzm.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/uk.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/ur_PK.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/vi.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json b/misc/dist/osx_tools.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json deleted file mode 100644 index c4f8f71d0e..0000000000 --- a/misc/dist/osx_tools.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "file_format_version" : "1.0.0", - "ICD": { - "library_path": "../../../Frameworks/libMoltenVK.dylib", - "api_version" : "1.1.0" - } -} diff --git a/misc/dist/osx_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/zh_CN.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/zh_HK.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/misc/dist/osx_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings b/misc/dist/osx_tools.app/Contents/Resources/zh_TW.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/camera/SCsub b/modules/camera/SCsub index de97724d09..9a6147d433 100644 --- a/modules/camera/SCsub +++ b/modules/camera/SCsub @@ -9,6 +9,6 @@ if env["platform"] == "windows": env_camera.add_source_files(env.modules_sources, "register_types.cpp") env_camera.add_source_files(env.modules_sources, "camera_win.cpp") -elif env["platform"] == "osx": +elif env["platform"] == "macos": env_camera.add_source_files(env.modules_sources, "register_types.cpp") - env_camera.add_source_files(env.modules_sources, "camera_osx.mm") + env_camera.add_source_files(env.modules_sources, "camera_macos.mm") diff --git a/modules/camera/camera_macos.h b/modules/camera/camera_macos.h new file mode 100644 index 0000000000..badf78f0e8 --- /dev/null +++ b/modules/camera/camera_macos.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* camera_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 CAMERA_MACOS_H +#define CAMERA_MACOS_H + +///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! +// If you fix something here, make sure you fix it there as well! + +#include "servers/camera_server.h" + +class CameraMacOS : public CameraServer { +public: + CameraMacOS(); + + void update_feeds(); +}; + +#endif /* CAMERA_MACOS_H */ diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm new file mode 100644 index 0000000000..0b9696a3e9 --- /dev/null +++ b/modules/camera/camera_macos.mm @@ -0,0 +1,355 @@ +/*************************************************************************/ +/* camera_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! +// If you fix something here, make sure you fix it there as well! + +#include "camera_macos.h" +#include "servers/camera/camera_feed.h" + +#import + +////////////////////////////////////////////////////////////////////////// +// MyCaptureSession - This is a little helper class so we can capture our frames + +@interface MyCaptureSession : AVCaptureSession { + Ref feed; + size_t width[2]; + size_t height[2]; + Vector img_data[2]; + + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; +} + +@end + +@implementation MyCaptureSession + +- (id)initForFeed:(Ref)p_feed andDevice:(AVCaptureDevice *)p_device { + if (self = [super init]) { + NSError *error; + feed = p_feed; + width[0] = 0; + height[0] = 0; + width[1] = 0; + height[1] = 0; + + [self beginConfiguration]; + + input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error]; + if (!input) { + print_line("Couldn't get input device for camera"); + } else { + [self addInput:input]; + } + + output = [AVCaptureVideoDataOutput new]; + if (!output) { + print_line("Couldn't get output device for camera"); + } else { + NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) }; + output.videoSettings = settings; + + // discard if the data output queue is blocked (as we process the still image) + [output setAlwaysDiscardsLateVideoFrames:YES]; + + // now set ourselves as the delegate to receive new frames. + [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; + + // this takes ownership + [self addOutput:output]; + } + + [self commitConfiguration]; + + // kick off our session.. + [self startRunning]; + }; + return self; +} + +- (void)cleanup { + // stop running + [self stopRunning]; + + // cleanup + [self beginConfiguration]; + + // remove input + if (input) { + [self removeInput:input]; + // don't release this + input = nullptr; + } + + // free up our output + if (output) { + [self removeOutput:output]; + [output setSampleBufferDelegate:nil queue:nullptr]; + output = nullptr; + } + + [self commitConfiguration]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + // This gets called every time our camera has a new image for us to process. + // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. + + // For now, version 1, we're just doing the bare minimum to make this work... + CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + // int _width = CVPixelBufferGetWidth(pixelBuffer); + // int _height = CVPixelBufferGetHeight(pixelBuffer); + + // It says that we need to lock this on the documentation pages but it's not in the samples + // need to lock our base address so we can access our pixel buffers, better safe then sorry? + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + + // get our buffers + unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); + unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); + if (dataY == nullptr) { + print_line("Couldn't access Y pixel buffer data"); + } else if (dataCbCr == nullptr) { + print_line("Couldn't access CbCr pixel buffer data"); + } else { + Ref img[2]; + + { + // do Y + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); + + if ((width[0] != new_width) || (height[0] != new_height)) { + width[0] = new_width; + height[0] = new_height; + img_data[0].resize(new_width * new_height); + } + + uint8_t *w = img_data[0].ptrw(); + memcpy(w, dataY, new_width * new_height); + + img[0].instantiate(); + img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]); + } + + { + // do CbCr + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); + + if ((width[1] != new_width) || (height[1] != new_height)) { + width[1] = new_width; + height[1] = new_height; + img_data[1].resize(2 * new_width * new_height); + } + + uint8_t *w = img_data[1].ptrw(); + memcpy(w, dataCbCr, 2 * new_width * new_height); + + ///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion + img[1].instantiate(); + img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]); + } + + // set our texture... + feed->set_YCbCr_imgs(img[0], img[1]); + } + + // and unlock + CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); +} + +@end + +////////////////////////////////////////////////////////////////////////// +// CameraFeedMacOS - Subclass for camera feeds in macOS + +class CameraFeedMacOS : public CameraFeed { +private: + AVCaptureDevice *device; + MyCaptureSession *capture_session; + +public: + AVCaptureDevice *get_device() const; + + CameraFeedMacOS(); + + void set_device(AVCaptureDevice *p_device); + + bool activate_feed(); + void deactivate_feed(); +}; + +AVCaptureDevice *CameraFeedMacOS::get_device() const { + return device; +}; + +CameraFeedMacOS::CameraFeedMacOS() { + device = nullptr; + capture_session = nullptr; +}; + +void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) { + device = p_device; + + // get some info + NSString *device_name = p_device.localizedName; + name = String::utf8(device_name.UTF8String); + position = CameraFeed::FEED_UNSPECIFIED; + if ([p_device position] == AVCaptureDevicePositionBack) { + position = CameraFeed::FEED_BACK; + } else if ([p_device position] == AVCaptureDevicePositionFront) { + position = CameraFeed::FEED_FRONT; + }; +}; + +bool CameraFeedMacOS::activate_feed() { + if (capture_session) { + // Already recording! + } else { + // Start camera capture, check permission. + if (@available(macOS 10.14, *)) { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusAuthorized) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } else if (status == AVAuthorizationStatusNotDetermined) { + // Request permission. + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + if (granted) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } + }]; + } + } else { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } + }; + + return true; +}; + +void CameraFeedMacOS::deactivate_feed() { + // end camera capture if we have one + if (capture_session) { + [capture_session cleanup]; + capture_session = nullptr; + }; +}; + +////////////////////////////////////////////////////////////////////////// +// MyDeviceNotifications - This is a little helper class gets notifications +// when devices are connected/disconnected + +@interface MyDeviceNotifications : NSObject { + CameraMacOS *camera_server; +} + +@end + +@implementation MyDeviceNotifications + +- (void)devices_changed:(NSNotification *)notification { + camera_server->update_feeds(); +} + +- (id)initForServer:(CameraMacOS *)p_server { + if (self = [super init]) { + camera_server = p_server; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil]; + }; + return self; +} + +- (void)dealloc { + // remove notifications + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; +} + +@end + +MyDeviceNotifications *device_notifications = nil; + +////////////////////////////////////////////////////////////////////////// +// CameraMacOS - Subclass for our camera server on macOS + +void CameraMacOS::update_feeds() { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 + AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; + NSArray *devices = session.devices; +#else + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; +#endif + + // remove devices that are gone.. + for (int i = feeds.size() - 1; i >= 0; i--) { + Ref feed = (Ref)feeds[i]; + + if (![devices containsObject:feed->get_device()]) { + // remove it from our array, this will also destroy it ;) + remove_feed(feed); + }; + }; + + for (AVCaptureDevice *device in devices) { + bool found = false; + for (int i = 0; i < feeds.size() && !found; i++) { + Ref feed = (Ref)feeds[i]; + if (feed->get_device() == device) { + found = true; + }; + }; + + if (!found) { + Ref newfeed; + newfeed.instantiate(); + newfeed->set_device(device); + + // assume display camera so inverse + Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0); + newfeed->set_transform(transform); + + add_feed(newfeed); + }; + }; +}; + +CameraMacOS::CameraMacOS() { + // Find available cameras we have at this time + update_feeds(); + + // should only have one of these.... + device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; +}; diff --git a/modules/camera/camera_osx.h b/modules/camera/camera_osx.h deleted file mode 100644 index b0db844599..0000000000 --- a/modules/camera/camera_osx.h +++ /dev/null @@ -1,46 +0,0 @@ -/*************************************************************************/ -/* camera_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 CAMERAOSX_H -#define CAMERAOSX_H - -///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! -// If you fix something here, make sure you fix it there as well! - -#include "servers/camera_server.h" - -class CameraOSX : public CameraServer { -public: - CameraOSX(); - - void update_feeds(); -}; - -#endif /* CAMERAOSX_H */ diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm deleted file mode 100644 index d199c31b2f..0000000000 --- a/modules/camera/camera_osx.mm +++ /dev/null @@ -1,355 +0,0 @@ -/*************************************************************************/ -/* camera_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!! -// If you fix something here, make sure you fix it there as well! - -#include "camera_osx.h" -#include "servers/camera/camera_feed.h" - -#import - -////////////////////////////////////////////////////////////////////////// -// MyCaptureSession - This is a little helper class so we can capture our frames - -@interface MyCaptureSession : AVCaptureSession { - Ref feed; - size_t width[2]; - size_t height[2]; - Vector img_data[2]; - - AVCaptureDeviceInput *input; - AVCaptureVideoDataOutput *output; -} - -@end - -@implementation MyCaptureSession - -- (id)initForFeed:(Ref)p_feed andDevice:(AVCaptureDevice *)p_device { - if (self = [super init]) { - NSError *error; - feed = p_feed; - width[0] = 0; - height[0] = 0; - width[1] = 0; - height[1] = 0; - - [self beginConfiguration]; - - input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error]; - if (!input) { - print_line("Couldn't get input device for camera"); - } else { - [self addInput:input]; - } - - output = [AVCaptureVideoDataOutput new]; - if (!output) { - print_line("Couldn't get output device for camera"); - } else { - NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) }; - output.videoSettings = settings; - - // discard if the data output queue is blocked (as we process the still image) - [output setAlwaysDiscardsLateVideoFrames:YES]; - - // now set ourselves as the delegate to receive new frames. - [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; - - // this takes ownership - [self addOutput:output]; - } - - [self commitConfiguration]; - - // kick off our session.. - [self startRunning]; - }; - return self; -} - -- (void)cleanup { - // stop running - [self stopRunning]; - - // cleanup - [self beginConfiguration]; - - // remove input - if (input) { - [self removeInput:input]; - // don't release this - input = nullptr; - } - - // free up our output - if (output) { - [self removeOutput:output]; - [output setSampleBufferDelegate:nil queue:nullptr]; - output = nullptr; - } - - [self commitConfiguration]; -} - -- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { - // This gets called every time our camera has a new image for us to process. - // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. - - // For now, version 1, we're just doing the bare minimum to make this work... - CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); - // int _width = CVPixelBufferGetWidth(pixelBuffer); - // int _height = CVPixelBufferGetHeight(pixelBuffer); - - // It says that we need to lock this on the documentation pages but it's not in the samples - // need to lock our base address so we can access our pixel buffers, better safe then sorry? - CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); - - // get our buffers - unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); - unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); - if (dataY == nullptr) { - print_line("Couldn't access Y pixel buffer data"); - } else if (dataCbCr == nullptr) { - print_line("Couldn't access CbCr pixel buffer data"); - } else { - Ref img[2]; - - { - // do Y - size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); - size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); - - if ((width[0] != new_width) || (height[0] != new_height)) { - width[0] = new_width; - height[0] = new_height; - img_data[0].resize(new_width * new_height); - } - - uint8_t *w = img_data[0].ptrw(); - memcpy(w, dataY, new_width * new_height); - - img[0].instantiate(); - img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]); - } - - { - // do CbCr - size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); - size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); - - if ((width[1] != new_width) || (height[1] != new_height)) { - width[1] = new_width; - height[1] = new_height; - img_data[1].resize(2 * new_width * new_height); - } - - uint8_t *w = img_data[1].ptrw(); - memcpy(w, dataCbCr, 2 * new_width * new_height); - - ///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion - img[1].instantiate(); - img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]); - } - - // set our texture... - feed->set_YCbCr_imgs(img[0], img[1]); - } - - // and unlock - CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); -} - -@end - -////////////////////////////////////////////////////////////////////////// -// CameraFeedOSX - Subclass for camera feeds in OSX - -class CameraFeedOSX : public CameraFeed { -private: - AVCaptureDevice *device; - MyCaptureSession *capture_session; - -public: - AVCaptureDevice *get_device() const; - - CameraFeedOSX(); - - void set_device(AVCaptureDevice *p_device); - - bool activate_feed(); - void deactivate_feed(); -}; - -AVCaptureDevice *CameraFeedOSX::get_device() const { - return device; -}; - -CameraFeedOSX::CameraFeedOSX() { - device = nullptr; - capture_session = nullptr; -}; - -void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { - device = p_device; - - // get some info - NSString *device_name = p_device.localizedName; - name = String::utf8(device_name.UTF8String); - position = CameraFeed::FEED_UNSPECIFIED; - if ([p_device position] == AVCaptureDevicePositionBack) { - position = CameraFeed::FEED_BACK; - } else if ([p_device position] == AVCaptureDevicePositionFront) { - position = CameraFeed::FEED_FRONT; - }; -}; - -bool CameraFeedOSX::activate_feed() { - if (capture_session) { - // Already recording! - } else { - // Start camera capture, check permission. - if (@available(macOS 10.14, *)) { - AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; - if (status == AVAuthorizationStatusAuthorized) { - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; - } else if (status == AVAuthorizationStatusNotDetermined) { - // Request permission. - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo - completionHandler:^(BOOL granted) { - if (granted) { - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; - } - }]; - } - } else { - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; - } - }; - - return true; -}; - -void CameraFeedOSX::deactivate_feed() { - // end camera capture if we have one - if (capture_session) { - [capture_session cleanup]; - capture_session = nullptr; - }; -}; - -////////////////////////////////////////////////////////////////////////// -// MyDeviceNotifications - This is a little helper class gets notifications -// when devices are connected/disconnected - -@interface MyDeviceNotifications : NSObject { - CameraOSX *camera_server; -} - -@end - -@implementation MyDeviceNotifications - -- (void)devices_changed:(NSNotification *)notification { - camera_server->update_feeds(); -} - -- (id)initForServer:(CameraOSX *)p_server { - if (self = [super init]) { - camera_server = p_server; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil]; - }; - return self; -} - -- (void)dealloc { - // remove notifications - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; -} - -@end - -MyDeviceNotifications *device_notifications = nil; - -////////////////////////////////////////////////////////////////////////// -// CameraOSX - Subclass for our camera server on OSX - -void CameraOSX::update_feeds() { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 - AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = session.devices; -#else - NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; -#endif - - // remove devices that are gone.. - for (int i = feeds.size() - 1; i >= 0; i--) { - Ref feed = (Ref)feeds[i]; - - if (![devices containsObject:feed->get_device()]) { - // remove it from our array, this will also destroy it ;) - remove_feed(feed); - }; - }; - - for (AVCaptureDevice *device in devices) { - bool found = false; - for (int i = 0; i < feeds.size() && !found; i++) { - Ref feed = (Ref)feeds[i]; - if (feed->get_device() == device) { - found = true; - }; - }; - - if (!found) { - Ref newfeed; - newfeed.instantiate(); - newfeed->set_device(device); - - // assume display camera so inverse - Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0); - newfeed->set_transform(transform); - - add_feed(newfeed); - }; - }; -}; - -CameraOSX::CameraOSX() { - // Find available cameras we have at this time - update_feeds(); - - // should only have one of these.... - device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; -}; diff --git a/modules/camera/config.py b/modules/camera/config.py index 8a22751aa7..d2b2542dd9 100644 --- a/modules/camera/config.py +++ b/modules/camera/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return platform == "osx" or platform == "windows" + return platform == "macos" or platform == "windows" def configure(env): diff --git a/modules/camera/register_types.cpp b/modules/camera/register_types.cpp index 98a4b5ca1a..40e2224d6b 100644 --- a/modules/camera/register_types.cpp +++ b/modules/camera/register_types.cpp @@ -33,8 +33,8 @@ #if defined(WINDOWS_ENABLED) #include "camera_win.h" #endif -#if defined(OSX_ENABLED) -#include "camera_osx.h" +#if defined(MACOS_ENABLED) +#include "camera_macos.h" #endif void initialize_camera_module(ModuleInitializationLevel p_level) { @@ -45,8 +45,8 @@ void initialize_camera_module(ModuleInitializationLevel p_level) { #if defined(WINDOWS_ENABLED) CameraServer::make_default(); #endif -#if defined(OSX_ENABLED) - CameraServer::make_default(); +#if defined(MACOS_ENABLED) + CameraServer::make_default(); #endif } diff --git a/modules/denoise/config.py b/modules/denoise/config.py index 3aa840acb0..521115dae5 100644 --- a/modules/denoise/config.py +++ b/modules/denoise/config.py @@ -4,7 +4,7 @@ def can_build(env, platform): # It's also only relevant for tools build and desktop platforms, # as doing lightmap generation and denoising on Android or HTML5 # would be a bit far-fetched. - desktop_platforms = ["linuxbsd", "osx", "windows"] + desktop_platforms = ["linuxbsd", "macos", "windows"] supported_arch = env["bits"] == "64" if env["arch"] == "arm64": supported_arch = False diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 033591a0b9..8002c185c7 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -292,7 +292,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { path = path.plus_file("blender"); #endif -#if defined(OSX_ENABLED) +#if defined(MACOS_ENABLED) if (!FileAccess::exists(path)) { path = path.plus_file("Blender"); } @@ -468,7 +468,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { // Autodetect auto_detected_path = ""; -#if defined(OSX_ENABLED) +#if defined(MACOS_ENABLED) { Vector mdfind_paths; diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 5876b6cbf3..95f1a657a4 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -45,7 +45,7 @@ uint32_t MobileVRInterface::get_capabilities() const { Vector3 MobileVRInterface::scale_magneto(const Vector3 &p_magnetometer) { // Our magnetometer doesn't give us nice clean data. - // Well it may on Mac OS X because we're getting a calibrated value in the current implementation but Android we're getting raw data. + // Well it may on macOS because we're getting a calibrated value in the current implementation but Android we're getting raw data. // This is a fairly simple adjustment we can do to correct for the magnetometer data being elliptical Vector3 mag_raw = p_magnetometer; diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 3bafa351a9..d10ebc7b47 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -55,7 +55,7 @@ env_mono.add_source_files(env.modules_sources, "utils/*.cpp") env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp") -if env["platform"] in ["osx", "iphone"]: +if env["platform"] in ["macos", "ios"]: env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm") env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m") elif env["platform"] == "android": diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 8e441e7e07..e69904c54b 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -63,15 +63,15 @@ def copy_file(src_dir, dst_dir, src_name, dst_name=""): def is_desktop(platform): - return platform in ["windows", "osx", "linuxbsd", "server", "uwp", "haiku"] + return platform in ["windows", "macos", "linuxbsd", "server", "uwp", "haiku"] def is_unix_like(platform): - return platform in ["osx", "linuxbsd", "server", "android", "haiku", "iphone"] + return platform in ["macos", "linuxbsd", "server", "android", "haiku", "ios"] def module_supports_tools_on(platform): - return platform not in ["android", "javascript", "iphone"] + return platform not in ["android", "javascript", "ios"] def find_wasm_src_dir(mono_root): @@ -89,7 +89,7 @@ def configure(env, env_mono): bits = env["bits"] is_android = env["platform"] == "android" is_javascript = env["platform"] == "javascript" - is_ios = env["platform"] == "iphone" + is_ios = env["platform"] == "ios" is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"] tools_enabled = env["tools"] @@ -206,7 +206,7 @@ def configure(env, env_mono): copy_file(mono_bin_path, "#bin", mono_dll_file) else: - is_apple = env["platform"] in ["osx", "iphone"] + is_apple = env["platform"] in ["macos", "ios"] is_macos = is_apple and not is_ios sharedlib_ext = ".dylib" if is_apple else ".so" @@ -221,7 +221,7 @@ def configure(env, env_mono): ) if not mono_root and is_macos: - # Try with some known directories under OSX + # Try with some known directories under macOS hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"] for hint_dir in hint_dirs: if os.path.isdir(hint_dir): @@ -270,7 +270,7 @@ def configure(env, env_mono): def copy_mono_lib(libname_wo_ext): copy_file( - mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.iphone.%s.a" % (libname_wo_ext, arch) + mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.ios.%s.a" % (libname_wo_ext, arch) ) # Copy Mono libraries to the output folder. These are meant to be bundled with @@ -539,7 +539,7 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): os.makedirs(target_mono_lib_dir) lib_file_names = [] - if platform == "osx": + if platform == "macos": lib_file_names = [ lib_name + ".dylib" for lib_name in ["libmono-btls-shared", "libmono-native-compat", "libMonoPosixHelper"] diff --git a/modules/mono/config.py b/modules/mono/config.py index df02d9a309..3e6584590c 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,4 +1,4 @@ -supported_platforms = ["windows", "osx", "linuxbsd", "server", "android", "haiku", "javascript", "iphone"] +supported_platforms = ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"] def can_build(env, platform): @@ -15,7 +15,7 @@ def configure(env): from SCons.Script import BoolVariable, PathVariable, Variables, Help - default_mono_static = platform in ["iphone", "javascript"] + default_mono_static = platform in ["ios", "javascript"] default_mono_bundles_zlib = platform in ["javascript"] envvars = Variables() diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 0128f5c706..5a499742e9 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -57,7 +57,7 @@ linuxbsd linuxbsd - osx + macos windows @@ -76,12 +76,12 @@ --> GODOT_WINDOWS;GODOT_PC GODOT_LINUXBSD;GODOT_PC - GODOT_OSX;GODOT_MACOS;GODOT_PC + GODOT_OSX;GODOT_MACOS;GODOT_PC GODOT_SERVER;GODOT_PC GODOT_UWP;GODOT_PC GODOT_HAIKU;GODOT_PC GODOT_ANDROID;GODOT_MOBILE - GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE + GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB $(GodotDefineConstants);$(GodotPlatformConstants) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index e2f4d2f5fd..6a80e81fdd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -336,10 +336,10 @@ MONO_AOT_MODE_LAST = 1000, // Add the required Mono libraries to the Xcode project - string MonoLibFile(string libFileName) => libFileName + ".iphone.fat.a"; + string MonoLibFile(string libFileName) => libFileName + ".ios.fat.a"; string MonoLibFromTemplate(string libFileName) => - Path.Combine(Internal.FullTemplatesDir, "iphone-mono-libs", MonoLibFile(libFileName)); + Path.Combine(Internal.FullTemplatesDir, "ios-mono-libs", MonoLibFile(libFileName)); exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmonosgen-2.0")); diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 3e46a89b7c..6a9ead9aa1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -380,7 +380,7 @@ namespace GodotTools.Export private static bool PlatformHasTemplateDir(string platform) { - // OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. + // macOS export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. return !new[] { OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform); } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 69960bdbeb..b39c3d1c0d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -263,16 +263,16 @@ namespace GodotTools var args = new List(); - bool osxAppBundleInstalled = false; + bool macOSAppBundleInstalled = false; if (OS.IsMacOS) { // The package path is '/Applications/Visual Studio Code.app' const string vscodeBundleId = "com.microsoft.VSCode"; - osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId); + macOSAppBundleInstalled = Internal.IsMacOSAppBundleInstalled(vscodeBundleId); - if (osxAppBundleInstalled) + if (macOSAppBundleInstalled) { args.Add("-b"); args.Add(vscodeBundleId); @@ -307,13 +307,13 @@ namespace GodotTools if (OS.IsMacOS) { - if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath)) + if (!macOSAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath)) { GD.PushError("Cannot find code editor: VSCode"); return Error.FileNotFound; } - command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath; + command = macOSAppBundleInstalled ? "/usr/bin/open" : _vsCodePath; } else { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 3f1d5ac3ca..7a0983a8cb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -30,7 +30,7 @@ namespace GodotTools.Ides.MonoDevelop { string bundleId = BundleIds[_editorId]; - if (Internal.IsOsxAppBundleInstalled(bundleId)) + if (Internal.IsMacOSAppBundleInstalled(bundleId)) { command = "open"; diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 77370090ec..72985db292 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -17,7 +17,7 @@ namespace GodotTools.Internals public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); - public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId); + public static bool IsMacOSAppBundleInstalled(string bundleId) => internal_IsMacOSAppBundleInstalled(bundleId); public static bool GodotIs32Bits() => internal_GodotIs32Bits(); @@ -63,7 +63,7 @@ namespace GodotTools.Internals private static extern string internal_SimplifyGodotPath(this string path); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsOsxAppBundleInstalled(string bundleId); + private static extern bool internal_IsMacOSAppBundleInstalled(string bundleId); [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool internal_GodotIs32Bits(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 2db549c623..5cef6e5c3c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -37,13 +37,13 @@ namespace GodotTools.Utils public static class Platforms { public const string Windows = "windows"; - public const string MacOS = "osx"; + public const string MacOS = "macos"; public const string LinuxBSD = "linuxbsd"; public const string Server = "server"; public const string UWP = "uwp"; public const string Haiku = "haiku"; public const string Android = "android"; - public const string iOS = "iphone"; + public const string iOS = "ios"; public const string HTML5 = "javascript"; } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index f7f710f3f1..5bf3839e94 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -47,7 +47,7 @@ #include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" #include "../mono_gd/gd_mono_marshal.h" -#include "../utils/osx_utils.h" +#include "../utils/macos_utils.h" #include "code_completion.h" #include "godotsharp_export.h" @@ -198,10 +198,10 @@ MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); } -MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { -#ifdef OSX_ENABLED +MonoBoolean godot_icall_Internal_IsMacOSAppBundleInstalled(MonoString *p_bundle_id) { +#ifdef MACOS_ENABLED String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); - return (MonoBoolean)osx_is_app_bundle_installed(bundle_id); + return (MonoBoolean)macos_is_app_bundle_installed(bundle_id); #else (void)p_bundle_id; // UNUSED return (MonoBoolean) false; @@ -366,7 +366,7 @@ void register_editor_internal_calls() { GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", godot_icall_Internal_FullTemplatesDir); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", godot_icall_Internal_IsOsxAppBundleInstalled); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsMacOSAppBundleInstalled", godot_icall_Internal_IsMacOSAppBundleInstalled); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration); diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index cb2b60fcce..f17b24e399 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -184,7 +184,7 @@ private: data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); #endif -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (!DirAccess::exists(data_editor_tools_dir)) { data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools"); } @@ -222,7 +222,7 @@ private: data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); #endif -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (!DirAccess::exists(data_mono_root_dir)) { data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib"); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 39a8ef22b7..d3d3bb2bef 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -55,7 +55,7 @@ #ifdef ANDROID_ENABLED #include "android_mono_config.h" #include "support/android_support.h" -#elif defined(IPHONE_ENABLED) +#elif defined(IOS_ENABLED) #include "support/ios_support.h" #endif @@ -188,7 +188,7 @@ MonoDomain *gd_initialize_mono_runtime() { MonoDomain *gd_initialize_mono_runtime() { gd_mono_debug_init(); -#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED) +#if defined(IOS_ENABLED) || defined(ANDROID_ENABLED) // I don't know whether this actually matters or not const char *runtime_version = "mobile"; #else @@ -263,7 +263,7 @@ void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_di if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { r_config_dir = mono_reg_info.config_dir; } -#elif defined(OSX_ENABLED) +#elif defined(MACOS_ENABLED) const char *c_assembly_rootdir = mono_assembly_getrootdir(); const char *c_config_dir = mono_get_config_dir(); @@ -343,7 +343,7 @@ void GDMono::initialize() { #if defined(ANDROID_ENABLED) gdmono::android::support::initialize(); -#elif defined(IPHONE_ENABLED) +#elif defined(IOS_ENABLED) gdmono::ios::support::initialize(); #endif diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h index 9fc35f8e31..93ba6a410e 100644 --- a/modules/mono/mono_gd/gd_mono_log.h +++ b/modules/mono/mono_gd/gd_mono_log.h @@ -35,7 +35,7 @@ #include "core/typedefs.h" -#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED) +#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) // We have custom mono log callbacks for WASM and iOS #define GD_MONO_LOG_ENABLED #endif diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h index bb163b89bc..0180dee3ea 100644 --- a/modules/mono/mono_gd/gd_mono_method_thunk.h +++ b/modules/mono/mono_gd/gd_mono_method_thunk.h @@ -39,7 +39,7 @@ #include "gd_mono_method.h" #include "gd_mono_utils.h" -#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED) +#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) #define HAVE_METHOD_THUNKS #endif diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h index 2f444d5089..03e86df698 100644 --- a/modules/mono/mono_gd/support/ios_support.h +++ b/modules/mono/mono_gd/support/ios_support.h @@ -31,7 +31,7 @@ #ifndef IOS_SUPPORT_H #define IOS_SUPPORT_H -#if defined(IPHONE_ENABLED) +#if defined(IOS_ENABLED) #include "core/string/ustring.h" @@ -45,6 +45,6 @@ void cleanup(); } // namespace ios } // namespace gdmono -#endif // IPHONE_ENABLED +#endif // IOS_ENABLED #endif // IOS_SUPPORT_H diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm index df97dfba49..7c941b9d1e 100644 --- a/modules/mono/mono_gd/support/ios_support.mm +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -30,7 +30,7 @@ #include "ios_support.h" -#if defined(IPHONE_ENABLED) +#if defined(IOS_ENABLED) #import #include @@ -147,4 +147,4 @@ GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) { os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'"); } -#endif // IPHONE_ENABLED +#endif // IOS_ENABLED diff --git a/modules/mono/utils/macos_utils.cpp b/modules/mono/utils/macos_utils.cpp new file mode 100644 index 0000000000..cd4f7a827e --- /dev/null +++ b/modules/mono/utils/macos_utils.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* macos_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "macos_utils.h" + +#ifdef MACOS_ENABLED + +#include "core/string/print_string.h" + +#import +#import + +bool macos_is_app_bundle_installed(const String &p_bundle_id) { + CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8); + CFArrayRef result = LSCopyApplicationURLsForBundleIdentifier(bundle_id, nullptr); + CFRelease(bundle_id); + + if (result) { + if (CFArrayGetCount(result) > 0) { + CFRelease(result); + return true; + } else { + CFRelease(result); + return false; + } + } else { + return false; + } +} + +#endif diff --git a/modules/mono/utils/macos_utils.h b/modules/mono/utils/macos_utils.h new file mode 100644 index 0000000000..7892cb2785 --- /dev/null +++ b/modules/mono/utils/macos_utils.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* macos_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "core/string/ustring.h" + +#ifndef MACOS_UTILS_H +#define MACOS_UTILS_H + +#ifdef MACOS_ENABLED + +bool macos_is_app_bundle_installed(const String &p_bundle_id); + +#endif + +#endif // MACOS_UTILS_H diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp deleted file mode 100644 index abb59420eb..0000000000 --- a/modules/mono/utils/osx_utils.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* osx_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "osx_utils.h" - -#ifdef OSX_ENABLED - -#include "core/string/print_string.h" - -#import -#import - -bool osx_is_app_bundle_installed(const String &p_bundle_id) { - CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8); - CFArrayRef result = LSCopyApplicationURLsForBundleIdentifier(bundle_id, nullptr); - CFRelease(bundle_id); - - if (result) { - if (CFArrayGetCount(result) > 0) { - CFRelease(result); - return true; - } else { - CFRelease(result); - return false; - } - } else { - return false; - } -} - -#endif diff --git a/modules/mono/utils/osx_utils.h b/modules/mono/utils/osx_utils.h deleted file mode 100644 index 2f6c6dad51..0000000000 --- a/modules/mono/utils/osx_utils.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************/ -/* osx_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "core/string/ustring.h" - -#ifndef OSX_UTILS_H -#define OSX_UTILS_H - -#ifdef OSX_ENABLED - -bool osx_is_app_bundle_installed(const String &p_bundle_id); - -#endif - -#endif // OSX_UTILS_H diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 69848a9e52..0170c007ae 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -624,15 +624,15 @@ env.Append(CPPDEFINES=["GDEXTENSION"]) env.Append(CPPPATH=["../"]) sources = Glob("../*.cpp") -if env["platform"] == "osx": - methods.write_osx_plist( - f'./bin/libtextserver_advanced.osx.{env["target"]}.framework', - f'libtextserver_advanced.osx.{env["target"]}', +if env["platform"] == "macos": + methods.write_macos_plist( + f'./bin/libtextserver_advanced.macos.{env["target"]}.framework', + f'libtextserver_advanced.macos.{env["target"]}', "org.godotengine.textserver_advanced", "ICU / HarfBuzz / Graphite Text Server", ) library = env.SharedLibrary( - f'./bin/libtextserver_advanced.osx.{env["target"]}.framework/libtextserver_advanced.osx.{env["target"]}', + f'./bin/libtextserver_advanced.macos.{env["target"]}.framework/libtextserver_advanced.macos.{env["target"]}', source=sources, ) else: diff --git a/modules/text_server_adv/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py index d404f2851e..3c5229462c 100644 --- a/modules/text_server_adv/gdextension_build/methods.py +++ b/modules/text_server_adv/gdextension_build/methods.py @@ -98,7 +98,7 @@ def make_icu_data(target, source, env): g.write("#endif") -def write_osx_plist(target, binary_name, identifier, name): +def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resourece/", exist_ok=True) f = open(f"{target}/Resourece/Info.plist", "w") diff --git a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension index 5956476a5e..11ed271ae9 100644 --- a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension +++ b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension @@ -8,5 +8,5 @@ linux.64.debug = "bin/libtextserver_advanced.linux.debug.64.so" linux.64.release = "bin/libtextserver_advanced.linux.release.64.so" windows.64.debug = "bin/libtextserver_advanced.windows.debug.64.dll" windows.64.release = "bin/libtextserver_advanced.windows.release.64.dll" -macos.debug = "bin/libtextserver_advanced.osx.debug.framework" -macos.release = "bin/libtextserver_advanced.osx.release.framework" +macos.debug = "bin/libtextserver_advanced.macos.debug.framework" +macos.release = "bin/libtextserver_advanced.macos.release.framework" diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 6c9e10db18..de0a549900 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -184,15 +184,15 @@ env.Append(CPPDEFINES=["GDEXTENSION"]) env.Append(CPPPATH=["../"]) sources = Glob("../*.cpp") -if env["platform"] == "osx": - methods.write_osx_plist( - f'./bin/libtextserver_fallback.osx.{env["target"]}.framework', - f'libtextserver_fallback.osx.{env["target"]}', +if env["platform"] == "macos": + methods.write_macos_plist( + f'./bin/libtextserver_fallback.macos.{env["target"]}.framework', + f'libtextserver_fallback.macos.{env["target"]}', "org.godotengine.textserver_fallback", "Fallback Text Server", ) library = env.SharedLibrary( - f'./bin/libtextserver_fallback.osx.{env["target"]}.framework/libtextserver_fallback.osx.{env["target"]}', + f'./bin/libtextserver_fallback.macos.{env["target"]}.framework/libtextserver_fallback.macos.{env["target"]}', source=sources, ) else: diff --git a/modules/text_server_fb/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py index d404f2851e..3c5229462c 100644 --- a/modules/text_server_fb/gdextension_build/methods.py +++ b/modules/text_server_fb/gdextension_build/methods.py @@ -98,7 +98,7 @@ def make_icu_data(target, source, env): g.write("#endif") -def write_osx_plist(target, binary_name, identifier, name): +def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resourece/", exist_ok=True) f = open(f"{target}/Resourece/Info.plist", "w") diff --git a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension index 1026c6cb85..9236555d63 100644 --- a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension +++ b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension @@ -8,5 +8,5 @@ linux.64.debug = "bin/libtextserver_fallback.linux.debug.64.so" linux.64.release = "bin/libtextserver_fallback.linux.release.64.so" windows.64.debug = "bin/libtextserver_fallback.windows.debug.64.dll" windows.64.release = "bin/libtextserver_fallback.windows.release.64.dll" -macos.debug = "bin/libtextserver_fallback.osx.debug.framework" -macos.release = "bin/libtextserver_fallback.osx.release.framework" +macos.debug = "bin/libtextserver_fallback.macos.debug.framework" +macos.release = "bin/libtextserver_fallback.macos.release.framework" diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp index 61ff56b62a..522ed8719b 100644 --- a/modules/visual_script/editor/visual_script_editor.cpp +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -1227,7 +1227,7 @@ void VisualScriptEditor::_member_selected() { selected = ti->get_metadata(0); if (ti->get_parent() == members->get_root()->get_first_child()) { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::META); #else bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); @@ -2208,7 +2208,7 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant & String(d["type"]) == "files" || String(d["type"]) == "nodes")) { if (String(d["type"]) == "obj_property") { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Getter. Hold Shift to drop a generic signature."), find_keycode_name(Key::META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature.")); @@ -2216,7 +2216,7 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant & } if (String(d["type"]) == "nodes") { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a simple reference to the node."), find_keycode_name(Key::META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a simple reference to the node.")); @@ -2224,7 +2224,7 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant & } if (String(d["type"]) == "visual_script_variable_drag") { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Variable Setter."), find_keycode_name(Key::META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Variable Setter.")); @@ -2287,7 +2287,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "visual_script_variable_drag") { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED bool use_set = Input::get_singleton()->is_key_pressed(Key::META); #else bool use_set = Input::get_singleton()->is_key_pressed(Key::CTRL); @@ -2396,7 +2396,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "files") { -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED bool use_preload = Input::get_singleton()->is_key_pressed(Key::META); #else bool use_preload = Input::get_singleton()->is_key_pressed(Key::CTRL); @@ -2459,7 +2459,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da return; } -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED bool use_node = Input::get_singleton()->is_key_pressed(Key::META); #else bool use_node = Input::get_singleton()->is_key_pressed(Key::CTRL); @@ -2524,7 +2524,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Node *node = Object::cast_to(obj); Vector2 pos = _get_pos_in_graph(p_point); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED bool use_get = Input::get_singleton()->is_key_pressed(Key::META); #else bool use_get = Input::get_singleton()->is_key_pressed(Key::CTRL); @@ -3378,7 +3378,7 @@ void VisualScriptEditor::connect_data(Ref vnode_old, Refis_key_pressed(Key::META); #else bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); diff --git a/platform/ios/SCsub b/platform/ios/SCsub new file mode 100644 index 0000000000..bf12ab6dd7 --- /dev/null +++ b/platform/ios/SCsub @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +Import("env") + +ios_lib = [ + "godot_ios.mm", + "os_ios.mm", + "main.m", + "app_delegate.mm", + "view_controller.mm", + "ios.mm", + "vulkan_context_ios.mm", + "display_server_ios.mm", + "joypad_ios.mm", + "godot_view.mm", + "tts_ios.mm", + "display_layer.mm", + "godot_app_delegate.m", + "godot_view_renderer.mm", + "godot_view_gesture_recognizer.mm", + "device_metrics.m", + "keyboard_input_view.mm", +] + +env_ios = env.Clone() +ios_lib = env_ios.add_library("ios", ios_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 = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}libtool" + else: + libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/libtool" + 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) diff --git a/platform/ios/api/api.cpp b/platform/ios/api/api.cpp new file mode 100644 index 0000000000..00c76a9256 --- /dev/null +++ b/platform/ios/api/api.cpp @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "api.h" + +#if defined(IOS_ENABLED) + +void register_ios_api() { + godot_ios_plugins_initialize(); +} + +void unregister_ios_api() { + godot_ios_plugins_deinitialize(); +} + +#else + +void register_ios_api() {} +void unregister_ios_api() {} + +#endif diff --git a/platform/ios/api/api.h b/platform/ios/api/api.h new file mode 100644 index 0000000000..c7fd4ce77b --- /dev/null +++ b/platform/ios/api/api.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_API_H +#define IOS_API_H + +#if defined(IOS_ENABLED) +extern void godot_ios_plugins_initialize(); +extern void godot_ios_plugins_deinitialize(); +#endif + +void register_ios_api(); +void unregister_ios_api(); + +#endif // IOS_API_H diff --git a/platform/ios/app_delegate.h b/platform/ios/app_delegate.h new file mode 100644 index 0000000000..0ec1dc071b --- /dev/null +++ b/platform/ios/app_delegate.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* app_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@class ViewController; + +// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again, +// so it can't be done with compilation time branching. +//#if defined(GLES3_ENABLED) +//@interface AppDelegate : NSObject { +//#endif +//#if defined(VULKAN_ENABLED) +@interface AppDelegate : NSObject +//#endif + +@property(strong, nonatomic) UIWindow *window; +@property(strong, class, readonly, nonatomic) ViewController *viewController; + +@end diff --git a/platform/ios/app_delegate.mm b/platform/ios/app_delegate.mm new file mode 100644 index 0000000000..fb183d52d4 --- /dev/null +++ b/platform/ios/app_delegate.mm @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* app_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "app_delegate.h" + +#include "core/config/project_settings.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#import "godot_view.h" +#include "main/main.h" +#include "os_ios.h" +#import "view_controller.h" + +#import +#import + +#define kRenderingFrequency 60 + +extern int gargc; +extern char **gargv; + +extern int ios_main(int, char **, String, String); +extern void ios_finish(); + +@implementation AppDelegate + +static ViewController *mainViewController = nil; + ++ (ViewController *)viewController { + return mainViewController; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // 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 + + CGRect windowBounds = [[UIScreen mainScreen] bounds]; + + // Create a full-screen window + self.window = [[UIWindow alloc] initWithFrame:windowBounds]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cacheDirectory = [paths objectAtIndex:0]; + + int err = ios_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory 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 NO; + } + + ViewController *viewController = [[ViewController alloc] init]; + viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; + viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; + + self.window.rootViewController = viewController; + + // Show the window + [self.window makeKeyAndVisible]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onAudioInterruption:) + name:AVAudioSessionInterruptionNotification + object:[AVAudioSession sharedInstance]]; + + mainViewController = viewController; + + // prevent to stop music in another background app + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; + + return YES; +} + +- (void)onAudioInterruption:(NSNotification *)notification { + if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { + if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) { + NSLog(@"Audio interruption began"); + OS_IOS::get_singleton()->on_focus_out(); + } else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) { + NSLog(@"Audio interruption ended"); + OS_IOS::get_singleton()->on_focus_in(); + } + } +} + +- (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 { + ios_finish(); +} + +// When application goes to background (e.g. user switches to another app or presses Home), +// then applicationWillResignActive -> applicationDidEnterBackground are called. +// When user opens the inactive app again, +// applicationWillEnterForeground -> applicationDidBecomeActive are called. + +// There are cases when applicationWillResignActive -> applicationDidBecomeActive +// sequence is called without the app going to background. For example, that happens +// if you open the app list without switching to another app or open/close the +// notification panel by swiping from the upper part of the screen. + +- (void)applicationWillResignActive:(UIApplication *)application { + OS_IOS::get_singleton()->on_focus_out(); +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + OS_IOS::get_singleton()->on_focus_in(); +} + +- (void)dealloc { + self.window = nil; +} + +@end diff --git a/platform/ios/detect.py b/platform/ios/detect.py new file mode 100644 index 0000000000..67c90b10a0 --- /dev/null +++ b/platform/ios/detect.py @@ -0,0 +1,152 @@ +import os +import sys +from methods import detect_darwin_sdk_path + + +def is_active(): + return True + + +def get_name(): + return "iOS" + + +def can_build(): + if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ): + return True + + return False + + +def get_opts(): + from SCons.Variables import BoolVariable + + return [ + ( + "IOS_TOOLCHAIN_PATH", + "Path to iOS toolchain", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain", + ), + ("IOS_SDK_PATH", "Path to the iOS SDK", ""), + BoolVariable("ios_simulator", "Build for iOS Simulator", False), + BoolVariable("ios_exceptions", "Enable exceptions", False), + ("ios_triple", "Triple for ios toolchain", ""), + ] + + +def get_flags(): + return [ + ("tools", False), + ("use_volk", False), + ] + + +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) + # `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces + # when using `target=release_debug`. + opt = "-O3" if env["target"] == "release" else "-O2" + env.Append(CCFLAGS=[opt, "-ftree-vectorize", "-fomit-frame-pointer"]) + env.Append(LINKFLAGS=[opt]) + elif env["optimize"] == "size": # optimize for size + env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) + env.Append(LINKFLAGS=["-Os"]) + + elif env["target"] == "debug": + env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) + env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1)]) + + if env["use_lto"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) + + ## Architecture + env["bits"] = "64" + if env["arch"] != "x86_64": + env["arch"] = "arm64" + + ## Compiler configuration + + # Save this in environment for use by other modules + if "OSXCROSS_IOS" in os.environ: + env["osxcross"] = True + + env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] + + compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}" + s_compiler_path = "$IOS_TOOLCHAIN_PATH/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" + 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" + + ## Compile flags + + if env["ios_simulator"]: + detect_darwin_sdk_path("iossimulator", env) + env.Append(ASFLAGS=["-mios-simulator-version-min=13.0"]) + env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"]) + env.extra_suffix = ".simulator" + env.extra_suffix + else: + detect_darwin_sdk_path("ios", env) + env.Append(ASFLAGS=["-miphoneos-version-min=11.0"]) + env.Append(CCFLAGS=["-miphoneos-version-min=11.0"]) + + if env["arch"] == "x86_64": + env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" + env.Append( + CCFLAGS=( + "-fobjc-arc -arch x86_64" + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" + " -fasm-blocks -isysroot $IOS_SDK_PATH" + ).split() + ) + env.Append(ASFLAGS=["-arch", "x86_64"]) + elif env["arch"] == "arm64": + env.Append( + CCFLAGS=( + "-fobjc-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" + " -isysroot $IOS_SDK_PATH".split() + ) + ) + env.Append(ASFLAGS=["-arch", "arm64"]) + env.Append(CPPDEFINES=["NEED_LONG_INT"]) + + # Disable exceptions on non-tools (template) builds + if not env["tools"]: + if env["ios_exceptions"]: + env.Append(CCFLAGS=["-fexceptions"]) + else: + env.Append(CCFLAGS=["-fno-exceptions"]) + + # Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation + env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) + + env.Prepend( + CPPPATH=[ + "$IOS_SDK_PATH/usr/include", + "$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers", + ] + ) + + env.Prepend(CPPPATH=["#platform/ios"]) + env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) + + if env["vulkan"]: + env.Append(CPPDEFINES=["VULKAN_ENABLED"]) diff --git a/platform/ios/device_metrics.h b/platform/ios/device_metrics.h new file mode 100644 index 0000000000..b9fb9b2fd9 --- /dev/null +++ b/platform/ios/device_metrics.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* device_metrics.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@interface GodotDeviceMetrics : NSObject + +@property(nonatomic, class, readonly, strong) NSDictionary *dpiList; + +@end diff --git a/platform/ios/device_metrics.m b/platform/ios/device_metrics.m new file mode 100644 index 0000000000..ec4dd8130d --- /dev/null +++ b/platform/ios/device_metrics.m @@ -0,0 +1,152 @@ +/*************************************************************************/ +/* device_metrics.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "device_metrics.h" + +@implementation GodotDeviceMetrics + ++ (NSDictionary *)dpiList { + return @{ + @[ + @"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, + }; +} + +@end diff --git a/platform/ios/display_layer.h b/platform/ios/display_layer.h new file mode 100644 index 0000000000..a17c75dba1 --- /dev/null +++ b/platform/ios/display_layer.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* display_layer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 +#import + +@protocol DisplayLayer + +- (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 +#else +@interface GodotMetalLayer : CALayer +#endif +#else +@interface GodotMetalLayer : CAMetalLayer +#endif +@end + +API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) +@interface GodotOpenGLLayer : CAEAGLLayer + +@end diff --git a/platform/ios/display_layer.mm b/platform/ios/display_layer.mm new file mode 100644 index 0000000000..7c83494768 --- /dev/null +++ b/platform/ios/display_layer.mm @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* display_layer.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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/config/project_settings.h" +#include "core/os/keyboard.h" +#include "display_server_ios.h" +#include "main/main.h" +#include "os_ios.h" +#include "servers/audio_server.h" + +#import +#import +#import +#import +#import +#import +#import + +@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/driver/driver_name") == "opengl3") { + 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 = nil; + } +} + +- (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)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; + } + + 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/ios/display_server_ios.h b/platform/ios/display_server_ios.h new file mode 100644 index 0000000000..bfd611adb7 --- /dev/null +++ b/platform/ios/display_server_ios.h @@ -0,0 +1,217 @@ +/*************************************************************************/ +/* display_server_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ios_h +#define display_server_ios_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/renderer_rd/renderer_compositor_rd.h" + +#include "vulkan_context_ios.h" + +#import +#ifdef USE_VOLK +#include +#else +#include +#endif +#endif + +class DisplayServerIOS : public DisplayServer { + GDCLASS(DisplayServerIOS, DisplayServer) + + _THREAD_SAFE_CLASS_ + +#if defined(VULKAN_ENABLED) + VulkanContextIOS *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + + id tts = nullptr; + + 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 &p_event); + + DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerIOS(); + +public: + String rendering_driver; + + static DisplayServerIOS *get_singleton(); + + static void register_ios_driver(); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + static Vector 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 &p_event); + void send_input_event(const Ref &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_double_click); + 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(Key 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 bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual Array tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + + virtual Rect2i get_display_safe_area() 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 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 float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector get_window_list() const override; + + virtual WindowID + get_window_at_screen_position(const Point2i &p_position) const override; + + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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 void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) 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; + + void resize_window(CGSize size); +}; + +#endif /* DISPLAY_SERVER_IOS_H */ diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm new file mode 100644 index 0000000000..73d4a2a427 --- /dev/null +++ b/platform/ios/display_server_ios.mm @@ -0,0 +1,655 @@ +/*************************************************************************/ +/* display_server_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ios.h" + +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" +#import "device_metrics.h" +#import "godot_view.h" +#include "ios.h" +#import "keyboard_input_view.h" +#include "os_ios.h" +#include "tts_ios.h" +#import "view_controller.h" + +#import +#import + +static const float kDisplayServerIOSAcceleration = 1.f; + +DisplayServerIOS *DisplayServerIOS::get_singleton() { + return (DisplayServerIOS *)DisplayServer::get_singleton(); +} + +DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + rendering_driver = p_rendering_driver; + + // Init TTS + tts = [[TTS_IOS alloc] init]; + +#if defined(GLES3_ENABLED) + // FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented + // again, + // Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS + // We are hardcoding the rendering_driver to "vulkan" down below + + if (rendering_driver == "opengl_es") { + bool gl_initialization_error = false; + + // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + + if (RasterizerGLES3::is_viable() == OK) { + RasterizerGLES3::register_config(); + RasterizerGLES3::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(RenderingServerDefault); + // // 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 + // RasterizerStorageGLES3system_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(VulkanContextIOS); + 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, p_vsync_mode, 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); + + RendererCompositorRD::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; +} + +DisplayServerIOS::~DisplayServerIOS() { +#if defined(VULKAN_ENABLED) + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + rendering_device_vulkan = nullptr; + } + + if (context_vulkan) { + context_vulkan->window_destroy(MAIN_WINDOW_ID); + memdelete(context_vulkan); + context_vulkan = nullptr; + } +#endif +} + +DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); +} + +Vector DisplayServerIOS::get_rendering_drivers_func() { + Vector drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(GLES3_ENABLED) + drivers.push_back("opengl_es"); +#endif + + return drivers; +} + +void DisplayServerIOS::register_ios_driver() { + register_create_function("iOS", create_func, get_rendering_drivers_func); +} + +// MARK: Events + +void DisplayServerIOS::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + window_resize_callback = p_callable; +} + +void DisplayServerIOS::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + window_event_callback = p_callable; +} +void DisplayServerIOS::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerIOS::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + input_text_callback = p_callable; +} + +void DisplayServerIOS::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerIOS::process_events() { + Input::get_singleton()->flush_buffered_events(); +} + +void DisplayServerIOS::_dispatch_input_events(const Ref &p_event) { + DisplayServerIOS::get_singleton()->send_input_event(p_event); +} + +void DisplayServerIOS::send_input_event(const Ref &p_event) const { + _window_callback(input_event_callback, p_event); +} + +void DisplayServerIOS::send_input_text(const String &p_text) const { + _window_callback(input_text_callback, p_text); +} + +void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) const { + _window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerIOS::_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 DisplayServerIOS::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) { + if (!GLOBAL_DEF("debug/disable_touch", false)) { + Ref ev; + ev.instantiate(); + + ev->set_index(p_idx); + ev->set_pressed(p_pressed); + ev->set_position(Vector2(p_x, p_y)); + perform_event(ev); + } +} + +void DisplayServerIOS::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 ev; + ev.instantiate(); + 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 DisplayServerIOS::perform_event(const Ref &p_event) { + Input::get_singleton()->parse_input_event(p_event); +} + +void DisplayServerIOS::touches_cancelled(int p_idx) { + touch_press(p_idx, -1, -1, false, false); +} + +// MARK: Keyboard + +void DisplayServerIOS::key(Key p_key, bool p_pressed) { + Ref ev; + ev.instantiate(); + ev->set_echo(false); + ev->set_pressed(p_pressed); + ev->set_keycode(p_key); + ev->set_physical_keycode(p_key); + ev->set_unicode((char32_t)p_key); + perform_event(ev); +} + +// MARK: Motion + +void DisplayServerIOS::update_gravity(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +} + +void DisplayServerIOS::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 / kDisplayServerIOSAcceleration, + p_y / kDisplayServerIOSAcceleration, + p_z / kDisplayServerIOSAcceleration); + + Input::get_singleton()->set_accelerometer(v_accelerometer); +} + +void DisplayServerIOS::update_magnetometer(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +} + +void DisplayServerIOS::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 DisplayServerIOS::has_feature(Feature p_feature) const { + switch (p_feature) { + // 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_WINDOW_TRANSPARENCY: + case FEATURE_CLIPBOARD: + case FEATURE_KEEP_SCREEN_ON: + case FEATURE_ORIENTATION: + case FEATURE_TOUCHSCREEN: + case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_TEXT_TO_SPEECH: + return true; + default: + return false; + } +} + +String DisplayServerIOS::get_name() const { + return "iOS"; +} + +bool DisplayServerIOS::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerIOS::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isPaused]; +} + +Array DisplayServerIOS::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, Array()); + return [tts getVoices]; +} + +void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!tts); + [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; +} + +void DisplayServerIOS::tts_pause() { + ERR_FAIL_COND(!tts); + [tts pauseSpeaking]; +} + +void DisplayServerIOS::tts_resume() { + ERR_FAIL_COND(!tts); + [tts resumeSpeaking]; +} + +void DisplayServerIOS::tts_stop() { + ERR_FAIL_COND(!tts); + [tts stopSpeaking]; +} + +Rect2i DisplayServerIOS::get_display_safe_area() 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(); + 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() + insets_position, screen_get_size() - insets_size); + } else { + return Rect2i(screen_get_position(), screen_get_size()); + } +} + +int DisplayServerIOS::get_screen_count() const { + return 1; +} + +Point2i DisplayServerIOS::screen_get_position(int p_screen) const { + return Size2i(); +} + +Size2i DisplayServerIOS::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 DisplayServerIOS::screen_get_usable_rect(int p_screen) const { + return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); +} + +int DisplayServerIOS::screen_get_dpi(int p_screen) const { + struct utsname systemInfo; + uname(&systemInfo); + + NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + + NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; + + for (NSArray *keyArray in iOSModelToDPI) { + if ([keyArray containsObject:string]) { + NSNumber *value = iOSModelToDPI[keyArray]; + return [value intValue]; + } + } + + // If device wasn't found in dictionary + // make a best guess from device metrics. + CGFloat scale = [UIScreen mainScreen].scale; + + UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; + + switch (idiom) { + case UIUserInterfaceIdiomPad: + return scale == 2 ? 264 : 132; + case UIUserInterfaceIdiomPhone: { + if (scale == 3) { + CGFloat nativeScale = [UIScreen mainScreen].nativeScale; + return nativeScale == 3 ? 458 : 401; + } + + return 326; + } + default: + return 72; + } +} + +float DisplayServerIOS::screen_get_refresh_rate(int p_screen) const { + return [UIScreen mainScreen].maximumFramesPerSecond; +} + +float DisplayServerIOS::screen_get_scale(int p_screen) const { + return [UIScreen mainScreen].nativeScale; +} + +Vector DisplayServerIOS::get_window_list() const { + Vector list; + list.push_back(MAIN_WINDOW_ID); + return list; +} + +DisplayServer::WindowID DisplayServerIOS::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +int64_t DisplayServerIOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)AppDelegate.viewController; + } + case WINDOW_VIEW: { + return (int64_t)AppDelegate.viewController.godotView; + } + default: { + return 0; + } + } +} + +void DisplayServerIOS::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerIOS::window_get_attached_instance_id(WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerIOS::window_set_title(const String &p_title, WindowID p_window) { + // Probably not supported for iOS +} + +int DisplayServerIOS::window_get_current_screen(WindowID p_window) const { + return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerIOS::window_set_current_screen(int p_screen, WindowID p_window) { + // Probably not supported for iOS +} + +Point2i DisplayServerIOS::window_get_position(WindowID p_window) const { + return Point2i(); +} + +void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) { + // Probably not supported for single window iOS app +} + +void DisplayServerIOS::window_set_transient(WindowID p_window, WindowID p_parent) { + // Probably not supported for iOS +} + +void DisplayServerIOS::window_set_max_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIOS::window_get_max_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerIOS::window_set_min_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIOS::window_get_min_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerIOS::window_set_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIOS::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 DisplayServerIOS::window_get_real_size(WindowID p_window) const { + return window_get_size(p_window); +} + +void DisplayServerIOS::window_set_mode(WindowMode p_mode, WindowID p_window) { + // Probably not supported for iOS +} + +DisplayServer::WindowMode DisplayServerIOS::window_get_mode(WindowID p_window) const { + return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerIOS::window_is_maximize_allowed(WindowID p_window) const { + return false; +} + +void DisplayServerIOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + // Probably not supported for iOS +} + +bool DisplayServerIOS::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + return false; +} + +void DisplayServerIOS::window_request_attention(WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerIOS::window_move_to_foreground(WindowID p_window) { + // Probably not supported for iOS +} + +float DisplayServerIOS::screen_get_max_scale() const { + return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +} + +void DisplayServerIOS::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { + screen_orientation = p_orientation; +} + +DisplayServer::ScreenOrientation DisplayServerIOS::screen_get_orientation(int p_screen) const { + return screen_orientation; +} + +bool DisplayServerIOS::window_can_draw(WindowID p_window) const { + return true; +} + +bool DisplayServerIOS::can_any_window_draw() const { + return true; +} + +bool DisplayServerIOS::screen_is_touchscreen(int p_screen) const { + return true; +} + +void DisplayServerIOS::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) { + NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()]; + + [AppDelegate.viewController.keyboardView + becomeFirstResponderWithString:existingString + multiline:p_multiline + cursorStart:p_cursor_start + cursorEnd:p_cursor_end]; +} + +void DisplayServerIOS::virtual_keyboard_hide() { + [AppDelegate.viewController.keyboardView resignFirstResponder]; +} + +void DisplayServerIOS::virtual_keyboard_set_height(int height) { + virtual_keyboard_height = height * screen_get_max_scale(); +} + +int DisplayServerIOS::virtual_keyboard_get_height() const { + return virtual_keyboard_height; +} + +void DisplayServerIOS::clipboard_set(const String &p_text) { + [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()]; +} + +String DisplayServerIOS::clipboard_get() const { + NSString *text = [UIPasteboard generalPasteboard].string; + + return String::utf8([text UTF8String]); +} + +void DisplayServerIOS::screen_set_keep_on(bool p_enable) { + [UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerIOS::screen_is_kept_on() const { + return [UIApplication sharedApplication].idleTimerDisabled; +} + +void DisplayServerIOS::resize_window(CGSize viewSize) { + Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); + +#if defined(VULKAN_ENABLED) + 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); +} + +void DisplayServerIOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(VULKAN_ENABLED) + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); +#endif +} + +DisplayServer::VSyncMode DisplayServerIOS::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(VULKAN_ENABLED) + return context_vulkan->get_vsync_mode(p_window); +#else + return DisplayServer::VSYNC_ENABLED; +#endif +} diff --git a/platform/ios/export/export.cpp b/platform/ios/export/export.cpp new file mode 100644 index 0000000000..1531c2bde5 --- /dev/null +++ b/platform/ios/export/export.cpp @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* export.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "export.h" + +#include "export_plugin.h" + +void register_ios_exporter() { + Ref platform; + platform.instantiate(); + + EditorExport::get_singleton()->add_export_platform(platform); +} diff --git a/platform/ios/export/export.h b/platform/ios/export/export.h new file mode 100644 index 0000000000..756a1356ea --- /dev/null +++ b/platform/ios/export/export.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* export.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_EXPORT_H +#define IOS_EXPORT_H + +void register_ios_exporter(); + +#endif // IOS_EXPORT_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp new file mode 100644 index 0000000000..a2e80d33fd --- /dev/null +++ b/platform/ios/export/export_plugin.cpp @@ -0,0 +1,1849 @@ +/*************************************************************************/ +/* export_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "export_plugin.h" + +#include "editor/editor_node.h" + +void EditorExportPlatformIOS::get_preset_features(const Ref &p_preset, List *r_features) { + String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); + // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. + r_features->push_back("etc2"); + + Vector architectures = _get_preset_architectures(p_preset); + for (int i = 0; i < architectures.size(); ++i) { + r_features->push_back(architectures[i]); + } +} + +Vector EditorExportPlatformIOS::_get_supported_architectures() { + Vector archs; + archs.push_back(ExportArchitecture("arm64", true)); + return archs; +} + +struct LoadingScreenInfo { + const char *preset_key; + const char *export_name; + int width = 0; + int height = 0; + bool rotate = false; +}; + +static const LoadingScreenInfo loading_screen_infos[] = { + { PNAME("landscape_launch_screens/iphone_2436x1125"), "Default-Landscape-X.png", 2436, 1125, false }, + { PNAME("landscape_launch_screens/iphone_2208x1242"), "Default-Landscape-736h@3x.png", 2208, 1242, false }, + { PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false }, + { PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false }, + + { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, true }, + { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, true }, + { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, true }, + { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, true }, + { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, true }, + { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, true }, + { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, true } +}; + +void EditorExportPlatformIOS::get_export_options(List *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"), "")); + + Vector architectures = _get_supported_architectures(); + for (int i = 0; i < architectures.size(); ++i) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); + + 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")); + + Vector found_plugins = get_plugins(); + for (int i = 0; i < found_plugins.size(); i++) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); + } + + HashSet plist_keys; + + for (int i = 0; i < found_plugins.size(); i++) { + // Editable plugin plist values + PluginConfigIOS plugin = found_plugins[i]; + + for (const KeyValue &E : plugin.plist) { + switch (E.value.type) { + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + E.key; + if (!plist_keys.has(preset_name)) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value)); + plist_keys.insert(preset_name); + } + } break; + default: + continue; + } + } + } + + plugins_changed.clear(); + plugins = found_plugins; + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "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())); + + 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"), "")); + } +} + +void EditorExportPlatformIOS::_fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug) { + static const String export_method_string[] = { + "app-store", + "development", + "ad-hoc", + "enterprise" + }; + static const String storyboard_image_scale_mode[] = { + "center", + "scaleAspectFit", + "scaleAspectFill", + "scaleToFill" + }; + String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); + String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); + bool dbg_manual = !p_preset->get("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); + bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); + String str; + String strnew; + str.parse_utf8((const char *)pfile.ptr(), pfile.size()); + Vector lines = str.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (lines[i].find("$binary") != -1) { + strnew += lines[i].replace("$binary", p_config.binary_name) + "\n"; + } else if (lines[i].find("$modules_buildfile") != -1) { + strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n"; + } else if (lines[i].find("$modules_fileref") != -1) { + strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n"; + } else if (lines[i].find("$modules_buildphase") != -1) { + strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n"; + } else if (lines[i].find("$modules_buildgrp") != -1) { + strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; + } else if (lines[i].find("$name") != -1) { + strnew += lines[i].replace("$name", p_config.pkg_name) + "\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) { + strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; + } else if (lines[i].find("$signature") != -1) { + strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; + } else if (lines[i].find("$team_id") != -1) { + strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; + } else if (lines[i].find("$default_build_config") != -1) { + strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n"; + } else if (lines[i].find("$export_method") != -1) { + int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); + strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; + } else if (lines[i].find("$provisioning_profile_uuid_release") != -1) { + strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n"; + } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) { + strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n"; + } else if (lines[i].find("$code_sign_style_debug") != -1) { + if (dbg_manual) { + strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n"; + } + } else if (lines[i].find("$code_sign_style_release") != -1) { + if (rel_manual) { + strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; + } + } else if (lines[i].find("$provisioning_profile_uuid") != -1) { + String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release"); + strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; + } else if (lines[i].find("$code_sign_identity_debug") != -1) { + strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; + } else if (lines[i].find("$code_sign_identity_release") != -1) { + strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; + } else if (lines[i].find("$additional_plist_content") != -1) { + strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; + } else if (lines[i].find("$godot_archs") != -1) { + strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n"; + } else if (lines[i].find("$linker_flags") != -1) { + strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; + } else if (lines[i].find("$targeted_device_family") != -1) { + String xcode_value; + switch ((int)p_preset->get("application/targeted_device_family")) { + case 0: // iPhone + xcode_value = "1"; + break; + case 1: // iPad + xcode_value = "2"; + break; + case 2: // iPhone & iPad + xcode_value = "1,2"; + break; + } + strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n"; + } else if (lines[i].find("$cpp_code") != -1) { + strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; + } else if (lines[i].find("$docs_in_place") != -1) { + strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "" : "") + "\n"; + } else if (lines[i].find("$docs_sharing") != -1) { + strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "" : "") + "\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 ? "aps-environmentdevelopment" : "") + "\n"; + } else if (lines[i].find("$required_device_capabilities") != -1) { + String capabilities; + + // I've removed armv7 as we can run on 64bit only devices + // Note that capabilities listed here are requirements for the app to be installed. + // They don't enable anything. + Vector capabilities_list = p_config.capabilities; + + if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { + capabilities_list.push_back("wifi"); + } + + for (int idx = 0; idx < capabilities_list.size(); idx++) { + capabilities += "" + capabilities_list[idx] + "\n"; + } + + strnew += lines[i].replace("$required_device_capabilities", capabilities); + } else if (lines[i].find("$interface_orientations") != -1) { + String orientations; + const DisplayServer::ScreenOrientation screen_orientation = + DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))); + + switch (screen_orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + break; + case DisplayServer::SCREEN_PORTRAIT: + orientations += "UIInterfaceOrientationPortrait\n"; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + // Allow both landscape orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + break; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + // Allow both portrait orientations depending on sensor direction. + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + case DisplayServer::SCREEN_SENSOR: + // Allow all screen orientations depending on sensor direction. + orientations += "UIInterfaceOrientationLandscapeLeft\n"; + orientations += "UIInterfaceOrientationLandscapeRight\n"; + orientations += "UIInterfaceOrientationPortrait\n"; + orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; + break; + } + + strnew += lines[i].replace("$interface_orientations", orientations); + } else if (lines[i].find("$camera_usage_description") != -1) { + String description = p_preset->get("privacy/camera_usage_description"); + strnew += lines[i].replace("$camera_usage_description", description) + "\n"; + } else if (lines[i].find("$microphone_usage_description") != -1) { + String description = p_preset->get("privacy/microphone_usage_description"); + strnew += lines[i].replace("$microphone_usage_description", description) + "\n"; + } 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 ? "UILaunchStoryboardName\nLaunch Screen" : ""; + 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 = \"\"; };" : ""; + 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 if (lines[i].find("$pbx_locale_file_reference") != -1) { + String locale_files; + Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + int index = 0; + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"\"; };"; + } + index++; + } + } + strnew += lines[i].replace("$pbx_locale_file_reference", locale_files); + } else if (lines[i].find("$pbx_locale_build_reference") != -1) { + String locale_files; + Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + int index = 0; + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,"; + } + index++; + } + } + strnew += lines[i].replace("$pbx_locale_build_reference", locale_files); + } else { + strnew += lines[i] + "\n"; + } + } + + // !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero... + // should apply the same fix in our macOS export. + CharString cs = strnew.utf8(); + pfile.resize(cs.size() - 1); + for (int i = 0; i < cs.size() - 1; i++) { + pfile.write[i] = cs[i]; + } +} + +String EditorExportPlatformIOS::_get_additional_plist_content() { + Vector> 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(); + } + return result; +} + +String EditorExportPlatformIOS::_get_linker_flags() { + Vector> 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 (result.length() > 0) { + result += ' '; + } + result += flags; + } + // the flags will be enclosed in quotes, so need to escape them + return result.replace("\"", "\\\""); +} + +String EditorExportPlatformIOS::_get_cpp_code() { + Vector> 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(); + } + return result; +} + +void EditorExportPlatformIOS::_blend_and_rotate(Ref &p_dst, Ref &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; + const char *export_name; + const char *actual_size_side; + const char *scale; + const char *unscaled_size; +}; + +static const IconInfo icon_infos[] = { + // Home screen on iPhone + { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" }, + { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" }, + { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" }, + + // Home screen on iPad + { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" }, + { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" }, + { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" }, + + // App Store + { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" }, + + // Spotlight + { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" }, + { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" }, + { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" } +}; + +Error EditorExportPlatformIOS::_export_icons(const Ref &p_preset, const String &p_iconset_dir) { + String json_description = "{\"images\":["; + String sizes; + + Ref da = DirAccess::open(p_iconset_dir); + ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); + + 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) { + // Resize main app icon + icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + Ref 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 { + // Load custom icon and resize if required + Ref 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; + } + if (img->get_width() != side_size || img->get_height() != side_size) { + WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + "."); + img->resize(side_size, side_size); + err = img->save_png(p_iconset_dir + info.export_name); + } else { + err = da->copy(icon_path, 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; + } + } + sizes += String(info.actual_size_side) + "\n"; + if (i > 0) { + json_description += ","; + } + json_description += String("{"); + json_description += String("\"idiom\":") + "\"" + info.idiom + "\","; + json_description += String("\"size\":") + "\"" + info.unscaled_size + "\","; + json_description += String("\"scale\":") + "\"" + info.scale + "\","; + json_description += String("\"filename\":") + "\"" + info.export_name + "\""; + json_description += String("}"); + } + json_description += "]}"; + + Ref json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); + ERR_FAIL_COND_V(json_file.is_null(), ERR_CANT_CREATE); + CharString json_utf8 = json_description.utf8(); + json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length()); + + Ref sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE); + ERR_FAIL_COND_V(sizes_file.is_null(), ERR_CANT_CREATE); + CharString sizes_utf8 = sizes.utf8(); + sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length()); + + return OK; +} + +Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref &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; + String image_path = p_dest_dir.plus_file("splash@2x.png"); + image.instantiate(); + 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.instantiate(); + 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 splash; + + const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + + if (!splash_path.is_empty()) { + splash.instantiate(); + const Error err = splash->load(splash_path); + if (err) { + splash.unref(); + } + } + + if (splash.is_null()) { + splash = Ref(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 &p_preset, const String &p_dest_dir) { + Ref da = DirAccess::open(p_dest_dir); + ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); + + for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { + LoadingScreenInfo info = loading_screen_infos[i]; + String loading_screen_file = p_preset->get(info.preset_key); + + 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"); + + if (loading_screen_file.size() > 0) { + // Load custom loading screens, and resize if required. + Ref 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) { + WARN_PRINT("Loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(info.width) + "x" + String::num_int64(info.height) + "."); + float aspect_ratio = (float)img->get_width() / (float)img->get_height(); + if (boot_logo_scale) { + if (info.height * aspect_ratio <= info.width) { + img->resize(info.height * aspect_ratio, info.height); + } else { + img->resize(info.width, info.width / aspect_ratio); + } + } + Ref new_img = memnew(Image); + new_img->create(info.width, info.height, false, Image::FORMAT_RGBA8); + new_img->fill(boot_bg_color); + _blend_and_rotate(new_img, img, false); + err = new_img->save_png(p_dest_dir + info.export_name); + } else { + err = da->copy(loading_screen_file, p_dest_dir + info.export_name); + } + if (err) { + 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 { + // Generate loading screen from the splash screen + Ref img = memnew(Image); + img->create(info.width, info.height, false, Image::FORMAT_RGBA8); + img->fill(boot_bg_color); + + Ref img_bs; + + if (boot_logo_path.length() > 0) { + img_bs = Ref(memnew(Image)); + ImageLoader::load_image(boot_logo_path, img_bs); + } + if (!img_bs.is_valid()) { + img_bs = Ref(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()); + } + } + } + + return OK; +} + +Error EditorExportPlatformIOS::_walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata) { + Vector dirs; + String current_dir = p_da->get_current_dir(); + p_da->list_dir_begin(); + String path = p_da->get_next(); + while (!path.is_empty()) { + if (p_da->current_is_dir()) { + if (path != "." && path != "..") { + dirs.push_back(path); + } + } else { + Error err = p_handler(current_dir.plus_file(path), p_userdata); + if (err) { + p_da->list_dir_end(); + return err; + } + } + path = p_da->get_next(); + } + p_da->list_dir_end(); + + for (int i = 0; i < dirs.size(); ++i) { + String dir = dirs[i]; + p_da->change_dir(dir); + Error err = _walk_dir_recursive(p_da, p_handler, p_userdata); + p_da->change_dir(".."); + if (err) { + return err; + } + } + + return OK; +} + +struct CodesignData { + const Ref &preset; + bool debug = false; + + CodesignData(const Ref &p_preset, bool p_debug) : + preset(p_preset), + debug(p_debug) { + } +}; + +Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { + if (p_file.ends_with(".dylib")) { + CodesignData *data = static_cast(p_userdata); + print_line(String("Signing ") + p_file); + + String sign_id; + if (data->debug) { + sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : data->preset->get("application/code_sign_identity_debug"); + } else { + sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : data->preset->get("application/code_sign_identity_release"); + } + + List codesign_args; + codesign_args.push_back("-f"); + codesign_args.push_back("-s"); + codesign_args.push_back(sign_id); + codesign_args.push_back(p_file); + String str; + Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true); + print_verbose("codesign (" + p_file + "):\n" + str); + + return err; + } + return OK; +} + +struct PbxId { +private: + static char _hex_char(uint8_t four_bits) { + if (four_bits < 10) { + return ('0' + four_bits); + } + return 'A' + (four_bits - 10); + } + + static String _hex_pad(uint32_t num) { + Vector ret; + ret.resize(sizeof(num) * 2); + for (uint64_t i = 0; i < sizeof(num) * 2; ++i) { + uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF; + ret.write[i] = _hex_char(four_bits); + } + return String::utf8(ret.ptr(), ret.size()); + } + +public: + uint32_t high_bits; + uint32_t mid_bits; + uint32_t low_bits; + + String str() const { + return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits); + } + + PbxId &operator++() { + low_bits++; + if (!low_bits) { + mid_bits++; + if (!mid_bits) { + high_bits++; + } + } + + return *this; + } +}; + +struct ExportLibsData { + Vector lib_paths; + String dest_dir; +}; + +void EditorExportPlatformIOS::_add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets) { + // 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 }; + String pbx_files; + String pbx_frameworks_build; + 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 = \"\"; };\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")) { + type = "archive.ar"; + } else { + type = "file"; + } + + String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build; + String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs; + + if (pbx_build.length() > 0) { + pbx_build += ",\n"; + pbx_refs += ",\n"; + } + pbx_build += build_id; + pbx_refs += ref_id; + + Dictionary format_dict; + format_dict["build_id"] = build_id; + format_dict["ref_id"] = ref_id; + format_dict["name"] = asset.exported_path.get_file(); + format_dict["file_path"] = asset.exported_path; + format_dict["file_type"] = type; + 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 + // even if turned off in capabilities. + + String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size()); + str = str.replace("$additional_pbx_files", pbx_files); + str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build); + 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); + for (int i = 0; i < cs.size() - 1; i++) { + p_project_data.write[i] = cs[i]; + } +} + +Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) { + String binary_name = p_out_dir.get_file().get_basename(); + + Ref da = DirAccess::create_for_path(p_asset); + if (da.is_null()) { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); + } + bool file_exists = da->file_exists(p_asset); + bool dir_exists = da->dir_exists(p_asset); + if (!file_exists && !dir_exists) { + return ERR_FILE_NOT_FOUND; + } + + String base_dir = p_asset.get_base_dir().replace("res://", ""); + String destination_dir; + String destination; + String asset_path; + + bool create_framework = false; + + if (p_is_framework && p_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; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + 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 && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) { + asset_path = String("dylibs").plus_file(base_dir); + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + 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; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + 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); + } + + Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } + } + + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + 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; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + String framework_name = file_name + ".framework"; + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List 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); + } + + // Creating Info.plist + { + String info_plist_format = "\n" + "\n" + "\n" + "\n" + "CFBundleShortVersionString\n" + "1.0\n" + "CFBundleIdentifier\n" + "com.gdnative.framework.$name\n" + "CFBundleName\n" + "$name\n" + "CFBundleExecutable\n" + "$name\n" + "DTPlatformName\n" + "iphoneos\n" + "CFBundleInfoDictionaryVersion\n" + "6.0\n" + "CFBundleVersion\n" + "1\n" + "CFBundlePackageType\n" + "FMWK\n" + "MinimumOSVersion\n" + "10.0\n" + "\n" + ""; + + String info_plist = info_plist_format.replace("$name", file_name); + + Ref f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { + f->store_string(info_plist); + } + } + } + + return OK; +} + +Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) { + 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, p_should_embed }; + r_exported_assets.push_back(exported_asset); + } else { + Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + } + + return OK; +} + +Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets) { + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + Vector 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 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 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, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); + err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + + Vector library_paths; + 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, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + return OK; +} + +Vector EditorExportPlatformIOS::_get_preset_architectures(const Ref &p_preset) { + Vector all_archs = _get_supported_architectures(); + Vector enabled_archs; + for (int i = 0; i < all_archs.size(); ++i) { + bool is_enabled = p_preset->get("architectures/" + all_archs[i].name); + if (is_enabled) { + enabled_archs.push_back(all_archs[i].name); + } + } + return enabled_archs; +} + +Error EditorExportPlatformIOS::_export_ios_plugins(const Ref &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug) { + String plugin_definition_cpp_code; + String plugin_initialization_cpp_code; + String plugin_deinitialization_cpp_code; + + Vector plugin_linked_dependencies; + Vector plugin_embedded_dependencies; + Vector plugin_files; + + Vector enabled_plugins = get_enabled_plugins(p_preset); + + Vector added_linked_dependenciy_names; + Vector added_embedded_dependenciy_names; + HashMap plist_values; + + HashSet plugin_linker_flags; + + Error err; + + for (int i = 0; i < enabled_plugins.size(); i++) { + PluginConfigIOS plugin = enabled_plugins[i]; + + // Export plugin binary. + String plugin_main_binary = PluginConfigIOS::get_plugin_main_binary(plugin, p_debug); + String plugin_binary_result_file = plugin.binary.get_file(); + // We shouldn't embed .xcframework that contains static libraries. + // Static libraries are not embedded anyway. + err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); + + ERR_FAIL_COND_V(err, err); + + // Adding dependencies. + // Use separate container for names to check for duplicates. + for (int j = 0; j < plugin.linked_dependencies.size(); j++) { + String dependency = plugin.linked_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.has(name)) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.system_dependencies.size(); j++) { + String dependency = plugin.system_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.has(name)) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.embedded_dependencies.size(); j++) { + String dependency = plugin.embedded_dependencies[j]; + String name = dependency.get_file(); + + if (added_embedded_dependenciy_names.has(name)) { + continue; + } + + added_embedded_dependenciy_names.push_back(name); + plugin_embedded_dependencies.push_back(dependency); + } + + plugin_files.append_array(plugin.files_to_copy); + + // Capabilities + // Also checking for duplicates. + for (int j = 0; j < plugin.capabilities.size(); j++) { + String capability = plugin.capabilities[j]; + + if (p_config_data.capabilities.has(capability)) { + continue; + } + + p_config_data.capabilities.push_back(capability); + } + + // Linker flags + // Checking duplicates + for (int j = 0; j < plugin.linker_flags.size(); j++) { + String linker_flag = plugin.linker_flags[j]; + plugin_linker_flags.insert(linker_flag); + } + + // Plist + // Using hash map container to remove duplicates + + for (const KeyValue &E : plugin.plist) { + String key = E.key; + const PluginConfigIOS::PlistItem &item = E.value; + + String value; + + switch (item.type) { + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + key; + String input_value = p_preset->get(preset_name); + value = "" + input_value + ""; + } break; + default: + value = item.value; + break; + } + + if (key.is_empty() || value.is_empty()) { + continue; + } + + String plist_key = "" + key + ""; + + plist_values[plist_key] = value; + } + + // CPP Code + String definition_comment = "// Plugin: " + plugin.name + "\n"; + String initialization_method = plugin.initialization_method + "();\n"; + String deinitialization_method = plugin.deinitialization_method + "();\n"; + + plugin_definition_cpp_code += definition_comment + + "extern void " + initialization_method + + "extern void " + deinitialization_method + "\n"; + + plugin_initialization_cpp_code += "\t" + initialization_method; + plugin_deinitialization_cpp_code += "\t" + deinitialization_method; + } + + // Updating `Info.plist` + { + for (const KeyValue &E : plist_values) { + String key = E.key; + String value = E.value; + + if (key.is_empty() || value.is_empty()) { + continue; + } + + p_config_data.plist_content += key + value + "\n"; + } + } + + // Export files + { + // Export linked plugin dependency + err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export embedded plugin dependency + err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export plugin files + err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + + // Update CPP + { + Dictionary plugin_format; + plugin_format["definition"] = plugin_definition_cpp_code; + plugin_format["initialization"] = plugin_initialization_cpp_code; + plugin_format["deinitialization"] = plugin_deinitialization_cpp_code; + + String plugin_cpp_code = "\n// Godot Plugins\n" + "void godot_ios_plugins_initialize();\n" + "void godot_ios_plugins_deinitialize();\n" + "// Exported Plugins\n\n" + "$definition" + "// Use Plugins\n" + "void godot_ios_plugins_initialize() {\n" + "$initialization" + "}\n\n" + "void godot_ios_plugins_deinitialize() {\n" + "$deinitialization" + "}\n"; + + p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_"); + } + + // Update Linker Flag Values + { + String result_linker_flags = " "; + for (const String &E : plugin_linker_flags) { + const String &flag = E; + + if (flag.length() == 0) { + continue; + } + + if (result_linker_flags.length() > 0) { + result_linker_flags += ' '; + } + + result_linker_flags += flag; + } + result_linker_flags = result_linker_flags.replace("\"", "\\\""); + p_config_data.linker_flags += result_linker_flags; + } + + return OK; +} + +Error EditorExportPlatformIOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + String src_pkg_name; + String dest_dir = p_path.get_base_dir() + "/"; + String binary_name = p_path.get_file().get_basename(); + + EditorProgress ep("export", "Exporting for iOS", 5, true); + + 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) { + src_pkg_name = p_preset->get("custom_template/debug"); + } else { + src_pkg_name = p_preset->get("custom_template/release"); + } + + if (src_pkg_name.is_empty()) { + String err; + src_pkg_name = find_export_template("ios.zip", &err); + if (src_pkg_name.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); + return ERR_FILE_NOT_FOUND; + } + } + + if (!DirAccess::exists(dest_dir)) { + return ERR_FILE_BAD_PATH; + } + + { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_valid()) { + String current_dir = da->get_current_dir(); + + // remove leftovers from last export so they don't interfere + // in case some files are no longer needed + if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { + da->erase_contents_recursive(); + } + if (da->change_dir(dest_dir + binary_name) == OK) { + da->erase_contents_recursive(); + } + + da->change_dir(current_dir); + + if (!da->dir_exists(dest_dir + binary_name)) { + Error err = da->make_dir(dest_dir + binary_name); + if (err) { + return err; + } + } + } + } + + if (ep.step("Making .pck", 0)) { + return ERR_SKIP; + } + String pack_path = dest_dir + binary_name + ".pck"; + Vector libraries; + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); + if (err) { + return err; + } + + if (ep.step("Extracting and configuring Xcode project", 1)) { + return ERR_SKIP; + } + + String library_to_use = "libgodot.ios." + String(p_debug ? "debug" : "release") + ".xcframework"; + + print_line("Static framework: " + library_to_use); + String pkg_name; + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + } else { + pkg_name = "Unnamed"; + } + + bool found_library = false; + + const String project_file = "godot_ios.xcodeproj/project.pbxproj"; + HashSet files_to_parse; + files_to_parse.insert("godot_ios/godot_ios-Info.plist"); + files_to_parse.insert(project_file); + files_to_parse.insert("godot_ios/export_options.plist"); + 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, + binary_name, + _get_additional_plist_content(), + String(" ").join(_get_preset_architectures(p_preset)), + _get_linker_flags(), + _get_cpp_code(), + "", + "", + "", + "", + Vector() + }; + + Vector assets; + + Ref tmp_app_path = DirAccess::create_for_path(dest_dir); + ERR_FAIL_COND_V(tmp_app_path.is_null(), ERR_CANT_CREATE); + + print_line("Unzipping..."); + Ref io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); + if (!src_pkg_zip) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name)); + return ERR_CANT_OPEN; + } + + err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug); + ERR_FAIL_COND_V(err, err); + + //export rest of the files + int ret = unzGoToFirstFile(src_pkg_zip); + Vector project_file_data; + while (ret == UNZ_OK) { +#if defined(MACOS_ENABLED) || defined(X11_ENABLED) + bool is_execute = false; +#endif + + //get filename + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + break; + } + + String file = String::utf8(fname); + + print_line("READ: " + file); + Vector data; + data.resize(info.uncompressed_size); + + //read + unzOpenCurrentFile(src_pkg_zip); + unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); + unzCloseCurrentFile(src_pkg_zip); + + //write + + file = file.replace_first("ios/", ""); + + if (files_to_parse.has(file)) { + _fix_config_file(p_preset, data, config_data, p_debug); + } else if (file.begins_with("libgodot.ios")) { + if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { + ret = unzGoToNextFile(src_pkg_zip); + continue; //ignore! + } + found_library = true; +#if defined(MACOS_ENABLED) || defined(X11_ENABLED) + is_execute = true; +#endif + file = file.replace(library_to_use, binary_name + ".xcframework"); + } + + if (file == project_file) { + project_file_data = data; + } + + ///@TODO need to parse logo files + + if (data.size() > 0) { + file = file.replace("godot_ios", binary_name); + + print_line("ADDING: " + file + " size: " + itos(data.size())); + + /* write it into our folder structure */ + file = dest_dir + file; + + /* make sure this folder exists */ + String dir_name = file.get_base_dir(); + if (!tmp_app_path->dir_exists(dir_name)) { + print_line("Creating " + dir_name); + Error dir_err = tmp_app_path->make_dir_recursive(dir_name); + if (dir_err) { + ERR_PRINT("Can't create '" + dir_name + "'."); + unzClose(src_pkg_zip); + return ERR_CANT_CREATE; + } + } + + /* write the file */ + { + Ref f = FileAccess::open(file, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Can't write '" + file + "'."); + unzClose(src_pkg_zip); + return ERR_CANT_CREATE; + }; + f->store_buffer(data.ptr(), data.size()); + } + +#if defined(MACOS_ENABLED) || defined(X11_ENABLED) + if (is_execute) { + // we need execute rights on this file + chmod(file.utf8().get_data(), 0755); + } +#endif + } + + ret = unzGoToNextFile(src_pkg_zip); + } + + /* we're done with our source zip */ + unzClose(src_pkg_zip); + + if (!found_library) { + ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive."); + return ERR_FILE_NOT_FOUND; + } + + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); + Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); + Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); + Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); + + Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = dest_dir + binary_name + "/en.lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); + f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); + f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); + f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); + } + + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + String fname = dest_dir + binary_name + "/" + lang + ".lproj"; + tmp_app_path->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + if (appnames.has(lang)) { + f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); + } + if (camera_usage_descriptions.has(lang)) { + f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); + } + if (microphone_usage_descriptions.has(lang)) { + f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); + } + if (photolibrary_usage_descriptions.has(lang)) { + f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";"); + } + } + } + } + + // Copy project static libs to the project + Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + Vector 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 + "'."); + 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); + } + if (err) { + return err; + } + + err = _export_icons(p_preset, iconset_dir); + 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/"; + + Ref launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (launch_screen_da.is_null()) { + 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); + } + } + + if (err) { + return err; + } + + print_line("Exporting additional assets"); + _export_additional_assets(dest_dir + binary_name, libraries, assets); + _add_assets_to_project(p_preset, project_file_data, assets); + String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj"; + { + Ref f = FileAccess::open(project_file_name, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Can't write '" + project_file_name + "'."); + return ERR_CANT_CREATE; + }; + f->store_buffer(project_file_data.ptr(), project_file_data.size()); + } + +#ifdef MACOS_ENABLED + { + if (ep.step("Code-signing dylibs", 2)) { + return ERR_SKIP; + } + Ref dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); + ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN); + CodesignData codesign_data(p_preset, p_debug); + err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); + ERR_FAIL_COND_V(err, err); + } + + if (ep.step("Making .xcarchive", 3)) { + return ERR_SKIP; + } + String archive_path = p_path.get_basename() + ".xcarchive"; + List archive_args; + archive_args.push_back("-project"); + archive_args.push_back(dest_dir + binary_name + ".xcodeproj"); + archive_args.push_back("-scheme"); + archive_args.push_back(binary_name); + archive_args.push_back("-sdk"); + archive_args.push_back("iphoneos"); + archive_args.push_back("-configuration"); + archive_args.push_back(p_debug ? "Debug" : "Release"); + archive_args.push_back("-destination"); + archive_args.push_back("generic/platform=iOS"); + archive_args.push_back("archive"); + archive_args.push_back("-allowProvisioningUpdates"); + archive_args.push_back("-archivePath"); + archive_args.push_back(archive_path); + String archive_str; + err = OS::get_singleton()->execute("xcodebuild", archive_args, &archive_str, nullptr, true); + ERR_FAIL_COND_V(err, err); + print_line("xcodebuild (.xcarchive):\n" + archive_str); + + if (ep.step("Making .ipa", 4)) { + return ERR_SKIP; + } + List export_args; + export_args.push_back("-exportArchive"); + export_args.push_back("-archivePath"); + export_args.push_back(archive_path); + export_args.push_back("-exportOptionsPlist"); + export_args.push_back(dest_dir + binary_name + "/export_options.plist"); + export_args.push_back("-allowProvisioningUpdates"); + export_args.push_back("-exportPath"); + export_args.push_back(dest_dir); + String export_str; + err = OS::get_singleton()->execute("xcodebuild", export_args, &export_str, nullptr, true); + ERR_FAIL_COND_V(err, err); + print_line("xcodebuild (.ipa):\n" + export_str); +#else + print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package."); +#endif + + return OK; +} + +bool EditorExportPlatformIOS::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err; + bool valid = false; + + // Look for export templates (first official, and if defined custom templates). + + bool dvalid = exists_export_template("ios.zip", &err); + bool rvalid = dvalid; // Both in the same ZIP. + + 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"; + } + } + 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"; + } + } + + valid = dvalid || rvalid; + r_missing_templates = !valid; + + // Validate the rest of the configuration. + + String team_id = p_preset->get("application/app_store_team_id"); + if (team_id.length() == 0) { + err += TTR("App Store Team ID not specified - cannot configure the project.") + "\n"; + valid = false; + } + + 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"; + valid = false; + } + + const String etc_error = test_etc2(); + if (!etc_error.is_empty()) { + valid = false; + err += etc_error; + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} + +EditorExportPlatformIOS::EditorExportPlatformIOS() { + logo = ImageTexture::create_from_image(memnew(Image(_ios_logo))); + plugins_changed.set(); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); +} + +EditorExportPlatformIOS::~EditorExportPlatformIOS() { + quit_request.set(); + check_for_changes_thread.wait_to_finish(); +} diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h new file mode 100644 index 0000000000..a30cb4644f --- /dev/null +++ b/platform/ios/export/export_plugin.h @@ -0,0 +1,293 @@ +/*************************************************************************/ +/* export_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_EXPORT_PLUGIN_H +#define IOS_EXPORT_PLUGIN_H + +#include "core/config/project_settings.h" +#include "core/io/file_access.h" +#include "core/io/image_loader.h" +#include "core/io/marshalls.h" +#include "core/io/resource_saver.h" +#include "core/io/zip_io.h" +#include "core/os/os.h" +#include "core/templates/safe_refcount.h" +#include "core/version.h" +#include "editor/editor_export.h" +#include "editor/editor_settings.h" +#include "main/splash.gen.h" +#include "platform/ios/logo.gen.h" +#include "string.h" + +#include "godot_plugin_config.h" + +#include + +class EditorExportPlatformIOS : public EditorExportPlatform { + GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); + + Ref logo; + + // Plugins + SafeFlag plugins_changed; + Thread check_for_changes_thread; + SafeFlag quit_request; + Mutex plugins_lock; + Vector plugins; + + typedef Error (*FileHandler)(String p_file, void *p_userdata); + static Error _walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata); + static Error _codesign(String p_file, void *p_userdata); + void _blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot); + + struct IOSConfigData { + String pkg_name; + String binary_name; + String plist_content; + String architectures; + String linker_flags; + String cpp_code; + String modules_buildfile; + String modules_fileref; + String modules_buildphase; + String modules_buildgrp; + Vector capabilities; + }; + struct ExportArchitecture { + String name; + bool is_default = false; + + ExportArchitecture() {} + + ExportArchitecture(String p_name, bool p_is_default) { + name = p_name; + is_default = p_is_default; + } + }; + + struct IOSExportAsset { + String exported_path; + bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed = false; + }; + + String _get_additional_plist_content(); + String _get_linker_flags(); + String _get_cpp_code(); + void _fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug); + Error _export_loading_screen_images(const Ref &p_preset, const String &p_dest_dir); + Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir); + Error _export_icons(const Ref &p_preset, const String &p_iconset_dir); + + Vector _get_supported_architectures(); + Vector _get_preset_architectures(const Ref &p_preset); + + void _add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); + Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets); + Error _export_ios_plugins(const Ref &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug); + + 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 (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { + if (r_error) { + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); + } + return false; + } + } + + return true; + } + + static void _check_for_changes_poll_thread(void *ud) { + EditorExportPlatformIOS *ea = static_cast(ud); + + while (!ea->quit_request.is_set()) { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed.is_set()) { + MutexLock lock(ea->plugins_lock); + + Vector loaded_plugins = get_plugins(); + + if (ea->plugins.size() != loaded_plugins.size()) { + ea->plugins_changed.set(); + } else { + for (int i = 0; i < ea->plugins.size(); i++) { + if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { + ea->plugins_changed.set(); + break; + } + } + } + } + + 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(300000); + + if (ea->quit_request.is_set()) { + break; + } + } + } + } + +protected: + virtual void get_preset_features(const Ref &p_preset, List *r_features) override; + virtual void get_export_options(List *r_options) override; + +public: + virtual String get_name() const override { return "iOS"; } + virtual String get_os_name() const override { return "iOS"; } + virtual Ref get_logo() const override { return logo; } + + virtual bool should_update_export_options() override { + bool export_options_changed = plugins_changed.is_set(); + if (export_options_changed) { + // don't clear unless we're reporting true, to avoid race + plugins_changed.clear(); + } + return export_options_changed; + } + + virtual List get_binary_extensions(const Ref &p_preset) const override { + List list; + list.push_back("ipa"); + return list; + } + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + + virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; + + virtual void get_platform_features(List *r_features) override { + r_features->push_back("mobile"); + r_features->push_back("ios"); + } + + virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { + } + + EditorExportPlatformIOS(); + ~EditorExportPlatformIOS(); + + /// List the gdip files in the directory specified by the p_path parameter. + static Vector list_plugin_config_files(const String &p_path, bool p_check_directories) { + Vector dir_files; + Ref da = DirAccess::open(p_path); + if (da.is_valid()) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file.is_empty()) { + break; + } + + if (file == "." || file == "..") { + continue; + } + + if (da->current_is_hidden()) { + continue; + } + + if (da->current_is_dir()) { + if (p_check_directories) { + Vector directory_files = list_plugin_config_files(p_path.plus_file(file), false); + for (int i = 0; i < directory_files.size(); ++i) { + dir_files.push_back(file.plus_file(directory_files[i])); + } + } + + continue; + } + + if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector get_plugins() { + Vector loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins"); + + if (DirAccess::exists(plugins_dir)) { + Vector plugins_filenames = list_plugin_config_files(plugins_dir, true); + + if (!plugins_filenames.is_empty()) { + Ref config_file = memnew(ConfigFile); + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfigIOS config = PluginConfigIOS::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 get_enabled_plugins(const Ref &p_presets) { + Vector enabled_plugins; + Vector all_plugins = get_plugins(); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfigIOS plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } +}; + +#endif diff --git a/platform/ios/export/godot_plugin_config.cpp b/platform/ios/export/godot_plugin_config.cpp new file mode 100644 index 0000000000..9118b95337 --- /dev/null +++ b/platform/ios/export/godot_plugin_config.cpp @@ -0,0 +1,285 @@ +/*************************************************************************/ +/* godot_plugin_config.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_config.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" + +String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { + String absolute_path; + + if (dependency_path.is_empty()) { + return absolute_path; + } + + if (dependency_path.is_absolute_path()) { + return dependency_path; + } + + String res_path = ProjectSettings::get_singleton()->globalize_path("res://"); + absolute_path = plugin_config_dir.plus_file(dependency_path); + + return absolute_path.replace(res_path, "res://"); +} + +String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) { + String absolute_path; + + if (dependency_path.is_empty()) { + return absolute_path; + } + + if (dependency_path.is_absolute_path()) { + return dependency_path; + } + + String system_path = "/System/Library/Frameworks"; + + return system_path.plus_file(dependency_path); +} + +Vector PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector p_paths) { + Vector paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]); + + if (path.is_empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +Vector PluginConfigIOS::resolve_system_dependencies(Vector p_paths) { + Vector paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_system_dependency_path(p_paths[i]); + + if (path.is_empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) { + bool valid_name = !plugin_config.name.is_empty(); + bool valid_binary_name = !plugin_config.binary.is_empty(); + bool valid_initialize = !plugin_config.initialization_method.is_empty(); + bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty(); + + bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize; + + if (!fields_value) { + return false; + } + + String plugin_extension = plugin_config.binary.get_extension().to_lower(); + + if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) || + (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) { + plugin_config.valid_config = true; + plugin_config.supports_targets = false; + } else { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String file_extension = plugin_config.binary.get_extension(); + String release_file_name = file_path.plus_file(file_name + ".release." + file_extension); + String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension); + + if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) || + (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) { + plugin_config.valid_config = true; + plugin_config.supports_targets = true; + } + } + + return plugin_config.valid_config; +} + +String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) { + if (!plugin_config.supports_targets) { + return plugin_config.binary; + } + + String plugin_binary_dir = plugin_config.binary.get_base_dir(); + String plugin_name_prefix = plugin_config.binary.get_basename().get_file(); + String plugin_extension = plugin_config.binary.get_extension(); + String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension; + + return plugin_binary_dir.plus_file(plugin_file); +} + +uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) { + uint64_t last_updated = FileAccess::get_modified_time(config_path); + + if (!plugin_config.supports_targets) { + last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); + } else { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String plugin_extension = plugin_config.binary.get_extension(); + String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension); + String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension); + + last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); + last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); + } + + return last_updated; +} + +PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref config_file, const String &path) { + PluginConfigIOS plugin_config = {}; + + if (!config_file.is_valid()) { + return plugin_config; + } + + config_file->clear(); + + Error err = config_file->load(path); + + if (err != OK) { + return plugin_config; + } + + String config_base_dir = path.get_base_dir(); + + plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String()); + plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String()); + plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String()); + + String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String()); + plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); + + if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) { + Vector linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector()); + Vector embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector()); + Vector system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector()); + Vector files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector()); + + plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies); + plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies); + plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies); + + plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files); + + plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector()); + + plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector()); + } + + if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) { + List keys; + config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys); + + for (int i = 0; i < keys.size(); i++) { + Vector key_components = keys[i].split(":"); + + String key_value = ""; + PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN; + + if (key_components.size() == 1) { + key_value = key_components[0]; + key_type = PluginConfigIOS::PlistItemType::STRING; + } else if (key_components.size() == 2) { + key_value = key_components[0]; + + if (key_components[1].to_lower() == "string") { + key_type = PluginConfigIOS::PlistItemType::STRING; + } else if (key_components[1].to_lower() == "integer") { + key_type = PluginConfigIOS::PlistItemType::INTEGER; + } else if (key_components[1].to_lower() == "boolean") { + key_type = PluginConfigIOS::PlistItemType::BOOLEAN; + } else if (key_components[1].to_lower() == "raw") { + key_type = PluginConfigIOS::PlistItemType::RAW; + } else if (key_components[1].to_lower() == "string_input") { + key_type = PluginConfigIOS::PlistItemType::STRING_INPUT; + } + } + + if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) { + continue; + } + + String value; + + switch (key_type) { + case PluginConfigIOS::PlistItemType::STRING: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = "" + raw_value + ""; + } break; + case PluginConfigIOS::PlistItemType::INTEGER: { + int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0); + Dictionary value_dictionary; + String value_format = "$value"; + value_dictionary["value"] = raw_value; + value = value_format.format(value_dictionary, "$_"); + } break; + case PluginConfigIOS::PlistItemType::BOOLEAN: + if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) { + value = ""; + } else { + value = ""; + } + break; + case PluginConfigIOS::PlistItemType::RAW: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + default: + continue; + } + + plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value }; + } + } + + if (validate_plugin(plugin_config)) { + plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); + } + + return plugin_config; +} diff --git a/platform/ios/export/godot_plugin_config.h b/platform/ios/export/godot_plugin_config.h new file mode 100644 index 0000000000..d2a2de4947 --- /dev/null +++ b/platform/ios/export/godot_plugin_config.h @@ -0,0 +1,132 @@ +/*************************************************************************/ +/* godot_plugin_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_GODOT_PLUGIN_CONFIG_H +#define IOS_GODOT_PLUGIN_CONFIG_H + +#include "core/error/error_list.h" +#include "core/io/config_file.h" +#include "core/string/ustring.h" + +/* + The `config` section and fields are required and defined as follow: +- **name**: name of the plugin +- **binary**: path to static `.a` library + +The `dependencies` and fields are optional. +- **linked**: dependencies that should only be linked. +- **embedded**: dependencies that should be linked and embedded into application. +- **system**: system dependencies that should be linked. +- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file. +- **files**: files that would be copied into application + +The `plist` section are optional. +- **key**: key and value that would be added in Info.plist file. + */ + +struct PluginConfigIOS { + inline static const char *PLUGIN_CONFIG_EXT = ".gdip"; + + inline static const char *CONFIG_SECTION = "config"; + inline static const char *CONFIG_NAME_KEY = "name"; + inline static const char *CONFIG_BINARY_KEY = "binary"; + inline static const char *CONFIG_INITIALIZE_KEY = "initialization"; + inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; + + inline static const char *DEPENDENCIES_SECTION = "dependencies"; + inline static const char *DEPENDENCIES_LINKED_KEY = "linked"; + inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; + inline static const char *DEPENDENCIES_SYSTEM_KEY = "system"; + inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; + inline static const char *DEPENDENCIES_FILES_KEY = "files"; + inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags"; + + inline static const char *PLIST_SECTION = "plist"; + + enum PlistItemType { + UNKNOWN, + STRING, + INTEGER, + BOOLEAN, + RAW, + STRING_INPUT, + }; + + struct PlistItem { + PlistItemType type; + String value; + }; + + // Set to true when the config file is properly loaded. + bool valid_config = false; + bool supports_targets = false; + // Unix timestamp of last change to this plugin. + uint64_t last_updated = 0; + + // Required config section + String name; + String binary; + String initialization_method; + String deinitialization_method; + + // Optional dependencies section + Vector linked_dependencies; + Vector embedded_dependencies; + Vector system_dependencies; + + Vector files_to_copy; + Vector capabilities; + + Vector linker_flags; + + // Optional plist section + // String value is default value. + // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types + // : = + HashMap plist; + + static String resolve_local_dependency_path(String plugin_config_dir, String dependency_path); + + static String resolve_system_dependency_path(String dependency_path); + + static Vector resolve_local_dependencies(String plugin_config_dir, Vector p_paths); + + static Vector resolve_system_dependencies(Vector p_paths); + + static bool validate_plugin(PluginConfigIOS &plugin_config); + + static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug); + + static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path); + + static PluginConfigIOS load_plugin_config(Ref config_file, const String &path); +}; + +#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/ios/godot_app_delegate.h b/platform/ios/godot_app_delegate.h new file mode 100644 index 0000000000..703a906bda --- /dev/null +++ b/platform/ios/godot_app_delegate.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* godot_app_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +typedef NSObject ApplicationDelegateService; + +@interface GodotApplicalitionDelegate : NSObject + +@property(class, readonly, strong) NSArray *services; + ++ (void)addService:(ApplicationDelegateService *)service; + +@end diff --git a/platform/ios/godot_app_delegate.m b/platform/ios/godot_app_delegate.m new file mode 100644 index 0000000000..84347f9a30 --- /dev/null +++ b/platform/ios/godot_app_delegate.m @@ -0,0 +1,467 @@ +/*************************************************************************/ +/* godot_app_delegate.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_app_delegate.h" + +#import "app_delegate.h" + +@interface GodotApplicalitionDelegate () + +@end + +@implementation GodotApplicalitionDelegate + +static NSMutableArray *services = nil; + ++ (NSArray *)services { + return services; +} + ++ (void)load { + services = [NSMutableArray new]; + [services addObject:[AppDelegate new]]; +} + ++ (void)addService:(ApplicationDelegateService *)service { + if (!services || !service) { + return; + } + [services addObject:service]; +} + +// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate + +// MARK: Window + +- (UIWindow *)window { + UIWindow *result = nil; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + UIWindow *value = [service window]; + + if (value) { + result = value; + } + } + + return result; +} + +// MARK: Initializing + +- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application willFinishLaunchingWithOptions:launchOptions]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application didFinishLaunchingWithOptions:launchOptions]) { + result = YES; + } + } + + return result; +} + +/* Can be handled by Info.plist. Not yet supported by Godot. + +// MARK: Scene + +- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {} + +- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {} + +*/ + +// MARK: Life-Cycle + +- (void)applicationDidBecomeActive:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidBecomeActive:application]; + } +} + +- (void)applicationWillResignActive:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillResignActive:application]; + } +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidEnterBackground:application]; + } +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillEnterForeground:application]; + } +} + +- (void)applicationWillTerminate:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillTerminate:application]; + } +} + +// MARK: Environment Changes + +- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataDidBecomeAvailable:application]; + } +} + +- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataWillBecomeUnavailable:application]; + } +} + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidReceiveMemoryWarning:application]; + } +} + +- (void)applicationSignificantTimeChange:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationSignificantTimeChange:application]; + } +} + +// MARK: App State Restoration + +- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldSaveSecureApplicationState:coder]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldRestoreSecureApplicationState:coder]) { + result = YES; + } + } + + return result; +} + +- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder]; + + if (controller) { + return controller; + } + } + + return nil; +} + +- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application willEncodeRestorableStateWithCoder:coder]; + } +} + +- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didDecodeRestorableStateWithCoder:coder]; + } +} + +// MARK: Download Data in Background + +- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; + } + + completionHandler(); +} + +// MARK: Remote Notification + +// Moved to the iOS Plugin + +// MARK: User Activity and Handling Quick Actions + +- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application willContinueUserActivityWithType:userActivityType]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) { + result = YES; + } + } + + return result; +} + +- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didUpdateUserActivity:userActivity]; + } +} + +- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didFailToContinueUserActivityWithType:userActivityType error:error]; + } +} + +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; + } +} + +// MARK: WatchKit + +- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application handleWatchKitExtensionRequest:userInfo reply:reply]; + } +} + +// MARK: HealthKit + +- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationShouldRequestHealthAuthorization:application]; + } +} + +// MARK: Opening an URL + +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:app openURL:url options:options]) { + return YES; + } + } + + return NO; +} + +// MARK: Disallowing Specified App Extension Types + +- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) { + result = YES; + } + } + + return result; +} + +// MARK: SiriKit + +- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + id result = [service application:application handlerForIntent:intent]; + + if (result) { + return result; + } + } + + return nil; +} + +// MARK: CloudKit + +- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata]; + } +} + +/* Handled By Info.plist file for now + +// MARK: Interface Geometry + +- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {} + +*/ + +@end diff --git a/platform/ios/godot_ios.mm b/platform/ios/godot_ios.mm new file mode 100644 index 0000000000..5f3e786b8a --- /dev/null +++ b/platform/ios/godot_ios.mm @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* godot_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "core/string/ustring.h" +#include "main/main.h" +#include "os_ios.h" + +#include +#include +#include + +static OS_IOS *os = nullptr; + +int add_path(int, char **); +int add_cmdline(int, char **); +int ios_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"; + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + p_args[p_argc] = nullptr; + + return p_argc; +} + +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; + } + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + } + + p_args[p_argc] = nullptr; + + return p_argc; +} + +int ios_main(int argc, char **argv, String data_dir, String cache_dir) { + size_t len = strlen(argv[0]); + + while (len--) { + if (argv[0][len] == '/') { + break; + } + } + + if (len >= 0) { + char path[512]; + memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len); + path[len] = 0; + printf("Path: %s\n", path); + chdir(path); + } + + printf("godot_ios %s\n", argv[0]); + char cwd[512]; + getcwd(cwd, sizeof(cwd)); + printf("cwd %s\n", cwd); + os = new OS_IOS(data_dir, cache_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] = 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 == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } else if (err != OK) { + return 255; + } + + os->initialize_modules(); + + return 0; +} + +void ios_finish() { + printf("ios_finish\n"); + Main::cleanup(); + delete os; +} diff --git a/platform/ios/godot_view.h b/platform/ios/godot_view.h new file mode 100644 index 0000000000..fcb97fa63a --- /dev/null +++ b/platform/ios/godot_view.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* godot_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +class String; + +@class GodotView; +@protocol DisplayLayer; +@protocol GodotViewRendererProtocol; + +@protocol GodotViewDelegate + +- (BOOL)godotViewFinishedSetup:(GodotView *)view; + +@end + +@interface GodotView : UIView + +@property(assign, nonatomic) id renderer; +@property(assign, nonatomic) id delegate; + +@property(assign, readonly, nonatomic) BOOL isActive; + +@property(assign, nonatomic) BOOL useCADisplayLink; +@property(strong, readonly, nonatomic) CALayer *renderingLayer; +@property(assign, readonly, nonatomic) BOOL canRender; + +@property(assign, nonatomic) NSTimeInterval renderingInterval; + +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; +- (void)stopRendering; +- (void)startRendering; + +- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; + +@end diff --git a/platform/ios/godot_view.mm b/platform/ios/godot_view.mm new file mode 100644 index 0000000000..9ed219508c --- /dev/null +++ b/platform/ios/godot_view.mm @@ -0,0 +1,481 @@ +/*************************************************************************/ +/* godot_view.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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/string/ustring.h" +#import "display_layer.h" +#include "display_server_ios.h" +#import "godot_view_gesture_recognizer.h" +#import "godot_view_renderer.h" + +#import + +static const int max_touches = 8; +static const float earth_gravity = 9.80665; + +@interface GodotView () { + UITouch *godot_touches[max_touches]; +} + +@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 *renderingLayer; + +@property(strong, nonatomic) CMMotionManager *motionManager; + +@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; + +@end + +@implementation GodotView + +- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { + if (self.renderingLayer) { + return self.renderingLayer; + } + + CALayer *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; + self.delegate = 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; + } +} + +- (void)godot_commonInit { + self.contentScaleFactor = [UIScreen mainScreen].nativeScale; + + [self initTouches]; + + self.multipleTouchEnabled = YES; + + // Configure and start accelerometer + if (!self.motionManager) { + self.motionManager = [[CMMotionManager alloc] init]; + 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]; +} + +- (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)]; + + // Approximate frame rate + // assumes device refreshes at 60 fps + int displayFPS = (NSInteger)(1.0 / self.renderingInterval); + + self.displayLink.preferredFramesPerSecond = displayFPS; + + // 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) { + // Continue. + } + + // 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; + } + + if (self.delegate) { + BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self]; + + if (!delegateFinishedSetup) { + 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 (DisplayServerIOS::get_singleton()) { + DisplayServerIOS::get_singleton()->resize_window(self.bounds.size); + } + } + + [super layoutSubviews]; +} + +// MARK: - Input + +// MARK: Touches + +- (void)initTouches { + for (int i = 0; i < max_touches; i++) { + godot_touches[i] = nullptr; + } +} + +- (int)getTouchIDForTouch:(UITouch *)p_touch { + int first = -1; + for (int i = 0; i < max_touches; i++) { + if (first == -1 && godot_touches[i] == nullptr) { + 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] == nullptr) { + continue; + } + if (godot_touches[i] == p_touch) { + godot_touches[i] = nullptr; + } else { + ++remaining; + } + } + return remaining; +} + +- (void)clearTouches { + for (int i = 0; i < max_touches; i++) { + godot_touches[i] = nullptr; + } +} + +- (void)godotTouchesBegan:(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]; + DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); + } + } +} + +- (void)godotTouchesMoved:(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]; + DisplayServerIOS::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); + } + } +} + +- (void)godotTouchesEnded:(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]; + DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); + } + } +} + +- (void)godotTouchesCancelled:(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); + DisplayServerIOS::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; + + // To be consistent with Android we convert the unit of measurement from g (Earth's gravity) + // to m/s^2. + gravity.x *= earth_gravity; + gravity.y *= earth_gravity; + gravity.z *= earth_gravity; + acceleration.x *= earth_gravity; + acceleration.y *= earth_gravity; + acceleration.z *= earth_gravity; + + ///@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: { + DisplayServerIOS::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z); + DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z); + DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z); + DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z); + } break; + case UIInterfaceOrientationLandscapeRight: { + DisplayServerIOS::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z); + DisplayServerIOS::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z); + DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z); + DisplayServerIOS::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z); + } break; + case UIInterfaceOrientationPortraitUpsideDown: { + DisplayServerIOS::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z); + DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z); + DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z); + DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z); + } break; + default: { // assume portrait + DisplayServerIOS::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z); + DisplayServerIOS::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z); + DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z); + DisplayServerIOS::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z); + } break; + } +} + +@end diff --git a/platform/ios/godot_view_gesture_recognizer.h b/platform/ios/godot_view_gesture_recognizer.h new file mode 100644 index 0000000000..9fd8a6b222 --- /dev/null +++ b/platform/ios/godot_view_gesture_recognizer.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* godot_view_gesture_recognizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +// GLViewGestureRecognizer allows iOS gestures to work correctly 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 + +#import + +@interface GodotViewGestureRecognizer : UIGestureRecognizer + +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; + +- (instancetype)init; + +@end diff --git a/platform/ios/godot_view_gesture_recognizer.mm b/platform/ios/godot_view_gesture_recognizer.mm new file mode 100644 index 0000000000..49a92add5e --- /dev/null +++ b/platform/ios/godot_view_gesture_recognizer.mm @@ -0,0 +1,186 @@ +/*************************************************************************/ +/* godot_view_gesture_recognizer.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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" + +#import "godot_view.h" + +#include "core/config/project_settings.h" + +// 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 () + +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@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 + +- (GodotView *)godotView { + return (GodotView *)self.view; +} + +- (instancetype)init { + self = [super init]; + + self.cancelsTouchesInView = YES; + self.delaysTouchesBegan = YES; + self.delaysTouchesEnded = YES; + self.requiresExclusiveTouchType = NO; + + self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); + + 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; + } +} + +- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { + [self.delayTimer fire]; + + self.delayedTouches = touches; + self.delayedEvent = event; + + self.delayTimer = [NSTimer + scheduledTimerWithTimeInterval:self.delayTimeInterval + target:self + selector:@selector(fireDelayedTouches:) + userInfo:nil + repeats:NO]; +} + +- (void)fireDelayedTouches:(id)timer { + [self.delayTimer invalidate]; + self.delayTimer = nil; + + if (self.delayedTouches) { + [self.godotView godotTouchesBegan: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]; + + [super touchesBegan:touches withEvent:event]; +} + +- (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.godotView]; + CGPoint to = [touch previousLocationInView:self.godotView]; + 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.godotView godotTouchesMoved:cleared withEvent:event]; + return; + } + } + + return; + } + + [self.godotView godotTouchesMoved:cleared withEvent:event]; + + [super touchesMoved:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self.delayTimer fire]; + + NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; + [self.godotView godotTouchesEnded:cleared withEvent:event]; + + [super touchesEnded:touches withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [self.delayTimer fire]; + [self.godotView godotTouchesCancelled:touches withEvent:event]; + + [super touchesCancelled:touches withEvent:event]; +} + +- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { + NSMutableSet *cleared = [touches mutableCopy]; + + for (UITouch *touch in touches) { + if (touch.view != self.view || touch.phase != phaseToSave) { + [cleared removeObject:touch]; + } + } + + return cleared; +} + +@end diff --git a/platform/ios/godot_view_renderer.h b/platform/ios/godot_view_renderer.h new file mode 100644 index 0000000000..b3ee23ae4f --- /dev/null +++ b/platform/ios/godot_view_renderer.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* godot_view_renderer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@protocol GodotViewRendererProtocol + +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; + +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; + +@end + +@interface GodotViewRenderer : NSObject + +@end diff --git a/platform/ios/godot_view_renderer.mm b/platform/ios/godot_view_renderer.mm new file mode 100644 index 0000000000..140410fbef --- /dev/null +++ b/platform/ios/godot_view_renderer.mm @@ -0,0 +1,118 @@ +/*************************************************************************/ +/* godot_view_renderer.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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/config/project_settings.h" +#include "core/os/keyboard.h" +#import "display_server_ios.h" +#include "main/main.h" +#include "os_ios.h" +#include "servers/audio_server.h" + +#import +#import +#import +#import +#import + +@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; + OS_IOS::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 (!OS_IOS::get_singleton()) { + return; + } + + OS_IOS::get_singleton()->iterate(); +} + +@end diff --git a/platform/ios/ios.h b/platform/ios/ios.h new file mode 100644 index 0000000000..0607d7b395 --- /dev/null +++ b/platform/ios/ios.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_H +#define IOS_H + +#include "core/object/class_db.h" +#import + +class iOS : public Object { + GDCLASS(iOS, Object); + + static void _bind_methods(); + +private: + CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr; + + CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13)); + void start_haptic_engine(); + void stop_haptic_engine(); + +public: + static void alert(const char *p_alert, const char *p_title); + + bool supports_haptic_engine(); + void vibrate_haptic_engine(float p_duration_seconds); + + String get_model() const; + String get_rate_url(int p_app_id) const; + + iOS(); +}; + +#endif diff --git a/platform/ios/ios.mm b/platform/ios/ios.mm new file mode 100644 index 0000000000..79baae028a --- /dev/null +++ b/platform/ios/ios.mm @@ -0,0 +1,180 @@ +/*************************************************************************/ +/* ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "ios.h" + +#import "app_delegate.h" +#import "view_controller.h" + +#import +#import +#include + +void iOS::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url); + ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine); + ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine); + ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine); +}; + +bool iOS::supports_haptic_engine() { + if (@available(iOS 13, *)) { + id capabilities = [CHHapticEngine capabilitiesForHardware]; + return capabilities.supportsHaptics; + } + + return false; +} + +CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { + if (haptic_engine == nullptr) { + NSError *error = nullptr; + haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error]; + + if (!error) { + [haptic_engine setAutoShutdownEnabled:true]; + } else { + haptic_engine = nullptr; + NSLog(@"Could not initialize haptic engine: %@", error); + } + } + + return haptic_engine; +} + +void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) { + if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy... + if (supports_haptic_engine()) { + CHHapticEngine *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + NSDictionary *hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : @(p_duration_seconds) + }, + }, + ], + }; + + NSError *error; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; + + [[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error]; + + NSLog(@"Could not vibrate using haptic engine: %@", error); + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + +void iOS::start_haptic_engine() { + if (@available(iOS 13, *)) { + if (supports_haptic_engine()) { + CHHapticEngine *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + [haptic_engine startWithCompletionHandler:^(NSError *returnedError) { + if (returnedError) { + NSLog(@"Could not start haptic engine: %@", returnedError); + } + }]; + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + +void iOS::stop_haptic_engine() { + if (@available(iOS 13, *)) { + if (supports_haptic_engine()) { + CHHapticEngine *haptic_engine = get_haptic_engine_instance(); + if (haptic_engine) { + [haptic_engine stopWithCompletionHandler:^(NSError *returnedError) { + if (returnedError) { + NSLog(@"Could not stop haptic engine: %@", returnedError); + } + }]; + } + + return; + } + } + + NSLog(@"Haptic engine is not supported in this version of iOS"); +} + +void iOS::alert(const char *p_alert, const char *p_title) { + 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 { + // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". + size_t size; + sysctlbyname("hw.machine", nullptr, &size, nullptr, 0); + char *model = (char *)malloc(size); + if (model == nullptr) { + return ""; + } + sysctlbyname("hw.machine", model, &size, nullptr, 0); + NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; + free(model); + const char *str = [platform UTF8String]; + return String::utf8(str != nullptr ? str : ""); +} + +String iOS::get_rate_url(int p_app_id) const { + String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; + + String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); + + printf("returning rate url %s\n", ret.utf8().get_data()); + return ret; +} + +iOS::iOS() {} diff --git a/platform/ios/joypad_ios.h b/platform/ios/joypad_ios.h new file mode 100644 index 0000000000..66c4b090bc --- /dev/null +++ b/platform/ios/joypad_ios.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* joypad_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@interface JoypadIOSObserver : NSObject + +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@end + +class JoypadIOS { +private: + JoypadIOSObserver *observer; + +public: + JoypadIOS(); + ~JoypadIOS(); + + void start_processing(); +}; diff --git a/platform/ios/joypad_ios.mm b/platform/ios/joypad_ios.mm new file mode 100644 index 0000000000..e147cb2527 --- /dev/null +++ b/platform/ios/joypad_ios.mm @@ -0,0 +1,344 @@ +/*************************************************************************/ +/* joypad_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ios.h" + +#include "core/config/project_settings.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "main/main.h" + +#import "godot_view.h" + +#include "os_ios.h" + +JoypadIOS::JoypadIOS() { + observer = [[JoypadIOSObserver alloc] init]; + [observer startObserving]; +} + +JoypadIOS::~JoypadIOS() { + if (observer) { + [observer finishObserving]; + observer = nil; + } +} + +void JoypadIOS::start_processing() { + if (observer) { + [observer startProcessing]; + } +} + +@interface JoypadIOSObserver () + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadIOSObserver + +- (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]; +} + +- (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, String::utf8([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... + _weakify(self); + _weakify(controller); + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonB) { + Input::get_singleton()->joy_button(joy_id, JoyButton::B, + gamepad.buttonB.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.buttonY) { + Input::get_singleton()->joy_button(joy_id, JoyButton::Y, + gamepad.buttonY.isPressed); + } else if (element == gamepad.leftShoulder) { + Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER, + gamepad.leftShoulder.isPressed); + } else if (element == gamepad.rightShoulder) { + Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER, + gamepad.rightShoulder.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, + gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, + gamepad.dpad.right.isPressed); + } + + if (element == gamepad.leftThumbstick) { + float value = gamepad.leftThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value); + value = -gamepad.leftThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value); + } else if (element == gamepad.rightThumbstick) { + float value = gamepad.rightThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value); + value = -gamepad.rightThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value); + } else if (element == gamepad.leftTrigger) { + float value = gamepad.leftTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value); + } else if (element == gamepad.rightTrigger) { + float value = gamepad.rightTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value); + } + }; + } else if (controller.microGamepad != nil) { + // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad + _weakify(self); + _weakify(controller); + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed); + } + }; + } + + ///@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/ios/keyboard_input_view.h b/platform/ios/keyboard_input_view.h new file mode 100644 index 0000000000..33fa5d571a --- /dev/null +++ b/platform/ios/keyboard_input_view.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* keyboard_input_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@interface GodotKeyboardInputView : UITextView + +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end; + +@end diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm new file mode 100644 index 0000000000..76e3f23c9d --- /dev/null +++ b/platform/ios/keyboard_input_view.mm @@ -0,0 +1,197 @@ +/*************************************************************************/ +/* keyboard_input_view.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "keyboard_input_view.h" + +#include "core/os/keyboard.h" +#include "display_server_ios.h" +#include "os_ios.h" + +@interface GodotKeyboardInputView () + +@property(nonatomic, copy) NSString *previousText; +@property(nonatomic, assign) NSRange previousSelectedRange; + +@end + +@implementation GodotKeyboardInputView + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { + self = [super initWithFrame:frame textContainer:textContainer]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.hidden = YES; + self.delegate = self; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(observeTextChange:) + name:UITextViewTextDidChangeNotification + object:self]; +} + +- (void)dealloc { + self.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +// MARK: Keyboard + +- (BOOL)canBecomeFirstResponder { + return YES; +} + +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end { + self.text = existingString; + self.previousText = existingString; + + NSInteger safeStartIndex = MAX(start, 0); + + NSRange textRange; + + // Either a simple cursor or a selection. + if (end > 0) { + textRange = NSMakeRange(safeStartIndex, end - start); + } else { + textRange = NSMakeRange(safeStartIndex, 0); + } + + self.selectedRange = textRange; + self.previousSelectedRange = textRange; + + return [self becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { + self.text = nil; + self.previousText = nil; + return [super resignFirstResponder]; +} + +// MARK: OS Messages + +- (void)deleteText:(NSInteger)charactersToDelete { + for (int i = 0; i < charactersToDelete; i++) { + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, true); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, false); + } +} + +- (void)enterText:(NSString *)substring { + String characters; + characters.parse_utf8([substring UTF8String]); + + for (int i = 0; i < characters.size(); i++) { + int character = characters[i]; + + switch (character) { + case 10: + character = (int)Key::ENTER; + break; + case 8198: + character = (int)Key::SPACE; + break; + default: + break; + } + + DisplayServerIOS::get_singleton()->key((Key)character, true); + DisplayServerIOS::get_singleton()->key((Key)character, false); + } +} + +// MARK: Observer + +- (void)observeTextChange:(NSNotification *)notification { + if (notification.object != self) { + return; + } + + if (self.previousSelectedRange.length == 0) { + // We are deleting all text before cursor if no range was selected. + // This way any inserted or changed text will be updated. + NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; + [self deleteText:substringToDelete.length]; + } else { + // If text was previously selected + // we are sending only one `backspace`. + // It will remove all text from text input. + [self deleteText:1]; + } + + NSString *substringToEnter; + + if (self.selectedRange.length == 0) { + // If previous cursor had a selection + // we have to calculate an inserted text. + if (self.previousSelectedRange.length != 0) { + NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length; + NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location); + NSInteger rangeLength = MAX(0, rangeEnd - rangeStart); + + NSRange calculatedRange; + + if (rangeLength >= 0) { + calculatedRange = NSMakeRange(rangeStart, rangeLength); + } else { + calculatedRange = NSMakeRange(rangeStart, 0); + } + + substringToEnter = [self.text substringWithRange:calculatedRange]; + } else { + substringToEnter = [self.text substringToIndex:self.selectedRange.location]; + } + } else { + substringToEnter = [self.text substringWithRange:self.selectedRange]; + } + + [self enterText:substringToEnter]; + + self.previousText = self.text; + self.previousSelectedRange = self.selectedRange; +} + +@end diff --git a/platform/ios/logo.png b/platform/ios/logo.png new file mode 100644 index 0000000000..966d8aa70a Binary files /dev/null and b/platform/ios/logo.png differ diff --git a/platform/ios/main.m b/platform/ios/main.m new file mode 100644 index 0000000000..acfa7ab731 --- /dev/null +++ b/platform/ios/main.m @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* main.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_app_delegate.h" + +#import +#include + +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; + + printf("running app main\n"); + @autoreleasepool { + NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]); + UIApplicationMain(argc, argv, nil, className); + } + printf("main done\n"); + return 0; +} diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h new file mode 100644 index 0000000000..bbc77d48de --- /dev/null +++ b/platform/ios/os_ios.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* os_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_ENABLED + +#ifndef OS_IOS_H +#define OS_IOS_H + +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/unix/os_unix.h" +#include "ios.h" +#include "joypad_ios.h" +#include "servers/audio_server.h" +#include "servers/rendering/renderer_compositor.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/ios/vulkan_context_ios.h" +#endif + +class OS_IOS : public OS_Unix { +private: + static HashMap dynamic_symbol_lookup_table; + friend void register_dynamic_symbol(char *name, void *address); + + AudioDriverCoreAudio audio_driver; + + iOS *ios = nullptr; + + JoypadIOS *joypad_ios = nullptr; + + MainLoop *main_loop = nullptr; + + virtual void initialize_core() override; + virtual void initialize() override; + + virtual void initialize_joypads() override { + } + + virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual MainLoop *get_main_loop() const override; + + virtual void delete_main_loop() override; + + virtual void finalize() override; + + String user_data_dir; + String cache_dir; + + bool is_focused = false; + + void deinitialize_modules(); + +public: + static OS_IOS *get_singleton(); + + OS_IOS(String p_data_dir, String p_cache_dir); + ~OS_IOS(); + + void initialize_modules(); + + bool iterate(); + + void start(); + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) 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 String get_name() const override; + virtual String get_model_name() const override; + + virtual Error shell_open(String p_uri) override; + + void set_user_data_dir(String p_dir); + virtual String get_user_data_dir() const override; + + virtual String get_cache_path() const override; + + virtual String get_locale() const override; + + virtual String get_unique_id() const override; + virtual String get_processor_name() const override; + + virtual void vibrate_handheld(int p_duration_ms = 500) override; + + virtual bool _check_internal_feature_support(const String &p_feature) override; + + void on_focus_out(); + void on_focus_in(); +}; + +#endif // OS_IOS_H + +#endif // IOS_ENABLED diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm new file mode 100644 index 0000000000..880315209e --- /dev/null +++ b/platform/ios/os_ios.mm @@ -0,0 +1,346 @@ +/*************************************************************************/ +/* os_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 IOS_ENABLED + +#include "os_ios.h" + +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/file_access_pack.h" +#include "display_server_ios.h" +#include "drivers/unix/syslog_logger.h" +#import "godot_view.h" +#include "main/main.h" +#import "view_controller.h" + +#import +#import +#import +#include + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#import +#ifdef USE_VOLK +#include +#else +#include +#endif +#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 OS_IOS::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) { + OS_IOS::dynamic_symbol_lookup_table[String(name)] = address; +} + +OS_IOS *OS_IOS::get_singleton() { + return (OS_IOS *)OS::get_singleton(); +} + +OS_IOS::OS_IOS(String p_data_dir, String p_cache_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; + cache_dir = p_cache_dir; + + Vector 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); + + DisplayServerIOS::register_ios_driver(); +} + +OS_IOS::~OS_IOS() {} + +void OS_IOS::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()); +} + +void OS_IOS::initialize_core() { + OS_Unix::initialize_core(); + + set_user_data_dir(user_data_dir); +} + +void OS_IOS::initialize() { + initialize_core(); +} + +void OS_IOS::initialize_modules() { + ios = memnew(iOS); + Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); + + joypad_ios = memnew(JoypadIOS); +} + +void OS_IOS::deinitialize_modules() { + if (joypad_ios) { + memdelete(joypad_ios); + } + + if (ios) { + memdelete(ios); + } +} + +void OS_IOS::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; + + if (main_loop) { + main_loop->initialize(); + } +} + +MainLoop *OS_IOS::get_main_loop() const { + return main_loop; +} + +void OS_IOS::delete_main_loop() { + if (main_loop) { + main_loop->finalize(); + memdelete(main_loop); + } + + main_loop = nullptr; +} + +bool OS_IOS::iterate() { + if (!main_loop) { + return true; + } + + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); + } + + return Main::iteration(); +} + +void OS_IOS::start() { + Main::start(); + + if (joypad_ios) { + joypad_ios->start_processing(); + } +} + +void OS_IOS::finalize() { + deinitialize_modules(); + + // Already gets called + //delete_main_loop(); +} + +// MARK: Dynamic Libraries + +Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { + if (p_path.length() == 0) { + p_library_handle = RTLD_SELF; + + if (r_resolved_path != nullptr) { + *r_resolved_path = p_path; + } + + return OK; + } + return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_path); +} + +Error OS_IOS::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 OS_IOS::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 = OS_IOS::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); +} + +String OS_IOS::get_name() const { + return "iOS"; +} + +String OS_IOS::get_model_name() const { + String model = ios->get_model(); + if (model != "") { + return model; + } + + return OS_Unix::get_model_name(); +} + +Error OS_IOS::shell_open(String p_uri) { + NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; + NSURL *url = [NSURL URLWithString:urlPath]; + + if (![[UIApplication sharedApplication] canOpenURL:url]) { + return ERR_CANT_OPEN; + } + + printf("opening url %s\n", p_uri.utf8().get_data()); + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + + return OK; +} + +void OS_IOS::set_user_data_dir(String p_dir) { + Ref 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()); +} + +String OS_IOS::get_user_data_dir() const { + return user_data_dir; +} + +String OS_IOS::get_cache_path() const { + return cache_dir; +} + +String OS_IOS::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 OS_IOS::get_unique_id() const { + NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; + return String::utf8([uuid UTF8String]); +} + +String OS_IOS::get_processor_name() const { + char buffer[256]; + size_t buffer_len = 256; + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) { + return String::utf8(buffer, buffer_len); + } + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); +} + +void OS_IOS::vibrate_handheld(int p_duration_ms) { + if (ios->supports_haptic_engine()) { + ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f); + } else { + // iOS <13 does not support duration for vibration + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); + } +} + +bool OS_IOS::_check_internal_feature_support(const String &p_feature) { + return p_feature == "mobile"; +} + +void OS_IOS::on_focus_out() { + if (is_focused) { + is_focused = false; + + if (DisplayServerIOS::get_singleton()) { + DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); + } + + [AppDelegate.viewController.godotView stopRendering]; + + audio_driver.stop(); + } +} + +void OS_IOS::on_focus_in() { + if (!is_focused) { + is_focused = true; + + if (DisplayServerIOS::get_singleton()) { + DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); + } + + [AppDelegate.viewController.godotView startRendering]; + + audio_driver.start(); + } +} + +#endif // IOS_ENABLED diff --git a/platform/ios/platform_config.h b/platform/ios/platform_config.h new file mode 100644 index 0000000000..fed77d8932 --- /dev/null +++ b/platform/ios/platform_config.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* platform_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +#define OPENGL_INCLUDE_H + +#define PLATFORM_REFCOUNT + +#define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/ios/tts_ios.h b/platform/ios/tts_ios.h new file mode 100644 index 0000000000..064316b0b2 --- /dev/null +++ b/platform/ios/tts_ios.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* tts_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 TTS_IOS_H +#define TTS_IOS_H + +#if __has_include() +#import +#else +#import +#endif + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/rb_map.h" +#include "core/variant/array.h" +#include "servers/display_server.h" + +@interface TTS_IOS : NSObject { + bool speaking; + HashMap ids; + + AVSpeechSynthesizer *av_synth; + List queue; +} + +- (void)pauseSpeaking; +- (void)resumeSpeaking; +- (void)stopSpeaking; +- (bool)isSpeaking; +- (bool)isPaused; +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; +- (Array)getVoices; +@end + +#endif // TTS_IOS_H diff --git a/platform/ios/tts_ios.mm b/platform/ios/tts_ios.mm new file mode 100644 index 0000000000..a079d02add --- /dev/null +++ b/platform/ios/tts_ios.mm @@ -0,0 +1,164 @@ +/*************************************************************************/ +/* tts_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "tts_ios.h" + +@implementation TTS_IOS + +- (id)init { + self = [super init]; + self->speaking = false; + self->av_synth = [[AVSpeechSynthesizer alloc] init]; + [self->av_synth setDelegate:self]; + print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); + return self; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance { + NSString *string = [utterance speechString]; + + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +- (void)update { + if (!speaking && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; + if (message.rate > 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; + } else if (message.rate < 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; + } + [new_utterance setPitchMultiplier:message.pitch]; + [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + + ids[new_utterance] = message.id; + [av_synth speakUtterance:new_utterance]; + + queue.pop_front(); + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); + speaking = true; + } +} + +- (void)pauseSpeaking { + [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; +} + +- (void)resumeSpeaking { + [av_synth continueSpeaking]; +} + +- (void)stopSpeaking { + for (DisplayServer::TTSUtterance &message : queue) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); + } + queue.clear(); + [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + speaking = false; +} + +- (bool)isSpeaking { + return speaking || (queue.size() > 0); +} + +- (bool)isPaused { + return [av_synth isPaused]; +} + +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { + if (interrupt) { + [self stopSpeaking]; + } + + if (text.is_empty()) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); + return; + } + + DisplayServer::TTSUtterance message; + message.text = text; + message.voice = voice; + message.volume = CLAMP(volume, 0, 100); + message.pitch = CLAMP(pitch, 0.f, 2.f); + message.rate = CLAMP(rate, 0.1f, 10.f); + message.id = utterance_id; + queue.push_back(message); + + if ([self isPaused]) { + [self resumeSpeaking]; + } else { + [self update]; + } +} + +- (Array)getVoices { + Array list; + for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { + NSString *voiceIdentifierString = [voice identifier]; + NSString *voiceLocaleIdentifier = [voice language]; + NSString *voiceName = [voice name]; + Dictionary voice_d; + voice_d["name"] = String::utf8([voiceName UTF8String]); + voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); + voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + return list; +} + +@end diff --git a/platform/ios/view_controller.h b/platform/ios/view_controller.h new file mode 100644 index 0000000000..c8b37a4d11 --- /dev/null +++ b/platform/ios/view_controller.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* view_controller.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +@class GodotView; +@class GodotNativeVideoView; +@class GodotKeyboardInputView; + +@interface ViewController : UIViewController + +@property(nonatomic, readonly, strong) GodotView *godotView; +@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; + +@end diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm new file mode 100644 index 0000000000..43669d3f94 --- /dev/null +++ b/platform/ios/view_controller.mm @@ -0,0 +1,240 @@ +/*************************************************************************/ +/* view_controller.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "view_controller.h" +#include "core/config/project_settings.h" +#include "display_server_ios.h" +#import "godot_view.h" +#import "godot_view_renderer.h" +#import "keyboard_input_view.h" +#include "os_ios.h" + +#import +#import + +@interface ViewController () + +@property(strong, nonatomic) GodotViewRenderer *renderer; +@property(strong, nonatomic) GodotKeyboardInputView *keyboardView; + +@property(strong, nonatomic) UIView *godotLoadingOverlay; + +@end + +@implementation ViewController + +- (GodotView *)godotView { + return (GodotView *)self.view; +} + +- (void)loadView { + GodotView *view = [[GodotView alloc] init]; + GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; + + self.renderer = renderer; + self.view = view; + + view.renderer = self.renderer; + view.delegate = self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + // Initialize view controller values. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + printf("*********** did receive memory warning!\n"); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self observeKeyboard]; + [self displayLoadingOverlay]; + + if (@available(iOS 11.0, *)) { + [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + } +} + +- (void)observeKeyboard { + printf("******** setting up keyboard input view\n"); + self.keyboardView = [GodotKeyboardInputView new]; + [self.view addSubview:self.keyboardView]; + + 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)displayLoadingOverlay { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *storyboardName = @"Launch Screen"; + + if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) { + return; + } + + UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; + + UIViewController *controller = [launchStoryboard instantiateInitialViewController]; + self.godotLoadingOverlay = controller.view; + self.godotLoadingOverlay.frame = self.view.bounds; + self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + [self.view addSubview:self.godotLoadingOverlay]; +} + +- (BOOL)godotViewFinishedSetup:(GodotView *)view { + [self.godotLoadingOverlay removeFromSuperview]; + self.godotLoadingOverlay = nil; + + return YES; +} + +- (void)dealloc { + self.keyboardView = nil; + + self.renderer = nil; + + if (self.godotLoadingOverlay) { + [self.godotLoadingOverlay removeFromSuperview]; + self.godotLoadingOverlay = nil; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +// MARK: Orientation + +- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { + return UIRectEdgeAll; +} + +- (BOOL)shouldAutorotate { + if (!DisplayServerIOS::get_singleton()) { + return NO; + } + + switch (DisplayServerIOS::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 { + if (!DisplayServerIOS::get_singleton()) { + return UIInterfaceOrientationMaskAll; + } + + switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { + case DisplayServer::SCREEN_PORTRAIT: + return UIInterfaceOrientationMaskPortrait; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + return UIInterfaceOrientationMaskLandscapeRight; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + return UIInterfaceOrientationMaskPortraitUpsideDown; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + return UIInterfaceOrientationMaskLandscape; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; + case DisplayServer::SCREEN_SENSOR: + return UIInterfaceOrientationMaskAll; + case DisplayServer::SCREEN_LANDSCAPE: + return UIInterfaceOrientationMaskLandscapeLeft; + } +} + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +- (BOOL)prefersHomeIndicatorAutoHidden { + if (GLOBAL_GET("display/window/ios/hide_home_indicator")) { + return YES; + } else { + return NO; + } +} + +// 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 (DisplayServerIOS::get_singleton()) { + DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); + } +} + +- (void)keyboardHidden:(NSNotification *)notification { + if (DisplayServerIOS::get_singleton()) { + DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(0); + } +} + +@end diff --git a/platform/ios/vulkan_context_ios.h b/platform/ios/vulkan_context_ios.h new file mode 100644 index 0000000000..e9c09e087a --- /dev/null +++ b/platform/ios/vulkan_context_ios.h @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* vulkan_context_ios.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 VULKAN_CONTEXT_IOS_H +#define VULKAN_CONTEXT_IOS_H + +#include "drivers/vulkan/vulkan_context.h" + +#import + +class VulkanContextIOS : public VulkanContext { + virtual const char *_get_platform_surface_extension() const; + +public: + Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height); + + VulkanContextIOS(); + ~VulkanContextIOS(); +}; + +#endif // VULKAN_CONTEXT_IOS_H diff --git a/platform/ios/vulkan_context_ios.mm b/platform/ios/vulkan_context_ios.mm new file mode 100644 index 0000000000..09cd369aa5 --- /dev/null +++ b/platform/ios/vulkan_context_ios.mm @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* vulkan_context_ios.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ios.h" +#ifdef USE_VOLK +#include +#else +#include +#endif + +const char *VulkanContextIOS::_get_platform_surface_extension() const { + return VK_MVK_IOS_SURFACE_EXTENSION_NAME; +} + +Error VulkanContextIOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) { + VkIOSSurfaceCreateInfoMVK createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.pView = (__bridge const void *)p_metal_layer; + + VkSurfaceKHR surface; + VkResult err = + vkCreateIOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + + return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); +} + +VulkanContextIOS::VulkanContextIOS() {} + +VulkanContextIOS::~VulkanContextIOS() {} diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub deleted file mode 100644 index 5e10bf5646..0000000000 --- a/platform/iphone/SCsub +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python - -Import("env") - -iphone_lib = [ - "godot_iphone.mm", - "os_iphone.mm", - "main.m", - "app_delegate.mm", - "view_controller.mm", - "ios.mm", - "vulkan_context_iphone.mm", - "display_server_iphone.mm", - "joypad_iphone.mm", - "godot_view.mm", - "tts_ios.mm", - "display_layer.mm", - "godot_app_delegate.m", - "godot_view_renderer.mm", - "godot_view_gesture_recognizer.mm", - "device_metrics.m", - "keyboard_input_view.mm", -] - -env_ios = env.Clone() -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" - else: - libtool = "$IPHONEPATH/usr/bin/libtool" - 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) diff --git a/platform/iphone/api/api.cpp b/platform/iphone/api/api.cpp deleted file mode 100644 index f2e6fd7a7a..0000000000 --- a/platform/iphone/api/api.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/*************************************************************************/ -/* api.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "api.h" - -#if defined(IPHONE_ENABLED) - -void register_iphone_api() { - godot_ios_plugins_initialize(); -} - -void unregister_iphone_api() { - godot_ios_plugins_deinitialize(); -} - -#else - -void register_iphone_api() {} -void unregister_iphone_api() {} - -#endif diff --git a/platform/iphone/api/api.h b/platform/iphone/api/api.h deleted file mode 100644 index ece91a9f1a..0000000000 --- a/platform/iphone/api/api.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************/ -/* api.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 IPHONE_API_H -#define IPHONE_API_H - -#if defined(IPHONE_ENABLED) -extern void godot_ios_plugins_initialize(); -extern void godot_ios_plugins_deinitialize(); -#endif - -void register_iphone_api(); -void unregister_iphone_api(); - -#endif // IPHONE_API_H diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h deleted file mode 100644 index 0ec1dc071b..0000000000 --- a/platform/iphone/app_delegate.h +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************/ -/* app_delegate.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@class ViewController; - -// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again, -// so it can't be done with compilation time branching. -//#if defined(GLES3_ENABLED) -//@interface AppDelegate : NSObject { -//#endif -//#if defined(VULKAN_ENABLED) -@interface AppDelegate : NSObject -//#endif - -@property(strong, nonatomic) UIWindow *window; -@property(strong, class, readonly, nonatomic) ViewController *viewController; - -@end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm deleted file mode 100644 index c5c9b5a5f9..0000000000 --- a/platform/iphone/app_delegate.mm +++ /dev/null @@ -1,149 +0,0 @@ -/*************************************************************************/ -/* app_delegate.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "app_delegate.h" - -#include "core/config/project_settings.h" -#include "drivers/coreaudio/audio_driver_coreaudio.h" -#import "godot_view.h" -#include "main/main.h" -#include "os_iphone.h" -#import "view_controller.h" - -#import -#import - -#define kRenderingFrequency 60 - -extern int gargc; -extern char **gargv; - -extern int iphone_main(int, char **, String, String); -extern void iphone_finish(); - -@implementation AppDelegate - -static ViewController *mainViewController = nil; - -+ (ViewController *)viewController { - return mainViewController; -} - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // 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 - - CGRect windowBounds = [[UIScreen mainScreen] bounds]; - - // Create a full-screen window - self.window = [[UIWindow alloc] initWithFrame:windowBounds]; - - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *cacheDirectory = [paths objectAtIndex:0]; - - int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory 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 NO; - } - - ViewController *viewController = [[ViewController alloc] init]; - viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; - viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; - - self.window.rootViewController = viewController; - - // Show the window - [self.window makeKeyAndVisible]; - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onAudioInterruption:) - name:AVAudioSessionInterruptionNotification - object:[AVAudioSession sharedInstance]]; - - mainViewController = viewController; - - // prevent to stop music in another background app - [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; - - return YES; -} - -- (void)onAudioInterruption:(NSNotification *)notification { - if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { - if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) { - NSLog(@"Audio interruption began"); - OSIPhone::get_singleton()->on_focus_out(); - } else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) { - NSLog(@"Audio interruption ended"); - OSIPhone::get_singleton()->on_focus_in(); - } - } -} - -- (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(); -} - -// When application goes to background (e.g. user switches to another app or presses Home), -// then applicationWillResignActive -> applicationDidEnterBackground are called. -// When user opens the inactive app again, -// applicationWillEnterForeground -> applicationDidBecomeActive are called. - -// There are cases when applicationWillResignActive -> applicationDidBecomeActive -// sequence is called without the app going to background. For example, that happens -// if you open the app list without switching to another app or open/close the -// notification panel by swiping from the upper part of the screen. - -- (void)applicationWillResignActive:(UIApplication *)application { - OSIPhone::get_singleton()->on_focus_out(); -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - OSIPhone::get_singleton()->on_focus_in(); -} - -- (void)dealloc { - self.window = nil; -} - -@end diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py deleted file mode 100644 index 862f1fe50b..0000000000 --- a/platform/iphone/detect.py +++ /dev/null @@ -1,152 +0,0 @@ -import os -import sys -from methods import detect_darwin_sdk_path - - -def is_active(): - return True - - -def get_name(): - return "iOS" - - -def can_build(): - if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ): - return True - - return False - - -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("ios_simulator", "Build for iOS Simulator", False), - BoolVariable("ios_exceptions", "Enable exceptions", False), - ("ios_triple", "Triple for ios toolchain", ""), - ] - - -def get_flags(): - return [ - ("tools", False), - ("use_volk", False), - ] - - -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) - # `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces - # when using `target=release_debug`. - opt = "-O3" if env["target"] == "release" else "-O2" - env.Append(CCFLAGS=[opt, "-ftree-vectorize", "-fomit-frame-pointer"]) - env.Append(LINKFLAGS=[opt]) - elif env["optimize"] == "size": # optimize for size - env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) - env.Append(LINKFLAGS=["-Os"]) - - elif env["target"] == "debug": - env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) - env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1)]) - - if env["use_lto"]: - env.Append(CCFLAGS=["-flto"]) - env.Append(LINKFLAGS=["-flto"]) - - ## Architecture - env["bits"] = "64" - if env["arch"] != "x86_64": - env["arch"] = "arm64" - - ## Compiler configuration - - # Save this in environment for use by other modules - if "OSXCROSS_IOS" in os.environ: - env["osxcross"] = True - - 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/" - - 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" - 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" - - ## Compile flags - - if env["ios_simulator"]: - detect_darwin_sdk_path("iphonesimulator", env) - env.Append(ASFLAGS=["-mios-simulator-version-min=13.0"]) - env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"]) - env.extra_suffix = ".simulator" + env.extra_suffix - else: - detect_darwin_sdk_path("iphone", env) - env.Append(ASFLAGS=["-miphoneos-version-min=11.0"]) - env.Append(CCFLAGS=["-miphoneos-version-min=11.0"]) - - if env["arch"] == "x86_64": - env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" - env.Append( - CCFLAGS=( - "-fobjc-arc -arch x86_64" - " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" - " -fasm-blocks -isysroot $IPHONESDK" - ).split() - ) - env.Append(ASFLAGS=["-arch", "x86_64"]) - elif env["arch"] == "arm64": - env.Append( - CCFLAGS=( - "-fobjc-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" - " -isysroot $IPHONESDK".split() - ) - ) - env.Append(ASFLAGS=["-arch", "arm64"]) - env.Append(CPPDEFINES=["NEED_LONG_INT"]) - - # Disable exceptions on non-tools (template) builds - if not env["tools"]: - if env["ios_exceptions"]: - env.Append(CCFLAGS=["-fexceptions"]) - else: - env.Append(CCFLAGS=["-fno-exceptions"]) - - # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation - env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) - - env.Prepend( - CPPPATH=[ - "$IPHONESDK/usr/include", - "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers", - ] - ) - - env.Prepend(CPPPATH=["#platform/iphone"]) - env.Append(CPPDEFINES=["IPHONE_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) - - if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h deleted file mode 100644 index b9fb9b2fd9..0000000000 --- a/platform/iphone/device_metrics.h +++ /dev/null @@ -1,37 +0,0 @@ -/*************************************************************************/ -/* device_metrics.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@interface GodotDeviceMetrics : NSObject - -@property(nonatomic, class, readonly, strong) NSDictionary *dpiList; - -@end diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m deleted file mode 100644 index ec4dd8130d..0000000000 --- a/platform/iphone/device_metrics.m +++ /dev/null @@ -1,152 +0,0 @@ -/*************************************************************************/ -/* device_metrics.m */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "device_metrics.h" - -@implementation GodotDeviceMetrics - -+ (NSDictionary *)dpiList { - return @{ - @[ - @"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, - }; -} - -@end diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h deleted file mode 100644 index a17c75dba1..0000000000 --- a/platform/iphone/display_layer.h +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* display_layer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 -#import - -@protocol DisplayLayer - -- (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 -#else -@interface GodotMetalLayer : CALayer -#endif -#else -@interface GodotMetalLayer : CAMetalLayer -#endif -@end - -API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) -@interface GodotOpenGLLayer : CAEAGLLayer - -@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm deleted file mode 100644 index 92e81448ac..0000000000 --- a/platform/iphone/display_layer.mm +++ /dev/null @@ -1,173 +0,0 @@ -/*************************************************************************/ -/* display_layer.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/config/project_settings.h" -#include "core/os/keyboard.h" -#include "display_server_iphone.h" -#include "main/main.h" -#include "os_iphone.h" -#include "servers/audio_server.h" - -#import -#import -#import -#import -#import -#import -#import - -@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/driver/driver_name") == "opengl3") { - 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 = nil; - } -} - -- (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)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; - } - - 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 deleted file mode 100644 index 7af222e3f8..0000000000 --- a/platform/iphone/display_server_iphone.h +++ /dev/null @@ -1,217 +0,0 @@ -/*************************************************************************/ -/* display_server_iphone.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/renderer_rd/renderer_compositor_rd.h" - -#include "vulkan_context_iphone.h" - -#import -#ifdef USE_VOLK -#include -#else -#include -#endif -#endif - -class DisplayServerIPhone : public DisplayServer { - GDCLASS(DisplayServerIPhone, DisplayServer) - - _THREAD_SAFE_CLASS_ - -#if defined(VULKAN_ENABLED) - VulkanContextIPhone *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - - id tts = nullptr; - - 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 &p_event); - - DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_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, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); - static Vector 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 &p_event); - void send_input_event(const Ref &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_double_click); - 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(Key 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 bool tts_is_speaking() const override; - virtual bool tts_is_paused() const override; - virtual Array tts_get_voices() const override; - - virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; - virtual void tts_pause() override; - virtual void tts_resume() override; - virtual void tts_stop() override; - - virtual Rect2i get_display_safe_area() 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 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 float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - - virtual Vector get_window_list() const override; - - virtual WindowID - get_window_at_screen_position(const Point2i &p_position) const override; - - virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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 void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) 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; - - 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 deleted file mode 100644 index 28ffc9595e..0000000000 --- a/platform/iphone/display_server_iphone.mm +++ /dev/null @@ -1,655 +0,0 @@ -/*************************************************************************/ -/* display_server_iphone.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/config/project_settings.h" -#include "core/io/file_access_pack.h" -#import "device_metrics.h" -#import "godot_view.h" -#include "ios.h" -#import "keyboard_input_view.h" -#include "os_iphone.h" -#include "tts_ios.h" -#import "view_controller.h" - -#import -#import - -static const float kDisplayServerIPhoneAcceleration = 1; - -DisplayServerIPhone *DisplayServerIPhone::get_singleton() { - return (DisplayServerIPhone *)DisplayServer::get_singleton(); -} - -DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - rendering_driver = p_rendering_driver; - - // Init TTS - tts = [[TTS_IOS alloc] init]; - -#if defined(GLES3_ENABLED) - // FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented - // again, - // Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS - // We are hardcoding the rendering_driver to "vulkan" down below - - if (rendering_driver == "opengl_es") { - bool gl_initialization_error = false; - - // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - - if (RasterizerGLES3::is_viable() == OK) { - RasterizerGLES3::register_config(); - RasterizerGLES3::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(RenderingServerDefault); - // // 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 - // RasterizerStorageGLES3system_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, p_vsync_mode, 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); - - RendererCompositorRD::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_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; - } - - if (context_vulkan) { - context_vulkan->window_destroy(MAIN_WINDOW_ID); - memdelete(context_vulkan); - context_vulkan = nullptr; - } -#endif -} - -DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); -} - -Vector DisplayServerIPhone::get_rendering_drivers_func() { - Vector drivers; - -#if defined(VULKAN_ENABLED) - drivers.push_back("vulkan"); -#endif -#if defined(GLES3_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() { - Input::get_singleton()->flush_buffered_events(); -} - -void DisplayServerIPhone::_dispatch_input_events(const Ref &p_event) { - DisplayServerIPhone::get_singleton()->send_input_event(p_event); -} - -void DisplayServerIPhone::send_input_event(const Ref &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_double_click) { - if (!GLOBAL_DEF("debug/disable_touch", false)) { - Ref ev; - ev.instantiate(); - - 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 ev; - ev.instantiate(); - 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 &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(Key p_key, bool p_pressed) { - Ref ev; - ev.instantiate(); - ev->set_echo(false); - ev->set_pressed(p_pressed); - ev->set_keycode(p_key); - ev->set_physical_keycode(p_key); - ev->set_unicode((char32_t)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); -} - -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_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_WINDOW_TRANSPARENCY: - case FEATURE_CLIPBOARD: - case FEATURE_KEEP_SCREEN_ON: - case FEATURE_ORIENTATION: - case FEATURE_TOUCHSCREEN: - case FEATURE_VIRTUAL_KEYBOARD: - case FEATURE_TEXT_TO_SPEECH: - return true; - default: - return false; - } -} - -String DisplayServerIPhone::get_name() const { - return "iPhone"; -} - -bool DisplayServerIPhone::tts_is_speaking() const { - ERR_FAIL_COND_V(!tts, false); - return [tts isSpeaking]; -} - -bool DisplayServerIPhone::tts_is_paused() const { - ERR_FAIL_COND_V(!tts, false); - return [tts isPaused]; -} - -Array DisplayServerIPhone::tts_get_voices() const { - ERR_FAIL_COND_V(!tts, Array()); - return [tts getVoices]; -} - -void DisplayServerIPhone::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - ERR_FAIL_COND(!tts); - [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; -} - -void DisplayServerIPhone::tts_pause() { - ERR_FAIL_COND(!tts); - [tts pauseSpeaking]; -} - -void DisplayServerIPhone::tts_resume() { - ERR_FAIL_COND(!tts); - [tts resumeSpeaking]; -} - -void DisplayServerIPhone::tts_stop() { - ERR_FAIL_COND(!tts); - [tts stopSpeaking]; -} - -Rect2i DisplayServerIPhone::get_display_safe_area() 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(); - 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() + insets_position, screen_get_size() - insets_size); - } else { - return Rect2i(screen_get_position(), screen_get_size()); - } -} - -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 { - 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]; - - NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; - - for (NSArray *keyArray in iOSModelToDPI) { - if ([keyArray containsObject:string]) { - NSNumber *value = iOSModelToDPI[keyArray]; - return [value intValue]; - } - } - - // If device wasn't found in dictionary - // make a best guess from device metrics. - CGFloat scale = [UIScreen mainScreen].scale; - - UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; - - switch (idiom) { - case UIUserInterfaceIdiomPad: - return scale == 2 ? 264 : 132; - case UIUserInterfaceIdiomPhone: { - if (scale == 3) { - CGFloat nativeScale = [UIScreen mainScreen].nativeScale; - return nativeScale == 3 ? 458 : 401; - } - - return 326; - } - default: - return 72; - } -} - -float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const { - return [UIScreen mainScreen].maximumFramesPerSecond; -} - -float DisplayServerIPhone::screen_get_scale(int p_screen) const { - return [UIScreen mainScreen].nativeScale; -} - -Vector DisplayServerIPhone::get_window_list() const { - Vector 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; -} - -int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { - ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); - switch (p_handle_type) { - case DISPLAY_HANDLE: { - return 0; // Not supported. - } - case WINDOW_HANDLE: { - return (int64_t)AppDelegate.viewController; - } - case WINDOW_VIEW: { - return (int64_t)AppDelegate.viewController.godotView; - } - default: { - return 0; - } - } -} - -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) { - NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()]; - - [AppDelegate.viewController.keyboardView - becomeFirstResponderWithString:existingString - multiline:p_multiline - cursorStart:p_cursor_start - cursorEnd:p_cursor_end]; -} - -void DisplayServerIPhone::virtual_keyboard_hide() { - [AppDelegate.viewController.keyboardView 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; -} - -void DisplayServerIPhone::resize_window(CGSize viewSize) { - Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); - -#if defined(VULKAN_ENABLED) - 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); -} - -void DisplayServerIPhone::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); -#endif -} - -DisplayServer::VSyncMode DisplayServerIPhone::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - return context_vulkan->get_vsync_mode(p_window); -#else - return DisplayServer::VSYNC_ENABLED; -#endif -} diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp deleted file mode 100644 index 3b02e661c1..0000000000 --- a/platform/iphone/export/export.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/*************************************************************************/ -/* export.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "export.h" - -#include "export_plugin.h" - -void register_iphone_exporter() { - Ref platform; - platform.instantiate(); - - EditorExport::get_singleton()->add_export_platform(platform); -} diff --git a/platform/iphone/export/export.h b/platform/iphone/export/export.h deleted file mode 100644 index adb3c23957..0000000000 --- a/platform/iphone/export/export.h +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************/ -/* export.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 IPHONE_EXPORT_H -#define IPHONE_EXPORT_H - -void register_iphone_exporter(); - -#endif // IPHONE_EXPORT_H diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp deleted file mode 100644 index 3b92bd19be..0000000000 --- a/platform/iphone/export/export_plugin.cpp +++ /dev/null @@ -1,1849 +0,0 @@ -/*************************************************************************/ -/* export_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "export_plugin.h" - -#include "editor/editor_node.h" - -void EditorExportPlatformIOS::get_preset_features(const Ref &p_preset, List *r_features) { - String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); - // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. - r_features->push_back("etc2"); - - Vector architectures = _get_preset_architectures(p_preset); - for (int i = 0; i < architectures.size(); ++i) { - r_features->push_back(architectures[i]); - } -} - -Vector EditorExportPlatformIOS::_get_supported_architectures() { - Vector archs; - archs.push_back(ExportArchitecture("arm64", true)); - return archs; -} - -struct LoadingScreenInfo { - const char *preset_key; - const char *export_name; - int width = 0; - int height = 0; - bool rotate = false; -}; - -static const LoadingScreenInfo loading_screen_infos[] = { - { PNAME("landscape_launch_screens/iphone_2436x1125"), "Default-Landscape-X.png", 2436, 1125, false }, - { PNAME("landscape_launch_screens/iphone_2208x1242"), "Default-Landscape-736h@3x.png", 2208, 1242, false }, - { PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false }, - { PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false }, - - { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, true }, - { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, true }, - { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, true }, - { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, true }, - { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, true }, - { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, true }, - { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, true } -}; - -void EditorExportPlatformIOS::get_export_options(List *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"), "")); - - Vector architectures = _get_supported_architectures(); - for (int i = 0; i < architectures.size(); ++i) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default)); - } - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); - - 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")); - - Vector found_plugins = get_plugins(); - for (int i = 0; i < found_plugins.size(); i++) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); - } - - HashSet plist_keys; - - for (int i = 0; i < found_plugins.size(); i++) { - // Editable plugin plist values - PluginConfigIOS plugin = found_plugins[i]; - - for (const KeyValue &E : plugin.plist) { - switch (E.value.type) { - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String preset_name = "plugins_plist/" + E.key; - if (!plist_keys.has(preset_name)) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value)); - plist_keys.insert(preset_name); - } - } break; - default: - continue; - } - } - } - - plugins_changed.clear(); - plugins = found_plugins; - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "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())); - - 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"), "")); - } -} - -void EditorExportPlatformIOS::_fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug) { - static const String export_method_string[] = { - "app-store", - "development", - "ad-hoc", - "enterprise" - }; - static const String storyboard_image_scale_mode[] = { - "center", - "scaleAspectFit", - "scaleAspectFill", - "scaleToFill" - }; - String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); - String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); - bool dbg_manual = !p_preset->get("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); - bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); - String str; - String strnew; - str.parse_utf8((const char *)pfile.ptr(), pfile.size()); - Vector lines = str.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].find("$binary") != -1) { - strnew += lines[i].replace("$binary", p_config.binary_name) + "\n"; - } else if (lines[i].find("$modules_buildfile") != -1) { - strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n"; - } else if (lines[i].find("$modules_fileref") != -1) { - strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n"; - } else if (lines[i].find("$modules_buildphase") != -1) { - strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n"; - } else if (lines[i].find("$modules_buildgrp") != -1) { - strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; - } else if (lines[i].find("$name") != -1) { - strnew += lines[i].replace("$name", p_config.pkg_name) + "\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) { - strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; - } else if (lines[i].find("$signature") != -1) { - strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; - } else if (lines[i].find("$team_id") != -1) { - strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; - } else if (lines[i].find("$default_build_config") != -1) { - strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n"; - } else if (lines[i].find("$export_method") != -1) { - int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); - strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; - } else if (lines[i].find("$provisioning_profile_uuid_release") != -1) { - strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n"; - } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) { - strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n"; - } else if (lines[i].find("$code_sign_style_debug") != -1) { - if (dbg_manual) { - strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; - } else { - strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n"; - } - } else if (lines[i].find("$code_sign_style_release") != -1) { - if (rel_manual) { - strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n"; - } else { - strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; - } - } else if (lines[i].find("$provisioning_profile_uuid") != -1) { - String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release"); - strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; - } else if (lines[i].find("$code_sign_identity_debug") != -1) { - strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; - } else if (lines[i].find("$code_sign_identity_release") != -1) { - strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; - } else if (lines[i].find("$additional_plist_content") != -1) { - strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; - } else if (lines[i].find("$godot_archs") != -1) { - strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n"; - } else if (lines[i].find("$linker_flags") != -1) { - strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; - } else if (lines[i].find("$targeted_device_family") != -1) { - String xcode_value; - switch ((int)p_preset->get("application/targeted_device_family")) { - case 0: // iPhone - xcode_value = "1"; - break; - case 1: // iPad - xcode_value = "2"; - break; - case 2: // iPhone & iPad - xcode_value = "1,2"; - break; - } - strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n"; - } else if (lines[i].find("$cpp_code") != -1) { - strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; - } else if (lines[i].find("$docs_in_place") != -1) { - strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "" : "") + "\n"; - } else if (lines[i].find("$docs_sharing") != -1) { - strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "" : "") + "\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 ? "aps-environmentdevelopment" : "") + "\n"; - } else if (lines[i].find("$required_device_capabilities") != -1) { - String capabilities; - - // I've removed armv7 as we can run on 64bit only devices - // Note that capabilities listed here are requirements for the app to be installed. - // They don't enable anything. - Vector capabilities_list = p_config.capabilities; - - if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { - capabilities_list.push_back("wifi"); - } - - for (int idx = 0; idx < capabilities_list.size(); idx++) { - capabilities += "" + capabilities_list[idx] + "\n"; - } - - strnew += lines[i].replace("$required_device_capabilities", capabilities); - } else if (lines[i].find("$interface_orientations") != -1) { - String orientations; - const DisplayServer::ScreenOrientation screen_orientation = - DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))); - - switch (screen_orientation) { - case DisplayServer::SCREEN_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - break; - case DisplayServer::SCREEN_PORTRAIT: - orientations += "UIInterfaceOrientationPortrait\n"; - break; - case DisplayServer::SCREEN_REVERSE_LANDSCAPE: - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_REVERSE_PORTRAIT: - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR_LANDSCAPE: - // Allow both landscape orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - break; - case DisplayServer::SCREEN_SENSOR_PORTRAIT: - // Allow both portrait orientations depending on sensor direction. - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - case DisplayServer::SCREEN_SENSOR: - // Allow all screen orientations depending on sensor direction. - orientations += "UIInterfaceOrientationLandscapeLeft\n"; - orientations += "UIInterfaceOrientationLandscapeRight\n"; - orientations += "UIInterfaceOrientationPortrait\n"; - orientations += "UIInterfaceOrientationPortraitUpsideDown\n"; - break; - } - - strnew += lines[i].replace("$interface_orientations", orientations); - } else if (lines[i].find("$camera_usage_description") != -1) { - String description = p_preset->get("privacy/camera_usage_description"); - strnew += lines[i].replace("$camera_usage_description", description) + "\n"; - } else if (lines[i].find("$microphone_usage_description") != -1) { - String description = p_preset->get("privacy/microphone_usage_description"); - strnew += lines[i].replace("$microphone_usage_description", description) + "\n"; - } 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 ? "UILaunchStoryboardName\nLaunch Screen" : ""; - 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 = \"\"; };" : ""; - 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 if (lines[i].find("$pbx_locale_file_reference") != -1) { - String locale_files; - Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); - if (translations.size() > 0) { - int index = 0; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid()) { - String lang = tr->get_locale(); - locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"\"; };"; - } - index++; - } - } - strnew += lines[i].replace("$pbx_locale_file_reference", locale_files); - } else if (lines[i].find("$pbx_locale_build_reference") != -1) { - String locale_files; - Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); - if (translations.size() > 0) { - int index = 0; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid()) { - String lang = tr->get_locale(); - locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,"; - } - index++; - } - } - strnew += lines[i].replace("$pbx_locale_build_reference", locale_files); - } else { - strnew += lines[i] + "\n"; - } - } - - // !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero... - // should apply the same fix in our OSX export. - CharString cs = strnew.utf8(); - pfile.resize(cs.size() - 1); - for (int i = 0; i < cs.size() - 1; i++) { - pfile.write[i] = cs[i]; - } -} - -String EditorExportPlatformIOS::_get_additional_plist_content() { - Vector> 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(); - } - return result; -} - -String EditorExportPlatformIOS::_get_linker_flags() { - Vector> 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 (result.length() > 0) { - result += ' '; - } - result += flags; - } - // the flags will be enclosed in quotes, so need to escape them - return result.replace("\"", "\\\""); -} - -String EditorExportPlatformIOS::_get_cpp_code() { - Vector> 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(); - } - return result; -} - -void EditorExportPlatformIOS::_blend_and_rotate(Ref &p_dst, Ref &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; - const char *export_name; - const char *actual_size_side; - const char *scale; - const char *unscaled_size; -}; - -static const IconInfo icon_infos[] = { - // Home screen on iPhone - { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" }, - { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" }, - { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" }, - - // Home screen on iPad - { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" }, - { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" }, - { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" }, - - // App Store - { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" }, - - // Spotlight - { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" }, - { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" }, - { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" } -}; - -Error EditorExportPlatformIOS::_export_icons(const Ref &p_preset, const String &p_iconset_dir) { - String json_description = "{\"images\":["; - String sizes; - - Ref da = DirAccess::open(p_iconset_dir); - ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); - - 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) { - // Resize main app icon - icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); - Ref 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 { - // Load custom icon and resize if required - Ref 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; - } - if (img->get_width() != side_size || img->get_height() != side_size) { - WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + "."); - img->resize(side_size, side_size); - err = img->save_png(p_iconset_dir + info.export_name); - } else { - err = da->copy(icon_path, 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; - } - } - sizes += String(info.actual_size_side) + "\n"; - if (i > 0) { - json_description += ","; - } - json_description += String("{"); - json_description += String("\"idiom\":") + "\"" + info.idiom + "\","; - json_description += String("\"size\":") + "\"" + info.unscaled_size + "\","; - json_description += String("\"scale\":") + "\"" + info.scale + "\","; - json_description += String("\"filename\":") + "\"" + info.export_name + "\""; - json_description += String("}"); - } - json_description += "]}"; - - Ref json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); - ERR_FAIL_COND_V(json_file.is_null(), ERR_CANT_CREATE); - CharString json_utf8 = json_description.utf8(); - json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length()); - - Ref sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE); - ERR_FAIL_COND_V(sizes_file.is_null(), ERR_CANT_CREATE); - CharString sizes_utf8 = sizes.utf8(); - sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length()); - - return OK; -} - -Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref &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; - String image_path = p_dest_dir.plus_file("splash@2x.png"); - image.instantiate(); - 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.instantiate(); - 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 splash; - - const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); - - if (!splash_path.is_empty()) { - splash.instantiate(); - const Error err = splash->load(splash_path); - if (err) { - splash.unref(); - } - } - - if (splash.is_null()) { - splash = Ref(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 &p_preset, const String &p_dest_dir) { - Ref da = DirAccess::open(p_dest_dir); - ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); - - for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { - LoadingScreenInfo info = loading_screen_infos[i]; - String loading_screen_file = p_preset->get(info.preset_key); - - 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"); - - if (loading_screen_file.size() > 0) { - // Load custom loading screens, and resize if required. - Ref 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) { - WARN_PRINT("Loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(info.width) + "x" + String::num_int64(info.height) + "."); - float aspect_ratio = (float)img->get_width() / (float)img->get_height(); - if (boot_logo_scale) { - if (info.height * aspect_ratio <= info.width) { - img->resize(info.height * aspect_ratio, info.height); - } else { - img->resize(info.width, info.width / aspect_ratio); - } - } - Ref new_img = memnew(Image); - new_img->create(info.width, info.height, false, Image::FORMAT_RGBA8); - new_img->fill(boot_bg_color); - _blend_and_rotate(new_img, img, false); - err = new_img->save_png(p_dest_dir + info.export_name); - } else { - err = da->copy(loading_screen_file, p_dest_dir + info.export_name); - } - if (err) { - 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 { - // Generate loading screen from the splash screen - Ref img = memnew(Image); - img->create(info.width, info.height, false, Image::FORMAT_RGBA8); - img->fill(boot_bg_color); - - Ref img_bs; - - if (boot_logo_path.length() > 0) { - img_bs = Ref(memnew(Image)); - ImageLoader::load_image(boot_logo_path, img_bs); - } - if (!img_bs.is_valid()) { - img_bs = Ref(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()); - } - } - } - - return OK; -} - -Error EditorExportPlatformIOS::_walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata) { - Vector dirs; - String current_dir = p_da->get_current_dir(); - p_da->list_dir_begin(); - String path = p_da->get_next(); - while (!path.is_empty()) { - if (p_da->current_is_dir()) { - if (path != "." && path != "..") { - dirs.push_back(path); - } - } else { - Error err = p_handler(current_dir.plus_file(path), p_userdata); - if (err) { - p_da->list_dir_end(); - return err; - } - } - path = p_da->get_next(); - } - p_da->list_dir_end(); - - for (int i = 0; i < dirs.size(); ++i) { - String dir = dirs[i]; - p_da->change_dir(dir); - Error err = _walk_dir_recursive(p_da, p_handler, p_userdata); - p_da->change_dir(".."); - if (err) { - return err; - } - } - - return OK; -} - -struct CodesignData { - const Ref &preset; - bool debug = false; - - CodesignData(const Ref &p_preset, bool p_debug) : - preset(p_preset), - debug(p_debug) { - } -}; - -Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { - if (p_file.ends_with(".dylib")) { - CodesignData *data = static_cast(p_userdata); - print_line(String("Signing ") + p_file); - - String sign_id; - if (data->debug) { - sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : data->preset->get("application/code_sign_identity_debug"); - } else { - sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : data->preset->get("application/code_sign_identity_release"); - } - - List codesign_args; - codesign_args.push_back("-f"); - codesign_args.push_back("-s"); - codesign_args.push_back(sign_id); - codesign_args.push_back(p_file); - String str; - Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true); - print_verbose("codesign (" + p_file + "):\n" + str); - - return err; - } - return OK; -} - -struct PbxId { -private: - static char _hex_char(uint8_t four_bits) { - if (four_bits < 10) { - return ('0' + four_bits); - } - return 'A' + (four_bits - 10); - } - - static String _hex_pad(uint32_t num) { - Vector ret; - ret.resize(sizeof(num) * 2); - for (uint64_t i = 0; i < sizeof(num) * 2; ++i) { - uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF; - ret.write[i] = _hex_char(four_bits); - } - return String::utf8(ret.ptr(), ret.size()); - } - -public: - uint32_t high_bits; - uint32_t mid_bits; - uint32_t low_bits; - - String str() const { - return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits); - } - - PbxId &operator++() { - low_bits++; - if (!low_bits) { - mid_bits++; - if (!mid_bits) { - high_bits++; - } - } - - return *this; - } -}; - -struct ExportLibsData { - Vector lib_paths; - String dest_dir; -}; - -void EditorExportPlatformIOS::_add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets) { - // 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 }; - String pbx_files; - String pbx_frameworks_build; - 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 = \"\"; };\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")) { - type = "archive.ar"; - } else { - type = "file"; - } - - String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build; - String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs; - - if (pbx_build.length() > 0) { - pbx_build += ",\n"; - pbx_refs += ",\n"; - } - pbx_build += build_id; - pbx_refs += ref_id; - - Dictionary format_dict; - format_dict["build_id"] = build_id; - format_dict["ref_id"] = ref_id; - format_dict["name"] = asset.exported_path.get_file(); - format_dict["file_path"] = asset.exported_path; - format_dict["file_type"] = type; - 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 - // even if turned off in capabilities. - - String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size()); - str = str.replace("$additional_pbx_files", pbx_files); - str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build); - 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); - for (int i = 0; i < cs.size() - 1; i++) { - p_project_data.write[i] = cs[i]; - } -} - -Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) { - String binary_name = p_out_dir.get_file().get_basename(); - - Ref da = DirAccess::create_for_path(p_asset); - if (da.is_null()) { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); - } - bool file_exists = da->file_exists(p_asset); - bool dir_exists = da->dir_exists(p_asset); - if (!file_exists && !dir_exists) { - return ERR_FILE_NOT_FOUND; - } - - String base_dir = p_asset.get_base_dir().replace("res://", ""); - String destination_dir; - String destination; - String asset_path; - - bool create_framework = false; - - if (p_is_framework && p_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; - - if (!p_custom_file_name) { - file_name = p_asset.get_basename().get_file(); - } else { - file_name = *p_custom_file_name; - } - - 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 && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) { - asset_path = String("dylibs").plus_file(base_dir); - - String file_name; - - if (!p_custom_file_name) { - file_name = p_asset.get_file(); - } else { - file_name = *p_custom_file_name; - } - - 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; - - if (!p_custom_file_name) { - file_name = p_asset.get_file(); - } else { - file_name = *p_custom_file_name; - } - - 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); - } - - Ref filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); - - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - return make_dir_err; - } - } - - Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - if (err) { - return err; - } - 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; - - if (!p_custom_file_name) { - file_name = p_asset.get_basename().get_file(); - } else { - file_name = *p_custom_file_name; - } - - String framework_name = file_name + ".framework"; - - // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib - { - List 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); - } - - // Creating Info.plist - { - String info_plist_format = "\n" - "\n" - "\n" - "\n" - "CFBundleShortVersionString\n" - "1.0\n" - "CFBundleIdentifier\n" - "com.gdnative.framework.$name\n" - "CFBundleName\n" - "$name\n" - "CFBundleExecutable\n" - "$name\n" - "DTPlatformName\n" - "iphoneos\n" - "CFBundleInfoDictionaryVersion\n" - "6.0\n" - "CFBundleVersion\n" - "1\n" - "CFBundlePackageType\n" - "FMWK\n" - "MinimumOSVersion\n" - "10.0\n" - "\n" - ""; - - String info_plist = info_plist_format.replace("$name", file_name); - - Ref f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); - if (f.is_valid()) { - f->store_string(info_plist); - } - } - } - - return OK; -} - -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets) { - 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, p_should_embed }; - r_exported_assets.push_back(exported_asset); - } else { - Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); - ERR_FAIL_COND_V(err, err); - } - } - - return OK; -} - -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets) { - Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); - for (int i = 0; i < export_plugins.size(); i++) { - Vector 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 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 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, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - Vector ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); - err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - } - - Vector library_paths; - 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, true, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - return OK; -} - -Vector EditorExportPlatformIOS::_get_preset_architectures(const Ref &p_preset) { - Vector all_archs = _get_supported_architectures(); - Vector enabled_archs; - for (int i = 0; i < all_archs.size(); ++i) { - bool is_enabled = p_preset->get("architectures/" + all_archs[i].name); - if (is_enabled) { - enabled_archs.push_back(all_archs[i].name); - } - } - return enabled_archs; -} - -Error EditorExportPlatformIOS::_export_ios_plugins(const Ref &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug) { - String plugin_definition_cpp_code; - String plugin_initialization_cpp_code; - String plugin_deinitialization_cpp_code; - - Vector plugin_linked_dependencies; - Vector plugin_embedded_dependencies; - Vector plugin_files; - - Vector enabled_plugins = get_enabled_plugins(p_preset); - - Vector added_linked_dependenciy_names; - Vector added_embedded_dependenciy_names; - HashMap plist_values; - - HashSet plugin_linker_flags; - - Error err; - - for (int i = 0; i < enabled_plugins.size(); i++) { - PluginConfigIOS plugin = enabled_plugins[i]; - - // Export plugin binary. - String plugin_main_binary = PluginConfigIOS::get_plugin_main_binary(plugin, p_debug); - String plugin_binary_result_file = plugin.binary.get_file(); - // We shouldn't embed .xcframework that contains static libraries. - // Static libraries are not embedded anyway. - err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); - - ERR_FAIL_COND_V(err, err); - - // Adding dependencies. - // Use separate container for names to check for duplicates. - for (int j = 0; j < plugin.linked_dependencies.size(); j++) { - String dependency = plugin.linked_dependencies[j]; - String name = dependency.get_file(); - - if (added_linked_dependenciy_names.has(name)) { - continue; - } - - added_linked_dependenciy_names.push_back(name); - plugin_linked_dependencies.push_back(dependency); - } - - for (int j = 0; j < plugin.system_dependencies.size(); j++) { - String dependency = plugin.system_dependencies[j]; - String name = dependency.get_file(); - - if (added_linked_dependenciy_names.has(name)) { - continue; - } - - added_linked_dependenciy_names.push_back(name); - plugin_linked_dependencies.push_back(dependency); - } - - for (int j = 0; j < plugin.embedded_dependencies.size(); j++) { - String dependency = plugin.embedded_dependencies[j]; - String name = dependency.get_file(); - - if (added_embedded_dependenciy_names.has(name)) { - continue; - } - - added_embedded_dependenciy_names.push_back(name); - plugin_embedded_dependencies.push_back(dependency); - } - - plugin_files.append_array(plugin.files_to_copy); - - // Capabilities - // Also checking for duplicates. - for (int j = 0; j < plugin.capabilities.size(); j++) { - String capability = plugin.capabilities[j]; - - if (p_config_data.capabilities.has(capability)) { - continue; - } - - p_config_data.capabilities.push_back(capability); - } - - // Linker flags - // Checking duplicates - for (int j = 0; j < plugin.linker_flags.size(); j++) { - String linker_flag = plugin.linker_flags[j]; - plugin_linker_flags.insert(linker_flag); - } - - // Plist - // Using hash map container to remove duplicates - - for (const KeyValue &E : plugin.plist) { - String key = E.key; - const PluginConfigIOS::PlistItem &item = E.value; - - String value; - - switch (item.type) { - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String preset_name = "plugins_plist/" + key; - String input_value = p_preset->get(preset_name); - value = "" + input_value + ""; - } break; - default: - value = item.value; - break; - } - - if (key.is_empty() || value.is_empty()) { - continue; - } - - String plist_key = "" + key + ""; - - plist_values[plist_key] = value; - } - - // CPP Code - String definition_comment = "// Plugin: " + plugin.name + "\n"; - String initialization_method = plugin.initialization_method + "();\n"; - String deinitialization_method = plugin.deinitialization_method + "();\n"; - - plugin_definition_cpp_code += definition_comment + - "extern void " + initialization_method + - "extern void " + deinitialization_method + "\n"; - - plugin_initialization_cpp_code += "\t" + initialization_method; - plugin_deinitialization_cpp_code += "\t" + deinitialization_method; - } - - // Updating `Info.plist` - { - for (const KeyValue &E : plist_values) { - String key = E.key; - String value = E.value; - - if (key.is_empty() || value.is_empty()) { - continue; - } - - p_config_data.plist_content += key + value + "\n"; - } - } - - // Export files - { - // Export linked plugin dependency - err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - // Export embedded plugin dependency - err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); - ERR_FAIL_COND_V(err, err); - - // Export plugin files - err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets); - ERR_FAIL_COND_V(err, err); - } - - // Update CPP - { - Dictionary plugin_format; - plugin_format["definition"] = plugin_definition_cpp_code; - plugin_format["initialization"] = plugin_initialization_cpp_code; - plugin_format["deinitialization"] = plugin_deinitialization_cpp_code; - - String plugin_cpp_code = "\n// Godot Plugins\n" - "void godot_ios_plugins_initialize();\n" - "void godot_ios_plugins_deinitialize();\n" - "// Exported Plugins\n\n" - "$definition" - "// Use Plugins\n" - "void godot_ios_plugins_initialize() {\n" - "$initialization" - "}\n\n" - "void godot_ios_plugins_deinitialize() {\n" - "$deinitialization" - "}\n"; - - p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_"); - } - - // Update Linker Flag Values - { - String result_linker_flags = " "; - for (const String &E : plugin_linker_flags) { - const String &flag = E; - - if (flag.length() == 0) { - continue; - } - - if (result_linker_flags.length() > 0) { - result_linker_flags += ' '; - } - - result_linker_flags += flag; - } - result_linker_flags = result_linker_flags.replace("\"", "\\\""); - p_config_data.linker_flags += result_linker_flags; - } - - return OK; -} - -Error EditorExportPlatformIOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { - ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - - String src_pkg_name; - String dest_dir = p_path.get_base_dir() + "/"; - String binary_name = p_path.get_file().get_basename(); - - EditorProgress ep("export", "Exporting for iOS", 5, true); - - 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) { - src_pkg_name = p_preset->get("custom_template/debug"); - } else { - src_pkg_name = p_preset->get("custom_template/release"); - } - - if (src_pkg_name.is_empty()) { - String err; - src_pkg_name = find_export_template("iphone.zip", &err); - if (src_pkg_name.is_empty()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); - return ERR_FILE_NOT_FOUND; - } - } - - if (!DirAccess::exists(dest_dir)) { - return ERR_FILE_BAD_PATH; - } - - { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da.is_valid()) { - String current_dir = da->get_current_dir(); - - // remove leftovers from last export so they don't interfere - // in case some files are no longer needed - if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { - da->erase_contents_recursive(); - } - if (da->change_dir(dest_dir + binary_name) == OK) { - da->erase_contents_recursive(); - } - - da->change_dir(current_dir); - - if (!da->dir_exists(dest_dir + binary_name)) { - Error err = da->make_dir(dest_dir + binary_name); - if (err) { - return err; - } - } - } - } - - if (ep.step("Making .pck", 0)) { - return ERR_SKIP; - } - String pack_path = dest_dir + binary_name + ".pck"; - Vector libraries; - Error err = save_pack(p_preset, p_debug, pack_path, &libraries); - if (err) { - return err; - } - - if (ep.step("Extracting and configuring Xcode project", 1)) { - return ERR_SKIP; - } - - String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".xcframework"; - - print_line("Static framework: " + library_to_use); - String pkg_name; - if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { - pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); - } else { - pkg_name = "Unnamed"; - } - - bool found_library = false; - - const String project_file = "godot_ios.xcodeproj/project.pbxproj"; - HashSet files_to_parse; - files_to_parse.insert("godot_ios/godot_ios-Info.plist"); - files_to_parse.insert(project_file); - files_to_parse.insert("godot_ios/export_options.plist"); - 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, - binary_name, - _get_additional_plist_content(), - String(" ").join(_get_preset_architectures(p_preset)), - _get_linker_flags(), - _get_cpp_code(), - "", - "", - "", - "", - Vector() - }; - - Vector assets; - - Ref tmp_app_path = DirAccess::create_for_path(dest_dir); - ERR_FAIL_COND_V(tmp_app_path.is_null(), ERR_CANT_CREATE); - - print_line("Unzipping..."); - Ref io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); - unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); - if (!src_pkg_zip) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name)); - return ERR_CANT_OPEN; - } - - err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug); - ERR_FAIL_COND_V(err, err); - - //export rest of the files - int ret = unzGoToFirstFile(src_pkg_zip); - Vector project_file_data; - while (ret == UNZ_OK) { -#if defined(OSX_ENABLED) || defined(X11_ENABLED) - bool is_execute = false; -#endif - - //get filename - unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); - if (ret != UNZ_OK) { - break; - } - - String file = String::utf8(fname); - - print_line("READ: " + file); - Vector data; - data.resize(info.uncompressed_size); - - //read - unzOpenCurrentFile(src_pkg_zip); - unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); - unzCloseCurrentFile(src_pkg_zip); - - //write - - file = file.replace_first("iphone/", ""); - - if (files_to_parse.has(file)) { - _fix_config_file(p_preset, data, config_data, p_debug); - } else if (file.begins_with("libgodot.iphone")) { - if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { - ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! - } - found_library = true; -#if defined(OSX_ENABLED) || defined(X11_ENABLED) - is_execute = true; -#endif - file = file.replace(library_to_use, binary_name + ".xcframework"); - } - - if (file == project_file) { - project_file_data = data; - } - - ///@TODO need to parse logo files - - if (data.size() > 0) { - file = file.replace("godot_ios", binary_name); - - print_line("ADDING: " + file + " size: " + itos(data.size())); - - /* write it into our folder structure */ - file = dest_dir + file; - - /* make sure this folder exists */ - String dir_name = file.get_base_dir(); - if (!tmp_app_path->dir_exists(dir_name)) { - print_line("Creating " + dir_name); - Error dir_err = tmp_app_path->make_dir_recursive(dir_name); - if (dir_err) { - ERR_PRINT("Can't create '" + dir_name + "'."); - unzClose(src_pkg_zip); - return ERR_CANT_CREATE; - } - } - - /* write the file */ - { - Ref f = FileAccess::open(file, FileAccess::WRITE); - if (f.is_null()) { - ERR_PRINT("Can't write '" + file + "'."); - unzClose(src_pkg_zip); - return ERR_CANT_CREATE; - }; - f->store_buffer(data.ptr(), data.size()); - } - -#if defined(OSX_ENABLED) || defined(X11_ENABLED) - if (is_execute) { - // we need execute rights on this file - chmod(file.utf8().get_data(), 0755); - } -#endif - } - - ret = unzGoToNextFile(src_pkg_zip); - } - - /* we're done with our source zip */ - unzClose(src_pkg_zip); - - if (!found_library) { - ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive."); - return ERR_FILE_NOT_FOUND; - } - - Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); - Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); - Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); - Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); - - Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); - if (translations.size() > 0) { - { - String fname = dest_dir + binary_name + "/en.lproj"; - tmp_app_path->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); - f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); - f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); - f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); - } - - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid()) { - String lang = tr->get_locale(); - String fname = dest_dir + binary_name + "/" + lang + ".lproj"; - tmp_app_path->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - if (appnames.has(lang)) { - f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); - } - if (camera_usage_descriptions.has(lang)) { - f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); - } - if (microphone_usage_descriptions.has(lang)) { - f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); - } - if (photolibrary_usage_descriptions.has(lang)) { - f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";"); - } - } - } - } - - // Copy project static libs to the project - Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); - for (int i = 0; i < export_plugins.size(); i++) { - Vector 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 + "'."); - 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); - } - if (err) { - return err; - } - - err = _export_icons(p_preset, iconset_dir); - 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/"; - - Ref launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (launch_screen_da.is_null()) { - 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); - } - } - - if (err) { - return err; - } - - print_line("Exporting additional assets"); - _export_additional_assets(dest_dir + binary_name, libraries, assets); - _add_assets_to_project(p_preset, project_file_data, assets); - String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj"; - { - Ref f = FileAccess::open(project_file_name, FileAccess::WRITE); - if (f.is_null()) { - ERR_PRINT("Can't write '" + project_file_name + "'."); - return ERR_CANT_CREATE; - }; - f->store_buffer(project_file_data.ptr(), project_file_data.size()); - } - -#ifdef OSX_ENABLED - { - if (ep.step("Code-signing dylibs", 2)) { - return ERR_SKIP; - } - Ref dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); - ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN); - CodesignData codesign_data(p_preset, p_debug); - err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); - ERR_FAIL_COND_V(err, err); - } - - if (ep.step("Making .xcarchive", 3)) { - return ERR_SKIP; - } - String archive_path = p_path.get_basename() + ".xcarchive"; - List archive_args; - archive_args.push_back("-project"); - archive_args.push_back(dest_dir + binary_name + ".xcodeproj"); - archive_args.push_back("-scheme"); - archive_args.push_back(binary_name); - archive_args.push_back("-sdk"); - archive_args.push_back("iphoneos"); - archive_args.push_back("-configuration"); - archive_args.push_back(p_debug ? "Debug" : "Release"); - archive_args.push_back("-destination"); - archive_args.push_back("generic/platform=iOS"); - archive_args.push_back("archive"); - archive_args.push_back("-allowProvisioningUpdates"); - archive_args.push_back("-archivePath"); - archive_args.push_back(archive_path); - String archive_str; - err = OS::get_singleton()->execute("xcodebuild", archive_args, &archive_str, nullptr, true); - ERR_FAIL_COND_V(err, err); - print_line("xcodebuild (.xcarchive):\n" + archive_str); - - if (ep.step("Making .ipa", 4)) { - return ERR_SKIP; - } - List export_args; - export_args.push_back("-exportArchive"); - export_args.push_back("-archivePath"); - export_args.push_back(archive_path); - export_args.push_back("-exportOptionsPlist"); - export_args.push_back(dest_dir + binary_name + "/export_options.plist"); - export_args.push_back("-allowProvisioningUpdates"); - export_args.push_back("-exportPath"); - export_args.push_back(dest_dir); - String export_str; - err = OS::get_singleton()->execute("xcodebuild", export_args, &export_str, nullptr, true); - ERR_FAIL_COND_V(err, err); - print_line("xcodebuild (.ipa):\n" + export_str); -#else - print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package."); -#endif - - return OK; -} - -bool EditorExportPlatformIOS::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { - String err; - bool valid = false; - - // Look for export templates (first official, and if defined custom templates). - - bool dvalid = exists_export_template("iphone.zip", &err); - bool rvalid = dvalid; // Both in the same ZIP. - - 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"; - } - } - 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"; - } - } - - valid = dvalid || rvalid; - r_missing_templates = !valid; - - // Validate the rest of the configuration. - - String team_id = p_preset->get("application/app_store_team_id"); - if (team_id.length() == 0) { - err += TTR("App Store Team ID not specified - cannot configure the project.") + "\n"; - valid = false; - } - - 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"; - valid = false; - } - - const String etc_error = test_etc2(); - if (!etc_error.is_empty()) { - valid = false; - err += etc_error; - } - - if (!err.is_empty()) { - r_error = err; - } - - return valid; -} - -EditorExportPlatformIOS::EditorExportPlatformIOS() { - logo = ImageTexture::create_from_image(memnew(Image(_iphone_logo))); - plugins_changed.set(); - check_for_changes_thread.start(_check_for_changes_poll_thread, this); -} - -EditorExportPlatformIOS::~EditorExportPlatformIOS() { - quit_request.set(); - check_for_changes_thread.wait_to_finish(); -} diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h deleted file mode 100644 index 1db7418e3f..0000000000 --- a/platform/iphone/export/export_plugin.h +++ /dev/null @@ -1,293 +0,0 @@ -/*************************************************************************/ -/* export_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 IPHONE_EXPORT_PLUGIN_H -#define IPHONE_EXPORT_PLUGIN_H - -#include "core/config/project_settings.h" -#include "core/io/file_access.h" -#include "core/io/image_loader.h" -#include "core/io/marshalls.h" -#include "core/io/resource_saver.h" -#include "core/io/zip_io.h" -#include "core/os/os.h" -#include "core/templates/safe_refcount.h" -#include "core/version.h" -#include "editor/editor_export.h" -#include "editor/editor_settings.h" -#include "main/splash.gen.h" -#include "platform/iphone/logo.gen.h" -#include "string.h" - -#include "godot_plugin_config.h" - -#include - -class EditorExportPlatformIOS : public EditorExportPlatform { - GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); - - Ref logo; - - // Plugins - SafeFlag plugins_changed; - Thread check_for_changes_thread; - SafeFlag quit_request; - Mutex plugins_lock; - Vector plugins; - - typedef Error (*FileHandler)(String p_file, void *p_userdata); - static Error _walk_dir_recursive(Ref &p_da, FileHandler p_handler, void *p_userdata); - static Error _codesign(String p_file, void *p_userdata); - void _blend_and_rotate(Ref &p_dst, Ref &p_src, bool p_rot); - - struct IOSConfigData { - String pkg_name; - String binary_name; - String plist_content; - String architectures; - String linker_flags; - String cpp_code; - String modules_buildfile; - String modules_fileref; - String modules_buildphase; - String modules_buildgrp; - Vector capabilities; - }; - struct ExportArchitecture { - String name; - bool is_default = false; - - ExportArchitecture() {} - - ExportArchitecture(String p_name, bool p_is_default) { - name = p_name; - is_default = p_is_default; - } - }; - - struct IOSExportAsset { - String exported_path; - bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource - bool should_embed = false; - }; - - String _get_additional_plist_content(); - String _get_linker_flags(); - String _get_cpp_code(); - void _fix_config_file(const Ref &p_preset, Vector &pfile, const IOSConfigData &p_config, bool p_debug); - Error _export_loading_screen_images(const Ref &p_preset, const String &p_dest_dir); - Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir); - Error _export_icons(const Ref &p_preset, const String &p_iconset_dir); - - Vector _get_supported_architectures(); - Vector _get_preset_architectures(const Ref &p_preset); - - void _add_assets_to_project(const Ref &p_preset, Vector &p_project_data, const Vector &p_additional_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector &p_assets, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); - Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector &r_exported_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector &p_libraries, Vector &r_exported_assets); - Error _export_ios_plugins(const Ref &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector &r_exported_assets, bool p_debug); - - 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 (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { - if (r_error) { - *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); - } - return false; - } - } - - return true; - } - - static void _check_for_changes_poll_thread(void *ud) { - EditorExportPlatformIOS *ea = static_cast(ud); - - while (!ea->quit_request.is_set()) { - // Nothing to do if we already know the plugins have changed. - if (!ea->plugins_changed.is_set()) { - MutexLock lock(ea->plugins_lock); - - Vector loaded_plugins = get_plugins(); - - if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed.set(); - } else { - for (int i = 0; i < ea->plugins.size(); i++) { - if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { - ea->plugins_changed.set(); - break; - } - } - } - } - - 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(300000); - - if (ea->quit_request.is_set()) { - break; - } - } - } - } - -protected: - virtual void get_preset_features(const Ref &p_preset, List *r_features) override; - virtual void get_export_options(List *r_options) override; - -public: - virtual String get_name() const override { return "iOS"; } - virtual String get_os_name() const override { return "iOS"; } - virtual Ref get_logo() const override { return logo; } - - virtual bool should_update_export_options() override { - bool export_options_changed = plugins_changed.is_set(); - if (export_options_changed) { - // don't clear unless we're reporting true, to avoid race - plugins_changed.clear(); - } - return export_options_changed; - } - - virtual List get_binary_extensions(const Ref &p_preset) const override { - List list; - list.push_back("ipa"); - return list; - } - virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - - virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; - - virtual void get_platform_features(List *r_features) override { - r_features->push_back("mobile"); - r_features->push_back("ios"); - } - - virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { - } - - EditorExportPlatformIOS(); - ~EditorExportPlatformIOS(); - - /// List the gdip files in the directory specified by the p_path parameter. - static Vector list_plugin_config_files(const String &p_path, bool p_check_directories) { - Vector dir_files; - Ref da = DirAccess::open(p_path); - if (da.is_valid()) { - da->list_dir_begin(); - while (true) { - String file = da->get_next(); - if (file.is_empty()) { - break; - } - - if (file == "." || file == "..") { - continue; - } - - if (da->current_is_hidden()) { - continue; - } - - if (da->current_is_dir()) { - if (p_check_directories) { - Vector directory_files = list_plugin_config_files(p_path.plus_file(file), false); - for (int i = 0; i < directory_files.size(); ++i) { - dir_files.push_back(file.plus_file(directory_files[i])); - } - } - - continue; - } - - if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) { - dir_files.push_back(file); - } - } - da->list_dir_end(); - } - - return dir_files; - } - - static Vector get_plugins() { - Vector loaded_plugins; - - String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins"); - - if (DirAccess::exists(plugins_dir)) { - Vector plugins_filenames = list_plugin_config_files(plugins_dir, true); - - if (!plugins_filenames.is_empty()) { - Ref config_file = memnew(ConfigFile); - for (int i = 0; i < plugins_filenames.size(); i++) { - PluginConfigIOS config = PluginConfigIOS::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 get_enabled_plugins(const Ref &p_presets) { - Vector enabled_plugins; - Vector all_plugins = get_plugins(); - for (int i = 0; i < all_plugins.size(); i++) { - PluginConfigIOS plugin = all_plugins[i]; - bool enabled = p_presets->get("plugins/" + plugin.name); - if (enabled) { - enabled_plugins.push_back(plugin); - } - } - - return enabled_plugins; - } -}; - -#endif diff --git a/platform/iphone/export/godot_plugin_config.cpp b/platform/iphone/export/godot_plugin_config.cpp deleted file mode 100644 index 9118b95337..0000000000 --- a/platform/iphone/export/godot_plugin_config.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/*************************************************************************/ -/* godot_plugin_config.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_config.h" - -#include "core/config/project_settings.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" - -String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { - String absolute_path; - - if (dependency_path.is_empty()) { - return absolute_path; - } - - if (dependency_path.is_absolute_path()) { - return dependency_path; - } - - String res_path = ProjectSettings::get_singleton()->globalize_path("res://"); - absolute_path = plugin_config_dir.plus_file(dependency_path); - - return absolute_path.replace(res_path, "res://"); -} - -String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) { - String absolute_path; - - if (dependency_path.is_empty()) { - return absolute_path; - } - - if (dependency_path.is_absolute_path()) { - return dependency_path; - } - - String system_path = "/System/Library/Frameworks"; - - return system_path.plus_file(dependency_path); -} - -Vector PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector p_paths) { - Vector paths; - - for (int i = 0; i < p_paths.size(); i++) { - String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]); - - if (path.is_empty()) { - continue; - } - - paths.push_back(path); - } - - return paths; -} - -Vector PluginConfigIOS::resolve_system_dependencies(Vector p_paths) { - Vector paths; - - for (int i = 0; i < p_paths.size(); i++) { - String path = resolve_system_dependency_path(p_paths[i]); - - if (path.is_empty()) { - continue; - } - - paths.push_back(path); - } - - return paths; -} - -bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) { - bool valid_name = !plugin_config.name.is_empty(); - bool valid_binary_name = !plugin_config.binary.is_empty(); - bool valid_initialize = !plugin_config.initialization_method.is_empty(); - bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty(); - - bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize; - - if (!fields_value) { - return false; - } - - String plugin_extension = plugin_config.binary.get_extension().to_lower(); - - if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) || - (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) { - plugin_config.valid_config = true; - plugin_config.supports_targets = false; - } else { - String file_path = plugin_config.binary.get_base_dir(); - String file_name = plugin_config.binary.get_basename().get_file(); - String file_extension = plugin_config.binary.get_extension(); - String release_file_name = file_path.plus_file(file_name + ".release." + file_extension); - String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension); - - if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) || - (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) { - plugin_config.valid_config = true; - plugin_config.supports_targets = true; - } - } - - return plugin_config.valid_config; -} - -String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) { - if (!plugin_config.supports_targets) { - return plugin_config.binary; - } - - String plugin_binary_dir = plugin_config.binary.get_base_dir(); - String plugin_name_prefix = plugin_config.binary.get_basename().get_file(); - String plugin_extension = plugin_config.binary.get_extension(); - String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension; - - return plugin_binary_dir.plus_file(plugin_file); -} - -uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) { - uint64_t last_updated = FileAccess::get_modified_time(config_path); - - if (!plugin_config.supports_targets) { - last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); - } else { - String file_path = plugin_config.binary.get_base_dir(); - String file_name = plugin_config.binary.get_basename().get_file(); - String plugin_extension = plugin_config.binary.get_extension(); - String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension); - String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension); - - last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); - last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); - } - - return last_updated; -} - -PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref config_file, const String &path) { - PluginConfigIOS plugin_config = {}; - - if (!config_file.is_valid()) { - return plugin_config; - } - - config_file->clear(); - - Error err = config_file->load(path); - - if (err != OK) { - return plugin_config; - } - - String config_base_dir = path.get_base_dir(); - - plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String()); - plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String()); - plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String()); - - String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String()); - plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); - - if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) { - Vector linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector()); - Vector embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector()); - Vector system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector()); - Vector files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector()); - - plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies); - plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies); - plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies); - - plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files); - - plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector()); - - plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector()); - } - - if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) { - List keys; - config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys); - - for (int i = 0; i < keys.size(); i++) { - Vector key_components = keys[i].split(":"); - - String key_value = ""; - PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN; - - if (key_components.size() == 1) { - key_value = key_components[0]; - key_type = PluginConfigIOS::PlistItemType::STRING; - } else if (key_components.size() == 2) { - key_value = key_components[0]; - - if (key_components[1].to_lower() == "string") { - key_type = PluginConfigIOS::PlistItemType::STRING; - } else if (key_components[1].to_lower() == "integer") { - key_type = PluginConfigIOS::PlistItemType::INTEGER; - } else if (key_components[1].to_lower() == "boolean") { - key_type = PluginConfigIOS::PlistItemType::BOOLEAN; - } else if (key_components[1].to_lower() == "raw") { - key_type = PluginConfigIOS::PlistItemType::RAW; - } else if (key_components[1].to_lower() == "string_input") { - key_type = PluginConfigIOS::PlistItemType::STRING_INPUT; - } - } - - if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) { - continue; - } - - String value; - - switch (key_type) { - case PluginConfigIOS::PlistItemType::STRING: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); - value = "" + raw_value + ""; - } break; - case PluginConfigIOS::PlistItemType::INTEGER: { - int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0); - Dictionary value_dictionary; - String value_format = "$value"; - value_dictionary["value"] = raw_value; - value = value_format.format(value_dictionary, "$_"); - } break; - case PluginConfigIOS::PlistItemType::BOOLEAN: - if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) { - value = ""; - } else { - value = ""; - } - break; - case PluginConfigIOS::PlistItemType::RAW: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); - value = raw_value; - } break; - case PluginConfigIOS::PlistItemType::STRING_INPUT: { - String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); - value = raw_value; - } break; - default: - continue; - } - - plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value }; - } - } - - if (validate_plugin(plugin_config)) { - plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); - } - - return plugin_config; -} diff --git a/platform/iphone/export/godot_plugin_config.h b/platform/iphone/export/godot_plugin_config.h deleted file mode 100644 index 9ef8aa8c6d..0000000000 --- a/platform/iphone/export/godot_plugin_config.h +++ /dev/null @@ -1,132 +0,0 @@ -/*************************************************************************/ -/* godot_plugin_config.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 IPHONE_GODOT_PLUGIN_CONFIG_H -#define IPHONE_GODOT_PLUGIN_CONFIG_H - -#include "core/error/error_list.h" -#include "core/io/config_file.h" -#include "core/string/ustring.h" - -/* - The `config` section and fields are required and defined as follow: -- **name**: name of the plugin -- **binary**: path to static `.a` library - -The `dependencies` and fields are optional. -- **linked**: dependencies that should only be linked. -- **embedded**: dependencies that should be linked and embedded into application. -- **system**: system dependencies that should be linked. -- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file. -- **files**: files that would be copied into application - -The `plist` section are optional. -- **key**: key and value that would be added in Info.plist file. - */ - -struct PluginConfigIOS { - inline static const char *PLUGIN_CONFIG_EXT = ".gdip"; - - inline static const char *CONFIG_SECTION = "config"; - inline static const char *CONFIG_NAME_KEY = "name"; - inline static const char *CONFIG_BINARY_KEY = "binary"; - inline static const char *CONFIG_INITIALIZE_KEY = "initialization"; - inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; - - inline static const char *DEPENDENCIES_SECTION = "dependencies"; - inline static const char *DEPENDENCIES_LINKED_KEY = "linked"; - inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; - inline static const char *DEPENDENCIES_SYSTEM_KEY = "system"; - inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; - inline static const char *DEPENDENCIES_FILES_KEY = "files"; - inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags"; - - inline static const char *PLIST_SECTION = "plist"; - - enum PlistItemType { - UNKNOWN, - STRING, - INTEGER, - BOOLEAN, - RAW, - STRING_INPUT, - }; - - struct PlistItem { - PlistItemType type; - String value; - }; - - // Set to true when the config file is properly loaded. - bool valid_config = false; - bool supports_targets = false; - // Unix timestamp of last change to this plugin. - uint64_t last_updated = 0; - - // Required config section - String name; - String binary; - String initialization_method; - String deinitialization_method; - - // Optional dependencies section - Vector linked_dependencies; - Vector embedded_dependencies; - Vector system_dependencies; - - Vector files_to_copy; - Vector capabilities; - - Vector linker_flags; - - // Optional plist section - // String value is default value. - // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types - // : = - HashMap plist; - - static String resolve_local_dependency_path(String plugin_config_dir, String dependency_path); - - static String resolve_system_dependency_path(String dependency_path); - - static Vector resolve_local_dependencies(String plugin_config_dir, Vector p_paths); - - static Vector resolve_system_dependencies(Vector p_paths); - - static bool validate_plugin(PluginConfigIOS &plugin_config); - - static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug); - - static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path); - - static PluginConfigIOS load_plugin_config(Ref config_file, const String &path); -}; - -#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h deleted file mode 100644 index 703a906bda..0000000000 --- a/platform/iphone/godot_app_delegate.h +++ /dev/null @@ -1,41 +0,0 @@ -/*************************************************************************/ -/* godot_app_delegate.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -typedef NSObject ApplicationDelegateService; - -@interface GodotApplicalitionDelegate : NSObject - -@property(class, readonly, strong) NSArray *services; - -+ (void)addService:(ApplicationDelegateService *)service; - -@end diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m deleted file mode 100644 index 84347f9a30..0000000000 --- a/platform/iphone/godot_app_delegate.m +++ /dev/null @@ -1,467 +0,0 @@ -/*************************************************************************/ -/* godot_app_delegate.m */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_app_delegate.h" - -#import "app_delegate.h" - -@interface GodotApplicalitionDelegate () - -@end - -@implementation GodotApplicalitionDelegate - -static NSMutableArray *services = nil; - -+ (NSArray *)services { - return services; -} - -+ (void)load { - services = [NSMutableArray new]; - [services addObject:[AppDelegate new]]; -} - -+ (void)addService:(ApplicationDelegateService *)service { - if (!services || !service) { - return; - } - [services addObject:service]; -} - -// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate - -// MARK: Window - -- (UIWindow *)window { - UIWindow *result = nil; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - UIWindow *value = [service window]; - - if (value) { - result = value; - } - } - - return result; -} - -// MARK: Initializing - -- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application willFinishLaunchingWithOptions:launchOptions]) { - result = YES; - } - } - - return result; -} - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application didFinishLaunchingWithOptions:launchOptions]) { - result = YES; - } - } - - return result; -} - -/* Can be handled by Info.plist. Not yet supported by Godot. - -// MARK: Scene - -- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {} - -- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {} - -*/ - -// MARK: Life-Cycle - -- (void)applicationDidBecomeActive:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationDidBecomeActive:application]; - } -} - -- (void)applicationWillResignActive:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationWillResignActive:application]; - } -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationDidEnterBackground:application]; - } -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationWillEnterForeground:application]; - } -} - -- (void)applicationWillTerminate:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationWillTerminate:application]; - } -} - -// MARK: Environment Changes - -- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationProtectedDataDidBecomeAvailable:application]; - } -} - -- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationProtectedDataWillBecomeUnavailable:application]; - } -} - -- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationDidReceiveMemoryWarning:application]; - } -} - -- (void)applicationSignificantTimeChange:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationSignificantTimeChange:application]; - } -} - -// MARK: App State Restoration - -- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application shouldSaveSecureApplicationState:coder]) { - result = YES; - } - } - - return result; -} - -- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application shouldRestoreSecureApplicationState:coder]) { - result = YES; - } - } - - return result; -} - -- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder]; - - if (controller) { - return controller; - } - } - - return nil; -} - -- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application willEncodeRestorableStateWithCoder:coder]; - } -} - -- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didDecodeRestorableStateWithCoder:coder]; - } -} - -// MARK: Download Data in Background - -- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; - } - - completionHandler(); -} - -// MARK: Remote Notification - -// Moved to the iOS Plugin - -// MARK: User Activity and Handling Quick Actions - -- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application willContinueUserActivityWithType:userActivityType]) { - result = YES; - } - } - - return result; -} - -- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) { - result = YES; - } - } - - return result; -} - -- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didUpdateUserActivity:userActivity]; - } -} - -- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didFailToContinueUserActivityWithType:userActivityType error:error]; - } -} - -- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; - } -} - -// MARK: WatchKit - -- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application handleWatchKitExtensionRequest:userInfo reply:reply]; - } -} - -// MARK: HealthKit - -- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service applicationShouldRequestHealthAuthorization:application]; - } -} - -// MARK: Opening an URL - -- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:app openURL:url options:options]) { - return YES; - } - } - - return NO; -} - -// MARK: Disallowing Specified App Extension Types - -- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { - BOOL result = NO; - - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) { - result = YES; - } - } - - return result; -} - -// MARK: SiriKit - -- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - id result = [service application:application handlerForIntent:intent]; - - if (result) { - return result; - } - } - - return nil; -} - -// MARK: CloudKit - -- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata]; - } -} - -/* Handled By Info.plist file for now - -// MARK: Interface Geometry - -- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {} - -*/ - -@end diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm deleted file mode 100644 index 59fdfa9dcd..0000000000 --- a/platform/iphone/godot_iphone.mm +++ /dev/null @@ -1,131 +0,0 @@ -/*************************************************************************/ -/* godot_iphone.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "core/string/ustring.h" -#include "main/main.h" -#include "os_iphone.h" - -#include -#include -#include - -static OSIPhone *os = nullptr; - -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"; - p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - p_args[p_argc] = nullptr; - - return p_argc; -} - -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; - } - p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - } - - p_args[p_argc] = nullptr; - - return p_argc; -} - -int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { - size_t len = strlen(argv[0]); - - while (len--) { - if (argv[0][len] == '/') { - break; - } - } - - if (len >= 0) { - char path[512]; - memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len); - path[len] = 0; - printf("Path: %s\n", path); - chdir(path); - } - - printf("godot_iphone %s\n", argv[0]); - char cwd[512]; - getcwd(cwd, sizeof(cwd)); - printf("cwd %s\n", cwd); - os = new OSIPhone(data_dir, cache_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] = 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 == ERR_HELP) { // Returned by --help and --version, so success. - return 0; - } else 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 deleted file mode 100644 index fcb97fa63a..0000000000 --- a/platform/iphone/godot_view.h +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************************************************/ -/* godot_view.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -class String; - -@class GodotView; -@protocol DisplayLayer; -@protocol GodotViewRendererProtocol; - -@protocol GodotViewDelegate - -- (BOOL)godotViewFinishedSetup:(GodotView *)view; - -@end - -@interface GodotView : UIView - -@property(assign, nonatomic) id renderer; -@property(assign, nonatomic) id delegate; - -@property(assign, readonly, nonatomic) BOOL isActive; - -@property(assign, nonatomic) BOOL useCADisplayLink; -@property(strong, readonly, nonatomic) CALayer *renderingLayer; -@property(assign, readonly, nonatomic) BOOL canRender; - -@property(assign, nonatomic) NSTimeInterval renderingInterval; - -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName; -- (void)stopRendering; -- (void)startRendering; - -- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; -- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; -- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; -- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; - -@end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm deleted file mode 100644 index e48dd2e507..0000000000 --- a/platform/iphone/godot_view.mm +++ /dev/null @@ -1,481 +0,0 @@ -/*************************************************************************/ -/* godot_view.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/string/ustring.h" -#import "display_layer.h" -#include "display_server_iphone.h" -#import "godot_view_gesture_recognizer.h" -#import "godot_view_renderer.h" - -#import - -static const int max_touches = 8; -static const float earth_gravity = 9.80665; - -@interface GodotView () { - UITouch *godot_touches[max_touches]; -} - -@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 *renderingLayer; - -@property(strong, nonatomic) CMMotionManager *motionManager; - -@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; - -@end - -@implementation GodotView - -- (CALayer *)initializeRenderingForDriver:(NSString *)driverName { - if (self.renderingLayer) { - return self.renderingLayer; - } - - CALayer *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; - self.delegate = 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; - } -} - -- (void)godot_commonInit { - self.contentScaleFactor = [UIScreen mainScreen].nativeScale; - - [self initTouches]; - - self.multipleTouchEnabled = YES; - - // Configure and start accelerometer - if (!self.motionManager) { - self.motionManager = [[CMMotionManager alloc] init]; - 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]; -} - -- (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)]; - - // Approximate frame rate - // assumes device refreshes at 60 fps - int displayFPS = (NSInteger)(1.0 / self.renderingInterval); - - self.displayLink.preferredFramesPerSecond = displayFPS; - - // 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) { - // Continue. - } - - // 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; - } - - if (self.delegate) { - BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self]; - - if (!delegateFinishedSetup) { - 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: Touches - -- (void)initTouches { - for (int i = 0; i < max_touches; i++) { - godot_touches[i] = nullptr; - } -} - -- (int)getTouchIDForTouch:(UITouch *)p_touch { - int first = -1; - for (int i = 0; i < max_touches; i++) { - if (first == -1 && godot_touches[i] == nullptr) { - 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] == nullptr) { - continue; - } - if (godot_touches[i] == p_touch) { - godot_touches[i] = nullptr; - } else { - ++remaining; - } - } - return remaining; -} - -- (void)clearTouches { - for (int i = 0; i < max_touches; i++) { - godot_touches[i] = nullptr; - } -} - -- (void)godotTouchesBegan:(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)godotTouchesMoved:(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)godotTouchesEnded:(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)godotTouchesCancelled:(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; - - // To be consistent with Android we convert the unit of measurement from g (Earth's gravity) - // to m/s^2. - gravity.x *= earth_gravity; - gravity.y *= earth_gravity; - gravity.z *= earth_gravity; - acceleration.x *= earth_gravity; - acceleration.y *= earth_gravity; - acceleration.z *= earth_gravity; - - ///@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/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h deleted file mode 100644 index 9fd8a6b222..0000000000 --- a/platform/iphone/godot_view_gesture_recognizer.h +++ /dev/null @@ -1,46 +0,0 @@ -/*************************************************************************/ -/* godot_view_gesture_recognizer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -// GLViewGestureRecognizer allows iOS gestures to work correctly 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 - -#import - -@interface GodotViewGestureRecognizer : UIGestureRecognizer - -@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; - -- (instancetype)init; - -@end diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm deleted file mode 100644 index 49a92add5e..0000000000 --- a/platform/iphone/godot_view_gesture_recognizer.mm +++ /dev/null @@ -1,186 +0,0 @@ -/*************************************************************************/ -/* godot_view_gesture_recognizer.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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" - -#import "godot_view.h" - -#include "core/config/project_settings.h" - -// 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 () - -@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; - -@end - -@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 - -- (GodotView *)godotView { - return (GodotView *)self.view; -} - -- (instancetype)init { - self = [super init]; - - self.cancelsTouchesInView = YES; - self.delaysTouchesBegan = YES; - self.delaysTouchesEnded = YES; - self.requiresExclusiveTouchType = NO; - - self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); - - 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; - } -} - -- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { - [self.delayTimer fire]; - - self.delayedTouches = touches; - self.delayedEvent = event; - - self.delayTimer = [NSTimer - scheduledTimerWithTimeInterval:self.delayTimeInterval - target:self - selector:@selector(fireDelayedTouches:) - userInfo:nil - repeats:NO]; -} - -- (void)fireDelayedTouches:(id)timer { - [self.delayTimer invalidate]; - self.delayTimer = nil; - - if (self.delayedTouches) { - [self.godotView godotTouchesBegan: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]; - - [super touchesBegan:touches withEvent:event]; -} - -- (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.godotView]; - CGPoint to = [touch previousLocationInView:self.godotView]; - 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.godotView godotTouchesMoved:cleared withEvent:event]; - return; - } - } - - return; - } - - [self.godotView godotTouchesMoved:cleared withEvent:event]; - - [super touchesMoved:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - [self.delayTimer fire]; - - NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; - [self.godotView godotTouchesEnded:cleared withEvent:event]; - - [super touchesEnded:touches withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - [self.delayTimer fire]; - [self.godotView godotTouchesCancelled:touches withEvent:event]; - - [super touchesCancelled:touches withEvent:event]; -} - -- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { - NSMutableSet *cleared = [touches mutableCopy]; - - for (UITouch *touch in touches) { - if (touch.view != self.view || touch.phase != phaseToSave) { - [cleared removeObject:touch]; - } - } - - return cleared; -} - -@end diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h deleted file mode 100644 index b3ee23ae4f..0000000000 --- a/platform/iphone/godot_view_renderer.h +++ /dev/null @@ -1,44 +0,0 @@ -/*************************************************************************/ -/* godot_view_renderer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@protocol GodotViewRendererProtocol - -@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; - -- (BOOL)setupView:(UIView *)view; -- (void)renderOnView:(UIView *)view; - -@end - -@interface GodotViewRenderer : NSObject - -@end diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm deleted file mode 100644 index 32477ae41c..0000000000 --- a/platform/iphone/godot_view_renderer.mm +++ /dev/null @@ -1,118 +0,0 @@ -/*************************************************************************/ -/* godot_view_renderer.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/config/project_settings.h" -#include "core/os/keyboard.h" -#import "display_server_iphone.h" -#include "main/main.h" -#include "os_iphone.h" -#include "servers/audio_server.h" - -#import -#import -#import -#import -#import - -@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/ios.h b/platform/iphone/ios.h deleted file mode 100644 index 0607d7b395..0000000000 --- a/platform/iphone/ios.h +++ /dev/null @@ -1,61 +0,0 @@ -/*************************************************************************/ -/* ios.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 IOS_H -#define IOS_H - -#include "core/object/class_db.h" -#import - -class iOS : public Object { - GDCLASS(iOS, Object); - - static void _bind_methods(); - -private: - CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr; - - CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13)); - void start_haptic_engine(); - void stop_haptic_engine(); - -public: - static void alert(const char *p_alert, const char *p_title); - - bool supports_haptic_engine(); - void vibrate_haptic_engine(float p_duration_seconds); - - String get_model() const; - String get_rate_url(int p_app_id) const; - - iOS(); -}; - -#endif diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm deleted file mode 100644 index 79baae028a..0000000000 --- a/platform/iphone/ios.mm +++ /dev/null @@ -1,180 +0,0 @@ -/*************************************************************************/ -/* ios.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "ios.h" - -#import "app_delegate.h" -#import "view_controller.h" - -#import -#import -#include - -void iOS::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url); - ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine); - ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine); - ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine); -}; - -bool iOS::supports_haptic_engine() { - if (@available(iOS 13, *)) { - id capabilities = [CHHapticEngine capabilitiesForHardware]; - return capabilities.supportsHaptics; - } - - return false; -} - -CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { - if (haptic_engine == nullptr) { - NSError *error = nullptr; - haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error]; - - if (!error) { - [haptic_engine setAutoShutdownEnabled:true]; - } else { - haptic_engine = nullptr; - NSLog(@"Could not initialize haptic engine: %@", error); - } - } - - return haptic_engine; -} - -void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) { - if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy... - if (supports_haptic_engine()) { - CHHapticEngine *haptic_engine = get_haptic_engine_instance(); - if (haptic_engine) { - NSDictionary *hapticDict = @{ - CHHapticPatternKeyPattern : @[ - @{CHHapticPatternKeyEvent : @{ - CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, - CHHapticPatternKeyTime : @(CHHapticTimeImmediate), - CHHapticPatternKeyEventDuration : @(p_duration_seconds) - }, - }, - ], - }; - - NSError *error; - CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; - - [[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error]; - - NSLog(@"Could not vibrate using haptic engine: %@", error); - } - - return; - } - } - - NSLog(@"Haptic engine is not supported in this version of iOS"); -} - -void iOS::start_haptic_engine() { - if (@available(iOS 13, *)) { - if (supports_haptic_engine()) { - CHHapticEngine *haptic_engine = get_haptic_engine_instance(); - if (haptic_engine) { - [haptic_engine startWithCompletionHandler:^(NSError *returnedError) { - if (returnedError) { - NSLog(@"Could not start haptic engine: %@", returnedError); - } - }]; - } - - return; - } - } - - NSLog(@"Haptic engine is not supported in this version of iOS"); -} - -void iOS::stop_haptic_engine() { - if (@available(iOS 13, *)) { - if (supports_haptic_engine()) { - CHHapticEngine *haptic_engine = get_haptic_engine_instance(); - if (haptic_engine) { - [haptic_engine stopWithCompletionHandler:^(NSError *returnedError) { - if (returnedError) { - NSLog(@"Could not stop haptic engine: %@", returnedError); - } - }]; - } - - return; - } - } - - NSLog(@"Haptic engine is not supported in this version of iOS"); -} - -void iOS::alert(const char *p_alert, const char *p_title) { - 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 { - // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". - size_t size; - sysctlbyname("hw.machine", nullptr, &size, nullptr, 0); - char *model = (char *)malloc(size); - if (model == nullptr) { - return ""; - } - sysctlbyname("hw.machine", model, &size, nullptr, 0); - NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; - free(model); - const char *str = [platform UTF8String]; - return String::utf8(str != nullptr ? str : ""); -} - -String iOS::get_rate_url(int p_app_id) const { - String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; - - String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); - - printf("returning rate url %s\n", ret.utf8().get_data()); - return ret; -} - -iOS::iOS() {} diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h deleted file mode 100644 index 37e272a2c9..0000000000 --- a/platform/iphone/joypad_iphone.h +++ /dev/null @@ -1,50 +0,0 @@ -/*************************************************************************/ -/* joypad_iphone.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@interface JoypadIPhoneObserver : NSObject - -- (void)startObserving; -- (void)startProcessing; -- (void)finishObserving; - -@end - -class JoypadIPhone { -private: - JoypadIPhoneObserver *observer; - -public: - JoypadIPhone(); - ~JoypadIPhone(); - - void start_processing(); -}; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm deleted file mode 100644 index 9c2feeaaca..0000000000 --- a/platform/iphone/joypad_iphone.mm +++ /dev/null @@ -1,344 +0,0 @@ -/*************************************************************************/ -/* joypad_iphone.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/config/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]; -} - -- (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, String::utf8([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... - _weakify(self); - _weakify(controller); - - controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { - _strongify(self); - _strongify(controller); - - int joy_id = [self getJoyIdForController:controller]; - - if (element == gamepad.buttonA) { - Input::get_singleton()->joy_button(joy_id, JoyButton::A, - gamepad.buttonA.isPressed); - } else if (element == gamepad.buttonB) { - Input::get_singleton()->joy_button(joy_id, JoyButton::B, - gamepad.buttonB.isPressed); - } else if (element == gamepad.buttonX) { - Input::get_singleton()->joy_button(joy_id, JoyButton::X, - gamepad.buttonX.isPressed); - } else if (element == gamepad.buttonY) { - Input::get_singleton()->joy_button(joy_id, JoyButton::Y, - gamepad.buttonY.isPressed); - } else if (element == gamepad.leftShoulder) { - Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER, - gamepad.leftShoulder.isPressed); - } else if (element == gamepad.rightShoulder) { - Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER, - gamepad.rightShoulder.isPressed); - } else if (element == gamepad.dpad) { - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, - gamepad.dpad.up.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, - gamepad.dpad.down.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, - gamepad.dpad.left.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, - gamepad.dpad.right.isPressed); - } - - if (element == gamepad.leftThumbstick) { - float value = gamepad.leftThumbstick.xAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value); - value = -gamepad.leftThumbstick.yAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value); - } else if (element == gamepad.rightThumbstick) { - float value = gamepad.rightThumbstick.xAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value); - value = -gamepad.rightThumbstick.yAxis.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value); - } else if (element == gamepad.leftTrigger) { - float value = gamepad.leftTrigger.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value); - } else if (element == gamepad.rightTrigger) { - float value = gamepad.rightTrigger.value; - Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value); - } - }; - } else if (controller.microGamepad != nil) { - // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad - _weakify(self); - _weakify(controller); - - controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { - _strongify(self); - _strongify(controller); - - int joy_id = [self getJoyIdForController:controller]; - - if (element == gamepad.buttonA) { - Input::get_singleton()->joy_button(joy_id, JoyButton::A, - gamepad.buttonA.isPressed); - } else if (element == gamepad.buttonX) { - Input::get_singleton()->joy_button(joy_id, JoyButton::X, - gamepad.buttonX.isPressed); - } else if (element == gamepad.dpad) { - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, - gamepad.dpad.up.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, - gamepad.dpad.down.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed); - Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed); - } - }; - } - - ///@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/keyboard_input_view.h b/platform/iphone/keyboard_input_view.h deleted file mode 100644 index 33fa5d571a..0000000000 --- a/platform/iphone/keyboard_input_view.h +++ /dev/null @@ -1,37 +0,0 @@ -/*************************************************************************/ -/* keyboard_input_view.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@interface GodotKeyboardInputView : UITextView - -- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end; - -@end diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm deleted file mode 100644 index f2a7b22483..0000000000 --- a/platform/iphone/keyboard_input_view.mm +++ /dev/null @@ -1,197 +0,0 @@ -/*************************************************************************/ -/* keyboard_input_view.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "keyboard_input_view.h" - -#include "core/os/keyboard.h" -#include "display_server_iphone.h" -#include "os_iphone.h" - -@interface GodotKeyboardInputView () - -@property(nonatomic, copy) NSString *previousText; -@property(nonatomic, assign) NSRange previousSelectedRange; - -@end - -@implementation GodotKeyboardInputView - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { - self = [super initWithFrame:frame textContainer:textContainer]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (void)godot_commonInit { - self.hidden = YES; - self.delegate = self; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(observeTextChange:) - name:UITextViewTextDidChangeNotification - object:self]; -} - -- (void)dealloc { - self.delegate = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -// MARK: Keyboard - -- (BOOL)canBecomeFirstResponder { - return YES; -} - -- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end { - self.text = existingString; - self.previousText = existingString; - - NSInteger safeStartIndex = MAX(start, 0); - - NSRange textRange; - - // Either a simple cursor or a selection. - if (end > 0) { - textRange = NSMakeRange(safeStartIndex, end - start); - } else { - textRange = NSMakeRange(safeStartIndex, 0); - } - - self.selectedRange = textRange; - self.previousSelectedRange = textRange; - - return [self becomeFirstResponder]; -} - -- (BOOL)resignFirstResponder { - self.text = nil; - self.previousText = nil; - return [super resignFirstResponder]; -} - -// MARK: OS Messages - -- (void)deleteText:(NSInteger)charactersToDelete { - for (int i = 0; i < charactersToDelete; i++) { - DisplayServerIPhone::get_singleton()->key(Key::BACKSPACE, true); - DisplayServerIPhone::get_singleton()->key(Key::BACKSPACE, false); - } -} - -- (void)enterText:(NSString *)substring { - String characters; - characters.parse_utf8([substring UTF8String]); - - for (int i = 0; i < characters.size(); i++) { - int character = characters[i]; - - switch (character) { - case 10: - character = (int)Key::ENTER; - break; - case 8198: - character = (int)Key::SPACE; - break; - default: - break; - } - - DisplayServerIPhone::get_singleton()->key((Key)character, true); - DisplayServerIPhone::get_singleton()->key((Key)character, false); - } -} - -// MARK: Observer - -- (void)observeTextChange:(NSNotification *)notification { - if (notification.object != self) { - return; - } - - if (self.previousSelectedRange.length == 0) { - // We are deleting all text before cursor if no range was selected. - // This way any inserted or changed text will be updated. - NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; - [self deleteText:substringToDelete.length]; - } else { - // If text was previously selected - // we are sending only one `backspace`. - // It will remove all text from text input. - [self deleteText:1]; - } - - NSString *substringToEnter; - - if (self.selectedRange.length == 0) { - // If previous cursor had a selection - // we have to calculate an inserted text. - if (self.previousSelectedRange.length != 0) { - NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length; - NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location); - NSInteger rangeLength = MAX(0, rangeEnd - rangeStart); - - NSRange calculatedRange; - - if (rangeLength >= 0) { - calculatedRange = NSMakeRange(rangeStart, rangeLength); - } else { - calculatedRange = NSMakeRange(rangeStart, 0); - } - - substringToEnter = [self.text substringWithRange:calculatedRange]; - } else { - substringToEnter = [self.text substringToIndex:self.selectedRange.location]; - } - } else { - substringToEnter = [self.text substringWithRange:self.selectedRange]; - } - - [self enterText:substringToEnter]; - - self.previousText = self.text; - self.previousSelectedRange = self.selectedRange; -} - -@end diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png deleted file mode 100644 index 966d8aa70a..0000000000 Binary files a/platform/iphone/logo.png and /dev/null differ diff --git a/platform/iphone/main.m b/platform/iphone/main.m deleted file mode 100644 index acfa7ab731..0000000000 --- a/platform/iphone/main.m +++ /dev/null @@ -1,56 +0,0 @@ -/*************************************************************************/ -/* main.m */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_app_delegate.h" - -#import -#include - -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; - - printf("running app main\n"); - @autoreleasepool { - NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]); - UIApplicationMain(argc, argv, nil, className); - } - printf("main done\n"); - return 0; -} diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h deleted file mode 100644 index d03403bbb4..0000000000 --- a/platform/iphone/os_iphone.h +++ /dev/null @@ -1,124 +0,0 @@ -/*************************************************************************/ -/* os_iphone.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -#ifndef OS_IPHONE_H -#define OS_IPHONE_H - -#include "drivers/coreaudio/audio_driver_coreaudio.h" -#include "drivers/unix/os_unix.h" -#include "ios.h" -#include "joypad_iphone.h" -#include "servers/audio_server.h" -#include "servers/rendering/renderer_compositor.h" - -#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: - static HashMap dynamic_symbol_lookup_table; - friend void register_dynamic_symbol(char *name, void *address); - - AudioDriverCoreAudio audio_driver; - - iOS *ios = nullptr; - - JoypadIPhone *joypad_iphone = nullptr; - - MainLoop *main_loop = nullptr; - - virtual void initialize_core() override; - virtual void initialize() override; - - virtual void initialize_joypads() override { - } - - virtual void set_main_loop(MainLoop *p_main_loop) override; - virtual MainLoop *get_main_loop() const override; - - virtual void delete_main_loop() override; - - virtual void finalize() override; - - String user_data_dir; - String cache_dir; - - bool is_focused = false; - - void deinitialize_modules(); - -public: - static OSIPhone *get_singleton(); - - OSIPhone(String p_data_dir, String p_cache_dir); - ~OSIPhone(); - - void initialize_modules(); - - bool iterate(); - - void start(); - - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) 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 String get_name() const override; - virtual String get_model_name() const override; - - virtual Error shell_open(String p_uri) override; - - void set_user_data_dir(String p_dir); - virtual String get_user_data_dir() const override; - - virtual String get_cache_path() const override; - - virtual String get_locale() const override; - - virtual String get_unique_id() const override; - virtual String get_processor_name() const override; - - virtual void vibrate_handheld(int p_duration_ms = 500) override; - - virtual bool _check_internal_feature_support(const String &p_feature) override; - - void on_focus_out(); - void on_focus_in(); -}; - -#endif // OS_IPHONE_H - -#endif // IPHONE_ENABLED diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm deleted file mode 100644 index 95b06b728e..0000000000 --- a/platform/iphone/os_iphone.mm +++ /dev/null @@ -1,346 +0,0 @@ -/*************************************************************************/ -/* os_iphone.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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/config/project_settings.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/io/file_access_pack.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 -#import -#import -#include - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#import -#ifdef USE_VOLK -#include -#else -#include -#endif -#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 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, String p_cache_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; - cache_dir = p_cache_dir; - - Vector 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::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()); -} - -void OSIPhone::initialize_core() { - OS_Unix::initialize_core(); - - set_user_data_dir(user_data_dir); -} - -void OSIPhone::initialize() { - initialize_core(); -} - -void OSIPhone::initialize_modules() { - 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); - } -} - -void OSIPhone::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - - if (main_loop) { - main_loop->initialize(); - } -} - -MainLoop *OSIPhone::get_main_loop() const { - return main_loop; -} - -void OSIPhone::delete_main_loop() { - if (main_loop) { - main_loop->finalize(); - 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, String *r_resolved_path) { - if (p_path.length() == 0) { - p_library_handle = RTLD_SELF; - - if (r_resolved_path != nullptr) { - *r_resolved_path = p_path; - } - - return OK; - } - return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_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); -} - -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]; - - if (![[UIApplication sharedApplication] canOpenURL:url]) { - return ERR_CANT_OPEN; - } - - printf("opening url %s\n", p_uri.utf8().get_data()); - - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - - return OK; -} - -void OSIPhone::set_user_data_dir(String p_dir) { - Ref 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()); -} - -String OSIPhone::get_user_data_dir() const { - return user_data_dir; -} - -String OSIPhone::get_cache_path() const { - return cache_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]); -} - -String OSIPhone::get_processor_name() const { - char buffer[256]; - size_t buffer_len = 256; - if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) { - return String::utf8(buffer, buffer_len); - } - ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); -} - -void OSIPhone::vibrate_handheld(int p_duration_ms) { - if (ios->supports_haptic_engine()) { - ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f); - } else { - // iOS <13 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]; - - 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]; - - audio_driver.start(); - } -} - -#endif // IPHONE_ENABLED diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h deleted file mode 100644 index fed77d8932..0000000000 --- a/platform/iphone/platform_config.h +++ /dev/null @@ -1,44 +0,0 @@ -/*************************************************************************/ -/* platform_config.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -#define OPENGL_INCLUDE_H - -#define PLATFORM_REFCOUNT - -#define PTHREAD_RENAME_SELF - -#define _weakify(var) __weak typeof(var) GDWeak_##var = var; -#define _strongify(var) \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wshadow\"") \ - __strong typeof(var) var = GDWeak_##var; \ - _Pragma("clang diagnostic pop") diff --git a/platform/iphone/tts_ios.h b/platform/iphone/tts_ios.h deleted file mode 100644 index 064316b0b2..0000000000 --- a/platform/iphone/tts_ios.h +++ /dev/null @@ -1,63 +0,0 @@ -/*************************************************************************/ -/* tts_ios.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 TTS_IOS_H -#define TTS_IOS_H - -#if __has_include() -#import -#else -#import -#endif - -#include "core/string/ustring.h" -#include "core/templates/list.h" -#include "core/templates/rb_map.h" -#include "core/variant/array.h" -#include "servers/display_server.h" - -@interface TTS_IOS : NSObject { - bool speaking; - HashMap ids; - - AVSpeechSynthesizer *av_synth; - List queue; -} - -- (void)pauseSpeaking; -- (void)resumeSpeaking; -- (void)stopSpeaking; -- (bool)isSpeaking; -- (bool)isPaused; -- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; -- (Array)getVoices; -@end - -#endif // TTS_IOS_H diff --git a/platform/iphone/tts_ios.mm b/platform/iphone/tts_ios.mm deleted file mode 100644 index a079d02add..0000000000 --- a/platform/iphone/tts_ios.mm +++ /dev/null @@ -1,164 +0,0 @@ -/*************************************************************************/ -/* tts_ios.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "tts_ios.h" - -@implementation TTS_IOS - -- (id)init { - self = [super init]; - self->speaking = false; - self->av_synth = [[AVSpeechSynthesizer alloc] init]; - [self->av_synth setDelegate:self]; - print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); - return self; -} - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance { - NSString *string = [utterance speechString]; - - // Convert from UTF-16 to UTF-32 position. - int pos = 0; - for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { - unichar c = [string characterAtIndex:i]; - if ((c & 0xfffffc00) == 0xd800) { - i++; - } - pos++; - } - - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); -} - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); - ids.erase(utterance); - speaking = false; - [self update]; -} - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); - ids.erase(utterance); - speaking = false; - [self update]; -} - -- (void)update { - if (!speaking && queue.size() > 0) { - DisplayServer::TTSUtterance &message = queue.front()->get(); - - AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; - [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; - if (message.rate > 1.f) { - [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; - } else if (message.rate < 1.f) { - [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; - } - [new_utterance setPitchMultiplier:message.pitch]; - [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; - - ids[new_utterance] = message.id; - [av_synth speakUtterance:new_utterance]; - - queue.pop_front(); - - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); - speaking = true; - } -} - -- (void)pauseSpeaking { - [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; -} - -- (void)resumeSpeaking { - [av_synth continueSpeaking]; -} - -- (void)stopSpeaking { - for (DisplayServer::TTSUtterance &message : queue) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); - } - queue.clear(); - [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; - speaking = false; -} - -- (bool)isSpeaking { - return speaking || (queue.size() > 0); -} - -- (bool)isPaused { - return [av_synth isPaused]; -} - -- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { - if (interrupt) { - [self stopSpeaking]; - } - - if (text.is_empty()) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); - return; - } - - DisplayServer::TTSUtterance message; - message.text = text; - message.voice = voice; - message.volume = CLAMP(volume, 0, 100); - message.pitch = CLAMP(pitch, 0.f, 2.f); - message.rate = CLAMP(rate, 0.1f, 10.f); - message.id = utterance_id; - queue.push_back(message); - - if ([self isPaused]) { - [self resumeSpeaking]; - } else { - [self update]; - } -} - -- (Array)getVoices { - Array list; - for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { - NSString *voiceIdentifierString = [voice identifier]; - NSString *voiceLocaleIdentifier = [voice language]; - NSString *voiceName = [voice name]; - Dictionary voice_d; - voice_d["name"] = String::utf8([voiceName UTF8String]); - voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); - voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); - list.push_back(voice_d); - } - return list; -} - -@end diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h deleted file mode 100644 index c8b37a4d11..0000000000 --- a/platform/iphone/view_controller.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************/ -/* view_controller.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -@class GodotView; -@class GodotNativeVideoView; -@class GodotKeyboardInputView; - -@interface ViewController : UIViewController - -@property(nonatomic, readonly, strong) GodotView *godotView; -@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; - -@end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm deleted file mode 100644 index 4f4ef4f046..0000000000 --- a/platform/iphone/view_controller.mm +++ /dev/null @@ -1,240 +0,0 @@ -/*************************************************************************/ -/* view_controller.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "view_controller.h" -#include "core/config/project_settings.h" -#include "display_server_iphone.h" -#import "godot_view.h" -#import "godot_view_renderer.h" -#import "keyboard_input_view.h" -#include "os_iphone.h" - -#import -#import - -@interface ViewController () - -@property(strong, nonatomic) GodotViewRenderer *renderer; -@property(strong, nonatomic) GodotKeyboardInputView *keyboardView; - -@property(strong, nonatomic) UIView *godotLoadingOverlay; - -@end - -@implementation ViewController - -- (GodotView *)godotView { - return (GodotView *)self.view; -} - -- (void)loadView { - GodotView *view = [[GodotView alloc] init]; - GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; - - self.renderer = renderer; - self.view = view; - - view.renderer = self.renderer; - view.delegate = self; -} - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (void)godot_commonInit { - // Initialize view controller values. -} - -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - printf("*********** did receive memory warning!\n"); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self observeKeyboard]; - [self displayLoadingOverlay]; - - if (@available(iOS 11.0, *)) { - [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; - } -} - -- (void)observeKeyboard { - printf("******** setting up keyboard input view\n"); - self.keyboardView = [GodotKeyboardInputView new]; - [self.view addSubview:self.keyboardView]; - - 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)displayLoadingOverlay { - NSBundle *bundle = [NSBundle mainBundle]; - NSString *storyboardName = @"Launch Screen"; - - if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) { - return; - } - - UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; - - UIViewController *controller = [launchStoryboard instantiateInitialViewController]; - self.godotLoadingOverlay = controller.view; - self.godotLoadingOverlay.frame = self.view.bounds; - self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - - [self.view addSubview:self.godotLoadingOverlay]; -} - -- (BOOL)godotViewFinishedSetup:(GodotView *)view { - [self.godotLoadingOverlay removeFromSuperview]; - self.godotLoadingOverlay = nil; - - return YES; -} - -- (void)dealloc { - self.keyboardView = nil; - - self.renderer = nil; - - if (self.godotLoadingOverlay) { - [self.godotLoadingOverlay removeFromSuperview]; - self.godotLoadingOverlay = nil; - } - - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -// MARK: Orientation - -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { - return UIRectEdgeAll; -} - -- (BOOL)shouldAutorotate { - 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 { - 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 DisplayServer::SCREEN_REVERSE_LANDSCAPE: - return UIInterfaceOrientationMaskLandscapeRight; - case DisplayServer::SCREEN_REVERSE_PORTRAIT: - return UIInterfaceOrientationMaskPortraitUpsideDown; - case DisplayServer::SCREEN_SENSOR_LANDSCAPE: - return UIInterfaceOrientationMaskLandscape; - case DisplayServer::SCREEN_SENSOR_PORTRAIT: - return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; - case DisplayServer::SCREEN_SENSOR: - return UIInterfaceOrientationMaskAll; - case DisplayServer::SCREEN_LANDSCAPE: - return UIInterfaceOrientationMaskLandscapeLeft; - } -} - -- (BOOL)prefersStatusBarHidden { - return YES; -} - -- (BOOL)prefersHomeIndicatorAutoHidden { - if (GLOBAL_GET("display/window/ios/hide_home_indicator")) { - return YES; - } else { - return NO; - } -} - -// 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); - } -} - -@end diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h deleted file mode 100644 index 7576525755..0000000000 --- a/platform/iphone/vulkan_context_iphone.h +++ /dev/null @@ -1,48 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_iphone.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 VULKAN_CONTEXT_IPHONE_H -#define VULKAN_CONTEXT_IPHONE_H - -#include "drivers/vulkan/vulkan_context.h" - -#import - -class VulkanContextIPhone : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; - -public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height); - - VulkanContextIPhone(); - ~VulkanContextIPhone(); -}; - -#endif // VULKAN_CONTEXT_IPHONE_H diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm deleted file mode 100644 index 17cb0f6009..0000000000 --- a/platform/iphone/vulkan_context_iphone.mm +++ /dev/null @@ -1,59 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_iphone.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_iphone.h" -#ifdef USE_VOLK -#include -#else -#include -#endif - -const char *VulkanContextIPhone::_get_platform_surface_extension() const { - return VK_MVK_IOS_SURFACE_EXTENSION_NAME; -} - -Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) { - VkIOSSurfaceCreateInfoMVK createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.pView = (__bridge const void *)p_metal_layer; - - VkSurfaceKHR surface; - VkResult err = - vkCreateIOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); - ERR_FAIL_COND_V(err, ERR_CANT_CREATE); - - return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); -} - -VulkanContextIPhone::VulkanContextIPhone() {} - -VulkanContextIPhone::~VulkanContextIPhone() {} diff --git a/platform/macos/SCsub b/platform/macos/SCsub new file mode 100644 index 0000000000..d0856c709a --- /dev/null +++ b/platform/macos/SCsub @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +Import("env") + +from platform_methods import run_in_subprocess +import platform_macos_builders + +files = [ + "os_macos.mm", + "godot_application.mm", + "godot_application_delegate.mm", + "crash_handler_macos.mm", + "macos_terminal_logger.mm", + "display_server_macos.mm", + "godot_content_view.mm", + "godot_window_delegate.mm", + "godot_window.mm", + "key_mapping_macos.mm", + "godot_main_macos.mm", + "dir_access_macos.mm", + "tts_macos.mm", + "joypad_macos.cpp", + "vulkan_context_macos.mm", + "gl_manager_macos_legacy.mm", +] + +prog = env.add_program("#bin/godot", files) + +if env["debug_symbols"] and env["separate_debug_symbols"]: + env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos)) diff --git a/platform/macos/crash_handler_macos.h b/platform/macos/crash_handler_macos.h new file mode 100644 index 0000000000..c9b0e77dc4 --- /dev/null +++ b/platform/macos/crash_handler_macos.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* crash_handler_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 CRASH_HANDLER_MACOS_H +#define CRASH_HANDLER_MACOS_H + +class CrashHandler { + bool disabled; + +public: + void initialize(); + + void disable(); + bool is_disabled() const { return disabled; }; + + CrashHandler(); + ~CrashHandler(); +}; + +#endif // CRASH_HANDLER_MACOS_H diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm new file mode 100644 index 0000000000..74bd012f0c --- /dev/null +++ b/platform/macos/crash_handler_macos.mm @@ -0,0 +1,203 @@ +/*************************************************************************/ +/* crash_handler_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "crash_handler_macos.h" + +#include "core/config/project_settings.h" +#include "core/os/os.h" +#include "core/string/print_string.h" +#include "core/version.h" +#include "main/main.h" + +#include +#include + +#if defined(DEBUG_ENABLED) +#define CRASH_HANDLER_ENABLED 1 +#endif + +#ifdef CRASH_HANDLER_ENABLED +#include +#include +#include +#include +#include + +#include +#include + +static uint64_t load_address() { + const struct segment_command_64 *cmd = getsegbyname("__TEXT"); + char full_path[1024]; + uint32_t size = sizeof(full_path); + + if (cmd && !_NSGetExecutablePath(full_path, &size)) { + uint32_t dyld_count = _dyld_image_count(); + for (uint32_t i = 0; i < dyld_count; i++) { + const char *image_name = _dyld_get_image_name(i); + if (image_name && strncmp(image_name, full_path, 1024) == 0) { + return cmd->vmaddr + _dyld_get_image_vmaddr_slide(i); + } + } + } + + return 0; +} + +static void handle_crash(int sig) { + if (OS::get_singleton() == nullptr) { + abort(); + } + + void *bt_buffer[256]; + size_t size = backtrace(bt_buffer, 256); + String _execpath = OS::get_singleton()->get_executable_path(); + + String msg; + const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + if (proj_settings) { + msg = proj_settings->get("debug/settings/crash_handler/message"); + } + + // Tell MainLoop about the crash. This can be handled by users too in Node. + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + } + + // Dump the backtrace to stderr with a message to the user + print_error("\n================================================================"); + print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig)); + + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).is_empty()) { + print_error(vformat("Engine version: %s", VERSION_FULL_NAME)); + } else { + print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH)); + } + print_error(vformat("Dumping the backtrace. %s", msg)); + char **strings = backtrace_symbols(bt_buffer, size); + if (strings) { + void *load_addr = (void *)load_address(); + + for (size_t i = 1; i < size; i++) { + char fname[1024]; + Dl_info info; + + snprintf(fname, 1024, "%s", strings[i]); + + // Try to demangle the function name to provide a more readable one + if (dladdr(bt_buffer[i], &info) && info.dli_sname) { + if (info.dli_sname[0] == '_') { + int status; + char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status); + + if (status == 0 && demangled) { + snprintf(fname, 1024, "%s", demangled); + } + + if (demangled) { + free(demangled); + } + } + } + + String output = fname; + + // Try to get the file/line number using atos + if (bt_buffer[i] > (void *)0x0 && OS::get_singleton()) { + List args; + char str[1024]; + + args.push_back("-o"); + args.push_back(_execpath); +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) + args.push_back("-arch"); + args.push_back("x86_64"); +#elif defined(__aarch64__) + args.push_back("-arch"); + args.push_back("arm64"); +#endif + args.push_back("-l"); + snprintf(str, 1024, "%p", load_addr); + args.push_back(str); + snprintf(str, 1024, "%p", bt_buffer[i]); + args.push_back(str); + + int ret; + String out = ""; + Error err = OS::get_singleton()->execute(String("atos"), args, &out, &ret); + if (err == OK && out.substr(0, 2) != "0x") { + out = out.substr(0, out.length() - 1); + output = out; + } + } + + print_error(vformat("[%d] %s", (int64_t)i, output)); + } + + free(strings); + } + print_error("-- END OF BACKTRACE --"); + print_error("================================================================"); + + // Abort to pass the error to the OS + abort(); +} +#endif + +CrashHandler::CrashHandler() { + disabled = false; +} + +CrashHandler::~CrashHandler() { + disable(); +} + +void CrashHandler::disable() { + if (disabled) { + return; + } + +#ifdef CRASH_HANDLER_ENABLED + signal(SIGSEGV, nullptr); + signal(SIGFPE, nullptr); + signal(SIGILL, nullptr); +#endif + + disabled = true; +} + +void CrashHandler::initialize() { +#ifdef CRASH_HANDLER_ENABLED + signal(SIGSEGV, handle_crash); + signal(SIGFPE, handle_crash); + signal(SIGILL, handle_crash); +#endif +} diff --git a/platform/macos/detect.py b/platform/macos/detect.py new file mode 100644 index 0000000000..20e7afa772 --- /dev/null +++ b/platform/macos/detect.py @@ -0,0 +1,253 @@ +import os +import sys +from methods import detect_darwin_sdk_path + + +def is_active(): + return True + + +def get_name(): + return "macOS" + + +def can_build(): + if sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ): + return True + + return False + + +def get_opts(): + from SCons.Variables import BoolVariable, EnumVariable + + return [ + ("osxcross_sdk", "OSXCross SDK version", "darwin16"), + ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), + ("vulkan_sdk_path", "Path to the Vulkan SDK", ""), + EnumVariable("macports_clang", "Build using Clang from MacPorts", "no", ("no", "5.0", "devel")), + BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), + 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), + BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False), + ] + + +def get_flags(): + return [ + ("use_volk", False), + ] + + +def get_mvk_sdk_path(): + def int_or_zero(i): + try: + return int(i) + except: + return 0 + + def ver_parse(a): + return [int_or_zero(i) for i in a.split(".")] + + dirname = os.path.expanduser("~/VulkanSDK") + files = os.listdir(dirname) + + ver_file = "0.0.0.0" + ver_num = ver_parse(ver_file) + + for file in files: + if os.path.isdir(os.path.join(dirname, file)): + ver_comp = ver_parse(file) + lib_name = os.path.join( + os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a" + ) + if os.path.isfile(lib_name) and ver_comp > ver_num: + ver_num = ver_comp + ver_file = file + + return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") + + +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"]) + elif env["optimize"] == "size": # optimize for size + env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) + if env["arch"] != "arm64": + env.Prepend(CCFLAGS=["-msse2"]) + + if env["debug_symbols"]: + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "release_debug": + if env["optimize"] == "speed": # optimize for speed (default) + env.Prepend(CCFLAGS=["-O2"]) + elif env["optimize"] == "size": # optimize for size + env.Prepend(CCFLAGS=["-Os"]) + if env["debug_symbols"]: + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "debug": + env.Prepend(CCFLAGS=["-g3"]) + env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) + + ## Architecture + + # macOS no longer runs on 32-bit since 10.7 which is unsupported since 2014 + # As such, we only support 64-bit + env["bits"] = "64" + + ## Compiler configuration + + # Save this in environment for use by other modules + if "OSXCROSS_ROOT" in os.environ: + env["osxcross"] = True + + if env["arch"] == "arm64": + print("Building for macOS 11.0+, platform arm64.") + env.Append(ASFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) + else: + print("Building for macOS 10.12+, platform x86_64.") + env.Append(ASFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + + env.Append(CCFLAGS=["-fobjc-arc"]) + + 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["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" + else: + env["CC"] = "clang" + env["CXX"] = "clang++" + + detect_darwin_sdk_path("macos", env) + env.Append(CCFLAGS=["-isysroot", "$MACOS_SDK_PATH"]) + env.Append(LINKFLAGS=["-isysroot", "$MACOS_SDK_PATH"]) + + else: # osxcross build + root = os.environ.get("OSXCROSS_ROOT", 0) + 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++" + 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" + + if env["use_ubsan"] or env["use_asan"] or env["use_tsan"]: + env.extra_suffix += ".san" + env.Append(CCFLAGS=["-DSANITIZERS_ENABLED"]) + + if env["use_ubsan"]: + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) + env.Append(LINKFLAGS=["-fsanitize=undefined"]) + env.Append(CCFLAGS=["-fsanitize=nullability-return,nullability-arg,function,nullability-assign"]) + + if env["use_asan"]: + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) + env.Append(LINKFLAGS=["-fsanitize=address"]) + + if env["use_tsan"]: + env.Append(CCFLAGS=["-fsanitize=thread"]) + env.Append(LINKFLAGS=["-fsanitize=thread"]) + + if env["use_coverage"]: + env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) + env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) + + ## Dependencies + + if env["builtin_libtheora"]: + if env["arch"] != "arm64": + env["x86_libtheora_opt_gcc"] = True + + ## Flags + + env.Prepend(CPPPATH=["#platform/macos"]) + env.Append( + CPPDEFINES=["MACOS_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"]) + + if env["opengl3"]: + env.Append(CPPDEFINES=["GLES_ENABLED", "GLES3_ENABLED"]) + env.Append(CCFLAGS=["-Wno-deprecated-declarations"]) # Disable deprecation warnings + env.Append(LINKFLAGS=["-framework", "OpenGL"]) + + env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) + + if env["vulkan"]: + env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) + if not env["use_volk"]: + env.Append(LINKFLAGS=["-lMoltenVK"]) + mvk_found = False + if env["vulkan_sdk_path"] != "": + mvk_path = os.path.join( + os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" + ) + if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): + mvk_found = True + env.Append(LINKFLAGS=["-L" + mvk_path]) + if not mvk_found: + mvk_path = get_mvk_sdk_path() + if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): + mvk_found = True + env.Append(LINKFLAGS=["-L" + mvk_path]) + if not mvk_found: + print( + "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path." + ) + sys.exit(255) diff --git a/platform/macos/dir_access_macos.h b/platform/macos/dir_access_macos.h new file mode 100644 index 0000000000..2b234ad96c --- /dev/null +++ b/platform/macos/dir_access_macos.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* dir_access_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 DIR_ACCESS_MACOS_H +#define DIR_ACCESS_MACOS_H + +#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED) + +#include +#include +#include +#include + +#include "core/io/dir_access.h" +#include "drivers/unix/dir_access_unix.h" + +class DirAccessMacOS : public DirAccessUnix { +protected: + virtual String fix_unicode_name(const char *p_name) const; + + virtual int get_drive_count(); + virtual String get_drive(int p_drive); + + virtual bool is_hidden(const String &p_name); +}; + +#endif //UNIX ENABLED +#endif diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm new file mode 100644 index 0000000000..8f3906c6b8 --- /dev/null +++ b/platform/macos/dir_access_macos.mm @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* dir_access_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "dir_access_macos.h" + +#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED) + +#include + +#import +#import + +String DirAccessMacOS::fix_unicode_name(const char *p_name) const { + String fname; + NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping]; + + fname.parse_utf8([nsstr UTF8String]); + + return fname; +} + +int DirAccessMacOS::get_drive_count() { + NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; + NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; + + return [vols count]; +} + +String DirAccessMacOS::get_drive(int p_drive) { + NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; + NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; + int count = [vols count]; + + ERR_FAIL_INDEX_V(p_drive, count, ""); + + String volname; + NSString *path = [vols[p_drive] path]; + + volname.parse_utf8([path UTF8String]); + + return volname; +} + +bool DirAccessMacOS::is_hidden(const String &p_name) { + String f = get_current_dir().plus_file(p_name); + NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; + NSNumber *hidden = nil; + if (![url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:nil]) { + return DirAccessUnix::is_hidden(p_name); + } + return [hidden boolValue]; +} + +#endif //posix_enabled diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h new file mode 100644 index 0000000000..41031ec81b --- /dev/null +++ b/platform/macos/display_server_macos.h @@ -0,0 +1,406 @@ +/*************************************************************************/ +/* display_server_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_MACOS_H +#define DISPLAY_SERVER_MACOS_H + +#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(GLES3_ENABLED) +#include "gl_manager_macos_legacy.h" +#endif // GLES3_ENABLED + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/macos/vulkan_context_macos.h" +#endif // VULKAN_ENABLED + +#import +#import +#import +#import +#import + +#undef BitMap +#undef CursorShape + +class DisplayServerMacOS : public DisplayServer { + GDCLASS(DisplayServerMacOS, DisplayServer) + + _THREAD_SAFE_CLASS_ + +public: + struct KeyEvent { + WindowID window_id = INVALID_WINDOW_ID; + unsigned int macos_state = false; + bool pressed = false; + bool echo = false; + bool raw = false; + Key keycode = Key::NONE; + Key physical_keycode = Key::NONE; + uint32_t unicode = 0; + }; + + struct WindowData { + id window_delegate; + id window_object; + id window_view; + + Vector mpath; + + Point2i mouse_pos; + + Size2i min_size; + Size2i max_size; + Size2i size; + + 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; + bool exclusive = false; + HashSet transient_children; + + bool layered_window = false; + bool fullscreen = false; + bool on_top = false; + bool borderless = false; + bool resize_disabled = false; + bool no_focus = false; + bool is_popup = false; + + Rect2i parent_safe_rect; + }; + + List popup_list; + uint64_t time_since_popup = 0; + +private: +#if defined(GLES3_ENABLED) + GLManager_MacOS *gl_manager = nullptr; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextMacOS *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + String rendering_driver; + + NSMenu *apple_menu = nullptr; + NSMenu *dock_menu = nullptr; + HashMap submenu; + + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + List warp_events; + NSTimeInterval last_warp = 0; + bool ignore_warp = false; + + Vector key_event_buffer; + int key_event_pos = 0; + + id tts = nullptr; + + Point2i im_selection; + String im_text; + + CGEventSourceRef event_source; + MouseMode mouse_mode = MOUSE_MODE_VISIBLE; + MouseButton last_button_state = MouseButton::NONE; + + bool drop_events = false; + bool in_dispatch_input_event = false; + + struct LayoutInfo { + String name; + String code; + }; + Vector kbd_layouts; + int current_layout = 0; + bool keyboard_layout_dirty = true; + + WindowID last_focused_window = INVALID_WINDOW_ID; + WindowID window_id_counter = MAIN_WINDOW_ID; + float display_max_scale = 1.f; + Point2i origin; + bool displays_arrangement_dirty = true; + bool is_resizing = false; + + CursorShape cursor_shape = CURSOR_ARROW; + NSCursor *cursors[CURSOR_MAX]; + HashMap> cursors_cache; + + HashMap windows; + + const NSMenu *_get_menu_root(const String &p_menu_root) const; + NSMenu *_get_menu_root(const String &p_menu_root); + + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); + void _update_window_style(WindowData p_wd); + void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); + + void _update_displays_arrangement(); + Point2i _get_screens_origin() const; + Point2i _get_native_screen_position(int p_screen) const; + static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info); + + static void _dispatch_input_events(const Ref &p_event); + void _dispatch_input_event(const Ref &p_event); + void _push_input(const Ref &p_event); + void _process_key_events(); + void _update_keyboard_layouts(); + static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); + NSImage *_convert_to_nsimg(Ref &p_image) const; + + static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); + +public: + NSMenu *get_dock_menu() const; + void menu_callback(id p_sender); + + bool has_window(WindowID p_window) const; + WindowData &get_window(WindowID p_window); + + void send_event(NSEvent *p_event); + void send_window_event(const WindowData &p_wd, WindowEvent p_event); + void release_pressed_events(); + void get_key_modifier_state(unsigned int p_macos_state, Ref r_state) const; + void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window); + void push_to_key_event_buffer(const KeyEvent &p_event); + void update_im_text(const Point2i &p_selection, const String &p_text); + void set_last_focused_window(WindowID p_window); + bool mouse_process_popups(bool p_close = false); + void popup_open(WindowID p_window); + void popup_close(WindowID p_window); + void set_is_resizing(bool p_is_resizing); + bool get_is_resizing() const; + + void window_update(WindowID p_window); + void window_destroy(WindowID p_window); + void window_resize(WindowID p_window, int p_width, int p_height); + + 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 = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; + virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; + + virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override; + virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const 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 bool global_menu_is_item_radio_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) const override; + virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; + virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; + virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; + virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; + virtual Ref global_menu_get_item_icon(const String &p_menu_root, int p_idx) const 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_radio_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 void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; + virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; + virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; + virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; + virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; + virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref &p_icon) 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 bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual Array tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + + virtual Error dialog_show(String p_title, String p_description, Vector 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; + + bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); + virtual void warp_mouse(const Point2i &p_position) override; + virtual Point2i mouse_get_position() const override; + void mouse_set_button_state(MouseButton p_state); + virtual MouseButton 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 float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector get_window_list() const override; + + virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_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 WindowID window_get_active_popup() const override; + virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override; + virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const 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 &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_exclusive(WindowID p_window, bool p_exclusive) 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 int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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 gl_window_make_current(DisplayServer::WindowID p_window_id) override; + + virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; + + virtual Point2i ime_get_selection() const override; + virtual String ime_get_text() const override; + + void cursor_update_shape(); + virtual void cursor_set_shape(CursorShape p_shape) override; + virtual CursorShape cursor_get_shape() const override; + virtual void cursor_set_custom_image(const Ref &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 Key keyboard_get_keycode_from_physical(Key p_keycode) 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 &p_icon) override; + + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + static Vector get_rendering_drivers_func(); + + static void register_macos_driver(); + + DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerMacOS(); +}; + +#endif // DISPLAY_SERVER_MACOS_H diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm new file mode 100644 index 0000000000..07ba5d7497 --- /dev/null +++ b/platform/macos/display_server_macos.mm @@ -0,0 +1,3304 @@ +/*************************************************************************/ +/* display_server_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_macos.h" + +#include "godot_content_view.h" +#include "godot_menu_item.h" +#include "godot_window.h" +#include "godot_window_delegate.h" +#include "key_mapping_macos.h" +#include "os_macos.h" + +#include "tts_macos.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" + +#import +#import +#import +#import +#import +#import + +#if defined(GLES3_ENABLED) +#include "drivers/gles3/rasterizer_gles3.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#endif + +const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const { + const NSMenu *menu = nullptr; + 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)) { + menu = submenu[p_menu_root]; + } + } + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; + } + return menu; +} + +NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { + NSMenu *menu = nullptr; + 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()]]; + [n_menu setAutoenablesItems:NO]; + submenu[p_menu_root] = n_menu; + } + menu = submenu[p_menu_root]; + } + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; + } + return menu; +} + +DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mode, VSyncMode p_vsync_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_object setWindowID:window_id_counter]; + + 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]; + [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; + + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { + [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } + + CALayer *layer = [(NSView *)wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, 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(GLES3_ENABLED) + if (gl_manager) { + Error err = gl_manager->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 an OpenGL context"); + } +#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 = [(NSView *)wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(id, wd.size.width, wd.size.height); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(id, wd.size.width, wd.size.height); + } +#endif + + return id; +} + +void DisplayServerMacOS::_update_window_style(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. + [(NSWindow *)p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; + [(NSWindow *)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.fullscreen) { + [(NSWindow *)p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [(NSWindow *)p_wd.window_object setLevel:NSNormalWindowLevel]; + } + [(NSWindow *)p_wd.window_object setHidesOnDeactivate:NO]; + } +} + +void DisplayServerMacOS::_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::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 = [(NSView *)wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor clearColor].CGColor]; + [layer setOpaque:NO]; + } +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, true); + } +#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 = [(NSView *)wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor]; + [layer setOpaque:YES]; + } +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, false); + } +#endif + wd.layered_window = false; + } + 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 DisplayServerMacOS::_update_displays_arrangement() { + 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; +} + +Point2i DisplayServerMacOS::_get_screens_origin() const { + // 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. + + if (displays_arrangement_dirty) { + const_cast(this)->_update_displays_arrangement(); + } + + return origin; +} + +Point2i DisplayServerMacOS::_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(); +} + +void DisplayServerMacOS::_displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->displays_arrangement_dirty = true; + } +} + +void DisplayServerMacOS::_dispatch_input_events(const Ref &p_event) { + ((DisplayServerMacOS *)(get_singleton()))->_dispatch_input_event(p_event); +} + +void DisplayServerMacOS::_dispatch_input_event(const Ref &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; + + { + List::Element *E = popup_list.back(); + if (E && Object::cast_to(*p_event)) { + // Redirect keyboard input to active popup. + if (windows.has(E->get())) { + Callable callable = windows[E->get()].input_event_callback; + if (callable.is_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); + } + } + in_dispatch_input_event = false; + return; + } + } + + Ref 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_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); + } + } + } else { + // Send to all windows. + for (KeyValue &E : windows) { + Callable callable = E.value.input_event_callback; + if (callable.is_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); + } + } + } + in_dispatch_input_event = false; + } +} + +void DisplayServerMacOS::_push_input(const Ref &p_event) { + Ref ev = p_event; + Input::get_singleton()->parse_input_event(ev); +} + +void DisplayServerMacOS::_process_key_events() { + Ref 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.instantiate(); + + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.macos_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); + k->set_unicode(ke.unicode); + + _push_input(k); + } else { + // IME input. + if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { + k.instantiate(); + + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.macos_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(Key::NONE); + k->set_physical_keycode(Key::NONE); + k->set_unicode(ke.unicode); + + _push_input(k); + } + if (ke.keycode != Key::NONE) { + k.instantiate(); + + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.macos_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); + + if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { + k->set_unicode(key_event_buffer[i + 1].unicode); + } + + _push_input(k); + } + } + } + + key_event_pos = 0; +} + +void DisplayServerMacOS::_update_keyboard_layouts() { + kbd_layouts.clear(); + current_layout = 0; + + TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); + NSString *cur_name = (__bridge NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); + CFRelease(cur_source); + + // Enum IME layouts. + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); + + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge 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; + } + } + + // Enum plain keyboard layouts. + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); + + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge 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; + } + } + + keyboard_layout_dirty = false; +} + +void DisplayServerMacOS::_keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->keyboard_layout_dirty = true; + } +} + +NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref &p_image) const { + p_image->convert(Image::FORMAT_RGBA8); + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:p_image->get_width() + pixelsHigh:p_image->get_height() + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:int(p_image->get_width()) * 4 + bitsPerPixel:32]; + ERR_FAIL_COND_V(imgrep == nil, nil); + uint8_t *pixels = [imgrep bitmapData]; + + int len = p_image->get_width() * p_image->get_height(); + const uint8_t *r = p_image->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(p_image->get_width(), p_image->get_height())]; + ERR_FAIL_COND_V(nsimg == nil, nil); + [nsimg addRepresentation:imgrep]; + return nsimg; +} + +NSCursor *DisplayServerMacOS::_cursor_from_selector(SEL p_selector, SEL p_fallback) { + if ([NSCursor respondsToSelector:p_selector]) { + id object = [NSCursor performSelector:p_selector]; + if ([object isKindOfClass:[NSCursor class]]) { + return object; + } + } + if (p_fallback) { + // Fallback should be a reasonable default, no need to check. + return [NSCursor performSelector:p_fallback]; + } + return [NSCursor arrowCursor]; +} + +NSMenu *DisplayServerMacOS::get_dock_menu() const { + return dock_menu; +} + +void DisplayServerMacOS::menu_callback(id p_sender) { + if (![p_sender representedObject]) { + return; + } + + GodotMenuItem *value = [p_sender representedObject]; + + if (value) { + if (value->max_states > 0) { + value->state++; + if (value->state >= value->max_states) { + value->state = 0; + } + } + + if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { + if ([p_sender state] == NSControlStateValueOff) { + [p_sender setState:NSControlStateValueOn]; + } else { + [p_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); + } + } +} + +bool DisplayServerMacOS::has_window(WindowID p_window) const { + return windows.has(p_window); +} + +DisplayServerMacOS::WindowData &DisplayServerMacOS::get_window(WindowID p_window) { + return windows[p_window]; +} + +void DisplayServerMacOS::send_event(NSEvent *p_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 ([p_event type] == NSEventTypeKeyDown) { + if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { + Ref k; + k.instantiate(); + + get_key_modifier_state([p_event modifierFlags], k); + k->set_window_id(DisplayServerMacOS::INVALID_WINDOW_ID); + k->set_pressed(true); + k->set_keycode(Key::PERIOD); + k->set_physical_keycode(Key::PERIOD); + k->set_echo([p_event isARepeat]); + + Input::get_singleton()->parse_input_event(k); + } + } +} + +void DisplayServerMacOS::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); + } +} + +void DisplayServerMacOS::release_pressed_events() { + _THREAD_SAFE_METHOD_ + if (Input::get_singleton()) { + Input::get_singleton()->release_pressed_events(); + } +} + +void DisplayServerMacOS::get_key_modifier_state(unsigned int p_macos_state, Ref r_state) const { + r_state->set_shift_pressed((p_macos_state & NSEventModifierFlagShift)); + r_state->set_ctrl_pressed((p_macos_state & NSEventModifierFlagControl)); + r_state->set_alt_pressed((p_macos_state & NSEventModifierFlagOption)); + r_state->set_meta_pressed((p_macos_state & NSEventModifierFlagCommand)); +} + +void DisplayServerMacOS::update_mouse_pos(DisplayServerMacOS::WindowData &p_wd, NSPoint p_location_in_window) { + const NSRect content_rect = [p_wd.window_view frame]; + const float scale = screen_get_max_scale(); + p_wd.mouse_pos.x = p_location_in_window.x * scale; + p_wd.mouse_pos.y = (content_rect.size.height - p_location_in_window.y) * scale; + Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); +} + +void DisplayServerMacOS::push_to_key_event_buffer(const DisplayServerMacOS::KeyEvent &p_event) { + if (key_event_pos >= key_event_buffer.size()) { + key_event_buffer.resize(1 + key_event_pos); + } + key_event_buffer.write[key_event_pos++] = p_event; +} + +void DisplayServerMacOS::update_im_text(const Point2i &p_selection, const String &p_text) { + im_selection = p_selection; + im_text = p_text; + + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); +} + +void DisplayServerMacOS::set_last_focused_window(WindowID p_window) { + last_focused_window = p_window; +} + +void DisplayServerMacOS::set_is_resizing(bool p_is_resizing) { + is_resizing = p_is_resizing; +} + +bool DisplayServerMacOS::get_is_resizing() const { + return is_resizing; +} + +void DisplayServerMacOS::window_update(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_update(p_window); + } +#endif +} + +void DisplayServerMacOS::window_destroy(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_destroy(p_window); + } +#endif +#ifdef VULKAN_ENABLED + if (context_vulkan) { + context_vulkan->window_destroy(p_window); + } +#endif + windows.erase(p_window); +} + +void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(p_window, p_width, p_height); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(p_window, p_width, p_height); + } +#endif +} + +bool DisplayServerMacOS::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_IME: + case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_HIDPI: + case FEATURE_ICON: + case FEATURE_NATIVE_ICON: + //case FEATURE_KEEP_SCREEN_ON: + case FEATURE_SWAP_BUFFERS: + case FEATURE_TEXT_TO_SPEECH: + return true; + default: { + } + } + return false; +} + +String DisplayServerMacOS::get_name() const { + return "macOS"; +} + +void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = p_max_states; + obj->state = p_default_state; + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { + _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; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + } + [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; + [menu setSubmenu:sub_menu forItem:menu_item]; + } +} + +void DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if (p_index != -1) { + [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; + } else { + [menu addItem:[NSMenuItem separatorItem]]; + } + } +} + +int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + } + + return -1; +} + +int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + for (NSInteger i = 0; i < [menu numberOfItems]; i++) { + const NSMenuItem *menu_item = [menu itemAtIndex:i]; + if (menu_item) { + const GodotMenuItem *obj = [menu_item representedObject]; + if (obj && obj->meta == p_tag) { + return i; + } + } + } + } + + return -1; +} + +bool DisplayServerMacOS::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 DisplayServerMacOS::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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX; + } + } + } + return false; +} + +bool DisplayServerMacOS::global_menu_is_item_radio_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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON; + } + } + } + return false; +} + +Callable DisplayServerMacOS::global_menu_get_item_callback(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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->callback; + } + } + } + return Callable(); +} + +Variant DisplayServerMacOS::global_menu_get_item_tag(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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->meta; + } + } + } + return Variant(); +} + +String DisplayServerMacOS::global_menu_get_item_text(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 String::utf8([[menu_item title] UTF8String]); + } + } + return String(); +} + +String DisplayServerMacOS::global_menu_get_item_submenu(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) { + const NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu) { + for (const KeyValue &E : submenu) { + if (E.value == sub_menu) { + return E.key; + } + } + } + } + } + return String(); +} + +Key DisplayServerMacOS::global_menu_get_item_accelerator(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) { + String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); + Key keycode = find_keycode(ret); + NSUInteger mask = [menu_item keyEquivalentModifierMask]; + if (mask & NSEventModifierFlagControl) { + keycode |= KeyModifierMask::CTRL; + } + if (mask & NSEventModifierFlagOption) { + keycode |= KeyModifierMask::ALT; + } + if (mask & NSEventModifierFlagShift) { + keycode |= KeyModifierMask::SHIFT; + } + if (mask & NSEventModifierFlagCommand) { + keycode |= KeyModifierMask::META; + } + if (mask & NSEventModifierFlagNumericPad) { + keycode |= KeyModifierMask::KPAD; + } + return keycode; + } + } + return Key::NONE; +} + +bool DisplayServerMacOS::global_menu_is_item_disabled(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 isEnabled]; + } + } + return false; +} + +String DisplayServerMacOS::global_menu_get_item_tooltip(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 String::utf8([[menu_item toolTip] UTF8String]); + } + } + return String(); +} + +int DisplayServerMacOS::global_menu_get_item_state(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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->state; + } + } + } + return 0; +} + +int DisplayServerMacOS::global_menu_get_item_max_states(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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->max_states; + } + } + } + return 0; +} + +Ref DisplayServerMacOS::global_menu_get_item_icon(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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + if (obj->img.is_valid()) { + return ImageTexture::create_from_image(obj->img); + } + } + } + } + return Ref(); +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; + } + } +} + +void DisplayServerMacOS::global_menu_set_item_radio_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) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE; + } + } +} + +void DisplayServerMacOS::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) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->callback = p_callback; + } + } +} + +void DisplayServerMacOS::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) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->meta = p_tag; + } + } +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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]; + } + } +} + +void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) { + _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 setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; + String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); + [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + } +} + +void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) { + _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 setEnabled:(!p_disabled)]; + } + } +} + +void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { + _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 setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; + } + } +} + +void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) { + _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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->state = p_state; + } + } + } +} + +void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) { + _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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->max_states = p_max_states; + } + } + } +} + +void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref &p_icon) { + _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) { + GodotMenuItem *obj = [menu_item representedObject]; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } else { + obj->img = Ref(); + [menu_item setImage:nil]; + } + } + } +} + +int DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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]; + } + } +} + +bool DisplayServerMacOS::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerMacOS::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return [tts isPaused]; +} + +Array DisplayServerMacOS::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, Array()); + return [tts getVoices]; +} + +void DisplayServerMacOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!tts); + [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; +} + +void DisplayServerMacOS::tts_pause() { + ERR_FAIL_COND(!tts); + [tts pauseSpeaking]; +} + +void DisplayServerMacOS::tts_resume() { + ERR_FAIL_COND(!tts); + [tts resumeSpeaking]; +} + +void DisplayServerMacOS::tts_stop() { + ERR_FAIL_COND(!tts); + [tts stopSpeaking]; +} + +Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vector 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); + } + + return OK; +} + +Error DisplayServerMacOS::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]; + + String ret; + ret.parse_utf8([[input stringValue] UTF8String]); + + 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); + } + + return OK; +} + +void DisplayServerMacOS::mouse_set_mode(MouseMode p_mode) { + _THREAD_SAFE_METHOD_ + + if (p_mode == mouse_mode) { + return; + } + + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[window_id]; + 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); + [wd.window_object setMovable:NO]; + const NSRect contentRect = [wd.window_view frame]; + NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0); + NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; + CGWarpMouseCursorPosition(lMouseWarpPos); + } else if (p_mode == MOUSE_MODE_HIDDEN) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } + [wd.window_object setMovable:YES]; + CGAssociateMouseAndMouseCursorPosition(true); + } else if (p_mode == MOUSE_MODE_CONFINED) { + CGDisplayShowCursor(kCGDirectMainDisplay); + [wd.window_object setMovable:NO]; + CGAssociateMouseAndMouseCursorPosition(false); + } else if (p_mode == MOUSE_MODE_CONFINED_HIDDEN) { + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } + [wd.window_object setMovable:NO]; + CGAssociateMouseAndMouseCursorPosition(false); + } else { // MOUSE_MODE_VISIBLE + CGDisplayShowCursor(kCGDirectMainDisplay); + [wd.window_object setMovable:YES]; + CGAssociateMouseAndMouseCursorPosition(true); + } + + last_warp = [[NSProcessInfo processInfo] systemUptime]; + ignore_warp = true; + warp_events.clear(); + mouse_mode = p_mode; + + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + cursor_update_shape(); + } +} + +DisplayServer::MouseMode DisplayServerMacOS::mouse_get_mode() const { + return mouse_mode; +} + +bool DisplayServerMacOS::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp) { + _THREAD_SAFE_METHOD_ + + if (ignore_warp) { + // Discard late events, before warp. + if (p_timestamp < last_warp) { + return true; + } + ignore_warp = false; + return true; + } + + if (mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { + // Discard late events. + if (p_timestamp < last_warp) { + return true; + } + + // Warp affects next event delta, subtract previous warp deltas. + List::Element *F = warp_events.front(); + while (F) { + if (F->get().timestamp < p_timestamp) { + List::Element *E = F; + r_delta.x -= E->get().delta.x; + r_delta.y -= E->get().delta.y; + F = F->next(); + warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta. + NSRect frame = [p_wd.window_object frame]; + NSPoint conf_pos = r_mpos; + conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height); + r_delta.x = conf_pos.x - r_mpos.x; + r_delta.y = r_mpos.y - conf_pos.y; + r_mpos = conf_pos; + + // Move mouse cursor. + NSRect point_in_window_rect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[p_wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data. + last_warp = [[NSProcessInfo processInfo] systemUptime]; + + DisplayServerMacOS::WarpEvent ev; + ev.timestamp = last_warp; + ev.delta = r_delta; + warp_events.push_back(ev); + } + + return false; +} + +void DisplayServerMacOS::warp_mouse(const Point2i &p_position) { + _THREAD_SAFE_METHOD_ + + if (mouse_mode != MOUSE_MODE_CAPTURED) { + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[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_position.x / scale, contentRect.size.height - (p_position.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 && mouse_mode != MOUSE_MODE_CONFINED_HIDDEN) { + CGAssociateMouseAndMouseCursorPosition(true); + } + } +} + +Point2i DisplayServerMacOS::mouse_get_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)) { + Vector2i pos = Vector2i((int)mouse_pos.x, (int)mouse_pos.y); + pos *= scale; + pos -= _get_screens_origin(); + pos.y *= -1; + return pos; + } + } + return Vector2i(); +} + +void DisplayServerMacOS::mouse_set_button_state(MouseButton p_state) { + last_button_state = p_state; +} + +MouseButton DisplayServerMacOS::mouse_get_button_state() const { + return last_button_state; +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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]; + + String ret; + ret.parse_utf8([string UTF8String]); + return ret; +} + +int DisplayServerMacOS::get_screen_count() const { + _THREAD_SAFE_METHOD_ + + NSArray *screenArray = [NSScreen screens]; + return [screenArray count]; +} + +Point2i DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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]; + + const NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + const CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + float scale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; + + float den2 = (displayPhysicalSize.width / 25.4f) * (displayPhysicalSize.width / 25.4f) + (displayPhysicalSize.height / 25.4f) * (displayPhysicalSize.height / 25.4f); + if (den2 > 0.0f) { + return ceil(sqrt(displayPixelSize.width * displayPixelSize.width + displayPixelSize.height * displayPixelSize.height) / sqrt(den2) * scale); + } + } + + return 72; +} + +float DisplayServerMacOS::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 DisplayServerMacOS::screen_get_max_scale() const { + _THREAD_SAFE_METHOD_ + + // Note: Do not update max display scale on screen configuration change, existing editor windows can't be rescaled on the fly. + return display_max_scale; +} + +Rect2i DisplayServerMacOS::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(); +} + +float DisplayServerMacOS::screen_get_refresh_rate(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]; + const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); + return (float)displayRefreshRate; + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + +Vector DisplayServerMacOS::get_window_list() const { + _THREAD_SAFE_METHOD_ + + Vector ret; + for (const KeyValue &E : windows) { + ret.push_back(E.key); + } + return ret; +} + +DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + WindowID id = _create_window(p_mode, p_vsync_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 DisplayServerMacOS::show_window(WindowID p_id) { + WindowData &wd = windows[p_id]; + + popup_open(p_id); + if (wd.no_focus || wd.is_popup) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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; +} + +void DisplayServerMacOS::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 DisplayServerMacOS::window_set_mouse_passthrough(const Vector &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.mpath = p_region; +} + +int DisplayServerMacOS::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 DisplayServerMacOS::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]; + + if (window_get_current_screen(p_window) == p_screen) { + return; + } + + bool was_fullscreen = false; + if (wd.fullscreen) { + // Temporary exit fullscreen mode to move window. + [wd.window_object toggleFullScreen:nil]; + was_fullscreen = true; + } + + 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); + + if (was_fullscreen) { + // Re-enter fullscreen mode. + [wd.window_object toggleFullScreen:nil]; + } +} + +void DisplayServerMacOS::window_set_exclusive(WindowID p_window, bool p_exclusive) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + if (wd.exclusive != p_exclusive) { + wd.exclusive = p_exclusive; + if (wd.transient_parent != INVALID_WINDOW_ID) { + WindowData &wd_parent = windows[wd.transient_parent]; + if (wd.exclusive) { + ERR_FAIL_COND_MSG([[wd_parent.window_object childWindows] count] > 0, "Transient parent has another exclusive child."); + [wd_parent.window_object addChildWindow:wd.window_object ordered:NSWindowAbove]; + } else { + [wd_parent.window_object removeChildWindow:wd.window_object]; + } + } + } +} + +Point2i DisplayServerMacOS::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]; + + // Use content rect position (without titlebar / window border). + const NSRect contentRect = [wd.window_view frame]; + const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; + 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 DisplayServerMacOS::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(); + + // Remove titlebar / window border size. + const NSRect contentRect = [wd.window_view frame]; + const NSRect windowRect = [wd.window_object frame]; + const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; + Point2i offset; + offset.x = (nsrect.origin.x - windowRect.origin.x); + offset.y = (nsrect.origin.y + nsrect.size.height); + offset.y -= (windowRect.origin.y + windowRect.size.height); + + [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x - offset.x, position.y - offset.y)]; + + _update_window_style(wd); + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); +} + +void DisplayServerMacOS::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_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + + if (wd_window.exclusive) { + [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_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + + if (wd_window.exclusive) { + [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove]; + } + } +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::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_style(wd); +} + +Size2i DisplayServerMacOS::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 DisplayServerMacOS::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(); +} + +void DisplayServerMacOS::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_EXCLUSIVE_FULLSCREEN: + case WINDOW_MODE_FULLSCREEN: { + [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; + _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_EXCLUSIVE_FULLSCREEN: + case WINDOW_MODE_FULLSCREEN: { + _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 DisplayServerMacOS::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; +} + +bool DisplayServerMacOS::window_is_maximize_allowed(WindowID p_window) const { + return true; +} + +void DisplayServerMacOS::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 fullscreen. + 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 { + _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_style(wd); + if ([wd.window_object isVisible]) { + if (wd.no_focus || wd.is_popup) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } + } + } break; + case WINDOW_FLAG_ALWAYS_ON_TOP: { + wd.on_top = p_enabled; + if (wd.fullscreen) { + return; + } + if (p_enabled) { + [(NSWindow *)wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; + } + } break; + case WINDOW_FLAG_TRANSPARENT: { + 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; + case WINDOW_FLAG_POPUP: { + ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); + ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); + wd.is_popup = p_enabled; + } break; + default: { + } + } +} + +bool DisplayServerMacOS::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: { + if (wd.fullscreen) { + return wd.on_top; + } else { + return [(NSWindow *)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; + case WINDOW_FLAG_POPUP: { + return wd.is_popup; + } break; + default: { + } + } + + return false; +} + +void DisplayServerMacOS::window_request_attention(WindowID p_window) { + // It's app global, ignore window id. + [NSApp requestUserAttention:NSCriticalRequest]; +} + +void DisplayServerMacOS::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.is_popup) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } +} + +bool DisplayServerMacOS::window_can_draw(WindowID p_window) const { + return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; +} + +bool DisplayServerMacOS::can_any_window_draw() const { + _THREAD_SAFE_METHOD_ + + for (const KeyValue &E : windows) { + if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { + return true; + } + } + return false; +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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; +} + +DisplayServer::WindowID DisplayServerMacOS::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 (const KeyValue &E : windows) { + if ([E.value.window_object windowNumber] == wnum) { + return E.key; + } + } + return INVALID_WINDOW_ID; +} + +int64_t DisplayServerMacOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].window_object; + } + case WINDOW_VIEW: { + return (int64_t)windows[p_window].window_view; + } + default: { + return 0; + } + } +} + +void DisplayServerMacOS::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 DisplayServerMacOS::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 DisplayServerMacOS::gl_window_make_current(DisplayServer::WindowID p_window_id) { +#if defined(GLES3_ENABLED) + gl_manager->window_make_current(p_window_id); +#endif +} + +void DisplayServerMacOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->set_use_vsync(p_vsync_mode); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + } +#endif +} + +DisplayServer::VSyncMode DisplayServerMacOS::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + return context_vulkan->get_vsync_mode(p_window); + } +#endif + return DisplayServer::VSYNC_ENABLED; +} + +Point2i DisplayServerMacOS::ime_get_selection() const { + return im_selection; +} + +String DisplayServerMacOS::ime_get_text() const { + return im_text; +} + +void DisplayServerMacOS::cursor_update_shape() { + _THREAD_SAFE_METHOD_ + + if (cursors[cursor_shape] != nullptr) { + [cursors[cursor_shape] set]; + } else { + switch (cursor_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: + [_cursor_from_selector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; + break; + case CURSOR_HSIZE: + [_cursor_from_selector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; + break; + case CURSOR_BDIAGSIZE: + [_cursor_from_selector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; + break; + case CURSOR_FDIAGSIZE: + [_cursor_from_selector(@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: + [_cursor_from_selector(@selector(_helpCursor)) set]; + break; + default: { + } + } + } +} + +void DisplayServerMacOS::cursor_set_shape(CursorShape p_shape) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (cursor_shape == p_shape) { + return; + } + + cursor_shape = p_shape; + + if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { + return; + } + + cursor_update_shape(); +} + +DisplayServerMacOS::CursorShape DisplayServerMacOS::cursor_get_shape() const { + return cursor_shape; +} + +void DisplayServerMacOS::cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + _THREAD_SAFE_METHOD_ + + if (p_cursor.is_valid()) { + HashMap>::Iterator cursor_c = cursors_cache.find(p_shape); + + if (cursor_c) { + if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) { + cursor_set_shape(p_shape); + return; + } + cursors_cache.erase(p_shape); + } + + Ref texture = p_cursor; + Ref atlas_texture = p_cursor; + Ref image; + Size2 texture_size; + Rect2 atlas_rect; + + if (texture.is_valid()) { + image = texture->get_image(); + } + + 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_image(); + + ERR_FAIL_COND(!image.is_valid()); + + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nullptr + 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] = cursor; + + Vector 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]; + } + } + } else { + // Reset to default system cursor. + if (cursors[p_shape] != nullptr) { + cursors[p_shape] = nullptr; + } + + cursor_update_shape(); + + cursors_cache.erase(p_shape); + } +} + +bool DisplayServerMacOS::get_swap_cancel_ok() { + return false; +} + +int DisplayServerMacOS::keyboard_get_layout_count() const { + if (keyboard_layout_dirty) { + const_cast(this)->_update_keyboard_layouts(); + } + return kbd_layouts.size(); +} + +void DisplayServerMacOS::keyboard_set_current_layout(int p_index) { + if (keyboard_layout_dirty) { + const_cast(this)->_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 = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i]); + break; + } + } + + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((__bridge TISInputSourceRef)[list_ime objectAtIndex:i]); + break; + } + } +} + +int DisplayServerMacOS::keyboard_get_current_layout() const { + if (keyboard_layout_dirty) { + const_cast(this)->_update_keyboard_layouts(); + } + + return current_layout; +} + +String DisplayServerMacOS::keyboard_get_layout_language(int p_index) const { + if (keyboard_layout_dirty) { + const_cast(this)->_update_keyboard_layouts(); + } + + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].code; +} + +String DisplayServerMacOS::keyboard_get_layout_name(int p_index) const { + if (keyboard_layout_dirty) { + const_cast(this)->_update_keyboard_layouts(); + } + + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].name; +} + +Key DisplayServerMacOS::keyboard_get_keycode_from_physical(Key p_keycode) const { + if (p_keycode == Key::PAUSE) { + return p_keycode; + } + + Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; + Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; + unsigned int macos_keycode = KeyMappingMacOS::unmap_key((Key)keycode_no_mod); + return (Key)(KeyMappingMacOS::remap_key(macos_keycode, 0) | modifiers); +} + +void DisplayServerMacOS::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_buffered_events(); + } + + for (KeyValue &E : windows) { + WindowData &wd = E.value; + if (wd.mpath.size() > 0) { + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + if (Geometry2D::is_point_in_polygon(wd.mouse_pos, 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]; + } + } + } +} + +void DisplayServerMacOS::force_process_and_drop_events() { + _THREAD_SAFE_METHOD_ + + drop_events = true; + process_events(); + drop_events = false; +} + +void DisplayServerMacOS::release_rendering_thread() { +} + +void DisplayServerMacOS::make_rendering_thread() { +} + +void DisplayServerMacOS::swap_buffers() { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->swap_buffers(); + } +#endif +} + +void DisplayServerMacOS::set_native_icon(const String &p_filename) { + _THREAD_SAFE_METHOD_ + + Ref f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND(f.is_null()); + + Vector data; + uint64_t len = f->get_length(); + data.resize(len); + f->get_buffer((uint8_t *)&data.write[0], len); + + NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len]; + ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); + + NSImage *icon = [[NSImage alloc] initWithData:icon_data]; + ERR_FAIL_COND_MSG(!icon, "Error loading icon."); + + [NSApp setApplicationIconImage:icon]; +} + +void DisplayServerMacOS::set_icon(const Ref &p_icon) { + _THREAD_SAFE_METHOD_ + + Ref img = p_icon; + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nullptr + 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]; +} + +DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan or OpenGL versions.", "Unable to initialize Video driver"); + } + return ds; +} + +Vector DisplayServerMacOS::get_rendering_drivers_func() { + Vector drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(GLES3_ENABLED) + drivers.push_back("opengl3"); +#endif + + return drivers; +} + +void DisplayServerMacOS::register_macos_driver() { + register_create_function("macos", create_func, get_rendering_drivers_func); +} + +DisplayServer::WindowID DisplayServerMacOS::window_get_active_popup() const { + const List::Element *E = popup_list.back(); + if (E) { + return E->get(); + } else { + return INVALID_WINDOW_ID; + } +} + +void DisplayServerMacOS::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.parent_safe_rect = p_rect; +} + +Rect2i DisplayServerMacOS::window_get_popup_safe_rect(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Rect2i()); + const WindowData &wd = windows[p_window]; + return wd.parent_safe_rect; +} + +void DisplayServerMacOS::popup_open(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[p_window]; + if (wd.is_popup) { + bool was_empty = popup_list.is_empty(); + // Find current popup parent, or root popup if new window is not transient. + List::Element *C = nullptr; + List::Element *E = popup_list.back(); + while (E) { + if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { + C = E; + E = E->prev(); + } else { + break; + } + } + if (C) { + send_window_event(windows[C->get()], DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + } + + if (was_empty && popup_list.is_empty()) { + // Inform OS that popup was opened, to close other native popups. + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; + } + time_since_popup = OS::get_singleton()->get_ticks_msec(); + popup_list.push_back(p_window); + } +} + +void DisplayServerMacOS::popup_close(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + bool was_empty = popup_list.is_empty(); + List::Element *E = popup_list.find(p_window); + while (E) { + List::Element *F = E->next(); + WindowID win_id = E->get(); + popup_list.erase(E); + + send_window_event(windows[win_id], DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + E = F; + } + if (!was_empty && popup_list.is_empty()) { + // Inform OS that all popups are closed. + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; + } +} + +bool DisplayServerMacOS::mouse_process_popups(bool p_close) { + _THREAD_SAFE_METHOD_ + + bool was_empty = popup_list.is_empty(); + bool closed = false; + if (p_close) { + // Close all popups. + List::Element *E = popup_list.front(); + if (E) { + send_window_event(windows[E->get()], DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + closed = true; + } + if (!was_empty) { + // Inform OS that all popups are closed. + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; + } + } else { + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; + if (delta < 250) { + return false; + } + + Point2i pos = mouse_get_position(); + List::Element *C = nullptr; + List::Element *E = popup_list.back(); + // Find top popup to close. + while (E) { + // Popup window area. + Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); + // Area of the parent window, which responsible for opening sub-menu. + Rect2i safe_rect = window_get_popup_safe_rect(E->get()); + if (win_rect.has_point(pos)) { + break; + } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { + break; + } else { + C = E; + E = E->prev(); + } + } + if (C) { + send_window_event(windows[C->get()], DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + closed = true; + } + if (!was_empty && popup_list.is_empty()) { + // Inform OS that all popups are closed. + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; + } + } + return closed; +} + +DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_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; + + memset(cursors, 0, sizeof(cursors)); + + event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + ERR_FAIL_COND(!event_source); + + CGEventSourceSetLocalEventsSuppressionInterval(event_source, 0.0); + + int screen_count = get_screen_count(); + for (int i = 0; i < screen_count; i++) { + display_max_scale = fmax(display_max_scale, screen_get_scale(i)); + } + + // Register to be notified on keyboard layout changes. + CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), + nullptr, _keyboard_layout_changed, + kTISNotifySelectedKeyboardInputSourceChanged, nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); + + // Register to be notified on displays arrangement changes. + CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); + + // Init TTS + tts = [[TTS_MacOS alloc] init]; + + 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"]; + [dock_menu setAutoenablesItems:NO]; + + // Setup Apple menu. + apple_menu = [[NSMenu alloc] initWithTitle:@""]; + title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; + [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; + [apple_menu setAutoenablesItems:NO]; + + [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]; + + [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"]; + + // Add items to the menu bar. + NSMenu *main_menu = [NSApp mainMenu]; + menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + [main_menu setSubmenu:apple_menu forItem:menu_item]; + [main_menu setAutoenablesItems:NO]; + + //!!!!!!!!!!!!!!!!!!!!!!!!!! + //TODO - do Vulkan and OpenGL support checks, driver selection and fallback + rendering_driver = p_rendering_driver; + +#if defined(GLES3_ENABLED) + if (rendering_driver == "opengl3") { + GLManager_MacOS::ContextType opengl_api_type = GLManager_MacOS::GLES_3_0_COMPATIBLE; + gl_manager = memnew(GLManager_MacOS(opengl_api_type)); + if (gl_manager->initialize() != OK) { + memdelete(gl_manager); + gl_manager = nullptr; + r_error = ERR_UNAVAILABLE; + ERR_FAIL_MSG("Could not initialize OpenGL"); + return; + } + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + context_vulkan = memnew(VulkanContextMacOS); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + 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, p_vsync_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(GLES3_ENABLED) + if (rendering_driver == "opengl3") { + RasterizerGLES3::make_current(); + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RendererCompositorRD::make_current(); + } +#endif +} + +DisplayServerMacOS::~DisplayServerMacOS() { + // Destroy all windows. + for (HashMap::Iterator E = windows.begin(); E;) { + HashMap::Iterator F = E; + ++E; + [F->value.window_object setContentView:nil]; + [F->value.window_object close]; + } + + // Destroy drivers. +#if defined(GLES3_ENABLED) + if (gl_manager) { + memdelete(gl_manager); + gl_manager = nullptr; + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + rendering_device_vulkan = nullptr; + } + + if (context_vulkan) { + memdelete(context_vulkan); + context_vulkan = nullptr; + } +#endif + + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr); + CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr); + + cursors_cache.clear(); +} diff --git a/platform/macos/export/codesign.cpp b/platform/macos/export/codesign.cpp new file mode 100644 index 0000000000..fd044c00cc --- /dev/null +++ b/platform/macos/export/codesign.cpp @@ -0,0 +1,1564 @@ +/*************************************************************************/ +/* codesign.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "codesign.h" + +#include "lipo.h" +#include "macho.h" +#include "plist.h" + +#include "core/os/os.h" +#include "editor/editor_paths.h" +#include "editor/editor_settings.h" + +#include "modules/modules_enabled.gen.h" // For regex. + +#include + +#ifdef MODULE_REGEX_ENABLED + +/*************************************************************************/ +/* CodeSignCodeResources */ +/*************************************************************************/ + +String CodeSignCodeResources::hash_sha1_base64(const String &p_path) { + Ref fa = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + + CryptoCore::SHA1Context ctx; + ctx.start(); + + unsigned char step[4096]; + while (true) { + uint64_t br = fa->get_buffer(step, 4096); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + unsigned char hash[0x14]; + ctx.finish(hash); + + return CryptoCore::b64_encode_str(hash, 0x14); +} + +String CodeSignCodeResources::hash_sha256_base64(const String &p_path) { + Ref fa = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + + CryptoCore::SHA256Context ctx; + ctx.start(); + + unsigned char step[4096]; + while (true) { + uint64_t br = fa->get_buffer(step, 4096); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + unsigned char hash[0x20]; + ctx.finish(hash); + + return CryptoCore::b64_encode_str(hash, 0x20); +} + +void CodeSignCodeResources::add_rule1(const String &p_rule, const String &p_key, int p_weight, bool p_store) { + rules1.push_back(CRRule(p_rule, p_key, p_weight, p_store)); +} + +void CodeSignCodeResources::add_rule2(const String &p_rule, const String &p_key, int p_weight, bool p_store) { + rules2.push_back(CRRule(p_rule, p_key, p_weight, p_store)); +} + +CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules1(const String &p_path) const { + CRMatch found = CRMatch::CR_MATCH_NO; + int weight = 0; + for (int i = 0; i < rules1.size(); i++) { + RegEx regex = RegEx(rules1[i].file_pattern); + if (regex.search(p_path).is_valid()) { + if (rules1[i].key == "omit") { + return CRMatch::CR_MATCH_NO; + } else if (rules1[i].key == "nested") { + if (weight <= rules1[i].weight) { + found = CRMatch::CR_MATCH_NESTED; + weight = rules1[i].weight; + } + } else if (rules1[i].key == "optional") { + if (weight <= rules1[i].weight) { + found = CRMatch::CR_MATCH_OPTIONAL; + weight = rules1[i].weight; + } + } else { + if (weight <= rules1[i].weight) { + found = CRMatch::CR_MATCH_YES; + weight = rules1[i].weight; + } + } + } + } + return found; +} + +CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules2(const String &p_path) const { + CRMatch found = CRMatch::CR_MATCH_NO; + int weight = 0; + for (int i = 0; i < rules2.size(); i++) { + RegEx regex = RegEx(rules2[i].file_pattern); + if (regex.search(p_path).is_valid()) { + if (rules2[i].key == "omit") { + return CRMatch::CR_MATCH_NO; + } else if (rules2[i].key == "nested") { + if (weight <= rules2[i].weight) { + found = CRMatch::CR_MATCH_NESTED; + weight = rules2[i].weight; + } + } else if (rules2[i].key == "optional") { + if (weight <= rules2[i].weight) { + found = CRMatch::CR_MATCH_OPTIONAL; + weight = rules2[i].weight; + } + } else { + if (weight <= rules2[i].weight) { + found = CRMatch::CR_MATCH_YES; + weight = rules2[i].weight; + } + } + } + } + return found; +} + +bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path) { + CRMatch found = match_rules1(p_path); + if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { + return true; // No match. + } + + CRFile f; + f.name = p_path; + f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); + f.nested = false; + f.hash = hash_sha1_base64(p_root.plus_file(p_path)); + print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash)); + + files1.push_back(f); + return true; +} + +bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) { + CRMatch found = match_rules2(p_path); + if (found == CRMatch::CR_MATCH_NESTED) { + return add_nested_file(p_root, p_path, p_root.plus_file(p_path)); + } + if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { + return true; // No match. + } + + CRFile f; + f.name = p_path; + f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); + f.nested = false; + f.hash = hash_sha1_base64(p_root.plus_file(p_path)); + f.hash2 = hash_sha256_base64(p_root.plus_file(p_path)); + + print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2)); + + files2.push_back(f); + return true; +} + +bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &p_path, const String &p_exepath) { +#define CLEANUP() \ + if (files_to_add.size() > 1) { \ + for (int j = 0; j < files_to_add.size(); j++) { \ + da->remove(files_to_add[j]); \ + } \ + } + + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(da.is_null(), false); + + Vector files_to_add; + if (LipO::is_lipo(p_exepath)) { + String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); + Error err = da->make_dir_recursive(tmp_path_name); + ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); + LipO lip; + if (lip.open_file(p_exepath)) { + for (int i = 0; i < lip.get_arch_count(); i++) { + if (!lip.extract_arch(i, tmp_path_name.plus_file("_rqexe_" + itos(i)))) { + CLEANUP(); + ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary."); + } + files_to_add.push_back(tmp_path_name.plus_file("_rqexe_" + itos(i))); + } + } + } else if (MachO::is_macho(p_exepath)) { + files_to_add.push_back(p_exepath); + } + + CRFile f; + f.name = p_path; + f.optional = false; + f.nested = true; + for (int i = 0; i < files_to_add.size(); i++) { + MachO mh; + if (!mh.open_file(files_to_add[i])) { + CLEANUP(); + ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid executable file."); + } + PackedByteArray hash = mh.get_cdhash_sha256(); // Use SHA-256 variant, if available. + if (hash.size() != 0x20) { + hash = mh.get_cdhash_sha1(); // Use SHA-1 instead. + if (hash.size() != 0x14) { + CLEANUP(); + ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Unsigned nested executable file."); + } + } + hash.resize(0x14); // Always clamp to 0x14 size. + f.hash = CryptoCore::b64_encode_str(hash.ptr(), hash.size()); + + PackedByteArray rq_blob = mh.get_requirements(); + String req_string; + if (rq_blob.size() > 8) { + CodeSignRequirements rq = CodeSignRequirements(rq_blob); + Vector rqs = rq.parse_requirements(); + for (int j = 0; j < rqs.size(); j++) { + if (rqs[j].begins_with("designated => ")) { + req_string = rqs[j].replace("designated => ", ""); + } + } + } + if (req_string.is_empty()) { + req_string = "cdhash H\"" + String::hex_encode_buffer(hash.ptr(), hash.size()) + "\""; + } + print_verbose(vformat("CodeSign/CodeResources: Nested object %s (cputype: %d) cdhash:%s designated rq:%s", f.name, mh.get_cputype(), f.hash, req_string)); + if (f.requirements != req_string) { + if (i != 0) { + f.requirements += " or "; + } + f.requirements += req_string; + } + } + files2.push_back(f); + + CLEANUP(); + return true; + +#undef CLEANUP +} + +bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(da.is_null(), false); + Error err = da->change_dir(p_root.plus_file(p_path)); + ERR_FAIL_COND_V(err != OK, false); + + bool ret = true; + da->list_dir_begin(); + String n = da->get_next(); + while (n != String()) { + if (n != "." && n != "..") { + String path = p_root.plus_file(p_path).plus_file(n); + if (path == p_main_exe_path) { + n = da->get_next(); + continue; // Skip main executable. + } + if (da->current_is_dir()) { + CRMatch found = match_rules2(p_path.plus_file(n)); + String fmw_ver = "Current"; // Framework version (default). + String info_path; + String main_exe; + bool bundle = false; + if (da->file_exists(path.plus_file("Contents/Info.plist"))) { + info_path = path.plus_file("Contents/Info.plist"); + main_exe = path.plus_file("Contents/MacOS"); + bundle = true; + } else if (da->file_exists(path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { + info_path = path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); + main_exe = path.plus_file(vformat("Versions/%s", fmw_ver)); + bundle = true; + } else if (da->file_exists(path.plus_file("Info.plist"))) { + info_path = path.plus_file("Info.plist"); + main_exe = path; + bundle = true; + } + if (bundle && found == CRMatch::CR_MATCH_NESTED && !info_path.is_empty()) { + // Read Info.plist. + PList info_plist; + if (info_plist.load_file(info_path)) { + if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { + main_exe = main_exe.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); + } else { + ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name."); + } + } else { + ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load."); + } + ret = ret && add_nested_file(p_root, p_path.plus_file(n), main_exe); + } else { + ret = ret && add_folder_recursive(p_root, p_path.plus_file(n), p_main_exe_path); + } + } else { + ret = ret && add_file1(p_root, p_path.plus_file(n)); + ret = ret && add_file2(p_root, p_path.plus_file(n)); + } + } + + n = da->get_next(); + } + + da->list_dir_end(); + return ret; +} + +bool CodeSignCodeResources::save_to_file(const String &p_path) { + PList pl; + + print_verbose(vformat("CodeSign/CodeResources: Writing to file: %s", p_path)); + + // Write version 1 hashes. + Ref files1_dict = PListNode::new_dict(); + pl.get_root()->push_subnode(files1_dict, "files"); + for (int i = 0; i < files1.size(); i++) { + if (files1[i].optional) { + Ref file_dict = PListNode::new_dict(); + files1_dict->push_subnode(file_dict, files1[i].name); + + file_dict->push_subnode(PListNode::new_data(files1[i].hash), "hash"); + file_dict->push_subnode(PListNode::new_bool(true), "optional"); + } else { + files1_dict->push_subnode(PListNode::new_data(files1[i].hash), files1[i].name); + } + } + + // Write version 2 hashes. + Ref files2_dict = PListNode::new_dict(); + pl.get_root()->push_subnode(files2_dict, "files2"); + for (int i = 0; i < files2.size(); i++) { + Ref file_dict = PListNode::new_dict(); + files2_dict->push_subnode(file_dict, files2[i].name); + + if (files2[i].nested) { + file_dict->push_subnode(PListNode::new_data(files2[i].hash), "cdhash"); + file_dict->push_subnode(PListNode::new_string(files2[i].requirements), "requirement"); + } else { + file_dict->push_subnode(PListNode::new_data(files2[i].hash), "hash"); + file_dict->push_subnode(PListNode::new_data(files2[i].hash2), "hash2"); + if (files2[i].optional) { + file_dict->push_subnode(PListNode::new_bool(true), "optional"); + } + } + } + + // Write version 1 rules. + Ref rules1_dict = PListNode::new_dict(); + pl.get_root()->push_subnode(rules1_dict, "rules"); + for (int i = 0; i < rules1.size(); i++) { + if (rules1[i].store) { + if (rules1[i].key.is_empty() && rules1[i].weight <= 0) { + rules1_dict->push_subnode(PListNode::new_bool(true), rules1[i].file_pattern); + } else { + Ref rule_dict = PListNode::new_dict(); + rules1_dict->push_subnode(rule_dict, rules1[i].file_pattern); + if (!rules1[i].key.is_empty()) { + rule_dict->push_subnode(PListNode::new_bool(true), rules1[i].key); + } + if (rules1[i].weight != 1) { + rule_dict->push_subnode(PListNode::new_real(rules1[i].weight), "weight"); + } + } + } + } + + // Write version 2 rules. + Ref rules2_dict = PListNode::new_dict(); + pl.get_root()->push_subnode(rules2_dict, "rules2"); + for (int i = 0; i < rules2.size(); i++) { + if (rules2[i].store) { + if (rules2[i].key.is_empty() && rules2[i].weight <= 0) { + rules2_dict->push_subnode(PListNode::new_bool(true), rules2[i].file_pattern); + } else { + Ref rule_dict = PListNode::new_dict(); + rules2_dict->push_subnode(rule_dict, rules2[i].file_pattern); + if (!rules2[i].key.is_empty()) { + rule_dict->push_subnode(PListNode::new_bool(true), rules2[i].key); + } + if (rules2[i].weight != 1) { + rule_dict->push_subnode(PListNode::new_real(rules2[i].weight), "weight"); + } + } + } + } + String text = pl.save_text(); + ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed."); + + Ref fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); + + CharString cs = text.utf8(); + fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); + return true; +} + +/*************************************************************************/ +/* CodeSignRequirements */ +/*************************************************************************/ + +CodeSignRequirements::CodeSignRequirements() { + blob.append_array({ 0xFA, 0xDE, 0x0C, 0x01 }); // Requirement set magic. + blob.append_array({ 0x00, 0x00, 0x00, 0x0C }); // Length of requirements set (12 bytes). + blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Empty. +} + +CodeSignRequirements::CodeSignRequirements(const PackedByteArray &p_data) { + blob = p_data; +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + r_out += "certificate "; + uint32_t tag_slot = _R(r_pos); + if (tag_slot == 0x00000000) { + r_out += "leaf"; + } else if (tag_slot == 0xffffffff) { + r_out += "root"; + } else { + r_out += itos((int32_t)tag_slot); + } + r_pos += 4; +#undef _R +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + uint32_t key_size = _R(r_pos); + ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); + CharString key; + key.resize(key_size); + memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); + r_pos += 4 + key_size + PAD(key_size, 4); + r_out += "[" + String::utf8(key, key_size) + "]"; +#undef _R +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + uint32_t key_size = _R(r_pos); + ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); + r_out += "[field."; + r_out += itos(blob[r_pos + 4] / 40) + "."; + r_out += itos(blob[r_pos + 4] % 40); + uint32_t spos = r_pos + 5; + while (spos < r_pos + 4 + key_size) { + r_out += "."; + if (blob[spos] <= 127) { + r_out += itos(blob[spos]); + spos += 1; + } else { + uint32_t x = (0x7F & blob[spos]) << 7; + spos += 1; + while (blob[spos] > 127) { + x = (x + (0x7F & blob[spos])) << 7; + spos += 1; + } + x = (x + (0x7F & blob[spos])); + r_out += itos(x); + spos += 1; + } + } + r_out += "]"; + r_pos += 4 + key_size + PAD(key_size, 4); +#undef _R +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + uint32_t tag_size = _R(r_pos); + ERR_FAIL_COND_MSG(r_pos + tag_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); + PackedByteArray data; + data.resize(tag_size); + memcpy(data.ptrw(), blob.ptr() + r_pos + 4, tag_size); + r_out += "H\"" + String::hex_encode_buffer(data.ptr(), data.size()) + "\""; + r_pos += 4 + tag_size + PAD(tag_size, 4); +#undef _R +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + uint32_t key_size = _R(r_pos); + ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); + CharString key; + key.resize(key_size); + memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); + r_pos += 4 + key_size + PAD(key_size, 4); + r_out += "\"" + String::utf8(key, key_size) + "\""; +#undef _R +} + +_FORCE_INLINE_ void CodeSignRequirements::_parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); + uint32_t date = _R(r_pos); + time_t t = 978307200 + date; + struct tm lt; +#ifdef WINDOWS_ENABLED + gmtime_s(<, &t); +#else + gmtime_r(&t, <); +#endif + r_out += vformat("<%04d-%02d-%02d ", (int)(1900 + lt.tm_year), (int)(lt.tm_mon + 1), (int)(lt.tm_mday)) + vformat("%02d:%02d:%02d +0000>", (int)(lt.tm_hour), (int)(lt.tm_min), (int)(lt.tm_sec)); +#undef _R +} + +_FORCE_INLINE_ bool CodeSignRequirements::_parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + ERR_FAIL_COND_V_MSG(r_pos >= p_rq_size, false, "CodeSign/Requirements: Out of bounds."); + uint32_t match = _R(r_pos); + r_pos += 4; + switch (match) { + case 0x00000000: { + r_out += "exists"; + } break; + case 0x00000001: { + r_out += "= "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000002: { + r_out += "~ "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000003: { + r_out += "= *"; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000004: { + r_out += "= "; + _parse_value(r_pos, r_out, p_rq_size); + r_out += "*"; + } break; + case 0x00000005: { + r_out += "< "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000006: { + r_out += "> "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000007: { + r_out += "<= "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000008: { + r_out += ">= "; + _parse_value(r_pos, r_out, p_rq_size); + } break; + case 0x00000009: { + r_out += "= "; + _parse_date(r_pos, r_out, p_rq_size); + } break; + case 0x0000000A: { + r_out += "< "; + _parse_date(r_pos, r_out, p_rq_size); + } break; + case 0x0000000B: { + r_out += "> "; + _parse_date(r_pos, r_out, p_rq_size); + } break; + case 0x0000000C: { + r_out += "<= "; + _parse_date(r_pos, r_out, p_rq_size); + } break; + case 0x0000000D: { + r_out += ">= "; + _parse_date(r_pos, r_out, p_rq_size); + } break; + case 0x0000000E: { + r_out += "absent"; + } break; + default: { + return false; + } + } + return true; +#undef _R +} + +Vector CodeSignRequirements::parse_requirements() const { +#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) + Vector list; + + // Read requirements set header. + ERR_FAIL_COND_V_MSG(blob.size() < 12, list, "CodeSign/Requirements: Blob is too small."); + uint32_t magic = _R(0); + ERR_FAIL_COND_V_MSG(magic != 0xfade0c01, list, "CodeSign/Requirements: Invalid set magic."); + uint32_t size = _R(4); + ERR_FAIL_COND_V_MSG(size != (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid set size."); + uint32_t count = _R(8); + + for (uint32_t i = 0; i < count; i++) { + String out; + + // Read requirement header. + uint32_t rq_type = _R(12 + i * 8); + uint32_t rq_offset = _R(12 + i * 8 + 4); + ERR_FAIL_COND_V_MSG(rq_offset + 12 >= (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid requirement offset."); + switch (rq_type) { + case 0x00000001: { + out += "host => "; + } break; + case 0x00000002: { + out += "guest => "; + } break; + case 0x00000003: { + out += "designated => "; + } break; + case 0x00000004: { + out += "library => "; + } break; + case 0x00000005: { + out += "plugin => "; + } break; + default: { + ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement type."); + } + } + uint32_t rq_magic = _R(rq_offset); + uint32_t rq_size = _R(rq_offset + 4); + uint32_t rq_ver = _R(rq_offset + 8); + uint32_t pos = rq_offset + 12; + ERR_FAIL_COND_V_MSG(rq_magic != 0xfade0c00, list, "CodeSign/Requirements: Invalid requirement magic."); + ERR_FAIL_COND_V_MSG(rq_ver != 0x00000001, list, "CodeSign/Requirements: Invalid requirement version."); + + // Read requirement tokens. + List tokens; + while (pos < rq_offset + rq_size) { + uint32_t rq_tag = _R(pos); + pos += 4; + String token; + switch (rq_tag) { + case 0x00000000: { + token = "false"; + } break; + case 0x00000001: { + token = "true"; + } break; + case 0x00000002: { + token = "identifier "; + _parse_value(pos, token, rq_offset + rq_size); + } break; + case 0x00000003: { + token = "anchor apple"; + } break; + case 0x00000004: { + _parse_certificate_slot(pos, token, rq_offset + rq_size); + token += " "; + _parse_hash_string(pos, token, rq_offset + rq_size); + } break; + case 0x00000005: { + token = "info"; + _parse_key(pos, token, rq_offset + rq_size); + token += " = "; + _parse_value(pos, token, rq_offset + rq_size); + } break; + case 0x00000006: { + token = "and"; + } break; + case 0x00000007: { + token = "or"; + } break; + case 0x00000008: { + token = "cdhash "; + _parse_hash_string(pos, token, rq_offset + rq_size); + } break; + case 0x00000009: { + token = "!"; + } break; + case 0x0000000A: { + token = "info"; + _parse_key(pos, token, rq_offset + rq_size); + token += " "; + ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); + } break; + case 0x0000000B: { + _parse_certificate_slot(pos, token, rq_offset + rq_size); + _parse_key(pos, token, rq_offset + rq_size); + token += " "; + ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); + } break; + case 0x0000000C: { + _parse_certificate_slot(pos, token, rq_offset + rq_size); + token += " trusted"; + } break; + case 0x0000000D: { + token = "anchor trusted"; + } break; + case 0x0000000E: { + _parse_certificate_slot(pos, token, rq_offset + rq_size); + _parse_oid_key(pos, token, rq_offset + rq_size); + token += " "; + ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); + } break; + case 0x0000000F: { + token = "anchor apple generic"; + } break; + default: { + ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement token."); + } break; + } + tokens.push_back(token); + } + + // Polish to infix notation (w/o bracket optimization). + for (List::Element *E = tokens.back(); E; E = E->prev()) { + if (E->get() == "and") { + ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); + String token = "(" + E->next()->get() + " and " + E->next()->next()->get() + ")"; + tokens.erase(E->next()->next()); + tokens.erase(E->next()); + E->get() = token; + } else if (E->get() == "or") { + ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); + String token = "(" + E->next()->get() + " or " + E->next()->next()->get() + ")"; + tokens.erase(E->next()->next()); + tokens.erase(E->next()); + E->get() = token; + } + } + + if (tokens.size() == 1) { + list.push_back(out + tokens.front()->get()); + } else { + ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid token sequence."); + } + } + + return list; +#undef _R +} + +PackedByteArray CodeSignRequirements::get_hash_sha1() const { + PackedByteArray hash; + hash.resize(0x14); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +PackedByteArray CodeSignRequirements::get_hash_sha256() const { + PackedByteArray hash; + hash.resize(0x20); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +int CodeSignRequirements::get_size() const { + return blob.size(); +} + +void CodeSignRequirements::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle."); + p_file->store_buffer(blob.ptr(), blob.size()); +} + +/*************************************************************************/ +/* CodeSignEntitlementsText */ +/*************************************************************************/ + +CodeSignEntitlementsText::CodeSignEntitlementsText() { + blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. + blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). +} + +CodeSignEntitlementsText::CodeSignEntitlementsText(const String &p_string) { + CharString utf8 = p_string.utf8(); + blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. + for (int i = 3; i >= 0; i--) { + uint8_t x = ((utf8.length() + 8) >> i * 8) & 0xFF; // Size. + blob.push_back(x); + } + for (int i = 0; i < utf8.length(); i++) { // Write data. + blob.push_back(utf8[i]); + } +} + +PackedByteArray CodeSignEntitlementsText::get_hash_sha1() const { + PackedByteArray hash; + hash.resize(0x14); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +PackedByteArray CodeSignEntitlementsText::get_hash_sha256() const { + PackedByteArray hash; + hash.resize(0x20); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +int CodeSignEntitlementsText::get_size() const { + return blob.size(); +} + +void CodeSignEntitlementsText::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle."); + p_file->store_buffer(blob.ptr(), blob.size()); +} + +/*************************************************************************/ +/* CodeSignEntitlementsBinary */ +/*************************************************************************/ + +CodeSignEntitlementsBinary::CodeSignEntitlementsBinary() { + blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. + blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). +} + +CodeSignEntitlementsBinary::CodeSignEntitlementsBinary(const String &p_string) { + PList pl = PList(p_string); + + PackedByteArray asn1 = pl.save_asn1(); + blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. + uint32_t size = asn1.size() + 8; + for (int i = 3; i >= 0; i--) { + uint8_t x = (size >> i * 8) & 0xFF; // Size. + blob.push_back(x); + } + blob.append_array(asn1); // Write data. +} + +PackedByteArray CodeSignEntitlementsBinary::get_hash_sha1() const { + PackedByteArray hash; + hash.resize(0x14); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +PackedByteArray CodeSignEntitlementsBinary::get_hash_sha256() const { + PackedByteArray hash; + hash.resize(0x20); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +int CodeSignEntitlementsBinary::get_size() const { + return blob.size(); +} + +void CodeSignEntitlementsBinary::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle."); + p_file->store_buffer(blob.ptr(), blob.size()); +} + +/*************************************************************************/ +/* CodeSignCodeDirectory */ +/*************************************************************************/ + +CodeSignCodeDirectory::CodeSignCodeDirectory() { + blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. + blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Size (8 bytes). +} + +CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit) { + pages = p_code_limit / (uint64_t(1) << p_page_size); + remain = p_code_limit % (uint64_t(1) << p_page_size); + code_slots = pages + (remain > 0 ? 1 : 0); + special_slots = 7; + + int cd_size = 8 + sizeof(CodeDirectoryHeader) + (code_slots + special_slots) * p_hash_size + p_id.size() + p_team_id.size(); + int cd_off = 8 + sizeof(CodeDirectoryHeader); + blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. + for (int i = 3; i >= 0; i--) { + uint8_t x = (cd_size >> i * 8) & 0xFF; // Size. + blob.push_back(x); + } + blob.resize(cd_size); + memset(blob.ptrw() + 8, 0x00, cd_size - 8); + CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); + + bool is_64_cl = (p_code_limit >= std::numeric_limits::max()); + + // Version and options. + cd->version = BSWAP32(0x20500); + cd->flags = BSWAP32(SIGNATURE_ADHOC | SIGNATURE_RUNTIME); + cd->special_slots = BSWAP32(special_slots); + cd->code_slots = BSWAP32(code_slots); + if (is_64_cl) { + cd->code_limit_64 = BSWAP64(p_code_limit); + } else { + cd->code_limit = BSWAP32(p_code_limit); + } + cd->hash_size = p_hash_size; + cd->hash_type = p_hash_type; + cd->page_size = p_page_size; + cd->exec_seg_base = 0x00; + cd->exec_seg_limit = BSWAP64(p_exe_limit); + cd->exec_seg_flags = 0; + if (p_main) { + cd->exec_seg_flags |= EXECSEG_MAIN_BINARY; + } + cd->exec_seg_flags = BSWAP64(cd->exec_seg_flags); + uint32_t version = (11 << 16) + (3 << 8) + 0; // Version 11.3.0 + cd->runtime = BSWAP32(version); + + // Copy ID. + cd->ident_offset = BSWAP32(cd_off); + memcpy(blob.ptrw() + cd_off, p_id.get_data(), p_id.size()); + cd_off += p_id.size(); + + // Copy Team ID. + if (p_team_id.length() > 0) { + cd->team_offset = BSWAP32(cd_off); + memcpy(blob.ptrw() + cd_off, p_team_id.get_data(), p_team_id.size()); + cd_off += p_team_id.size(); + } else { + cd->team_offset = 0; + } + + // Scatter vector. + cd->scatter_vector_offset = 0; // Not used. + + // Executable hashes offset. + cd->hash_offset = BSWAP32(cd_off + special_slots * cd->hash_size); +} + +bool CodeSignCodeDirectory::set_hash_in_slot(const PackedByteArray &p_hash, int p_slot) { + ERR_FAIL_COND_V_MSG((p_slot < -special_slots) || (p_slot >= code_slots), false, vformat("CodeSign/CodeDirectory: Invalid hash slot index: %d.", p_slot)); + CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); + for (int i = 0; i < cd->hash_size; i++) { + blob.write[BSWAP32(cd->hash_offset) + p_slot * cd->hash_size + i] = p_hash[i]; + } + return true; +} + +int32_t CodeSignCodeDirectory::get_page_count() { + return pages; +} + +int32_t CodeSignCodeDirectory::get_page_remainder() { + return remain; +} + +PackedByteArray CodeSignCodeDirectory::get_hash_sha1() const { + PackedByteArray hash; + hash.resize(0x14); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +PackedByteArray CodeSignCodeDirectory::get_hash_sha256() const { + PackedByteArray hash; + hash.resize(0x20); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +int CodeSignCodeDirectory::get_size() const { + return blob.size(); +} + +void CodeSignCodeDirectory::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle."); + p_file->store_buffer(blob.ptr(), blob.size()); +} + +/*************************************************************************/ +/* CodeSignSignature */ +/*************************************************************************/ + +CodeSignSignature::CodeSignSignature() { + blob.append_array({ 0xFA, 0xDE, 0x0B, 0x01 }); // Signature magic. + uint32_t sign_size = 8; // Ad-hoc signature is empty. + for (int i = 3; i >= 0; i--) { + uint8_t x = (sign_size >> i * 8) & 0xFF; // Size. + blob.push_back(x); + } +} + +PackedByteArray CodeSignSignature::get_hash_sha1() const { + PackedByteArray hash; + hash.resize(0x14); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +PackedByteArray CodeSignSignature::get_hash_sha256() const { + PackedByteArray hash; + hash.resize(0x20); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; +} + +int CodeSignSignature::get_size() const { + return blob.size(); +} + +void CodeSignSignature::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle."); + p_file->store_buffer(blob.ptr(), blob.size()); +} + +/*************************************************************************/ +/* CodeSignSuperBlob */ +/*************************************************************************/ + +bool CodeSignSuperBlob::add_blob(const Ref &p_blob) { + if (p_blob.is_valid()) { + blobs.push_back(p_blob); + return true; + } else { + return false; + } +} + +int CodeSignSuperBlob::get_size() const { + int size = 12 + blobs.size() * 8; + for (int i = 0; i < blobs.size(); i++) { + if (blobs[i].is_null()) { + return 0; + } + size += blobs[i]->get_size(); + } + return size; +} + +void CodeSignSuperBlob::write_to_file(Ref p_file) const { + ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle."); + uint32_t size = get_size(); + uint32_t data_offset = 12 + blobs.size() * 8; + + // Write header. + p_file->store_32(BSWAP32(0xfade0cc0)); + p_file->store_32(BSWAP32(size)); + p_file->store_32(BSWAP32(blobs.size())); + + // Write index. + for (int i = 0; i < blobs.size(); i++) { + if (blobs[i].is_null()) { + return; + } + p_file->store_32(BSWAP32(blobs[i]->get_index_type())); + p_file->store_32(BSWAP32(data_offset)); + data_offset += blobs[i]->get_size(); + } + + // Write blobs. + for (int i = 0; i < blobs.size(); i++) { + blobs[i]->write_to_file(p_file); + } +} + +/*************************************************************************/ +/* CodeSign */ +/*************************************************************************/ + +PackedByteArray CodeSign::file_hash_sha1(const String &p_path) { + PackedByteArray file_hash; + Ref f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); + + CryptoCore::SHA1Context ctx; + ctx.start(); + + unsigned char step[4096]; + while (true) { + uint64_t br = f->get_buffer(step, 4096); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + file_hash.resize(0x14); + ctx.finish(file_hash.ptrw()); + return file_hash; +} + +PackedByteArray CodeSign::file_hash_sha256(const String &p_path) { + PackedByteArray file_hash; + Ref f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); + + CryptoCore::SHA256Context ctx; + ctx.start(); + + unsigned char step[4096]; + while (true) { + uint64_t br = f->get_buffer(step, 4096); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + file_hash.resize(0x20); + ctx.finish(file_hash.ptrw()); + return file_hash; +} + +Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg) { +#define CLEANUP() \ + if (files_to_sign.size() > 1) { \ + for (int j = 0; j < files_to_sign.size(); j++) { \ + da->remove(files_to_sign[j]); \ + } \ + } + + print_verbose(vformat("CodeSign: Signing executable: %s, bundle: %s with entitlements %s", p_exe_path, p_bundle_path, p_ent_path)); + + PackedByteArray info_hash1, info_hash2; + PackedByteArray res_hash1, res_hash2; + String id; + String main_exe = p_exe_path; + + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_null()) { + r_error_msg = TTR("Can't get filesystem access."); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); + } + + // Read Info.plist. + if (!p_info.is_empty()) { + print_verbose(vformat("CodeSign: Reading bundle info...")); + PList info_plist; + if (info_plist.load_file(p_info)) { + info_hash1 = file_hash_sha1(p_info); + info_hash2 = file_hash_sha256(p_info); + if (info_hash1.is_empty() || info_hash2.is_empty()) { + r_error_msg = TTR("Failed to get Info.plist hash."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get Info.plist hash."); + } + + if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { + main_exe = p_exe_path.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); + } else { + r_error_msg = TTR("Invalid Info.plist, no exe name."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name."); + } + + if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleIdentifier")) { + id = info_plist.get_root()->data_dict["CFBundleIdentifier"]->data_string.get_data(); + } else { + r_error_msg = TTR("Invalid Info.plist, no bundle id."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no bundle id."); + } + } else { + r_error_msg = TTR("Invalid Info.plist, can't load."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, can't load."); + } + } + + // Extract fat binary. + Vector files_to_sign; + if (LipO::is_lipo(main_exe)) { + print_verbose(vformat("CodeSign: Executable is fat, extracting...")); + String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); + Error err = da->make_dir_recursive(tmp_path_name); + if (err != OK) { + r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name); + ERR_FAIL_V_MSG(FAILED, vformat("CodeSign: Failed to create \"%s\" subfolder.", tmp_path_name)); + } + LipO lip; + if (lip.open_file(main_exe)) { + for (int i = 0; i < lip.get_arch_count(); i++) { + if (!lip.extract_arch(i, tmp_path_name.plus_file("_exe_" + itos(i)))) { + CLEANUP(); + r_error_msg = TTR("Failed to extract thin binary."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary."); + } + files_to_sign.push_back(tmp_path_name.plus_file("_exe_" + itos(i))); + } + } + } else if (MachO::is_macho(main_exe)) { + print_verbose(vformat("CodeSign: Executable is thin...")); + files_to_sign.push_back(main_exe); + } else { + r_error_msg = TTR("Invalid binary format."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid binary format."); + } + + // Check if it's already signed. + if (!p_force) { + for (int i = 0; i < files_to_sign.size(); i++) { + MachO mh; + mh.open_file(files_to_sign[i]); + if (mh.is_signed()) { + CLEANUP(); + r_error_msg = TTR("Already signed!"); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Already signed!"); + } + } + } + + // Generate core resources. + if (!p_bundle_path.is_empty()) { + print_verbose(vformat("CodeSign: Generating bundle CodeResources...")); + CodeSignCodeResources cr; + + if (p_ios_bundle) { + cr.add_rule1("^.*"); + cr.add_rule1("^.*\\.lproj/", "optional", 100); + cr.add_rule1("^.*\\.lproj/locversion.plist$", "omit", 1100); + cr.add_rule1("^Base\\.lproj/", "", 1010); + cr.add_rule1("^version.plist$"); + + cr.add_rule2(".*\\.dSYM($|/)", "", 11); + cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); + cr.add_rule2("^.*"); + cr.add_rule2("^.*\\.lproj/", "optional", 1000); + cr.add_rule2("^.*\\.lproj/locversion.plist$", "omit", 1100); + cr.add_rule2("^Base\\.lproj/", "", 1010); + cr.add_rule2("^Info\\.plist$", "omit", 20); + cr.add_rule2("^PkgInfo$", "omit", 20); + cr.add_rule2("^embedded\\.provisionprofile$", "", 10); + cr.add_rule2("^version\\.plist$", "", 20); + + cr.add_rule2("^_MASReceipt", "omit", 2000, false); + cr.add_rule2("^_CodeSignature", "omit", 2000, false); + cr.add_rule2("^CodeResources", "omit", 2000, false); + } else { + cr.add_rule1("^Resources/"); + cr.add_rule1("^Resources/.*\\.lproj/", "optional", 1000); + cr.add_rule1("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); + cr.add_rule1("^Resources/Base\\.lproj/", "", 1010); + cr.add_rule1("^version.plist$"); + + cr.add_rule2(".*\\.dSYM($|/)", "", 11); + cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); + cr.add_rule2("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", "nested", 10); + cr.add_rule2("^.*"); + cr.add_rule2("^Info\\.plist$", "omit", 20); + cr.add_rule2("^PkgInfo$", "omit", 20); + cr.add_rule2("^Resources/", "", 20); + cr.add_rule2("^Resources/.*\\.lproj/", "optional", 1000); + cr.add_rule2("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); + cr.add_rule2("^Resources/Base\\.lproj/", "", 1010); + cr.add_rule2("^[^/]+$", "nested", 10); + cr.add_rule2("^embedded\\.provisionprofile$", "", 10); + cr.add_rule2("^version\\.plist$", "", 20); + cr.add_rule2("^_MASReceipt", "omit", 2000, false); + cr.add_rule2("^_CodeSignature", "omit", 2000, false); + cr.add_rule2("^CodeResources", "omit", 2000, false); + } + + if (!cr.add_folder_recursive(p_bundle_path, "", main_exe)) { + CLEANUP(); + r_error_msg = TTR("Failed to process nested resources."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources."); + } + Error err = da->make_dir_recursive(p_bundle_path.plus_file("_CodeSignature")); + if (err != OK) { + CLEANUP(); + r_error_msg = TTR("Failed to create _CodeSignature subfolder."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder."); + } + cr.save_to_file(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); + res_hash1 = file_hash_sha1(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); + res_hash2 = file_hash_sha256(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); + if (res_hash1.is_empty() || res_hash2.is_empty()) { + CLEANUP(); + r_error_msg = TTR("Failed to get CodeResources hash."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get CodeResources hash."); + } + } + + // Generate common signature structures. + if (id.is_empty()) { + CryptoCore::RandomGenerator rng; + ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); + uint8_t uuid[16]; + Error err = rng.get_random_bytes(uuid, 16); + ERR_FAIL_COND_V_MSG(err, err, "Failed to generate UUID."); + id = (String("a-55554944") /*a-UUID*/ + String::hex_encode_buffer(uuid, 16)); + } + CharString uuid_str = id.utf8(); + print_verbose(vformat("CodeSign: Used bundle ID: %s", id)); + + print_verbose(vformat("CodeSign: Processing entitlements...")); + + Ref cet; + Ref ceb; + if (!p_ent_path.is_empty()) { + String entitlements = FileAccess::get_file_as_string(p_ent_path); + if (entitlements.is_empty()) { + CLEANUP(); + r_error_msg = TTR("Invalid entitlements file."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid entitlements file."); + } + cet = Ref(memnew(CodeSignEntitlementsText(entitlements))); + ceb = Ref(memnew(CodeSignEntitlementsBinary(entitlements))); + } + + print_verbose(vformat("CodeSign: Generating requirements...")); + Ref rq; + String team_id = ""; + rq = Ref(memnew(CodeSignRequirements())); + + // Sign executables. + for (int i = 0; i < files_to_sign.size(); i++) { + MachO mh; + if (!mh.open_file(files_to_sign[i])) { + CLEANUP(); + r_error_msg = TTR("Invalid executable file."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid executable file."); + } + print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype())); + + print_verbose(vformat("CodeSign: Generating CodeDirectory...")); + Ref cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); + Ref cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); + print_verbose(vformat("CodeSign: Calculating special slot hashes...")); + if (info_hash2.size() == 0x20) { + cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST); + } + if (info_hash1.size() == 0x14) { + cd1->set_hash_in_slot(info_hash1, CodeSignCodeDirectory::SLOT_INFO_PLIST); + } + cd1->set_hash_in_slot(rq->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); + cd2->set_hash_in_slot(rq->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); + if (res_hash2.size() == 0x20) { + cd2->set_hash_in_slot(res_hash2, CodeSignCodeDirectory::SLOT_RESOURCES); + } + if (res_hash1.size() == 0x14) { + cd1->set_hash_in_slot(res_hash1, CodeSignCodeDirectory::SLOT_RESOURCES); + } + if (cet.is_valid()) { + cd1->set_hash_in_slot(cet->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); //Text variant. + cd2->set_hash_in_slot(cet->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); + } + if (ceb.is_valid()) { + cd1->set_hash_in_slot(ceb->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); //ASN.1 variant. + cd2->set_hash_in_slot(ceb->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); + } + + // Calculate signature size. + int sign_size = 12; // SuperBlob header. + sign_size += cd1->get_size() + 8; + sign_size += cd2->get_size() + 8; + sign_size += rq->get_size() + 8; + if (cet.is_valid()) { + sign_size += cet->get_size() + 8; + } + if (ceb.is_valid()) { + sign_size += ceb->get_size() + 8; + } + sign_size += 16; // Empty signature size. + + // Alloc/resize signature load command. + print_verbose(vformat("CodeSign: Reallocating space for the signature superblob (%d)...", sign_size)); + if (!mh.set_signature_size(sign_size)) { + CLEANUP(); + r_error_msg = TTR("Can't resize signature load command."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command."); + } + + print_verbose(vformat("CodeSign: Calculating executable code hashes...")); + // Calculate executable code hashes. + PackedByteArray buffer; + PackedByteArray hash1, hash2; + hash1.resize(0x14); + hash2.resize(0x20); + buffer.resize(1 << 12); + mh.get_file()->seek(0); + for (int32_t j = 0; j < cd2->get_page_count(); j++) { + mh.get_file()->get_buffer(buffer.ptrw(), (1 << 12)); + CryptoCore::SHA256Context ctx2; + ctx2.start(); + ctx2.update(buffer.ptr(), (1 << 12)); + ctx2.finish(hash2.ptrw()); + cd2->set_hash_in_slot(hash2, j); + + CryptoCore::SHA1Context ctx1; + ctx1.start(); + ctx1.update(buffer.ptr(), (1 << 12)); + ctx1.finish(hash1.ptrw()); + cd1->set_hash_in_slot(hash1, j); + } + if (cd2->get_page_remainder() > 0) { + mh.get_file()->get_buffer(buffer.ptrw(), cd2->get_page_remainder()); + CryptoCore::SHA256Context ctx2; + ctx2.start(); + ctx2.update(buffer.ptr(), cd2->get_page_remainder()); + ctx2.finish(hash2.ptrw()); + cd2->set_hash_in_slot(hash2, cd2->get_page_count()); + + CryptoCore::SHA1Context ctx1; + ctx1.start(); + ctx1.update(buffer.ptr(), cd1->get_page_remainder()); + ctx1.finish(hash1.ptrw()); + cd1->set_hash_in_slot(hash1, cd1->get_page_count()); + } + + print_verbose(vformat("CodeSign: Generating signature...")); + Ref cs; + cs = Ref(memnew(CodeSignSignature())); + + print_verbose(vformat("CodeSign: Writing signature superblob...")); + // Write signature data to the executable. + CodeSignSuperBlob sb = CodeSignSuperBlob(); + sb.add_blob(cd2); + sb.add_blob(cd1); + sb.add_blob(rq); + if (cet.is_valid()) { + sb.add_blob(cet); + } + if (ceb.is_valid()) { + sb.add_blob(ceb); + } + sb.add_blob(cs); + mh.get_file()->seek(mh.get_signature_offset()); + sb.write_to_file(mh.get_file()); + } + if (files_to_sign.size() > 1) { + print_verbose(vformat("CodeSign: Rebuilding fat executable...")); + LipO lip; + if (!lip.create_file(main_exe, files_to_sign)) { + CLEANUP(); + r_error_msg = TTR("Failed to create fat binary."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create fat binary."); + } + CLEANUP(); + } + FileAccess::set_unix_permissions(main_exe, 0755); // Restore unix permissions. + return OK; +#undef CLEANUP +} + +Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da.is_null()) { + r_error_msg = TTR("Can't get filesystem access."); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); + } + + if (da->dir_exists(p_path)) { + String fmw_ver = "Current"; // Framework version (default). + String info_path; + String main_exe; + String bundle_path; + bool bundle = false; + bool ios_bundle = false; + if (da->file_exists(p_path.plus_file("Contents/Info.plist"))) { + info_path = p_path.plus_file("Contents/Info.plist"); + main_exe = p_path.plus_file("Contents/MacOS"); + bundle_path = p_path.plus_file("Contents"); + bundle = true; + } else if (da->file_exists(p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { + info_path = p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); + main_exe = p_path.plus_file(vformat("Versions/%s", fmw_ver)); + bundle_path = p_path.plus_file(vformat("Versions/%s", fmw_ver)); + bundle = true; + } else if (da->file_exists(p_path.plus_file("Info.plist"))) { + info_path = p_path.plus_file("Info.plist"); + main_exe = p_path; + bundle_path = p_path; + bundle = true; + ios_bundle = true; + } + if (bundle) { + return _codesign_file(p_use_hardened_runtime, p_force, info_path, main_exe, bundle_path, p_ent_path, ios_bundle, r_error_msg); + } else { + r_error_msg = TTR("Unknown bundle type."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown bundle type."); + } + } else if (da->file_exists(p_path)) { + return _codesign_file(p_use_hardened_runtime, p_force, "", p_path, "", p_ent_path, false, r_error_msg); + } else { + r_error_msg = TTR("Unknown object type."); + ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown object type."); + } +} + +#endif // MODULE_REGEX_ENABLED diff --git a/platform/macos/export/codesign.h b/platform/macos/export/codesign.h new file mode 100644 index 0000000000..3a08c0ea86 --- /dev/null +++ b/platform/macos/export/codesign.h @@ -0,0 +1,368 @@ +/*************************************************************************/ +/* codesign.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +// macOS code signature creation utility. +// +// Current implementation has the following limitation: +// - Only version 11.3.0 signatures are supported. +// - Only "framework" and "app" bundle types are supported. +// - Page hash array scattering is not supported. +// - Reading and writing binary property lists i snot supported (third-party frameworks with binary Info.plist will not work unless .plist is converted to text format). +// - Requirements code generator is not implemented (only hard-coded requirements for the ad-hoc signing is supported). +// - RFC5652/CMS blob generation is not implemented, supports ad-hoc signing only. + +#ifndef CODESIGN_H +#define CODESIGN_H + +#include "core/crypto/crypto_core.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" + +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif + +#include "plist.h" + +#ifdef MODULE_REGEX_ENABLED + +/*************************************************************************/ +/* CodeSignCodeResources */ +/*************************************************************************/ + +class CodeSignCodeResources { +public: + enum class CRMatch { + CR_MATCH_NO, + CR_MATCH_YES, + CR_MATCH_NESTED, + CR_MATCH_OPTIONAL, + }; + +private: + struct CRFile { + String name; + String hash; + String hash2; + bool optional; + bool nested; + String requirements; + }; + + struct CRRule { + String file_pattern; + String key; + int weight; + bool store; + CRRule() { + weight = 1; + store = true; + } + CRRule(const String &p_file_pattern, const String &p_key, int p_weight, bool p_store) { + file_pattern = p_file_pattern; + key = p_key; + weight = p_weight; + store = p_store; + } + }; + + Vector rules1; + Vector rules2; + + Vector files1; + Vector files2; + + String hash_sha1_base64(const String &p_path); + String hash_sha256_base64(const String &p_path); + +public: + void add_rule1(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true); + void add_rule2(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true); + + CRMatch match_rules1(const String &p_path) const; + CRMatch match_rules2(const String &p_path) const; + + bool add_file1(const String &p_root, const String &p_path); + bool add_file2(const String &p_root, const String &p_path); + bool add_nested_file(const String &p_root, const String &p_path, const String &p_exepath); + + bool add_folder_recursive(const String &p_root, const String &p_path = "", const String &p_main_exe_path = ""); + + bool save_to_file(const String &p_path); +}; + +/*************************************************************************/ +/* CodeSignBlob */ +/*************************************************************************/ + +class CodeSignBlob : public RefCounted { +public: + virtual PackedByteArray get_hash_sha1() const = 0; + virtual PackedByteArray get_hash_sha256() const = 0; + + virtual int get_size() const = 0; + virtual uint32_t get_index_type() const = 0; + + virtual void write_to_file(Ref p_file) const = 0; +}; + +/*************************************************************************/ +/* CodeSignRequirements */ +/*************************************************************************/ + +// Note: Proper code generator is not implemented (any we probably won't ever need it), just a hardcoded bytecode for the limited set of cases. + +class CodeSignRequirements : public CodeSignBlob { + PackedByteArray blob; + + static inline size_t PAD(size_t s, size_t a) { + return (s % a == 0) ? 0 : (a - s % a); + } + + _FORCE_INLINE_ void _parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ void _parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ void _parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ void _parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ void _parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ void _parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + _FORCE_INLINE_ bool _parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; + +public: + CodeSignRequirements(); + CodeSignRequirements(const PackedByteArray &p_data); + + Vector parse_requirements() const; + + virtual PackedByteArray get_hash_sha1() const override; + virtual PackedByteArray get_hash_sha256() const override; + + virtual int get_size() const override; + + virtual uint32_t get_index_type() const override { return 0x00000002; }; + virtual void write_to_file(Ref p_file) const override; +}; + +/*************************************************************************/ +/* CodeSignEntitlementsText */ +/*************************************************************************/ + +// PList formatted entitlements. + +class CodeSignEntitlementsText : public CodeSignBlob { + PackedByteArray blob; + +public: + CodeSignEntitlementsText(); + CodeSignEntitlementsText(const String &p_string); + + virtual PackedByteArray get_hash_sha1() const override; + virtual PackedByteArray get_hash_sha256() const override; + + virtual int get_size() const override; + + virtual uint32_t get_index_type() const override { return 0x00000005; }; + virtual void write_to_file(Ref p_file) const override; +}; + +/*************************************************************************/ +/* CodeSignEntitlementsBinary */ +/*************************************************************************/ + +// ASN.1 serialized entitlements. + +class CodeSignEntitlementsBinary : public CodeSignBlob { + PackedByteArray blob; + +public: + CodeSignEntitlementsBinary(); + CodeSignEntitlementsBinary(const String &p_string); + + virtual PackedByteArray get_hash_sha1() const override; + virtual PackedByteArray get_hash_sha256() const override; + + virtual int get_size() const override; + + virtual uint32_t get_index_type() const override { return 0x00000007; }; + virtual void write_to_file(Ref p_file) const override; +}; + +/*************************************************************************/ +/* CodeSignCodeDirectory */ +/*************************************************************************/ + +// Code Directory, runtime options, code segment and special structure hashes. + +class CodeSignCodeDirectory : public CodeSignBlob { +public: + enum Slot { + SLOT_INFO_PLIST = -1, + SLOT_REQUIREMENTS = -2, + SLOT_RESOURCES = -3, + SLOT_APP_SPECIFIC = -4, // Unused. + SLOT_ENTITLEMENTS = -5, + SLOT_RESERVER1 = -6, // Unused. + SLOT_DER_ENTITLEMENTS = -7, + }; + + enum CodeSignExecSegFlags { + EXECSEG_MAIN_BINARY = 0x1, + EXECSEG_ALLOW_UNSIGNED = 0x10, + EXECSEG_DEBUGGER = 0x20, + EXECSEG_JIT = 0x40, + EXECSEG_SKIP_LV = 0x80, + EXECSEG_CAN_LOAD_CDHASH = 0x100, + EXECSEG_CAN_EXEC_CDHASH = 0x200, + }; + + enum CodeSignatureFlags { + SIGNATURE_HOST = 0x0001, + SIGNATURE_ADHOC = 0x0002, + SIGNATURE_TASK_ALLOW = 0x0004, + SIGNATURE_INSTALLER = 0x0008, + SIGNATURE_FORCED_LV = 0x0010, + SIGNATURE_INVALID_ALLOWED = 0x0020, + SIGNATURE_FORCE_HARD = 0x0100, + SIGNATURE_FORCE_KILL = 0x0200, + SIGNATURE_FORCE_EXPIRATION = 0x0400, + SIGNATURE_RESTRICT = 0x0800, + SIGNATURE_ENFORCEMENT = 0x1000, + SIGNATURE_LIBRARY_VALIDATION = 0x2000, + SIGNATURE_ENTITLEMENTS_VALIDATED = 0x4000, + SIGNATURE_NVRAM_UNRESTRICTED = 0x8000, + SIGNATURE_RUNTIME = 0x10000, + SIGNATURE_LINKER_SIGNED = 0x20000, + }; + +private: + PackedByteArray blob; + + struct CodeDirectoryHeader { + uint32_t version; // Using version 0x0020500. + uint32_t flags; // // Option flags. + uint32_t hash_offset; // Slot zero offset. + uint32_t ident_offset; // Identifier string offset. + uint32_t special_slots; // Nr. of slots with negative index. + uint32_t code_slots; // Nr. of slots with index >= 0, (code_limit / page_size). + uint32_t code_limit; // Everything before code signature load command offset. + uint8_t hash_size; // 20 (SHA-1) or 32 (SHA-256). + uint8_t hash_type; // 1 (SHA-1) or 2 (SHA-256). + uint8_t platform; // Not used. + uint8_t page_size; // Page size, power of two, 2^12 (4096). + uint32_t spare2; // Not used. + // Version 0x20100 + uint32_t scatter_vector_offset; // Set to 0 and ignore. + // Version 0x20200 + uint32_t team_offset; // Team id string offset. + // Version 0x20300 + uint32_t spare3; // Not used. + uint64_t code_limit_64; // Set to 0 and ignore. + // Version 0x20400 + uint64_t exec_seg_base; // Start of the signed code segmet. + uint64_t exec_seg_limit; // Code segment (__TEXT) vmsize. + uint64_t exec_seg_flags; // Executable segment flags. + // Version 0x20500 + uint32_t runtime; // Runtime version. + uint32_t pre_encrypt_offset; // Set to 0 and ignore. + }; + + int32_t pages = 0; + int32_t remain = 0; + int32_t code_slots = 0; + int32_t special_slots = 0; + +public: + CodeSignCodeDirectory(); + CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit); + + int32_t get_page_count(); + int32_t get_page_remainder(); + + bool set_hash_in_slot(const PackedByteArray &p_hash, int p_slot); + + virtual PackedByteArray get_hash_sha1() const override; + virtual PackedByteArray get_hash_sha256() const override; + + virtual int get_size() const override; + virtual uint32_t get_index_type() const override { return 0x00000000; }; + + virtual void write_to_file(Ref p_file) const override; +}; + +/*************************************************************************/ +/* CodeSignSignature */ +/*************************************************************************/ + +class CodeSignSignature : public CodeSignBlob { + PackedByteArray blob; + +public: + CodeSignSignature(); + + virtual PackedByteArray get_hash_sha1() const override; + virtual PackedByteArray get_hash_sha256() const override; + + virtual int get_size() const override; + virtual uint32_t get_index_type() const override { return 0x00010000; }; + + virtual void write_to_file(Ref p_file) const override; +}; + +/*************************************************************************/ +/* CodeSignSuperBlob */ +/*************************************************************************/ + +class CodeSignSuperBlob { + Vector> blobs; + +public: + bool add_blob(const Ref &p_blob); + + int get_size() const; + void write_to_file(Ref p_file) const; +}; + +/*************************************************************************/ +/* CodeSign */ +/*************************************************************************/ + +class CodeSign { + static PackedByteArray file_hash_sha1(const String &p_path); + static PackedByteArray file_hash_sha256(const String &p_path); + static Error _codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg); + +public: + static Error codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg); +}; + +#endif // MODULE_REGEX_ENABLED + +#endif // CODESIGN_H diff --git a/platform/macos/export/export.cpp b/platform/macos/export/export.cpp new file mode 100644 index 0000000000..ff7457081f --- /dev/null +++ b/platform/macos/export/export.cpp @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* export.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "export.h" + +#include "export_plugin.h" + +void register_macos_exporter() { + EDITOR_DEF("export/macos/force_builtin_codesign", false); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE)); + + Ref platform; + platform.instantiate(); + + EditorExport::get_singleton()->add_export_platform(platform); +} diff --git a/platform/macos/export/export.h b/platform/macos/export/export.h new file mode 100644 index 0000000000..260c691209 --- /dev/null +++ b/platform/macos/export/export.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* export.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 MACOS_EXPORT_H +#define MACOS_EXPORT_H + +void register_macos_exporter(); + +#endif // MACOS_EXPORT_H diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp new file mode 100644 index 0000000000..162a188f95 --- /dev/null +++ b/platform/macos/export/export_plugin.cpp @@ -0,0 +1,1673 @@ +/*************************************************************************/ +/* export_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "export_plugin.h" + +#include "codesign.h" + +#include "editor/editor_node.h" +#include "editor/editor_paths.h" + +#include "modules/modules_enabled.gen.h" // For regex. + +void EditorExportPlatformMacOS::get_preset_features(const Ref &p_preset, List *r_features) { + if (p_preset->get("texture_format/s3tc")) { + r_features->push_back("s3tc"); + } + if (p_preset->get("texture_format/etc")) { + r_features->push_back("etc"); + } + if (p_preset->get("texture_format/etc2")) { + r_features->push_back("etc2"); + } + + r_features->push_back("64"); +} + +bool EditorExportPlatformMacOS::get_export_option_visibility(const String &p_option, const HashMap &p_options) const { + // These options are not supported by built-in codesign, used on non macOS host. + if (!OS::get_singleton()->has_feature("macos")) { + if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) { + return false; + } + } + + // These entitlements are required to run managed code, and are always enabled in Mono builds. + if (Engine::get_singleton()->has_singleton("GodotSharp")) { + if (p_option == "codesign/entitlements/allow_jit_code_execution" || p_option == "codesign/entitlements/allow_unsigned_executable_memory" || p_option == "codesign/entitlements/allow_dyld_environment_variables") { + return false; + } + } + return true; +} + +void EditorExportPlatformMacOS::get_export_options(List *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::INT, "debug/export_console_script", PROPERTY_HINT_ENUM, "No,Debug Only,Debug and Release"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); + 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/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games")); + 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")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/location"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/address_book"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/calendars"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_bluetooth"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_downloads", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array())); + + 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"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); +} + +void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector &p_source, Vector &p_dest) { + int src_len = p_size * p_size; + + Vector result; + result.resize(src_len * 1.25); //temp vector for rle encoded data, make it 25% larger for worst case scenario + int res_size = 0; + + uint8_t buf[128]; + int buf_size = 0; + + int i = 0; + while (i < src_len) { + uint8_t cur = p_source.ptr()[i * 4 + p_ch]; + + if (i < src_len - 2) { + 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); + memcpy(&result.write[res_size], &buf, buf_size); + res_size += buf_size; + buf_size = 0; + } + + uint8_t lim = i + 130 >= src_len ? src_len - i - 1 : 130; + bool hit_lim = true; + + for (int j = 3; j <= lim; j++) { + 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); + result.write[res_size++] = cur; + break; + } + } + if (hit_lim) { + result.write[res_size++] = (uint8_t)(lim - 3 + 0x80); + result.write[res_size++] = cur; + i = i + lim; + } + } else { + buf[buf_size++] = cur; + if (buf_size == 128) { + result.write[res_size++] = (uint8_t)(buf_size - 1); + memcpy(&result.write[res_size], &buf, buf_size); + res_size += buf_size; + buf_size = 0; + } + } + } else { + buf[buf_size++] = cur; + result.write[res_size++] = (uint8_t)(buf_size - 1); + memcpy(&result.write[res_size], &buf, buf_size); + res_size += buf_size; + buf_size = 0; + } + + i++; + } + + int ofs = p_dest.size(); + p_dest.resize(p_dest.size() + res_size); + memcpy(&p_dest.write[ofs], result.ptr(), res_size); +} + +void EditorExportPlatformMacOS::_make_icon(const Ref &p_icon, Vector &p_data) { + Ref it = memnew(ImageTexture); + + Vector data; + + data.resize(8); + data.write[0] = 'i'; + data.write[1] = 'c'; + data.write[2] = 'n'; + data.write[3] = 's'; + + struct MacOSIconInfo { + const char *name; + const char *mask_name; + bool is_png; + int size; + }; + + static const MacOSIconInfo icon_infos[] = { + { "ic10", "", true, 1024 }, //1024×1024 32-bit PNG and 512×512@2x 32-bit "retina" PNG + { "ic09", "", true, 512 }, //512×512 32-bit PNG + { "ic14", "", true, 512 }, //256×256@2x 32-bit "retina" PNG + { "ic08", "", true, 256 }, //256×256 32-bit PNG + { "ic13", "", true, 256 }, //128×128@2x 32-bit "retina" PNG + { "ic07", "", true, 128 }, //128×128 32-bit PNG + { "ic12", "", true, 64 }, //32×32@2× 32-bit "retina" PNG + { "ic11", "", true, 32 }, //16×16@2× 32-bit "retina" PNG + { "il32", "l8mk", false, 32 }, //32×32 24-bit RLE + 8-bit uncompressed mask + { "is32", "s8mk", false, 16 } //16×16 24-bit RLE + 8-bit uncompressed mask + }; + + for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { + Ref copy = p_icon; // does this make sense? doesn't this just increase the reference count instead of making a copy? Do we even need a copy? + copy->convert(Image::FORMAT_RGBA8); + copy->resize(icon_infos[i].size, icon_infos[i].size); + + if (icon_infos[i].is_png) { + // Encode PNG icon. + it->set_image(copy); + String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png"); + ResourceSaver::save(path, it); + + { + Ref f = FileAccess::open(path, FileAccess::READ); + if (f.is_null()) { + // Clean up generated file. + DirAccess::remove_file_or_error(path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not open icon file \"%s\"."), path)); + return; + } + + int ofs = data.size(); + uint64_t len = f->get_length(); + data.resize(data.size() + len + 8); + f->get_buffer(&data.write[ofs + 8], len); + len += 8; + len = BSWAP32(len); + memcpy(&data.write[ofs], icon_infos[i].name, 4); + encode_uint32(len, &data.write[ofs + 4]); + } + + // Clean up generated file. + DirAccess::remove_file_or_error(path); + + } else { + Vector src_data = copy->get_data(); + + //encode 24bit RGB RLE icon + { + int ofs = data.size(); + data.resize(data.size() + 8); + + _rgba8_to_packbits_encode(0, icon_infos[i].size, src_data, data); // encode R + _rgba8_to_packbits_encode(1, icon_infos[i].size, src_data, data); // encode G + _rgba8_to_packbits_encode(2, icon_infos[i].size, src_data, data); // encode B + + int len = data.size() - ofs; + len = BSWAP32(len); + memcpy(&data.write[ofs], icon_infos[i].name, 4); + encode_uint32(len, &data.write[ofs + 4]); + } + + //encode 8bit mask uncompressed icon + { + int ofs = data.size(); + int len = copy->get_width() * copy->get_height(); + data.resize(data.size() + len + 8); + + for (int j = 0; j < len; j++) { + data.write[ofs + 8 + j] = src_data.ptr()[j * 4 + 3]; + } + len += 8; + len = BSWAP32(len); + memcpy(&data.write[ofs], icon_infos[i].mask_name, 4); + encode_uint32(len, &data.write[ofs + 4]); + } + } + } + + uint32_t total_len = data.size(); + total_len = BSWAP32(total_len); + encode_uint32(total_len, &data.write[4]); + + p_data = data; +} + +void EditorExportPlatformMacOS::_fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary) { + String str; + String strnew; + str.parse_utf8((const char *)plist.ptr(), plist.size()); + Vector lines = str.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (lines[i].find("$binary") != -1) { + strnew += lines[i].replace("$binary", p_binary) + "\n"; + } else if (lines[i].find("$name") != -1) { + strnew += lines[i].replace("$name", ProjectSettings::get_singleton()->get("application/config/name")) + "\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) { + strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; + } else if (lines[i].find("$signature") != -1) { + strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; + } else if (lines[i].find("$app_category") != -1) { + String cat = p_preset->get("application/app_category"); + strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; + } else if (lines[i].find("$copyright") != -1) { + strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].find("$highres") != -1) { + strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t" : "\t") + "\n"; + } else if (lines[i].find("$usage_descriptions") != -1) { + String descriptions; + if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { + descriptions += "\tNSMicrophoneUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/microphone_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { + descriptions += "\tNSCameraUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/camera_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) { + descriptions += "\tNSLocationUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/location_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { + descriptions += "\tNSContactsUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/address_book_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { + descriptions += "\tNSCalendarsUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/calendar_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { + descriptions += "\tNSPhotoLibraryUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/photos_library_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) { + descriptions += "\tNSDesktopFolderUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/desktop_folder_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) { + descriptions += "\tNSDocumentsFolderUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/documents_folder_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) { + descriptions += "\tNSDownloadsFolderUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/downloads_folder_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) { + descriptions += "\tNSNetworkVolumesUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/network_volumes_usage_description") + "\n"; + } + if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) { + descriptions += "\tNSRemovableVolumesUsageDescription\n"; + descriptions += "\t" + (String)p_preset->get("privacy/removable_volumes_usage_description") + "\n"; + } + if (!descriptions.is_empty()) { + strnew += lines[i].replace("$usage_descriptions", descriptions); + } + } else { + strnew += lines[i] + "\n"; + } + } + + CharString cs = strnew.utf8(); + plist.resize(cs.size() - 1); + for (int i = 0; i < cs.size() - 1; i++) { + plist.write[i] = cs[i]; + } +} + +/** + * If we're running the macOS version of the Godot editor we'll: + * - export our application bundle to a temporary folder + * - attempt to code sign it + * - and then wrap it up in a DMG + */ + +Error EditorExportPlatformMacOS::_notarize(const Ref &p_preset, const String &p_path) { +#ifdef MACOS_ENABLED + List 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, &str, nullptr, true); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } + + print_verbose("altool (" + p_path + "):\n" + str); + int rq_offset = str.find("RequestUUID"); + if (rq_offset == -1) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed.")); + return FAILED; + } else { + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u -p \""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple \""); + } + +#endif + + return OK; +} + +Error EditorExportPlatformMacOS::_code_sign(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) { + bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign"); + bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-"); + + if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) { + print_verbose("using built-in codesign..."); +#ifdef MODULE_REGEX_ENABLED + +#ifdef MACOS_ENABLED + if (p_preset->get("codesign/timestamp") && p_warn) { + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); + } + if (p_preset->get("codesign/hardened_runtime") && p_warn) { + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); + } +#endif + + String error_msg; + Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg)); + return FAILED; + } +#else + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module.")); +#endif + return OK; + } else { + print_verbose("using external codesign..."); + List args; + if (p_preset->get("codesign/timestamp")) { + if (ad_hoc) { + if (p_warn) { + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); + } + } else { + args.push_back("--timestamp"); + } + } + if (p_preset->get("codesign/hardened_runtime")) { + if (ad_hoc) { + if (p_warn) { + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); + } + } else { + args.push_back("--options"); + args.push_back("runtime"); + } + } + + if (p_path.get_extension() != "dmg") { + args.push_back("--entitlements"); + args.push_back(p_ent_path); + } + + 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.is_empty()) { + args.push_back(user_arg); + } + } + + args.push_back("-s"); + if (ad_hoc) { + args.push_back("-"); + } else { + args.push_back(p_preset->get("codesign/identity")); + } + + args.push_back("-v"); /* provide some more feedback */ + + if (p_preset->get("codesign/replace_existing_signature")) { + args.push_back("-f"); + } + + args.push_back(p_path); + + String str; + Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed.")); + return err; + } + + print_verbose("codesign (" + p_path + "):\n" + str); + if (str.find("no identity found") != -1) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); + return FAILED; + } + if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file.")); + return FAILED; + } + return OK; + } +} + +Error EditorExportPlatformMacOS::_code_sign_directory(const Ref &p_preset, const String &p_path, + const String &p_ent_path, bool p_should_error_on_non_code) { +#ifdef MACOS_ENABLED + static Vector extensions_to_sign; + + if (extensions_to_sign.is_empty()) { + extensions_to_sign.push_back("dylib"); + extensions_to_sign.push_back("framework"); + } + + Error dir_access_error; + Ref dir_access{ DirAccess::open(p_path, &dir_access_error) }; + + if (dir_access_error != OK) { + return dir_access_error; + } + + dir_access->list_dir_begin(); + String current_file{ dir_access->get_next() }; + while (!current_file.is_empty()) { + String current_file_path{ p_path.plus_file(current_file) }; + + if (current_file == ".." || current_file == ".") { + current_file = dir_access->get_next(); + continue; + } + + if (extensions_to_sign.find(current_file.get_extension()) > -1) { + Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path, false) }; + if (code_sign_error != OK) { + return code_sign_error; + } + } else if (dir_access->current_is_dir()) { + Error code_sign_error{ _code_sign_directory(p_preset, current_file_path, p_ent_path, p_should_error_on_non_code) }; + if (code_sign_error != OK) { + return code_sign_error; + } + } else if (p_should_error_on_non_code) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign file %s."), current_file)); + return Error::FAILED; + } + + current_file = dir_access->get_next(); + } +#endif + + return OK; +} + +Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref &dir_access, const String &p_src_path, + const String &p_in_app_path, bool p_sign_enabled, + const Ref &p_preset, const String &p_ent_path, + bool p_should_error_on_non_code_sign) { + Error err{ OK }; + if (dir_access->dir_exists(p_src_path)) { +#ifndef UNIX_ENABLED + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file())); +#endif + print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path); + err = dir_access->make_dir_recursive(p_in_app_path); + if (err == OK) { + err = dir_access->copy_dir(p_src_path, p_in_app_path, -1, true); + } + } else { + print_verbose("export dylib: " + p_src_path + " -> " + p_in_app_path); + err = dir_access->copy(p_src_path, p_in_app_path); + } + if (err == OK && p_sign_enabled) { + if (dir_access->dir_exists(p_src_path) && p_src_path.get_extension().is_empty()) { + // If it is a directory, find and sign all dynamic libraries. + err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign); + } else { + err = _code_sign(p_preset, p_in_app_path, p_ent_path, false); + } + } + return err; +} + +Error EditorExportPlatformMacOS::_export_macos_plugins_for(Ref p_editor_export_plugin, + const String &p_app_path_name, Ref &dir_access, + bool p_sign_enabled, const Ref &p_preset, + const String &p_ent_path) { + Error error{ OK }; + const Vector &macos_plugins{ p_editor_export_plugin->get_macos_plugin_files() }; + for (int i = 0; i < macos_plugins.size(); ++i) { + String src_path{ ProjectSettings::get_singleton()->globalize_path(macos_plugins[i]) }; + String path_in_app{ p_app_path_name + "/Contents/PlugIns/" + src_path.get_file() }; + error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, false); + if (error != OK) { + break; + } + } + return error; +} + +Error EditorExportPlatformMacOS::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { + List args; + + if (FileAccess::exists(p_dmg_path)) { + OS::get_singleton()->move_to_trash(p_dmg_path); + } + + args.push_back("create"); + args.push_back(p_dmg_path); + args.push_back("-volname"); + args.push_back(p_pkg_name); + args.push_back("-fs"); + args.push_back("HFS+"); + args.push_back("-srcfolder"); + args.push_back(p_app_path_name); + + String str; + Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("Could not start hdiutil executable.")); + return err; + } + + print_verbose("hdiutil returned: " + str); + if (str.find("create failed") != -1) { + if (str.find("File exists") != -1) { + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed - file exists.")); + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed.")); + } + return FAILED; + } + + return OK; +} + +Error EditorExportPlatformMacOS::_export_debug_script(const Ref &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { + Ref f = FileAccess::open(p_path, FileAccess::WRITE); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), vformat(TTR("Could not open file \"%s\"."), p_path)); + return ERR_CANT_CREATE; + } + + f->store_line("#!/bin/sh"); + f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'"); + f->store_line("function realpath() { python -c \"import os,sys; print(os.path.realpath(sys.argv[1]))\" \"$0\"; }"); + f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\""); + f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\""); + + return OK; +} + +Error EditorExportPlatformMacOS::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + String src_pkg_name; + + EditorProgress ep("export", "Exporting for macOS", 3, true); + + if (p_debug) { + src_pkg_name = p_preset->get("custom_template/debug"); + } else { + src_pkg_name = p_preset->get("custom_template/release"); + } + + if (src_pkg_name.is_empty()) { + String err; + src_pkg_name = find_export_template("macos.zip", &err); + if (src_pkg_name.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); + return ERR_FILE_NOT_FOUND; + } + } + + if (!DirAccess::exists(p_path.get_base_dir())) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("The given export path doesn't exist.")); + return ERR_FILE_BAD_PATH; + } + + Ref io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + + if (ep.step(TTR("Creating app bundle"), 0)) { + return ERR_SKIP; + } + + unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); + if (!src_pkg_zip) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not find template app to export: \"%s\"."), src_pkg_name)); + return ERR_FILE_NOT_FOUND; + } + + int ret = unzGoToFirstFile(src_pkg_zip); + + String binary_to_use = "godot_macos_" + String(p_debug ? "debug" : "release") + ".64"; + + String pkg_name; + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + } else { + pkg_name = "Unnamed"; + } + + pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); + + String export_format; + if (use_dmg() && p_path.ends_with("dmg")) { + export_format = "dmg"; + } else if (p_path.ends_with("zip")) { + export_format = "zip"; + } else if (p_path.ends_with("app")) { + export_format = "app"; + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); + return ERR_CANT_CREATE; + } + + // Create our application bundle. + String tmp_app_dir_name = pkg_name + ".app"; + String tmp_base_path_name; + String tmp_app_path_name; + String scr_path; + if (export_format == "app") { + tmp_base_path_name = p_path.get_base_dir(); + tmp_app_path_name = p_path; + scr_path = p_path.get_basename() + ".command"; + } else { + tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name); + tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name); + scr_path = tmp_base_path_name.plus_file(pkg_name + ".command"); + } + + print_verbose("Exporting to " + tmp_app_path_name); + + Error err = OK; + + Ref tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name); + if (tmp_app_dir.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory: \"%s\"."), tmp_base_path_name)); + err = ERR_CANT_CREATE; + } + + DirAccess::remove_file_or_error(scr_path); + if (DirAccess::exists(tmp_app_path_name)) { + String old_dir = tmp_app_dir->get_current_dir(); + if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { + tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(old_dir); + } + } + + Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables"); + + // Create our folder structure. + if (err == OK) { + print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/MacOS")); + } + } + + if (err == OK) { + print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Frameworks")); + } + } + + if ((err == OK) && helpers.size() > 0) { + print_line("Creating " + tmp_app_path_name + "/Contents/Helpers"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Helpers"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Helpers")); + } + } + + if (err == OK) { + print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Resources")); + } + } + + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); + Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); + Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); + Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized"); + Dictionary address_book_usage_descriptions = p_preset->get("privacy/address_book_usage_description_localized"); + Dictionary calendar_usage_descriptions = p_preset->get("privacy/calendar_usage_description_localized"); + Dictionary photos_library_usage_descriptions = p_preset->get("privacy/photos_library_usage_description_localized"); + Dictionary desktop_folder_usage_descriptions = p_preset->get("privacy/desktop_folder_usage_description_localized"); + Dictionary documents_folder_usage_descriptions = p_preset->get("privacy/documents_folder_usage_description_localized"); + Dictionary downloads_folder_usage_descriptions = p_preset->get("privacy/downloads_folder_usage_description_localized"); + Dictionary network_volumes_usage_descriptions = p_preset->get("privacy/network_volumes_usage_description_localized"); + Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized"); + Dictionary copyrights = p_preset->get("application/copyright_localized"); + + Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; + tmp_app_dir->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); + if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { + f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { + f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) { + f->store_line("NSLocationUsageDescription = \"" + p_preset->get("privacy/location_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { + f->store_line("NSContactsUsageDescription = \"" + p_preset->get("privacy/address_book_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { + f->store_line("NSCalendarsUsageDescription = \"" + p_preset->get("privacy/calendar_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { + f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photos_library_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) { + f->store_line("NSDesktopFolderUsageDescription = \"" + p_preset->get("privacy/desktop_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) { + f->store_line("NSDocumentsFolderUsageDescription = \"" + p_preset->get("privacy/documents_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) { + f->store_line("NSDownloadsFolderUsageDescription = \"" + p_preset->get("privacy/downloads_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) { + f->store_line("NSNetworkVolumesUsageDescription = \"" + p_preset->get("privacy/network_volumes_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) { + f->store_line("NSRemovableVolumesUsageDescription = \"" + p_preset->get("privacy/removable_volumes_usage_description").operator String() + "\";"); + } + f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";"); + } + + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String lang = tr->get_locale(); + String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; + tmp_app_dir->make_dir_recursive(fname); + Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); + if (appnames.has(lang)) { + f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); + } + if (microphone_usage_descriptions.has(lang)) { + f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); + } + if (camera_usage_descriptions.has(lang)) { + f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); + } + if (location_usage_descriptions.has(lang)) { + f->store_line("NSLocationUsageDescription = \"" + location_usage_descriptions[lang].operator String() + "\";"); + } + if (address_book_usage_descriptions.has(lang)) { + f->store_line("NSContactsUsageDescription = \"" + address_book_usage_descriptions[lang].operator String() + "\";"); + } + if (calendar_usage_descriptions.has(lang)) { + f->store_line("NSCalendarsUsageDescription = \"" + calendar_usage_descriptions[lang].operator String() + "\";"); + } + if (photos_library_usage_descriptions.has(lang)) { + f->store_line("NSPhotoLibraryUsageDescription = \"" + photos_library_usage_descriptions[lang].operator String() + "\";"); + } + if (desktop_folder_usage_descriptions.has(lang)) { + f->store_line("NSDesktopFolderUsageDescription = \"" + desktop_folder_usage_descriptions[lang].operator String() + "\";"); + } + if (documents_folder_usage_descriptions.has(lang)) { + f->store_line("NSDocumentsFolderUsageDescription = \"" + documents_folder_usage_descriptions[lang].operator String() + "\";"); + } + if (downloads_folder_usage_descriptions.has(lang)) { + f->store_line("NSDownloadsFolderUsageDescription = \"" + downloads_folder_usage_descriptions[lang].operator String() + "\";"); + } + if (network_volumes_usage_descriptions.has(lang)) { + f->store_line("NSNetworkVolumesUsageDescription = \"" + network_volumes_usage_descriptions[lang].operator String() + "\";"); + } + if (removable_volumes_usage_descriptions.has(lang)) { + f->store_line("NSRemovableVolumesUsageDescription = \"" + removable_volumes_usage_descriptions[lang].operator String() + "\";"); + } + if (copyrights.has(lang)) { + f->store_line("NSHumanReadableCopyright = \"" + copyrights[lang].operator String() + "\";"); + } + } + } + } + + // Now process our template. + bool found_binary = false; + Vector dylibs_found; + + while (ret == UNZ_OK && err == OK) { + bool is_execute = false; + + // Get filename. + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + break; + } + + String file = String::utf8(fname); + + Vector data; + data.resize(info.uncompressed_size); + + // Read. + unzOpenCurrentFile(src_pkg_zip); + unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); + unzCloseCurrentFile(src_pkg_zip); + + // Write. + file = file.replace_first("macos_template.app/", ""); + + if (((info.external_fa >> 16L) & 0120000) == 0120000) { +#ifndef UNIX_ENABLED + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!")); +#endif + // Handle symlinks in the archive. + file = tmp_app_path_name.plus_file(file); + if (err == OK) { + err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } + } + if (err == OK) { + String lnk_data = String::utf8((const char *)data.ptr(), data.size()); + err = tmp_app_dir->create_link(lnk_data, file); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not created symlink \"%s\" -> \"%s\"."), lnk_data, file)); + } + print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data)); + } + + ret = unzGoToNextFile(src_pkg_zip); + continue; // next + } + + if (file == "Contents/Info.plist") { + _fix_plist(p_preset, data, pkg_name); + } + + if (file.begins_with("Contents/MacOS/godot_")) { + if (file != "Contents/MacOS/" + binary_to_use) { + ret = unzGoToNextFile(src_pkg_zip); + continue; // skip + } + found_binary = true; + is_execute = true; + file = "Contents/MacOS/" + pkg_name; + } + + if (file == "Contents/Resources/icon.icns") { + // See if there is an icon. + String iconpath; + if (p_preset->get("application/icon") != "") { + iconpath = p_preset->get("application/icon"); + } else { + iconpath = ProjectSettings::get_singleton()->get("application/config/icon"); + } + + if (!iconpath.is_empty()) { + if (iconpath.get_extension() == "icns") { + Ref icon = FileAccess::open(iconpath, FileAccess::READ); + if (icon.is_valid()) { + data.resize(icon->get_length()); + icon->get_buffer(&data.write[0], icon->get_length()); + } + } else { + Ref icon; + icon.instantiate(); + icon->load(iconpath); + if (!icon->is_empty()) { + _make_icon(icon, data); + } + } + } + } + + if (data.size() > 0) { + if (file.find("/data.mono.macos.64.release_debug/") != -1) { + if (!p_debug) { + ret = unzGoToNextFile(src_pkg_zip); + continue; // skip + } + file = file.replace("/data.mono.macos.64.release_debug/", "/GodotSharp/"); + } + if (file.find("/data.mono.macos.64.release/") != -1) { + if (p_debug) { + ret = unzGoToNextFile(src_pkg_zip); + continue; // skip + } + file = file.replace("/data.mono.macos.64.release/", "/GodotSharp/"); + } + + if (file.ends_with(".dylib")) { + dylibs_found.push_back(file); + } + + print_verbose("ADDING: " + file + " size: " + itos(data.size())); + + // Write it into our application bundle. + file = tmp_app_path_name.plus_file(file); + if (err == OK) { + err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } + } + if (err == OK) { + Ref f = FileAccess::open(file, FileAccess::WRITE); + if (f.is_valid()) { + f->store_buffer(data.ptr(), data.size()); + f.unref(); + if (is_execute) { + // chmod with 0755 if the file is executable. + FileAccess::set_unix_permissions(file, 0755); + } + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file)); + err = ERR_CANT_CREATE; + } + } + } + + ret = unzGoToNextFile(src_pkg_zip); + } + + // We're done with our source zip. + unzClose(src_pkg_zip); + + if (!found_binary) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template binary \"%s\" not found. It might be missing from your template archive."), binary_to_use)); + err = ERR_FILE_NOT_FOUND; + } + + // Save console script. + if (err == OK) { + int con_scr = p_preset->get("debug/export_console_script"); + if ((con_scr == 1 && p_debug) || (con_scr == 2)) { + err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path); + FileAccess::set_unix_permissions(scr_path, 0755); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console script.")); + } + } + } + + if (err == OK) { + if (ep.step(TTR("Making PKG"), 1)) { + return ERR_SKIP; + } + + String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; + Vector shared_objects; + err = save_pack(p_preset, p_debug, pack_path, &shared_objects); + + // See if we can code sign our new package. + bool sign_enabled = p_preset->get("codesign/enable"); + + String ent_path = p_preset->get("codesign/entitlements/custom_file"); + String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements"); + if (sign_enabled && (ent_path.is_empty())) { + ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); + + Ref ent_f = FileAccess::open(ent_path, FileAccess::WRITE); + if (ent_f.is_valid()) { + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line(""); + if (Engine::get_singleton()->has_singleton("GodotSharp")) { + // These entitlements are required to run managed code, and are always enabled in Mono builds. + ent_f->store_line("com.apple.security.cs.allow-jit"); + ent_f->store_line(""); + ent_f->store_line("com.apple.security.cs.allow-unsigned-executable-memory"); + ent_f->store_line(""); + ent_f->store_line("com.apple.security.cs.allow-dyld-environment-variables"); + ent_f->store_line(""); + } else { + if ((bool)p_preset->get("codesign/entitlements/allow_jit_code_execution")) { + ent_f->store_line("com.apple.security.cs.allow-jit"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/allow_unsigned_executable_memory")) { + ent_f->store_line("com.apple.security.cs.allow-unsigned-executable-memory"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/allow_dyld_environment_variables")) { + ent_f->store_line("com.apple.security.cs.allow-dyld-environment-variables"); + ent_f->store_line(""); + } + } + + if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) { + ent_f->store_line("com.apple.security.cs.disable-library-validation"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/audio_input")) { + ent_f->store_line("com.apple.security.device.audio-input"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/camera")) { + ent_f->store_line("com.apple.security.device.camera"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/location")) { + ent_f->store_line("com.apple.security.personal-information.location"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/address_book")) { + ent_f->store_line("com.apple.security.personal-information.addressbook"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/calendars")) { + ent_f->store_line("com.apple.security.personal-information.calendars"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/photos_library")) { + ent_f->store_line("com.apple.security.personal-information.photos-library"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/apple_events")) { + ent_f->store_line("com.apple.security.automation.apple-events"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/debugging")) { + ent_f->store_line("com.apple.security.get-task-allow"); + ent_f->store_line(""); + } + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { + ent_f->store_line("com.apple.security.app-sandbox"); + ent_f->store_line(""); + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_server")) { + ent_f->store_line("com.apple.security.network.server"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_client")) { + ent_f->store_line("com.apple.security.network.client"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_usb")) { + ent_f->store_line("com.apple.security.device.usb"); + ent_f->store_line(""); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_bluetooth")) { + ent_f->store_line("com.apple.security.device.bluetooth"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 1) { + ent_f->store_line("com.apple.security.files.downloads.read-only"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 2) { + ent_f->store_line("com.apple.security.files.downloads.read-write"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 1) { + ent_f->store_line("com.apple.security.files.pictures.read-only"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 2) { + ent_f->store_line("com.apple.security.files.pictures.read-write"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 1) { + ent_f->store_line("com.apple.security.files.music.read-only"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 2) { + ent_f->store_line("com.apple.security.files.music.read-write"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 1) { + ent_f->store_line("com.apple.security.files.movies.read-only"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 2) { + ent_f->store_line("com.apple.security.files.movies.read-write"); + ent_f->store_line(""); + } + } + + ent_f->store_line(""); + ent_f->store_line(""); + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create entitlements file.")); + err = ERR_CANT_CREATE; + } + + if ((err == OK) && helpers.size() > 0) { + ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE); + if (ent_f.is_valid()) { + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line("com.apple.security.app-sandbox"); + ent_f->store_line(""); + ent_f->store_line("com.apple.security.inherit"); + ent_f->store_line(""); + ent_f->store_line(""); + ent_f->store_line(""); + } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create helper entitlements file.")); + err = ERR_CANT_CREATE; + } + } + } + + if ((err == OK) && helpers.size() > 0) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < helpers.size(); i++) { + String hlp_path = helpers[i]; + err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); + if (err == OK && sign_enabled) { + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false); + } + FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); + } + } + + bool ad_hoc = true; + if (err == OK) { +#ifdef MACOS_ENABLED + String sign_identity = p_preset->get("codesign/identity"); +#else + String sign_identity = "-"; +#endif + ad_hoc = (sign_identity == "" || sign_identity == "-"); + bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); + if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); + err = ERR_CANT_CREATE; + } + } + + if (err == OK) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); + if (shared_objects[i].target.is_empty()) { + String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(); + err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true); + } else { + String path_in_app = tmp_app_path_name.plus_file(shared_objects[i].target).plus_file(src_path.get_file()); + err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false); + } + if (err != OK) { + break; + } + } + + Vector> export_plugins{ EditorExport::get_singleton()->get_export_plugins() }; + for (int i = 0; i < export_plugins.size(); ++i) { + err = _export_macos_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path); + if (err != OK) { + break; + } + } + } + + if (sign_enabled) { + for (int i = 0; i < dylibs_found.size(); i++) { + if (err == OK) { + err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path, false); + } + } + } + + if (err == OK && sign_enabled) { + if (ep.step(TTR("Code signing bundle"), 2)) { + return ERR_SKIP; + } + err = _code_sign(p_preset, tmp_app_path_name, ent_path); + } + + if (export_format == "dmg") { + // Create a DMG. + if (err == OK) { + if (ep.step(TTR("Making DMG"), 3)) { + return ERR_SKIP; + } + err = _create_dmg(p_path, pkg_name, tmp_base_path_name); + } + // Sign DMG. + if (err == OK && sign_enabled && !ad_hoc) { + if (ep.step(TTR("Code signing DMG"), 3)) { + return ERR_SKIP; + } + err = _code_sign(p_preset, p_path, ent_path, false); + } + } else if (export_format == "zip") { + // Create ZIP. + if (err == OK) { + if (ep.step(TTR("Making ZIP"), 3)) { + return ERR_SKIP; + } + if (FileAccess::exists(p_path)) { + OS::get_singleton()->move_to_trash(p_path); + } + + Ref io_fa_dst; + zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); + zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); + + _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name); + + zipClose(zip, nullptr); + } + } + +#ifdef MACOS_ENABLED + bool noto_enabled = p_preset->get("notarization/enable"); + if (err == OK && noto_enabled) { + if (export_format == "app") { + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); + } else { + if (ep.step(TTR("Sending archive for notarization"), 4)) { + return ERR_SKIP; + } + err = _notarize(p_preset, p_path); + } + } +#endif + + // Clean up temporary entitlements files. + DirAccess::remove_file_or_error(hlp_ent_path); + + // Clean up temporary .app dir and generated entitlements. + if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") { + tmp_app_dir->remove(ent_path); + } + if (export_format != "app") { + if (tmp_app_dir->change_dir(tmp_base_path_name) == OK) { + tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(".."); + tmp_app_dir->remove(pkg_name); + } + } + } + + return err; +} + +void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { + String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder); + + Ref da = DirAccess::open(dir); + da->list_dir_begin(); + String f = da->get_next(); + while (!f.is_empty()) { + if (f == "." || f == "..") { + f = da->get_next(); + continue; + } + if (da->is_link(f)) { + 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.minute; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.second; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + // 0120000: symbolic link type + // 0000755: permissions rwxr-xr-x + // 0000644: permissions rw-r--r-- + uint32_t _mode = 0120644; + 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); + + String target = da->read_link(f); + zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); + zipCloseFileInZip(p_zip); + } else 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)) || p_folder.ends_with("Helpers") || f.ends_with(".command"); + + 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.minute; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.second; + 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); + + Ref fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); + if (fa.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); + return; + } + const int bufsize = 16384; + uint8_t buf[bufsize]; + + while (true) { + uint64_t got = fa->get_buffer(buf, bufsize); + if (got == 0) { + break; + } + zipWriteInFileInZip(p_zip, buf, got); + } + + zipCloseFileInZip(p_zip); + } + f = da->get_next(); + } + da->list_dir_end(); +} + +bool EditorExportPlatformMacOS::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err; + bool valid = false; + + // Look for export templates (custom templates). + 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"; + } + } + 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"; + } + } + + // Look for export templates (official templates, check only is custom templates are not set). + if (!dvalid || !rvalid) { + dvalid = exists_export_template("macos.zip", &err); + rvalid = dvalid; // Both in the same ZIP. + } + + valid = dvalid || rvalid; + r_missing_templates = !valid; + + 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"); + +#ifdef MACOS_ENABLED + bool noto_enabled = p_preset->get("notarization/enable"); + bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-")); + + if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) { + err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n"; + } + if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) { + err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n"; + } + + if (noto_enabled) { + if (ad_hoc) { + err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n"; + valid = false; + } + if (!sign_enabled) { + err += TTR("Notarization: Code signing is required for notarization.") + "\n"; + valid = false; + } + if (!(bool)p_preset->get("codesign/hardened_runtime")) { + err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n"; + valid = false; + } + if (!(bool)p_preset->get("codesign/timestamp")) { + err += TTR("Notarization: Timestamping is required for notarization.") + "\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; + } + } else { + err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; + if (!sign_enabled) { + err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; + } else { + if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) { + err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n"; + } + if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) { + err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n"; + } + } + } +#else + err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; + if (!sign_enabled) { + err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; + } +#endif + + if (sign_enabled) { + if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { + err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + if ((bool)p_preset->get("codesign/entitlements/camera") && ((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { + err += TTR("Privacy: Camera access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + if ((bool)p_preset->get("codesign/entitlements/location") && ((String)p_preset->get("privacy/location_usage_description")).is_empty()) { + err += TTR("Privacy: Location information access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + if ((bool)p_preset->get("codesign/entitlements/address_book") && ((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { + err += TTR("Privacy: Address book access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + if ((bool)p_preset->get("codesign/entitlements/calendars") && ((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { + err += TTR("Privacy: Calendar access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + if ((bool)p_preset->get("codesign/entitlements/photos_library") && ((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { + err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n"; + valid = false; + } + } + + if (!err.is_empty()) { + r_error = err; + } + return valid; +} + +EditorExportPlatformMacOS::EditorExportPlatformMacOS() { + logo = ImageTexture::create_from_image(memnew(Image(_macos_logo))); +} + +EditorExportPlatformMacOS::~EditorExportPlatformMacOS() { +} diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h new file mode 100644 index 0000000000..410ec22545 --- /dev/null +++ b/platform/macos/export/export_plugin.h @@ -0,0 +1,137 @@ +/*************************************************************************/ +/* export_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 MACOS_EXPORT_PLUGIN_H +#define MACOS_EXPORT_PLUGIN_H + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/marshalls.h" +#include "core/io/resource_saver.h" +#include "core/io/zip_io.h" +#include "core/os/os.h" +#include "core/version.h" +#include "editor/editor_export.h" +#include "editor/editor_settings.h" +#include "platform/macos/logo.gen.h" + +#include + +class EditorExportPlatformMacOS : public EditorExportPlatform { + GDCLASS(EditorExportPlatformMacOS, EditorExportPlatform); + + int version_code = 0; + + Ref logo; + + void _fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary); + void _make_icon(const Ref &p_icon, Vector &p_data); + + Error _notarize(const Ref &p_preset, const String &p_path); + Error _code_sign(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true); + Error _code_sign_directory(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true); + Error _copy_and_sign_files(Ref &dir_access, const String &p_src_path, const String &p_in_app_path, + bool p_sign_enabled, const Ref &p_preset, const String &p_ent_path, + bool p_should_error_on_non_code_sign); + Error _export_macos_plugins_for(Ref p_editor_export_plugin, const String &p_app_path_name, + Ref &dir_access, bool p_sign_enabled, const Ref &p_preset, + const String &p_ent_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); + Error _export_debug_script(const Ref &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); + + bool use_codesign() const { return true; } +#ifdef MACOS_ENABLED + bool use_dmg() const { return true; } +#else + 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 (!(is_ascii_alphanumeric_char(c) || 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 &p_preset, List *r_features) override; + virtual void get_export_options(List *r_options) override; + virtual bool get_export_option_visibility(const String &p_option, const HashMap &p_options) const override; + +public: + virtual String get_name() const override { return "macOS"; } + virtual String get_os_name() const override { return "macOS"; } + virtual Ref get_logo() const override { return logo; } + + virtual List get_binary_extensions(const Ref &p_preset) const override { + List list; + if (use_dmg()) { + list.push_back("dmg"); + } + list.push_back("zip"); + list.push_back("app"); + return list; + } + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + + virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; + + virtual void get_platform_features(List *r_features) override { + r_features->push_back("pc"); + r_features->push_back("s3tc"); + r_features->push_back("macos"); + } + + virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { + } + + EditorExportPlatformMacOS(); + ~EditorExportPlatformMacOS(); +}; + +#endif diff --git a/platform/macos/export/lipo.cpp b/platform/macos/export/lipo.cpp new file mode 100644 index 0000000000..82baf18c52 --- /dev/null +++ b/platform/macos/export/lipo.cpp @@ -0,0 +1,236 @@ +/*************************************************************************/ +/* lipo.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. + +#include "lipo.h" + +#ifdef MODULE_REGEX_ENABLED + +bool LipO::is_lipo(const String &p_path) { + Ref fb = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); + uint32_t magic = fb->get_32(); + return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf); +} + +bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_files) { + close(); + + fa = FileAccess::open(p_output_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_output_path)); + + uint64_t max_size = 0; + for (int i = 0; i < p_files.size(); i++) { + MachO mh; + if (!mh.open_file(p_files[i])) { + ERR_FAIL_V_MSG(false, vformat("LipO: Invalid MachO file: \"%s.\"", p_files[i])); + } + + FatArch arch; + arch.cputype = mh.get_cputype(); + arch.cpusubtype = mh.get_cpusubtype(); + arch.offset = 0; + arch.size = mh.get_size(); + arch.align = mh.get_align(); + max_size += arch.size; + + archs.push_back(arch); + + Ref fb = FileAccess::open(p_files[i], FileAccess::READ); + if (fb.is_null()) { + close(); + ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); + } + } + + // Write header. + bool is_64 = (max_size >= std::numeric_limits::max()); + if (is_64) { + fa->store_32(0xbfbafeca); + } else { + fa->store_32(0xbebafeca); + } + fa->store_32(BSWAP32(archs.size())); + uint64_t offset = archs.size() * (is_64 ? 32 : 20) + 8; + for (int i = 0; i < archs.size(); i++) { + archs.write[i].offset = offset + PAD(offset, uint64_t(1) << archs[i].align); + if (is_64) { + fa->store_32(BSWAP32(archs[i].cputype)); + fa->store_32(BSWAP32(archs[i].cpusubtype)); + fa->store_64(BSWAP64(archs[i].offset)); + fa->store_64(BSWAP64(archs[i].size)); + fa->store_32(BSWAP32(archs[i].align)); + fa->store_32(0); + } else { + fa->store_32(BSWAP32(archs[i].cputype)); + fa->store_32(BSWAP32(archs[i].cpusubtype)); + fa->store_32(BSWAP32(archs[i].offset)); + fa->store_32(BSWAP32(archs[i].size)); + fa->store_32(BSWAP32(archs[i].align)); + } + offset = archs[i].offset + archs[i].size; + } + + // Write files and padding. + for (int i = 0; i < archs.size(); i++) { + Ref fb = FileAccess::open(p_files[i], FileAccess::READ); + if (fb.is_null()) { + close(); + ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); + } + uint64_t cur = fa->get_position(); + for (uint64_t j = cur; j < archs[i].offset; j++) { + fa->store_8(0); + } + int pages = archs[i].size / 4096; + int remain = archs[i].size % 4096; + unsigned char step[4096]; + for (int j = 0; j < pages; j++) { + uint64_t br = fb->get_buffer(step, 4096); + if (br > 0) { + fa->store_buffer(step, br); + } + } + uint64_t br = fb->get_buffer(step, remain); + if (br > 0) { + fa->store_buffer(step, br); + } + } + return true; +} + +bool LipO::open_file(const String &p_path) { + close(); + + fa = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); + + uint32_t magic = fa->get_32(); + if (magic == 0xbebafeca) { + // 32-bit fat binary, bswap. + uint32_t nfat_arch = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < nfat_arch; i++) { + FatArch arch; + arch.cputype = BSWAP32(fa->get_32()); + arch.cpusubtype = BSWAP32(fa->get_32()); + arch.offset = BSWAP32(fa->get_32()); + arch.size = BSWAP32(fa->get_32()); + arch.align = BSWAP32(fa->get_32()); + + archs.push_back(arch); + } + } else if (magic == 0xcafebabe) { + // 32-bit fat binary. + uint32_t nfat_arch = fa->get_32(); + for (uint32_t i = 0; i < nfat_arch; i++) { + FatArch arch; + arch.cputype = fa->get_32(); + arch.cpusubtype = fa->get_32(); + arch.offset = fa->get_32(); + arch.size = fa->get_32(); + arch.align = fa->get_32(); + + archs.push_back(arch); + } + } else if (magic == 0xbfbafeca) { + // 64-bit fat binary, bswap. + uint32_t nfat_arch = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < nfat_arch; i++) { + FatArch arch; + arch.cputype = BSWAP32(fa->get_32()); + arch.cpusubtype = BSWAP32(fa->get_32()); + arch.offset = BSWAP64(fa->get_64()); + arch.size = BSWAP64(fa->get_64()); + arch.align = BSWAP32(fa->get_32()); + fa->get_32(); // Skip, reserved. + + archs.push_back(arch); + } + } else if (magic == 0xcafebabf) { + // 64-bit fat binary. + uint32_t nfat_arch = fa->get_32(); + for (uint32_t i = 0; i < nfat_arch; i++) { + FatArch arch; + arch.cputype = fa->get_32(); + arch.cpusubtype = fa->get_32(); + arch.offset = fa->get_64(); + arch.size = fa->get_64(); + arch.align = fa->get_32(); + fa->get_32(); // Skip, reserved. + + archs.push_back(arch); + } + } else { + close(); + ERR_FAIL_V_MSG(false, vformat("LipO: Invalid fat binary: \"%s\".", p_path)); + } + return true; +} + +int LipO::get_arch_count() const { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened."); + return archs.size(); +} + +bool LipO::extract_arch(int p_index, const String &p_path) { + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "LipO: File not opened."); + ERR_FAIL_INDEX_V(p_index, archs.size(), false); + + Ref fb = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); + + fa->seek(archs[p_index].offset); + + int pages = archs[p_index].size / 4096; + int remain = archs[p_index].size % 4096; + unsigned char step[4096]; + for (int i = 0; i < pages; i++) { + uint64_t br = fa->get_buffer(step, 4096); + if (br > 0) { + fb->store_buffer(step, br); + } + } + uint64_t br = fa->get_buffer(step, remain); + if (br > 0) { + fb->store_buffer(step, br); + } + return true; +} + +void LipO::close() { + archs.clear(); +} + +LipO::~LipO() { + close(); +} + +#endif // MODULE_REGEX_ENABLED diff --git a/platform/macos/export/lipo.h b/platform/macos/export/lipo.h new file mode 100644 index 0000000000..0e419be17e --- /dev/null +++ b/platform/macos/export/lipo.h @@ -0,0 +1,76 @@ +/*************************************************************************/ +/* lipo.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +// Universal / Universal 2 fat binary file creator and extractor. + +#ifndef LIPO_H +#define LIPO_H + +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" +#include "modules/modules_enabled.gen.h" // For regex. + +#include "macho.h" + +#ifdef MODULE_REGEX_ENABLED + +class LipO : public RefCounted { + struct FatArch { + uint32_t cputype; + uint32_t cpusubtype; + uint64_t offset; + uint64_t size; + uint32_t align; + }; + + Ref fa; + Vector archs; + + static inline size_t PAD(size_t s, size_t a) { + return (a - s % a); + } + +public: + static bool is_lipo(const String &p_path); + + bool create_file(const String &p_output_path, const PackedStringArray &p_files); + + bool open_file(const String &p_path); + int get_arch_count() const; + bool extract_arch(int p_index, const String &p_path); + + void close(); + + ~LipO(); +}; + +#endif // MODULE_REGEX_ENABLED + +#endif // LIPO_H diff --git a/platform/macos/export/macho.cpp b/platform/macos/export/macho.cpp new file mode 100644 index 0000000000..e6e67eff06 --- /dev/null +++ b/platform/macos/export/macho.cpp @@ -0,0 +1,548 @@ +/*************************************************************************/ +/* macho.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. + +#include "macho.h" + +#ifdef MODULE_REGEX_ENABLED + +uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) { + uint32_t align = p_max; + if (p_vmaddr != 0) { + uint64_t seg_align = 1; + align = 0; + while ((seg_align & p_vmaddr) == 0) { + seg_align = seg_align << 1; + align++; + } + align = CLAMP(align, p_min, p_max); + } + return align; +} + +bool MachO::alloc_signature(uint64_t p_size) { + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); + if (signature_offset != 0) { + // Nothing to do, already have signature load command. + return true; + } + if (lc_limit == 0 || lc_limit + 16 > exe_base) { + ERR_FAIL_V_MSG(false, "MachO: Can't allocate signature load command, please use \"codesign_allocate\" utility first."); + } else { + // Add signature load command. + signature_offset = lc_limit; + + fa->seek(lc_limit); + LoadCommandHeader lc; + lc.cmd = LC_CODE_SIGNATURE; + lc.cmdsize = 16; + if (swap) { + lc.cmdsize = BSWAP32(lc.cmdsize); + } + fa->store_buffer((const uint8_t *)&lc, sizeof(LoadCommandHeader)); + + uint32_t lc_offset = fa->get_length() + PAD(fa->get_length(), 16); + uint32_t lc_size = 0; + if (swap) { + lc_offset = BSWAP32(lc_offset); + lc_size = BSWAP32(lc_size); + } + fa->store_32(lc_offset); + fa->store_32(lc_size); + + // Write new command number. + fa->seek(0x10); + uint32_t ncmds = fa->get_32(); + uint32_t cmdssize = fa->get_32(); + if (swap) { + ncmds = BSWAP32(ncmds); + cmdssize = BSWAP32(cmdssize); + } + ncmds += 1; + cmdssize += 16; + if (swap) { + ncmds = BSWAP32(ncmds); + cmdssize = BSWAP32(cmdssize); + } + fa->seek(0x10); + fa->store_32(ncmds); + fa->store_32(cmdssize); + + lc_limit = lc_limit + sizeof(LoadCommandHeader) + 8; + + return true; + } +} + +bool MachO::is_macho(const String &p_path) { + Ref fb = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); + uint32_t magic = fb->get_32(); + return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf); +} + +bool MachO::open_file(const String &p_path) { + fa = FileAccess::open(p_path, FileAccess::READ_WRITE); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); + uint32_t magic = fa->get_32(); + MachHeader mach_header; + + // Read MachO header. + swap = (magic == 0xcffaedfe || magic == 0xcefaedfe); + if (magic == 0xcefaedfe || magic == 0xfeedface) { + // Thin 32-bit binary. + fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); + } else if (magic == 0xcffaedfe || magic == 0xfeedfacf) { + // Thin 64-bit binary. + fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); + fa->get_32(); // Skip extra reserved field. + } else { + ERR_FAIL_V_MSG(false, vformat("MachO: File is not a valid MachO binary: \"%s\".", p_path)); + } + + if (swap) { + mach_header.ncmds = BSWAP32(mach_header.ncmds); + mach_header.cpusubtype = BSWAP32(mach_header.cpusubtype); + mach_header.cputype = BSWAP32(mach_header.cputype); + } + cpusubtype = mach_header.cpusubtype; + cputype = mach_header.cputype; + align = 0; + exe_base = std::numeric_limits::max(); + exe_limit = 0; + lc_limit = 0; + link_edit_offset = 0; + signature_offset = 0; + + // Read load commands. + for (uint32_t i = 0; i < mach_header.ncmds; i++) { + LoadCommandHeader lc; + fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); + if (swap) { + lc.cmd = BSWAP32(lc.cmd); + lc.cmdsize = BSWAP32(lc.cmdsize); + } + uint64_t ps = fa->get_position(); + switch (lc.cmd) { + case LC_SEGMENT: { + LoadCommandSegment lc_seg; + fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); + if (swap) { + lc_seg.nsects = BSWAP32(lc_seg.nsects); + lc_seg.vmaddr = BSWAP32(lc_seg.vmaddr); + lc_seg.vmsize = BSWAP32(lc_seg.vmsize); + } + align = MAX(align, seg_align(lc_seg.vmaddr, 2, 15)); + if (String(lc_seg.segname) == "__TEXT") { + exe_limit = MAX(exe_limit, lc_seg.vmsize); + for (uint32_t j = 0; j < lc_seg.nsects; j++) { + Section lc_sect; + fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section)); + if (String(lc_sect.sectname) == "__text") { + if (swap) { + exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); + } else { + exe_base = MIN(exe_base, lc_sect.offset); + } + } + if (swap) { + align = MAX(align, BSWAP32(lc_sect.align)); + } else { + align = MAX(align, lc_sect.align); + } + } + } else if (String(lc_seg.segname) == "__LINKEDIT") { + link_edit_offset = ps - 8; + } + } break; + case LC_SEGMENT_64: { + LoadCommandSegment64 lc_seg; + fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); + if (swap) { + lc_seg.nsects = BSWAP32(lc_seg.nsects); + lc_seg.vmaddr = BSWAP64(lc_seg.vmaddr); + lc_seg.vmsize = BSWAP64(lc_seg.vmsize); + } + align = MAX(align, seg_align(lc_seg.vmaddr, 3, 15)); + if (String(lc_seg.segname) == "__TEXT") { + exe_limit = MAX(exe_limit, lc_seg.vmsize); + for (uint32_t j = 0; j < lc_seg.nsects; j++) { + Section64 lc_sect; + fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section64)); + if (String(lc_sect.sectname) == "__text") { + if (swap) { + exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); + } else { + exe_base = MIN(exe_base, lc_sect.offset); + } + if (swap) { + align = MAX(align, BSWAP32(lc_sect.align)); + } else { + align = MAX(align, lc_sect.align); + } + } + } + } else if (String(lc_seg.segname) == "__LINKEDIT") { + link_edit_offset = ps - 8; + } + } break; + case LC_CODE_SIGNATURE: { + signature_offset = ps - 8; + } break; + default: { + } break; + } + fa->seek(ps + lc.cmdsize - 8); + lc_limit = ps + lc.cmdsize - 8; + } + + if (exe_limit == 0 || lc_limit == 0) { + ERR_FAIL_V_MSG(false, vformat("MachO: No load commands or executable code found: \"%s\".", p_path)); + } + + return true; +} + +uint64_t MachO::get_exe_base() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return exe_base; +} + +uint64_t MachO::get_exe_limit() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return exe_limit; +} + +int32_t MachO::get_align() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return align; +} + +uint32_t MachO::get_cputype() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return cputype; +} + +uint32_t MachO::get_cpusubtype() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return cpusubtype; +} + +uint64_t MachO::get_size() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + return fa->get_length(); +} + +uint64_t MachO::get_signature_offset() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); + + fa->seek(signature_offset + 8); + if (swap) { + return BSWAP32(fa->get_32()); + } else { + return fa->get_32(); + } +} + +uint64_t MachO::get_code_limit() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + + if (signature_offset == 0) { + return fa->get_length() + PAD(fa->get_length(), 16); + } else { + return get_signature_offset(); + } +} + +uint64_t MachO::get_signature_size() { + ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); + + fa->seek(signature_offset + 12); + if (swap) { + return BSWAP32(fa->get_32()); + } else { + return fa->get_32(); + } +} + +bool MachO::is_signed() { + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); + if (signature_offset == 0) { + return false; + } + + fa->seek(get_signature_offset()); + uint32_t magic = BSWAP32(fa->get_32()); + if (magic != 0xfade0cc0) { + return false; // No SuperBlob found. + } + fa->get_32(); // Skip size field, unused. + uint32_t count = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < count; i++) { + uint32_t index_type = BSWAP32(fa->get_32()); + uint32_t offset = BSWAP32(fa->get_32()); + if (index_type == 0x00000000) { // CodeDirectory index type. + fa->seek(get_signature_offset() + offset + 12); + uint32_t flags = BSWAP32(fa->get_32()); + if (flags & 0x20000) { + return false; // Found CD, linker-signed. + } else { + return true; // Found CD, not linker-signed. + } + } + } + return false; // No CD found. +} + +PackedByteArray MachO::get_cdhash_sha1() { + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); + if (signature_offset == 0) { + return PackedByteArray(); + } + + fa->seek(get_signature_offset()); + uint32_t magic = BSWAP32(fa->get_32()); + if (magic != 0xfade0cc0) { + return PackedByteArray(); // No SuperBlob found. + } + fa->get_32(); // Skip size field, unused. + uint32_t count = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < count; i++) { + fa->get_32(); // Index type, skip. + uint32_t offset = BSWAP32(fa->get_32()); + uint64_t pos = fa->get_position(); + + fa->seek(get_signature_offset() + offset); + uint32_t cdmagic = BSWAP32(fa->get_32()); + uint32_t cdsize = BSWAP32(fa->get_32()); + if (cdmagic == 0xfade0c02) { // CodeDirectory. + fa->seek(get_signature_offset() + offset + 36); + uint8_t hash_size = fa->get_8(); + uint8_t hash_type = fa->get_8(); + if (hash_size == 0x14 && hash_type == 0x01) { /* SHA-1 */ + PackedByteArray hash; + hash.resize(0x14); + + fa->seek(get_signature_offset() + offset); + PackedByteArray blob; + blob.resize(cdsize); + fa->get_buffer(blob.ptrw(), cdsize); + + CryptoCore::SHA1Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; + } + } + fa->seek(pos); + } + return PackedByteArray(); +} + +PackedByteArray MachO::get_cdhash_sha256() { + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); + if (signature_offset == 0) { + return PackedByteArray(); + } + + fa->seek(get_signature_offset()); + uint32_t magic = BSWAP32(fa->get_32()); + if (magic != 0xfade0cc0) { + return PackedByteArray(); // No SuperBlob found. + } + fa->get_32(); // Skip size field, unused. + uint32_t count = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < count; i++) { + fa->get_32(); // Index type, skip. + uint32_t offset = BSWAP32(fa->get_32()); + uint64_t pos = fa->get_position(); + + fa->seek(get_signature_offset() + offset); + uint32_t cdmagic = BSWAP32(fa->get_32()); + uint32_t cdsize = BSWAP32(fa->get_32()); + if (cdmagic == 0xfade0c02) { // CodeDirectory. + fa->seek(get_signature_offset() + offset + 36); + uint8_t hash_size = fa->get_8(); + uint8_t hash_type = fa->get_8(); + if (hash_size == 0x20 && hash_type == 0x02) { /* SHA-256 */ + PackedByteArray hash; + hash.resize(0x20); + + fa->seek(get_signature_offset() + offset); + PackedByteArray blob; + blob.resize(cdsize); + fa->get_buffer(blob.ptrw(), cdsize); + + CryptoCore::SHA256Context ctx; + ctx.start(); + ctx.update(blob.ptr(), blob.size()); + ctx.finish(hash.ptrw()); + + return hash; + } + } + fa->seek(pos); + } + return PackedByteArray(); +} + +PackedByteArray MachO::get_requirements() { + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); + if (signature_offset == 0) { + return PackedByteArray(); + } + + fa->seek(get_signature_offset()); + uint32_t magic = BSWAP32(fa->get_32()); + if (magic != 0xfade0cc0) { + return PackedByteArray(); // No SuperBlob found. + } + fa->get_32(); // Skip size field, unused. + uint32_t count = BSWAP32(fa->get_32()); + for (uint32_t i = 0; i < count; i++) { + fa->get_32(); // Index type, skip. + uint32_t offset = BSWAP32(fa->get_32()); + uint64_t pos = fa->get_position(); + + fa->seek(get_signature_offset() + offset); + uint32_t rqmagic = BSWAP32(fa->get_32()); + uint32_t rqsize = BSWAP32(fa->get_32()); + if (rqmagic == 0xfade0c01) { // Requirements. + PackedByteArray blob; + fa->seek(get_signature_offset() + offset); + blob.resize(rqsize); + fa->get_buffer(blob.ptrw(), rqsize); + return blob; + } + fa->seek(pos); + } + return PackedByteArray(); +} + +const Ref MachO::get_file() const { + return fa; +} + +Ref MachO::get_file() { + return fa; +} + +bool MachO::set_signature_size(uint64_t p_size) { + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); + + // Ensure signature load command exists. + ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found."); + ERR_FAIL_COND_V_MSG(!alloc_signature(p_size), false, "MachO: Can't allocate signature load command."); + + // Update signature load command. + uint64_t old_size = get_signature_size(); + uint64_t new_size = p_size + PAD(p_size, 16384); + + if (new_size <= old_size) { + fa->seek(get_signature_offset()); + for (uint64_t i = 0; i < old_size; i++) { + fa->store_8(0x00); + } + return true; + } + + fa->seek(signature_offset + 12); + if (swap) { + fa->store_32(BSWAP32(new_size)); + } else { + fa->store_32(new_size); + } + + uint64_t end = get_signature_offset() + new_size; + + // Update "__LINKEDIT" segment. + LoadCommandHeader lc; + fa->seek(link_edit_offset); + fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); + if (swap) { + lc.cmd = BSWAP32(lc.cmd); + lc.cmdsize = BSWAP32(lc.cmdsize); + } + switch (lc.cmd) { + case LC_SEGMENT: { + LoadCommandSegment lc_seg; + fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); + if (swap) { + lc_seg.vmsize = BSWAP32(lc_seg.vmsize); + lc_seg.filesize = BSWAP32(lc_seg.filesize); + lc_seg.fileoff = BSWAP32(lc_seg.fileoff); + } + + lc_seg.vmsize = end - lc_seg.fileoff; + lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); + lc_seg.filesize = end - lc_seg.fileoff; + + if (swap) { + lc_seg.vmsize = BSWAP32(lc_seg.vmsize); + lc_seg.filesize = BSWAP32(lc_seg.filesize); + } + fa->seek(link_edit_offset + 8); + fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); + } break; + case LC_SEGMENT_64: { + LoadCommandSegment64 lc_seg; + fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); + if (swap) { + lc_seg.vmsize = BSWAP64(lc_seg.vmsize); + lc_seg.filesize = BSWAP64(lc_seg.filesize); + lc_seg.fileoff = BSWAP64(lc_seg.fileoff); + } + lc_seg.vmsize = end - lc_seg.fileoff; + lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); + lc_seg.filesize = end - lc_seg.fileoff; + if (swap) { + lc_seg.vmsize = BSWAP64(lc_seg.vmsize); + lc_seg.filesize = BSWAP64(lc_seg.filesize); + } + fa->seek(link_edit_offset + 8); + fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); + } break; + default: { + ERR_FAIL_V_MSG(false, "MachO: Invalid __LINKEDIT segment type."); + } break; + } + fa->seek(get_signature_offset()); + for (uint64_t i = 0; i < new_size; i++) { + fa->store_8(0x00); + } + return true; +} + +#endif // MODULE_REGEX_ENABLED diff --git a/platform/macos/export/macho.h b/platform/macos/export/macho.h new file mode 100644 index 0000000000..6cfc3c44f5 --- /dev/null +++ b/platform/macos/export/macho.h @@ -0,0 +1,215 @@ +/*************************************************************************/ +/* macho.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +// Mach-O binary object file format parser and editor. + +#ifndef MACHO_H +#define MACHO_H + +#include "core/crypto/crypto.h" +#include "core/crypto/crypto_core.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" +#include "modules/modules_enabled.gen.h" // For regex. + +#ifdef MODULE_REGEX_ENABLED + +class MachO : public RefCounted { + struct MachHeader { + uint32_t cputype; + uint32_t cpusubtype; + uint32_t filetype; + uint32_t ncmds; + uint32_t sizeofcmds; + uint32_t flags; + }; + + enum LoadCommandID { + LC_SEGMENT = 0x00000001, + LC_SYMTAB = 0x00000002, + LC_SYMSEG = 0x00000003, + LC_THREAD = 0x00000004, + LC_UNIXTHREAD = 0x00000005, + LC_LOADFVMLIB = 0x00000006, + LC_IDFVMLIB = 0x00000007, + LC_IDENT = 0x00000008, + LC_FVMFILE = 0x00000009, + LC_PREPAGE = 0x0000000a, + LC_DYSYMTAB = 0x0000000b, + LC_LOAD_DYLIB = 0x0000000c, + LC_ID_DYLIB = 0x0000000d, + LC_LOAD_DYLINKER = 0x0000000e, + LC_ID_DYLINKER = 0x0000000f, + LC_PREBOUND_DYLIB = 0x00000010, + LC_ROUTINES = 0x00000011, + LC_SUB_FRAMEWORK = 0x00000012, + LC_SUB_UMBRELLA = 0x00000013, + LC_SUB_CLIENT = 0x00000014, + LC_SUB_LIBRARY = 0x00000015, + LC_TWOLEVEL_HINTS = 0x00000016, + LC_PREBIND_CKSUM = 0x00000017, + LC_LOAD_WEAK_DYLIB = 0x80000018, + LC_SEGMENT_64 = 0x00000019, + LC_ROUTINES_64 = 0x0000001a, + LC_UUID = 0x0000001b, + LC_RPATH = 0x8000001c, + LC_CODE_SIGNATURE = 0x0000001d, + LC_SEGMENT_SPLIT_INFO = 0x0000001e, + LC_REEXPORT_DYLIB = 0x8000001f, + LC_LAZY_LOAD_DYLIB = 0x00000020, + LC_ENCRYPTION_INFO = 0x00000021, + LC_DYLD_INFO = 0x00000022, + LC_DYLD_INFO_ONLY = 0x80000022, + LC_LOAD_UPWARD_DYLIB = 0x80000023, + LC_VERSION_MIN_MACOSX = 0x00000024, + LC_VERSION_MIN_IPHONEOS = 0x00000025, + LC_FUNCTION_STARTS = 0x00000026, + LC_DYLD_ENVIRONMENT = 0x00000027, + LC_MAIN = 0x80000028, + LC_DATA_IN_CODE = 0x00000029, + LC_SOURCE_VERSION = 0x0000002a, + LC_DYLIB_CODE_SIGN_DRS = 0x0000002b, + LC_ENCRYPTION_INFO_64 = 0x0000002c, + LC_LINKER_OPTION = 0x0000002d, + LC_LINKER_OPTIMIZATION_HINT = 0x0000002e, + LC_VERSION_MIN_TVOS = 0x0000002f, + LC_VERSION_MIN_WATCHOS = 0x00000030, + }; + + struct LoadCommandHeader { + uint32_t cmd; + uint32_t cmdsize; + }; + + struct LoadCommandSegment { + char segname[16]; + uint32_t vmaddr; + uint32_t vmsize; + uint32_t fileoff; + uint32_t filesize; + uint32_t maxprot; + uint32_t initprot; + uint32_t nsects; + uint32_t flags; + }; + + struct LoadCommandSegment64 { + char segname[16]; + uint64_t vmaddr; + uint64_t vmsize; + uint64_t fileoff; + uint64_t filesize; + uint32_t maxprot; + uint32_t initprot; + uint32_t nsects; + uint32_t flags; + }; + + struct Section { + char sectname[16]; + char segname[16]; + uint32_t addr; + uint32_t size; + uint32_t offset; + uint32_t align; + uint32_t reloff; + uint32_t nreloc; + uint32_t flags; + uint32_t reserved1; + uint32_t reserved2; + }; + + struct Section64 { + char sectname[16]; + char segname[16]; + uint64_t addr; + uint64_t size; + uint32_t offset; + uint32_t align; + uint32_t reloff; + uint32_t nreloc; + uint32_t flags; + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + }; + + Ref fa; + bool swap = false; + + uint64_t lc_limit = 0; + + uint64_t exe_limit = 0; + uint64_t exe_base = std::numeric_limits::max(); // Start of first __text section. + uint32_t align = 0; + uint32_t cputype = 0; + uint32_t cpusubtype = 0; + + uint64_t link_edit_offset = 0; // __LINKEDIT segment offset. + uint64_t signature_offset = 0; // Load command offset. + + uint32_t seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max); + bool alloc_signature(uint64_t p_size); + + static inline size_t PAD(size_t s, size_t a) { + return (a - s % a); + } + +public: + static bool is_macho(const String &p_path); + + bool open_file(const String &p_path); + + uint64_t get_exe_base(); + uint64_t get_exe_limit(); + int32_t get_align(); + uint32_t get_cputype(); + uint32_t get_cpusubtype(); + uint64_t get_size(); + uint64_t get_code_limit(); + + uint64_t get_signature_offset(); + bool is_signed(); + + PackedByteArray get_cdhash_sha1(); + PackedByteArray get_cdhash_sha256(); + + PackedByteArray get_requirements(); + + const Ref get_file() const; + Ref get_file(); + + uint64_t get_signature_size(); + bool set_signature_size(uint64_t p_size); +}; + +#endif // MODULE_REGEX_ENABLED + +#endif // MACHO_H diff --git a/platform/macos/export/plist.cpp b/platform/macos/export/plist.cpp new file mode 100644 index 0000000000..36de9dd34b --- /dev/null +++ b/platform/macos/export/plist.cpp @@ -0,0 +1,570 @@ +/*************************************************************************/ +/* plist.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. + +#include "plist.h" + +#ifdef MODULE_REGEX_ENABLED + +Ref PListNode::new_array() { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY; + return node; +} + +Ref PListNode::new_dict() { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; + return node; +} + +Ref PListNode::new_string(const String &p_string) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING; + node->data_string = p_string.utf8(); + return node; +} + +Ref PListNode::new_data(const String &p_string) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA; + node->data_string = p_string.utf8(); + return node; +} + +Ref PListNode::new_date(const String &p_string) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE; + node->data_string = p_string.utf8(); + return node; +} + +Ref PListNode::new_bool(bool p_bool) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN; + node->data_bool = p_bool; + return node; +} + +Ref PListNode::new_int(int32_t p_int) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER; + node->data_int = p_int; + return node; +} + +Ref PListNode::new_real(float p_real) { + Ref node = memnew(PListNode()); + ERR_FAIL_COND_V(node.is_null(), Ref()); + node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL; + node->data_real = p_real; + return node; +} + +bool PListNode::push_subnode(const Ref &p_node, const String &p_key) { + ERR_FAIL_COND_V(p_node.is_null(), false); + if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) { + ERR_FAIL_COND_V(p_key.is_empty(), false); + ERR_FAIL_COND_V(data_dict.has(p_key), false); + data_dict[p_key] = p_node; + return true; + } else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) { + data_array.push_back(p_node); + return true; + } else { + ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY."); + } +} + +size_t PListNode::get_asn1_size(uint8_t p_len_octets) const { + // Get size of all data, excluding type and size information. + switch (data_type) { + case PList::PLNodeType::PL_NODE_TYPE_NIL: { + return 0; + } break; + case PList::PLNodeType::PL_NODE_TYPE_DATA: + case PList::PLNodeType::PL_NODE_TYPE_DATE: { + ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); + } break; + case PList::PLNodeType::PL_NODE_TYPE_STRING: { + return data_string.length(); + } break; + case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { + return 1; + } break; + case PList::PLNodeType::PL_NODE_TYPE_INTEGER: + case PList::PLNodeType::PL_NODE_TYPE_REAL: { + return 4; + } break; + case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { + size_t size = 0; + for (int i = 0; i < data_array.size(); i++) { + size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets); + } + return size; + } break; + case PList::PLNodeType::PL_NODE_TYPE_DICT: { + size_t size = 0; + + for (const KeyValue> &E : data_dict) { + size += 1 + _asn1_size_len(p_len_octets); // Sequence. + size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key. + size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value. + } + return size; + } break; + default: { + return 0; + } break; + } +} + +int PListNode::_asn1_size_len(uint8_t p_len_octets) { + if (p_len_octets > 1) { + return p_len_octets + 1; + } else { + return 1; + } +} + +void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const { + uint32_t size = get_asn1_size(p_len_octets); + if (p_len_octets > 1) { + p_stream.push_back(0x80 + p_len_octets); + } + for (int i = p_len_octets - 1; i >= 0; i--) { + uint8_t x = (size >> i * 8) & 0xFF; + p_stream.push_back(x); + } +} + +bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const { + // Convert to binary ASN1 stream. + bool valid = true; + switch (data_type) { + case PList::PLNodeType::PL_NODE_TYPE_NIL: { + // Nothing to store. + } break; + case PList::PLNodeType::PL_NODE_TYPE_DATE: + case PList::PLNodeType::PL_NODE_TYPE_DATA: { + ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); + } break; + case PList::PLNodeType::PL_NODE_TYPE_STRING: { + p_stream.push_back(0x0C); + store_asn1_size(p_stream, p_len_octets); + for (int i = 0; i < data_string.size(); i++) { + p_stream.push_back(data_string[i]); + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { + p_stream.push_back(0x01); + store_asn1_size(p_stream, p_len_octets); + if (data_bool) { + p_stream.push_back(0x01); + } else { + p_stream.push_back(0x00); + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { + p_stream.push_back(0x02); + store_asn1_size(p_stream, p_len_octets); + for (int i = 4; i >= 0; i--) { + uint8_t x = (data_int >> i * 8) & 0xFF; + p_stream.push_back(x); + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_REAL: { + p_stream.push_back(0x03); + store_asn1_size(p_stream, p_len_octets); + for (int i = 4; i >= 0; i--) { + uint8_t x = (data_int >> i * 8) & 0xFF; + p_stream.push_back(x); + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { + p_stream.push_back(0x30); // Sequence. + store_asn1_size(p_stream, p_len_octets); + for (int i = 0; i < data_array.size(); i++) { + valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets); + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_DICT: { + p_stream.push_back(0x31); // Set. + store_asn1_size(p_stream, p_len_octets); + for (const KeyValue> &E : data_dict) { + CharString cs = E.key.utf8(); + uint32_t size = cs.length(); + + // Sequence. + p_stream.push_back(0x30); + uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets); + if (p_len_octets > 1) { + p_stream.push_back(0x80 + p_len_octets); + } + for (int i = p_len_octets - 1; i >= 0; i--) { + uint8_t x = (seq_size >> i * 8) & 0xFF; + p_stream.push_back(x); + } + // Key. + p_stream.push_back(0x0C); + if (p_len_octets > 1) { + p_stream.push_back(0x80 + p_len_octets); + } + for (int i = p_len_octets - 1; i >= 0; i--) { + uint8_t x = (size >> i * 8) & 0xFF; + p_stream.push_back(x); + } + for (uint32_t i = 0; i < size; i++) { + p_stream.push_back(cs[i]); + } + // Value. + valid = valid && E.value->store_asn1(p_stream, p_len_octets); + } + } break; + } + return valid; +} + +void PListNode::store_text(String &p_stream, uint8_t p_indent) const { + // Convert to text XML stream. + switch (data_type) { + case PList::PLNodeType::PL_NODE_TYPE_NIL: { + // Nothing to store. + } break; + case PList::PLNodeType::PL_NODE_TYPE_DATA: { + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + p_stream += String("\t").repeat(p_indent); + p_stream += data_string + "\n"; + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_DATE: { + p_stream += String("\t").repeat(p_indent); + p_stream += ""; + p_stream += data_string; + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_STRING: { + p_stream += String("\t").repeat(p_indent); + p_stream += ""; + p_stream += String::utf8(data_string); + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { + p_stream += String("\t").repeat(p_indent); + if (data_bool) { + p_stream += "\n"; + } else { + p_stream += "\n"; + } + } break; + case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { + p_stream += String("\t").repeat(p_indent); + p_stream += ""; + p_stream += itos(data_int); + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_REAL: { + p_stream += String("\t").repeat(p_indent); + p_stream += ""; + p_stream += rtos(data_real); + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + for (int i = 0; i < data_array.size(); i++) { + data_array[i]->store_text(p_stream, p_indent + 1); + } + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + } break; + case PList::PLNodeType::PL_NODE_TYPE_DICT: { + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + for (const KeyValue> &E : data_dict) { + p_stream += String("\t").repeat(p_indent + 1); + p_stream += ""; + p_stream += E.key; + p_stream += "\n"; + E.value->store_text(p_stream, p_indent + 1); + } + p_stream += String("\t").repeat(p_indent); + p_stream += "\n"; + } break; + } +} + +/*************************************************************************/ + +PList::PList() { + root = PListNode::new_dict(); +} + +PList::PList(const String &p_string) { + load_string(p_string); +} + +bool PList::load_file(const String &p_filename) { + root = Ref(); + + Ref fb = FileAccess::open(p_filename, FileAccess::READ); + if (fb.is_null()) { + return false; + } + + unsigned char magic[8]; + fb->get_buffer(magic, 8); + + if (String((const char *)magic, 8) == "bplist00") { + ERR_FAIL_V_MSG(false, "PList: Binary property lists are not supported."); + } else { + // Load text plist. + Error err; + Vector array = FileAccess::get_file_as_array(p_filename, &err); + ERR_FAIL_COND_V(err != OK, false); + + String ret; + ret.parse_utf8((const char *)array.ptr(), array.size()); + return load_string(ret); + } +} + +bool PList::load_string(const String &p_string) { + root = Ref(); + + int pos = 0; + bool in_plist = false; + bool done_plist = false; + List> stack; + String key; + while (pos >= 0) { + int open_token_s = p_string.find("<", pos); + if (open_token_s == -1) { + ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. No tags found."); + } + int open_token_e = p_string.find(">", open_token_s); + pos = open_token_e; + + String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1); + if (token.is_empty()) { + ERR_FAIL_V_MSG(false, "PList: Invalid token name."); + } + String value; + if (token[0] == '?' || token[0] == '!') { // Skip and + int end_token_e = p_string.find(">", open_token_s); + pos = end_token_e; + continue; + } + + if (token.find("plist", 0) == 0) { + in_plist = true; + continue; + } + + if (token == "/plist") { + done_plist = true; + break; + } + + if (!in_plist) { + ERR_FAIL_V_MSG(false, "PList: Node outside of tag."); + } + + if (token == "dict") { + if (!stack.is_empty()) { + // Add subnode end enter it. + Ref dict = PListNode::new_dict(); + dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; + if (!stack.back()->get()->push_subnode(dict, key)) { + ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); + } + stack.push_back(dict); + } else { + // Add root node. + if (!root.is_null()) { + ERR_FAIL_V_MSG(false, "PList: Root node already set."); + } + Ref dict = PListNode::new_dict(); + stack.push_back(dict); + root = dict; + } + continue; + } + + if (token == "/dict") { + // Exit current dict. + if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) { + ERR_FAIL_V_MSG(false, "PList: Mismatched tag."); + } + stack.pop_back(); + continue; + } + + if (token == "array") { + if (!stack.is_empty()) { + // Add subnode end enter it. + Ref arr = PListNode::new_array(); + if (!stack.back()->get()->push_subnode(arr, key)) { + ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); + } + stack.push_back(arr); + } else { + // Add root node. + if (!root.is_null()) { + ERR_FAIL_V_MSG(false, "PList: Root node already set."); + } + Ref arr = PListNode::new_array(); + stack.push_back(arr); + root = arr; + } + continue; + } + + if (token == "/array") { + // Exit current array. + if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) { + ERR_FAIL_V_MSG(false, "PList: Mismatched tag."); + } + stack.pop_back(); + continue; + } + + if (token[token.length() - 1] == '/') { + token = token.substr(0, token.length() - 1); + } else { + int end_token_s = p_string.find(" tag.", token)); + } + int end_token_e = p_string.find(">", end_token_s); + pos = end_token_e; + String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2); + if (end_token != token) { + ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> and <%s> token pair.", token, end_token)); + } + value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1); + } + if (token == "key") { + key = value; + } else { + Ref var = nullptr; + if (token == "true") { + var = PListNode::new_bool(true); + } else if (token == "false") { + var = PListNode::new_bool(false); + } else if (token == "integer") { + var = PListNode::new_int(value.to_int()); + } else if (token == "real") { + var = PListNode::new_real(value.to_float()); + } else if (token == "string") { + var = PListNode::new_string(value); + } else if (token == "data") { + var = PListNode::new_data(value); + } else if (token == "date") { + var = PListNode::new_date(value); + } else { + ERR_FAIL_V_MSG(false, "PList: Invalid value type."); + } + if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) { + ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); + } + } + } + if (!stack.is_empty() || !done_plist) { + ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed."); + } + return true; +} + +PackedByteArray PList::save_asn1() const { + if (root == nullptr) { + ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node."); + } + size_t size = root->get_asn1_size(1); + uint8_t len_octets = 0; + if (size < 0x80) { + len_octets = 1; + } else { + size = root->get_asn1_size(2); + if (size < 0xFFFF) { + len_octets = 2; + } else { + size = root->get_asn1_size(3); + if (size < 0xFFFFFF) { + len_octets = 3; + } else { + size = root->get_asn1_size(4); + if (size < 0xFFFFFFFF) { + len_octets = 4; + } else { + ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB."); + } + } + } + } + + PackedByteArray ret; + if (!root->store_asn1(ret, len_octets)) { + ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error."); + } + return ret; +} + +String PList::save_text() const { + if (root == nullptr) { + ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node."); + } + + String ret; + ret += "\n"; + ret += "\n"; + ret += "\n"; + + root->store_text(ret, 0); + + ret += "\n\n"; + return ret; +} + +Ref PList::get_root() { + return root; +} + +#endif // MODULE_REGEX_ENABLED diff --git a/platform/macos/export/plist.h b/platform/macos/export/plist.h new file mode 100644 index 0000000000..ba9eaec196 --- /dev/null +++ b/platform/macos/export/plist.h @@ -0,0 +1,116 @@ +/*************************************************************************/ +/* plist.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +// Property list file format (application/x-plist) parser, property list ASN-1 serialization. + +#ifndef PLIST_H +#define PLIST_H + +#include "core/crypto/crypto_core.h" +#include "core/io/file_access.h" +#include "modules/modules_enabled.gen.h" // For regex. + +#ifdef MODULE_REGEX_ENABLED + +class PListNode; + +class PList : public RefCounted { + friend class PListNode; + +public: + enum PLNodeType { + PL_NODE_TYPE_NIL, + PL_NODE_TYPE_STRING, + PL_NODE_TYPE_ARRAY, + PL_NODE_TYPE_DICT, + PL_NODE_TYPE_BOOLEAN, + PL_NODE_TYPE_INTEGER, + PL_NODE_TYPE_REAL, + PL_NODE_TYPE_DATA, + PL_NODE_TYPE_DATE, + }; + +private: + Ref root; + +public: + PList(); + PList(const String &p_string); + + bool load_file(const String &p_filename); + bool load_string(const String &p_string); + + PackedByteArray save_asn1() const; + String save_text() const; + + Ref get_root(); +}; + +/*************************************************************************/ + +class PListNode : public RefCounted { + static int _asn1_size_len(uint8_t p_len_octets); + +public: + PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL; + + CharString data_string; + Vector> data_array; + HashMap> data_dict; + union { + int32_t data_int; + bool data_bool; + float data_real; + }; + + static Ref new_array(); + static Ref new_dict(); + static Ref new_string(const String &p_string); + static Ref new_data(const String &p_string); + static Ref new_date(const String &p_string); + static Ref new_bool(bool p_bool); + static Ref new_int(int32_t p_int); + static Ref new_real(float p_real); + + bool push_subnode(const Ref &p_node, const String &p_key = ""); + + size_t get_asn1_size(uint8_t p_len_octets) const; + + void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const; + bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const; + void store_text(String &p_stream, uint8_t p_indent) const; + + PListNode() {} + ~PListNode() {} +}; + +#endif // MODULE_REGEX_ENABLED + +#endif // PLIST_H diff --git a/platform/macos/gl_manager_macos_legacy.h b/platform/macos/gl_manager_macos_legacy.h new file mode 100644 index 0000000000..9f866f2b32 --- /dev/null +++ b/platform/macos/gl_manager_macos_legacy.h @@ -0,0 +1,97 @@ +/*************************************************************************/ +/* gl_manager_macos_legacy.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GL_MANAGER_MACOS_LEGACY_H +#define GL_MANAGER_MACOS_LEGACY_H + +#if defined(MACOS_ENABLED) && defined(GLES3_ENABLED) + +#include "core/error/error_list.h" +#include "core/os/os.h" +#include "core/templates/local_vector.h" +#include "servers/display_server.h" + +#import +#import +#import + +class GLManager_MacOS { +public: + enum ContextType { + GLES_3_0_COMPATIBLE, + }; + +private: + struct GLWindow { + int width = 0; + int height = 0; + + id window_view = nullptr; + NSOpenGLContext *context = nullptr; + }; + + RBMap windows; + + NSOpenGLContext *shared_context = nullptr; + DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID; + + Error create_context(GLWindow &win); + + bool use_vsync = false; + ContextType context_type; + +public: + Error window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height); + void window_destroy(DisplayServer::WindowID p_window_id); + void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); + + int window_get_width(DisplayServer::WindowID p_window_id = 0); + int window_get_height(DisplayServer::WindowID p_window_id = 0); + + void release_current(); + void make_current(); + void swap_buffers(); + + void window_make_current(DisplayServer::WindowID p_window_id); + + void window_update(DisplayServer::WindowID p_window_id); + void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled); + + Error initialize(); + + void set_use_vsync(bool p_use); + bool is_using_vsync() const; + + GLManager_MacOS(ContextType p_context_type); + ~GLManager_MacOS(); +}; + +#endif // MACOS_ENABLED && GLES3_ENABLED +#endif // GL_MANAGER_MACOS_LEGACY_H diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm new file mode 100644 index 0000000000..e6bb7aaa85 --- /dev/null +++ b/platform/macos/gl_manager_macos_legacy.mm @@ -0,0 +1,228 @@ +/*************************************************************************/ +/* gl_manager_macos_legacy.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "gl_manager_macos_legacy.h" + +#ifdef MACOS_ENABLED +#ifdef GLES3_ENABLED + +#include +#include + +Error GLManager_MacOS::create_context(GLWindow &win) { + NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAClosestPolicy, + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAColorSize, 32, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAStencilSize, 8, + 0 + }; + + NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE); + + win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context]; + ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE); + if (shared_context == nullptr) { + shared_context = win.context; + } + + [win.context setView:win.window_view]; + [win.context makeCurrentContext]; + + return OK; +} + +Error GLManager_MacOS::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) { + GLWindow win; + win.width = p_width; + win.height = p_height; + win.window_view = p_view; + + if (create_context(win) != OK) { + return FAILED; + } + + windows[p_window_id] = win; + window_make_current(p_window_id); + + return OK; +} + +void GLManager_MacOS::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { + if (!windows.has(p_window_id)) { + return; + } + + GLWindow &win = windows[p_window_id]; + + win.width = p_width; + win.height = p_height; + + GLint dim[2]; + dim[0] = p_width; + dim[1] = p_height; + CGLSetParameter((CGLContextObj)[win.context CGLContextObj], kCGLCPSurfaceBackingSize, &dim[0]); + CGLEnable((CGLContextObj)[win.context CGLContextObj], kCGLCESurfaceBackingSize); + if (OS::get_singleton()->is_hidpi_allowed()) { + [win.window_view setWantsBestResolutionOpenGLSurface:YES]; + } else { + [win.window_view setWantsBestResolutionOpenGLSurface:NO]; + } + + [win.context update]; +} + +int GLManager_MacOS::window_get_width(DisplayServer::WindowID p_window_id) { + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.width; +} + +int GLManager_MacOS::window_get_height(DisplayServer::WindowID p_window_id) { + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.height; +} + +void GLManager_MacOS::window_destroy(DisplayServer::WindowID p_window_id) { + if (!windows.has(p_window_id)) { + return; + } + + if (current_window == p_window_id) { + current_window = DisplayServer::INVALID_WINDOW_ID; + } + + windows.erase(p_window_id); +} + +void GLManager_MacOS::release_current() { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { + return; + } + + [NSOpenGLContext clearCurrentContext]; +} + +void GLManager_MacOS::window_make_current(DisplayServer::WindowID p_window_id) { + if (current_window == p_window_id) { + return; + } + if (!windows.has(p_window_id)) { + return; + } + + GLWindow &win = windows[p_window_id]; + [win.context makeCurrentContext]; + + current_window = p_window_id; +} + +void GLManager_MacOS::make_current() { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { + return; + } + if (!windows.has(current_window)) { + return; + } + + GLWindow &win = windows[current_window]; + [win.context makeCurrentContext]; +} + +void GLManager_MacOS::swap_buffers() { + for (const KeyValue &E : windows) { + [E.value.context flushBuffer]; + } +} + +void GLManager_MacOS::window_update(DisplayServer::WindowID p_window_id) { + if (!windows.has(p_window_id)) { + return; + } + + GLWindow &win = windows[p_window_id]; + [win.context update]; +} + +void GLManager_MacOS::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) { + if (!windows.has(p_window_id)) { + return; + } + + GLWindow &win = windows[p_window_id]; + if (p_enabled) { + GLint opacity = 0; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } else { + GLint opacity = 1; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } + [win.context update]; +} + +Error GLManager_MacOS::initialize() { + return OK; +} + +void GLManager_MacOS::set_use_vsync(bool p_use) { + use_vsync = p_use; + + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) { + GLint swapInterval = p_use ? 1 : 0; + CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); + use_vsync = p_use; + } +} + +bool GLManager_MacOS::is_using_vsync() const { + return use_vsync; +} + +GLManager_MacOS::GLManager_MacOS(ContextType p_context_type) { + context_type = p_context_type; +} + +GLManager_MacOS::~GLManager_MacOS() { + release_current(); +} + +#endif // GLES3_ENABLED +#endif // MACOS_ENABLED diff --git a/platform/macos/godot_application.h b/platform/macos/godot_application.h new file mode 100644 index 0000000000..8d48a659f3 --- /dev/null +++ b/platform/macos/godot_application.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* godot_application.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_APPLICATION_H +#define GODOT_APPLICATION_H + +#include "core/os/os.h" + +#import +#import + +@interface GodotApplication : NSApplication +@end + +#endif // GODOT_APPLICATION_H diff --git a/platform/macos/godot_application.mm b/platform/macos/godot_application.mm new file mode 100644 index 0000000000..3f71c77fd1 --- /dev/null +++ b/platform/macos/godot_application.mm @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* godot_application.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_application.h" + +#include "display_server_macos.h" + +@implementation GodotApplication + +- (void)sendEvent:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown || [event type] == NSEventTypeOtherMouseDown) { + if (ds->mouse_process_popups()) { + return; + } + } + ds->send_event(event); + } + + // 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 diff --git a/platform/macos/godot_application_delegate.h b/platform/macos/godot_application_delegate.h new file mode 100644 index 0000000000..f5b67b580f --- /dev/null +++ b/platform/macos/godot_application_delegate.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* godot_application_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_APPLICATION_DELEGATE_H +#define GODOT_APPLICATION_DELEGATE_H + +#include "core/os/os.h" + +#import +#import + +@interface GodotApplicationDelegate : NSObject +- (void)forceUnbundledWindowActivationHackStep1; +- (void)forceUnbundledWindowActivationHackStep2; +- (void)forceUnbundledWindowActivationHackStep3; +- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end + +#endif // GODOT_APPLICATION_DELEGATE_H diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm new file mode 100644 index 0000000000..bacdcc2bc4 --- /dev/null +++ b/platform/macos/godot_application_delegate.mm @@ -0,0 +1,163 @@ +/*************************************************************************/ +/* godot_application_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_application_delegate.h" + +#include "display_server_macos.h" +#include "os_macos.h" + +@implementation GodotApplicationDelegate + +- (void)forceUnbundledWindowActivationHackStep1 { + // Step 1: Switch focus to macOS SystemUIServer process. + // Required to perform step 2, TransformProcessType will fail if app is already the in focus. + for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { + [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 || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { + // If the executable is started from terminal or is not 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]; + } +} + +- (id)init { + self = [super init]; + + NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager]; + [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; + [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; + + return self; +} + +- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); + if (!event || !os) { + return; + } + + List args; + if (([event eventClass] == kInternetEventClass) && ([event eventID] == kAEGetURL)) { + // Opening URL scheme. + NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + args.push_back(vformat("--uri=\"%s\"", String::utf8([url UTF8String]))); + } + + if (([event eventClass] == kCoreEventClass) && ([event eventID] == kAEOpenDocuments)) { + // Opening file association. + NSAppleEventDescriptor *files = [event paramDescriptorForKeyword:keyDirectObject]; + if (files) { + NSInteger count = [files numberOfItems]; + for (NSInteger i = 1; i <= count; i++) { + NSURL *url = [NSURL URLWithString:[[files descriptorAtIndex:i] stringValue]]; + args.push_back(String::utf8([url.path UTF8String])); + } + } + } + + if (!args.is_empty()) { + if (os->get_main_loop()) { + // Application is already running, open a new instance with the URL/files as command line arguments. + os->create_instance(args); + } else { + // Application is just started, add to the list of command line arguments and continue. + os->set_cmdline_platform_args(args); + } + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->mouse_process_popups(true); + } + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + +- (void)globalMenuCallback:(id)sender { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + return ds->menu_callback(sender); + } +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + return ds->get_dock_menu(); + } else { + return nullptr; + } +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->send_window_event(ds->get_window(DisplayServerMacOS::MAIN_WINDOW_ID), DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + } + return NSTerminateCancel; +} + +- (void)showAbout:(id)sender { + OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); + if (os && os->get_main_loop()) { + os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } +} + +@end diff --git a/platform/macos/godot_content_view.h b/platform/macos/godot_content_view.h new file mode 100644 index 0000000000..353305aec1 --- /dev/null +++ b/platform/macos/godot_content_view.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* godot_content_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_CONTENT_VIEW_H +#define GODOT_CONTENT_VIEW_H + +#include "servers/display_server.h" + +#import +#import + +#if defined(GLES3_ENABLED) +#import +#define RootView NSOpenGLView +#else +#define RootView NSView +#endif + +#import + +@interface GodotContentView : RootView { + DisplayServer::WindowID window_id; + NSTrackingArea *tracking_area; + NSMutableAttributedString *marked_text; + bool ime_input_event_in_progress; + bool mouse_down_control; + bool ignore_momentum_scroll; + bool last_pen_inverted; +} + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor; +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy; +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed; +- (void)setWindowID:(DisplayServer::WindowID)wid; +- (void)cancelComposition; + +@end + +#endif // GODOT_CONTENT_VIEW_H diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm new file mode 100644 index 0000000000..9ca7498a15 --- /dev/null +++ b/platform/macos/godot_content_view.mm @@ -0,0 +1,771 @@ +/*************************************************************************/ +/* godot_content_view.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_content_view.h" + +#include "display_server_macos.h" +#include "key_mapping_macos.h" + +@implementation GodotContentView + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + tracking_area = nil; + ime_input_event_in_progress = false; + mouse_down_control = false; + ignore_momentum_scroll = false; + last_pen_inverted = false; + [self updateTrackingAreas]; + + if (@available(macOS 10.13, *)) { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif + } + marked_text = [[NSMutableAttributedString alloc] init]; + return self; +} + +- (void)setWindowID:(DisplayServerMacOS::WindowID)wid { + window_id = wid; +} + +// MARK: Backing Layer + +- (CALayer *)makeBackingLayer { + return [[CAMetalLayer class] layer]; +} + +- (void)updateLayer { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + ds->window_update(window_id); + [super updateLayer]; +} + +- (BOOL)wantsUpdateLayer { + return YES; +} + +- (BOOL)isOpaque { + return YES; +} + +// MARK: IME + +- (BOOL)hasMarkedText { + return (marked_text.length > 0); +} + +- (NSRange)markedRange { + return NSMakeRange(0, marked_text.length); +} + +- (NSRange)selectedRange { + static const NSRange kEmptyRange = { NSNotFound, 0 }; + return kEmptyRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + if ([aString isKindOfClass:[NSAttributedString class]]) { + marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString]; + } else { + marked_text = [[NSMutableAttributedString alloc] initWithString:aString]; + } + if (marked_text.length == 0) { + [self unmarkText]; + return; + } + + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ime_input_event_in_progress = true; + ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String])); + } +} + +- (void)doCommandBySelector:(SEL)aSelector { + [self tryToPerform:aSelector with:self]; +} + +- (void)unmarkText { + ime_input_event_in_progress = false; + [[marked_text mutableString] setString:@""]; + + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ds->update_im_text(Point2i(), String()); + } +} + +- (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 { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NSMakeRect(0, 0, 0, 0); + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0); + NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin; + + return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0); +} + +- (void)cancelComposition { + [self unmarkText]; + [[NSTextInputContext 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; + } + + NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet]; + NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) { + [[NSTextInputContext currentInputContext] discardMarkedText]; + [self cancelComposition]; + return; + } + + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + [self cancelComposition]; + return; + } + + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + + DisplayServerMacOS::KeyEvent ke; + + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = false; + ke.raw = false; // IME input event. + ke.keycode = Key::NONE; + ke.physical_keycode = Key::NONE; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + [self cancelComposition]; +} + +// MARK: Drag and drop + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NO; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (!wd.drop_files_callback.is_null()) { + Vector files; + NSPasteboard *pboard = [sender draggingPasteboard]; + + if (@available(macOS 10.13, *)) { + NSArray *items = pboard.pasteboardItems; + for (NSPasteboardItem *item in items) { + NSString *url = [item stringForType:NSPasteboardTypeFileURL]; + NSString *file = [NSURL URLWithString:url].path; + files.push_back(String::utf8([file UTF8String])); + } +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString *file in filenames) { + files.push_back(String::utf8([file UTF8String])); + } +#endif + } + + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } + + return NO; +} + +// MARK: Focus + +- (BOOL)canBecomeKeyView { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus && !wd.is_popup; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +// MARK: Mouse + +- (void)cursorUpdate:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds) { + return; + } + + ds->cursor_update_shape(); +} + +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + MouseButton last_button_state = ds->mouse_get_button_state(); + + if (pressed) { + last_button_state |= mask; + } else { + last_button_state &= (MouseButton)~mask; + } + ds->mouse_set_button_state(last_button_state); + + Ref mb; + mb.instantiate(); + mb->set_window_id(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + ds->get_key_modifier_state([event modifierFlags], mb); + mb->set_button_index(index); + mb->set_pressed(pressed); + mb->set_position(wd.mouse_pos); + mb->set_global_position(wd.mouse_pos); + mb->set_button_mask(last_button_state); + if (index == MouseButton::LEFT && pressed) { + mb->set_double_click([event clickCount] == 2); + } + + Input::get_singleton()->parse_input_event(mb); +} + +- (void)mouseDown:(NSEvent *)event { + if (([event modifierFlags] & NSEventModifierFlagControl)) { + mouse_down_control = true; + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; + } else { + mouse_down_control = false; + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true]; + } +} + +- (void)mouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event { + if (mouse_down_control) { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; + } else { + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false]; + } +} + +- (void)mouseMoved:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) { + return; + } + + Ref mm; + mm.instantiate(); + + mm->set_window_id(window_id); + mm->set_button_mask(ds->mouse_get_button_state()); + ds->update_mouse_pos(wd, mpos); + mm->set_position(wd.mouse_pos); + mm->set_pressure([event pressure]); + NSEventSubtype subtype = [event subtype]; + if (subtype == NSEventSubtypeTabletPoint) { + const NSPoint p = [event tilt]; + mm->set_tilt(Vector2(p.x, p.y)); + mm->set_pen_inverted(last_pen_inverted); + } else if (subtype == NSEventSubtypeTabletProximity) { + // Check if using the eraser end of pen only on proximity event. + last_pen_inverted = [event pointingDeviceType] == NSPointingDeviceTypeEraser; + mm->set_pen_inverted(last_pen_inverted); + } + mm->set_global_position(wd.mouse_pos); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale(); + mm->set_relative(relativeMotion); + ds->get_key_modifier_state([event modifierFlags], mm); + + Input::get_singleton()->parse_input_event(mm); +} + +- (void)rightMouseDown:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; +} + +- (void)rightMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; +} + +- (void)otherMouseDown:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true]; + } else { + return; + } +} + +- (void)otherMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false]; + } else { + return; + } +} + +- (void)mouseExited:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_MOUSE_EXIT); + } +} + +- (void)mouseEntered:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_MOUSE_ENTER); + } + + ds->cursor_update_shape(); +} + +- (void)magnifyWithEvent:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + Ref ev; + ev.instantiate(); + ev->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], ev); + ds->update_mouse_pos(wd, [event locationInWindow]); + ev->set_position(wd.mouse_pos); + ev->set_factor([event magnification] + 1.0); + + Input::get_singleton()->parse_input_event(ev); +} + +- (void)updateTrackingAreas { + if (tracking_area != nil) { + [self removeTrackingArea:tracking_area]; + } + + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + + [self addTrackingArea:tracking_area]; + [super updateTrackingAreas]; +} + +// MARK: Keyboard + +- (void)keyDown:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]))) { + // Fallback unicode character handler used if IME is not active. + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + + DisplayServerMacOS::KeyEvent ke; + + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerMacOS::KeyEvent ke; + + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.raw = false; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } + + // Pass events to IME handler + if (wd.im_active) { + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } +} + +- (void)flagsChanged:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress + if (!ime_input_event_in_progress) { + DisplayServerMacOS::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.macos_state = mod; + ke.keycode = KeyMappingMacOS::remap_key(key, mod); + ke.physical_keycode = KeyMappingMacOS::translate_key(key); + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } +} + +- (void)keyUp:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + 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(KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]))) { + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + DisplayServerMacOS::KeyEvent ke; + + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerMacOS::KeyEvent ke; + + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } +} + +// MARK: Scroll and pan + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + MouseButton mask = mouse_button_to_mask(button); + + Ref sc; + sc.instantiate(); + + sc->set_window_id(window_id); + ds->get_key_modifier_state([event 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); + MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); + + sc.instantiate(); + 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); + last_button_state &= (MouseButton)~mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); +} + +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + Ref pg; + pg.instantiate(); + + pg->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], pg); + pg->set_position(wd.mouse_pos); + pg->set_delta(Vector2(-dx, -dy)); + + Input::get_singleton()->parse_input_event(pg); +} + +- (void)scrollWheel:(NSEvent *)event { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + + double delta_x = [event scrollingDeltaX]; + double delta_y = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) { + delta_x *= 0.03; + delta_y *= 0.03; + } + + if ([event momentumPhase] != NSEventPhaseNone) { + if (ignore_momentum_scroll) { + return; + } + } else { + ignore_momentum_scroll = false; + } + + if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { + [self processPanEvent:event dx:delta_x dy:delta_y]; + } else { + if (fabs(delta_x)) { + [self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)]; + } + if (fabs(delta_y)) { + [self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)]; + } + } +} + +@end diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm new file mode 100644 index 0000000000..66071f1404 --- /dev/null +++ b/platform/macos/godot_main_macos.mm @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* godot_main_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "main/main.h" + +#include "os_macos.h" + +#include +#include + +#if defined(SANITIZERS_ENABLED) +#include +#endif + +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 + +#if defined(SANITIZERS_ENABLED) + // Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times. + struct rlimit stack_lim = { 0x1E00000, 0x1E00000 }; + setrlimit(RLIMIT_STACK, &stack_lim); +#endif + + int first_arg = 1; + const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; + printf("arguments\n"); + for (int i = 0; i < argc; i++) { + if (strcmp(dbg_arg, argv[i]) == 0) { + first_arg = i + 2; + } + printf("%i: %s\n", i, argv[i]); + } + +#ifdef DEBUG_ENABLED + // Lets report the path we made current after all that. + char cwd[4096]; + getcwd(cwd, 4096); + printf("Current path: %s\n", cwd); +#endif + + OS_MacOS os; + Error err; + + // We must override main when testing is enabled. + TEST_MAIN_OVERRIDE + + err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); + + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } else if (err != OK) { + return 255; + } + + if (Main::start()) { + os.run(); // It is actually the OS that decides how to run. + } + + Main::cleanup(); + + return os.get_exit_code(); +} diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h new file mode 100644 index 0000000000..2c12897f10 --- /dev/null +++ b/platform/macos/godot_menu_item.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* godot_menu_item.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_MENU_ITEM_H +#define GODOT_MENU_ITEM_H + +#include "servers/display_server.h" + +#import +#import + +enum GlobalMenuCheckType { + CHECKABLE_TYPE_NONE, + CHECKABLE_TYPE_CHECK_BOX, + CHECKABLE_TYPE_RADIO_BUTTON, +}; + +@interface GodotMenuItem : NSObject { +@public + Callable callback; + Variant meta; + int id; + GlobalMenuCheckType checkable_type; + int max_states; + int state; + Ref img; +} + +@end + +@implementation GodotMenuItem +@end + +#endif // GODOT_MENU_ITEM_H diff --git a/platform/macos/godot_window.h b/platform/macos/godot_window.h new file mode 100644 index 0000000000..16ff101142 --- /dev/null +++ b/platform/macos/godot_window.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_WINDOW_H +#define GODOT_WINDOW_H + +#include "servers/display_server.h" + +#import +#import + +@interface GodotWindow : NSWindow { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_H diff --git a/platform/macos/godot_window.mm b/platform/macos/godot_window.mm new file mode 100644 index 0000000000..e205e7546d --- /dev/null +++ b/platform/macos/godot_window.mm @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* godot_window.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_window.h" + +#include "display_server_macos.h" + +@implementation GodotWindow + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + return self; +} + +- (void)setWindowID:(DisplayServerMacOS::WindowID)wid { + window_id = wid; +} + +- (BOOL)canBecomeKeyWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus && !wd.is_popup; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus && !wd.is_popup; +} + +@end diff --git a/platform/macos/godot_window_delegate.h b/platform/macos/godot_window_delegate.h new file mode 100644 index 0000000000..8a1f681fcd --- /dev/null +++ b/platform/macos/godot_window_delegate.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_WINDOW_DELEGATE_H +#define GODOT_WINDOW_DELEGATE_H + +#include "servers/display_server.h" + +#import +#import + +@interface GodotWindowDelegate : NSObject { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_DELEGATE_H diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm new file mode 100644 index 0000000000..e1e88274f0 --- /dev/null +++ b/platform/macos/godot_window_delegate.mm @@ -0,0 +1,270 @@ +/*************************************************************************/ +/* godot_window_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_window_delegate.h" + +#include "display_server_macos.h" + +@implementation GodotWindowDelegate + +- (void)setWindowID:(DisplayServer::WindowID)wid { + window_id = wid; +} + +- (BOOL)windowShouldClose:(id)sender { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + ds->send_window_event(ds->get_window(window_id), DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); + return NO; +} + +- (void)windowWillClose:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + ds->popup_close(window_id); + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + while (wd.transient_children.size()) { + ds->window_set_transient(*wd.transient_children.begin(), DisplayServerMacOS::INVALID_WINDOW_ID); + } + + if (wd.transient_parent != DisplayServerMacOS::INVALID_WINDOW_ID) { + ds->window_set_transient(window_id, DisplayServerMacOS::INVALID_WINDOW_ID); + } + + ds->window_destroy(window_id); +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = true; + // Reset window size limits. + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = false; + + // Set window size limits. + const float scale = ds->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)]; + } + + // Restore resizability state. + if (wd.resize_disabled) { + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } + + // Restore on-top state. + if (wd.on_top) { + [wd.window_object setLevel:NSFloatingWindowLevel]; + } + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + CGFloat new_scale_factor = [wd.window_object backingScaleFactor]; + CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + + if (new_scale_factor != old_scale_factor) { + // Set new display scale and window size. + const float scale = ds->screen_get_max_scale(); + const NSRect content_rect = [wd.window_view frame]; + + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_DPI_CHANGE); + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + //Force window resize event + [self windowDidResize:notification]; + } +} + +- (void)windowWillStartLiveResize:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->set_is_resizing(true); + } +} + +- (void)windowDidEndLiveResize:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->set_is_resizing(false); + } +} + +- (void)windowDidResize:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + ds->window_resize(window_id, wd.size.width, wd.size.height); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->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)windowDidMove:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + ds->release_pressed_events(); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->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 { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) { + const NSRect content_rect = [wd.window_view frame]; + NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0); + NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y }; + CGWarpMouseCursorPosition(mouse_warp_pos); + } else { + ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + } + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_IN); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_IN); +} + +@end diff --git a/platform/macos/joypad_macos.cpp b/platform/macos/joypad_macos.cpp new file mode 100644 index 0000000000..1ddcfec1b5 --- /dev/null +++ b/platform/macos/joypad_macos.cpp @@ -0,0 +1,616 @@ +/*************************************************************************/ +/* joypad_macos.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "joypad_macos.h" + +#include + +#define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad") + +static JoypadMacOS *self = nullptr; + +joypad::joypad() { + ff_constant_force.lMagnitude = 10000; + ff_effect.dwDuration = 0; + ff_effect.dwSamplePeriod = 0; + ff_effect.dwGain = 10000; + ff_effect.dwFlags = FFEFF_OBJECTOFFSETS; + ff_effect.dwTriggerButton = FFEB_NOTRIGGER; + ff_effect.dwStartDelay = 0; + ff_effect.dwTriggerRepeatInterval = 0; + ff_effect.lpEnvelope = nullptr; + ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); + ff_effect.lpvTypeSpecificParams = &ff_constant_force; + ff_effect.dwSize = sizeof(ff_effect); +} + +void joypad::free() { + if (device_ref) { + IOHIDDeviceUnscheduleFromRunLoop(device_ref, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); + } + if (ff_device) { + FFDeviceReleaseEffect(ff_device, ff_object); + FFReleaseDevice(ff_device); + ff_device = nullptr; + memfree(ff_axes); + memfree(ff_directions); + } +} + +bool joypad::has_element(IOHIDElementCookie p_cookie, Vector *p_list) const { + for (int i = 0; i < p_list->size(); i++) { + if (p_cookie == p_list->get(i).cookie) { + return true; + } + } + return false; +} + +int joypad::get_hid_element_state(rec_element *p_element) const { + int value = 0; + if (p_element && p_element->ref) { + IOHIDValueRef valueRef; + if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) { + value = (SInt32)IOHIDValueGetIntegerValue(valueRef); + + // Record min and max for auto calibration. + if (value < p_element->min) { + p_element->min = value; + } + if (value > p_element->max) { + p_element->max = value; + } + } + } + return value; +} + +void joypad::add_hid_element(IOHIDElementRef p_element) { + const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; + + if (p_element && (elementTypeID == IOHIDElementGetTypeID())) { + const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element); + const uint32_t usagePage = IOHIDElementGetUsagePage(p_element); + const uint32_t usage = IOHIDElementGetUsage(p_element); + Vector *list = nullptr; + + switch (IOHIDElementGetType(p_element)) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: { + switch (usagePage) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: + if (!has_element(cookie, &axis_elements)) { + list = &axis_elements; + } + break; + + case kHIDUsage_GD_Hatswitch: + if (!has_element(cookie, &hat_elements)) { + list = &hat_elements; + } + break; + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + if (!has_element(cookie, &button_elements)) { + list = &button_elements; + } + break; + } + break; + + case kHIDPage_Simulation: + switch (usage) { + case kHIDUsage_Sim_Rudder: + case kHIDUsage_Sim_Throttle: + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: + if (!has_element(cookie, &axis_elements)) { + list = &axis_elements; + } + break; + + default: + break; + } + break; + + case kHIDPage_Button: + case kHIDPage_Consumer: + if (!has_element(cookie, &button_elements)) { + list = &button_elements; + } + break; + + default: + break; + } + } break; + + case kIOHIDElementTypeCollection: { + CFArrayRef array = IOHIDElementGetChildren(p_element); + if (array) { + add_hid_elements(array); + } + } break; + + default: + break; + } + + if (list) { // Add to list. + rec_element element; + + element.ref = p_element; + element.usage = usage; + + element.min = (SInt32)IOHIDElementGetLogicalMin(p_element); + element.max = (SInt32)IOHIDElementGetLogicalMax(p_element); + element.cookie = IOHIDElementGetCookie(p_element); + list->push_back(element); + list->sort_custom(); + } + } +} + +static void hid_element_added(const void *p_value, void *p_parameter) { + joypad *joy = static_cast(p_parameter); + joy->add_hid_element((IOHIDElementRef)p_value); +} + +void joypad::add_hid_elements(CFArrayRef p_array) { + CFRange range = { 0, CFArrayGetCount(p_array) }; + CFArrayApplyFunction(p_array, range, hid_element_added, this); +} + +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) { + self->_device_added(res, ioHIDDeviceObject); +} + +static bool is_joypad(IOHIDDeviceRef p_device_ref) { + int usage_page = 0; + int usage = 0; + CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsagePageKey)); + if (refCF) { + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage_page); + } + if (usage_page != kHIDPage_GenericDesktop) { + return false; + } + + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsageKey)); + if (refCF) { + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage); + } + if ((usage != kHIDUsage_GD_Joystick && + usage != kHIDUsage_GD_GamePad && + usage != kHIDUsage_GD_MultiAxisController)) { + return false; + } + return true; +} + +void JoypadMacOS::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { + if (p_res != kIOReturnSuccess || have_device(p_device)) { + return; + } + + joypad new_joypad; + if (is_joypad(p_device)) { + configure_joypad(p_device, &new_joypad); +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 + if (IOHIDDeviceGetService) { +#endif + const io_service_t ioservice = IOHIDDeviceGetService(p_device); + if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) { + new_joypad.ffservice = ioservice; + } +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 + } +#endif + device_list.push_back(new_joypad); + } + IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); +} + +void JoypadMacOS::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) { + int device = get_joy_ref(p_device); + ERR_FAIL_COND(device == -1); + + input->joy_connection_changed(device_list[device].id, false, ""); + device_list.write[device].free(); + device_list.remove_at(device); +} + +static String _hex_str(uint8_t p_byte) { + static const char *dict = "0123456789abcdef"; + char ret[3]; + ret[2] = 0; + + ret[0] = dict[p_byte >> 4]; + ret[1] = dict[p_byte & 0xF]; + + return ret; +} + +bool JoypadMacOS::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { + p_joy->device_ref = p_device_ref; + // Get device name. + String name; + char c_name[256]; + CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); + if (!refCF) { + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDManufacturerKey)); + } + if ((!refCF) || (!CFStringGetCString((CFStringRef)refCF, c_name, sizeof(c_name), kCFStringEncodingUTF8))) { + name = "Unidentified Joypad"; + } else { + name = c_name; + } + + int id = input->get_unused_joy_id(); + ERR_FAIL_COND_V(id == -1, false); + p_joy->id = id; + int vendor = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVendorIDKey)); + if (refCF) { + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &vendor); + } + + int product_id = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductIDKey)); + 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, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); + input->joy_connection_changed(id, true, name, uid); + } else { + // Bluetooth device. + String guid = "05000000"; + for (int i = 0; i < 12; i++) { + if (i < name.size()) { + guid += _hex_str(name[i]); + } else { + guid += "00"; + } + } + input->joy_connection_changed(id, true, name, guid); + } + + CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone); + if (array) { + p_joy->add_hid_elements(array); + CFRelease(array); + } + // Xbox controller hat values start at 1 rather than 0. + p_joy->offset_hat = vendor == 0x45e && + (product_id == 0x0b05 || + product_id == 0x02e0 || + product_id == 0x02fd || + product_id == 0x0b13); + + return true; +} + +#define FF_ERR() \ + { \ + if (ret != FF_OK) { \ + FFReleaseDevice(ff_device); \ + ff_device = nullptr; \ + return false; \ + } \ + } +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); + + ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_RESET); + FF_ERR(); + + ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_SETACTUATORSON); + FF_ERR(); + + if (check_ff_features()) { + ret = FFDeviceCreateEffect(ff_device, kFFEffectType_ConstantForce_ID, &ff_effect, &ff_object); + FF_ERR(); + return true; + } + FFReleaseDevice(ff_device); + ff_device = nullptr; + return false; +} +#undef FF_ERR + +#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; + } + int num_axes = features.numFfAxes; + ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes); + ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes); + + for (int i = 0; i < num_axes; i++) { + ff_axes[i] = features.ffAxes[i]; + ff_directions[i] = 0; + } + + ff_effect.cAxes = num_axes; + ff_effect.rgdwAxes = ff_axes; + ff_effect.rglDirection = ff_directions; + return true; + } + return false; +} + +static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) { + int range = (p_max - p_min + 1); + int value = p_value - p_min; + HatMask hat_value = HatMask::CENTER; + if (range == 4) { + value *= 2; + } + if (p_offset_hat) { + value -= 1; + } + + switch (value) { + case 0: + hat_value = HatMask::UP; + break; + case 1: + hat_value = (HatMask::UP | HatMask::RIGHT); + break; + case 2: + hat_value = HatMask::RIGHT; + break; + case 3: + hat_value = (HatMask::DOWN | HatMask::RIGHT); + break; + case 4: + hat_value = HatMask::DOWN; + break; + case 5: + hat_value = (HatMask::DOWN | HatMask::LEFT); + break; + case 6: + hat_value = HatMask::LEFT; + break; + case 7: + hat_value = (HatMask::UP | HatMask::LEFT); + break; + default: + hat_value = HatMask::CENTER; + break; + } + return hat_value; +} + +void JoypadMacOS::poll_joypads() const { + while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + // No-op. Pending callbacks will fire. + } +} + +static float axis_correct(int p_value, int p_min, int p_max) { + // Convert to a value between -1.0f and 1.0f. + return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f; +} + +void JoypadMacOS::process_joypads() { + poll_joypads(); + + for (int i = 0; i < device_list.size(); i++) { + joypad &joy = device_list.write[i]; + + for (int j = 0; j < joy.axis_elements.size(); j++) { + rec_element &elem = joy.axis_elements.write[j]; + int value = joy.get_hid_element_state(&elem); + input->joy_axis(joy.id, (JoyAxis)j, axis_correct(value, elem.min, elem.max)); + } + for (int j = 0; j < joy.button_elements.size(); j++) { + int value = joy.get_hid_element_state(&joy.button_elements.write[j]); + input->joy_button(joy.id, (JoyButton)j, (value >= 1)); + } + for (int j = 0; j < joy.hat_elements.size(); j++) { + rec_element &elem = joy.hat_elements.write[j]; + int value = joy.get_hid_element_state(&elem); + HatMask hat_value = process_hat_value(elem.min, elem.max, value, joy.offset_hat); + input->joy_hat(joy.id, hat_value); + } + + if (joy.ffservice) { + uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); + if (timestamp > joy.ff_timestamp) { + Vector2 strength = input->get_joy_vibration_strength(joy.id); + float duration = input->get_joy_vibration_duration(joy.id); + if (strength.x == 0 && strength.y == 0) { + joypad_vibration_stop(joy.id, timestamp); + } else { + float gain = MAX(strength.x, strength.y); + joypad_vibration_start(joy.id, gain, duration, timestamp); + } + } + } + } +} + +void JoypadMacOS::joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp) { + joypad *joy = &device_list.write[get_joy_index(p_id)]; + joy->ff_timestamp = p_timestamp; + joy->ff_effect.dwDuration = p_duration * FF_SECONDS; + joy->ff_effect.dwGain = p_magnitude * FF_FFNOMINALMAX; + FFEffectSetParameters(joy->ff_object, &joy->ff_effect, FFEP_DURATION | FFEP_GAIN); + FFEffectStart(joy->ff_object, 1, 0); +} + +void JoypadMacOS::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { + joypad *joy = &device_list.write[get_joy_index(p_id)]; + joy->ff_timestamp = p_timestamp; + FFEffectStop(joy->ff_object); +} + +int JoypadMacOS::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; + } + } + return -1; +} + +int JoypadMacOS::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; +} + +bool JoypadMacOS::have_device(IOHIDDeviceRef p_device) const { + for (int i = 0; i < device_list.size(); i++) { + if (device_list[i].device_ref == p_device) { + return true; + } + } + return false; +} + +static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) { + CFDictionaryRef retval = nullptr; + CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + + if (pageNumRef && usageNumRef) { + const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; + const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; + retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + + if (pageNumRef) { + CFRelease(pageNumRef); + } + if (usageNumRef) { + CFRelease(usageNumRef); + } + + if (!retval) { + *okay = 0; + } + + return retval; +} + +void JoypadMacOS::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, 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) { + // No-op. Callback fires once per existing device. + } +} + +JoypadMacOS::JoypadMacOS(Input *in) { + self = this; + input = in; + + int okay = 1; + const void *vals[] = { + (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), + (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), + (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) : nullptr; + + for (size_t i = 0; i < n_elements; i++) { + if (vals[i]) { + CFRelease((CFTypeRef)vals[i]); + } + } + + if (array) { + hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (hid_manager) { + config_hid_manager(array); + } + CFRelease(array); + } +} + +JoypadMacOS::~JoypadMacOS() { + for (int i = 0; i < device_list.size(); i++) { + device_list.write[i].free(); + } + + IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); + IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone); + CFRelease(hid_manager); + hid_manager = nullptr; +} diff --git a/platform/macos/joypad_macos.h b/platform/macos/joypad_macos.h new file mode 100644 index 0000000000..4b14fed6d5 --- /dev/null +++ b/platform/macos/joypad_macos.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* joypad_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 JOYPAD_MACOS_H +#define JOYPAD_MACOS_H + +#ifdef MACOS_10_0_4 +#import +#else +#import +#endif +#import +#import +#import + +#include "core/input/input.h" + +struct rec_element { + IOHIDElementRef ref; + IOHIDElementCookie cookie; + + uint32_t usage = 0; + + int min = 0; + int max = 0; + + struct Comparator { + bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; } + }; +}; + +struct joypad { + IOHIDDeviceRef device_ref = nullptr; + + Vector axis_elements; + Vector button_elements; + Vector hat_elements; + + int id = 0; + bool offset_hat = false; + + io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff. + FFCONSTANTFORCE ff_constant_force; + FFDeviceObjectReference ff_device = nullptr; + FFEffectObjectReference ff_object = nullptr; + uint64_t ff_timestamp = 0; + LONG *ff_directions = nullptr; + FFEFFECT ff_effect; + DWORD *ff_axes = nullptr; + + void add_hid_elements(CFArrayRef p_array); + void add_hid_element(IOHIDElementRef p_element); + + bool has_element(IOHIDElementCookie p_cookie, Vector *p_list) const; + bool config_force_feedback(io_service_t p_service); + bool check_ff_features(); + + int get_hid_element_state(rec_element *p_element) const; + + void free(); + joypad(); +}; + +class JoypadMacOS { + enum { + JOYPADS_MAX = 16, + }; + +private: + Input *input = nullptr; + IOHIDManagerRef hid_manager; + + Vector device_list; + + bool have_device(IOHIDDeviceRef p_device) const; + 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 config_hid_manager(CFArrayRef p_matching_array) const; + + void joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp); + void joypad_vibration_stop(int p_id, uint64_t p_timestamp); + +public: + void process_joypads(); + + void _device_added(IOReturn p_res, IOHIDDeviceRef p_device); + void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device); + + JoypadMacOS(Input *in); + ~JoypadMacOS(); +}; + +#endif // JOYPAD_MACOS_H diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h new file mode 100644 index 0000000000..fc5b791e44 --- /dev/null +++ b/platform/macos/key_mapping_macos.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* key_mapping_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 KEY_MAPPING_MACOS_H +#define KEY_MAPPING_MACOS_H + +#include "core/os/keyboard.h" + +class KeyMappingMacOS { + KeyMappingMacOS() {} + + static bool is_numpad_key(unsigned int key); + +public: + // Mappings input. + static Key translate_key(unsigned int key); + static unsigned int unmap_key(Key key); + static Key remap_key(unsigned int key, unsigned int state); + + // Mapping for menu shortcuts. + static String keycode_get_native_string(Key p_keycode); + static unsigned int keycode_get_native_mask(Key p_keycode); +}; + +#endif // KEY_MAPPING_MACOS_H diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm new file mode 100644 index 0000000000..f6cff7124b --- /dev/null +++ b/platform/macos/key_mapping_macos.mm @@ -0,0 +1,496 @@ +/*************************************************************************/ +/* key_mapping_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "key_mapping_macos.h" + +#import +#import + +bool KeyMappingMacOS::is_numpad_key(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; +} + +// Keyboard symbol translation table. +static const Key _macos_to_godot_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::KEY_1, + /* 13 */ Key::KEY_2, + /* 14 */ Key::KEY_3, + /* 15 */ Key::KEY_4, + /* 16 */ Key::KEY_6, + /* 17 */ Key::KEY_5, + /* 18 */ Key::EQUAL, + /* 19 */ Key::KEY_9, + /* 1a */ Key::KEY_7, + /* 1b */ Key::MINUS, + /* 1c */ Key::KEY_8, + /* 1d */ Key::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::CTRL, + /* 3c */ Key::SHIFT, + /* 3d */ Key::ALT, + /* 3e */ Key::CTRL, + /* 3f */ Key::UNKNOWN, /* Function */ + /* 40 */ Key::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::F18, + /* 50 */ Key::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::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::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, +}; + +// Translates a OS X keycode to a Godot keycode. +Key KeyMappingMacOS::translate_key(unsigned int key) { + if (key >= 128) { + return Key::UNKNOWN; + } + + return _macos_to_godot_table[key]; +} + +// Translates a Godot keycode back to a macOS keycode. +unsigned int KeyMappingMacOS::unmap_key(Key key) { + for (int i = 0; i <= 126; i++) { + if (_macos_to_godot_table[i] == key) { + return i; + } + } + return 127; +} + +struct _KeyCodeMap { + UniChar kchar; + Key kcode; +}; + +static const _KeyCodeMap _keycodes[55] = { + { '`', Key::QUOTELEFT }, + { '~', Key::ASCIITILDE }, + { '0', Key::KEY_0 }, + { '1', Key::KEY_1 }, + { '2', Key::KEY_2 }, + { '3', Key::KEY_3 }, + { '4', Key::KEY_4 }, + { '5', Key::KEY_5 }, + { '6', Key::KEY_6 }, + { '7', Key::KEY_7 }, + { '8', Key::KEY_8 }, + { '9', Key::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 } +}; + +// Remap key according to current keyboard layout. +Key KeyMappingMacOS::remap_key(unsigned int key, unsigned int state) { + if (is_numpad_key(key)) { + return translate_key(key); + } + + TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource(); + if (!current_keyboard) { + return translate_key(key); + } + + CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData); + if (!layout_data) { + return translate_key(key); + } + + const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); + + UInt32 keys_down = 0; + UniChar chars[4]; + UniCharCount real_length; + + OSStatus err = UCKeyTranslate(keyboard_layout, + key, + kUCKeyActionDisplay, + (state >> 8) & 0xFF, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keys_down, + sizeof(chars) / sizeof(chars[0]), + &real_length, + chars); + + if (err != noErr) { + return translate_key(key); + } + + for (unsigned int i = 0; i < 55; i++) { + if (_keycodes[i].kchar == chars[0]) { + return _keycodes[i].kcode; + } + } + return translate_key(key); +} + +struct _KeyCodeText { + Key code; + char32_t text; +}; + +static const _KeyCodeText _native_keycodes[] = { + /* clang-format off */ + {Key::ESCAPE ,0x001B}, + {Key::TAB ,0x0009}, + {Key::BACKTAB ,0x007F}, + {Key::BACKSPACE ,0x0008}, + {Key::ENTER ,0x000D}, + {Key::INSERT ,NSInsertFunctionKey}, + {Key::KEY_DELETE ,0x007F}, + {Key::PAUSE ,NSPauseFunctionKey}, + {Key::PRINT ,NSPrintScreenFunctionKey}, + {Key::SYSREQ ,NSSysReqFunctionKey}, + {Key::CLEAR ,NSClearLineFunctionKey}, + {Key::HOME ,0x2196}, + {Key::END ,0x2198}, + {Key::LEFT ,0x001C}, + {Key::UP ,0x001E}, + {Key::RIGHT ,0x001D}, + {Key::DOWN ,0x001F}, + {Key::PAGEUP ,0x21DE}, + {Key::PAGEDOWN ,0x21DF}, + {Key::NUMLOCK ,NSClearLineFunctionKey}, + {Key::SCROLLLOCK ,NSScrollLockFunctionKey}, + {Key::F1 ,NSF1FunctionKey}, + {Key::F2 ,NSF2FunctionKey}, + {Key::F3 ,NSF3FunctionKey}, + {Key::F4 ,NSF4FunctionKey}, + {Key::F5 ,NSF5FunctionKey}, + {Key::F6 ,NSF6FunctionKey}, + {Key::F7 ,NSF7FunctionKey}, + {Key::F8 ,NSF8FunctionKey}, + {Key::F9 ,NSF9FunctionKey}, + {Key::F10 ,NSF10FunctionKey}, + {Key::F11 ,NSF11FunctionKey}, + {Key::F12 ,NSF12FunctionKey}, + {Key::F13 ,NSF13FunctionKey}, + {Key::F14 ,NSF14FunctionKey}, + {Key::F15 ,NSF15FunctionKey}, + {Key::F16 ,NSF16FunctionKey}, + {Key::F17 ,NSF17FunctionKey}, + {Key::F18 ,NSF18FunctionKey}, + {Key::F19 ,NSF19FunctionKey}, + {Key::F20 ,NSF20FunctionKey}, + {Key::F21 ,NSF21FunctionKey}, + {Key::F22 ,NSF22FunctionKey}, + {Key::F23 ,NSF23FunctionKey}, + {Key::F24 ,NSF24FunctionKey}, + {Key::F25 ,NSF25FunctionKey}, + {Key::F26 ,NSF26FunctionKey}, + {Key::F27 ,NSF27FunctionKey}, + {Key::F28 ,NSF28FunctionKey}, + {Key::F29 ,NSF29FunctionKey}, + {Key::F30 ,NSF30FunctionKey}, + {Key::F31 ,NSF31FunctionKey}, + {Key::F32 ,NSF32FunctionKey}, + {Key::F33 ,NSF33FunctionKey}, + {Key::F34 ,NSF34FunctionKey}, + {Key::F35 ,NSF35FunctionKey}, + {Key::MENU ,NSMenuFunctionKey}, + {Key::HELP ,NSHelpFunctionKey}, + {Key::STOP ,NSStopFunctionKey}, + {Key::LAUNCH0 ,NSUserFunctionKey}, + {Key::SPACE ,0x0020}, + {Key::EXCLAM ,'!'}, + {Key::QUOTEDBL ,'\"'}, + {Key::NUMBERSIGN ,'#'}, + {Key::DOLLAR ,'$'}, + {Key::PERCENT ,'\%'}, + {Key::AMPERSAND ,'&'}, + {Key::APOSTROPHE ,'\''}, + {Key::PARENLEFT ,'('}, + {Key::PARENRIGHT ,')'}, + {Key::ASTERISK ,'*'}, + {Key::PLUS ,'+'}, + {Key::COMMA ,','}, + {Key::MINUS ,'-'}, + {Key::PERIOD ,'.'}, + {Key::SLASH ,'/'}, + {Key::KEY_0 ,'0'}, + {Key::KEY_1 ,'1'}, + {Key::KEY_2 ,'2'}, + {Key::KEY_3 ,'3'}, + {Key::KEY_4 ,'4'}, + {Key::KEY_5 ,'5'}, + {Key::KEY_6 ,'6'}, + {Key::KEY_7 ,'7'}, + {Key::KEY_8 ,'8'}, + {Key::KEY_9 ,'9'}, + {Key::COLON ,':'}, + {Key::SEMICOLON ,';'}, + {Key::LESS ,'<'}, + {Key::EQUAL ,'='}, + {Key::GREATER ,'>'}, + {Key::QUESTION ,'?'}, + {Key::AT ,'@'}, + {Key::A ,'a'}, + {Key::B ,'b'}, + {Key::C ,'c'}, + {Key::D ,'d'}, + {Key::E ,'e'}, + {Key::F ,'f'}, + {Key::G ,'g'}, + {Key::H ,'h'}, + {Key::I ,'i'}, + {Key::J ,'j'}, + {Key::K ,'k'}, + {Key::L ,'l'}, + {Key::M ,'m'}, + {Key::N ,'n'}, + {Key::O ,'o'}, + {Key::P ,'p'}, + {Key::Q ,'q'}, + {Key::R ,'r'}, + {Key::S ,'s'}, + {Key::T ,'t'}, + {Key::U ,'u'}, + {Key::V ,'v'}, + {Key::W ,'w'}, + {Key::X ,'x'}, + {Key::Y ,'y'}, + {Key::Z ,'z'}, + {Key::BRACKETLEFT ,'['}, + {Key::BACKSLASH ,'\\'}, + {Key::BRACKETRIGHT ,']'}, + {Key::ASCIICIRCUM ,'^'}, + {Key::UNDERSCORE ,'_'}, + {Key::QUOTELEFT ,'`'}, + {Key::BRACELEFT ,'{'}, + {Key::BAR ,'|'}, + {Key::BRACERIGHT ,'}'}, + {Key::ASCIITILDE ,'~'}, + {Key::NONE ,0x0000} + /* clang-format on */ +}; + +String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) { + const _KeyCodeText *kct = &_native_keycodes[0]; + + while (kct->text) { + if (kct->code == p_keycode) { + return String::chr(kct->text); + } + kct++; + } + return String(); +} + +unsigned int KeyMappingMacOS::keycode_get_native_mask(Key p_keycode) { + unsigned int mask = 0; + if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) { + mask |= NSEventModifierFlagControl; + } + if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) { + mask |= NSEventModifierFlagOption; + } + if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) { + mask |= NSEventModifierFlagShift; + } + if ((p_keycode & KeyModifierMask::META) != Key::NONE) { + mask |= NSEventModifierFlagCommand; + } + if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) { + mask |= NSEventModifierFlagNumericPad; + } + return mask; +} diff --git a/platform/macos/logo.png b/platform/macos/logo.png new file mode 100644 index 0000000000..b5a660b165 Binary files /dev/null and b/platform/macos/logo.png differ diff --git a/platform/macos/macos_terminal_logger.h b/platform/macos/macos_terminal_logger.h new file mode 100644 index 0000000000..bad2c1d657 --- /dev/null +++ b/platform/macos/macos_terminal_logger.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* macos_terminal_logger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 MACOS_TERMINAL_LOGGER_H +#define MACOS_TERMINAL_LOGGER_H + +#ifdef MACOS_ENABLED + +#include "core/io/logger.h" + +class MacOSTerminalLogger : 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, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; +}; + +#endif // MACOS_ENABLED +#endif // MACOS_TERMINAL_LOGGER_H diff --git a/platform/macos/macos_terminal_logger.mm b/platform/macos/macos_terminal_logger.mm new file mode 100644 index 0000000000..b5ea2938ee --- /dev/null +++ b/platform/macos/macos_terminal_logger.mm @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* macos_terminal_logger.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "macos_terminal_logger.h" + +#ifdef MACOS_ENABLED + +#include + +void MacOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { + 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: + 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; + } +} + +#endif // MACOS_ENABLED diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h new file mode 100644 index 0000000000..914cdb9af7 --- /dev/null +++ b/platform/macos/os_macos.h @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* os_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_MACOS_H +#define OS_MACOS_H + +#include "core/input/input.h" +#include "crash_handler_macos.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/coremidi/midi_driver_coremidi.h" +#include "drivers/unix/os_unix.h" +#include "joypad_macos.h" +#include "servers/audio_server.h" + +class OS_MacOS : public OS_Unix { + bool force_quit = false; + + JoypadMacOS *joypad_macos = nullptr; + +#ifdef COREAUDIO_ENABLED + AudioDriverCoreAudio audio_driver; +#endif +#ifdef COREMIDI_ENABLED + MIDIDriverCoreMidi midi_driver; +#endif + + CrashHandler crash_handler; + + CFRunLoopObserverRef pre_wait_observer; + + MainLoop *main_loop = nullptr; + + List launch_service_args; + + static _FORCE_INLINE_ String get_framework_executable(const String &p_path); + static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + +protected: + virtual void initialize_core() override; + virtual void initialize() override; + virtual void finalize() override; + + virtual void initialize_joypads() override; + + virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual void delete_main_loop() override; + +public: + virtual void set_cmdline_platform_args(const List &p_args); + virtual List get_cmdline_platform_args() const override; + + virtual String get_name() const override; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; + + virtual MainLoop *get_main_loop() const override; + + virtual String get_config_path() const override; + virtual String get_data_path() const override; + virtual String get_cache_path() const override; + virtual String get_bundle_resource_dir() const override; + virtual String get_bundle_icon_path() const override; + virtual String get_godot_dir_name() const override; + + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; + + virtual Error shell_open(String p_uri) override; + + virtual String get_locale() const override; + + virtual String get_executable_path() const override; + virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; + virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; + + virtual String get_unique_id() const override; + virtual String get_processor_name() const override; + + virtual bool _check_internal_feature_support(const String &p_feature) override; + + virtual void disable_crash_handler() override; + virtual bool is_disable_crash_handler() const override; + + virtual Error move_to_trash(const String &p_path) override; + + void run(); + + OS_MacOS(); + ~OS_MacOS(); +}; + +#endif diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm new file mode 100644 index 0000000000..2c6cd7de0b --- /dev/null +++ b/platform/macos/os_macos.mm @@ -0,0 +1,524 @@ +/*************************************************************************/ +/* os_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_macos.h" + +#include "core/version_generated.gen.h" +#include "main/main.h" + +#include "dir_access_macos.h" +#include "display_server_macos.h" +#include "godot_application.h" +#include "godot_application_delegate.h" +#include "macos_terminal_logger.h" + +#include +#include +#include +#include +#include + +_FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) { + // Append framework executable name, or return as is if p_path is not a framework. + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { + return p_path.plus_file(p_path.get_file().get_basename()); + } else { + return p_path; + } +} + +void OS_MacOS::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { + // Prevent main loop from sleeping and redraw window during resize / modal popups. + + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (get_singleton()->get_main_loop() && ds && (get_singleton()->get_render_thread_mode() != RENDER_SEPARATE_THREAD || !ds->get_is_resizing())) { + Main::force_redraw(); + if (!Main::is_iterating()) { // Avoid cyclic loop. + Main::iteration(); + } + } + + CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. +} + +void OS_MacOS::initialize() { + crash_handler.initialize(); + + initialize_core(); +} + +String OS_MacOS::get_processor_name() const { + char buffer[256]; + size_t buffer_len = 256; + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) { + return String::utf8(buffer, buffer_len); + } + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); +} + +void OS_MacOS::initialize_core() { + OS_Unix::initialize_core(); + + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + DirAccess::make_default(DirAccess::ACCESS_USERDATA); + DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); +} + +void OS_MacOS::finalize() { +#ifdef COREMIDI_ENABLED + midi_driver.close(); +#endif + + delete_main_loop(); + + if (joypad_macos) { + memdelete(joypad_macos); + } +} + +void OS_MacOS::initialize_joypads() { + joypad_macos = memnew(JoypadMacOS(Input::get_singleton())); +} + +void OS_MacOS::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; +} + +void OS_MacOS::delete_main_loop() { + if (!main_loop) { + return; + } + + memdelete(main_loop); + main_loop = nullptr; +} + +void OS_MacOS::set_cmdline_platform_args(const List &p_args) { + launch_service_args = p_args; +} + +List OS_MacOS::get_cmdline_platform_args() const { + return launch_service_args; +} + +String OS_MacOS::get_name() const { + return "macOS"; +} + +void OS_MacOS::alert(const String &p_alert, const String &p_title) { + 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]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; + } +} + +Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { + String path = get_framework_executable(p_path); + + if (!FileAccess::exists(path)) { + // Load .dylib or framework from within the executable path. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); + } + + if (!FileAccess::exists(path)) { + // Load .dylib or framework from a standard macOS location. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); + } + + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); + + if (r_resolved_path != nullptr) { + *r_resolved_path = path; + } + + return OK; +} + +MainLoop *OS_MacOS::get_main_loop() const { + return main_loop; +} + +String OS_MacOS::get_config_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. + if (has_environment("XDG_CONFIG_HOME")) { + if (get_environment("XDG_CONFIG_HOME").is_absolute_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Application Support` or `.` per the XDG Base Directory specification."); + } + } + if (has_environment("HOME")) { + return get_environment("HOME").plus_file("Library/Application Support"); + } + return "."; +} + +String OS_MacOS::get_data_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. + if (has_environment("XDG_DATA_HOME")) { + if (get_environment("XDG_DATA_HOME").is_absolute_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); + } + } + return get_config_path(); +} + +String OS_MacOS::get_cache_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. + if (has_environment("XDG_CACHE_HOME")) { + if (get_environment("XDG_CACHE_HOME").is_absolute_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Caches` or `get_config_path()` per the XDG Base Directory specification."); + } + } + if (has_environment("HOME")) { + return get_environment("HOME").plus_file("Library/Caches"); + } + return get_config_path(); +} + +String OS_MacOS::get_bundle_resource_dir() const { + String ret; + + NSBundle *main = [NSBundle mainBundle]; + if (main) { + NSString *resource_path = [main resourcePath]; + ret.parse_utf8([resource_path UTF8String]); + } + return ret; +} + +String OS_MacOS::get_bundle_icon_path() const { + String ret; + + NSBundle *main = [NSBundle mainBundle]; + if (main) { + NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; + if (icon_path) { + ret.parse_utf8([icon_path UTF8String]); + } + } + return ret; +} + +// Get properly capitalized engine name for system paths +String OS_MacOS::get_godot_dir_name() const { + return String(VERSION_SHORT_NAME).capitalize(); +} + +String OS_MacOS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { + NSSearchPathDirectory id; + bool found = true; + + switch (p_dir) { + case SYSTEM_DIR_DESKTOP: { + id = NSDesktopDirectory; + } break; + case SYSTEM_DIR_DOCUMENTS: { + id = NSDocumentDirectory; + } break; + case SYSTEM_DIR_DOWNLOADS: { + id = NSDownloadsDirectory; + } break; + case SYSTEM_DIR_MOVIES: { + id = NSMoviesDirectory; + } break; + case SYSTEM_DIR_MUSIC: { + id = NSMusicDirectory; + } break; + case SYSTEM_DIR_PICTURES: { + id = NSPicturesDirectory; + } break; + default: { + found = false; + } + } + + String ret; + if (found) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); + if (paths && [paths count] >= 1) { + ret.parse_utf8([[paths firstObject] UTF8String]); + } + } + + return ret; +} + +Error OS_MacOS::shell_open(String p_uri) { + 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; +} + +String OS_MacOS::get_locale() const { + NSString *locale_code = [[NSLocale preferredLanguages] objectAtIndex:0]; + return String([locale_code UTF8String]).replace("-", "_"); +} + +String OS_MacOS::get_executable_path() const { + char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; + int pid = getpid(); + pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + if (ret <= 0) { + return OS::get_executable_path(); + } else { + String path; + path.parse_utf8(pathbuf); + + return path; + } +} + +Error OS_MacOS::create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id, bool p_open_console) { + // Use NSWorkspace if path is an .app bundle. + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSBundle *bundle = [NSBundle bundleWithURL:url]; + if (bundle) { + NSMutableArray *arguments = [[NSMutableArray alloc] init]; + for (const String &arg : p_arguments) { + [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]]; + } + if (@available(macOS 10.15, *)) { + NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; + [configuration setArguments:arguments]; + [configuration setCreatesNewApplicationInstance:YES]; + __block dispatch_semaphore_t lock = dispatch_semaphore_create(0); + __block Error err = ERR_TIMEOUT; + __block pid_t pid = 0; + + [[NSWorkspace sharedWorkspace] openApplicationAtURL:url + configuration:configuration + completionHandler:^(NSRunningApplication *app, NSError *error) { + if (error) { + err = ERR_CANT_FORK; + NSLog(@"Failed to execute: %@", error.localizedDescription); + } else { + pid = [app processIdentifier]; + err = OK; + } + dispatch_semaphore_signal(lock); + }]; + dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. + + if (err == OK) { + if (r_child_id) { + *r_child_id = (ProcessID)pid; + } + } + + return err; + } else { + Error err = ERR_TIMEOUT; + NSError *error = nullptr; + NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; + if (error) { + err = ERR_CANT_FORK; + NSLog(@"Failed to execute: %@", error.localizedDescription); + } else { + if (r_child_id) { + *r_child_id = (ProcessID)[app processIdentifier]; + } + err = OK; + } + return err; + } + } else { + return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console); + } +} + +Error OS_MacOS::create_instance(const List &p_arguments, ProcessID *r_child_id) { + // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname != nil) { + String path; + path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); + return create_process(path, p_arguments, r_child_id, false); + } else { + return create_process(get_executable_path(), p_arguments, r_child_id, false); + } +} + +String OS_MacOS::get_unique_id() const { + static String serial_number; + + if (serial_number.is_empty()) { + io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + CFStringRef serial_number_cf_string = nullptr; + if (platform_expert) { + serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + IOObjectRelease(platform_expert); + } + + NSString *serial_number_ns_string = nil; + if (serial_number_cf_string) { + serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string]; + CFRelease(serial_number_cf_string); + } + + if (serial_number_ns_string) { + serial_number.parse_utf8([serial_number_ns_string UTF8String]); + } + } + + return serial_number; +} + +bool OS_MacOS::_check_internal_feature_support(const String &p_feature) { + return p_feature == "pc"; +} + +void OS_MacOS::disable_crash_handler() { + crash_handler.disable(); +} + +bool OS_MacOS::is_disable_crash_handler() const { + return crash_handler.is_disabled(); +} + +Error OS_MacOS::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_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); + return FAILED; + } + + return OK; +} + +void OS_MacOS::run() { + force_quit = false; + + if (!main_loop) { + return; + } + + main_loop->initialize(); + + bool quit = false; + while (!force_quit && !quit) { + @try { + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); // Get rid of pending events. + } + joypad_macos->process_joypads(); + + if (Main::iteration()) { + quit = true; + } + } @catch (NSException *exception) { + ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); + } + } + + main_loop->finalize(); +} + +OS_MacOS::OS_MacOS() { + main_loop = nullptr; + force_quit = false; + + Vector loggers; + loggers.push_back(memnew(MacOSTerminalLogger)); + _set_logger(memnew(CompositeLogger(loggers))); + +#ifdef COREAUDIO_ENABLED + AudioDriverManager::add_driver(&audio_driver); +#endif + + DisplayServerMacOS::register_macos_driver(); + + // 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. + + NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:main_menu]; + [NSApp finishLaunching]; + + id delegate = [[GodotApplicationDelegate alloc] init]; + ERR_FAIL_COND(!delegate); + [NSApp setDelegate:delegate]; + + pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + + // 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]; + } + + [NSApp activateIgnoringOtherApps:YES]; +} + +OS_MacOS::~OS_MacOS() { + CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + CFRelease(pre_wait_observer); +} diff --git a/platform/macos/platform_config.h b/platform/macos/platform_config.h new file mode 100644 index 0000000000..e114606b82 --- /dev/null +++ b/platform/macos/platform_config.h @@ -0,0 +1,34 @@ +/*************************************************************************/ +/* platform_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +#define OPENGL_INCLUDE_H "thirdparty/glad/glad/glad.h" +#define PTHREAD_RENAME_SELF diff --git a/platform/macos/platform_macos_builders.py b/platform/macos/platform_macos_builders.py new file mode 100644 index 0000000000..3a1cc92bd2 --- /dev/null +++ b/platform/macos/platform_macos_builders.py @@ -0,0 +1,21 @@ +"""Functions used to generate source files during build time + +All such functions are invoked in a subprocess on Windows to prevent build flakiness. + +""" +import os +from platform_methods import subprocess_main + + +def make_debug_macos(target, source, env): + 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])) + else: + os.system("dsymutil {0} -o {0}.dSYM".format(target[0])) + os.system("strip -u -r {0}".format(target[0])) + + +if __name__ == "__main__": + subprocess_main(globals()) diff --git a/platform/macos/tts_macos.h b/platform/macos/tts_macos.h new file mode 100644 index 0000000000..344676868a --- /dev/null +++ b/platform/macos/tts_macos.h @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* tts_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 TTS_MACOS_H +#define TTS_MACOS_H + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/rb_map.h" +#include "core/variant/array.h" +#include "servers/display_server.h" + +#import + +#if __has_include() +#import +#else +#import +#endif + +@interface TTS_MacOS : NSObject { + // AVSpeechSynthesizer + bool speaking; + HashMap ids; + + // NSSpeechSynthesizer + bool paused; + bool have_utterance; + int last_utterance; + + id synth; // NSSpeechSynthesizer or AVSpeechSynthesizer + List queue; +} + +- (void)pauseSpeaking; +- (void)resumeSpeaking; +- (void)stopSpeaking; +- (bool)isSpeaking; +- (bool)isPaused; +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; +- (Array)getVoices; +@end + +#endif // TTS_MACOS_H diff --git a/platform/macos/tts_macos.mm b/platform/macos/tts_macos.mm new file mode 100644 index 0000000000..3c101b9531 --- /dev/null +++ b/platform/macos/tts_macos.mm @@ -0,0 +1,266 @@ +/*************************************************************************/ +/* tts_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "tts_macos.h" + +@implementation TTS_MacOS + +- (id)init { + self = [super init]; + self->speaking = false; + self->have_utterance = false; + self->last_utterance = -1; + self->paused = false; + if (@available(macOS 10.14, *)) { + self->synth = [[AVSpeechSynthesizer alloc] init]; + [self->synth setDelegate:self]; + print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); + } else { + self->synth = [[NSSpeechSynthesizer alloc] init]; + [self->synth setDelegate:self]; + print_verbose("Text-to-Speech: NSSpeechSynthesizer initialized."); + } + return self; +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + NSString *string = [utterance speechString]; + + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +// AVSpeechSynthesizer callback (macOS 10.14+) + +- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); + ids.erase(utterance); + speaking = false; + [self update]; +} + +// NSSpeechSynthesizer callback (macOS 10.4+) + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth willSpeakWord:(NSRange)characterRange ofString:(NSString *)string { + if (!paused && have_utterance) { + // Convert from UTF-16 to UTF-32 position. + int pos = 0; + for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { + unichar c = [string characterAtIndex:i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, last_utterance, pos); + } +} + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth didFinishSpeaking:(BOOL)success { + if (!paused && have_utterance) { + if (success) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, last_utterance); + } else { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); + } + have_utterance = false; + } + speaking = false; + [self update]; +} + +- (void)update { + if (!speaking && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; + if (message.rate > 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; + } else if (message.rate < 1.f) { + [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; + } + [new_utterance setPitchMultiplier:message.pitch]; + [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + + ids[new_utterance] = message.id; + [av_synth speakUtterance:new_utterance]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth setObject:nil forProperty:NSSpeechResetProperty error:nil]; + [ns_synth setVoice:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]; + int base_pitch = [[ns_synth objectForProperty:NSSpeechPitchBaseProperty error:nil] intValue]; + [ns_synth setObject:[NSNumber numberWithInt:(base_pitch * (message.pitch / 2.f + 0.5f))] forProperty:NSSpeechPitchBaseProperty error:nullptr]; + [ns_synth setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; + [ns_synth setRate:(message.rate * 200)]; + + last_utterance = message.id; + have_utterance = true; + [ns_synth startSpeakingString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; + } + queue.pop_front(); + + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); + speaking = true; + } +} + +- (void)pauseSpeaking { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth pauseSpeakingAtBoundary:NSSpeechImmediateBoundary]; + } + paused = true; +} + +- (void)resumeSpeaking { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth continueSpeaking]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + [ns_synth continueSpeaking]; + } + paused = false; +} + +- (void)stopSpeaking { + for (DisplayServer::TTSUtterance &message : queue) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); + } + queue.clear(); + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + } else { + NSSpeechSynthesizer *ns_synth = synth; + if (have_utterance) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); + } + [ns_synth stopSpeaking]; + } + have_utterance = false; + speaking = false; + paused = false; +} + +- (bool)isSpeaking { + return speaking || (queue.size() > 0); +} + +- (bool)isPaused { + if (@available(macOS 10.14, *)) { + AVSpeechSynthesizer *av_synth = synth; + return [av_synth isPaused]; + } else { + return paused; + } +} + +- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { + if (interrupt) { + [self stopSpeaking]; + } + + if (text.is_empty()) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); + return; + } + + DisplayServer::TTSUtterance message; + message.text = text; + message.voice = voice; + message.volume = CLAMP(volume, 0, 100); + message.pitch = CLAMP(pitch, 0.f, 2.f); + message.rate = CLAMP(rate, 0.1f, 10.f); + message.id = utterance_id; + queue.push_back(message); + + if ([self isPaused]) { + [self resumeSpeaking]; + } else { + [self update]; + } +} + +- (Array)getVoices { + Array list; + if (@available(macOS 10.14, *)) { + for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { + NSString *voiceIdentifierString = [voice identifier]; + NSString *voiceLocaleIdentifier = [voice language]; + NSString *voiceName = [voice name]; + Dictionary voice_d; + voice_d["name"] = String::utf8([voiceName UTF8String]); + voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); + voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + } else { + for (NSString *voiceIdentifierString in [NSSpeechSynthesizer availableVoices]) { + NSString *voiceLocaleIdentifier = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceLocaleIdentifier]; + NSString *voiceName = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceName]; + Dictionary voice_d; + voice_d["name"] = String([voiceName UTF8String]); + voice_d["id"] = String([voiceIdentifierString UTF8String]); + voice_d["language"] = String([voiceLocaleIdentifier UTF8String]); + list.push_back(voice_d); + } + } + return list; +} + +@end diff --git a/platform/macos/vulkan_context_macos.h b/platform/macos/vulkan_context_macos.h new file mode 100644 index 0000000000..4dc6a12756 --- /dev/null +++ b/platform/macos/vulkan_context_macos.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* vulkan_context_macos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 VULKAN_DEVICE_MACOS_H +#define VULKAN_DEVICE_MACOS_H + +#include "drivers/vulkan/vulkan_context.h" +#import + +class VulkanContextMacOS : public VulkanContext { + virtual const char *_get_platform_surface_extension() const; + +public: + Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height); + + VulkanContextMacOS(); + ~VulkanContextMacOS(); +}; + +#endif // VULKAN_DEVICE_MACOS_H diff --git a/platform/macos/vulkan_context_macos.mm b/platform/macos/vulkan_context_macos.mm new file mode 100644 index 0000000000..cf317f3c68 --- /dev/null +++ b/platform/macos/vulkan_context_macos.mm @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* vulkan_context_macos.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_macos.h" +#ifdef USE_VOLK +#include +#else +#include +#endif + +const char *VulkanContextMacOS::_get_platform_surface_extension() const { + return VK_MVK_MACOS_SURFACE_EXTENSION_NAME; +} + +Error VulkanContextMacOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height) { + VkMacOSSurfaceCreateInfoMVK createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.pView = (__bridge const void *)p_window; + + VkSurfaceKHR surface; + VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); +} + +VulkanContextMacOS::VulkanContextMacOS() { +} + +VulkanContextMacOS::~VulkanContextMacOS() { +} diff --git a/platform/osx/SCsub b/platform/osx/SCsub deleted file mode 100644 index 3a4c95613d..0000000000 --- a/platform/osx/SCsub +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -Import("env") - -from platform_methods import run_in_subprocess -import platform_osx_builders - -files = [ - "os_osx.mm", - "godot_application.mm", - "godot_application_delegate.mm", - "crash_handler_osx.mm", - "osx_terminal_logger.mm", - "display_server_osx.mm", - "godot_content_view.mm", - "godot_window_delegate.mm", - "godot_window.mm", - "key_mapping_osx.mm", - "godot_main_osx.mm", - "dir_access_osx.mm", - "tts_osx.mm", - "joypad_osx.cpp", - "vulkan_context_osx.mm", - "gl_manager_osx_legacy.mm", -] - -prog = env.add_program("#bin/godot", files) - -if env["debug_symbols"] and env["separate_debug_symbols"]: - env.AddPostAction(prog, run_in_subprocess(platform_osx_builders.make_debug_osx)) diff --git a/platform/osx/crash_handler_osx.h b/platform/osx/crash_handler_osx.h deleted file mode 100644 index 72938e5e0a..0000000000 --- a/platform/osx/crash_handler_osx.h +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************/ -/* crash_handler_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 CRASH_HANDLER_OSX_H -#define CRASH_HANDLER_OSX_H - -class CrashHandler { - bool disabled; - -public: - void initialize(); - - void disable(); - bool is_disabled() const { return disabled; }; - - CrashHandler(); - ~CrashHandler(); -}; - -#endif // CRASH_HANDLER_OSX_H diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm deleted file mode 100644 index a798ba3b46..0000000000 --- a/platform/osx/crash_handler_osx.mm +++ /dev/null @@ -1,203 +0,0 @@ -/*************************************************************************/ -/* crash_handler_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "crash_handler_osx.h" - -#include "core/config/project_settings.h" -#include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/version.h" -#include "main/main.h" - -#include -#include - -#if defined(DEBUG_ENABLED) -#define CRASH_HANDLER_ENABLED 1 -#endif - -#ifdef CRASH_HANDLER_ENABLED -#include -#include -#include -#include -#include - -#include -#include - -static uint64_t load_address() { - const struct segment_command_64 *cmd = getsegbyname("__TEXT"); - char full_path[1024]; - uint32_t size = sizeof(full_path); - - if (cmd && !_NSGetExecutablePath(full_path, &size)) { - uint32_t dyld_count = _dyld_image_count(); - for (uint32_t i = 0; i < dyld_count; i++) { - const char *image_name = _dyld_get_image_name(i); - if (image_name && strncmp(image_name, full_path, 1024) == 0) { - return cmd->vmaddr + _dyld_get_image_vmaddr_slide(i); - } - } - } - - return 0; -} - -static void handle_crash(int sig) { - if (OS::get_singleton() == nullptr) { - abort(); - } - - void *bt_buffer[256]; - size_t size = backtrace(bt_buffer, 256); - String _execpath = OS::get_singleton()->get_executable_path(); - - String msg; - const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); - if (proj_settings) { - msg = proj_settings->get("debug/settings/crash_handler/message"); - } - - // Tell MainLoop about the crash. This can be handled by users too in Node. - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); - } - - // Dump the backtrace to stderr with a message to the user - print_error("\n================================================================"); - print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig)); - - // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).is_empty()) { - print_error(vformat("Engine version: %s", VERSION_FULL_NAME)); - } else { - print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH)); - } - print_error(vformat("Dumping the backtrace. %s", msg)); - char **strings = backtrace_symbols(bt_buffer, size); - if (strings) { - void *load_addr = (void *)load_address(); - - for (size_t i = 1; i < size; i++) { - char fname[1024]; - Dl_info info; - - snprintf(fname, 1024, "%s", strings[i]); - - // Try to demangle the function name to provide a more readable one - if (dladdr(bt_buffer[i], &info) && info.dli_sname) { - if (info.dli_sname[0] == '_') { - int status; - char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status); - - if (status == 0 && demangled) { - snprintf(fname, 1024, "%s", demangled); - } - - if (demangled) { - free(demangled); - } - } - } - - String output = fname; - - // Try to get the file/line number using atos - if (bt_buffer[i] > (void *)0x0 && OS::get_singleton()) { - List args; - char str[1024]; - - args.push_back("-o"); - args.push_back(_execpath); -#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) - args.push_back("-arch"); - args.push_back("x86_64"); -#elif defined(__aarch64__) - args.push_back("-arch"); - args.push_back("arm64"); -#endif - args.push_back("-l"); - snprintf(str, 1024, "%p", load_addr); - args.push_back(str); - snprintf(str, 1024, "%p", bt_buffer[i]); - args.push_back(str); - - int ret; - String out = ""; - Error err = OS::get_singleton()->execute(String("atos"), args, &out, &ret); - if (err == OK && out.substr(0, 2) != "0x") { - out = out.substr(0, out.length() - 1); - output = out; - } - } - - print_error(vformat("[%d] %s", (int64_t)i, output)); - } - - free(strings); - } - print_error("-- END OF BACKTRACE --"); - print_error("================================================================"); - - // Abort to pass the error to the OS - abort(); -} -#endif - -CrashHandler::CrashHandler() { - disabled = false; -} - -CrashHandler::~CrashHandler() { - disable(); -} - -void CrashHandler::disable() { - if (disabled) { - return; - } - -#ifdef CRASH_HANDLER_ENABLED - signal(SIGSEGV, nullptr); - signal(SIGFPE, nullptr); - signal(SIGILL, nullptr); -#endif - - disabled = true; -} - -void CrashHandler::initialize() { -#ifdef CRASH_HANDLER_ENABLED - signal(SIGSEGV, handle_crash); - signal(SIGFPE, handle_crash); - signal(SIGILL, handle_crash); -#endif -} diff --git a/platform/osx/detect.py b/platform/osx/detect.py deleted file mode 100644 index 47765cff71..0000000000 --- a/platform/osx/detect.py +++ /dev/null @@ -1,251 +0,0 @@ -import os -import sys -from methods import detect_darwin_sdk_path - - -def is_active(): - return True - - -def get_name(): - return "OSX" - - -def can_build(): - if sys.platform == "darwin" or ("OSXCROSS_ROOT" in os.environ): - return True - - return False - - -def get_opts(): - from SCons.Variables import BoolVariable, EnumVariable - - return [ - ("osxcross_sdk", "OSXCross SDK version", "darwin16"), - ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), - ("vulkan_sdk_path", "Path to the Vulkan SDK", ""), - EnumVariable("macports_clang", "Build using Clang from MacPorts", "no", ("no", "5.0", "devel")), - BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), - 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), - BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False), - ] - - -def get_flags(): - return [ - ("use_volk", False), - ] - - -def get_mvk_sdk_path(): - def int_or_zero(i): - try: - return int(i) - except: - return 0 - - def ver_parse(a): - return [int_or_zero(i) for i in a.split(".")] - - dirname = os.path.expanduser("~/VulkanSDK") - files = os.listdir(dirname) - - ver_file = "0.0.0.0" - ver_num = ver_parse(ver_file) - - for file in files: - if os.path.isdir(os.path.join(dirname, file)): - ver_comp = ver_parse(file) - lib_name = os.path.join( - os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a" - ) - if os.path.isfile(lib_name) and ver_comp > ver_num: - ver_num = ver_comp - ver_file = file - - return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") - - -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"]) - elif env["optimize"] == "size": # optimize for size - env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) - if env["arch"] != "arm64": - env.Prepend(CCFLAGS=["-msse2"]) - - if env["debug_symbols"]: - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "release_debug": - if env["optimize"] == "speed": # optimize for speed (default) - env.Prepend(CCFLAGS=["-O2"]) - elif env["optimize"] == "size": # optimize for size - env.Prepend(CCFLAGS=["-Os"]) - if env["debug_symbols"]: - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "debug": - env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) - - ## Architecture - - # Mac OS X no longer runs on 32-bit since 10.7 which is unsupported since 2014 - # As such, we only support 64-bit - env["bits"] = "64" - - ## Compiler configuration - - # Save this in environment for use by other modules - if "OSXCROSS_ROOT" in os.environ: - env["osxcross"] = True - - if env["arch"] == "arm64": - print("Building for macOS 11.0+, platform arm64.") - env.Append(ASFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) - env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) - env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) - else: - print("Building for macOS 10.12+, platform x86_64.") - env.Append(ASFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) - env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) - env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) - - env.Append(CCFLAGS=["-fobjc-arc"]) - - 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["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" - else: - 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"]) - - else: # osxcross build - root = os.environ.get("OSXCROSS_ROOT", 0) - 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++" - 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" - - if env["use_ubsan"] or env["use_asan"] or env["use_tsan"]: - env.extra_suffix += ".san" - env.Append(CCFLAGS=["-DSANITIZERS_ENABLED"]) - - if env["use_ubsan"]: - env.Append( - CCFLAGS=[ - "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" - ] - ) - env.Append(LINKFLAGS=["-fsanitize=undefined"]) - env.Append(CCFLAGS=["-fsanitize=nullability-return,nullability-arg,function,nullability-assign"]) - - if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) - env.Append(LINKFLAGS=["-fsanitize=address"]) - - if env["use_tsan"]: - env.Append(CCFLAGS=["-fsanitize=thread"]) - env.Append(LINKFLAGS=["-fsanitize=thread"]) - - if env["use_coverage"]: - env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - - ## Dependencies - - 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", "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"]) - - if env["opengl3"]: - env.Append(CPPDEFINES=["GLES_ENABLED", "GLES3_ENABLED"]) - env.Append(CCFLAGS=["-Wno-deprecated-declarations"]) # Disable deprecation warnings - env.Append(LINKFLAGS=["-framework", "OpenGL"]) - - env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) - - if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) - env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) - if not env["use_volk"]: - env.Append(LINKFLAGS=["-lMoltenVK"]) - mvk_found = False - if env["vulkan_sdk_path"] != "": - mvk_path = os.path.join( - os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" - ) - if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): - mvk_found = True - env.Append(LINKFLAGS=["-L" + mvk_path]) - if not mvk_found: - mvk_path = get_mvk_sdk_path() - if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): - mvk_found = True - env.Append(LINKFLAGS=["-L" + mvk_path]) - if not mvk_found: - print( - "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path." - ) - sys.exit(255) diff --git a/platform/osx/dir_access_osx.h b/platform/osx/dir_access_osx.h deleted file mode 100644 index 3c66c81d4f..0000000000 --- a/platform/osx/dir_access_osx.h +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************/ -/* dir_access_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 DIR_ACCESS_OSX_H -#define DIR_ACCESS_OSX_H - -#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED) - -#include -#include -#include -#include - -#include "core/io/dir_access.h" -#include "drivers/unix/dir_access_unix.h" - -class DirAccessOSX : public DirAccessUnix { -protected: - virtual String fix_unicode_name(const char *p_name) const; - - virtual int get_drive_count(); - virtual String get_drive(int p_drive); - - virtual bool is_hidden(const String &p_name); -}; - -#endif //UNIX ENABLED -#endif diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm deleted file mode 100644 index 6bafb9470d..0000000000 --- a/platform/osx/dir_access_osx.mm +++ /dev/null @@ -1,81 +0,0 @@ -/*************************************************************************/ -/* dir_access_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "dir_access_osx.h" - -#if defined(UNIX_ENABLED) || defined(LIBC_FILEIO_ENABLED) - -#include - -#import -#import - -String DirAccessOSX::fix_unicode_name(const char *p_name) const { - String fname; - NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping]; - - fname.parse_utf8([nsstr UTF8String]); - - return fname; -} - -int DirAccessOSX::get_drive_count() { - NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; - NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; - - return [vols count]; -} - -String DirAccessOSX::get_drive(int p_drive) { - NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; - NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; - int count = [vols count]; - - ERR_FAIL_INDEX_V(p_drive, count, ""); - - String volname; - NSString *path = [vols[p_drive] path]; - - volname.parse_utf8([path UTF8String]); - - return volname; -} - -bool DirAccessOSX::is_hidden(const String &p_name) { - String f = get_current_dir().plus_file(p_name); - NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; - NSNumber *hidden = nil; - if (![url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:nil]) { - return DirAccessUnix::is_hidden(p_name); - } - return [hidden boolValue]; -} - -#endif //posix_enabled diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h deleted file mode 100644 index 9575cb29a2..0000000000 --- a/platform/osx/display_server_osx.h +++ /dev/null @@ -1,406 +0,0 @@ -/*************************************************************************/ -/* display_server_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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(GLES3_ENABLED) -#include "gl_manager_osx_legacy.h" -#endif // GLES3_ENABLED - -#if defined(VULKAN_ENABLED) -#include "drivers/vulkan/rendering_device_vulkan.h" -#include "platform/osx/vulkan_context_osx.h" -#endif // VULKAN_ENABLED - -#import -#import -#import -#import -#import - -#undef BitMap -#undef CursorShape - -class DisplayServerOSX : public DisplayServer { - GDCLASS(DisplayServerOSX, DisplayServer) - - _THREAD_SAFE_CLASS_ - -public: - struct KeyEvent { - WindowID window_id = INVALID_WINDOW_ID; - unsigned int osx_state = false; - bool pressed = false; - bool echo = false; - bool raw = false; - Key keycode = Key::NONE; - Key physical_keycode = Key::NONE; - uint32_t unicode = 0; - }; - - struct WindowData { - id window_delegate; - id window_object; - id window_view; - - Vector mpath; - - Point2i mouse_pos; - - Size2i min_size; - Size2i max_size; - Size2i size; - - 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; - bool exclusive = false; - HashSet transient_children; - - bool layered_window = false; - bool fullscreen = false; - bool on_top = false; - bool borderless = false; - bool resize_disabled = false; - bool no_focus = false; - bool is_popup = false; - - Rect2i parent_safe_rect; - }; - - List popup_list; - uint64_t time_since_popup = 0; - -private: -#if defined(GLES3_ENABLED) - GLManager_OSX *gl_manager = nullptr; -#endif -#if defined(VULKAN_ENABLED) - VulkanContextOSX *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - String rendering_driver; - - NSMenu *apple_menu = nullptr; - NSMenu *dock_menu = nullptr; - HashMap submenu; - - struct WarpEvent { - NSTimeInterval timestamp; - NSPoint delta; - }; - List warp_events; - NSTimeInterval last_warp = 0; - bool ignore_warp = false; - - Vector key_event_buffer; - int key_event_pos = 0; - - id tts = nullptr; - - Point2i im_selection; - String im_text; - - CGEventSourceRef event_source; - MouseMode mouse_mode = MOUSE_MODE_VISIBLE; - MouseButton last_button_state = MouseButton::NONE; - - bool drop_events = false; - bool in_dispatch_input_event = false; - - struct LayoutInfo { - String name; - String code; - }; - Vector kbd_layouts; - int current_layout = 0; - bool keyboard_layout_dirty = true; - - WindowID last_focused_window = INVALID_WINDOW_ID; - WindowID window_id_counter = MAIN_WINDOW_ID; - float display_max_scale = 1.f; - Point2i origin; - bool displays_arrangement_dirty = true; - bool is_resizing = false; - - CursorShape cursor_shape = CURSOR_ARROW; - NSCursor *cursors[CURSOR_MAX]; - HashMap> cursors_cache; - - HashMap windows; - - const NSMenu *_get_menu_root(const String &p_menu_root) const; - NSMenu *_get_menu_root(const String &p_menu_root); - - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); - void _update_window_style(WindowData p_wd); - void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); - - void _update_displays_arrangement(); - Point2i _get_screens_origin() const; - Point2i _get_native_screen_position(int p_screen) const; - static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info); - - static void _dispatch_input_events(const Ref &p_event); - void _dispatch_input_event(const Ref &p_event); - void _push_input(const Ref &p_event); - void _process_key_events(); - void _update_keyboard_layouts(); - static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); - NSImage *_convert_to_nsimg(Ref &p_image) const; - - static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); - -public: - NSMenu *get_dock_menu() const; - void menu_callback(id p_sender); - - bool has_window(WindowID p_window) const; - WindowData &get_window(WindowID p_window); - - void send_event(NSEvent *p_event); - void send_window_event(const WindowData &p_wd, WindowEvent p_event); - void release_pressed_events(); - void get_key_modifier_state(unsigned int p_osx_state, Ref r_state) const; - void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window); - void push_to_key_event_buffer(const KeyEvent &p_event); - void update_im_text(const Point2i &p_selection, const String &p_text); - void set_last_focused_window(WindowID p_window); - bool mouse_process_popups(bool p_close = false); - void popup_open(WindowID p_window); - void popup_close(WindowID p_window); - void set_is_resizing(bool p_is_resizing); - bool get_is_resizing() const; - - void window_update(WindowID p_window); - void window_destroy(WindowID p_window); - void window_resize(WindowID p_window, int p_width, int p_height); - - 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 = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; - virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; - - virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override; - virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const 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 bool global_menu_is_item_radio_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) const override; - virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override; - virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override; - virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; - virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; - virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; - virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; - virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; - virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; - virtual Ref global_menu_get_item_icon(const String &p_menu_root, int p_idx) const 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_radio_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 void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; - virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; - virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; - virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; - virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; - virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref &p_icon) 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 bool tts_is_speaking() const override; - virtual bool tts_is_paused() const override; - virtual Array tts_get_voices() const override; - - virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; - virtual void tts_pause() override; - virtual void tts_resume() override; - virtual void tts_stop() override; - - virtual Error dialog_show(String p_title, String p_description, Vector 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; - - bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); - virtual void warp_mouse(const Point2i &p_position) override; - virtual Point2i mouse_get_position() const override; - void mouse_set_button_state(MouseButton p_state); - virtual MouseButton 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 float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - - virtual Vector get_window_list() const override; - - virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_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 WindowID window_get_active_popup() const override; - virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override; - virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const 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 &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_exclusive(WindowID p_window, bool p_exclusive) 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 int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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 gl_window_make_current(DisplayServer::WindowID p_window_id) override; - - virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; - virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - - virtual Point2i ime_get_selection() const override; - virtual String ime_get_text() const override; - - void cursor_update_shape(); - virtual void cursor_set_shape(CursorShape p_shape) override; - virtual CursorShape cursor_get_shape() const override; - virtual void cursor_set_custom_image(const Ref &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 Key keyboard_get_keycode_from_physical(Key p_keycode) 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 &p_icon) override; - - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); - static Vector get_rendering_drivers_func(); - - static void register_osx_driver(); - - DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_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 deleted file mode 100644 index 11474dac46..0000000000 --- a/platform/osx/display_server_osx.mm +++ /dev/null @@ -1,3304 +0,0 @@ -/*************************************************************************/ -/* display_server_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "godot_content_view.h" -#include "godot_menu_item.h" -#include "godot_window.h" -#include "godot_window_delegate.h" -#include "key_mapping_osx.h" -#include "os_osx.h" - -#include "tts_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" - -#import -#import -#import -#import -#import -#import - -#if defined(GLES3_ENABLED) -#include "drivers/gles3/rasterizer_gles3.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#endif - -const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { - const NSMenu *menu = nullptr; - 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)) { - menu = submenu[p_menu_root]; - } - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - -NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { - NSMenu *menu = nullptr; - 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()]]; - [n_menu setAutoenablesItems:NO]; - submenu[p_menu_root] = n_menu; - } - menu = submenu[p_menu_root]; - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - -DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_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_object setWindowID:window_id_counter]; - - 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]; - [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; - - if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { - [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; - } - - CALayer *layer = [(NSView *)wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } - -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, 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(GLES3_ENABLED) - if (gl_manager) { - Error err = gl_manager->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 an OpenGL context"); - } -#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 = [(NSView *)wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } - -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(id, wd.size.width, wd.size.height); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(id, wd.size.width, wd.size.height); - } -#endif - - return id; -} - -void DisplayServerOSX::_update_window_style(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. - [(NSWindow *)p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; - [(NSWindow *)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.fullscreen) { - [(NSWindow *)p_wd.window_object setLevel:NSFloatingWindowLevel]; - } else { - [(NSWindow *)p_wd.window_object setLevel:NSNormalWindowLevel]; - } - [(NSWindow *)p_wd.window_object setHidesOnDeactivate:NO]; - } -} - -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::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 = [(NSView *)wd.window_view layer]; - if (layer) { - [layer setBackgroundColor:[NSColor clearColor].CGColor]; - [layer setOpaque:NO]; - } -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_set_per_pixel_transparency_enabled(p_window, true); - } -#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 = [(NSView *)wd.window_view layer]; - if (layer) { - [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor]; - [layer setOpaque:YES]; - } -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_set_per_pixel_transparency_enabled(p_window, false); - } -#endif - wd.layered_window = false; - } - 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::_update_displays_arrangement() { - 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; -} - -Point2i DisplayServerOSX::_get_screens_origin() const { - // 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. - - if (displays_arrangement_dirty) { - const_cast(this)->_update_displays_arrangement(); - } - - 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(); -} - -void DisplayServerOSX::_displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->displays_arrangement_dirty = true; - } -} - -void DisplayServerOSX::_dispatch_input_events(const Ref &p_event) { - ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); -} - -void DisplayServerOSX::_dispatch_input_event(const Ref &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; - - { - List::Element *E = popup_list.back(); - if (E && Object::cast_to(*p_event)) { - // Redirect keyboard input to active popup. - if (windows.has(E->get())) { - Callable callable = windows[E->get()].input_event_callback; - if (callable.is_valid()) { - callable.call((const Variant **)&evp, 1, ret, ce); - } - } - in_dispatch_input_event = false; - return; - } - } - - Ref 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_valid()) { - callable.call((const Variant **)&evp, 1, ret, ce); - } - } - } else { - // Send to all windows. - for (KeyValue &E : windows) { - Callable callable = E.value.input_event_callback; - if (callable.is_valid()) { - callable.call((const Variant **)&evp, 1, ret, ce); - } - } - } - in_dispatch_input_event = false; - } -} - -void DisplayServerOSX::_push_input(const Ref &p_event) { - Ref ev = p_event; - Input::get_singleton()->parse_input_event(ev); -} - -void DisplayServerOSX::_process_key_events() { - Ref 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.instantiate(); - - 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((Key)ke.physical_keycode); - k->set_unicode(ke.unicode); - - _push_input(k); - } else { - // IME input. - if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { - k.instantiate(); - - 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(Key::NONE); - k->set_physical_keycode(Key::NONE); - k->set_unicode(ke.unicode); - - _push_input(k); - } - if (ke.keycode != Key::NONE) { - k.instantiate(); - - 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((Key)ke.physical_keycode); - - if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { - k->set_unicode(key_event_buffer[i + 1].unicode); - } - - _push_input(k); - } - } - } - - key_event_pos = 0; -} - -void DisplayServerOSX::_update_keyboard_layouts() { - kbd_layouts.clear(); - current_layout = 0; - - TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); - NSString *cur_name = (__bridge NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); - CFRelease(cur_source); - - // Enum IME layouts. - NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); - for (NSUInteger i = 0; i < [list_ime count]; i++) { - LayoutInfo ly; - NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge 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; - } - } - - // Enum plain keyboard layouts. - NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; - NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); - for (NSUInteger i = 0; i < [list_kbd count]; i++) { - LayoutInfo ly; - NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge 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; - } - } - - keyboard_layout_dirty = false; -} - -void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->keyboard_layout_dirty = true; - } -} - -NSImage *DisplayServerOSX::_convert_to_nsimg(Ref &p_image) const { - p_image->convert(Image::FORMAT_RGBA8); - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - pixelsWide:p_image->get_width() - pixelsHigh:p_image->get_height() - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:int(p_image->get_width()) * 4 - bitsPerPixel:32]; - ERR_FAIL_COND_V(imgrep == nil, nil); - uint8_t *pixels = [imgrep bitmapData]; - - int len = p_image->get_width() * p_image->get_height(); - const uint8_t *r = p_image->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(p_image->get_width(), p_image->get_height())]; - ERR_FAIL_COND_V(nsimg == nil, nil); - [nsimg addRepresentation:imgrep]; - return nsimg; -} - -NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) { - if ([NSCursor respondsToSelector:p_selector]) { - id object = [NSCursor performSelector:p_selector]; - if ([object isKindOfClass:[NSCursor class]]) { - return object; - } - } - if (p_fallback) { - // Fallback should be a reasonable default, no need to check. - return [NSCursor performSelector:p_fallback]; - } - return [NSCursor arrowCursor]; -} - -NSMenu *DisplayServerOSX::get_dock_menu() const { - return dock_menu; -} - -void DisplayServerOSX::menu_callback(id p_sender) { - if (![p_sender representedObject]) { - return; - } - - GodotMenuItem *value = [p_sender representedObject]; - - if (value) { - if (value->max_states > 0) { - value->state++; - if (value->state >= value->max_states) { - value->state = 0; - } - } - - if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { - if ([p_sender state] == NSControlStateValueOff) { - [p_sender setState:NSControlStateValueOn]; - } else { - [p_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); - } - } -} - -bool DisplayServerOSX::has_window(WindowID p_window) const { - return windows.has(p_window); -} - -DisplayServerOSX::WindowData &DisplayServerOSX::get_window(WindowID p_window) { - return windows[p_window]; -} - -void DisplayServerOSX::send_event(NSEvent *p_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 ([p_event type] == NSEventTypeKeyDown) { - if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { - Ref k; - k.instantiate(); - - get_key_modifier_state([p_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([p_event isARepeat]); - - Input::get_singleton()->parse_input_event(k); - } - } -} - -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); - } -} - -void DisplayServerOSX::release_pressed_events() { - _THREAD_SAFE_METHOD_ - if (Input::get_singleton()) { - Input::get_singleton()->release_pressed_events(); - } -} - -void DisplayServerOSX::get_key_modifier_state(unsigned int p_osx_state, Ref r_state) const { - r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); - r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); - r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); - r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); -} - -void DisplayServerOSX::update_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_location_in_window) { - const NSRect content_rect = [p_wd.window_view frame]; - const float scale = screen_get_max_scale(); - p_wd.mouse_pos.x = p_location_in_window.x * scale; - p_wd.mouse_pos.y = (content_rect.size.height - p_location_in_window.y) * scale; - Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); -} - -void DisplayServerOSX::push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { - if (key_event_pos >= key_event_buffer.size()) { - key_event_buffer.resize(1 + key_event_pos); - } - key_event_buffer.write[key_event_pos++] = p_event; -} - -void DisplayServerOSX::update_im_text(const Point2i &p_selection, const String &p_text) { - im_selection = p_selection; - im_text = p_text; - - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); -} - -void DisplayServerOSX::set_last_focused_window(WindowID p_window) { - last_focused_window = p_window; -} - -void DisplayServerOSX::set_is_resizing(bool p_is_resizing) { - is_resizing = p_is_resizing; -} - -bool DisplayServerOSX::get_is_resizing() const { - return is_resizing; -} - -void DisplayServerOSX::window_update(WindowID p_window) { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_update(p_window); - } -#endif -} - -void DisplayServerOSX::window_destroy(WindowID p_window) { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_destroy(p_window); - } -#endif -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(p_window); - } -#endif - windows.erase(p_window); -} - -void DisplayServerOSX::window_resize(WindowID p_window, int p_width, int p_height) { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(p_window, p_width, p_height); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(p_window, p_width, p_height); - } -#endif -} - -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_IME: - case FEATURE_WINDOW_TRANSPARENCY: - case FEATURE_HIDPI: - case FEATURE_ICON: - case FEATURE_NATIVE_ICON: - //case FEATURE_KEEP_SCREEN_ON: - case FEATURE_SWAP_BUFFERS: - case FEATURE_TEXT_TO_SPEECH: - return true; - default: { - } - } - return false; -} - -String DisplayServerOSX::get_name() const { - return "OSX"; -} - -void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [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, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_icon_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; - if (p_icon.is_valid()) { - obj->img = p_icon->get_image(); - obj->img = obj->img->duplicate(); - if (obj->img->is_compressed()) { - obj->img->decompress(); - } - obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); - [menu_item setImage:_convert_to_nsimg(obj->img)]; - } - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_icon_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; - if (p_icon.is_valid()) { - obj->img = p_icon->get_image(); - obj->img = obj->img->duplicate(); - if (obj->img->is_compressed()) { - obj->img->decompress(); - } - obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); - [menu_item setImage:_convert_to_nsimg(obj->img)]; - } - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; - if (p_icon.is_valid()) { - obj->img = p_icon->get_image(); - obj->img = obj->img->duplicate(); - if (obj->img->is_compressed()) { - obj->img->decompress(); - } - obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); - [menu_item setImage:_convert_to_nsimg(obj->img)]; - } - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = p_callback; - obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = p_max_states; - obj->state = p_default_state; - [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; - [menu_item setRepresentedObject:obj]; - } -} - -void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { - _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; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; - } - [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; - [menu setSubmenu:sub_menu forItem:menu_item]; - } -} - -void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root, int p_index) { - _THREAD_SAFE_METHOD_ - - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - if (p_index != -1) { - [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; - } else { - [menu addItem:[NSMenuItem separatorItem]]; - } - } -} - -int DisplayServerOSX::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { - _THREAD_SAFE_METHOD_ - - const NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; - } - - return -1; -} - -int DisplayServerOSX::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const { - _THREAD_SAFE_METHOD_ - - const NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - for (NSInteger i = 0; i < [menu numberOfItems]; i++) { - const NSMenuItem *menu_item = [menu itemAtIndex:i]; - if (menu_item) { - const GodotMenuItem *obj = [menu_item representedObject]; - if (obj && obj->meta == p_tag) { - return i; - } - } - } - } - - return -1; -} - -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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX; - } - } - } - return false; -} - -bool DisplayServerOSX::global_menu_is_item_radio_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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON; - } - } - } - return false; -} - -Callable DisplayServerOSX::global_menu_get_item_callback(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) { - GodotMenuItem *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) 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) { - GodotMenuItem *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) 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 String::utf8([[menu_item title] UTF8String]); - } - } - return String(); -} - -String DisplayServerOSX::global_menu_get_item_submenu(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) { - const NSMenu *sub_menu = [menu_item submenu]; - if (sub_menu) { - for (const KeyValue &E : submenu) { - if (E.value == sub_menu) { - return E.key; - } - } - } - } - } - return String(); -} - -Key DisplayServerOSX::global_menu_get_item_accelerator(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) { - String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); - Key keycode = find_keycode(ret); - NSUInteger mask = [menu_item keyEquivalentModifierMask]; - if (mask & NSEventModifierFlagControl) { - keycode |= KeyModifierMask::CTRL; - } - if (mask & NSEventModifierFlagOption) { - keycode |= KeyModifierMask::ALT; - } - if (mask & NSEventModifierFlagShift) { - keycode |= KeyModifierMask::SHIFT; - } - if (mask & NSEventModifierFlagCommand) { - keycode |= KeyModifierMask::META; - } - if (mask & NSEventModifierFlagNumericPad) { - keycode |= KeyModifierMask::KPAD; - } - return keycode; - } - } - return Key::NONE; -} - -bool DisplayServerOSX::global_menu_is_item_disabled(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 isEnabled]; - } - } - return false; -} - -String DisplayServerOSX::global_menu_get_item_tooltip(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 String::utf8([[menu_item toolTip] UTF8String]); - } - } - return String(); -} - -int DisplayServerOSX::global_menu_get_item_state(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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - return obj->state; - } - } - } - return 0; -} - -int DisplayServerOSX::global_menu_get_item_max_states(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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - return obj->max_states; - } - } - } - return 0; -} - -Ref DisplayServerOSX::global_menu_get_item_icon(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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - if (obj->img.is_valid()) { - return ImageTexture::create_from_image(obj->img); - } - } - } - } - return Ref(); -} - -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) { - GodotMenuItem *obj = [menu_item representedObject]; - obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; - } - } -} - -void DisplayServerOSX::global_menu_set_item_radio_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) { - GodotMenuItem *obj = [menu_item representedObject]; - obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE; - } - } -} - -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) { - GodotMenuItem *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) { - GodotMenuItem *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]; - } - } -} - -void DisplayServerOSX::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) { - _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 setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_keycode)]; - String keycode = KeyMappingOSX::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); - [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } - } -} - -void DisplayServerOSX::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) { - _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 setEnabled:(!p_disabled)]; - } - } -} - -void DisplayServerOSX::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { - _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 setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; - } - } -} - -void DisplayServerOSX::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) { - _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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - obj->state = p_state; - } - } - } -} - -void DisplayServerOSX::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) { - _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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - obj->max_states = p_max_states; - } - } - } -} - -void DisplayServerOSX::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref &p_icon) { - _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) { - GodotMenuItem *obj = [menu_item representedObject]; - if (p_icon.is_valid()) { - obj->img = p_icon->get_image(); - obj->img = obj->img->duplicate(); - if (obj->img->is_compressed()) { - obj->img->decompress(); - } - obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); - [menu_item setImage:_convert_to_nsimg(obj->img)]; - } else { - obj->img = Ref(); - [menu_item setImage:nil]; - } - } - } -} - -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]; - } - } -} - -bool DisplayServerOSX::tts_is_speaking() const { - ERR_FAIL_COND_V(!tts, false); - return [tts isSpeaking]; -} - -bool DisplayServerOSX::tts_is_paused() const { - ERR_FAIL_COND_V(!tts, false); - return [tts isPaused]; -} - -Array DisplayServerOSX::tts_get_voices() const { - ERR_FAIL_COND_V(!tts, Array()); - return [tts getVoices]; -} - -void DisplayServerOSX::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - ERR_FAIL_COND(!tts); - [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; -} - -void DisplayServerOSX::tts_pause() { - ERR_FAIL_COND(!tts); - [tts pauseSpeaking]; -} - -void DisplayServerOSX::tts_resume() { - ERR_FAIL_COND(!tts); - [tts resumeSpeaking]; -} - -void DisplayServerOSX::tts_stop() { - ERR_FAIL_COND(!tts); - [tts stopSpeaking]; -} - -Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector 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); - } - - 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]; - - String ret; - ret.parse_utf8([[input stringValue] UTF8String]); - - 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); - } - - return OK; -} - -void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { - _THREAD_SAFE_METHOD_ - - if (p_mode == mouse_mode) { - return; - } - - WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; - WindowData &wd = windows[window_id]; - 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); - [wd.window_object setMovable:NO]; - const NSRect contentRect = [wd.window_view frame]; - NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0); - NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - CGWarpMouseCursorPosition(lMouseWarpPos); - } else if (p_mode == MOUSE_MODE_HIDDEN) { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - CGDisplayHideCursor(kCGDirectMainDisplay); - } - [wd.window_object setMovable:YES]; - CGAssociateMouseAndMouseCursorPosition(true); - } else if (p_mode == MOUSE_MODE_CONFINED) { - CGDisplayShowCursor(kCGDirectMainDisplay); - [wd.window_object setMovable:NO]; - CGAssociateMouseAndMouseCursorPosition(false); - } else if (p_mode == MOUSE_MODE_CONFINED_HIDDEN) { - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - CGDisplayHideCursor(kCGDirectMainDisplay); - } - [wd.window_object setMovable:NO]; - CGAssociateMouseAndMouseCursorPosition(false); - } else { // MOUSE_MODE_VISIBLE - CGDisplayShowCursor(kCGDirectMainDisplay); - [wd.window_object setMovable:YES]; - CGAssociateMouseAndMouseCursorPosition(true); - } - - last_warp = [[NSProcessInfo processInfo] systemUptime]; - ignore_warp = true; - warp_events.clear(); - mouse_mode = p_mode; - - if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - cursor_update_shape(); - } -} - -DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const { - return mouse_mode; -} - -bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp) { - _THREAD_SAFE_METHOD_ - - if (ignore_warp) { - // Discard late events, before warp. - if (p_timestamp < last_warp) { - return true; - } - ignore_warp = false; - return true; - } - - if (mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { - // Discard late events. - if (p_timestamp < last_warp) { - return true; - } - - // Warp affects next event delta, subtract previous warp deltas. - List::Element *F = warp_events.front(); - while (F) { - if (F->get().timestamp < p_timestamp) { - List::Element *E = F; - r_delta.x -= E->get().delta.x; - r_delta.y -= E->get().delta.y; - F = F->next(); - warp_events.erase(E); - } else { - F = F->next(); - } - } - - // Confine mouse position to the window, and update delta. - NSRect frame = [p_wd.window_object frame]; - NSPoint conf_pos = r_mpos; - conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width); - conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height); - r_delta.x = conf_pos.x - r_mpos.x; - r_delta.y = r_mpos.y - conf_pos.y; - r_mpos = conf_pos; - - // Move mouse cursor. - NSRect point_in_window_rect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); - conf_pos = [[p_wd.window_view window] convertRectToScreen:point_in_window_rect].origin; - conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; - CGWarpMouseCursorPosition(conf_pos); - - // Save warp data. - last_warp = [[NSProcessInfo processInfo] systemUptime]; - - DisplayServerOSX::WarpEvent ev; - ev.timestamp = last_warp; - ev.delta = r_delta; - warp_events.push_back(ev); - } - - return false; -} - -void DisplayServerOSX::warp_mouse(const Point2i &p_position) { - _THREAD_SAFE_METHOD_ - - if (mouse_mode != MOUSE_MODE_CAPTURED) { - WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; - WindowData &wd = windows[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_position.x / scale, contentRect.size.height - (p_position.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 && mouse_mode != MOUSE_MODE_CONFINED_HIDDEN) { - CGAssociateMouseAndMouseCursorPosition(true); - } - } -} - -Point2i DisplayServerOSX::mouse_get_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)) { - Vector2i pos = Vector2i((int)mouse_pos.x, (int)mouse_pos.y); - pos *= scale; - pos -= _get_screens_origin(); - pos.y *= -1; - return pos; - } - } - return Vector2i(); -} - -void DisplayServerOSX::mouse_set_button_state(MouseButton p_state) { - last_button_state = p_state; -} - -MouseButton 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]; - - String ret; - ret.parse_utf8([string UTF8String]); - return ret; -} - -int DisplayServerOSX::get_screen_count() const { - _THREAD_SAFE_METHOD_ - - NSArray *screenArray = [NSScreen screens]; - return [screenArray count]; -} - -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]; - - const NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - const CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - float scale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; - - float den2 = (displayPhysicalSize.width / 25.4f) * (displayPhysicalSize.width / 25.4f) + (displayPhysicalSize.height / 25.4f) * (displayPhysicalSize.height / 25.4f); - if (den2 > 0.0f) { - return ceil(sqrt(displayPixelSize.width * displayPixelSize.width + displayPixelSize.height * displayPixelSize.height) / sqrt(den2) * scale); - } - } - - return 72; -} - -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_ - - // Note: Do not update max display scale on screen configuration change, existing editor windows can't be rescaled on the fly. - return display_max_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(); -} - -float DisplayServerOSX::screen_get_refresh_rate(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]; - const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); - return (float)displayRefreshRate; - } - ERR_PRINT("An error occurred while trying to get the screen refresh rate."); - return SCREEN_REFRESH_RATE_FALLBACK; -} - -Vector DisplayServerOSX::get_window_list() const { - _THREAD_SAFE_METHOD_ - - Vector ret; - for (const KeyValue &E : windows) { - ret.push_back(E.key); - } - return ret; -} - -DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ - - WindowID id = _create_window(p_mode, p_vsync_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]; - - popup_open(p_id); - if (wd.no_focus || wd.is_popup) { - [wd.window_object orderFront:nil]; - } else { - [wd.window_object makeKeyAndOrderFront:nil]; - } -} - -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_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; -} - -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 &p_region, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - wd.mpath = p_region; -} - -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_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - if (window_get_current_screen(p_window) == p_screen) { - return; - } - - bool was_fullscreen = false; - if (wd.fullscreen) { - // Temporary exit fullscreen mode to move window. - [wd.window_object toggleFullScreen:nil]; - was_fullscreen = true; - } - - 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); - - if (was_fullscreen) { - // Re-enter fullscreen mode. - [wd.window_object toggleFullScreen:nil]; - } -} - -void DisplayServerOSX::window_set_exclusive(WindowID p_window, bool p_exclusive) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - if (wd.exclusive != p_exclusive) { - wd.exclusive = p_exclusive; - if (wd.transient_parent != INVALID_WINDOW_ID) { - WindowData &wd_parent = windows[wd.transient_parent]; - if (wd.exclusive) { - ERR_FAIL_COND_MSG([[wd_parent.window_object childWindows] count] > 0, "Transient parent has another exclusive child."); - [wd_parent.window_object addChildWindow:wd.window_object ordered:NSWindowAbove]; - } else { - [wd_parent.window_object removeChildWindow:wd.window_object]; - } - } - } -} - -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]; - - // Use content rect position (without titlebar / window border). - const NSRect contentRect = [wd.window_view frame]; - const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; - 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(); - - // Remove titlebar / window border size. - const NSRect contentRect = [wd.window_view frame]; - const NSRect windowRect = [wd.window_object frame]; - const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; - Point2i offset; - offset.x = (nsrect.origin.x - windowRect.origin.x); - offset.y = (nsrect.origin.y + nsrect.size.height); - offset.y -= (windowRect.origin.y + windowRect.size.height); - - [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x - offset.x, position.y - offset.y)]; - - _update_window_style(wd); - update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); -} - -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_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - - if (wd_window.exclusive) { - [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_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - - if (wd_window.exclusive) { - [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove]; - } - } -} - -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_style(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(); -} - -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_EXCLUSIVE_FULLSCREEN: - case WINDOW_MODE_FULLSCREEN: { - [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; - _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_EXCLUSIVE_FULLSCREEN: - case WINDOW_MODE_FULLSCREEN: { - _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; -} - -bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { - return true; -} - -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 fullscreen. - 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 { - _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_style(wd); - if ([wd.window_object isVisible]) { - if (wd.no_focus || wd.is_popup) { - [wd.window_object orderFront:nil]; - } else { - [wd.window_object makeKeyAndOrderFront:nil]; - } - } - } break; - case WINDOW_FLAG_ALWAYS_ON_TOP: { - wd.on_top = p_enabled; - if (wd.fullscreen) { - return; - } - if (p_enabled) { - [(NSWindow *)wd.window_object setLevel:NSFloatingWindowLevel]; - } else { - [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel]; - } - } break; - case WINDOW_FLAG_TRANSPARENT: { - 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; - case WINDOW_FLAG_POPUP: { - ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); - ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); - wd.is_popup = 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: { - if (wd.fullscreen) { - return wd.on_top; - } else { - return [(NSWindow *)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; - case WINDOW_FLAG_POPUP: { - return wd.is_popup; - } 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.is_popup) { - [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 (const KeyValue &E : windows) { - 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; -} - -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 (const KeyValue &E : windows) { - if ([E.value.window_object windowNumber] == wnum) { - return E.key; - } - } - return INVALID_WINDOW_ID; -} - -int64_t DisplayServerOSX::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { - ERR_FAIL_COND_V(!windows.has(p_window), 0); - switch (p_handle_type) { - case DISPLAY_HANDLE: { - return 0; // Not supported. - } - case WINDOW_HANDLE: { - return (int64_t)windows[p_window].window_object; - } - case WINDOW_VIEW: { - return (int64_t)windows[p_window].window_view; - } - default: { - return 0; - } - } -} - -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; -} - -void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) { -#if defined(GLES3_ENABLED) - gl_manager->window_make_current(p_window_id); -#endif -} - -void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->set_use_vsync(p_vsync_mode); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); - } -#endif -} - -DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); - } -#endif - return DisplayServer::VSYNC_ENABLED; -} - -Point2i DisplayServerOSX::ime_get_selection() const { - return im_selection; -} - -String DisplayServerOSX::ime_get_text() const { - return im_text; -} - -void DisplayServerOSX::cursor_update_shape() { - _THREAD_SAFE_METHOD_ - - if (cursors[cursor_shape] != nullptr) { - [cursors[cursor_shape] set]; - } else { - switch (cursor_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: - [_cursor_from_selector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; - break; - case CURSOR_HSIZE: - [_cursor_from_selector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; - break; - case CURSOR_BDIAGSIZE: - [_cursor_from_selector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; - break; - case CURSOR_FDIAGSIZE: - [_cursor_from_selector(@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: - [_cursor_from_selector(@selector(_helpCursor)) set]; - break; - default: { - } - } - } -} - -void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - - if (cursor_shape == p_shape) { - return; - } - - cursor_shape = p_shape; - - if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { - return; - } - - cursor_update_shape(); -} - -DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const { - return cursor_shape; -} - -void DisplayServerOSX::cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - _THREAD_SAFE_METHOD_ - - if (p_cursor.is_valid()) { - HashMap>::Iterator cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->value[0] == p_cursor && cursor_c->value[1] == p_hotspot) { - cursor_set_shape(p_shape); - return; - } - cursors_cache.erase(p_shape); - } - - Ref texture = p_cursor; - Ref atlas_texture = p_cursor; - Ref image; - Size2 texture_size; - Rect2 atlas_rect; - - if (texture.is_valid()) { - image = texture->get_image(); - } - - 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_image(); - - ERR_FAIL_COND(!image.is_valid()); - - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:nullptr - 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] = cursor; - - Vector 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]; - } - } - } else { - // Reset to default system cursor. - if (cursors[p_shape] != nullptr) { - cursors[p_shape] = nullptr; - } - - cursor_update_shape(); - - cursors_cache.erase(p_shape); - } -} - -bool DisplayServerOSX::get_swap_cancel_ok() { - return false; -} - -int DisplayServerOSX::keyboard_get_layout_count() const { - if (keyboard_layout_dirty) { - const_cast(this)->_update_keyboard_layouts(); - } - return kbd_layouts.size(); -} - -void DisplayServerOSX::keyboard_set_current_layout(int p_index) { - if (keyboard_layout_dirty) { - const_cast(this)->_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 = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); - for (NSUInteger i = 0; i < [list_kbd count]; i++) { - NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); - if ([name isEqualToString:cur_name]) { - TISSelectInputSource((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i]); - break; - } - } - - NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); - for (NSUInteger i = 0; i < [list_ime count]; i++) { - NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); - if ([name isEqualToString:cur_name]) { - TISSelectInputSource((__bridge TISInputSourceRef)[list_ime objectAtIndex:i]); - break; - } - } -} - -int DisplayServerOSX::keyboard_get_current_layout() const { - if (keyboard_layout_dirty) { - const_cast(this)->_update_keyboard_layouts(); - } - - return current_layout; -} - -String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { - if (keyboard_layout_dirty) { - const_cast(this)->_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) { - const_cast(this)->_update_keyboard_layouts(); - } - - ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); - return kbd_layouts[p_index].name; -} - -Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const { - if (p_keycode == Key::PAUSE) { - return p_keycode; - } - - Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; - Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; - unsigned int osx_keycode = KeyMappingOSX::unmap_key((Key)keycode_no_mod); - return (Key)(KeyMappingOSX::remap_key(osx_keycode, 0) | modifiers); -} - -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_buffered_events(); - } - - for (KeyValue &E : windows) { - WindowData &wd = E.value; - if (wd.mpath.size() > 0) { - update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); - if (Geometry2D::is_point_in_polygon(wd.mouse_pos, 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]; - } - } - } -} - -void DisplayServerOSX::force_process_and_drop_events() { - _THREAD_SAFE_METHOD_ - - drop_events = true; - process_events(); - drop_events = false; -} - -void DisplayServerOSX::release_rendering_thread() { -} - -void DisplayServerOSX::make_rendering_thread() { -} - -void DisplayServerOSX::swap_buffers() { -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->swap_buffers(); - } -#endif -} - -void DisplayServerOSX::set_native_icon(const String &p_filename) { - _THREAD_SAFE_METHOD_ - - Ref f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND(f.is_null()); - - Vector data; - uint64_t len = f->get_length(); - data.resize(len); - f->get_buffer((uint8_t *)&data.write[0], len); - - NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len]; - ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); - - NSImage *icon = [[NSImage alloc] initWithData:icon_data]; - ERR_FAIL_COND_MSG(!icon, "Error loading icon."); - - [NSApp setApplicationIconImage:icon]; -} - -void DisplayServerOSX::set_icon(const Ref &p_icon) { - _THREAD_SAFE_METHOD_ - - Ref img = p_icon; - img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:nullptr - 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]; -} - -DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); - if (r_error != OK) { - OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan or OpenGL versions.", "Unable to initialize Video driver"); - } - return ds; -} - -Vector DisplayServerOSX::get_rendering_drivers_func() { - Vector drivers; - -#if defined(VULKAN_ENABLED) - drivers.push_back("vulkan"); -#endif -#if defined(GLES3_ENABLED) - drivers.push_back("opengl3"); -#endif - - return drivers; -} - -void DisplayServerOSX::register_osx_driver() { - register_create_function("osx", create_func, get_rendering_drivers_func); -} - -DisplayServer::WindowID DisplayServerOSX::window_get_active_popup() const { - const List::Element *E = popup_list.back(); - if (E) { - return E->get(); - } else { - return INVALID_WINDOW_ID; - } -} - -void DisplayServerOSX::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - wd.parent_safe_rect = p_rect; -} - -Rect2i DisplayServerOSX::window_get_popup_safe_rect(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Rect2i()); - const WindowData &wd = windows[p_window]; - return wd.parent_safe_rect; -} - -void DisplayServerOSX::popup_open(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - WindowData &wd = windows[p_window]; - if (wd.is_popup) { - bool was_empty = popup_list.is_empty(); - // Find current popup parent, or root popup if new window is not transient. - List::Element *C = nullptr; - List::Element *E = popup_list.back(); - while (E) { - if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { - C = E; - E = E->prev(); - } else { - break; - } - } - if (C) { - send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - } - - if (was_empty && popup_list.is_empty()) { - // Inform OS that popup was opened, to close other native popups. - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; - } - time_since_popup = OS::get_singleton()->get_ticks_msec(); - popup_list.push_back(p_window); - } -} - -void DisplayServerOSX::popup_close(WindowID p_window) { - _THREAD_SAFE_METHOD_ - - bool was_empty = popup_list.is_empty(); - List::Element *E = popup_list.find(p_window); - while (E) { - List::Element *F = E->next(); - WindowID win_id = E->get(); - popup_list.erase(E); - - send_window_event(windows[win_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - E = F; - } - if (!was_empty && popup_list.is_empty()) { - // Inform OS that all popups are closed. - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; - } -} - -bool DisplayServerOSX::mouse_process_popups(bool p_close) { - _THREAD_SAFE_METHOD_ - - bool was_empty = popup_list.is_empty(); - bool closed = false; - if (p_close) { - // Close all popups. - List::Element *E = popup_list.front(); - if (E) { - send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - closed = true; - } - if (!was_empty) { - // Inform OS that all popups are closed. - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; - } - } else { - uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; - if (delta < 250) { - return false; - } - - Point2i pos = mouse_get_position(); - List::Element *C = nullptr; - List::Element *E = popup_list.back(); - // Find top popup to close. - while (E) { - // Popup window area. - Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); - // Area of the parent window, which responsible for opening sub-menu. - Rect2i safe_rect = window_get_popup_safe_rect(E->get()); - if (win_rect.has_point(pos)) { - break; - } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { - break; - } else { - C = E; - E = E->prev(); - } - } - if (C) { - send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - closed = true; - } - if (!was_empty && popup_list.is_empty()) { - // Inform OS that all popups are closed. - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"]; - } - } - return closed; -} - -DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_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; - - memset(cursors, 0, sizeof(cursors)); - - event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - ERR_FAIL_COND(!event_source); - - CGEventSourceSetLocalEventsSuppressionInterval(event_source, 0.0); - - int screen_count = get_screen_count(); - for (int i = 0; i < screen_count; i++) { - display_max_scale = fmax(display_max_scale, screen_get_scale(i)); - } - - // Register to be notified on keyboard layout changes. - CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - nullptr, _keyboard_layout_changed, - kTISNotifySelectedKeyboardInputSourceChanged, nullptr, - CFNotificationSuspensionBehaviorDeliverImmediately); - - // Register to be notified on displays arrangement changes. - CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); - - // Init TTS - tts = [[TTS_OSX alloc] init]; - - 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"]; - [dock_menu setAutoenablesItems:NO]; - - // Setup Apple menu. - apple_menu = [[NSMenu alloc] initWithTitle:@""]; - title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; - [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; - [apple_menu setAutoenablesItems:NO]; - - [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]; - - [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"]; - - // Add items to the menu bar. - NSMenu *main_menu = [NSApp mainMenu]; - menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; - [main_menu setSubmenu:apple_menu forItem:menu_item]; - [main_menu setAutoenablesItems:NO]; - - //!!!!!!!!!!!!!!!!!!!!!!!!!! - //TODO - do Vulkan and OpenGL support checks, driver selection and fallback - rendering_driver = p_rendering_driver; - -#if defined(GLES3_ENABLED) - if (rendering_driver == "opengl3") { - GLManager_OSX::ContextType opengl_api_type = GLManager_OSX::GLES_3_0_COMPATIBLE; - gl_manager = memnew(GLManager_OSX(opengl_api_type)); - if (gl_manager->initialize() != OK) { - memdelete(gl_manager); - gl_manager = nullptr; - r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Could not initialize OpenGL"); - return; - } - } -#endif -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextOSX); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - 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, p_vsync_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(GLES3_ENABLED) - if (rendering_driver == "opengl3") { - RasterizerGLES3::make_current(); - } -#endif -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RendererCompositorRD::make_current(); - } -#endif -} - -DisplayServerOSX::~DisplayServerOSX() { - // Destroy all windows. - for (HashMap::Iterator E = windows.begin(); E;) { - HashMap::Iterator F = E; - ++E; - [F->value.window_object setContentView:nil]; - [F->value.window_object close]; - } - - // Destroy drivers. -#if defined(GLES3_ENABLED) - if (gl_manager) { - memdelete(gl_manager); - gl_manager = nullptr; - } -#endif -#if defined(VULKAN_ENABLED) - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; - } - - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; - } -#endif - - CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr); - CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr); - - cursors_cache.clear(); -} diff --git a/platform/osx/export/codesign.cpp b/platform/osx/export/codesign.cpp deleted file mode 100644 index fd044c00cc..0000000000 --- a/platform/osx/export/codesign.cpp +++ /dev/null @@ -1,1564 +0,0 @@ -/*************************************************************************/ -/* codesign.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "codesign.h" - -#include "lipo.h" -#include "macho.h" -#include "plist.h" - -#include "core/os/os.h" -#include "editor/editor_paths.h" -#include "editor/editor_settings.h" - -#include "modules/modules_enabled.gen.h" // For regex. - -#include - -#ifdef MODULE_REGEX_ENABLED - -/*************************************************************************/ -/* CodeSignCodeResources */ -/*************************************************************************/ - -String CodeSignCodeResources::hash_sha1_base64(const String &p_path) { - Ref fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); - - CryptoCore::SHA1Context ctx; - ctx.start(); - - unsigned char step[4096]; - while (true) { - uint64_t br = fa->get_buffer(step, 4096); - if (br > 0) { - ctx.update(step, br); - } - if (br < 4096) { - break; - } - } - - unsigned char hash[0x14]; - ctx.finish(hash); - - return CryptoCore::b64_encode_str(hash, 0x14); -} - -String CodeSignCodeResources::hash_sha256_base64(const String &p_path) { - Ref fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); - - CryptoCore::SHA256Context ctx; - ctx.start(); - - unsigned char step[4096]; - while (true) { - uint64_t br = fa->get_buffer(step, 4096); - if (br > 0) { - ctx.update(step, br); - } - if (br < 4096) { - break; - } - } - - unsigned char hash[0x20]; - ctx.finish(hash); - - return CryptoCore::b64_encode_str(hash, 0x20); -} - -void CodeSignCodeResources::add_rule1(const String &p_rule, const String &p_key, int p_weight, bool p_store) { - rules1.push_back(CRRule(p_rule, p_key, p_weight, p_store)); -} - -void CodeSignCodeResources::add_rule2(const String &p_rule, const String &p_key, int p_weight, bool p_store) { - rules2.push_back(CRRule(p_rule, p_key, p_weight, p_store)); -} - -CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules1(const String &p_path) const { - CRMatch found = CRMatch::CR_MATCH_NO; - int weight = 0; - for (int i = 0; i < rules1.size(); i++) { - RegEx regex = RegEx(rules1[i].file_pattern); - if (regex.search(p_path).is_valid()) { - if (rules1[i].key == "omit") { - return CRMatch::CR_MATCH_NO; - } else if (rules1[i].key == "nested") { - if (weight <= rules1[i].weight) { - found = CRMatch::CR_MATCH_NESTED; - weight = rules1[i].weight; - } - } else if (rules1[i].key == "optional") { - if (weight <= rules1[i].weight) { - found = CRMatch::CR_MATCH_OPTIONAL; - weight = rules1[i].weight; - } - } else { - if (weight <= rules1[i].weight) { - found = CRMatch::CR_MATCH_YES; - weight = rules1[i].weight; - } - } - } - } - return found; -} - -CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules2(const String &p_path) const { - CRMatch found = CRMatch::CR_MATCH_NO; - int weight = 0; - for (int i = 0; i < rules2.size(); i++) { - RegEx regex = RegEx(rules2[i].file_pattern); - if (regex.search(p_path).is_valid()) { - if (rules2[i].key == "omit") { - return CRMatch::CR_MATCH_NO; - } else if (rules2[i].key == "nested") { - if (weight <= rules2[i].weight) { - found = CRMatch::CR_MATCH_NESTED; - weight = rules2[i].weight; - } - } else if (rules2[i].key == "optional") { - if (weight <= rules2[i].weight) { - found = CRMatch::CR_MATCH_OPTIONAL; - weight = rules2[i].weight; - } - } else { - if (weight <= rules2[i].weight) { - found = CRMatch::CR_MATCH_YES; - weight = rules2[i].weight; - } - } - } - } - return found; -} - -bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path) { - CRMatch found = match_rules1(p_path); - if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { - return true; // No match. - } - - CRFile f; - f.name = p_path; - f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); - f.nested = false; - f.hash = hash_sha1_base64(p_root.plus_file(p_path)); - print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash)); - - files1.push_back(f); - return true; -} - -bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) { - CRMatch found = match_rules2(p_path); - if (found == CRMatch::CR_MATCH_NESTED) { - return add_nested_file(p_root, p_path, p_root.plus_file(p_path)); - } - if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { - return true; // No match. - } - - CRFile f; - f.name = p_path; - f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); - f.nested = false; - f.hash = hash_sha1_base64(p_root.plus_file(p_path)); - f.hash2 = hash_sha256_base64(p_root.plus_file(p_path)); - - print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2)); - - files2.push_back(f); - return true; -} - -bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &p_path, const String &p_exepath) { -#define CLEANUP() \ - if (files_to_add.size() > 1) { \ - for (int j = 0; j < files_to_add.size(); j++) { \ - da->remove(files_to_add[j]); \ - } \ - } - - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(da.is_null(), false); - - Vector files_to_add; - if (LipO::is_lipo(p_exepath)) { - String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); - Error err = da->make_dir_recursive(tmp_path_name); - ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); - LipO lip; - if (lip.open_file(p_exepath)) { - for (int i = 0; i < lip.get_arch_count(); i++) { - if (!lip.extract_arch(i, tmp_path_name.plus_file("_rqexe_" + itos(i)))) { - CLEANUP(); - ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary."); - } - files_to_add.push_back(tmp_path_name.plus_file("_rqexe_" + itos(i))); - } - } - } else if (MachO::is_macho(p_exepath)) { - files_to_add.push_back(p_exepath); - } - - CRFile f; - f.name = p_path; - f.optional = false; - f.nested = true; - for (int i = 0; i < files_to_add.size(); i++) { - MachO mh; - if (!mh.open_file(files_to_add[i])) { - CLEANUP(); - ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid executable file."); - } - PackedByteArray hash = mh.get_cdhash_sha256(); // Use SHA-256 variant, if available. - if (hash.size() != 0x20) { - hash = mh.get_cdhash_sha1(); // Use SHA-1 instead. - if (hash.size() != 0x14) { - CLEANUP(); - ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Unsigned nested executable file."); - } - } - hash.resize(0x14); // Always clamp to 0x14 size. - f.hash = CryptoCore::b64_encode_str(hash.ptr(), hash.size()); - - PackedByteArray rq_blob = mh.get_requirements(); - String req_string; - if (rq_blob.size() > 8) { - CodeSignRequirements rq = CodeSignRequirements(rq_blob); - Vector rqs = rq.parse_requirements(); - for (int j = 0; j < rqs.size(); j++) { - if (rqs[j].begins_with("designated => ")) { - req_string = rqs[j].replace("designated => ", ""); - } - } - } - if (req_string.is_empty()) { - req_string = "cdhash H\"" + String::hex_encode_buffer(hash.ptr(), hash.size()) + "\""; - } - print_verbose(vformat("CodeSign/CodeResources: Nested object %s (cputype: %d) cdhash:%s designated rq:%s", f.name, mh.get_cputype(), f.hash, req_string)); - if (f.requirements != req_string) { - if (i != 0) { - f.requirements += " or "; - } - f.requirements += req_string; - } - } - files2.push_back(f); - - CLEANUP(); - return true; - -#undef CLEANUP -} - -bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(da.is_null(), false); - Error err = da->change_dir(p_root.plus_file(p_path)); - ERR_FAIL_COND_V(err != OK, false); - - bool ret = true; - da->list_dir_begin(); - String n = da->get_next(); - while (n != String()) { - if (n != "." && n != "..") { - String path = p_root.plus_file(p_path).plus_file(n); - if (path == p_main_exe_path) { - n = da->get_next(); - continue; // Skip main executable. - } - if (da->current_is_dir()) { - CRMatch found = match_rules2(p_path.plus_file(n)); - String fmw_ver = "Current"; // Framework version (default). - String info_path; - String main_exe; - bool bundle = false; - if (da->file_exists(path.plus_file("Contents/Info.plist"))) { - info_path = path.plus_file("Contents/Info.plist"); - main_exe = path.plus_file("Contents/MacOS"); - bundle = true; - } else if (da->file_exists(path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { - info_path = path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); - main_exe = path.plus_file(vformat("Versions/%s", fmw_ver)); - bundle = true; - } else if (da->file_exists(path.plus_file("Info.plist"))) { - info_path = path.plus_file("Info.plist"); - main_exe = path; - bundle = true; - } - if (bundle && found == CRMatch::CR_MATCH_NESTED && !info_path.is_empty()) { - // Read Info.plist. - PList info_plist; - if (info_plist.load_file(info_path)) { - if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { - main_exe = main_exe.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); - } else { - ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name."); - } - } else { - ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load."); - } - ret = ret && add_nested_file(p_root, p_path.plus_file(n), main_exe); - } else { - ret = ret && add_folder_recursive(p_root, p_path.plus_file(n), p_main_exe_path); - } - } else { - ret = ret && add_file1(p_root, p_path.plus_file(n)); - ret = ret && add_file2(p_root, p_path.plus_file(n)); - } - } - - n = da->get_next(); - } - - da->list_dir_end(); - return ret; -} - -bool CodeSignCodeResources::save_to_file(const String &p_path) { - PList pl; - - print_verbose(vformat("CodeSign/CodeResources: Writing to file: %s", p_path)); - - // Write version 1 hashes. - Ref files1_dict = PListNode::new_dict(); - pl.get_root()->push_subnode(files1_dict, "files"); - for (int i = 0; i < files1.size(); i++) { - if (files1[i].optional) { - Ref file_dict = PListNode::new_dict(); - files1_dict->push_subnode(file_dict, files1[i].name); - - file_dict->push_subnode(PListNode::new_data(files1[i].hash), "hash"); - file_dict->push_subnode(PListNode::new_bool(true), "optional"); - } else { - files1_dict->push_subnode(PListNode::new_data(files1[i].hash), files1[i].name); - } - } - - // Write version 2 hashes. - Ref files2_dict = PListNode::new_dict(); - pl.get_root()->push_subnode(files2_dict, "files2"); - for (int i = 0; i < files2.size(); i++) { - Ref file_dict = PListNode::new_dict(); - files2_dict->push_subnode(file_dict, files2[i].name); - - if (files2[i].nested) { - file_dict->push_subnode(PListNode::new_data(files2[i].hash), "cdhash"); - file_dict->push_subnode(PListNode::new_string(files2[i].requirements), "requirement"); - } else { - file_dict->push_subnode(PListNode::new_data(files2[i].hash), "hash"); - file_dict->push_subnode(PListNode::new_data(files2[i].hash2), "hash2"); - if (files2[i].optional) { - file_dict->push_subnode(PListNode::new_bool(true), "optional"); - } - } - } - - // Write version 1 rules. - Ref rules1_dict = PListNode::new_dict(); - pl.get_root()->push_subnode(rules1_dict, "rules"); - for (int i = 0; i < rules1.size(); i++) { - if (rules1[i].store) { - if (rules1[i].key.is_empty() && rules1[i].weight <= 0) { - rules1_dict->push_subnode(PListNode::new_bool(true), rules1[i].file_pattern); - } else { - Ref rule_dict = PListNode::new_dict(); - rules1_dict->push_subnode(rule_dict, rules1[i].file_pattern); - if (!rules1[i].key.is_empty()) { - rule_dict->push_subnode(PListNode::new_bool(true), rules1[i].key); - } - if (rules1[i].weight != 1) { - rule_dict->push_subnode(PListNode::new_real(rules1[i].weight), "weight"); - } - } - } - } - - // Write version 2 rules. - Ref rules2_dict = PListNode::new_dict(); - pl.get_root()->push_subnode(rules2_dict, "rules2"); - for (int i = 0; i < rules2.size(); i++) { - if (rules2[i].store) { - if (rules2[i].key.is_empty() && rules2[i].weight <= 0) { - rules2_dict->push_subnode(PListNode::new_bool(true), rules2[i].file_pattern); - } else { - Ref rule_dict = PListNode::new_dict(); - rules2_dict->push_subnode(rule_dict, rules2[i].file_pattern); - if (!rules2[i].key.is_empty()) { - rule_dict->push_subnode(PListNode::new_bool(true), rules2[i].key); - } - if (rules2[i].weight != 1) { - rule_dict->push_subnode(PListNode::new_real(rules2[i].weight), "weight"); - } - } - } - } - String text = pl.save_text(); - ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed."); - - Ref fa = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); - - CharString cs = text.utf8(); - fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); - return true; -} - -/*************************************************************************/ -/* CodeSignRequirements */ -/*************************************************************************/ - -CodeSignRequirements::CodeSignRequirements() { - blob.append_array({ 0xFA, 0xDE, 0x0C, 0x01 }); // Requirement set magic. - blob.append_array({ 0x00, 0x00, 0x00, 0x0C }); // Length of requirements set (12 bytes). - blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Empty. -} - -CodeSignRequirements::CodeSignRequirements(const PackedByteArray &p_data) { - blob = p_data; -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - r_out += "certificate "; - uint32_t tag_slot = _R(r_pos); - if (tag_slot == 0x00000000) { - r_out += "leaf"; - } else if (tag_slot == 0xffffffff) { - r_out += "root"; - } else { - r_out += itos((int32_t)tag_slot); - } - r_pos += 4; -#undef _R -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - uint32_t key_size = _R(r_pos); - ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); - CharString key; - key.resize(key_size); - memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); - r_pos += 4 + key_size + PAD(key_size, 4); - r_out += "[" + String::utf8(key, key_size) + "]"; -#undef _R -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - uint32_t key_size = _R(r_pos); - ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); - r_out += "[field."; - r_out += itos(blob[r_pos + 4] / 40) + "."; - r_out += itos(blob[r_pos + 4] % 40); - uint32_t spos = r_pos + 5; - while (spos < r_pos + 4 + key_size) { - r_out += "."; - if (blob[spos] <= 127) { - r_out += itos(blob[spos]); - spos += 1; - } else { - uint32_t x = (0x7F & blob[spos]) << 7; - spos += 1; - while (blob[spos] > 127) { - x = (x + (0x7F & blob[spos])) << 7; - spos += 1; - } - x = (x + (0x7F & blob[spos])); - r_out += itos(x); - spos += 1; - } - } - r_out += "]"; - r_pos += 4 + key_size + PAD(key_size, 4); -#undef _R -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - uint32_t tag_size = _R(r_pos); - ERR_FAIL_COND_MSG(r_pos + tag_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); - PackedByteArray data; - data.resize(tag_size); - memcpy(data.ptrw(), blob.ptr() + r_pos + 4, tag_size); - r_out += "H\"" + String::hex_encode_buffer(data.ptr(), data.size()) + "\""; - r_pos += 4 + tag_size + PAD(tag_size, 4); -#undef _R -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - uint32_t key_size = _R(r_pos); - ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); - CharString key; - key.resize(key_size); - memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); - r_pos += 4 + key_size + PAD(key_size, 4); - r_out += "\"" + String::utf8(key, key_size) + "\""; -#undef _R -} - -_FORCE_INLINE_ void CodeSignRequirements::_parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); - uint32_t date = _R(r_pos); - time_t t = 978307200 + date; - struct tm lt; -#ifdef WINDOWS_ENABLED - gmtime_s(<, &t); -#else - gmtime_r(&t, <); -#endif - r_out += vformat("<%04d-%02d-%02d ", (int)(1900 + lt.tm_year), (int)(lt.tm_mon + 1), (int)(lt.tm_mday)) + vformat("%02d:%02d:%02d +0000>", (int)(lt.tm_hour), (int)(lt.tm_min), (int)(lt.tm_sec)); -#undef _R -} - -_FORCE_INLINE_ bool CodeSignRequirements::_parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - ERR_FAIL_COND_V_MSG(r_pos >= p_rq_size, false, "CodeSign/Requirements: Out of bounds."); - uint32_t match = _R(r_pos); - r_pos += 4; - switch (match) { - case 0x00000000: { - r_out += "exists"; - } break; - case 0x00000001: { - r_out += "= "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000002: { - r_out += "~ "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000003: { - r_out += "= *"; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000004: { - r_out += "= "; - _parse_value(r_pos, r_out, p_rq_size); - r_out += "*"; - } break; - case 0x00000005: { - r_out += "< "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000006: { - r_out += "> "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000007: { - r_out += "<= "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000008: { - r_out += ">= "; - _parse_value(r_pos, r_out, p_rq_size); - } break; - case 0x00000009: { - r_out += "= "; - _parse_date(r_pos, r_out, p_rq_size); - } break; - case 0x0000000A: { - r_out += "< "; - _parse_date(r_pos, r_out, p_rq_size); - } break; - case 0x0000000B: { - r_out += "> "; - _parse_date(r_pos, r_out, p_rq_size); - } break; - case 0x0000000C: { - r_out += "<= "; - _parse_date(r_pos, r_out, p_rq_size); - } break; - case 0x0000000D: { - r_out += ">= "; - _parse_date(r_pos, r_out, p_rq_size); - } break; - case 0x0000000E: { - r_out += "absent"; - } break; - default: { - return false; - } - } - return true; -#undef _R -} - -Vector CodeSignRequirements::parse_requirements() const { -#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) - Vector list; - - // Read requirements set header. - ERR_FAIL_COND_V_MSG(blob.size() < 12, list, "CodeSign/Requirements: Blob is too small."); - uint32_t magic = _R(0); - ERR_FAIL_COND_V_MSG(magic != 0xfade0c01, list, "CodeSign/Requirements: Invalid set magic."); - uint32_t size = _R(4); - ERR_FAIL_COND_V_MSG(size != (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid set size."); - uint32_t count = _R(8); - - for (uint32_t i = 0; i < count; i++) { - String out; - - // Read requirement header. - uint32_t rq_type = _R(12 + i * 8); - uint32_t rq_offset = _R(12 + i * 8 + 4); - ERR_FAIL_COND_V_MSG(rq_offset + 12 >= (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid requirement offset."); - switch (rq_type) { - case 0x00000001: { - out += "host => "; - } break; - case 0x00000002: { - out += "guest => "; - } break; - case 0x00000003: { - out += "designated => "; - } break; - case 0x00000004: { - out += "library => "; - } break; - case 0x00000005: { - out += "plugin => "; - } break; - default: { - ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement type."); - } - } - uint32_t rq_magic = _R(rq_offset); - uint32_t rq_size = _R(rq_offset + 4); - uint32_t rq_ver = _R(rq_offset + 8); - uint32_t pos = rq_offset + 12; - ERR_FAIL_COND_V_MSG(rq_magic != 0xfade0c00, list, "CodeSign/Requirements: Invalid requirement magic."); - ERR_FAIL_COND_V_MSG(rq_ver != 0x00000001, list, "CodeSign/Requirements: Invalid requirement version."); - - // Read requirement tokens. - List tokens; - while (pos < rq_offset + rq_size) { - uint32_t rq_tag = _R(pos); - pos += 4; - String token; - switch (rq_tag) { - case 0x00000000: { - token = "false"; - } break; - case 0x00000001: { - token = "true"; - } break; - case 0x00000002: { - token = "identifier "; - _parse_value(pos, token, rq_offset + rq_size); - } break; - case 0x00000003: { - token = "anchor apple"; - } break; - case 0x00000004: { - _parse_certificate_slot(pos, token, rq_offset + rq_size); - token += " "; - _parse_hash_string(pos, token, rq_offset + rq_size); - } break; - case 0x00000005: { - token = "info"; - _parse_key(pos, token, rq_offset + rq_size); - token += " = "; - _parse_value(pos, token, rq_offset + rq_size); - } break; - case 0x00000006: { - token = "and"; - } break; - case 0x00000007: { - token = "or"; - } break; - case 0x00000008: { - token = "cdhash "; - _parse_hash_string(pos, token, rq_offset + rq_size); - } break; - case 0x00000009: { - token = "!"; - } break; - case 0x0000000A: { - token = "info"; - _parse_key(pos, token, rq_offset + rq_size); - token += " "; - ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); - } break; - case 0x0000000B: { - _parse_certificate_slot(pos, token, rq_offset + rq_size); - _parse_key(pos, token, rq_offset + rq_size); - token += " "; - ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); - } break; - case 0x0000000C: { - _parse_certificate_slot(pos, token, rq_offset + rq_size); - token += " trusted"; - } break; - case 0x0000000D: { - token = "anchor trusted"; - } break; - case 0x0000000E: { - _parse_certificate_slot(pos, token, rq_offset + rq_size); - _parse_oid_key(pos, token, rq_offset + rq_size); - token += " "; - ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); - } break; - case 0x0000000F: { - token = "anchor apple generic"; - } break; - default: { - ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement token."); - } break; - } - tokens.push_back(token); - } - - // Polish to infix notation (w/o bracket optimization). - for (List::Element *E = tokens.back(); E; E = E->prev()) { - if (E->get() == "and") { - ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); - String token = "(" + E->next()->get() + " and " + E->next()->next()->get() + ")"; - tokens.erase(E->next()->next()); - tokens.erase(E->next()); - E->get() = token; - } else if (E->get() == "or") { - ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); - String token = "(" + E->next()->get() + " or " + E->next()->next()->get() + ")"; - tokens.erase(E->next()->next()); - tokens.erase(E->next()); - E->get() = token; - } - } - - if (tokens.size() == 1) { - list.push_back(out + tokens.front()->get()); - } else { - ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid token sequence."); - } - } - - return list; -#undef _R -} - -PackedByteArray CodeSignRequirements::get_hash_sha1() const { - PackedByteArray hash; - hash.resize(0x14); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -PackedByteArray CodeSignRequirements::get_hash_sha256() const { - PackedByteArray hash; - hash.resize(0x20); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -int CodeSignRequirements::get_size() const { - return blob.size(); -} - -void CodeSignRequirements::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle."); - p_file->store_buffer(blob.ptr(), blob.size()); -} - -/*************************************************************************/ -/* CodeSignEntitlementsText */ -/*************************************************************************/ - -CodeSignEntitlementsText::CodeSignEntitlementsText() { - blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. - blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). -} - -CodeSignEntitlementsText::CodeSignEntitlementsText(const String &p_string) { - CharString utf8 = p_string.utf8(); - blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. - for (int i = 3; i >= 0; i--) { - uint8_t x = ((utf8.length() + 8) >> i * 8) & 0xFF; // Size. - blob.push_back(x); - } - for (int i = 0; i < utf8.length(); i++) { // Write data. - blob.push_back(utf8[i]); - } -} - -PackedByteArray CodeSignEntitlementsText::get_hash_sha1() const { - PackedByteArray hash; - hash.resize(0x14); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -PackedByteArray CodeSignEntitlementsText::get_hash_sha256() const { - PackedByteArray hash; - hash.resize(0x20); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -int CodeSignEntitlementsText::get_size() const { - return blob.size(); -} - -void CodeSignEntitlementsText::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle."); - p_file->store_buffer(blob.ptr(), blob.size()); -} - -/*************************************************************************/ -/* CodeSignEntitlementsBinary */ -/*************************************************************************/ - -CodeSignEntitlementsBinary::CodeSignEntitlementsBinary() { - blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. - blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). -} - -CodeSignEntitlementsBinary::CodeSignEntitlementsBinary(const String &p_string) { - PList pl = PList(p_string); - - PackedByteArray asn1 = pl.save_asn1(); - blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. - uint32_t size = asn1.size() + 8; - for (int i = 3; i >= 0; i--) { - uint8_t x = (size >> i * 8) & 0xFF; // Size. - blob.push_back(x); - } - blob.append_array(asn1); // Write data. -} - -PackedByteArray CodeSignEntitlementsBinary::get_hash_sha1() const { - PackedByteArray hash; - hash.resize(0x14); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -PackedByteArray CodeSignEntitlementsBinary::get_hash_sha256() const { - PackedByteArray hash; - hash.resize(0x20); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -int CodeSignEntitlementsBinary::get_size() const { - return blob.size(); -} - -void CodeSignEntitlementsBinary::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle."); - p_file->store_buffer(blob.ptr(), blob.size()); -} - -/*************************************************************************/ -/* CodeSignCodeDirectory */ -/*************************************************************************/ - -CodeSignCodeDirectory::CodeSignCodeDirectory() { - blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. - blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Size (8 bytes). -} - -CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit) { - pages = p_code_limit / (uint64_t(1) << p_page_size); - remain = p_code_limit % (uint64_t(1) << p_page_size); - code_slots = pages + (remain > 0 ? 1 : 0); - special_slots = 7; - - int cd_size = 8 + sizeof(CodeDirectoryHeader) + (code_slots + special_slots) * p_hash_size + p_id.size() + p_team_id.size(); - int cd_off = 8 + sizeof(CodeDirectoryHeader); - blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. - for (int i = 3; i >= 0; i--) { - uint8_t x = (cd_size >> i * 8) & 0xFF; // Size. - blob.push_back(x); - } - blob.resize(cd_size); - memset(blob.ptrw() + 8, 0x00, cd_size - 8); - CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); - - bool is_64_cl = (p_code_limit >= std::numeric_limits::max()); - - // Version and options. - cd->version = BSWAP32(0x20500); - cd->flags = BSWAP32(SIGNATURE_ADHOC | SIGNATURE_RUNTIME); - cd->special_slots = BSWAP32(special_slots); - cd->code_slots = BSWAP32(code_slots); - if (is_64_cl) { - cd->code_limit_64 = BSWAP64(p_code_limit); - } else { - cd->code_limit = BSWAP32(p_code_limit); - } - cd->hash_size = p_hash_size; - cd->hash_type = p_hash_type; - cd->page_size = p_page_size; - cd->exec_seg_base = 0x00; - cd->exec_seg_limit = BSWAP64(p_exe_limit); - cd->exec_seg_flags = 0; - if (p_main) { - cd->exec_seg_flags |= EXECSEG_MAIN_BINARY; - } - cd->exec_seg_flags = BSWAP64(cd->exec_seg_flags); - uint32_t version = (11 << 16) + (3 << 8) + 0; // Version 11.3.0 - cd->runtime = BSWAP32(version); - - // Copy ID. - cd->ident_offset = BSWAP32(cd_off); - memcpy(blob.ptrw() + cd_off, p_id.get_data(), p_id.size()); - cd_off += p_id.size(); - - // Copy Team ID. - if (p_team_id.length() > 0) { - cd->team_offset = BSWAP32(cd_off); - memcpy(blob.ptrw() + cd_off, p_team_id.get_data(), p_team_id.size()); - cd_off += p_team_id.size(); - } else { - cd->team_offset = 0; - } - - // Scatter vector. - cd->scatter_vector_offset = 0; // Not used. - - // Executable hashes offset. - cd->hash_offset = BSWAP32(cd_off + special_slots * cd->hash_size); -} - -bool CodeSignCodeDirectory::set_hash_in_slot(const PackedByteArray &p_hash, int p_slot) { - ERR_FAIL_COND_V_MSG((p_slot < -special_slots) || (p_slot >= code_slots), false, vformat("CodeSign/CodeDirectory: Invalid hash slot index: %d.", p_slot)); - CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); - for (int i = 0; i < cd->hash_size; i++) { - blob.write[BSWAP32(cd->hash_offset) + p_slot * cd->hash_size + i] = p_hash[i]; - } - return true; -} - -int32_t CodeSignCodeDirectory::get_page_count() { - return pages; -} - -int32_t CodeSignCodeDirectory::get_page_remainder() { - return remain; -} - -PackedByteArray CodeSignCodeDirectory::get_hash_sha1() const { - PackedByteArray hash; - hash.resize(0x14); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -PackedByteArray CodeSignCodeDirectory::get_hash_sha256() const { - PackedByteArray hash; - hash.resize(0x20); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -int CodeSignCodeDirectory::get_size() const { - return blob.size(); -} - -void CodeSignCodeDirectory::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle."); - p_file->store_buffer(blob.ptr(), blob.size()); -} - -/*************************************************************************/ -/* CodeSignSignature */ -/*************************************************************************/ - -CodeSignSignature::CodeSignSignature() { - blob.append_array({ 0xFA, 0xDE, 0x0B, 0x01 }); // Signature magic. - uint32_t sign_size = 8; // Ad-hoc signature is empty. - for (int i = 3; i >= 0; i--) { - uint8_t x = (sign_size >> i * 8) & 0xFF; // Size. - blob.push_back(x); - } -} - -PackedByteArray CodeSignSignature::get_hash_sha1() const { - PackedByteArray hash; - hash.resize(0x14); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -PackedByteArray CodeSignSignature::get_hash_sha256() const { - PackedByteArray hash; - hash.resize(0x20); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; -} - -int CodeSignSignature::get_size() const { - return blob.size(); -} - -void CodeSignSignature::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle."); - p_file->store_buffer(blob.ptr(), blob.size()); -} - -/*************************************************************************/ -/* CodeSignSuperBlob */ -/*************************************************************************/ - -bool CodeSignSuperBlob::add_blob(const Ref &p_blob) { - if (p_blob.is_valid()) { - blobs.push_back(p_blob); - return true; - } else { - return false; - } -} - -int CodeSignSuperBlob::get_size() const { - int size = 12 + blobs.size() * 8; - for (int i = 0; i < blobs.size(); i++) { - if (blobs[i].is_null()) { - return 0; - } - size += blobs[i]->get_size(); - } - return size; -} - -void CodeSignSuperBlob::write_to_file(Ref p_file) const { - ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle."); - uint32_t size = get_size(); - uint32_t data_offset = 12 + blobs.size() * 8; - - // Write header. - p_file->store_32(BSWAP32(0xfade0cc0)); - p_file->store_32(BSWAP32(size)); - p_file->store_32(BSWAP32(blobs.size())); - - // Write index. - for (int i = 0; i < blobs.size(); i++) { - if (blobs[i].is_null()) { - return; - } - p_file->store_32(BSWAP32(blobs[i]->get_index_type())); - p_file->store_32(BSWAP32(data_offset)); - data_offset += blobs[i]->get_size(); - } - - // Write blobs. - for (int i = 0; i < blobs.size(); i++) { - blobs[i]->write_to_file(p_file); - } -} - -/*************************************************************************/ -/* CodeSign */ -/*************************************************************************/ - -PackedByteArray CodeSign::file_hash_sha1(const String &p_path) { - PackedByteArray file_hash; - Ref f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); - - CryptoCore::SHA1Context ctx; - ctx.start(); - - unsigned char step[4096]; - while (true) { - uint64_t br = f->get_buffer(step, 4096); - if (br > 0) { - ctx.update(step, br); - } - if (br < 4096) { - break; - } - } - - file_hash.resize(0x14); - ctx.finish(file_hash.ptrw()); - return file_hash; -} - -PackedByteArray CodeSign::file_hash_sha256(const String &p_path) { - PackedByteArray file_hash; - Ref f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); - - CryptoCore::SHA256Context ctx; - ctx.start(); - - unsigned char step[4096]; - while (true) { - uint64_t br = f->get_buffer(step, 4096); - if (br > 0) { - ctx.update(step, br); - } - if (br < 4096) { - break; - } - } - - file_hash.resize(0x20); - ctx.finish(file_hash.ptrw()); - return file_hash; -} - -Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg) { -#define CLEANUP() \ - if (files_to_sign.size() > 1) { \ - for (int j = 0; j < files_to_sign.size(); j++) { \ - da->remove(files_to_sign[j]); \ - } \ - } - - print_verbose(vformat("CodeSign: Signing executable: %s, bundle: %s with entitlements %s", p_exe_path, p_bundle_path, p_ent_path)); - - PackedByteArray info_hash1, info_hash2; - PackedByteArray res_hash1, res_hash2; - String id; - String main_exe = p_exe_path; - - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da.is_null()) { - r_error_msg = TTR("Can't get filesystem access."); - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); - } - - // Read Info.plist. - if (!p_info.is_empty()) { - print_verbose(vformat("CodeSign: Reading bundle info...")); - PList info_plist; - if (info_plist.load_file(p_info)) { - info_hash1 = file_hash_sha1(p_info); - info_hash2 = file_hash_sha256(p_info); - if (info_hash1.is_empty() || info_hash2.is_empty()) { - r_error_msg = TTR("Failed to get Info.plist hash."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get Info.plist hash."); - } - - if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { - main_exe = p_exe_path.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); - } else { - r_error_msg = TTR("Invalid Info.plist, no exe name."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name."); - } - - if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleIdentifier")) { - id = info_plist.get_root()->data_dict["CFBundleIdentifier"]->data_string.get_data(); - } else { - r_error_msg = TTR("Invalid Info.plist, no bundle id."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no bundle id."); - } - } else { - r_error_msg = TTR("Invalid Info.plist, can't load."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, can't load."); - } - } - - // Extract fat binary. - Vector files_to_sign; - if (LipO::is_lipo(main_exe)) { - print_verbose(vformat("CodeSign: Executable is fat, extracting...")); - String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); - Error err = da->make_dir_recursive(tmp_path_name); - if (err != OK) { - r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name); - ERR_FAIL_V_MSG(FAILED, vformat("CodeSign: Failed to create \"%s\" subfolder.", tmp_path_name)); - } - LipO lip; - if (lip.open_file(main_exe)) { - for (int i = 0; i < lip.get_arch_count(); i++) { - if (!lip.extract_arch(i, tmp_path_name.plus_file("_exe_" + itos(i)))) { - CLEANUP(); - r_error_msg = TTR("Failed to extract thin binary."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary."); - } - files_to_sign.push_back(tmp_path_name.plus_file("_exe_" + itos(i))); - } - } - } else if (MachO::is_macho(main_exe)) { - print_verbose(vformat("CodeSign: Executable is thin...")); - files_to_sign.push_back(main_exe); - } else { - r_error_msg = TTR("Invalid binary format."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid binary format."); - } - - // Check if it's already signed. - if (!p_force) { - for (int i = 0; i < files_to_sign.size(); i++) { - MachO mh; - mh.open_file(files_to_sign[i]); - if (mh.is_signed()) { - CLEANUP(); - r_error_msg = TTR("Already signed!"); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Already signed!"); - } - } - } - - // Generate core resources. - if (!p_bundle_path.is_empty()) { - print_verbose(vformat("CodeSign: Generating bundle CodeResources...")); - CodeSignCodeResources cr; - - if (p_ios_bundle) { - cr.add_rule1("^.*"); - cr.add_rule1("^.*\\.lproj/", "optional", 100); - cr.add_rule1("^.*\\.lproj/locversion.plist$", "omit", 1100); - cr.add_rule1("^Base\\.lproj/", "", 1010); - cr.add_rule1("^version.plist$"); - - cr.add_rule2(".*\\.dSYM($|/)", "", 11); - cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); - cr.add_rule2("^.*"); - cr.add_rule2("^.*\\.lproj/", "optional", 1000); - cr.add_rule2("^.*\\.lproj/locversion.plist$", "omit", 1100); - cr.add_rule2("^Base\\.lproj/", "", 1010); - cr.add_rule2("^Info\\.plist$", "omit", 20); - cr.add_rule2("^PkgInfo$", "omit", 20); - cr.add_rule2("^embedded\\.provisionprofile$", "", 10); - cr.add_rule2("^version\\.plist$", "", 20); - - cr.add_rule2("^_MASReceipt", "omit", 2000, false); - cr.add_rule2("^_CodeSignature", "omit", 2000, false); - cr.add_rule2("^CodeResources", "omit", 2000, false); - } else { - cr.add_rule1("^Resources/"); - cr.add_rule1("^Resources/.*\\.lproj/", "optional", 1000); - cr.add_rule1("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); - cr.add_rule1("^Resources/Base\\.lproj/", "", 1010); - cr.add_rule1("^version.plist$"); - - cr.add_rule2(".*\\.dSYM($|/)", "", 11); - cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); - cr.add_rule2("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", "nested", 10); - cr.add_rule2("^.*"); - cr.add_rule2("^Info\\.plist$", "omit", 20); - cr.add_rule2("^PkgInfo$", "omit", 20); - cr.add_rule2("^Resources/", "", 20); - cr.add_rule2("^Resources/.*\\.lproj/", "optional", 1000); - cr.add_rule2("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); - cr.add_rule2("^Resources/Base\\.lproj/", "", 1010); - cr.add_rule2("^[^/]+$", "nested", 10); - cr.add_rule2("^embedded\\.provisionprofile$", "", 10); - cr.add_rule2("^version\\.plist$", "", 20); - cr.add_rule2("^_MASReceipt", "omit", 2000, false); - cr.add_rule2("^_CodeSignature", "omit", 2000, false); - cr.add_rule2("^CodeResources", "omit", 2000, false); - } - - if (!cr.add_folder_recursive(p_bundle_path, "", main_exe)) { - CLEANUP(); - r_error_msg = TTR("Failed to process nested resources."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources."); - } - Error err = da->make_dir_recursive(p_bundle_path.plus_file("_CodeSignature")); - if (err != OK) { - CLEANUP(); - r_error_msg = TTR("Failed to create _CodeSignature subfolder."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder."); - } - cr.save_to_file(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); - res_hash1 = file_hash_sha1(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); - res_hash2 = file_hash_sha256(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); - if (res_hash1.is_empty() || res_hash2.is_empty()) { - CLEANUP(); - r_error_msg = TTR("Failed to get CodeResources hash."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get CodeResources hash."); - } - } - - // Generate common signature structures. - if (id.is_empty()) { - CryptoCore::RandomGenerator rng; - ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); - uint8_t uuid[16]; - Error err = rng.get_random_bytes(uuid, 16); - ERR_FAIL_COND_V_MSG(err, err, "Failed to generate UUID."); - id = (String("a-55554944") /*a-UUID*/ + String::hex_encode_buffer(uuid, 16)); - } - CharString uuid_str = id.utf8(); - print_verbose(vformat("CodeSign: Used bundle ID: %s", id)); - - print_verbose(vformat("CodeSign: Processing entitlements...")); - - Ref cet; - Ref ceb; - if (!p_ent_path.is_empty()) { - String entitlements = FileAccess::get_file_as_string(p_ent_path); - if (entitlements.is_empty()) { - CLEANUP(); - r_error_msg = TTR("Invalid entitlements file."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid entitlements file."); - } - cet = Ref(memnew(CodeSignEntitlementsText(entitlements))); - ceb = Ref(memnew(CodeSignEntitlementsBinary(entitlements))); - } - - print_verbose(vformat("CodeSign: Generating requirements...")); - Ref rq; - String team_id = ""; - rq = Ref(memnew(CodeSignRequirements())); - - // Sign executables. - for (int i = 0; i < files_to_sign.size(); i++) { - MachO mh; - if (!mh.open_file(files_to_sign[i])) { - CLEANUP(); - r_error_msg = TTR("Invalid executable file."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid executable file."); - } - print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype())); - - print_verbose(vformat("CodeSign: Generating CodeDirectory...")); - Ref cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); - Ref cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); - print_verbose(vformat("CodeSign: Calculating special slot hashes...")); - if (info_hash2.size() == 0x20) { - cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST); - } - if (info_hash1.size() == 0x14) { - cd1->set_hash_in_slot(info_hash1, CodeSignCodeDirectory::SLOT_INFO_PLIST); - } - cd1->set_hash_in_slot(rq->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); - cd2->set_hash_in_slot(rq->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); - if (res_hash2.size() == 0x20) { - cd2->set_hash_in_slot(res_hash2, CodeSignCodeDirectory::SLOT_RESOURCES); - } - if (res_hash1.size() == 0x14) { - cd1->set_hash_in_slot(res_hash1, CodeSignCodeDirectory::SLOT_RESOURCES); - } - if (cet.is_valid()) { - cd1->set_hash_in_slot(cet->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); //Text variant. - cd2->set_hash_in_slot(cet->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); - } - if (ceb.is_valid()) { - cd1->set_hash_in_slot(ceb->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); //ASN.1 variant. - cd2->set_hash_in_slot(ceb->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); - } - - // Calculate signature size. - int sign_size = 12; // SuperBlob header. - sign_size += cd1->get_size() + 8; - sign_size += cd2->get_size() + 8; - sign_size += rq->get_size() + 8; - if (cet.is_valid()) { - sign_size += cet->get_size() + 8; - } - if (ceb.is_valid()) { - sign_size += ceb->get_size() + 8; - } - sign_size += 16; // Empty signature size. - - // Alloc/resize signature load command. - print_verbose(vformat("CodeSign: Reallocating space for the signature superblob (%d)...", sign_size)); - if (!mh.set_signature_size(sign_size)) { - CLEANUP(); - r_error_msg = TTR("Can't resize signature load command."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command."); - } - - print_verbose(vformat("CodeSign: Calculating executable code hashes...")); - // Calculate executable code hashes. - PackedByteArray buffer; - PackedByteArray hash1, hash2; - hash1.resize(0x14); - hash2.resize(0x20); - buffer.resize(1 << 12); - mh.get_file()->seek(0); - for (int32_t j = 0; j < cd2->get_page_count(); j++) { - mh.get_file()->get_buffer(buffer.ptrw(), (1 << 12)); - CryptoCore::SHA256Context ctx2; - ctx2.start(); - ctx2.update(buffer.ptr(), (1 << 12)); - ctx2.finish(hash2.ptrw()); - cd2->set_hash_in_slot(hash2, j); - - CryptoCore::SHA1Context ctx1; - ctx1.start(); - ctx1.update(buffer.ptr(), (1 << 12)); - ctx1.finish(hash1.ptrw()); - cd1->set_hash_in_slot(hash1, j); - } - if (cd2->get_page_remainder() > 0) { - mh.get_file()->get_buffer(buffer.ptrw(), cd2->get_page_remainder()); - CryptoCore::SHA256Context ctx2; - ctx2.start(); - ctx2.update(buffer.ptr(), cd2->get_page_remainder()); - ctx2.finish(hash2.ptrw()); - cd2->set_hash_in_slot(hash2, cd2->get_page_count()); - - CryptoCore::SHA1Context ctx1; - ctx1.start(); - ctx1.update(buffer.ptr(), cd1->get_page_remainder()); - ctx1.finish(hash1.ptrw()); - cd1->set_hash_in_slot(hash1, cd1->get_page_count()); - } - - print_verbose(vformat("CodeSign: Generating signature...")); - Ref cs; - cs = Ref(memnew(CodeSignSignature())); - - print_verbose(vformat("CodeSign: Writing signature superblob...")); - // Write signature data to the executable. - CodeSignSuperBlob sb = CodeSignSuperBlob(); - sb.add_blob(cd2); - sb.add_blob(cd1); - sb.add_blob(rq); - if (cet.is_valid()) { - sb.add_blob(cet); - } - if (ceb.is_valid()) { - sb.add_blob(ceb); - } - sb.add_blob(cs); - mh.get_file()->seek(mh.get_signature_offset()); - sb.write_to_file(mh.get_file()); - } - if (files_to_sign.size() > 1) { - print_verbose(vformat("CodeSign: Rebuilding fat executable...")); - LipO lip; - if (!lip.create_file(main_exe, files_to_sign)) { - CLEANUP(); - r_error_msg = TTR("Failed to create fat binary."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create fat binary."); - } - CLEANUP(); - } - FileAccess::set_unix_permissions(main_exe, 0755); // Restore unix permissions. - return OK; -#undef CLEANUP -} - -Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da.is_null()) { - r_error_msg = TTR("Can't get filesystem access."); - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); - } - - if (da->dir_exists(p_path)) { - String fmw_ver = "Current"; // Framework version (default). - String info_path; - String main_exe; - String bundle_path; - bool bundle = false; - bool ios_bundle = false; - if (da->file_exists(p_path.plus_file("Contents/Info.plist"))) { - info_path = p_path.plus_file("Contents/Info.plist"); - main_exe = p_path.plus_file("Contents/MacOS"); - bundle_path = p_path.plus_file("Contents"); - bundle = true; - } else if (da->file_exists(p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { - info_path = p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); - main_exe = p_path.plus_file(vformat("Versions/%s", fmw_ver)); - bundle_path = p_path.plus_file(vformat("Versions/%s", fmw_ver)); - bundle = true; - } else if (da->file_exists(p_path.plus_file("Info.plist"))) { - info_path = p_path.plus_file("Info.plist"); - main_exe = p_path; - bundle_path = p_path; - bundle = true; - ios_bundle = true; - } - if (bundle) { - return _codesign_file(p_use_hardened_runtime, p_force, info_path, main_exe, bundle_path, p_ent_path, ios_bundle, r_error_msg); - } else { - r_error_msg = TTR("Unknown bundle type."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown bundle type."); - } - } else if (da->file_exists(p_path)) { - return _codesign_file(p_use_hardened_runtime, p_force, "", p_path, "", p_ent_path, false, r_error_msg); - } else { - r_error_msg = TTR("Unknown object type."); - ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown object type."); - } -} - -#endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/codesign.h b/platform/osx/export/codesign.h deleted file mode 100644 index 3a08c0ea86..0000000000 --- a/platform/osx/export/codesign.h +++ /dev/null @@ -1,368 +0,0 @@ -/*************************************************************************/ -/* codesign.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -// macOS code signature creation utility. -// -// Current implementation has the following limitation: -// - Only version 11.3.0 signatures are supported. -// - Only "framework" and "app" bundle types are supported. -// - Page hash array scattering is not supported. -// - Reading and writing binary property lists i snot supported (third-party frameworks with binary Info.plist will not work unless .plist is converted to text format). -// - Requirements code generator is not implemented (only hard-coded requirements for the ad-hoc signing is supported). -// - RFC5652/CMS blob generation is not implemented, supports ad-hoc signing only. - -#ifndef CODESIGN_H -#define CODESIGN_H - -#include "core/crypto/crypto_core.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/object/ref_counted.h" - -#include "modules/modules_enabled.gen.h" // For regex. -#ifdef MODULE_REGEX_ENABLED -#include "modules/regex/regex.h" -#endif - -#include "plist.h" - -#ifdef MODULE_REGEX_ENABLED - -/*************************************************************************/ -/* CodeSignCodeResources */ -/*************************************************************************/ - -class CodeSignCodeResources { -public: - enum class CRMatch { - CR_MATCH_NO, - CR_MATCH_YES, - CR_MATCH_NESTED, - CR_MATCH_OPTIONAL, - }; - -private: - struct CRFile { - String name; - String hash; - String hash2; - bool optional; - bool nested; - String requirements; - }; - - struct CRRule { - String file_pattern; - String key; - int weight; - bool store; - CRRule() { - weight = 1; - store = true; - } - CRRule(const String &p_file_pattern, const String &p_key, int p_weight, bool p_store) { - file_pattern = p_file_pattern; - key = p_key; - weight = p_weight; - store = p_store; - } - }; - - Vector rules1; - Vector rules2; - - Vector files1; - Vector files2; - - String hash_sha1_base64(const String &p_path); - String hash_sha256_base64(const String &p_path); - -public: - void add_rule1(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true); - void add_rule2(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true); - - CRMatch match_rules1(const String &p_path) const; - CRMatch match_rules2(const String &p_path) const; - - bool add_file1(const String &p_root, const String &p_path); - bool add_file2(const String &p_root, const String &p_path); - bool add_nested_file(const String &p_root, const String &p_path, const String &p_exepath); - - bool add_folder_recursive(const String &p_root, const String &p_path = "", const String &p_main_exe_path = ""); - - bool save_to_file(const String &p_path); -}; - -/*************************************************************************/ -/* CodeSignBlob */ -/*************************************************************************/ - -class CodeSignBlob : public RefCounted { -public: - virtual PackedByteArray get_hash_sha1() const = 0; - virtual PackedByteArray get_hash_sha256() const = 0; - - virtual int get_size() const = 0; - virtual uint32_t get_index_type() const = 0; - - virtual void write_to_file(Ref p_file) const = 0; -}; - -/*************************************************************************/ -/* CodeSignRequirements */ -/*************************************************************************/ - -// Note: Proper code generator is not implemented (any we probably won't ever need it), just a hardcoded bytecode for the limited set of cases. - -class CodeSignRequirements : public CodeSignBlob { - PackedByteArray blob; - - static inline size_t PAD(size_t s, size_t a) { - return (s % a == 0) ? 0 : (a - s % a); - } - - _FORCE_INLINE_ void _parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ void _parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ void _parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ void _parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ void _parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ void _parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - _FORCE_INLINE_ bool _parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const; - -public: - CodeSignRequirements(); - CodeSignRequirements(const PackedByteArray &p_data); - - Vector parse_requirements() const; - - virtual PackedByteArray get_hash_sha1() const override; - virtual PackedByteArray get_hash_sha256() const override; - - virtual int get_size() const override; - - virtual uint32_t get_index_type() const override { return 0x00000002; }; - virtual void write_to_file(Ref p_file) const override; -}; - -/*************************************************************************/ -/* CodeSignEntitlementsText */ -/*************************************************************************/ - -// PList formatted entitlements. - -class CodeSignEntitlementsText : public CodeSignBlob { - PackedByteArray blob; - -public: - CodeSignEntitlementsText(); - CodeSignEntitlementsText(const String &p_string); - - virtual PackedByteArray get_hash_sha1() const override; - virtual PackedByteArray get_hash_sha256() const override; - - virtual int get_size() const override; - - virtual uint32_t get_index_type() const override { return 0x00000005; }; - virtual void write_to_file(Ref p_file) const override; -}; - -/*************************************************************************/ -/* CodeSignEntitlementsBinary */ -/*************************************************************************/ - -// ASN.1 serialized entitlements. - -class CodeSignEntitlementsBinary : public CodeSignBlob { - PackedByteArray blob; - -public: - CodeSignEntitlementsBinary(); - CodeSignEntitlementsBinary(const String &p_string); - - virtual PackedByteArray get_hash_sha1() const override; - virtual PackedByteArray get_hash_sha256() const override; - - virtual int get_size() const override; - - virtual uint32_t get_index_type() const override { return 0x00000007; }; - virtual void write_to_file(Ref p_file) const override; -}; - -/*************************************************************************/ -/* CodeSignCodeDirectory */ -/*************************************************************************/ - -// Code Directory, runtime options, code segment and special structure hashes. - -class CodeSignCodeDirectory : public CodeSignBlob { -public: - enum Slot { - SLOT_INFO_PLIST = -1, - SLOT_REQUIREMENTS = -2, - SLOT_RESOURCES = -3, - SLOT_APP_SPECIFIC = -4, // Unused. - SLOT_ENTITLEMENTS = -5, - SLOT_RESERVER1 = -6, // Unused. - SLOT_DER_ENTITLEMENTS = -7, - }; - - enum CodeSignExecSegFlags { - EXECSEG_MAIN_BINARY = 0x1, - EXECSEG_ALLOW_UNSIGNED = 0x10, - EXECSEG_DEBUGGER = 0x20, - EXECSEG_JIT = 0x40, - EXECSEG_SKIP_LV = 0x80, - EXECSEG_CAN_LOAD_CDHASH = 0x100, - EXECSEG_CAN_EXEC_CDHASH = 0x200, - }; - - enum CodeSignatureFlags { - SIGNATURE_HOST = 0x0001, - SIGNATURE_ADHOC = 0x0002, - SIGNATURE_TASK_ALLOW = 0x0004, - SIGNATURE_INSTALLER = 0x0008, - SIGNATURE_FORCED_LV = 0x0010, - SIGNATURE_INVALID_ALLOWED = 0x0020, - SIGNATURE_FORCE_HARD = 0x0100, - SIGNATURE_FORCE_KILL = 0x0200, - SIGNATURE_FORCE_EXPIRATION = 0x0400, - SIGNATURE_RESTRICT = 0x0800, - SIGNATURE_ENFORCEMENT = 0x1000, - SIGNATURE_LIBRARY_VALIDATION = 0x2000, - SIGNATURE_ENTITLEMENTS_VALIDATED = 0x4000, - SIGNATURE_NVRAM_UNRESTRICTED = 0x8000, - SIGNATURE_RUNTIME = 0x10000, - SIGNATURE_LINKER_SIGNED = 0x20000, - }; - -private: - PackedByteArray blob; - - struct CodeDirectoryHeader { - uint32_t version; // Using version 0x0020500. - uint32_t flags; // // Option flags. - uint32_t hash_offset; // Slot zero offset. - uint32_t ident_offset; // Identifier string offset. - uint32_t special_slots; // Nr. of slots with negative index. - uint32_t code_slots; // Nr. of slots with index >= 0, (code_limit / page_size). - uint32_t code_limit; // Everything before code signature load command offset. - uint8_t hash_size; // 20 (SHA-1) or 32 (SHA-256). - uint8_t hash_type; // 1 (SHA-1) or 2 (SHA-256). - uint8_t platform; // Not used. - uint8_t page_size; // Page size, power of two, 2^12 (4096). - uint32_t spare2; // Not used. - // Version 0x20100 - uint32_t scatter_vector_offset; // Set to 0 and ignore. - // Version 0x20200 - uint32_t team_offset; // Team id string offset. - // Version 0x20300 - uint32_t spare3; // Not used. - uint64_t code_limit_64; // Set to 0 and ignore. - // Version 0x20400 - uint64_t exec_seg_base; // Start of the signed code segmet. - uint64_t exec_seg_limit; // Code segment (__TEXT) vmsize. - uint64_t exec_seg_flags; // Executable segment flags. - // Version 0x20500 - uint32_t runtime; // Runtime version. - uint32_t pre_encrypt_offset; // Set to 0 and ignore. - }; - - int32_t pages = 0; - int32_t remain = 0; - int32_t code_slots = 0; - int32_t special_slots = 0; - -public: - CodeSignCodeDirectory(); - CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit); - - int32_t get_page_count(); - int32_t get_page_remainder(); - - bool set_hash_in_slot(const PackedByteArray &p_hash, int p_slot); - - virtual PackedByteArray get_hash_sha1() const override; - virtual PackedByteArray get_hash_sha256() const override; - - virtual int get_size() const override; - virtual uint32_t get_index_type() const override { return 0x00000000; }; - - virtual void write_to_file(Ref p_file) const override; -}; - -/*************************************************************************/ -/* CodeSignSignature */ -/*************************************************************************/ - -class CodeSignSignature : public CodeSignBlob { - PackedByteArray blob; - -public: - CodeSignSignature(); - - virtual PackedByteArray get_hash_sha1() const override; - virtual PackedByteArray get_hash_sha256() const override; - - virtual int get_size() const override; - virtual uint32_t get_index_type() const override { return 0x00010000; }; - - virtual void write_to_file(Ref p_file) const override; -}; - -/*************************************************************************/ -/* CodeSignSuperBlob */ -/*************************************************************************/ - -class CodeSignSuperBlob { - Vector> blobs; - -public: - bool add_blob(const Ref &p_blob); - - int get_size() const; - void write_to_file(Ref p_file) const; -}; - -/*************************************************************************/ -/* CodeSign */ -/*************************************************************************/ - -class CodeSign { - static PackedByteArray file_hash_sha1(const String &p_path); - static PackedByteArray file_hash_sha256(const String &p_path); - static Error _codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg); - -public: - static Error codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg); -}; - -#endif // MODULE_REGEX_ENABLED - -#endif // CODESIGN_H diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp deleted file mode 100644 index bd35b39e9e..0000000000 --- a/platform/osx/export/export.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/*************************************************************************/ -/* export.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "export.h" - -#include "export_plugin.h" - -void register_osx_exporter() { - EDITOR_DEF("export/macos/force_builtin_codesign", false); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE)); - - Ref platform; - platform.instantiate(); - - EditorExport::get_singleton()->add_export_platform(platform); -} diff --git a/platform/osx/export/export.h b/platform/osx/export/export.h deleted file mode 100644 index b386337a09..0000000000 --- a/platform/osx/export/export.h +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************/ -/* export.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 OSX_EXPORT_H -#define OSX_EXPORT_H - -void register_osx_exporter(); - -#endif // OSX_EXPORT_H diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp deleted file mode 100644 index a22d7e5e3d..0000000000 --- a/platform/osx/export/export_plugin.cpp +++ /dev/null @@ -1,1673 +0,0 @@ -/*************************************************************************/ -/* export_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "export_plugin.h" - -#include "codesign.h" - -#include "editor/editor_node.h" -#include "editor/editor_paths.h" - -#include "modules/modules_enabled.gen.h" // For regex. - -void EditorExportPlatformOSX::get_preset_features(const Ref &p_preset, List *r_features) { - if (p_preset->get("texture_format/s3tc")) { - r_features->push_back("s3tc"); - } - if (p_preset->get("texture_format/etc")) { - r_features->push_back("etc"); - } - if (p_preset->get("texture_format/etc2")) { - r_features->push_back("etc2"); - } - - r_features->push_back("64"); -} - -bool EditorExportPlatformOSX::get_export_option_visibility(const String &p_option, const HashMap &p_options) const { - // These options are not supported by built-in codesign, used on non macOS host. - if (!OS::get_singleton()->has_feature("macos")) { - if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) { - return false; - } - } - - // These entitlements are required to run managed code, and are always enabled in Mono builds. - if (Engine::get_singleton()->has_singleton("GodotSharp")) { - if (p_option == "codesign/entitlements/allow_jit_code_execution" || p_option == "codesign/entitlements/allow_unsigned_executable_memory" || p_option == "codesign/entitlements/allow_dyld_environment_variables") { - return false; - } - } - return true; -} - -void EditorExportPlatformOSX::get_export_options(List *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::INT, "debug/export_console_script", PROPERTY_HINT_ENUM, "No,Debug Only,Debug and Release"), 1)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); - 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/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games")); - 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")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/location"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/address_book"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/calendars"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false)); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_bluetooth"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_downloads", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array())); - - 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"), "")); - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); -} - -void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector &p_source, Vector &p_dest) { - int src_len = p_size * p_size; - - Vector result; - result.resize(src_len * 1.25); //temp vector for rle encoded data, make it 25% larger for worst case scenario - int res_size = 0; - - uint8_t buf[128]; - int buf_size = 0; - - int i = 0; - while (i < src_len) { - uint8_t cur = p_source.ptr()[i * 4 + p_ch]; - - if (i < src_len - 2) { - 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); - memcpy(&result.write[res_size], &buf, buf_size); - res_size += buf_size; - buf_size = 0; - } - - uint8_t lim = i + 130 >= src_len ? src_len - i - 1 : 130; - bool hit_lim = true; - - for (int j = 3; j <= lim; j++) { - 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); - result.write[res_size++] = cur; - break; - } - } - if (hit_lim) { - result.write[res_size++] = (uint8_t)(lim - 3 + 0x80); - result.write[res_size++] = cur; - i = i + lim; - } - } else { - buf[buf_size++] = cur; - if (buf_size == 128) { - result.write[res_size++] = (uint8_t)(buf_size - 1); - memcpy(&result.write[res_size], &buf, buf_size); - res_size += buf_size; - buf_size = 0; - } - } - } else { - buf[buf_size++] = cur; - result.write[res_size++] = (uint8_t)(buf_size - 1); - memcpy(&result.write[res_size], &buf, buf_size); - res_size += buf_size; - buf_size = 0; - } - - i++; - } - - int ofs = p_dest.size(); - p_dest.resize(p_dest.size() + res_size); - memcpy(&p_dest.write[ofs], result.ptr(), res_size); -} - -void EditorExportPlatformOSX::_make_icon(const Ref &p_icon, Vector &p_data) { - Ref it = memnew(ImageTexture); - - Vector data; - - data.resize(8); - data.write[0] = 'i'; - data.write[1] = 'c'; - data.write[2] = 'n'; - data.write[3] = 's'; - - struct MacOSIconInfo { - const char *name; - const char *mask_name; - bool is_png; - int size; - }; - - static const MacOSIconInfo icon_infos[] = { - { "ic10", "", true, 1024 }, //1024×1024 32-bit PNG and 512×512@2x 32-bit "retina" PNG - { "ic09", "", true, 512 }, //512×512 32-bit PNG - { "ic14", "", true, 512 }, //256×256@2x 32-bit "retina" PNG - { "ic08", "", true, 256 }, //256×256 32-bit PNG - { "ic13", "", true, 256 }, //128×128@2x 32-bit "retina" PNG - { "ic07", "", true, 128 }, //128×128 32-bit PNG - { "ic12", "", true, 64 }, //32×32@2× 32-bit "retina" PNG - { "ic11", "", true, 32 }, //16×16@2× 32-bit "retina" PNG - { "il32", "l8mk", false, 32 }, //32×32 24-bit RLE + 8-bit uncompressed mask - { "is32", "s8mk", false, 16 } //16×16 24-bit RLE + 8-bit uncompressed mask - }; - - for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { - Ref copy = p_icon; // does this make sense? doesn't this just increase the reference count instead of making a copy? Do we even need a copy? - copy->convert(Image::FORMAT_RGBA8); - copy->resize(icon_infos[i].size, icon_infos[i].size); - - if (icon_infos[i].is_png) { - // Encode PNG icon. - it->set_image(copy); - String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png"); - ResourceSaver::save(path, it); - - { - Ref f = FileAccess::open(path, FileAccess::READ); - if (f.is_null()) { - // Clean up generated file. - DirAccess::remove_file_or_error(path); - add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not open icon file \"%s\"."), path)); - return; - } - - int ofs = data.size(); - uint64_t len = f->get_length(); - data.resize(data.size() + len + 8); - f->get_buffer(&data.write[ofs + 8], len); - len += 8; - len = BSWAP32(len); - memcpy(&data.write[ofs], icon_infos[i].name, 4); - encode_uint32(len, &data.write[ofs + 4]); - } - - // Clean up generated file. - DirAccess::remove_file_or_error(path); - - } else { - Vector src_data = copy->get_data(); - - //encode 24bit RGB RLE icon - { - int ofs = data.size(); - data.resize(data.size() + 8); - - _rgba8_to_packbits_encode(0, icon_infos[i].size, src_data, data); // encode R - _rgba8_to_packbits_encode(1, icon_infos[i].size, src_data, data); // encode G - _rgba8_to_packbits_encode(2, icon_infos[i].size, src_data, data); // encode B - - int len = data.size() - ofs; - len = BSWAP32(len); - memcpy(&data.write[ofs], icon_infos[i].name, 4); - encode_uint32(len, &data.write[ofs + 4]); - } - - //encode 8bit mask uncompressed icon - { - int ofs = data.size(); - int len = copy->get_width() * copy->get_height(); - data.resize(data.size() + len + 8); - - for (int j = 0; j < len; j++) { - data.write[ofs + 8 + j] = src_data.ptr()[j * 4 + 3]; - } - len += 8; - len = BSWAP32(len); - memcpy(&data.write[ofs], icon_infos[i].mask_name, 4); - encode_uint32(len, &data.write[ofs + 4]); - } - } - } - - uint32_t total_len = data.size(); - total_len = BSWAP32(total_len); - encode_uint32(total_len, &data.write[4]); - - p_data = data; -} - -void EditorExportPlatformOSX::_fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary) { - String str; - String strnew; - str.parse_utf8((const char *)plist.ptr(), plist.size()); - Vector lines = str.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].find("$binary") != -1) { - strnew += lines[i].replace("$binary", p_binary) + "\n"; - } else if (lines[i].find("$name") != -1) { - strnew += lines[i].replace("$name", ProjectSettings::get_singleton()->get("application/config/name")) + "\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) { - strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; - } else if (lines[i].find("$signature") != -1) { - strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; - } else if (lines[i].find("$app_category") != -1) { - String cat = p_preset->get("application/app_category"); - strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; - } else if (lines[i].find("$copyright") != -1) { - strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; - } else if (lines[i].find("$highres") != -1) { - strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t" : "\t") + "\n"; - } else if (lines[i].find("$usage_descriptions") != -1) { - String descriptions; - if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { - descriptions += "\tNSMicrophoneUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/microphone_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { - descriptions += "\tNSCameraUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/camera_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) { - descriptions += "\tNSLocationUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/location_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { - descriptions += "\tNSContactsUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/address_book_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { - descriptions += "\tNSCalendarsUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/calendar_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { - descriptions += "\tNSPhotoLibraryUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/photos_library_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) { - descriptions += "\tNSDesktopFolderUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/desktop_folder_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) { - descriptions += "\tNSDocumentsFolderUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/documents_folder_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) { - descriptions += "\tNSDownloadsFolderUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/downloads_folder_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) { - descriptions += "\tNSNetworkVolumesUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/network_volumes_usage_description") + "\n"; - } - if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) { - descriptions += "\tNSRemovableVolumesUsageDescription\n"; - descriptions += "\t" + (String)p_preset->get("privacy/removable_volumes_usage_description") + "\n"; - } - if (!descriptions.is_empty()) { - strnew += lines[i].replace("$usage_descriptions", descriptions); - } - } else { - strnew += lines[i] + "\n"; - } - } - - CharString cs = strnew.utf8(); - plist.resize(cs.size() - 1); - for (int i = 0; i < cs.size() - 1; i++) { - plist.write[i] = cs[i]; - } -} - -/** - * If we're running the OSX version of the Godot editor we'll: - * - export our application bundle to a temporary folder - * - attempt to code sign it - * - and then wrap it up in a DMG - */ - -Error EditorExportPlatformOSX::_notarize(const Ref &p_preset, const String &p_path) { -#ifdef OSX_ENABLED - List 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, &str, nullptr, true); - if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); - return err; - } - - print_verbose("altool (" + p_path + "):\n" + str); - int rq_offset = str.find("RequestUUID"); - if (rq_offset == -1) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed.")); - return FAILED; - } else { - int next_nl = str.find("\n", rq_offset); - String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u -p \""); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple \""); - } - -#endif - - return OK; -} - -Error EditorExportPlatformOSX::_code_sign(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) { - bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign"); - bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-"); - - if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) { - print_verbose("using built-in codesign..."); -#ifdef MODULE_REGEX_ENABLED - -#ifdef OSX_ENABLED - if (p_preset->get("codesign/timestamp") && p_warn) { - add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); - } - if (p_preset->get("codesign/hardened_runtime") && p_warn) { - add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); - } -#endif - - String error_msg; - Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg); - if (err != OK) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg)); - return FAILED; - } -#else - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module.")); -#endif - return OK; - } else { - print_verbose("using external codesign..."); - List args; - if (p_preset->get("codesign/timestamp")) { - if (ad_hoc) { - if (p_warn) { - add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); - } - } else { - args.push_back("--timestamp"); - } - } - if (p_preset->get("codesign/hardened_runtime")) { - if (ad_hoc) { - if (p_warn) { - add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); - } - } else { - args.push_back("--options"); - args.push_back("runtime"); - } - } - - if (p_path.get_extension() != "dmg") { - args.push_back("--entitlements"); - args.push_back(p_ent_path); - } - - 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.is_empty()) { - args.push_back(user_arg); - } - } - - args.push_back("-s"); - if (ad_hoc) { - args.push_back("-"); - } else { - args.push_back(p_preset->get("codesign/identity")); - } - - args.push_back("-v"); /* provide some more feedback */ - - if (p_preset->get("codesign/replace_existing_signature")) { - args.push_back("-f"); - } - - args.push_back(p_path); - - String str; - Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true); - if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed.")); - return err; - } - - print_verbose("codesign (" + p_path + "):\n" + str); - if (str.find("no identity found") != -1) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); - return FAILED; - } - if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file.")); - return FAILED; - } - return OK; - } -} - -Error EditorExportPlatformOSX::_code_sign_directory(const Ref &p_preset, const String &p_path, - const String &p_ent_path, bool p_should_error_on_non_code) { -#ifdef OSX_ENABLED - static Vector extensions_to_sign; - - if (extensions_to_sign.is_empty()) { - extensions_to_sign.push_back("dylib"); - extensions_to_sign.push_back("framework"); - } - - Error dir_access_error; - Ref dir_access{ DirAccess::open(p_path, &dir_access_error) }; - - if (dir_access_error != OK) { - return dir_access_error; - } - - dir_access->list_dir_begin(); - String current_file{ dir_access->get_next() }; - while (!current_file.is_empty()) { - String current_file_path{ p_path.plus_file(current_file) }; - - if (current_file == ".." || current_file == ".") { - current_file = dir_access->get_next(); - continue; - } - - if (extensions_to_sign.find(current_file.get_extension()) > -1) { - Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path, false) }; - if (code_sign_error != OK) { - return code_sign_error; - } - } else if (dir_access->current_is_dir()) { - Error code_sign_error{ _code_sign_directory(p_preset, current_file_path, p_ent_path, p_should_error_on_non_code) }; - if (code_sign_error != OK) { - return code_sign_error; - } - } else if (p_should_error_on_non_code) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign file %s."), current_file)); - return Error::FAILED; - } - - current_file = dir_access->get_next(); - } -#endif - - return OK; -} - -Error EditorExportPlatformOSX::_copy_and_sign_files(Ref &dir_access, const String &p_src_path, - const String &p_in_app_path, bool p_sign_enabled, - const Ref &p_preset, const String &p_ent_path, - bool p_should_error_on_non_code_sign) { - Error err{ OK }; - if (dir_access->dir_exists(p_src_path)) { -#ifndef UNIX_ENABLED - add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file())); -#endif - print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path); - err = dir_access->make_dir_recursive(p_in_app_path); - if (err == OK) { - err = dir_access->copy_dir(p_src_path, p_in_app_path, -1, true); - } - } else { - print_verbose("export dylib: " + p_src_path + " -> " + p_in_app_path); - err = dir_access->copy(p_src_path, p_in_app_path); - } - if (err == OK && p_sign_enabled) { - if (dir_access->dir_exists(p_src_path) && p_src_path.get_extension().is_empty()) { - // If it is a directory, find and sign all dynamic libraries. - err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign); - } else { - err = _code_sign(p_preset, p_in_app_path, p_ent_path, false); - } - } - return err; -} - -Error EditorExportPlatformOSX::_export_osx_plugins_for(Ref p_editor_export_plugin, - const String &p_app_path_name, Ref &dir_access, - bool p_sign_enabled, const Ref &p_preset, - const String &p_ent_path) { - Error error{ OK }; - const Vector &osx_plugins{ p_editor_export_plugin->get_osx_plugin_files() }; - for (int i = 0; i < osx_plugins.size(); ++i) { - String src_path{ ProjectSettings::get_singleton()->globalize_path(osx_plugins[i]) }; - String path_in_app{ p_app_path_name + "/Contents/PlugIns/" + src_path.get_file() }; - error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, false); - if (error != OK) { - break; - } - } - return error; -} - -Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { - List args; - - if (FileAccess::exists(p_dmg_path)) { - OS::get_singleton()->move_to_trash(p_dmg_path); - } - - args.push_back("create"); - args.push_back(p_dmg_path); - args.push_back("-volname"); - args.push_back(p_pkg_name); - args.push_back("-fs"); - args.push_back("HFS+"); - args.push_back("-srcfolder"); - args.push_back(p_app_path_name); - - String str; - Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("Could not start hdiutil executable.")); - return err; - } - - print_verbose("hdiutil returned: " + str); - if (str.find("create failed") != -1) { - if (str.find("File exists") != -1) { - add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed - file exists.")); - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed.")); - } - return FAILED; - } - - return OK; -} - -Error EditorExportPlatformOSX::_export_debug_script(const Ref &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { - Ref f = FileAccess::open(p_path, FileAccess::WRITE); - if (f.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), vformat(TTR("Could not open file \"%s\"."), p_path)); - return ERR_CANT_CREATE; - } - - f->store_line("#!/bin/sh"); - f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'"); - f->store_line("function realpath() { python -c \"import os,sys; print(os.path.realpath(sys.argv[1]))\" \"$0\"; }"); - f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\""); - f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\""); - - return OK; -} - -Error EditorExportPlatformOSX::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { - ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - - String src_pkg_name; - - EditorProgress ep("export", "Exporting for OSX", 3, true); - - if (p_debug) { - src_pkg_name = p_preset->get("custom_template/debug"); - } else { - src_pkg_name = p_preset->get("custom_template/release"); - } - - if (src_pkg_name.is_empty()) { - String err; - src_pkg_name = find_export_template("osx.zip", &err); - if (src_pkg_name.is_empty()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); - return ERR_FILE_NOT_FOUND; - } - } - - if (!DirAccess::exists(p_path.get_base_dir())) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("The given export path doesn't exist.")); - return ERR_FILE_BAD_PATH; - } - - Ref io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); - - if (ep.step(TTR("Creating app bundle"), 0)) { - return ERR_SKIP; - } - - unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); - if (!src_pkg_zip) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not find template app to export: \"%s\"."), src_pkg_name)); - return ERR_FILE_NOT_FOUND; - } - - int ret = unzGoToFirstFile(src_pkg_zip); - - String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64"; - - String pkg_name; - if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { - pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); - } else { - pkg_name = "Unnamed"; - } - - pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); - - String export_format; - if (use_dmg() && p_path.ends_with("dmg")) { - export_format = "dmg"; - } else if (p_path.ends_with("zip")) { - export_format = "zip"; - } else if (p_path.ends_with("app")) { - export_format = "app"; - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); - return ERR_CANT_CREATE; - } - - // Create our application bundle. - String tmp_app_dir_name = pkg_name + ".app"; - String tmp_base_path_name; - String tmp_app_path_name; - String scr_path; - if (export_format == "app") { - tmp_base_path_name = p_path.get_base_dir(); - tmp_app_path_name = p_path; - scr_path = p_path.get_basename() + ".command"; - } else { - tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name); - tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name); - scr_path = tmp_base_path_name.plus_file(pkg_name + ".command"); - } - - print_verbose("Exporting to " + tmp_app_path_name); - - Error err = OK; - - Ref tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name); - if (tmp_app_dir.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory: \"%s\"."), tmp_base_path_name)); - err = ERR_CANT_CREATE; - } - - DirAccess::remove_file_or_error(scr_path); - if (DirAccess::exists(tmp_app_path_name)) { - String old_dir = tmp_app_dir->get_current_dir(); - if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { - tmp_app_dir->erase_contents_recursive(); - tmp_app_dir->change_dir(old_dir); - } - } - - Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables"); - - // Create our folder structure. - if (err == OK) { - print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS"); - err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/MacOS")); - } - } - - if (err == OK) { - print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks"); - err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Frameworks")); - } - } - - if ((err == OK) && helpers.size() > 0) { - print_line("Creating " + tmp_app_path_name + "/Contents/Helpers"); - err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Helpers"); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Helpers")); - } - } - - if (err == OK) { - print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources"); - err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Resources")); - } - } - - Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); - Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); - Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); - Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized"); - Dictionary address_book_usage_descriptions = p_preset->get("privacy/address_book_usage_description_localized"); - Dictionary calendar_usage_descriptions = p_preset->get("privacy/calendar_usage_description_localized"); - Dictionary photos_library_usage_descriptions = p_preset->get("privacy/photos_library_usage_description_localized"); - Dictionary desktop_folder_usage_descriptions = p_preset->get("privacy/desktop_folder_usage_description_localized"); - Dictionary documents_folder_usage_descriptions = p_preset->get("privacy/documents_folder_usage_description_localized"); - Dictionary downloads_folder_usage_descriptions = p_preset->get("privacy/downloads_folder_usage_description_localized"); - Dictionary network_volumes_usage_descriptions = p_preset->get("privacy/network_volumes_usage_description_localized"); - Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized"); - Dictionary copyrights = p_preset->get("application/copyright_localized"); - - Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); - if (translations.size() > 0) { - { - String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; - tmp_app_dir->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); - if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { - f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { - f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) { - f->store_line("NSLocationUsageDescription = \"" + p_preset->get("privacy/location_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { - f->store_line("NSContactsUsageDescription = \"" + p_preset->get("privacy/address_book_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { - f->store_line("NSCalendarsUsageDescription = \"" + p_preset->get("privacy/calendar_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { - f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photos_library_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) { - f->store_line("NSDesktopFolderUsageDescription = \"" + p_preset->get("privacy/desktop_folder_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) { - f->store_line("NSDocumentsFolderUsageDescription = \"" + p_preset->get("privacy/documents_folder_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) { - f->store_line("NSDownloadsFolderUsageDescription = \"" + p_preset->get("privacy/downloads_folder_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) { - f->store_line("NSNetworkVolumesUsageDescription = \"" + p_preset->get("privacy/network_volumes_usage_description").operator String() + "\";"); - } - if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) { - f->store_line("NSRemovableVolumesUsageDescription = \"" + p_preset->get("privacy/removable_volumes_usage_description").operator String() + "\";"); - } - f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";"); - } - - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid()) { - String lang = tr->get_locale(); - String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; - tmp_app_dir->make_dir_recursive(fname); - Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - f->store_line("/* Localized versions of Info.plist keys */"); - f->store_line(""); - if (appnames.has(lang)) { - f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); - } - if (microphone_usage_descriptions.has(lang)) { - f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); - } - if (camera_usage_descriptions.has(lang)) { - f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); - } - if (location_usage_descriptions.has(lang)) { - f->store_line("NSLocationUsageDescription = \"" + location_usage_descriptions[lang].operator String() + "\";"); - } - if (address_book_usage_descriptions.has(lang)) { - f->store_line("NSContactsUsageDescription = \"" + address_book_usage_descriptions[lang].operator String() + "\";"); - } - if (calendar_usage_descriptions.has(lang)) { - f->store_line("NSCalendarsUsageDescription = \"" + calendar_usage_descriptions[lang].operator String() + "\";"); - } - if (photos_library_usage_descriptions.has(lang)) { - f->store_line("NSPhotoLibraryUsageDescription = \"" + photos_library_usage_descriptions[lang].operator String() + "\";"); - } - if (desktop_folder_usage_descriptions.has(lang)) { - f->store_line("NSDesktopFolderUsageDescription = \"" + desktop_folder_usage_descriptions[lang].operator String() + "\";"); - } - if (documents_folder_usage_descriptions.has(lang)) { - f->store_line("NSDocumentsFolderUsageDescription = \"" + documents_folder_usage_descriptions[lang].operator String() + "\";"); - } - if (downloads_folder_usage_descriptions.has(lang)) { - f->store_line("NSDownloadsFolderUsageDescription = \"" + downloads_folder_usage_descriptions[lang].operator String() + "\";"); - } - if (network_volumes_usage_descriptions.has(lang)) { - f->store_line("NSNetworkVolumesUsageDescription = \"" + network_volumes_usage_descriptions[lang].operator String() + "\";"); - } - if (removable_volumes_usage_descriptions.has(lang)) { - f->store_line("NSRemovableVolumesUsageDescription = \"" + removable_volumes_usage_descriptions[lang].operator String() + "\";"); - } - if (copyrights.has(lang)) { - f->store_line("NSHumanReadableCopyright = \"" + copyrights[lang].operator String() + "\";"); - } - } - } - } - - // Now process our template. - bool found_binary = false; - Vector dylibs_found; - - while (ret == UNZ_OK && err == OK) { - bool is_execute = false; - - // Get filename. - unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); - if (ret != UNZ_OK) { - break; - } - - String file = String::utf8(fname); - - Vector data; - data.resize(info.uncompressed_size); - - // Read. - unzOpenCurrentFile(src_pkg_zip); - unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); - unzCloseCurrentFile(src_pkg_zip); - - // Write. - file = file.replace_first("osx_template.app/", ""); - - if (((info.external_fa >> 16L) & 0120000) == 0120000) { -#ifndef UNIX_ENABLED - add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!")); -#endif - // Handle symlinks in the archive. - file = tmp_app_path_name.plus_file(file); - if (err == OK) { - err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); - } - } - if (err == OK) { - String lnk_data = String::utf8((const char *)data.ptr(), data.size()); - err = tmp_app_dir->create_link(lnk_data, file); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not created symlink \"%s\" -> \"%s\"."), lnk_data, file)); - } - print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data)); - } - - ret = unzGoToNextFile(src_pkg_zip); - continue; // next - } - - if (file == "Contents/Info.plist") { - _fix_plist(p_preset, data, pkg_name); - } - - if (file.begins_with("Contents/MacOS/godot_")) { - if (file != "Contents/MacOS/" + binary_to_use) { - ret = unzGoToNextFile(src_pkg_zip); - continue; // skip - } - found_binary = true; - is_execute = true; - file = "Contents/MacOS/" + pkg_name; - } - - if (file == "Contents/Resources/icon.icns") { - // See if there is an icon. - String iconpath; - if (p_preset->get("application/icon") != "") { - iconpath = p_preset->get("application/icon"); - } else { - iconpath = ProjectSettings::get_singleton()->get("application/config/icon"); - } - - if (!iconpath.is_empty()) { - if (iconpath.get_extension() == "icns") { - Ref icon = FileAccess::open(iconpath, FileAccess::READ); - if (icon.is_valid()) { - data.resize(icon->get_length()); - icon->get_buffer(&data.write[0], icon->get_length()); - } - } else { - Ref icon; - icon.instantiate(); - icon->load(iconpath); - if (!icon->is_empty()) { - _make_icon(icon, data); - } - } - } - } - - if (data.size() > 0) { - if (file.find("/data.mono.osx.64.release_debug/") != -1) { - if (!p_debug) { - ret = unzGoToNextFile(src_pkg_zip); - continue; // skip - } - file = file.replace("/data.mono.osx.64.release_debug/", "/GodotSharp/"); - } - if (file.find("/data.mono.osx.64.release/") != -1) { - if (p_debug) { - ret = unzGoToNextFile(src_pkg_zip); - continue; // skip - } - file = file.replace("/data.mono.osx.64.release/", "/GodotSharp/"); - } - - if (file.ends_with(".dylib")) { - dylibs_found.push_back(file); - } - - print_verbose("ADDING: " + file + " size: " + itos(data.size())); - - // Write it into our application bundle. - file = tmp_app_path_name.plus_file(file); - if (err == OK) { - err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); - } - } - if (err == OK) { - Ref f = FileAccess::open(file, FileAccess::WRITE); - if (f.is_valid()) { - f->store_buffer(data.ptr(), data.size()); - f.unref(); - if (is_execute) { - // chmod with 0755 if the file is executable. - FileAccess::set_unix_permissions(file, 0755); - } - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file)); - err = ERR_CANT_CREATE; - } - } - } - - ret = unzGoToNextFile(src_pkg_zip); - } - - // We're done with our source zip. - unzClose(src_pkg_zip); - - if (!found_binary) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template binary \"%s\" not found. It might be missing from your template archive."), binary_to_use)); - err = ERR_FILE_NOT_FOUND; - } - - // Save console script. - if (err == OK) { - int con_scr = p_preset->get("debug/export_console_script"); - if ((con_scr == 1 && p_debug) || (con_scr == 2)) { - err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path); - FileAccess::set_unix_permissions(scr_path, 0755); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console script.")); - } - } - } - - if (err == OK) { - if (ep.step(TTR("Making PKG"), 1)) { - return ERR_SKIP; - } - - String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; - Vector shared_objects; - err = save_pack(p_preset, p_debug, pack_path, &shared_objects); - - // See if we can code sign our new package. - bool sign_enabled = p_preset->get("codesign/enable"); - - String ent_path = p_preset->get("codesign/entitlements/custom_file"); - String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements"); - if (sign_enabled && (ent_path.is_empty())) { - ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); - - Ref ent_f = FileAccess::open(ent_path, FileAccess::WRITE); - if (ent_f.is_valid()) { - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line(""); - if (Engine::get_singleton()->has_singleton("GodotSharp")) { - // These entitlements are required to run managed code, and are always enabled in Mono builds. - ent_f->store_line("com.apple.security.cs.allow-jit"); - ent_f->store_line(""); - ent_f->store_line("com.apple.security.cs.allow-unsigned-executable-memory"); - ent_f->store_line(""); - ent_f->store_line("com.apple.security.cs.allow-dyld-environment-variables"); - ent_f->store_line(""); - } else { - if ((bool)p_preset->get("codesign/entitlements/allow_jit_code_execution")) { - ent_f->store_line("com.apple.security.cs.allow-jit"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/allow_unsigned_executable_memory")) { - ent_f->store_line("com.apple.security.cs.allow-unsigned-executable-memory"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/allow_dyld_environment_variables")) { - ent_f->store_line("com.apple.security.cs.allow-dyld-environment-variables"); - ent_f->store_line(""); - } - } - - if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) { - ent_f->store_line("com.apple.security.cs.disable-library-validation"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/audio_input")) { - ent_f->store_line("com.apple.security.device.audio-input"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/camera")) { - ent_f->store_line("com.apple.security.device.camera"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/location")) { - ent_f->store_line("com.apple.security.personal-information.location"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/address_book")) { - ent_f->store_line("com.apple.security.personal-information.addressbook"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/calendars")) { - ent_f->store_line("com.apple.security.personal-information.calendars"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/photos_library")) { - ent_f->store_line("com.apple.security.personal-information.photos-library"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/apple_events")) { - ent_f->store_line("com.apple.security.automation.apple-events"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/debugging")) { - ent_f->store_line("com.apple.security.get-task-allow"); - ent_f->store_line(""); - } - - if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { - ent_f->store_line("com.apple.security.app-sandbox"); - ent_f->store_line(""); - - if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_server")) { - ent_f->store_line("com.apple.security.network.server"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_client")) { - ent_f->store_line("com.apple.security.network.client"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_usb")) { - ent_f->store_line("com.apple.security.device.usb"); - ent_f->store_line(""); - } - if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_bluetooth")) { - ent_f->store_line("com.apple.security.device.bluetooth"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 1) { - ent_f->store_line("com.apple.security.files.downloads.read-only"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 2) { - ent_f->store_line("com.apple.security.files.downloads.read-write"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 1) { - ent_f->store_line("com.apple.security.files.pictures.read-only"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 2) { - ent_f->store_line("com.apple.security.files.pictures.read-write"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 1) { - ent_f->store_line("com.apple.security.files.music.read-only"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 2) { - ent_f->store_line("com.apple.security.files.music.read-write"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 1) { - ent_f->store_line("com.apple.security.files.movies.read-only"); - ent_f->store_line(""); - } - if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 2) { - ent_f->store_line("com.apple.security.files.movies.read-write"); - ent_f->store_line(""); - } - } - - ent_f->store_line(""); - ent_f->store_line(""); - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create entitlements file.")); - err = ERR_CANT_CREATE; - } - - if ((err == OK) && helpers.size() > 0) { - ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE); - if (ent_f.is_valid()) { - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line("com.apple.security.app-sandbox"); - ent_f->store_line(""); - ent_f->store_line("com.apple.security.inherit"); - ent_f->store_line(""); - ent_f->store_line(""); - ent_f->store_line(""); - } else { - add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create helper entitlements file.")); - err = ERR_CANT_CREATE; - } - } - } - - if ((err == OK) && helpers.size() > 0) { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < helpers.size(); i++) { - String hlp_path = helpers[i]; - err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); - if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false); - } - FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); - } - } - - bool ad_hoc = true; - if (err == OK) { -#ifdef OSX_ENABLED - String sign_identity = p_preset->get("codesign/identity"); -#else - String sign_identity = "-"; -#endif - ad_hoc = (sign_identity == "" || sign_identity == "-"); - bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); - if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); - err = ERR_CANT_CREATE; - } - } - - if (err == OK) { - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < shared_objects.size(); i++) { - String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); - if (shared_objects[i].target.is_empty()) { - String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(); - err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true); - } else { - String path_in_app = tmp_app_path_name.plus_file(shared_objects[i].target).plus_file(src_path.get_file()); - err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false); - } - if (err != OK) { - break; - } - } - - Vector> export_plugins{ EditorExport::get_singleton()->get_export_plugins() }; - for (int i = 0; i < export_plugins.size(); ++i) { - err = _export_osx_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path); - if (err != OK) { - break; - } - } - } - - if (sign_enabled) { - for (int i = 0; i < dylibs_found.size(); i++) { - if (err == OK) { - err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path, false); - } - } - } - - if (err == OK && sign_enabled) { - if (ep.step(TTR("Code signing bundle"), 2)) { - return ERR_SKIP; - } - err = _code_sign(p_preset, tmp_app_path_name, ent_path); - } - - if (export_format == "dmg") { - // Create a DMG. - if (err == OK) { - if (ep.step(TTR("Making DMG"), 3)) { - return ERR_SKIP; - } - err = _create_dmg(p_path, pkg_name, tmp_base_path_name); - } - // Sign DMG. - if (err == OK && sign_enabled && !ad_hoc) { - if (ep.step(TTR("Code signing DMG"), 3)) { - return ERR_SKIP; - } - err = _code_sign(p_preset, p_path, ent_path, false); - } - } else if (export_format == "zip") { - // Create ZIP. - if (err == OK) { - if (ep.step(TTR("Making ZIP"), 3)) { - return ERR_SKIP; - } - if (FileAccess::exists(p_path)) { - OS::get_singleton()->move_to_trash(p_path); - } - - Ref io_fa_dst; - zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); - zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); - - _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name); - - zipClose(zip, nullptr); - } - } - -#ifdef OSX_ENABLED - bool noto_enabled = p_preset->get("notarization/enable"); - if (err == OK && noto_enabled) { - if (export_format == "app") { - add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); - } else { - if (ep.step(TTR("Sending archive for notarization"), 4)) { - return ERR_SKIP; - } - err = _notarize(p_preset, p_path); - } - } -#endif - - // Clean up temporary entitlements files. - DirAccess::remove_file_or_error(hlp_ent_path); - - // Clean up temporary .app dir and generated entitlements. - if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") { - tmp_app_dir->remove(ent_path); - } - if (export_format != "app") { - if (tmp_app_dir->change_dir(tmp_base_path_name) == OK) { - tmp_app_dir->erase_contents_recursive(); - tmp_app_dir->change_dir(".."); - tmp_app_dir->remove(pkg_name); - } - } - } - - return err; -} - -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_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder); - - Ref da = DirAccess::open(dir); - da->list_dir_begin(); - String f = da->get_next(); - while (!f.is_empty()) { - if (f == "." || f == "..") { - f = da->get_next(); - continue; - } - if (da->is_link(f)) { - 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.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ - zipfi.tmz_date.tm_sec = time.second; - zipfi.tmz_date.tm_year = date.year; - zipfi.dosDate = 0; - // 0120000: symbolic link type - // 0000755: permissions rwxr-xr-x - // 0000644: permissions rw-r--r-- - uint32_t _mode = 0120644; - 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); - - String target = da->read_link(f); - zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); - zipCloseFileInZip(p_zip); - } else 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)) || p_folder.ends_with("Helpers") || f.ends_with(".command"); - - 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.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ - zipfi.tmz_date.tm_sec = time.second; - 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); - - Ref fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); - if (fa.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); - return; - } - const int bufsize = 16384; - uint8_t buf[bufsize]; - - while (true) { - uint64_t got = fa->get_buffer(buf, bufsize); - if (got == 0) { - break; - } - zipWriteInFileInZip(p_zip, buf, got); - } - - zipCloseFileInZip(p_zip); - } - f = da->get_next(); - } - da->list_dir_end(); -} - -bool EditorExportPlatformOSX::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { - String err; - bool valid = false; - - // Look for export templates (custom templates). - 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"; - } - } - 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"; - } - } - - // Look for export templates (official templates, check only is custom templates are not set). - if (!dvalid || !rvalid) { - dvalid = exists_export_template("osx.zip", &err); - rvalid = dvalid; // Both in the same ZIP. - } - - valid = dvalid || rvalid; - r_missing_templates = !valid; - - 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"); - -#ifdef OSX_ENABLED - bool noto_enabled = p_preset->get("notarization/enable"); - bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-")); - - if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) { - err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n"; - } - if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) { - err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n"; - } - - if (noto_enabled) { - if (ad_hoc) { - err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n"; - valid = false; - } - if (!sign_enabled) { - err += TTR("Notarization: Code signing is required for notarization.") + "\n"; - valid = false; - } - if (!(bool)p_preset->get("codesign/hardened_runtime")) { - err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n"; - valid = false; - } - if (!(bool)p_preset->get("codesign/timestamp")) { - err += TTR("Notarization: Timestamping is required for notarization.") + "\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; - } - } else { - err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; - if (!sign_enabled) { - err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; - } else { - if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) { - err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n"; - } - if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) { - err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n"; - } - } - } -#else - err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; - if (!sign_enabled) { - err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; - } -#endif - - if (sign_enabled) { - if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { - err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/camera") && ((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { - err += TTR("Privacy: Camera access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/location") && ((String)p_preset->get("privacy/location_usage_description")).is_empty()) { - err += TTR("Privacy: Location information access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/address_book") && ((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { - err += TTR("Privacy: Address book access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/calendars") && ((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { - err += TTR("Privacy: Calendar access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/photos_library") && ((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { - err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - } - - if (!err.is_empty()) { - r_error = err; - } - return valid; -} - -EditorExportPlatformOSX::EditorExportPlatformOSX() { - logo = ImageTexture::create_from_image(memnew(Image(_osx_logo))); -} - -EditorExportPlatformOSX::~EditorExportPlatformOSX() { -} diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h deleted file mode 100644 index ec97d4139f..0000000000 --- a/platform/osx/export/export_plugin.h +++ /dev/null @@ -1,137 +0,0 @@ -/*************************************************************************/ -/* export_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 OSX_EXPORT_PLUGIN_H -#define OSX_EXPORT_PLUGIN_H - -#include "core/config/project_settings.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/io/marshalls.h" -#include "core/io/resource_saver.h" -#include "core/io/zip_io.h" -#include "core/os/os.h" -#include "core/version.h" -#include "editor/editor_export.h" -#include "editor/editor_settings.h" -#include "platform/osx/logo.gen.h" - -#include - -class EditorExportPlatformOSX : public EditorExportPlatform { - GDCLASS(EditorExportPlatformOSX, EditorExportPlatform); - - int version_code = 0; - - Ref logo; - - void _fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary); - void _make_icon(const Ref &p_icon, Vector &p_data); - - Error _notarize(const Ref &p_preset, const String &p_path); - Error _code_sign(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true); - Error _code_sign_directory(const Ref &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true); - Error _copy_and_sign_files(Ref &dir_access, const String &p_src_path, const String &p_in_app_path, - bool p_sign_enabled, const Ref &p_preset, const String &p_ent_path, - bool p_should_error_on_non_code_sign); - Error _export_osx_plugins_for(Ref p_editor_export_plugin, const String &p_app_path_name, - Ref &dir_access, bool p_sign_enabled, const Ref &p_preset, - const String &p_ent_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); - Error _export_debug_script(const Ref &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); - - bool use_codesign() const { return true; } -#ifdef OSX_ENABLED - bool use_dmg() const { return true; } -#else - 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 (!(is_ascii_alphanumeric_char(c) || 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 &p_preset, List *r_features) override; - virtual void get_export_options(List *r_options) override; - virtual bool get_export_option_visibility(const String &p_option, const HashMap &p_options) const override; - -public: - virtual String get_name() const override { return "macOS"; } - virtual String get_os_name() const override { return "macOS"; } - virtual Ref get_logo() const override { return logo; } - - virtual List get_binary_extensions(const Ref &p_preset) const override { - List list; - if (use_dmg()) { - list.push_back("dmg"); - } - list.push_back("zip"); - list.push_back("app"); - return list; - } - virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - - virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; - - virtual void get_platform_features(List *r_features) override { - r_features->push_back("pc"); - r_features->push_back("s3tc"); - r_features->push_back("macos"); - } - - virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { - } - - EditorExportPlatformOSX(); - ~EditorExportPlatformOSX(); -}; - -#endif diff --git a/platform/osx/export/lipo.cpp b/platform/osx/export/lipo.cpp deleted file mode 100644 index 82baf18c52..0000000000 --- a/platform/osx/export/lipo.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/*************************************************************************/ -/* lipo.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. - -#include "lipo.h" - -#ifdef MODULE_REGEX_ENABLED - -bool LipO::is_lipo(const String &p_path) { - Ref fb = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); - uint32_t magic = fb->get_32(); - return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf); -} - -bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_files) { - close(); - - fa = FileAccess::open(p_output_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_output_path)); - - uint64_t max_size = 0; - for (int i = 0; i < p_files.size(); i++) { - MachO mh; - if (!mh.open_file(p_files[i])) { - ERR_FAIL_V_MSG(false, vformat("LipO: Invalid MachO file: \"%s.\"", p_files[i])); - } - - FatArch arch; - arch.cputype = mh.get_cputype(); - arch.cpusubtype = mh.get_cpusubtype(); - arch.offset = 0; - arch.size = mh.get_size(); - arch.align = mh.get_align(); - max_size += arch.size; - - archs.push_back(arch); - - Ref fb = FileAccess::open(p_files[i], FileAccess::READ); - if (fb.is_null()) { - close(); - ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); - } - } - - // Write header. - bool is_64 = (max_size >= std::numeric_limits::max()); - if (is_64) { - fa->store_32(0xbfbafeca); - } else { - fa->store_32(0xbebafeca); - } - fa->store_32(BSWAP32(archs.size())); - uint64_t offset = archs.size() * (is_64 ? 32 : 20) + 8; - for (int i = 0; i < archs.size(); i++) { - archs.write[i].offset = offset + PAD(offset, uint64_t(1) << archs[i].align); - if (is_64) { - fa->store_32(BSWAP32(archs[i].cputype)); - fa->store_32(BSWAP32(archs[i].cpusubtype)); - fa->store_64(BSWAP64(archs[i].offset)); - fa->store_64(BSWAP64(archs[i].size)); - fa->store_32(BSWAP32(archs[i].align)); - fa->store_32(0); - } else { - fa->store_32(BSWAP32(archs[i].cputype)); - fa->store_32(BSWAP32(archs[i].cpusubtype)); - fa->store_32(BSWAP32(archs[i].offset)); - fa->store_32(BSWAP32(archs[i].size)); - fa->store_32(BSWAP32(archs[i].align)); - } - offset = archs[i].offset + archs[i].size; - } - - // Write files and padding. - for (int i = 0; i < archs.size(); i++) { - Ref fb = FileAccess::open(p_files[i], FileAccess::READ); - if (fb.is_null()) { - close(); - ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i])); - } - uint64_t cur = fa->get_position(); - for (uint64_t j = cur; j < archs[i].offset; j++) { - fa->store_8(0); - } - int pages = archs[i].size / 4096; - int remain = archs[i].size % 4096; - unsigned char step[4096]; - for (int j = 0; j < pages; j++) { - uint64_t br = fb->get_buffer(step, 4096); - if (br > 0) { - fa->store_buffer(step, br); - } - } - uint64_t br = fb->get_buffer(step, remain); - if (br > 0) { - fa->store_buffer(step, br); - } - } - return true; -} - -bool LipO::open_file(const String &p_path) { - close(); - - fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); - - uint32_t magic = fa->get_32(); - if (magic == 0xbebafeca) { - // 32-bit fat binary, bswap. - uint32_t nfat_arch = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < nfat_arch; i++) { - FatArch arch; - arch.cputype = BSWAP32(fa->get_32()); - arch.cpusubtype = BSWAP32(fa->get_32()); - arch.offset = BSWAP32(fa->get_32()); - arch.size = BSWAP32(fa->get_32()); - arch.align = BSWAP32(fa->get_32()); - - archs.push_back(arch); - } - } else if (magic == 0xcafebabe) { - // 32-bit fat binary. - uint32_t nfat_arch = fa->get_32(); - for (uint32_t i = 0; i < nfat_arch; i++) { - FatArch arch; - arch.cputype = fa->get_32(); - arch.cpusubtype = fa->get_32(); - arch.offset = fa->get_32(); - arch.size = fa->get_32(); - arch.align = fa->get_32(); - - archs.push_back(arch); - } - } else if (magic == 0xbfbafeca) { - // 64-bit fat binary, bswap. - uint32_t nfat_arch = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < nfat_arch; i++) { - FatArch arch; - arch.cputype = BSWAP32(fa->get_32()); - arch.cpusubtype = BSWAP32(fa->get_32()); - arch.offset = BSWAP64(fa->get_64()); - arch.size = BSWAP64(fa->get_64()); - arch.align = BSWAP32(fa->get_32()); - fa->get_32(); // Skip, reserved. - - archs.push_back(arch); - } - } else if (magic == 0xcafebabf) { - // 64-bit fat binary. - uint32_t nfat_arch = fa->get_32(); - for (uint32_t i = 0; i < nfat_arch; i++) { - FatArch arch; - arch.cputype = fa->get_32(); - arch.cpusubtype = fa->get_32(); - arch.offset = fa->get_64(); - arch.size = fa->get_64(); - arch.align = fa->get_32(); - fa->get_32(); // Skip, reserved. - - archs.push_back(arch); - } - } else { - close(); - ERR_FAIL_V_MSG(false, vformat("LipO: Invalid fat binary: \"%s\".", p_path)); - } - return true; -} - -int LipO::get_arch_count() const { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened."); - return archs.size(); -} - -bool LipO::extract_arch(int p_index, const String &p_path) { - ERR_FAIL_COND_V_MSG(fa.is_null(), false, "LipO: File not opened."); - ERR_FAIL_INDEX_V(p_index, archs.size(), false); - - Ref fb = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path)); - - fa->seek(archs[p_index].offset); - - int pages = archs[p_index].size / 4096; - int remain = archs[p_index].size % 4096; - unsigned char step[4096]; - for (int i = 0; i < pages; i++) { - uint64_t br = fa->get_buffer(step, 4096); - if (br > 0) { - fb->store_buffer(step, br); - } - } - uint64_t br = fa->get_buffer(step, remain); - if (br > 0) { - fb->store_buffer(step, br); - } - return true; -} - -void LipO::close() { - archs.clear(); -} - -LipO::~LipO() { - close(); -} - -#endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/lipo.h b/platform/osx/export/lipo.h deleted file mode 100644 index 0e419be17e..0000000000 --- a/platform/osx/export/lipo.h +++ /dev/null @@ -1,76 +0,0 @@ -/*************************************************************************/ -/* lipo.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -// Universal / Universal 2 fat binary file creator and extractor. - -#ifndef LIPO_H -#define LIPO_H - -#include "core/io/file_access.h" -#include "core/object/ref_counted.h" -#include "modules/modules_enabled.gen.h" // For regex. - -#include "macho.h" - -#ifdef MODULE_REGEX_ENABLED - -class LipO : public RefCounted { - struct FatArch { - uint32_t cputype; - uint32_t cpusubtype; - uint64_t offset; - uint64_t size; - uint32_t align; - }; - - Ref fa; - Vector archs; - - static inline size_t PAD(size_t s, size_t a) { - return (a - s % a); - } - -public: - static bool is_lipo(const String &p_path); - - bool create_file(const String &p_output_path, const PackedStringArray &p_files); - - bool open_file(const String &p_path); - int get_arch_count() const; - bool extract_arch(int p_index, const String &p_path); - - void close(); - - ~LipO(); -}; - -#endif // MODULE_REGEX_ENABLED - -#endif // LIPO_H diff --git a/platform/osx/export/macho.cpp b/platform/osx/export/macho.cpp deleted file mode 100644 index e6e67eff06..0000000000 --- a/platform/osx/export/macho.cpp +++ /dev/null @@ -1,548 +0,0 @@ -/*************************************************************************/ -/* macho.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. - -#include "macho.h" - -#ifdef MODULE_REGEX_ENABLED - -uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) { - uint32_t align = p_max; - if (p_vmaddr != 0) { - uint64_t seg_align = 1; - align = 0; - while ((seg_align & p_vmaddr) == 0) { - seg_align = seg_align << 1; - align++; - } - align = CLAMP(align, p_min, p_max); - } - return align; -} - -bool MachO::alloc_signature(uint64_t p_size) { - ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); - if (signature_offset != 0) { - // Nothing to do, already have signature load command. - return true; - } - if (lc_limit == 0 || lc_limit + 16 > exe_base) { - ERR_FAIL_V_MSG(false, "MachO: Can't allocate signature load command, please use \"codesign_allocate\" utility first."); - } else { - // Add signature load command. - signature_offset = lc_limit; - - fa->seek(lc_limit); - LoadCommandHeader lc; - lc.cmd = LC_CODE_SIGNATURE; - lc.cmdsize = 16; - if (swap) { - lc.cmdsize = BSWAP32(lc.cmdsize); - } - fa->store_buffer((const uint8_t *)&lc, sizeof(LoadCommandHeader)); - - uint32_t lc_offset = fa->get_length() + PAD(fa->get_length(), 16); - uint32_t lc_size = 0; - if (swap) { - lc_offset = BSWAP32(lc_offset); - lc_size = BSWAP32(lc_size); - } - fa->store_32(lc_offset); - fa->store_32(lc_size); - - // Write new command number. - fa->seek(0x10); - uint32_t ncmds = fa->get_32(); - uint32_t cmdssize = fa->get_32(); - if (swap) { - ncmds = BSWAP32(ncmds); - cmdssize = BSWAP32(cmdssize); - } - ncmds += 1; - cmdssize += 16; - if (swap) { - ncmds = BSWAP32(ncmds); - cmdssize = BSWAP32(cmdssize); - } - fa->seek(0x10); - fa->store_32(ncmds); - fa->store_32(cmdssize); - - lc_limit = lc_limit + sizeof(LoadCommandHeader) + 8; - - return true; - } -} - -bool MachO::is_macho(const String &p_path) { - Ref fb = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); - uint32_t magic = fb->get_32(); - return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf); -} - -bool MachO::open_file(const String &p_path) { - fa = FileAccess::open(p_path, FileAccess::READ_WRITE); - ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); - uint32_t magic = fa->get_32(); - MachHeader mach_header; - - // Read MachO header. - swap = (magic == 0xcffaedfe || magic == 0xcefaedfe); - if (magic == 0xcefaedfe || magic == 0xfeedface) { - // Thin 32-bit binary. - fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); - } else if (magic == 0xcffaedfe || magic == 0xfeedfacf) { - // Thin 64-bit binary. - fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); - fa->get_32(); // Skip extra reserved field. - } else { - ERR_FAIL_V_MSG(false, vformat("MachO: File is not a valid MachO binary: \"%s\".", p_path)); - } - - if (swap) { - mach_header.ncmds = BSWAP32(mach_header.ncmds); - mach_header.cpusubtype = BSWAP32(mach_header.cpusubtype); - mach_header.cputype = BSWAP32(mach_header.cputype); - } - cpusubtype = mach_header.cpusubtype; - cputype = mach_header.cputype; - align = 0; - exe_base = std::numeric_limits::max(); - exe_limit = 0; - lc_limit = 0; - link_edit_offset = 0; - signature_offset = 0; - - // Read load commands. - for (uint32_t i = 0; i < mach_header.ncmds; i++) { - LoadCommandHeader lc; - fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); - if (swap) { - lc.cmd = BSWAP32(lc.cmd); - lc.cmdsize = BSWAP32(lc.cmdsize); - } - uint64_t ps = fa->get_position(); - switch (lc.cmd) { - case LC_SEGMENT: { - LoadCommandSegment lc_seg; - fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); - if (swap) { - lc_seg.nsects = BSWAP32(lc_seg.nsects); - lc_seg.vmaddr = BSWAP32(lc_seg.vmaddr); - lc_seg.vmsize = BSWAP32(lc_seg.vmsize); - } - align = MAX(align, seg_align(lc_seg.vmaddr, 2, 15)); - if (String(lc_seg.segname) == "__TEXT") { - exe_limit = MAX(exe_limit, lc_seg.vmsize); - for (uint32_t j = 0; j < lc_seg.nsects; j++) { - Section lc_sect; - fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section)); - if (String(lc_sect.sectname) == "__text") { - if (swap) { - exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); - } else { - exe_base = MIN(exe_base, lc_sect.offset); - } - } - if (swap) { - align = MAX(align, BSWAP32(lc_sect.align)); - } else { - align = MAX(align, lc_sect.align); - } - } - } else if (String(lc_seg.segname) == "__LINKEDIT") { - link_edit_offset = ps - 8; - } - } break; - case LC_SEGMENT_64: { - LoadCommandSegment64 lc_seg; - fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); - if (swap) { - lc_seg.nsects = BSWAP32(lc_seg.nsects); - lc_seg.vmaddr = BSWAP64(lc_seg.vmaddr); - lc_seg.vmsize = BSWAP64(lc_seg.vmsize); - } - align = MAX(align, seg_align(lc_seg.vmaddr, 3, 15)); - if (String(lc_seg.segname) == "__TEXT") { - exe_limit = MAX(exe_limit, lc_seg.vmsize); - for (uint32_t j = 0; j < lc_seg.nsects; j++) { - Section64 lc_sect; - fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section64)); - if (String(lc_sect.sectname) == "__text") { - if (swap) { - exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); - } else { - exe_base = MIN(exe_base, lc_sect.offset); - } - if (swap) { - align = MAX(align, BSWAP32(lc_sect.align)); - } else { - align = MAX(align, lc_sect.align); - } - } - } - } else if (String(lc_seg.segname) == "__LINKEDIT") { - link_edit_offset = ps - 8; - } - } break; - case LC_CODE_SIGNATURE: { - signature_offset = ps - 8; - } break; - default: { - } break; - } - fa->seek(ps + lc.cmdsize - 8); - lc_limit = ps + lc.cmdsize - 8; - } - - if (exe_limit == 0 || lc_limit == 0) { - ERR_FAIL_V_MSG(false, vformat("MachO: No load commands or executable code found: \"%s\".", p_path)); - } - - return true; -} - -uint64_t MachO::get_exe_base() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return exe_base; -} - -uint64_t MachO::get_exe_limit() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return exe_limit; -} - -int32_t MachO::get_align() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return align; -} - -uint32_t MachO::get_cputype() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return cputype; -} - -uint32_t MachO::get_cpusubtype() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return cpusubtype; -} - -uint64_t MachO::get_size() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - return fa->get_length(); -} - -uint64_t MachO::get_signature_offset() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); - - fa->seek(signature_offset + 8); - if (swap) { - return BSWAP32(fa->get_32()); - } else { - return fa->get_32(); - } -} - -uint64_t MachO::get_code_limit() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - - if (signature_offset == 0) { - return fa->get_length() + PAD(fa->get_length(), 16); - } else { - return get_signature_offset(); - } -} - -uint64_t MachO::get_signature_size() { - ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); - ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); - - fa->seek(signature_offset + 12); - if (swap) { - return BSWAP32(fa->get_32()); - } else { - return fa->get_32(); - } -} - -bool MachO::is_signed() { - ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); - if (signature_offset == 0) { - return false; - } - - fa->seek(get_signature_offset()); - uint32_t magic = BSWAP32(fa->get_32()); - if (magic != 0xfade0cc0) { - return false; // No SuperBlob found. - } - fa->get_32(); // Skip size field, unused. - uint32_t count = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < count; i++) { - uint32_t index_type = BSWAP32(fa->get_32()); - uint32_t offset = BSWAP32(fa->get_32()); - if (index_type == 0x00000000) { // CodeDirectory index type. - fa->seek(get_signature_offset() + offset + 12); - uint32_t flags = BSWAP32(fa->get_32()); - if (flags & 0x20000) { - return false; // Found CD, linker-signed. - } else { - return true; // Found CD, not linker-signed. - } - } - } - return false; // No CD found. -} - -PackedByteArray MachO::get_cdhash_sha1() { - ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); - if (signature_offset == 0) { - return PackedByteArray(); - } - - fa->seek(get_signature_offset()); - uint32_t magic = BSWAP32(fa->get_32()); - if (magic != 0xfade0cc0) { - return PackedByteArray(); // No SuperBlob found. - } - fa->get_32(); // Skip size field, unused. - uint32_t count = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < count; i++) { - fa->get_32(); // Index type, skip. - uint32_t offset = BSWAP32(fa->get_32()); - uint64_t pos = fa->get_position(); - - fa->seek(get_signature_offset() + offset); - uint32_t cdmagic = BSWAP32(fa->get_32()); - uint32_t cdsize = BSWAP32(fa->get_32()); - if (cdmagic == 0xfade0c02) { // CodeDirectory. - fa->seek(get_signature_offset() + offset + 36); - uint8_t hash_size = fa->get_8(); - uint8_t hash_type = fa->get_8(); - if (hash_size == 0x14 && hash_type == 0x01) { /* SHA-1 */ - PackedByteArray hash; - hash.resize(0x14); - - fa->seek(get_signature_offset() + offset); - PackedByteArray blob; - blob.resize(cdsize); - fa->get_buffer(blob.ptrw(), cdsize); - - CryptoCore::SHA1Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; - } - } - fa->seek(pos); - } - return PackedByteArray(); -} - -PackedByteArray MachO::get_cdhash_sha256() { - ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); - if (signature_offset == 0) { - return PackedByteArray(); - } - - fa->seek(get_signature_offset()); - uint32_t magic = BSWAP32(fa->get_32()); - if (magic != 0xfade0cc0) { - return PackedByteArray(); // No SuperBlob found. - } - fa->get_32(); // Skip size field, unused. - uint32_t count = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < count; i++) { - fa->get_32(); // Index type, skip. - uint32_t offset = BSWAP32(fa->get_32()); - uint64_t pos = fa->get_position(); - - fa->seek(get_signature_offset() + offset); - uint32_t cdmagic = BSWAP32(fa->get_32()); - uint32_t cdsize = BSWAP32(fa->get_32()); - if (cdmagic == 0xfade0c02) { // CodeDirectory. - fa->seek(get_signature_offset() + offset + 36); - uint8_t hash_size = fa->get_8(); - uint8_t hash_type = fa->get_8(); - if (hash_size == 0x20 && hash_type == 0x02) { /* SHA-256 */ - PackedByteArray hash; - hash.resize(0x20); - - fa->seek(get_signature_offset() + offset); - PackedByteArray blob; - blob.resize(cdsize); - fa->get_buffer(blob.ptrw(), cdsize); - - CryptoCore::SHA256Context ctx; - ctx.start(); - ctx.update(blob.ptr(), blob.size()); - ctx.finish(hash.ptrw()); - - return hash; - } - } - fa->seek(pos); - } - return PackedByteArray(); -} - -PackedByteArray MachO::get_requirements() { - ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); - if (signature_offset == 0) { - return PackedByteArray(); - } - - fa->seek(get_signature_offset()); - uint32_t magic = BSWAP32(fa->get_32()); - if (magic != 0xfade0cc0) { - return PackedByteArray(); // No SuperBlob found. - } - fa->get_32(); // Skip size field, unused. - uint32_t count = BSWAP32(fa->get_32()); - for (uint32_t i = 0; i < count; i++) { - fa->get_32(); // Index type, skip. - uint32_t offset = BSWAP32(fa->get_32()); - uint64_t pos = fa->get_position(); - - fa->seek(get_signature_offset() + offset); - uint32_t rqmagic = BSWAP32(fa->get_32()); - uint32_t rqsize = BSWAP32(fa->get_32()); - if (rqmagic == 0xfade0c01) { // Requirements. - PackedByteArray blob; - fa->seek(get_signature_offset() + offset); - blob.resize(rqsize); - fa->get_buffer(blob.ptrw(), rqsize); - return blob; - } - fa->seek(pos); - } - return PackedByteArray(); -} - -const Ref MachO::get_file() const { - return fa; -} - -Ref MachO::get_file() { - return fa; -} - -bool MachO::set_signature_size(uint64_t p_size) { - ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); - - // Ensure signature load command exists. - ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found."); - ERR_FAIL_COND_V_MSG(!alloc_signature(p_size), false, "MachO: Can't allocate signature load command."); - - // Update signature load command. - uint64_t old_size = get_signature_size(); - uint64_t new_size = p_size + PAD(p_size, 16384); - - if (new_size <= old_size) { - fa->seek(get_signature_offset()); - for (uint64_t i = 0; i < old_size; i++) { - fa->store_8(0x00); - } - return true; - } - - fa->seek(signature_offset + 12); - if (swap) { - fa->store_32(BSWAP32(new_size)); - } else { - fa->store_32(new_size); - } - - uint64_t end = get_signature_offset() + new_size; - - // Update "__LINKEDIT" segment. - LoadCommandHeader lc; - fa->seek(link_edit_offset); - fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); - if (swap) { - lc.cmd = BSWAP32(lc.cmd); - lc.cmdsize = BSWAP32(lc.cmdsize); - } - switch (lc.cmd) { - case LC_SEGMENT: { - LoadCommandSegment lc_seg; - fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); - if (swap) { - lc_seg.vmsize = BSWAP32(lc_seg.vmsize); - lc_seg.filesize = BSWAP32(lc_seg.filesize); - lc_seg.fileoff = BSWAP32(lc_seg.fileoff); - } - - lc_seg.vmsize = end - lc_seg.fileoff; - lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); - lc_seg.filesize = end - lc_seg.fileoff; - - if (swap) { - lc_seg.vmsize = BSWAP32(lc_seg.vmsize); - lc_seg.filesize = BSWAP32(lc_seg.filesize); - } - fa->seek(link_edit_offset + 8); - fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); - } break; - case LC_SEGMENT_64: { - LoadCommandSegment64 lc_seg; - fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); - if (swap) { - lc_seg.vmsize = BSWAP64(lc_seg.vmsize); - lc_seg.filesize = BSWAP64(lc_seg.filesize); - lc_seg.fileoff = BSWAP64(lc_seg.fileoff); - } - lc_seg.vmsize = end - lc_seg.fileoff; - lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); - lc_seg.filesize = end - lc_seg.fileoff; - if (swap) { - lc_seg.vmsize = BSWAP64(lc_seg.vmsize); - lc_seg.filesize = BSWAP64(lc_seg.filesize); - } - fa->seek(link_edit_offset + 8); - fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); - } break; - default: { - ERR_FAIL_V_MSG(false, "MachO: Invalid __LINKEDIT segment type."); - } break; - } - fa->seek(get_signature_offset()); - for (uint64_t i = 0; i < new_size; i++) { - fa->store_8(0x00); - } - return true; -} - -#endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/macho.h b/platform/osx/export/macho.h deleted file mode 100644 index 6cfc3c44f5..0000000000 --- a/platform/osx/export/macho.h +++ /dev/null @@ -1,215 +0,0 @@ -/*************************************************************************/ -/* macho.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -// Mach-O binary object file format parser and editor. - -#ifndef MACHO_H -#define MACHO_H - -#include "core/crypto/crypto.h" -#include "core/crypto/crypto_core.h" -#include "core/io/file_access.h" -#include "core/object/ref_counted.h" -#include "modules/modules_enabled.gen.h" // For regex. - -#ifdef MODULE_REGEX_ENABLED - -class MachO : public RefCounted { - struct MachHeader { - uint32_t cputype; - uint32_t cpusubtype; - uint32_t filetype; - uint32_t ncmds; - uint32_t sizeofcmds; - uint32_t flags; - }; - - enum LoadCommandID { - LC_SEGMENT = 0x00000001, - LC_SYMTAB = 0x00000002, - LC_SYMSEG = 0x00000003, - LC_THREAD = 0x00000004, - LC_UNIXTHREAD = 0x00000005, - LC_LOADFVMLIB = 0x00000006, - LC_IDFVMLIB = 0x00000007, - LC_IDENT = 0x00000008, - LC_FVMFILE = 0x00000009, - LC_PREPAGE = 0x0000000a, - LC_DYSYMTAB = 0x0000000b, - LC_LOAD_DYLIB = 0x0000000c, - LC_ID_DYLIB = 0x0000000d, - LC_LOAD_DYLINKER = 0x0000000e, - LC_ID_DYLINKER = 0x0000000f, - LC_PREBOUND_DYLIB = 0x00000010, - LC_ROUTINES = 0x00000011, - LC_SUB_FRAMEWORK = 0x00000012, - LC_SUB_UMBRELLA = 0x00000013, - LC_SUB_CLIENT = 0x00000014, - LC_SUB_LIBRARY = 0x00000015, - LC_TWOLEVEL_HINTS = 0x00000016, - LC_PREBIND_CKSUM = 0x00000017, - LC_LOAD_WEAK_DYLIB = 0x80000018, - LC_SEGMENT_64 = 0x00000019, - LC_ROUTINES_64 = 0x0000001a, - LC_UUID = 0x0000001b, - LC_RPATH = 0x8000001c, - LC_CODE_SIGNATURE = 0x0000001d, - LC_SEGMENT_SPLIT_INFO = 0x0000001e, - LC_REEXPORT_DYLIB = 0x8000001f, - LC_LAZY_LOAD_DYLIB = 0x00000020, - LC_ENCRYPTION_INFO = 0x00000021, - LC_DYLD_INFO = 0x00000022, - LC_DYLD_INFO_ONLY = 0x80000022, - LC_LOAD_UPWARD_DYLIB = 0x80000023, - LC_VERSION_MIN_MACOSX = 0x00000024, - LC_VERSION_MIN_IPHONEOS = 0x00000025, - LC_FUNCTION_STARTS = 0x00000026, - LC_DYLD_ENVIRONMENT = 0x00000027, - LC_MAIN = 0x80000028, - LC_DATA_IN_CODE = 0x00000029, - LC_SOURCE_VERSION = 0x0000002a, - LC_DYLIB_CODE_SIGN_DRS = 0x0000002b, - LC_ENCRYPTION_INFO_64 = 0x0000002c, - LC_LINKER_OPTION = 0x0000002d, - LC_LINKER_OPTIMIZATION_HINT = 0x0000002e, - LC_VERSION_MIN_TVOS = 0x0000002f, - LC_VERSION_MIN_WATCHOS = 0x00000030, - }; - - struct LoadCommandHeader { - uint32_t cmd; - uint32_t cmdsize; - }; - - struct LoadCommandSegment { - char segname[16]; - uint32_t vmaddr; - uint32_t vmsize; - uint32_t fileoff; - uint32_t filesize; - uint32_t maxprot; - uint32_t initprot; - uint32_t nsects; - uint32_t flags; - }; - - struct LoadCommandSegment64 { - char segname[16]; - uint64_t vmaddr; - uint64_t vmsize; - uint64_t fileoff; - uint64_t filesize; - uint32_t maxprot; - uint32_t initprot; - uint32_t nsects; - uint32_t flags; - }; - - struct Section { - char sectname[16]; - char segname[16]; - uint32_t addr; - uint32_t size; - uint32_t offset; - uint32_t align; - uint32_t reloff; - uint32_t nreloc; - uint32_t flags; - uint32_t reserved1; - uint32_t reserved2; - }; - - struct Section64 { - char sectname[16]; - char segname[16]; - uint64_t addr; - uint64_t size; - uint32_t offset; - uint32_t align; - uint32_t reloff; - uint32_t nreloc; - uint32_t flags; - uint32_t reserved1; - uint32_t reserved2; - uint32_t reserved3; - }; - - Ref fa; - bool swap = false; - - uint64_t lc_limit = 0; - - uint64_t exe_limit = 0; - uint64_t exe_base = std::numeric_limits::max(); // Start of first __text section. - uint32_t align = 0; - uint32_t cputype = 0; - uint32_t cpusubtype = 0; - - uint64_t link_edit_offset = 0; // __LINKEDIT segment offset. - uint64_t signature_offset = 0; // Load command offset. - - uint32_t seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max); - bool alloc_signature(uint64_t p_size); - - static inline size_t PAD(size_t s, size_t a) { - return (a - s % a); - } - -public: - static bool is_macho(const String &p_path); - - bool open_file(const String &p_path); - - uint64_t get_exe_base(); - uint64_t get_exe_limit(); - int32_t get_align(); - uint32_t get_cputype(); - uint32_t get_cpusubtype(); - uint64_t get_size(); - uint64_t get_code_limit(); - - uint64_t get_signature_offset(); - bool is_signed(); - - PackedByteArray get_cdhash_sha1(); - PackedByteArray get_cdhash_sha256(); - - PackedByteArray get_requirements(); - - const Ref get_file() const; - Ref get_file(); - - uint64_t get_signature_size(); - bool set_signature_size(uint64_t p_size); -}; - -#endif // MODULE_REGEX_ENABLED - -#endif // MACHO_H diff --git a/platform/osx/export/plist.cpp b/platform/osx/export/plist.cpp deleted file mode 100644 index 36de9dd34b..0000000000 --- a/platform/osx/export/plist.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/*************************************************************************/ -/* plist.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex. - -#include "plist.h" - -#ifdef MODULE_REGEX_ENABLED - -Ref PListNode::new_array() { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY; - return node; -} - -Ref PListNode::new_dict() { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; - return node; -} - -Ref PListNode::new_string(const String &p_string) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING; - node->data_string = p_string.utf8(); - return node; -} - -Ref PListNode::new_data(const String &p_string) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA; - node->data_string = p_string.utf8(); - return node; -} - -Ref PListNode::new_date(const String &p_string) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE; - node->data_string = p_string.utf8(); - return node; -} - -Ref PListNode::new_bool(bool p_bool) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN; - node->data_bool = p_bool; - return node; -} - -Ref PListNode::new_int(int32_t p_int) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER; - node->data_int = p_int; - return node; -} - -Ref PListNode::new_real(float p_real) { - Ref node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL; - node->data_real = p_real; - return node; -} - -bool PListNode::push_subnode(const Ref &p_node, const String &p_key) { - ERR_FAIL_COND_V(p_node.is_null(), false); - if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) { - ERR_FAIL_COND_V(p_key.is_empty(), false); - ERR_FAIL_COND_V(data_dict.has(p_key), false); - data_dict[p_key] = p_node; - return true; - } else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) { - data_array.push_back(p_node); - return true; - } else { - ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY."); - } -} - -size_t PListNode::get_asn1_size(uint8_t p_len_octets) const { - // Get size of all data, excluding type and size information. - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - return 0; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATA: - case PList::PLNodeType::PL_NODE_TYPE_DATE: { - ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - return data_string.length(); - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - return 1; - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - return 4; - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - size_t size = 0; - for (int i = 0; i < data_array.size(); i++) { - size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets); - } - return size; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - size_t size = 0; - - for (const KeyValue> &E : data_dict) { - size += 1 + _asn1_size_len(p_len_octets); // Sequence. - size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key. - size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value. - } - return size; - } break; - default: { - return 0; - } break; - } -} - -int PListNode::_asn1_size_len(uint8_t p_len_octets) { - if (p_len_octets > 1) { - return p_len_octets + 1; - } else { - return 1; - } -} - -void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const { - uint32_t size = get_asn1_size(p_len_octets); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (size >> i * 8) & 0xFF; - p_stream.push_back(x); - } -} - -bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const { - // Convert to binary ASN1 stream. - bool valid = true; - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - // Nothing to store. - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATE: - case PList::PLNodeType::PL_NODE_TYPE_DATA: { - ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - p_stream.push_back(0x0C); - store_asn1_size(p_stream, p_len_octets); - for (int i = 0; i < data_string.size(); i++) { - p_stream.push_back(data_string[i]); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - p_stream.push_back(0x01); - store_asn1_size(p_stream, p_len_octets); - if (data_bool) { - p_stream.push_back(0x01); - } else { - p_stream.push_back(0x00); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { - p_stream.push_back(0x02); - store_asn1_size(p_stream, p_len_octets); - for (int i = 4; i >= 0; i--) { - uint8_t x = (data_int >> i * 8) & 0xFF; - p_stream.push_back(x); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - p_stream.push_back(0x03); - store_asn1_size(p_stream, p_len_octets); - for (int i = 4; i >= 0; i--) { - uint8_t x = (data_int >> i * 8) & 0xFF; - p_stream.push_back(x); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - p_stream.push_back(0x30); // Sequence. - store_asn1_size(p_stream, p_len_octets); - for (int i = 0; i < data_array.size(); i++) { - valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - p_stream.push_back(0x31); // Set. - store_asn1_size(p_stream, p_len_octets); - for (const KeyValue> &E : data_dict) { - CharString cs = E.key.utf8(); - uint32_t size = cs.length(); - - // Sequence. - p_stream.push_back(0x30); - uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (seq_size >> i * 8) & 0xFF; - p_stream.push_back(x); - } - // Key. - p_stream.push_back(0x0C); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (size >> i * 8) & 0xFF; - p_stream.push_back(x); - } - for (uint32_t i = 0; i < size; i++) { - p_stream.push_back(cs[i]); - } - // Value. - valid = valid && E.value->store_asn1(p_stream, p_len_octets); - } - } break; - } - return valid; -} - -void PListNode::store_text(String &p_stream, uint8_t p_indent) const { - // Convert to text XML stream. - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - // Nothing to store. - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATA: { - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - p_stream += String("\t").repeat(p_indent); - p_stream += data_string + "\n"; - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATE: { - p_stream += String("\t").repeat(p_indent); - p_stream += ""; - p_stream += data_string; - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - p_stream += String("\t").repeat(p_indent); - p_stream += ""; - p_stream += String::utf8(data_string); - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - p_stream += String("\t").repeat(p_indent); - if (data_bool) { - p_stream += "\n"; - } else { - p_stream += "\n"; - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { - p_stream += String("\t").repeat(p_indent); - p_stream += ""; - p_stream += itos(data_int); - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - p_stream += String("\t").repeat(p_indent); - p_stream += ""; - p_stream += rtos(data_real); - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - for (int i = 0; i < data_array.size(); i++) { - data_array[i]->store_text(p_stream, p_indent + 1); - } - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - for (const KeyValue> &E : data_dict) { - p_stream += String("\t").repeat(p_indent + 1); - p_stream += ""; - p_stream += E.key; - p_stream += "\n"; - E.value->store_text(p_stream, p_indent + 1); - } - p_stream += String("\t").repeat(p_indent); - p_stream += "\n"; - } break; - } -} - -/*************************************************************************/ - -PList::PList() { - root = PListNode::new_dict(); -} - -PList::PList(const String &p_string) { - load_string(p_string); -} - -bool PList::load_file(const String &p_filename) { - root = Ref(); - - Ref fb = FileAccess::open(p_filename, FileAccess::READ); - if (fb.is_null()) { - return false; - } - - unsigned char magic[8]; - fb->get_buffer(magic, 8); - - if (String((const char *)magic, 8) == "bplist00") { - ERR_FAIL_V_MSG(false, "PList: Binary property lists are not supported."); - } else { - // Load text plist. - Error err; - Vector array = FileAccess::get_file_as_array(p_filename, &err); - ERR_FAIL_COND_V(err != OK, false); - - String ret; - ret.parse_utf8((const char *)array.ptr(), array.size()); - return load_string(ret); - } -} - -bool PList::load_string(const String &p_string) { - root = Ref(); - - int pos = 0; - bool in_plist = false; - bool done_plist = false; - List> stack; - String key; - while (pos >= 0) { - int open_token_s = p_string.find("<", pos); - if (open_token_s == -1) { - ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. No tags found."); - } - int open_token_e = p_string.find(">", open_token_s); - pos = open_token_e; - - String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1); - if (token.is_empty()) { - ERR_FAIL_V_MSG(false, "PList: Invalid token name."); - } - String value; - if (token[0] == '?' || token[0] == '!') { // Skip and - int end_token_e = p_string.find(">", open_token_s); - pos = end_token_e; - continue; - } - - if (token.find("plist", 0) == 0) { - in_plist = true; - continue; - } - - if (token == "/plist") { - done_plist = true; - break; - } - - if (!in_plist) { - ERR_FAIL_V_MSG(false, "PList: Node outside of tag."); - } - - if (token == "dict") { - if (!stack.is_empty()) { - // Add subnode end enter it. - Ref dict = PListNode::new_dict(); - dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; - if (!stack.back()->get()->push_subnode(dict, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - stack.push_back(dict); - } else { - // Add root node. - if (!root.is_null()) { - ERR_FAIL_V_MSG(false, "PList: Root node already set."); - } - Ref dict = PListNode::new_dict(); - stack.push_back(dict); - root = dict; - } - continue; - } - - if (token == "/dict") { - // Exit current dict. - if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) { - ERR_FAIL_V_MSG(false, "PList: Mismatched tag."); - } - stack.pop_back(); - continue; - } - - if (token == "array") { - if (!stack.is_empty()) { - // Add subnode end enter it. - Ref arr = PListNode::new_array(); - if (!stack.back()->get()->push_subnode(arr, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - stack.push_back(arr); - } else { - // Add root node. - if (!root.is_null()) { - ERR_FAIL_V_MSG(false, "PList: Root node already set."); - } - Ref arr = PListNode::new_array(); - stack.push_back(arr); - root = arr; - } - continue; - } - - if (token == "/array") { - // Exit current array. - if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) { - ERR_FAIL_V_MSG(false, "PList: Mismatched tag."); - } - stack.pop_back(); - continue; - } - - if (token[token.length() - 1] == '/') { - token = token.substr(0, token.length() - 1); - } else { - int end_token_s = p_string.find(" tag.", token)); - } - int end_token_e = p_string.find(">", end_token_s); - pos = end_token_e; - String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2); - if (end_token != token) { - ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> and <%s> token pair.", token, end_token)); - } - value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1); - } - if (token == "key") { - key = value; - } else { - Ref var = nullptr; - if (token == "true") { - var = PListNode::new_bool(true); - } else if (token == "false") { - var = PListNode::new_bool(false); - } else if (token == "integer") { - var = PListNode::new_int(value.to_int()); - } else if (token == "real") { - var = PListNode::new_real(value.to_float()); - } else if (token == "string") { - var = PListNode::new_string(value); - } else if (token == "data") { - var = PListNode::new_data(value); - } else if (token == "date") { - var = PListNode::new_date(value); - } else { - ERR_FAIL_V_MSG(false, "PList: Invalid value type."); - } - if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - } - } - if (!stack.is_empty() || !done_plist) { - ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed."); - } - return true; -} - -PackedByteArray PList::save_asn1() const { - if (root == nullptr) { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node."); - } - size_t size = root->get_asn1_size(1); - uint8_t len_octets = 0; - if (size < 0x80) { - len_octets = 1; - } else { - size = root->get_asn1_size(2); - if (size < 0xFFFF) { - len_octets = 2; - } else { - size = root->get_asn1_size(3); - if (size < 0xFFFFFF) { - len_octets = 3; - } else { - size = root->get_asn1_size(4); - if (size < 0xFFFFFFFF) { - len_octets = 4; - } else { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB."); - } - } - } - } - - PackedByteArray ret; - if (!root->store_asn1(ret, len_octets)) { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error."); - } - return ret; -} - -String PList::save_text() const { - if (root == nullptr) { - ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node."); - } - - String ret; - ret += "\n"; - ret += "\n"; - ret += "\n"; - - root->store_text(ret, 0); - - ret += "\n\n"; - return ret; -} - -Ref PList::get_root() { - return root; -} - -#endif // MODULE_REGEX_ENABLED diff --git a/platform/osx/export/plist.h b/platform/osx/export/plist.h deleted file mode 100644 index ba9eaec196..0000000000 --- a/platform/osx/export/plist.h +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************/ -/* plist.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -// Property list file format (application/x-plist) parser, property list ASN-1 serialization. - -#ifndef PLIST_H -#define PLIST_H - -#include "core/crypto/crypto_core.h" -#include "core/io/file_access.h" -#include "modules/modules_enabled.gen.h" // For regex. - -#ifdef MODULE_REGEX_ENABLED - -class PListNode; - -class PList : public RefCounted { - friend class PListNode; - -public: - enum PLNodeType { - PL_NODE_TYPE_NIL, - PL_NODE_TYPE_STRING, - PL_NODE_TYPE_ARRAY, - PL_NODE_TYPE_DICT, - PL_NODE_TYPE_BOOLEAN, - PL_NODE_TYPE_INTEGER, - PL_NODE_TYPE_REAL, - PL_NODE_TYPE_DATA, - PL_NODE_TYPE_DATE, - }; - -private: - Ref root; - -public: - PList(); - PList(const String &p_string); - - bool load_file(const String &p_filename); - bool load_string(const String &p_string); - - PackedByteArray save_asn1() const; - String save_text() const; - - Ref get_root(); -}; - -/*************************************************************************/ - -class PListNode : public RefCounted { - static int _asn1_size_len(uint8_t p_len_octets); - -public: - PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL; - - CharString data_string; - Vector> data_array; - HashMap> data_dict; - union { - int32_t data_int; - bool data_bool; - float data_real; - }; - - static Ref new_array(); - static Ref new_dict(); - static Ref new_string(const String &p_string); - static Ref new_data(const String &p_string); - static Ref new_date(const String &p_string); - static Ref new_bool(bool p_bool); - static Ref new_int(int32_t p_int); - static Ref new_real(float p_real); - - bool push_subnode(const Ref &p_node, const String &p_key = ""); - - size_t get_asn1_size(uint8_t p_len_octets) const; - - void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const; - bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const; - void store_text(String &p_stream, uint8_t p_indent) const; - - PListNode() {} - ~PListNode() {} -}; - -#endif // MODULE_REGEX_ENABLED - -#endif // PLIST_H diff --git a/platform/osx/gl_manager_osx_legacy.h b/platform/osx/gl_manager_osx_legacy.h deleted file mode 100644 index 2d4913a7a6..0000000000 --- a/platform/osx/gl_manager_osx_legacy.h +++ /dev/null @@ -1,97 +0,0 @@ -/*************************************************************************/ -/* gl_manager_osx_legacy.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 GL_MANAGER_OSX_LEGACY_H -#define GL_MANAGER_OSX_LEGACY_H - -#if defined(OSX_ENABLED) && defined(GLES3_ENABLED) - -#include "core/error/error_list.h" -#include "core/os/os.h" -#include "core/templates/local_vector.h" -#include "servers/display_server.h" - -#import -#import -#import - -class GLManager_OSX { -public: - enum ContextType { - GLES_3_0_COMPATIBLE, - }; - -private: - struct GLWindow { - int width = 0; - int height = 0; - - id window_view = nullptr; - NSOpenGLContext *context = nullptr; - }; - - RBMap windows; - - NSOpenGLContext *shared_context = nullptr; - DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID; - - Error create_context(GLWindow &win); - - bool use_vsync = false; - ContextType context_type; - -public: - Error window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height); - void window_destroy(DisplayServer::WindowID p_window_id); - void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); - - int window_get_width(DisplayServer::WindowID p_window_id = 0); - int window_get_height(DisplayServer::WindowID p_window_id = 0); - - void release_current(); - void make_current(); - void swap_buffers(); - - void window_make_current(DisplayServer::WindowID p_window_id); - - void window_update(DisplayServer::WindowID p_window_id); - void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled); - - Error initialize(); - - void set_use_vsync(bool p_use); - bool is_using_vsync() const; - - GLManager_OSX(ContextType p_context_type); - ~GLManager_OSX(); -}; - -#endif // OSX_ENABLED && GLES3_ENABLED -#endif // GL_MANAGER_OSX_LEGACY_H diff --git a/platform/osx/gl_manager_osx_legacy.mm b/platform/osx/gl_manager_osx_legacy.mm deleted file mode 100644 index c769d7f5c5..0000000000 --- a/platform/osx/gl_manager_osx_legacy.mm +++ /dev/null @@ -1,228 +0,0 @@ -/*************************************************************************/ -/* gl_manager_osx_legacy.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gl_manager_osx_legacy.h" - -#ifdef OSX_ENABLED -#ifdef GLES3_ENABLED - -#include -#include - -Error GLManager_OSX::create_context(GLWindow &win) { - NSOpenGLPixelFormatAttribute attributes[] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAClosestPolicy, - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, - NSOpenGLPFAColorSize, 32, - NSOpenGLPFADepthSize, 24, - NSOpenGLPFAStencilSize, 8, - 0 - }; - - NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; - ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE); - - win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context]; - ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE); - if (shared_context == nullptr) { - shared_context = win.context; - } - - [win.context setView:win.window_view]; - [win.context makeCurrentContext]; - - return OK; -} - -Error GLManager_OSX::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) { - GLWindow win; - win.width = p_width; - win.height = p_height; - win.window_view = p_view; - - if (create_context(win) != OK) { - return FAILED; - } - - windows[p_window_id] = win; - window_make_current(p_window_id); - - return OK; -} - -void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { - if (!windows.has(p_window_id)) { - return; - } - - GLWindow &win = windows[p_window_id]; - - win.width = p_width; - win.height = p_height; - - GLint dim[2]; - dim[0] = p_width; - dim[1] = p_height; - CGLSetParameter((CGLContextObj)[win.context CGLContextObj], kCGLCPSurfaceBackingSize, &dim[0]); - CGLEnable((CGLContextObj)[win.context CGLContextObj], kCGLCESurfaceBackingSize); - if (OS::get_singleton()->is_hidpi_allowed()) { - [win.window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [win.window_view setWantsBestResolutionOpenGLSurface:NO]; - } - - [win.context update]; -} - -int GLManager_OSX::window_get_width(DisplayServer::WindowID p_window_id) { - if (!windows.has(p_window_id)) { - return 0; - } - - GLWindow &win = windows[p_window_id]; - return win.width; -} - -int GLManager_OSX::window_get_height(DisplayServer::WindowID p_window_id) { - if (!windows.has(p_window_id)) { - return 0; - } - - GLWindow &win = windows[p_window_id]; - return win.height; -} - -void GLManager_OSX::window_destroy(DisplayServer::WindowID p_window_id) { - if (!windows.has(p_window_id)) { - return; - } - - if (current_window == p_window_id) { - current_window = DisplayServer::INVALID_WINDOW_ID; - } - - windows.erase(p_window_id); -} - -void GLManager_OSX::release_current() { - if (current_window == DisplayServer::INVALID_WINDOW_ID) { - return; - } - - [NSOpenGLContext clearCurrentContext]; -} - -void GLManager_OSX::window_make_current(DisplayServer::WindowID p_window_id) { - if (current_window == p_window_id) { - return; - } - if (!windows.has(p_window_id)) { - return; - } - - GLWindow &win = windows[p_window_id]; - [win.context makeCurrentContext]; - - current_window = p_window_id; -} - -void GLManager_OSX::make_current() { - if (current_window == DisplayServer::INVALID_WINDOW_ID) { - return; - } - if (!windows.has(current_window)) { - return; - } - - GLWindow &win = windows[current_window]; - [win.context makeCurrentContext]; -} - -void GLManager_OSX::swap_buffers() { - for (const KeyValue &E : windows) { - [E.value.context flushBuffer]; - } -} - -void GLManager_OSX::window_update(DisplayServer::WindowID p_window_id) { - if (!windows.has(p_window_id)) { - return; - } - - GLWindow &win = windows[p_window_id]; - [win.context update]; -} - -void GLManager_OSX::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) { - if (!windows.has(p_window_id)) { - return; - } - - GLWindow &win = windows[p_window_id]; - if (p_enabled) { - GLint opacity = 0; - [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; - } else { - GLint opacity = 1; - [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; - } - [win.context update]; -} - -Error GLManager_OSX::initialize() { - return OK; -} - -void GLManager_OSX::set_use_vsync(bool p_use) { - use_vsync = p_use; - - CGLContextObj ctx = CGLGetCurrentContext(); - if (ctx) { - GLint swapInterval = p_use ? 1 : 0; - CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - use_vsync = p_use; - } -} - -bool GLManager_OSX::is_using_vsync() const { - return use_vsync; -} - -GLManager_OSX::GLManager_OSX(ContextType p_context_type) { - context_type = p_context_type; -} - -GLManager_OSX::~GLManager_OSX() { - release_current(); -} - -#endif // GLES3_ENABLED -#endif // OSX diff --git a/platform/osx/godot_application.h b/platform/osx/godot_application.h deleted file mode 100644 index 8d48a659f3..0000000000 --- a/platform/osx/godot_application.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************/ -/* godot_application.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_APPLICATION_H -#define GODOT_APPLICATION_H - -#include "core/os/os.h" - -#import -#import - -@interface GodotApplication : NSApplication -@end - -#endif // GODOT_APPLICATION_H diff --git a/platform/osx/godot_application.mm b/platform/osx/godot_application.mm deleted file mode 100644 index 13313a025a..0000000000 --- a/platform/osx/godot_application.mm +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* godot_application.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_application.h" - -#include "display_server_osx.h" - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown || [event type] == NSEventTypeOtherMouseDown) { - if (ds->mouse_process_popups()) { - return; - } - } - ds->send_event(event); - } - - // 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 diff --git a/platform/osx/godot_application_delegate.h b/platform/osx/godot_application_delegate.h deleted file mode 100644 index f5b67b580f..0000000000 --- a/platform/osx/godot_application_delegate.h +++ /dev/null @@ -1,46 +0,0 @@ -/*************************************************************************/ -/* godot_application_delegate.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_APPLICATION_DELEGATE_H -#define GODOT_APPLICATION_DELEGATE_H - -#include "core/os/os.h" - -#import -#import - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; -@end - -#endif // GODOT_APPLICATION_DELEGATE_H diff --git a/platform/osx/godot_application_delegate.mm b/platform/osx/godot_application_delegate.mm deleted file mode 100644 index 4d3558b273..0000000000 --- a/platform/osx/godot_application_delegate.mm +++ /dev/null @@ -1,163 +0,0 @@ -/*************************************************************************/ -/* godot_application_delegate.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_application_delegate.h" - -#include "display_server_osx.h" -#include "os_osx.h" - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step 1: Switch focus to macOS SystemUIServer process. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { - [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 || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { - // If the executable is started from terminal or is not 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]; - } -} - -- (id)init { - self = [super init]; - - NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager]; - [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; - [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; - - return self; -} - -- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { - OS_OSX *os = (OS_OSX *)OS::get_singleton(); - if (!event || !os) { - return; - } - - List args; - if (([event eventClass] == kInternetEventClass) && ([event eventID] == kAEGetURL)) { - // Opening URL scheme. - NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - args.push_back(vformat("--uri=\"%s\"", String::utf8([url UTF8String]))); - } - - if (([event eventClass] == kCoreEventClass) && ([event eventID] == kAEOpenDocuments)) { - // Opening file association. - NSAppleEventDescriptor *files = [event paramDescriptorForKeyword:keyDirectObject]; - if (files) { - NSInteger count = [files numberOfItems]; - for (NSInteger i = 1; i <= count; i++) { - NSURL *url = [NSURL URLWithString:[[files descriptorAtIndex:i] stringValue]]; - args.push_back(String::utf8([url.path UTF8String])); - } - } - } - - if (!args.is_empty()) { - if (os->get_main_loop()) { - // Application is already running, open a new instance with the URL/files as command line arguments. - os->create_instance(args); - } else { - // Application is just started, add to the list of command line arguments and continue. - os->set_cmdline_platform_args(args); - } - } -} - -- (void)applicationDidResignActive:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->mouse_process_popups(true); - } - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } -} - -- (void)applicationDidBecomeActive:(NSNotification *)notification { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } -} - -- (void)globalMenuCallback:(id)sender { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - return ds->menu_callback(sender); - } -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - return ds->get_dock_menu(); - } else { - return nullptr; - } -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->send_window_event(ds->get_window(DisplayServerOSX::MAIN_WINDOW_ID), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - } - return NSTerminateCancel; -} - -- (void)showAbout:(id)sender { - OS_OSX *os = (OS_OSX *)OS::get_singleton(); - if (os && os->get_main_loop()) { - os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); - } -} - -@end diff --git a/platform/osx/godot_content_view.h b/platform/osx/godot_content_view.h deleted file mode 100644 index 353305aec1..0000000000 --- a/platform/osx/godot_content_view.h +++ /dev/null @@ -1,66 +0,0 @@ -/*************************************************************************/ -/* godot_content_view.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_CONTENT_VIEW_H -#define GODOT_CONTENT_VIEW_H - -#include "servers/display_server.h" - -#import -#import - -#if defined(GLES3_ENABLED) -#import -#define RootView NSOpenGLView -#else -#define RootView NSView -#endif - -#import - -@interface GodotContentView : RootView { - DisplayServer::WindowID window_id; - NSTrackingArea *tracking_area; - NSMutableAttributedString *marked_text; - bool ime_input_event_in_progress; - bool mouse_down_control; - bool ignore_momentum_scroll; - bool last_pen_inverted; -} - -- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor; -- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy; -- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed; -- (void)setWindowID:(DisplayServer::WindowID)wid; -- (void)cancelComposition; - -@end - -#endif // GODOT_CONTENT_VIEW_H diff --git a/platform/osx/godot_content_view.mm b/platform/osx/godot_content_view.mm deleted file mode 100644 index 018b90e629..0000000000 --- a/platform/osx/godot_content_view.mm +++ /dev/null @@ -1,771 +0,0 @@ -/*************************************************************************/ -/* godot_content_view.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_content_view.h" - -#include "display_server_osx.h" -#include "key_mapping_osx.h" - -@implementation GodotContentView - -- (id)init { - self = [super init]; - window_id = DisplayServer::INVALID_WINDOW_ID; - tracking_area = nil; - ime_input_event_in_progress = false; - mouse_down_control = false; - ignore_momentum_scroll = false; - last_pen_inverted = false; - [self updateTrackingAreas]; - - if (@available(macOS 10.13, *)) { - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; -#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. - } else { - [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; -#endif - } - marked_text = [[NSMutableAttributedString alloc] init]; - return self; -} - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -// MARK: Backing Layer - -- (CALayer *)makeBackingLayer { - return [[CAMetalLayer class] layer]; -} - -- (void)updateLayer { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - ds->window_update(window_id); - [super updateLayer]; -} - -- (BOOL)wantsUpdateLayer { - return YES; -} - -- (BOOL)isOpaque { - return YES; -} - -// MARK: IME - -- (BOOL)hasMarkedText { - return (marked_text.length > 0); -} - -- (NSRange)markedRange { - return NSMakeRange(0, marked_text.length); -} - -- (NSRange)selectedRange { - static const NSRange kEmptyRange = { NSNotFound, 0 }; - return kEmptyRange; -} - -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - if ([aString isKindOfClass:[NSAttributedString class]]) { - marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString]; - } else { - marked_text = [[NSMutableAttributedString alloc] initWithString:aString]; - } - if (marked_text.length == 0) { - [self unmarkText]; - return; - } - - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - if (wd.im_active) { - ime_input_event_in_progress = true; - ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String])); - } -} - -- (void)doCommandBySelector:(SEL)aSelector { - [self tryToPerform:aSelector with:self]; -} - -- (void)unmarkText { - ime_input_event_in_progress = false; - [[marked_text mutableString] setString:@""]; - - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - if (wd.im_active) { - ds->update_im_text(Point2i(), String()); - } -} - -- (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 { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return NSMakeRect(0, 0, 0, 0); - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - const NSRect content_rect = [wd.window_view frame]; - const float scale = ds->screen_get_max_scale(); - NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0); - NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin; - - return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0); -} - -- (void)cancelComposition { - [self unmarkText]; - [[NSTextInputContext 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; - } - - NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet]; - NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) { - [[NSTextInputContext currentInputContext] discardMarkedText]; - [self cancelComposition]; - return; - } - - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - [self cancelComposition]; - return; - } - - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[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 = Key::NONE; - ke.physical_keycode = Key::NONE; - ke.unicode = codepoint; - - ds->push_to_key_event_buffer(ke); - } - [self cancelComposition]; -} - -// MARK: Drag and drop - -- (NSDragOperation)draggingEntered:(id)sender { - return NSDragOperationCopy; -} - -- (NSDragOperation)draggingUpdated:(id)sender { - return NSDragOperationCopy; -} - -- (BOOL)performDragOperation:(id)sender { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return NO; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - if (!wd.drop_files_callback.is_null()) { - Vector files; - NSPasteboard *pboard = [sender draggingPasteboard]; - - if (@available(macOS 10.13, *)) { - NSArray *items = pboard.pasteboardItems; - for (NSPasteboardItem *item in items) { - NSString *url = [item stringForType:NSPasteboardTypeFileURL]; - NSString *file = [NSURL URLWithString:url].path; - files.push_back(String::utf8([file UTF8String])); - } -#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. - } else { - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - for (NSString *file in filenames) { - files.push_back(String::utf8([file UTF8String])); - } -#endif - } - - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); - } - - return NO; -} - -// MARK: Focus - -- (BOOL)canBecomeKeyView { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return YES; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - return !wd.no_focus && !wd.is_popup; -} - -- (BOOL)acceptsFirstResponder { - return YES; -} - -// MARK: Mouse - -- (void)cursorUpdate:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds) { - return; - } - - ds->cursor_update_shape(); -} - -- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - MouseButton last_button_state = ds->mouse_get_button_state(); - - if (pressed) { - last_button_state |= mask; - } else { - last_button_state &= (MouseButton)~mask; - } - ds->mouse_set_button_state(last_button_state); - - Ref mb; - mb.instantiate(); - mb->set_window_id(window_id); - ds->update_mouse_pos(wd, [event locationInWindow]); - ds->get_key_modifier_state([event modifierFlags], mb); - mb->set_button_index(index); - mb->set_pressed(pressed); - mb->set_position(wd.mouse_pos); - mb->set_global_position(wd.mouse_pos); - mb->set_button_mask(last_button_state); - if (index == MouseButton::LEFT && pressed) { - mb->set_double_click([event clickCount] == 2); - } - - Input::get_singleton()->parse_input_event(mb); -} - -- (void)mouseDown:(NSEvent *)event { - if (([event modifierFlags] & NSEventModifierFlagControl)) { - mouse_down_control = true; - [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; - } else { - mouse_down_control = false; - [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true]; - } -} - -- (void)mouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event { - if (mouse_down_control) { - [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; - } else { - [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false]; - } -} - -- (void)mouseMoved:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); - NSPoint mpos = [event locationInWindow]; - - if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) { - return; - } - - Ref mm; - mm.instantiate(); - - mm->set_window_id(window_id); - mm->set_button_mask(ds->mouse_get_button_state()); - ds->update_mouse_pos(wd, mpos); - mm->set_position(wd.mouse_pos); - mm->set_pressure([event pressure]); - NSEventSubtype subtype = [event subtype]; - if (subtype == NSEventSubtypeTabletPoint) { - const NSPoint p = [event tilt]; - mm->set_tilt(Vector2(p.x, p.y)); - mm->set_pen_inverted(last_pen_inverted); - } else if (subtype == NSEventSubtypeTabletProximity) { - // Check if using the eraser end of pen only on proximity event. - last_pen_inverted = [event pointingDeviceType] == NSPointingDeviceTypeEraser; - mm->set_pen_inverted(last_pen_inverted); - } - mm->set_global_position(wd.mouse_pos); - mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale(); - mm->set_relative(relativeMotion); - ds->get_key_modifier_state([event modifierFlags], mm); - - Input::get_singleton()->parse_input_event(mm); -} - -- (void)rightMouseDown:(NSEvent *)event { - [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; -} - -- (void)rightMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)rightMouseUp:(NSEvent *)event { - [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; -} - -- (void)otherMouseDown:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true]; - } else if ((int)[event buttonNumber] == 3) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true]; - } else if ((int)[event buttonNumber] == 4) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true]; - } else { - return; - } -} - -- (void)otherMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)otherMouseUp:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false]; - } else if ((int)[event buttonNumber] == 3) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false]; - } else if ((int)[event buttonNumber] == 4) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false]; - } else { - return; - } -} - -- (void)mouseExited:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); - } -} - -- (void)mouseEntered:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); - } - - ds->cursor_update_shape(); -} - -- (void)magnifyWithEvent:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - Ref ev; - ev.instantiate(); - ev->set_window_id(window_id); - ds->get_key_modifier_state([event modifierFlags], ev); - ds->update_mouse_pos(wd, [event locationInWindow]); - ev->set_position(wd.mouse_pos); - ev->set_factor([event magnification] + 1.0); - - Input::get_singleton()->parse_input_event(ev); -} - -- (void)updateTrackingAreas { - if (tracking_area != nil) { - [self removeTrackingArea:tracking_area]; - } - - NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; - tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - - [self addTrackingArea:tracking_area]; - [super updateTrackingAreas]; -} - -// MARK: Keyboard - -- (void)keyDown:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress. - if (!ime_input_event_in_progress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { - // Fallback unicode character handler used if IME is not active. - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); - ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; - - ds->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 = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); - ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); - ke.raw = false; - ke.unicode = 0; - - ds->push_to_key_event_buffer(ke); - } - } - - // Pass events to IME handler - if (wd.im_active) { - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; - } -} - -- (void)flagsChanged:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress - if (!ime_input_event_in_progress) { - 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 = KeyMappingOSX::remap_key(key, mod); - ke.physical_keycode = KeyMappingOSX::translate_key(key); - ke.unicode = 0; - - ds->push_to_key_event_buffer(ke); - } -} - -- (void)keyUp:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - // Ignore all input if IME input is in progress. - if (!ime_input_event_in_progress) { - 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(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); - ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; - - ds->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 = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); - ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); - ke.raw = true; - ke.unicode = 0; - - ds->push_to_key_event_buffer(ke); - } - } -} - -// MARK: Scroll and pan - -- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - MouseButton mask = mouse_button_to_mask(button); - - Ref sc; - sc.instantiate(); - - sc->set_window_id(window_id); - ds->get_key_modifier_state([event 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); - MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask; - sc->set_button_mask(last_button_state); - ds->mouse_set_button_state(last_button_state); - - Input::get_singleton()->parse_input_event(sc); - - sc.instantiate(); - 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); - last_button_state &= (MouseButton)~mask; - sc->set_button_mask(last_button_state); - ds->mouse_set_button_state(last_button_state); - - Input::get_singleton()->parse_input_event(sc); -} - -- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - Ref pg; - pg.instantiate(); - - pg->set_window_id(window_id); - ds->get_key_modifier_state([event modifierFlags], pg); - pg->set_position(wd.mouse_pos); - pg->set_delta(Vector2(-dx, -dy)); - - Input::get_singleton()->parse_input_event(pg); -} - -- (void)scrollWheel:(NSEvent *)event { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - ds->update_mouse_pos(wd, [event locationInWindow]); - - double delta_x = [event scrollingDeltaX]; - double delta_y = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) { - delta_x *= 0.03; - delta_y *= 0.03; - } - - if ([event momentumPhase] != NSEventPhaseNone) { - if (ignore_momentum_scroll) { - return; - } - } else { - ignore_momentum_scroll = false; - } - - if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { - [self processPanEvent:event dx:delta_x dy:delta_y]; - } else { - if (fabs(delta_x)) { - [self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)]; - } - if (fabs(delta_y)) { - [self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)]; - } - } -} - -@end diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm deleted file mode 100644 index 722928ad60..0000000000 --- a/platform/osx/godot_main_osx.mm +++ /dev/null @@ -1,92 +0,0 @@ -/*************************************************************************/ -/* godot_main_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "main/main.h" - -#include "os_osx.h" - -#include -#include - -#if defined(SANITIZERS_ENABLED) -#include -#endif - -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 - -#if defined(SANITIZERS_ENABLED) - // Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times. - struct rlimit stack_lim = { 0x1E00000, 0x1E00000 }; - setrlimit(RLIMIT_STACK, &stack_lim); -#endif - - int first_arg = 1; - const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; - printf("arguments\n"); - for (int i = 0; i < argc; i++) { - if (strcmp(dbg_arg, argv[i]) == 0) { - first_arg = i + 2; - } - printf("%i: %s\n", i, argv[i]); - } - -#ifdef DEBUG_ENABLED - // Lets report the path we made current after all that. - char cwd[4096]; - getcwd(cwd, 4096); - printf("Current path: %s\n", cwd); -#endif - - OS_OSX os; - Error err; - - // We must override main when testing is enabled. - TEST_MAIN_OVERRIDE - - err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); - - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return 0; - } else if (err != OK) { - return 255; - } - - if (Main::start()) { - os.run(); // It is actually the OS that decides how to run. - } - - Main::cleanup(); - - return os.get_exit_code(); -} diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h deleted file mode 100644 index 2c12897f10..0000000000 --- a/platform/osx/godot_menu_item.h +++ /dev/null @@ -1,61 +0,0 @@ -/*************************************************************************/ -/* godot_menu_item.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_MENU_ITEM_H -#define GODOT_MENU_ITEM_H - -#include "servers/display_server.h" - -#import -#import - -enum GlobalMenuCheckType { - CHECKABLE_TYPE_NONE, - CHECKABLE_TYPE_CHECK_BOX, - CHECKABLE_TYPE_RADIO_BUTTON, -}; - -@interface GodotMenuItem : NSObject { -@public - Callable callback; - Variant meta; - int id; - GlobalMenuCheckType checkable_type; - int max_states; - int state; - Ref img; -} - -@end - -@implementation GodotMenuItem -@end - -#endif // GODOT_MENU_ITEM_H diff --git a/platform/osx/godot_window.h b/platform/osx/godot_window.h deleted file mode 100644 index 16ff101142..0000000000 --- a/platform/osx/godot_window.h +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************/ -/* godot_window.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_WINDOW_H -#define GODOT_WINDOW_H - -#include "servers/display_server.h" - -#import -#import - -@interface GodotWindow : NSWindow { - DisplayServer::WindowID window_id; -} - -- (void)setWindowID:(DisplayServer::WindowID)wid; - -@end - -#endif //GODOT_WINDOW_H diff --git a/platform/osx/godot_window.mm b/platform/osx/godot_window.mm deleted file mode 100644 index d43853a94b..0000000000 --- a/platform/osx/godot_window.mm +++ /dev/null @@ -1,69 +0,0 @@ -/*************************************************************************/ -/* godot_window.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_window.h" - -#include "display_server_osx.h" - -@implementation GodotWindow - -- (id)init { - self = [super init]; - window_id = DisplayServer::INVALID_WINDOW_ID; - return self; -} - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -- (BOOL)canBecomeKeyWindow { - // Required for NSWindowStyleMaskBorderless windows. - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return YES; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - return !wd.no_focus && !wd.is_popup; -} - -- (BOOL)canBecomeMainWindow { - // Required for NSWindowStyleMaskBorderless windows. - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return YES; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - return !wd.no_focus && !wd.is_popup; -} - -@end diff --git a/platform/osx/godot_window_delegate.h b/platform/osx/godot_window_delegate.h deleted file mode 100644 index 8a1f681fcd..0000000000 --- a/platform/osx/godot_window_delegate.h +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************/ -/* godot_window_delegate.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_WINDOW_DELEGATE_H -#define GODOT_WINDOW_DELEGATE_H - -#include "servers/display_server.h" - -#import -#import - -@interface GodotWindowDelegate : NSObject { - DisplayServer::WindowID window_id; -} - -- (void)setWindowID:(DisplayServer::WindowID)wid; - -@end - -#endif //GODOT_WINDOW_DELEGATE_H diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm deleted file mode 100644 index 521127f01b..0000000000 --- a/platform/osx/godot_window_delegate.mm +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* godot_window_delegate.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_window_delegate.h" - -#include "display_server_osx.h" - -@implementation GodotWindowDelegate - -- (void)setWindowID:(DisplayServer::WindowID)wid { - window_id = wid; -} - -- (BOOL)windowShouldClose:(id)sender { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return YES; - } - - ds->send_window_event(ds->get_window(window_id), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - return NO; -} - -- (void)windowWillClose:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - ds->popup_close(window_id); - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - while (wd.transient_children.size()) { - ds->window_set_transient(*wd.transient_children.begin(), DisplayServerOSX::INVALID_WINDOW_ID); - } - - if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { - ds->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); - } - - ds->window_destroy(window_id); -} - -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - wd.fullscreen = true; - // Reset window size limits. - [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; - [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - - // Force window resize event. - [self windowDidResize:notification]; -} - -- (void)windowDidExitFullScreen:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - wd.fullscreen = false; - - // Set window size limits. - const float scale = ds->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)]; - } - - // Restore resizability state. - if (wd.resize_disabled) { - [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; - } - - // Restore on-top state. - if (wd.on_top) { - [wd.window_object setLevel:NSFloatingWindowLevel]; - } - - // Force window resize event. - [self windowDidResize:notification]; -} - -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - CGFloat new_scale_factor = [wd.window_object backingScaleFactor]; - CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; - - if (new_scale_factor != old_scale_factor) { - // Set new display scale and window size. - const float scale = ds->screen_get_max_scale(); - const NSRect content_rect = [wd.window_view frame]; - - wd.size.width = content_rect.size.width * scale; - wd.size.height = content_rect.size.height * scale; - - ds->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)windowWillStartLiveResize:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->set_is_resizing(true); - } -} - -- (void)windowDidEndLiveResize:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (ds) { - ds->set_is_resizing(false); - } -} - -- (void)windowDidResize:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - const NSRect content_rect = [wd.window_view frame]; - const float scale = ds->screen_get_max_scale(); - wd.size.width = content_rect.size.width * scale; - wd.size.height = content_rect.size.height * scale; - - CALayer *layer = [wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } - - ds->window_resize(window_id, wd.size.width, wd.size.height); - - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(ds->window_get_position(window_id), ds->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)windowDidMove:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - ds->release_pressed_events(); - - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(ds->window_get_position(window_id), ds->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 { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) { - const NSRect content_rect = [wd.window_view frame]; - NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0); - NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin; - CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y }; - CGWarpMouseCursorPosition(mouse_warp_pos); - } else { - ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); - } - - ds->set_last_focused_window(window_id); - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); -} - -- (void)windowDidResignKey:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - ds->release_pressed_events(); - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); -} - -- (void)windowDidMiniaturize:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - ds->release_pressed_events(); - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); -} - -- (void)windowDidDeminiaturize:(NSNotification *)notification { - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (!ds || !ds->has_window(window_id)) { - return; - } - - DisplayServerOSX::WindowData &wd = ds->get_window(window_id); - - ds->set_last_focused_window(window_id); - ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); -} - -@end diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp deleted file mode 100644 index be9567e17c..0000000000 --- a/platform/osx/joypad_osx.cpp +++ /dev/null @@ -1,616 +0,0 @@ -/*************************************************************************/ -/* joypad_osx.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "joypad_osx.h" - -#include - -#define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad") - -static JoypadOSX *self = nullptr; - -joypad::joypad() { - ff_constant_force.lMagnitude = 10000; - ff_effect.dwDuration = 0; - ff_effect.dwSamplePeriod = 0; - ff_effect.dwGain = 10000; - ff_effect.dwFlags = FFEFF_OBJECTOFFSETS; - ff_effect.dwTriggerButton = FFEB_NOTRIGGER; - ff_effect.dwStartDelay = 0; - ff_effect.dwTriggerRepeatInterval = 0; - ff_effect.lpEnvelope = nullptr; - ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); - ff_effect.lpvTypeSpecificParams = &ff_constant_force; - ff_effect.dwSize = sizeof(ff_effect); -} - -void joypad::free() { - if (device_ref) { - IOHIDDeviceUnscheduleFromRunLoop(device_ref, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); - } - if (ff_device) { - FFDeviceReleaseEffect(ff_device, ff_object); - FFReleaseDevice(ff_device); - ff_device = nullptr; - memfree(ff_axes); - memfree(ff_directions); - } -} - -bool joypad::has_element(IOHIDElementCookie p_cookie, Vector *p_list) const { - for (int i = 0; i < p_list->size(); i++) { - if (p_cookie == p_list->get(i).cookie) { - return true; - } - } - return false; -} - -int joypad::get_hid_element_state(rec_element *p_element) const { - int value = 0; - if (p_element && p_element->ref) { - IOHIDValueRef valueRef; - if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) { - value = (SInt32)IOHIDValueGetIntegerValue(valueRef); - - // Record min and max for auto calibration. - if (value < p_element->min) { - p_element->min = value; - } - if (value > p_element->max) { - p_element->max = value; - } - } - } - return value; -} - -void joypad::add_hid_element(IOHIDElementRef p_element) { - const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; - - if (p_element && (elementTypeID == IOHIDElementGetTypeID())) { - const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element); - const uint32_t usagePage = IOHIDElementGetUsagePage(p_element); - const uint32_t usage = IOHIDElementGetUsage(p_element); - Vector *list = nullptr; - - switch (IOHIDElementGetType(p_element)) { - case kIOHIDElementTypeInput_Misc: - case kIOHIDElementTypeInput_Button: - case kIOHIDElementTypeInput_Axis: { - switch (usagePage) { - case kHIDPage_GenericDesktop: - switch (usage) { - case kHIDUsage_GD_X: - case kHIDUsage_GD_Y: - case kHIDUsage_GD_Z: - case kHIDUsage_GD_Rx: - case kHIDUsage_GD_Ry: - case kHIDUsage_GD_Rz: - case kHIDUsage_GD_Slider: - case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: - if (!has_element(cookie, &axis_elements)) { - list = &axis_elements; - } - break; - - case kHIDUsage_GD_Hatswitch: - if (!has_element(cookie, &hat_elements)) { - list = &hat_elements; - } - break; - case kHIDUsage_GD_DPadUp: - case kHIDUsage_GD_DPadDown: - case kHIDUsage_GD_DPadRight: - case kHIDUsage_GD_DPadLeft: - case kHIDUsage_GD_Start: - case kHIDUsage_GD_Select: - if (!has_element(cookie, &button_elements)) { - list = &button_elements; - } - break; - } - break; - - case kHIDPage_Simulation: - switch (usage) { - case kHIDUsage_Sim_Rudder: - case kHIDUsage_Sim_Throttle: - case kHIDUsage_Sim_Accelerator: - case kHIDUsage_Sim_Brake: - if (!has_element(cookie, &axis_elements)) { - list = &axis_elements; - } - break; - - default: - break; - } - break; - - case kHIDPage_Button: - case kHIDPage_Consumer: - if (!has_element(cookie, &button_elements)) { - list = &button_elements; - } - break; - - default: - break; - } - } break; - - case kIOHIDElementTypeCollection: { - CFArrayRef array = IOHIDElementGetChildren(p_element); - if (array) { - add_hid_elements(array); - } - } break; - - default: - break; - } - - if (list) { // Add to list. - rec_element element; - - element.ref = p_element; - element.usage = usage; - - element.min = (SInt32)IOHIDElementGetLogicalMin(p_element); - element.max = (SInt32)IOHIDElementGetLogicalMax(p_element); - element.cookie = IOHIDElementGetCookie(p_element); - list->push_back(element); - list->sort_custom(); - } - } -} - -static void hid_element_added(const void *p_value, void *p_parameter) { - joypad *joy = static_cast(p_parameter); - joy->add_hid_element((IOHIDElementRef)p_value); -} - -void joypad::add_hid_elements(CFArrayRef p_array) { - CFRange range = { 0, CFArrayGetCount(p_array) }; - CFArrayApplyFunction(p_array, range, hid_element_added, this); -} - -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) { - self->_device_added(res, ioHIDDeviceObject); -} - -static bool is_joypad(IOHIDDeviceRef p_device_ref) { - int usage_page = 0; - int usage = 0; - CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsagePageKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage_page); - } - if (usage_page != kHIDPage_GenericDesktop) { - return false; - } - - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsageKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage); - } - if ((usage != kHIDUsage_GD_Joystick && - usage != kHIDUsage_GD_GamePad && - usage != kHIDUsage_GD_MultiAxisController)) { - return false; - } - return true; -} - -void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { - if (p_res != kIOReturnSuccess || have_device(p_device)) { - return; - } - - joypad new_joypad; - if (is_joypad(p_device)) { - configure_joypad(p_device, &new_joypad); -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - if (IOHIDDeviceGetService) { -#endif - const io_service_t ioservice = IOHIDDeviceGetService(p_device); - if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) { - new_joypad.ffservice = ioservice; - } -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - } -#endif - device_list.push_back(new_joypad); - } - IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); -} - -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(device_list[device].id, false, ""); - device_list.write[device].free(); - device_list.remove_at(device); -} - -static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; - char ret[3]; - ret[2] = 0; - - ret[0] = dict[p_byte >> 4]; - ret[1] = dict[p_byte & 0xF]; - - return ret; -} - -bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { - p_joy->device_ref = p_device_ref; - // Get device name. - String name; - char c_name[256]; - CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); - if (!refCF) { - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDManufacturerKey)); - } - if ((!refCF) || (!CFStringGetCString((CFStringRef)refCF, c_name, sizeof(c_name), kCFStringEncodingUTF8))) { - name = "Unidentified Joypad"; - } else { - name = c_name; - } - - int id = input->get_unused_joy_id(); - ERR_FAIL_COND_V(id == -1, false); - p_joy->id = id; - int vendor = 0; - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVendorIDKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &vendor); - } - - int product_id = 0; - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductIDKey)); - 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, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); - input->joy_connection_changed(id, true, name, uid); - } else { - // Bluetooth device. - String guid = "05000000"; - for (int i = 0; i < 12; i++) { - if (i < name.size()) { - guid += _hex_str(name[i]); - } else { - guid += "00"; - } - } - input->joy_connection_changed(id, true, name, guid); - } - - CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone); - if (array) { - p_joy->add_hid_elements(array); - CFRelease(array); - } - // Xbox controller hat values start at 1 rather than 0. - p_joy->offset_hat = vendor == 0x45e && - (product_id == 0x0b05 || - product_id == 0x02e0 || - product_id == 0x02fd || - product_id == 0x0b13); - - return true; -} - -#define FF_ERR() \ - { \ - if (ret != FF_OK) { \ - FFReleaseDevice(ff_device); \ - ff_device = nullptr; \ - return false; \ - } \ - } -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); - - ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_RESET); - FF_ERR(); - - ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_SETACTUATORSON); - FF_ERR(); - - if (check_ff_features()) { - ret = FFDeviceCreateEffect(ff_device, kFFEffectType_ConstantForce_ID, &ff_effect, &ff_object); - FF_ERR(); - return true; - } - FFReleaseDevice(ff_device); - ff_device = nullptr; - return false; -} -#undef FF_ERR - -#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; - } - int num_axes = features.numFfAxes; - ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes); - ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes); - - for (int i = 0; i < num_axes; i++) { - ff_axes[i] = features.ffAxes[i]; - ff_directions[i] = 0; - } - - ff_effect.cAxes = num_axes; - ff_effect.rgdwAxes = ff_axes; - ff_effect.rglDirection = ff_directions; - return true; - } - return false; -} - -static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) { - int range = (p_max - p_min + 1); - int value = p_value - p_min; - HatMask hat_value = HatMask::CENTER; - if (range == 4) { - value *= 2; - } - if (p_offset_hat) { - value -= 1; - } - - switch (value) { - case 0: - hat_value = HatMask::UP; - break; - case 1: - hat_value = (HatMask::UP | HatMask::RIGHT); - break; - case 2: - hat_value = HatMask::RIGHT; - break; - case 3: - hat_value = (HatMask::DOWN | HatMask::RIGHT); - break; - case 4: - hat_value = HatMask::DOWN; - break; - case 5: - hat_value = (HatMask::DOWN | HatMask::LEFT); - break; - case 6: - hat_value = HatMask::LEFT; - break; - case 7: - hat_value = (HatMask::UP | HatMask::LEFT); - break; - default: - hat_value = HatMask::CENTER; - break; - } - return hat_value; -} - -void JoypadOSX::poll_joypads() const { - while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - // No-op. Pending callbacks will fire. - } -} - -static float axis_correct(int p_value, int p_min, int p_max) { - // Convert to a value between -1.0f and 1.0f. - return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f; -} - -void JoypadOSX::process_joypads() { - poll_joypads(); - - for (int i = 0; i < device_list.size(); i++) { - joypad &joy = device_list.write[i]; - - for (int j = 0; j < joy.axis_elements.size(); j++) { - rec_element &elem = joy.axis_elements.write[j]; - int value = joy.get_hid_element_state(&elem); - input->joy_axis(joy.id, (JoyAxis)j, axis_correct(value, elem.min, elem.max)); - } - for (int j = 0; j < joy.button_elements.size(); j++) { - int value = joy.get_hid_element_state(&joy.button_elements.write[j]); - input->joy_button(joy.id, (JoyButton)j, (value >= 1)); - } - for (int j = 0; j < joy.hat_elements.size(); j++) { - rec_element &elem = joy.hat_elements.write[j]; - int value = joy.get_hid_element_state(&elem); - HatMask hat_value = process_hat_value(elem.min, elem.max, value, joy.offset_hat); - input->joy_hat(joy.id, hat_value); - } - - if (joy.ffservice) { - uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); - if (timestamp > joy.ff_timestamp) { - Vector2 strength = input->get_joy_vibration_strength(joy.id); - float duration = input->get_joy_vibration_duration(joy.id); - if (strength.x == 0 && strength.y == 0) { - joypad_vibration_stop(joy.id, timestamp); - } else { - float gain = MAX(strength.x, strength.y); - joypad_vibration_start(joy.id, gain, duration, timestamp); - } - } - } - } -} - -void JoypadOSX::joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp) { - joypad *joy = &device_list.write[get_joy_index(p_id)]; - joy->ff_timestamp = p_timestamp; - joy->ff_effect.dwDuration = p_duration * FF_SECONDS; - joy->ff_effect.dwGain = p_magnitude * FF_FFNOMINALMAX; - FFEffectSetParameters(joy->ff_object, &joy->ff_effect, FFEP_DURATION | FFEP_GAIN); - FFEffectStart(joy->ff_object, 1, 0); -} - -void JoypadOSX::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { - joypad *joy = &device_list.write[get_joy_index(p_id)]; - joy->ff_timestamp = p_timestamp; - FFEffectStop(joy->ff_object); -} - -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; - } - } - 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; -} - -bool JoypadOSX::have_device(IOHIDDeviceRef p_device) const { - for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].device_ref == p_device) { - return true; - } - } - return false; -} - -static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) { - CFDictionaryRef retval = nullptr; - CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); - - if (pageNumRef && usageNumRef) { - const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; - const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; - retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - } - - if (pageNumRef) { - CFRelease(pageNumRef); - } - if (usageNumRef) { - CFRelease(usageNumRef); - } - - if (!retval) { - *okay = 0; - } - - return retval; -} - -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, 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) { - // No-op. Callback fires once per existing device. - } -} - -JoypadOSX::JoypadOSX(Input *in) { - self = this; - input = in; - - int okay = 1; - const void *vals[] = { - (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), - (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), - (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) : nullptr; - - for (size_t i = 0; i < n_elements; i++) { - if (vals[i]) { - CFRelease((CFTypeRef)vals[i]); - } - } - - if (array) { - hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (hid_manager) { - config_hid_manager(array); - } - CFRelease(array); - } -} - -JoypadOSX::~JoypadOSX() { - for (int i = 0; i < device_list.size(); i++) { - device_list.write[i].free(); - } - - IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); - IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone); - CFRelease(hid_manager); - hid_manager = nullptr; -} diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h deleted file mode 100644 index 3f89048ce6..0000000000 --- a/platform/osx/joypad_osx.h +++ /dev/null @@ -1,124 +0,0 @@ -/*************************************************************************/ -/* joypad_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 JOYPADOSX_H -#define JOYPADOSX_H - -#ifdef MACOS_10_0_4 -#import -#else -#import -#endif -#import -#import -#import - -#include "core/input/input.h" - -struct rec_element { - IOHIDElementRef ref; - IOHIDElementCookie cookie; - - uint32_t usage = 0; - - int min = 0; - int max = 0; - - struct Comparator { - bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; } - }; -}; - -struct joypad { - IOHIDDeviceRef device_ref = nullptr; - - Vector axis_elements; - Vector button_elements; - Vector hat_elements; - - int id = 0; - bool offset_hat = false; - - io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff. - FFCONSTANTFORCE ff_constant_force; - FFDeviceObjectReference ff_device = nullptr; - FFEffectObjectReference ff_object = nullptr; - uint64_t ff_timestamp = 0; - LONG *ff_directions = nullptr; - FFEFFECT ff_effect; - DWORD *ff_axes = nullptr; - - void add_hid_elements(CFArrayRef p_array); - void add_hid_element(IOHIDElementRef p_element); - - bool has_element(IOHIDElementCookie p_cookie, Vector *p_list) const; - bool config_force_feedback(io_service_t p_service); - bool check_ff_features(); - - int get_hid_element_state(rec_element *p_element) const; - - void free(); - joypad(); -}; - -class JoypadOSX { - enum { - JOYPADS_MAX = 16, - }; - -private: - Input *input = nullptr; - IOHIDManagerRef hid_manager; - - Vector device_list; - - bool have_device(IOHIDDeviceRef p_device) const; - 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 config_hid_manager(CFArrayRef p_matching_array) const; - - void joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp); - void joypad_vibration_stop(int p_id, uint64_t p_timestamp); - -public: - void process_joypads(); - - void _device_added(IOReturn p_res, IOHIDDeviceRef p_device); - void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device); - - JoypadOSX(Input *in); - ~JoypadOSX(); -}; - -#endif // JOYPADOSX_H diff --git a/platform/osx/key_mapping_osx.h b/platform/osx/key_mapping_osx.h deleted file mode 100644 index 252cc907bb..0000000000 --- a/platform/osx/key_mapping_osx.h +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* key_mapping_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 KEY_MAPPING_OSX_H -#define KEY_MAPPING_OSX_H - -#include "core/os/keyboard.h" - -class KeyMappingOSX { - KeyMappingOSX() {} - - static bool is_numpad_key(unsigned int key); - -public: - // Mappings input. - static Key translate_key(unsigned int key); - static unsigned int unmap_key(Key key); - static Key remap_key(unsigned int key, unsigned int state); - - // Mapping for menu shortcuts. - static String keycode_get_native_string(Key p_keycode); - static unsigned int keycode_get_native_mask(Key p_keycode); -}; - -#endif // KEY_MAPPING_OSX_H diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm deleted file mode 100644 index 0bf6bc7d1c..0000000000 --- a/platform/osx/key_mapping_osx.mm +++ /dev/null @@ -1,496 +0,0 @@ -/*************************************************************************/ -/* key_mapping_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "key_mapping_osx.h" - -#import -#import - -bool KeyMappingOSX::is_numpad_key(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; -} - -// Keyboard symbol translation table. -static const Key _osx_to_godot_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::KEY_1, - /* 13 */ Key::KEY_2, - /* 14 */ Key::KEY_3, - /* 15 */ Key::KEY_4, - /* 16 */ Key::KEY_6, - /* 17 */ Key::KEY_5, - /* 18 */ Key::EQUAL, - /* 19 */ Key::KEY_9, - /* 1a */ Key::KEY_7, - /* 1b */ Key::MINUS, - /* 1c */ Key::KEY_8, - /* 1d */ Key::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::CTRL, - /* 3c */ Key::SHIFT, - /* 3d */ Key::ALT, - /* 3e */ Key::CTRL, - /* 3f */ Key::UNKNOWN, /* Function */ - /* 40 */ Key::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::F18, - /* 50 */ Key::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::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::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, -}; - -// Translates a OS X keycode to a Godot keycode. -Key KeyMappingOSX::translate_key(unsigned int key) { - if (key >= 128) { - return Key::UNKNOWN; - } - - return _osx_to_godot_table[key]; -} - -// Translates a Godot keycode back to a OSX keycode. -unsigned int KeyMappingOSX::unmap_key(Key key) { - for (int i = 0; i <= 126; i++) { - if (_osx_to_godot_table[i] == key) { - return i; - } - } - return 127; -} - -struct _KeyCodeMap { - UniChar kchar; - Key kcode; -}; - -static const _KeyCodeMap _keycodes[55] = { - { '`', Key::QUOTELEFT }, - { '~', Key::ASCIITILDE }, - { '0', Key::KEY_0 }, - { '1', Key::KEY_1 }, - { '2', Key::KEY_2 }, - { '3', Key::KEY_3 }, - { '4', Key::KEY_4 }, - { '5', Key::KEY_5 }, - { '6', Key::KEY_6 }, - { '7', Key::KEY_7 }, - { '8', Key::KEY_8 }, - { '9', Key::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 } -}; - -// Remap key according to current keyboard layout. -Key KeyMappingOSX::remap_key(unsigned int key, unsigned int state) { - if (is_numpad_key(key)) { - return translate_key(key); - } - - TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource(); - if (!current_keyboard) { - return translate_key(key); - } - - CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layout_data) { - return translate_key(key); - } - - const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); - - UInt32 keys_down = 0; - UniChar chars[4]; - UniCharCount real_length; - - OSStatus err = UCKeyTranslate(keyboard_layout, - key, - kUCKeyActionDisplay, - (state >> 8) & 0xFF, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keys_down, - sizeof(chars) / sizeof(chars[0]), - &real_length, - chars); - - if (err != noErr) { - return translate_key(key); - } - - for (unsigned int i = 0; i < 55; i++) { - if (_keycodes[i].kchar == chars[0]) { - return _keycodes[i].kcode; - } - } - return translate_key(key); -} - -struct _KeyCodeText { - Key code; - char32_t text; -}; - -static const _KeyCodeText _native_keycodes[] = { - /* clang-format off */ - {Key::ESCAPE ,0x001B}, - {Key::TAB ,0x0009}, - {Key::BACKTAB ,0x007F}, - {Key::BACKSPACE ,0x0008}, - {Key::ENTER ,0x000D}, - {Key::INSERT ,NSInsertFunctionKey}, - {Key::KEY_DELETE ,0x007F}, - {Key::PAUSE ,NSPauseFunctionKey}, - {Key::PRINT ,NSPrintScreenFunctionKey}, - {Key::SYSREQ ,NSSysReqFunctionKey}, - {Key::CLEAR ,NSClearLineFunctionKey}, - {Key::HOME ,0x2196}, - {Key::END ,0x2198}, - {Key::LEFT ,0x001C}, - {Key::UP ,0x001E}, - {Key::RIGHT ,0x001D}, - {Key::DOWN ,0x001F}, - {Key::PAGEUP ,0x21DE}, - {Key::PAGEDOWN ,0x21DF}, - {Key::NUMLOCK ,NSClearLineFunctionKey}, - {Key::SCROLLLOCK ,NSScrollLockFunctionKey}, - {Key::F1 ,NSF1FunctionKey}, - {Key::F2 ,NSF2FunctionKey}, - {Key::F3 ,NSF3FunctionKey}, - {Key::F4 ,NSF4FunctionKey}, - {Key::F5 ,NSF5FunctionKey}, - {Key::F6 ,NSF6FunctionKey}, - {Key::F7 ,NSF7FunctionKey}, - {Key::F8 ,NSF8FunctionKey}, - {Key::F9 ,NSF9FunctionKey}, - {Key::F10 ,NSF10FunctionKey}, - {Key::F11 ,NSF11FunctionKey}, - {Key::F12 ,NSF12FunctionKey}, - {Key::F13 ,NSF13FunctionKey}, - {Key::F14 ,NSF14FunctionKey}, - {Key::F15 ,NSF15FunctionKey}, - {Key::F16 ,NSF16FunctionKey}, - {Key::F17 ,NSF17FunctionKey}, - {Key::F18 ,NSF18FunctionKey}, - {Key::F19 ,NSF19FunctionKey}, - {Key::F20 ,NSF20FunctionKey}, - {Key::F21 ,NSF21FunctionKey}, - {Key::F22 ,NSF22FunctionKey}, - {Key::F23 ,NSF23FunctionKey}, - {Key::F24 ,NSF24FunctionKey}, - {Key::F25 ,NSF25FunctionKey}, - {Key::F26 ,NSF26FunctionKey}, - {Key::F27 ,NSF27FunctionKey}, - {Key::F28 ,NSF28FunctionKey}, - {Key::F29 ,NSF29FunctionKey}, - {Key::F30 ,NSF30FunctionKey}, - {Key::F31 ,NSF31FunctionKey}, - {Key::F32 ,NSF32FunctionKey}, - {Key::F33 ,NSF33FunctionKey}, - {Key::F34 ,NSF34FunctionKey}, - {Key::F35 ,NSF35FunctionKey}, - {Key::MENU ,NSMenuFunctionKey}, - {Key::HELP ,NSHelpFunctionKey}, - {Key::STOP ,NSStopFunctionKey}, - {Key::LAUNCH0 ,NSUserFunctionKey}, - {Key::SPACE ,0x0020}, - {Key::EXCLAM ,'!'}, - {Key::QUOTEDBL ,'\"'}, - {Key::NUMBERSIGN ,'#'}, - {Key::DOLLAR ,'$'}, - {Key::PERCENT ,'\%'}, - {Key::AMPERSAND ,'&'}, - {Key::APOSTROPHE ,'\''}, - {Key::PARENLEFT ,'('}, - {Key::PARENRIGHT ,')'}, - {Key::ASTERISK ,'*'}, - {Key::PLUS ,'+'}, - {Key::COMMA ,','}, - {Key::MINUS ,'-'}, - {Key::PERIOD ,'.'}, - {Key::SLASH ,'/'}, - {Key::KEY_0 ,'0'}, - {Key::KEY_1 ,'1'}, - {Key::KEY_2 ,'2'}, - {Key::KEY_3 ,'3'}, - {Key::KEY_4 ,'4'}, - {Key::KEY_5 ,'5'}, - {Key::KEY_6 ,'6'}, - {Key::KEY_7 ,'7'}, - {Key::KEY_8 ,'8'}, - {Key::KEY_9 ,'9'}, - {Key::COLON ,':'}, - {Key::SEMICOLON ,';'}, - {Key::LESS ,'<'}, - {Key::EQUAL ,'='}, - {Key::GREATER ,'>'}, - {Key::QUESTION ,'?'}, - {Key::AT ,'@'}, - {Key::A ,'a'}, - {Key::B ,'b'}, - {Key::C ,'c'}, - {Key::D ,'d'}, - {Key::E ,'e'}, - {Key::F ,'f'}, - {Key::G ,'g'}, - {Key::H ,'h'}, - {Key::I ,'i'}, - {Key::J ,'j'}, - {Key::K ,'k'}, - {Key::L ,'l'}, - {Key::M ,'m'}, - {Key::N ,'n'}, - {Key::O ,'o'}, - {Key::P ,'p'}, - {Key::Q ,'q'}, - {Key::R ,'r'}, - {Key::S ,'s'}, - {Key::T ,'t'}, - {Key::U ,'u'}, - {Key::V ,'v'}, - {Key::W ,'w'}, - {Key::X ,'x'}, - {Key::Y ,'y'}, - {Key::Z ,'z'}, - {Key::BRACKETLEFT ,'['}, - {Key::BACKSLASH ,'\\'}, - {Key::BRACKETRIGHT ,']'}, - {Key::ASCIICIRCUM ,'^'}, - {Key::UNDERSCORE ,'_'}, - {Key::QUOTELEFT ,'`'}, - {Key::BRACELEFT ,'{'}, - {Key::BAR ,'|'}, - {Key::BRACERIGHT ,'}'}, - {Key::ASCIITILDE ,'~'}, - {Key::NONE ,0x0000} - /* clang-format on */ -}; - -String KeyMappingOSX::keycode_get_native_string(Key p_keycode) { - const _KeyCodeText *kct = &_native_keycodes[0]; - - while (kct->text) { - if (kct->code == p_keycode) { - return String::chr(kct->text); - } - kct++; - } - return String(); -} - -unsigned int KeyMappingOSX::keycode_get_native_mask(Key p_keycode) { - unsigned int mask = 0; - if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) { - mask |= NSEventModifierFlagControl; - } - if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) { - mask |= NSEventModifierFlagOption; - } - if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) { - mask |= NSEventModifierFlagShift; - } - if ((p_keycode & KeyModifierMask::META) != Key::NONE) { - mask |= NSEventModifierFlagCommand; - } - if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) { - mask |= NSEventModifierFlagNumericPad; - } - return mask; -} diff --git a/platform/osx/logo.png b/platform/osx/logo.png deleted file mode 100644 index b5a660b165..0000000000 Binary files a/platform/osx/logo.png and /dev/null differ diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h deleted file mode 100644 index b105be4a06..0000000000 --- a/platform/osx/os_osx.h +++ /dev/null @@ -1,120 +0,0 @@ -/*************************************************************************/ -/* os_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_OSX_H -#define OS_OSX_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 "servers/audio_server.h" - -class OS_OSX : public OS_Unix { - bool force_quit = false; - - JoypadOSX *joypad_osx = nullptr; - -#ifdef COREAUDIO_ENABLED - AudioDriverCoreAudio audio_driver; -#endif -#ifdef COREMIDI_ENABLED - MIDIDriverCoreMidi midi_driver; -#endif - - CrashHandler crash_handler; - - CFRunLoopObserverRef pre_wait_observer; - - MainLoop *main_loop = nullptr; - - List launch_service_args; - - static _FORCE_INLINE_ String get_framework_executable(const String &p_path); - static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); - -protected: - virtual void initialize_core() override; - virtual void initialize() override; - virtual void finalize() override; - - virtual void initialize_joypads() override; - - virtual void set_main_loop(MainLoop *p_main_loop) override; - virtual void delete_main_loop() override; - -public: - virtual void set_cmdline_platform_args(const List &p_args); - virtual List get_cmdline_platform_args() const override; - - virtual String get_name() const override; - - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; - - virtual MainLoop *get_main_loop() const override; - - virtual String get_config_path() const override; - virtual String get_data_path() const override; - virtual String get_cache_path() const override; - virtual String get_bundle_resource_dir() const override; - virtual String get_bundle_icon_path() const override; - virtual String get_godot_dir_name() const override; - - virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; - - virtual Error shell_open(String p_uri) override; - - virtual String get_locale() const override; - - virtual String get_executable_path() const override; - virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; - virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; - - virtual String get_unique_id() const override; - virtual String get_processor_name() const override; - - virtual bool _check_internal_feature_support(const String &p_feature) override; - - virtual void disable_crash_handler() override; - virtual bool is_disable_crash_handler() const override; - - virtual Error move_to_trash(const String &p_path) override; - - void run(); - - OS_OSX(); - ~OS_OSX(); -}; - -#endif diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm deleted file mode 100644 index 5230ed4155..0000000000 --- a/platform/osx/os_osx.mm +++ /dev/null @@ -1,524 +0,0 @@ -/*************************************************************************/ -/* os_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_osx.h" - -#include "core/version_generated.gen.h" -#include "main/main.h" - -#include "dir_access_osx.h" -#include "display_server_osx.h" -#include "godot_application.h" -#include "godot_application_delegate.h" -#include "osx_terminal_logger.h" - -#include -#include -#include -#include -#include - -_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) { - // Append framework executable name, or return as is if p_path is not a framework. - Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { - return p_path.plus_file(p_path.get_file().get_basename()); - } else { - return p_path; - } -} - -void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { - // Prevent main loop from sleeping and redraw window during resize / modal popups. - - DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); - if (get_singleton()->get_main_loop() && ds && (get_singleton()->get_render_thread_mode() != RENDER_SEPARATE_THREAD || !ds->get_is_resizing())) { - Main::force_redraw(); - if (!Main::is_iterating()) { // Avoid cyclic loop. - Main::iteration(); - } - } - - CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. -} - -void OS_OSX::initialize() { - crash_handler.initialize(); - - initialize_core(); -} - -String OS_OSX::get_processor_name() const { - char buffer[256]; - size_t buffer_len = 256; - if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) { - return String::utf8(buffer, buffer_len); - } - ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); -} - -void OS_OSX::initialize_core() { - OS_Unix::initialize_core(); - - DirAccess::make_default(DirAccess::ACCESS_RESOURCES); - DirAccess::make_default(DirAccess::ACCESS_USERDATA); - DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); -} - -void OS_OSX::finalize() { -#ifdef COREMIDI_ENABLED - midi_driver.close(); -#endif - - delete_main_loop(); - - if (joypad_osx) { - memdelete(joypad_osx); - } -} - -void OS_OSX::initialize_joypads() { - joypad_osx = memnew(JoypadOSX(Input::get_singleton())); -} - -void OS_OSX::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; -} - -void OS_OSX::delete_main_loop() { - if (!main_loop) { - return; - } - - memdelete(main_loop); - main_loop = nullptr; -} - -void OS_OSX::set_cmdline_platform_args(const List &p_args) { - launch_service_args = p_args; -} - -List OS_OSX::get_cmdline_platform_args() const { - return launch_service_args; -} - -String OS_OSX::get_name() const { - return "macOS"; -} - -void OS_OSX::alert(const String &p_alert, const String &p_title) { - 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]; - if (key_window) { - [key_window makeKeyAndOrderFront:nil]; - } -} - -Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { - String path = get_framework_executable(p_path); - - if (!FileAccess::exists(path)) { - // Load .dylib or framework from within the executable path. - path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); - } - - if (!FileAccess::exists(path)) { - // Load .dylib or framework from a standard macOS location. - path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); - } - - p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); - ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); - - if (r_resolved_path != nullptr) { - *r_resolved_path = path; - } - - return OK; -} - -MainLoop *OS_OSX::get_main_loop() const { - return main_loop; -} - -String OS_OSX::get_config_path() const { - // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. - if (has_environment("XDG_CONFIG_HOME")) { - if (get_environment("XDG_CONFIG_HOME").is_absolute_path()) { - return get_environment("XDG_CONFIG_HOME"); - } else { - WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Application Support` or `.` per the XDG Base Directory specification."); - } - } - if (has_environment("HOME")) { - return get_environment("HOME").plus_file("Library/Application Support"); - } - return "."; -} - -String OS_OSX::get_data_path() const { - // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. - if (has_environment("XDG_DATA_HOME")) { - if (get_environment("XDG_DATA_HOME").is_absolute_path()) { - return get_environment("XDG_DATA_HOME"); - } else { - WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); - } - } - return get_config_path(); -} - -String OS_OSX::get_cache_path() const { - // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. - if (has_environment("XDG_CACHE_HOME")) { - if (get_environment("XDG_CACHE_HOME").is_absolute_path()) { - return get_environment("XDG_CACHE_HOME"); - } else { - WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Caches` or `get_config_path()` per the XDG Base Directory specification."); - } - } - if (has_environment("HOME")) { - return get_environment("HOME").plus_file("Library/Caches"); - } - return get_config_path(); -} - -String OS_OSX::get_bundle_resource_dir() const { - String ret; - - NSBundle *main = [NSBundle mainBundle]; - if (main) { - NSString *resource_path = [main resourcePath]; - ret.parse_utf8([resource_path UTF8String]); - } - return ret; -} - -String OS_OSX::get_bundle_icon_path() const { - String ret; - - NSBundle *main = [NSBundle mainBundle]; - if (main) { - NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; - if (icon_path) { - ret.parse_utf8([icon_path UTF8String]); - } - } - return ret; -} - -// 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, bool p_shared_storage) const { - NSSearchPathDirectory id; - bool found = true; - - switch (p_dir) { - case SYSTEM_DIR_DESKTOP: { - id = NSDesktopDirectory; - } break; - case SYSTEM_DIR_DOCUMENTS: { - id = NSDocumentDirectory; - } break; - case SYSTEM_DIR_DOWNLOADS: { - id = NSDownloadsDirectory; - } break; - case SYSTEM_DIR_MOVIES: { - id = NSMoviesDirectory; - } break; - case SYSTEM_DIR_MUSIC: { - id = NSMusicDirectory; - } break; - case SYSTEM_DIR_PICTURES: { - id = NSPicturesDirectory; - } break; - default: { - found = false; - } - } - - String ret; - if (found) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); - if (paths && [paths count] >= 1) { - ret.parse_utf8([[paths firstObject] UTF8String]); - } - } - - return ret; -} - -Error OS_OSX::shell_open(String p_uri) { - 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; -} - -String OS_OSX::get_locale() const { - NSString *locale_code = [[NSLocale preferredLanguages] objectAtIndex:0]; - return String([locale_code UTF8String]).replace("-", "_"); -} - -String OS_OSX::get_executable_path() const { - char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - int pid = getpid(); - pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); - if (ret <= 0) { - return OS::get_executable_path(); - } else { - String path; - path.parse_utf8(pathbuf); - - return path; - } -} - -Error OS_OSX::create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id, bool p_open_console) { - // Use NSWorkspace if path is an .app bundle. - NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; - NSBundle *bundle = [NSBundle bundleWithURL:url]; - if (bundle) { - NSMutableArray *arguments = [[NSMutableArray alloc] init]; - for (const String &arg : p_arguments) { - [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]]; - } - if (@available(macOS 10.15, *)) { - NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; - [configuration setArguments:arguments]; - [configuration setCreatesNewApplicationInstance:YES]; - __block dispatch_semaphore_t lock = dispatch_semaphore_create(0); - __block Error err = ERR_TIMEOUT; - __block pid_t pid = 0; - - [[NSWorkspace sharedWorkspace] openApplicationAtURL:url - configuration:configuration - completionHandler:^(NSRunningApplication *app, NSError *error) { - if (error) { - err = ERR_CANT_FORK; - NSLog(@"Failed to execute: %@", error.localizedDescription); - } else { - pid = [app processIdentifier]; - err = OK; - } - dispatch_semaphore_signal(lock); - }]; - dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. - - if (err == OK) { - if (r_child_id) { - *r_child_id = (ProcessID)pid; - } - } - - return err; - } else { - Error err = ERR_TIMEOUT; - NSError *error = nullptr; - NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; - if (error) { - err = ERR_CANT_FORK; - NSLog(@"Failed to execute: %@", error.localizedDescription); - } else { - if (r_child_id) { - *r_child_id = (ProcessID)[app processIdentifier]; - } - err = OK; - } - return err; - } - } else { - return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console); - } -} - -Error OS_OSX::create_instance(const List &p_arguments, ProcessID *r_child_id) { - // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname != nil) { - String path; - path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); - return create_process(path, p_arguments, r_child_id, false); - } else { - return create_process(get_executable_path(), p_arguments, r_child_id, false); - } -} - -String OS_OSX::get_unique_id() const { - static String serial_number; - - if (serial_number.is_empty()) { - io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - CFStringRef serial_number_cf_string = nullptr; - if (platform_expert) { - serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); - IOObjectRelease(platform_expert); - } - - NSString *serial_number_ns_string = nil; - if (serial_number_cf_string) { - serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string]; - CFRelease(serial_number_cf_string); - } - - if (serial_number_ns_string) { - serial_number.parse_utf8([serial_number_ns_string UTF8String]); - } - } - - return serial_number; -} - -bool OS_OSX::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; -} - -void OS_OSX::disable_crash_handler() { - crash_handler.disable(); -} - -bool OS_OSX::is_disable_crash_handler() const { - return crash_handler.is_disabled(); -} - -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_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); - return FAILED; - } - - return OK; -} - -void OS_OSX::run() { - force_quit = false; - - if (!main_loop) { - return; - } - - main_loop->initialize(); - - bool quit = false; - while (!force_quit && !quit) { - @try { - if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); // Get rid of pending events. - } - joypad_osx->process_joypads(); - - if (Main::iteration()) { - quit = true; - } - } @catch (NSException *exception) { - ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); - } - } - - main_loop->finalize(); -} - -OS_OSX::OS_OSX() { - main_loop = nullptr; - force_quit = false; - - Vector loggers; - loggers.push_back(memnew(OSXTerminalLogger)); - _set_logger(memnew(CompositeLogger(loggers))); - -#ifdef COREAUDIO_ENABLED - AudioDriverManager::add_driver(&audio_driver); -#endif - - DisplayServerOSX::register_osx_driver(); - - // 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. - - NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; - [NSApp setMainMenu:main_menu]; - [NSApp finishLaunching]; - - id delegate = [[GodotApplicationDelegate alloc] init]; - ERR_FAIL_COND(!delegate); - [NSApp setDelegate:delegate]; - - pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - - // 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]; - } - - [NSApp activateIgnoringOtherApps:YES]; -} - -OS_OSX::~OS_OSX() { - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - CFRelease(pre_wait_observer); -} diff --git a/platform/osx/osx_terminal_logger.h b/platform/osx/osx_terminal_logger.h deleted file mode 100644 index 8413509c4b..0000000000 --- a/platform/osx/osx_terminal_logger.h +++ /dev/null @@ -1,44 +0,0 @@ -/*************************************************************************/ -/* osx_terminal_logger.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 OSX_TERMINAL_LOGGER_H -#define OSX_TERMINAL_LOGGER_H - -#ifdef OSX_ENABLED - -#include "core/io/logger.h" - -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, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; -}; - -#endif // OSX_ENABLED -#endif // OSX_TERMINAL_LOGGER_H diff --git a/platform/osx/osx_terminal_logger.mm b/platform/osx/osx_terminal_logger.mm deleted file mode 100644 index 48e26f42bf..0000000000 --- a/platform/osx/osx_terminal_logger.mm +++ /dev/null @@ -1,82 +0,0 @@ -/*************************************************************************/ -/* osx_terminal_logger.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "osx_terminal_logger.h" - -#ifdef OSX_ENABLED - -#include - -void OSXTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { - 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: - 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; - } -} - -#endif // OSX_ENABLED diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h deleted file mode 100644 index e114606b82..0000000000 --- a/platform/osx/platform_config.h +++ /dev/null @@ -1,34 +0,0 @@ -/*************************************************************************/ -/* platform_config.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 - -#define OPENGL_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 deleted file mode 100644 index 953ed479db..0000000000 --- a/platform/osx/platform_osx_builders.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Functions used to generate source files during build time - -All such functions are invoked in a subprocess on Windows to prevent build flakiness. - -""" -import os -from platform_methods import subprocess_main - - -def make_debug_osx(target, source, env): - 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])) - else: - os.system("dsymutil {0} -o {0}.dSYM".format(target[0])) - os.system("strip -u -r {0}".format(target[0])) - - -if __name__ == "__main__": - subprocess_main(globals()) diff --git a/platform/osx/tts_osx.h b/platform/osx/tts_osx.h deleted file mode 100644 index 449418e48f..0000000000 --- a/platform/osx/tts_osx.h +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* tts_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 TTS_OSX_H -#define TTS_OSX_H - -#include "core/string/ustring.h" -#include "core/templates/list.h" -#include "core/templates/rb_map.h" -#include "core/variant/array.h" -#include "servers/display_server.h" - -#import - -#if __has_include() -#import -#else -#import -#endif - -@interface TTS_OSX : NSObject { - // AVSpeechSynthesizer - bool speaking; - HashMap ids; - - // NSSpeechSynthesizer - bool paused; - bool have_utterance; - int last_utterance; - - id synth; // NSSpeechSynthesizer or AVSpeechSynthesizer - List queue; -} - -- (void)pauseSpeaking; -- (void)resumeSpeaking; -- (void)stopSpeaking; -- (bool)isSpeaking; -- (bool)isPaused; -- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt; -- (Array)getVoices; -@end - -#endif // TTS_OSX_H diff --git a/platform/osx/tts_osx.mm b/platform/osx/tts_osx.mm deleted file mode 100644 index e6a5236cd9..0000000000 --- a/platform/osx/tts_osx.mm +++ /dev/null @@ -1,266 +0,0 @@ -/*************************************************************************/ -/* tts_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "tts_osx.h" - -@implementation TTS_OSX - -- (id)init { - self = [super init]; - self->speaking = false; - self->have_utterance = false; - self->last_utterance = -1; - self->paused = false; - if (@available(macOS 10.14, *)) { - self->synth = [[AVSpeechSynthesizer alloc] init]; - [self->synth setDelegate:self]; - print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized."); - } else { - self->synth = [[NSSpeechSynthesizer alloc] init]; - [self->synth setDelegate:self]; - print_verbose("Text-to-Speech: NSSpeechSynthesizer initialized."); - } - return self; -} - -// AVSpeechSynthesizer callback (macOS 10.14+) - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { - NSString *string = [utterance speechString]; - - // Convert from UTF-16 to UTF-32 position. - int pos = 0; - for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { - unichar c = [string characterAtIndex:i]; - if ((c & 0xfffffc00) == 0xd800) { - i++; - } - pos++; - } - - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos); -} - -// AVSpeechSynthesizer callback (macOS 10.14+) - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]); - ids.erase(utterance); - speaking = false; - [self update]; -} - -// AVSpeechSynthesizer callback (macOS 10.14+) - -- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]); - ids.erase(utterance); - speaking = false; - [self update]; -} - -// NSSpeechSynthesizer callback (macOS 10.4+) - -- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth willSpeakWord:(NSRange)characterRange ofString:(NSString *)string { - if (!paused && have_utterance) { - // Convert from UTF-16 to UTF-32 position. - int pos = 0; - for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) { - unichar c = [string characterAtIndex:i]; - if ((c & 0xfffffc00) == 0xd800) { - i++; - } - pos++; - } - - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, last_utterance, pos); - } -} - -- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth didFinishSpeaking:(BOOL)success { - if (!paused && have_utterance) { - if (success) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, last_utterance); - } else { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); - } - have_utterance = false; - } - speaking = false; - [self update]; -} - -- (void)update { - if (!speaking && queue.size() > 0) { - DisplayServer::TTSUtterance &message = queue.front()->get(); - - if (@available(macOS 10.14, *)) { - AVSpeechSynthesizer *av_synth = synth; - AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; - [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]]; - if (message.rate > 1.f) { - [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)]; - } else if (message.rate < 1.f) { - [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)]; - } - [new_utterance setPitchMultiplier:message.pitch]; - [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; - - ids[new_utterance] = message.id; - [av_synth speakUtterance:new_utterance]; - } else { - NSSpeechSynthesizer *ns_synth = synth; - [ns_synth setObject:nil forProperty:NSSpeechResetProperty error:nil]; - [ns_synth setVoice:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]; - int base_pitch = [[ns_synth objectForProperty:NSSpeechPitchBaseProperty error:nil] intValue]; - [ns_synth setObject:[NSNumber numberWithInt:(base_pitch * (message.pitch / 2.f + 0.5f))] forProperty:NSSpeechPitchBaseProperty error:nullptr]; - [ns_synth setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))]; - [ns_synth setRate:(message.rate * 200)]; - - last_utterance = message.id; - have_utterance = true; - [ns_synth startSpeakingString:[NSString stringWithUTF8String:message.text.utf8().get_data()]]; - } - queue.pop_front(); - - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); - speaking = true; - } -} - -- (void)pauseSpeaking { - if (@available(macOS 10.14, *)) { - AVSpeechSynthesizer *av_synth = synth; - [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; - } else { - NSSpeechSynthesizer *ns_synth = synth; - [ns_synth pauseSpeakingAtBoundary:NSSpeechImmediateBoundary]; - } - paused = true; -} - -- (void)resumeSpeaking { - if (@available(macOS 10.14, *)) { - AVSpeechSynthesizer *av_synth = synth; - [av_synth continueSpeaking]; - } else { - NSSpeechSynthesizer *ns_synth = synth; - [ns_synth continueSpeaking]; - } - paused = false; -} - -- (void)stopSpeaking { - for (DisplayServer::TTSUtterance &message : queue) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); - } - queue.clear(); - if (@available(macOS 10.14, *)) { - AVSpeechSynthesizer *av_synth = synth; - [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; - } else { - NSSpeechSynthesizer *ns_synth = synth; - if (have_utterance) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance); - } - [ns_synth stopSpeaking]; - } - have_utterance = false; - speaking = false; - paused = false; -} - -- (bool)isSpeaking { - return speaking || (queue.size() > 0); -} - -- (bool)isPaused { - if (@available(macOS 10.14, *)) { - AVSpeechSynthesizer *av_synth = synth; - return [av_synth isPaused]; - } else { - return paused; - } -} - -- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt { - if (interrupt) { - [self stopSpeaking]; - } - - if (text.is_empty()) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id); - return; - } - - DisplayServer::TTSUtterance message; - message.text = text; - message.voice = voice; - message.volume = CLAMP(volume, 0, 100); - message.pitch = CLAMP(pitch, 0.f, 2.f); - message.rate = CLAMP(rate, 0.1f, 10.f); - message.id = utterance_id; - queue.push_back(message); - - if ([self isPaused]) { - [self resumeSpeaking]; - } else { - [self update]; - } -} - -- (Array)getVoices { - Array list; - if (@available(macOS 10.14, *)) { - for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) { - NSString *voiceIdentifierString = [voice identifier]; - NSString *voiceLocaleIdentifier = [voice language]; - NSString *voiceName = [voice name]; - Dictionary voice_d; - voice_d["name"] = String::utf8([voiceName UTF8String]); - voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]); - voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]); - list.push_back(voice_d); - } - } else { - for (NSString *voiceIdentifierString in [NSSpeechSynthesizer availableVoices]) { - NSString *voiceLocaleIdentifier = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceLocaleIdentifier]; - NSString *voiceName = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceName]; - Dictionary voice_d; - voice_d["name"] = String([voiceName UTF8String]); - voice_d["id"] = String([voiceIdentifierString UTF8String]); - voice_d["language"] = String([voiceLocaleIdentifier UTF8String]); - list.push_back(voice_d); - } - } - return list; -} - -@end diff --git a/platform/osx/vulkan_context_osx.h b/platform/osx/vulkan_context_osx.h deleted file mode 100644 index ade0f4a4c9..0000000000 --- a/platform/osx/vulkan_context_osx.h +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_osx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 VULKAN_DEVICE_OSX_H -#define VULKAN_DEVICE_OSX_H - -#include "drivers/vulkan/vulkan_context.h" -#import - -class VulkanContextOSX : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; - -public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height); - - VulkanContextOSX(); - ~VulkanContextOSX(); -}; - -#endif // VULKAN_DEVICE_OSX_H diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm deleted file mode 100644 index bdabc24c28..0000000000 --- a/platform/osx/vulkan_context_osx.mm +++ /dev/null @@ -1,59 +0,0 @@ -/*************************************************************************/ -/* vulkan_context_osx.mm */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_osx.h" -#ifdef USE_VOLK -#include -#else -#include -#endif - -const char *VulkanContextOSX::_get_platform_surface_extension() const { - return VK_MVK_MACOS_SURFACE_EXTENSION_NAME; -} - -Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height) { - VkMacOSSurfaceCreateInfoMVK createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.pView = (__bridge const void *)p_window; - - VkSurfaceKHR surface; - VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); - ERR_FAIL_COND_V(err, ERR_CANT_CREATE); - return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); -} - -VulkanContextOSX::VulkanContextOSX() { -} - -VulkanContextOSX::~VulkanContextOSX() { -} diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 22f968eac7..8968c1cc17 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -407,7 +407,7 @@ void CodeEdit::gui_input(const Ref &p_gui_input) { } /* Ctrl + Hover symbols */ -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (k->get_keycode() == Key::META) { #else if (k->get_keycode() == Key::CTRL) { diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index f731a0007a..ad30985a46 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -1304,7 +1304,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { { Vector FSR_upscale_modes; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) // MoltenVK does not support some of the operations used by the normal mode of FSR. Fallback works just fine though. FSR_upscale_modes.push_back("\n#define MODE_FSR_UPSCALE_FALLBACK\n"); #else diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index a2a0538e04..5ede2e761d 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -4263,7 +4263,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e rb->volumetric_fog->fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView()); RD::get_singleton()->set_resource_name(rb->volumetric_fog->fog_map, "Fog map"); -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) Vector dm; dm.resize(target_width * target_height * volumetric_fog_depth * 4); dm.fill(0); @@ -4352,7 +4352,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4372,7 +4372,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4384,7 +4384,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4656,7 +4656,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e } { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4667,7 +4667,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e } { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; @@ -4679,7 +4679,7 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e { RD::Uniform u; -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; #else u.uniform_type = RD::UNIFORM_TYPE_IMAGE; diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index 04e05380f1..176465234e 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -177,7 +177,7 @@ void ShaderRD::_build_variant_code(StringBuilder &builder, uint32_t p_variant, c for (const KeyValue &E : p_version->code_sections) { builder.append(String("#define ") + String(E.key) + "_CODE_USED\n"); } -#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) builder.append("#define MOLTENVK_USED\n"); #endif } break; diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index d28380d056..7605f24cf8 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -3251,7 +3251,7 @@ TEST_CASE("[SceneTree][CodeEdit] symbol lookup") { SIGNAL_WATCH(code_edit, "symbol_validate"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(code_edit, Key::META); #else SEND_GUI_KEY_EVENT(code_edit, Key::CTRL); diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 5d969b1fbc..0fce359c5a 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -724,7 +724,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "t"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::SHIFT | KeyModifierMask::ALT) #else SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::SHIFT | KeyModifierMask::CMD) @@ -736,7 +736,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "tes"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::SHIFT | KeyModifierMask::ALT) #else SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::SHIFT | KeyModifierMask::CMD) @@ -1902,7 +1902,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::CMD | KeyModifierMask::SHIFT); @@ -2013,7 +2013,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::CMD | KeyModifierMask::SHIFT); @@ -2244,7 +2244,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::UP | KeyModifierMask::CMD | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::HOME | KeyModifierMask::CMD | KeyModifierMask::SHIFT); @@ -2285,7 +2285,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::DOWN | KeyModifierMask::CMD | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::END | KeyModifierMask::CMD | KeyModifierMask::SHIFT); @@ -2326,7 +2326,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::CMD | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::HOME | KeyModifierMask::SHIFT); @@ -2383,7 +2383,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::CMD | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::END | KeyModifierMask::SHIFT); -- cgit v1.2.3