Rename OSX to macOS and iPhoneOS to iOS.
This commit is contained in:
parent
292c952e3b
commit
8823eae328
245 changed files with 1151 additions and 1149 deletions
30
platform/macos/SCsub
Normal file
30
platform/macos/SCsub
Normal file
|
|
@ -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))
|
||||
47
platform/macos/crash_handler_macos.h
Normal file
47
platform/macos/crash_handler_macos.h
Normal file
|
|
@ -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
|
||||
203
platform/macos/crash_handler_macos.mm
Normal file
203
platform/macos/crash_handler_macos.mm
Normal file
|
|
@ -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 <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(DEBUG_ENABLED)
|
||||
#define CRASH_HANDLER_ENABLED 1
|
||||
#endif
|
||||
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/getsect.h>
|
||||
|
||||
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<String> 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
|
||||
}
|
||||
253
platform/macos/detect.py
Normal file
253
platform/macos/detect.py
Normal file
|
|
@ -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)
|
||||
55
platform/macos/dir_access_macos.h
Normal file
55
platform/macos/dir_access_macos.h
Normal file
|
|
@ -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 <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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
|
||||
81
platform/macos/dir_access_macos.mm
Normal file
81
platform/macos/dir_access_macos.mm
Normal file
|
|
@ -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 <errno.h>
|
||||
|
||||
#import <AppKit/NSWorkspace.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
||||
406
platform/macos/display_server_macos.h
Normal file
406
platform/macos/display_server_macos.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <AppKit/NSCursor.h>
|
||||
#import <ApplicationServices/ApplicationServices.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#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<Vector2> 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<WindowID> transient_children;
|
||||
|
||||
bool layered_window = false;
|
||||
bool fullscreen = false;
|
||||
bool on_top = false;
|
||||
bool borderless = false;
|
||||
bool resize_disabled = false;
|
||||
bool no_focus = false;
|
||||
bool is_popup = false;
|
||||
|
||||
Rect2i parent_safe_rect;
|
||||
};
|
||||
|
||||
List<WindowID> 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<String, NSMenu *> submenu;
|
||||
|
||||
struct WarpEvent {
|
||||
NSTimeInterval timestamp;
|
||||
NSPoint delta;
|
||||
};
|
||||
List<WarpEvent> warp_events;
|
||||
NSTimeInterval last_warp = 0;
|
||||
bool ignore_warp = false;
|
||||
|
||||
Vector<KeyEvent> 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<LayoutInfo> 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<CursorShape, Vector<Variant>> cursors_cache;
|
||||
|
||||
HashMap<WindowID, WindowData> 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<InputEvent> &p_event);
|
||||
void _dispatch_input_event(const Ref<InputEvent> &p_event);
|
||||
void _push_input(const Ref<InputEvent> &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<Image> &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<InputEventWithModifiers> 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<Texture2D> &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<Texture2D> &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<Texture2D> &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<Texture2D> 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<Texture2D> &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<String> p_buttons, const Callable &p_callback) override;
|
||||
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
|
||||
|
||||
virtual void mouse_set_mode(MouseMode p_mode) override;
|
||||
virtual MouseMode mouse_get_mode() const override;
|
||||
|
||||
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<int> 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<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
|
||||
virtual void window_set_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<Resource> &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<Image> &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<String> 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
|
||||
3304
platform/macos/display_server_macos.mm
Normal file
3304
platform/macos/display_server_macos.mm
Normal file
File diff suppressed because it is too large
Load diff
1564
platform/macos/export/codesign.cpp
Normal file
1564
platform/macos/export/codesign.cpp
Normal file
File diff suppressed because it is too large
Load diff
368
platform/macos/export/codesign.h
Normal file
368
platform/macos/export/codesign.h
Normal file
|
|
@ -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<CRRule> rules1;
|
||||
Vector<CRRule> rules2;
|
||||
|
||||
Vector<CRFile> files1;
|
||||
Vector<CRFile> 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<FileAccess> 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<String> 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<FileAccess> 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<FileAccess> 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<FileAccess> 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<FileAccess> 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<FileAccess> p_file) const override;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CodeSignSuperBlob */
|
||||
/*************************************************************************/
|
||||
|
||||
class CodeSignSuperBlob {
|
||||
Vector<Ref<CodeSignBlob>> blobs;
|
||||
|
||||
public:
|
||||
bool add_blob(const Ref<CodeSignBlob> &p_blob);
|
||||
|
||||
int get_size() const;
|
||||
void write_to_file(Ref<FileAccess> 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
|
||||
43
platform/macos/export/export.cpp
Normal file
43
platform/macos/export/export.cpp
Normal file
|
|
@ -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<EditorExportPlatformMacOS> platform;
|
||||
platform.instantiate();
|
||||
|
||||
EditorExport::get_singleton()->add_export_platform(platform);
|
||||
}
|
||||
36
platform/macos/export/export.h
Normal file
36
platform/macos/export/export.h
Normal file
|
|
@ -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
|
||||
1673
platform/macos/export/export_plugin.cpp
Normal file
1673
platform/macos/export/export_plugin.cpp
Normal file
File diff suppressed because it is too large
Load diff
137
platform/macos/export/export_plugin.h
Normal file
137
platform/macos/export/export_plugin.h
Normal file
|
|
@ -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 <sys/stat.h>
|
||||
|
||||
class EditorExportPlatformMacOS : public EditorExportPlatform {
|
||||
GDCLASS(EditorExportPlatformMacOS, EditorExportPlatform);
|
||||
|
||||
int version_code = 0;
|
||||
|
||||
Ref<ImageTexture> logo;
|
||||
|
||||
void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary);
|
||||
void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data);
|
||||
|
||||
Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
|
||||
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true);
|
||||
Error _code_sign_directory(const Ref<EditorExportPreset> &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<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path,
|
||||
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
|
||||
bool p_should_error_on_non_code_sign);
|
||||
Error _export_macos_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name,
|
||||
Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportPreset> &p_preset, List<String> *r_features) override;
|
||||
virtual void get_export_options(List<ExportOption> *r_options) override;
|
||||
virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
|
||||
|
||||
public:
|
||||
virtual String get_name() const override { return "macOS"; }
|
||||
virtual String get_os_name() const override { return "macOS"; }
|
||||
virtual Ref<Texture2D> get_logo() const override { return logo; }
|
||||
|
||||
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
|
||||
List<String> list;
|
||||
if (use_dmg()) {
|
||||
list.push_back("dmg");
|
||||
}
|
||||
list.push_back("zip");
|
||||
list.push_back("app");
|
||||
return list;
|
||||
}
|
||||
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
|
||||
|
||||
virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
|
||||
|
||||
virtual void get_platform_features(List<String> *r_features) override {
|
||||
r_features->push_back("pc");
|
||||
r_features->push_back("s3tc");
|
||||
r_features->push_back("macos");
|
||||
}
|
||||
|
||||
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {
|
||||
}
|
||||
|
||||
EditorExportPlatformMacOS();
|
||||
~EditorExportPlatformMacOS();
|
||||
};
|
||||
|
||||
#endif
|
||||
236
platform/macos/export/lipo.cpp
Normal file
236
platform/macos/export/lipo.cpp
Normal file
|
|
@ -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<FileAccess> 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<FileAccess> 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<uint32_t>::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<FileAccess> 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<FileAccess> 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
|
||||
76
platform/macos/export/lipo.h
Normal file
76
platform/macos/export/lipo.h
Normal file
|
|
@ -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<FileAccess> fa;
|
||||
Vector<FatArch> 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
|
||||
548
platform/macos/export/macho.cpp
Normal file
548
platform/macos/export/macho.cpp
Normal file
|
|
@ -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<FileAccess> 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<uint64_t>::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<FileAccess> MachO::get_file() const {
|
||||
return fa;
|
||||
}
|
||||
|
||||
Ref<FileAccess> 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
|
||||
215
platform/macos/export/macho.h
Normal file
215
platform/macos/export/macho.h
Normal file
|
|
@ -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<FileAccess> fa;
|
||||
bool swap = false;
|
||||
|
||||
uint64_t lc_limit = 0;
|
||||
|
||||
uint64_t exe_limit = 0;
|
||||
uint64_t exe_base = std::numeric_limits<uint64_t>::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<FileAccess> get_file() const;
|
||||
Ref<FileAccess> get_file();
|
||||
|
||||
uint64_t get_signature_size();
|
||||
bool set_signature_size(uint64_t p_size);
|
||||
};
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
|
||||
#endif // MACHO_H
|
||||
570
platform/macos/export/plist.cpp
Normal file
570
platform/macos/export/plist.cpp
Normal file
|
|
@ -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> PListNode::new_array() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_dict() {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_string(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_data(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_date(const String &p_string) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE;
|
||||
node->data_string = p_string.utf8();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_bool(bool p_bool) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN;
|
||||
node->data_bool = p_bool;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_int(int32_t p_int) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER;
|
||||
node->data_int = p_int;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ref<PListNode> PListNode::new_real(float p_real) {
|
||||
Ref<PListNode> node = memnew(PListNode());
|
||||
ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
|
||||
node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL;
|
||||
node->data_real = p_real;
|
||||
return node;
|
||||
}
|
||||
|
||||
bool PListNode::push_subnode(const Ref<PListNode> &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<String, Ref<PListNode>> &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<String, Ref<PListNode>> &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 += "<data>\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += data_string + "\n";
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</data>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DATE: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<date>";
|
||||
p_stream += data_string;
|
||||
p_stream += "</date>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_STRING: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<string>";
|
||||
p_stream += String::utf8(data_string);
|
||||
p_stream += "</string>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
if (data_bool) {
|
||||
p_stream += "<true/>\n";
|
||||
} else {
|
||||
p_stream += "<false/>\n";
|
||||
}
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<integer>";
|
||||
p_stream += itos(data_int);
|
||||
p_stream += "</integer>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_REAL: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<real>";
|
||||
p_stream += rtos(data_real);
|
||||
p_stream += "</real>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<array>\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 += "</array>\n";
|
||||
} break;
|
||||
case PList::PLNodeType::PL_NODE_TYPE_DICT: {
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "<dict>\n";
|
||||
for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
|
||||
p_stream += String("\t").repeat(p_indent + 1);
|
||||
p_stream += "<key>";
|
||||
p_stream += E.key;
|
||||
p_stream += "</key>\n";
|
||||
E.value->store_text(p_stream, p_indent + 1);
|
||||
}
|
||||
p_stream += String("\t").repeat(p_indent);
|
||||
p_stream += "</dict>\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<PListNode>();
|
||||
|
||||
Ref<FileAccess> 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<uint8_t> 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<PListNode>();
|
||||
|
||||
int pos = 0;
|
||||
bool in_plist = false;
|
||||
bool done_plist = false;
|
||||
List<Ref<PListNode>> 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 <?xml ... ?> and <!DOCTYPE ... >
|
||||
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 <plist> tag.");
|
||||
}
|
||||
|
||||
if (token == "dict") {
|
||||
if (!stack.is_empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> 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<PListNode> 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 </dict> tag.");
|
||||
}
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "array") {
|
||||
if (!stack.is_empty()) {
|
||||
// Add subnode end enter it.
|
||||
Ref<PListNode> 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<PListNode> 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 </array> 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("</", pos);
|
||||
if (end_token_s == -1) {
|
||||
ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> 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<PListNode> 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 += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
|
||||
ret += "<plist version=\"1.0\">\n";
|
||||
|
||||
root->store_text(ret, 0);
|
||||
|
||||
ret += "</plist>\n\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<PListNode> PList::get_root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
#endif // MODULE_REGEX_ENABLED
|
||||
116
platform/macos/export/plist.h
Normal file
116
platform/macos/export/plist.h
Normal file
|
|
@ -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<PListNode> 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<PListNode> 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<Ref<PListNode>> data_array;
|
||||
HashMap<String, Ref<PListNode>> data_dict;
|
||||
union {
|
||||
int32_t data_int;
|
||||
bool data_bool;
|
||||
float data_real;
|
||||
};
|
||||
|
||||
static Ref<PListNode> new_array();
|
||||
static Ref<PListNode> new_dict();
|
||||
static Ref<PListNode> new_string(const String &p_string);
|
||||
static Ref<PListNode> new_data(const String &p_string);
|
||||
static Ref<PListNode> new_date(const String &p_string);
|
||||
static Ref<PListNode> new_bool(bool p_bool);
|
||||
static Ref<PListNode> new_int(int32_t p_int);
|
||||
static Ref<PListNode> new_real(float p_real);
|
||||
|
||||
bool push_subnode(const Ref<PListNode> &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
|
||||
97
platform/macos/gl_manager_macos_legacy.h
Normal file
97
platform/macos/gl_manager_macos_legacy.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <ApplicationServices/ApplicationServices.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
|
||||
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<DisplayServer::WindowID, GLWindow> 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
|
||||
228
platform/macos/gl_manager_macos_legacy.mm
Normal file
228
platform/macos/gl_manager_macos_legacy.mm
Normal file
|
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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<DisplayServer::WindowID, GLWindow> &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
|
||||
42
platform/macos/godot_application.h
Normal file
42
platform/macos/godot_application.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotApplication : NSApplication
|
||||
@end
|
||||
|
||||
#endif // GODOT_APPLICATION_H
|
||||
58
platform/macos/godot_application.mm
Normal file
58
platform/macos/godot_application.mm
Normal file
|
|
@ -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
|
||||
46
platform/macos/godot_application_delegate.h
Normal file
46
platform/macos/godot_application_delegate.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotApplicationDelegate : NSObject
|
||||
- (void)forceUnbundledWindowActivationHackStep1;
|
||||
- (void)forceUnbundledWindowActivationHackStep2;
|
||||
- (void)forceUnbundledWindowActivationHackStep3;
|
||||
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
|
||||
@end
|
||||
|
||||
#endif // GODOT_APPLICATION_DELEGATE_H
|
||||
163
platform/macos/godot_application_delegate.mm
Normal file
163
platform/macos/godot_application_delegate.mm
Normal file
|
|
@ -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<String> 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
|
||||
66
platform/macos/godot_content_view.h
Normal file
66
platform/macos/godot_content_view.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if defined(GLES3_ENABLED)
|
||||
#import <AppKit/NSOpenGLView.h>
|
||||
#define RootView NSOpenGLView
|
||||
#else
|
||||
#define RootView NSView
|
||||
#endif
|
||||
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
@interface GodotContentView : RootView <NSTextInputClient> {
|
||||
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
|
||||
771
platform/macos/godot_content_view.mm
Normal file
771
platform/macos/godot_content_view.mm
Normal file
|
|
@ -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<NSDraggingInfo>)sender {
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id<NSDraggingInfo>)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<String> 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<InputEventMouseButton> 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<InputEventMouseMotion> 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<InputEventMagnifyGesture> 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<InputEventMouseButton> 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<InputEventPanGesture> 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
|
||||
92
platform/macos/godot_main_macos.mm
Normal file
92
platform/macos/godot_main_macos.mm
Normal file
|
|
@ -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 <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(SANITIZERS_ENABLED)
|
||||
#include <sys/resource.h>
|
||||
#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();
|
||||
}
|
||||
61
platform/macos/godot_menu_item.h
Normal file
61
platform/macos/godot_menu_item.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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<Image> img;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotMenuItem
|
||||
@end
|
||||
|
||||
#endif // GODOT_MENU_ITEM_H
|
||||
47
platform/macos/godot_window.h
Normal file
47
platform/macos/godot_window.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotWindow : NSWindow {
|
||||
DisplayServer::WindowID window_id;
|
||||
}
|
||||
|
||||
- (void)setWindowID:(DisplayServer::WindowID)wid;
|
||||
|
||||
@end
|
||||
|
||||
#endif //GODOT_WINDOW_H
|
||||
69
platform/macos/godot_window.mm
Normal file
69
platform/macos/godot_window.mm
Normal file
|
|
@ -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
|
||||
47
platform/macos/godot_window_delegate.h
Normal file
47
platform/macos/godot_window_delegate.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotWindowDelegate : NSObject <NSWindowDelegate> {
|
||||
DisplayServer::WindowID window_id;
|
||||
}
|
||||
|
||||
- (void)setWindowID:(DisplayServer::WindowID)wid;
|
||||
|
||||
@end
|
||||
|
||||
#endif //GODOT_WINDOW_DELEGATE_H
|
||||
270
platform/macos/godot_window_delegate.mm
Normal file
270
platform/macos/godot_window_delegate.mm
Normal file
|
|
@ -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
|
||||
616
platform/macos/joypad_macos.cpp
Normal file
616
platform/macos/joypad_macos.cpp
Normal file
|
|
@ -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 <machine/endian.h>
|
||||
|
||||
#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<rec_element> *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<rec_element> *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<rec_element::Comparator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_element_added(const void *p_value, void *p_parameter) {
|
||||
joypad *joy = static_cast<joypad *>(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;
|
||||
}
|
||||
124
platform/macos/joypad_macos.h
Normal file
124
platform/macos/joypad_macos.h
Normal file
|
|
@ -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 <IOKit/hidsystem/IOHIDUsageTables.h>
|
||||
#else
|
||||
#import <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
|
||||
#endif
|
||||
#import <ForceFeedback/ForceFeedback.h>
|
||||
#import <ForceFeedback/ForceFeedbackConstants.h>
|
||||
#import <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
#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<rec_element> axis_elements;
|
||||
Vector<rec_element> button_elements;
|
||||
Vector<rec_element> 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<rec_element> *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<joypad> 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
|
||||
52
platform/macos/key_mapping_macos.h
Normal file
52
platform/macos/key_mapping_macos.h
Normal file
|
|
@ -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
|
||||
496
platform/macos/key_mapping_macos.mm
Normal file
496
platform/macos/key_mapping_macos.mm
Normal file
|
|
@ -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 <Carbon/Carbon.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
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;
|
||||
}
|
||||
BIN
platform/macos/logo.png
Normal file
BIN
platform/macos/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7 KiB |
44
platform/macos/macos_terminal_logger.h
Normal file
44
platform/macos/macos_terminal_logger.h
Normal file
|
|
@ -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
|
||||
82
platform/macos/macos_terminal_logger.mm
Normal file
82
platform/macos/macos_terminal_logger.mm
Normal file
|
|
@ -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 <os/log.h>
|
||||
|
||||
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
|
||||
120
platform/macos/os_macos.h
Normal file
120
platform/macos/os_macos.h
Normal file
|
|
@ -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<String> 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<String> &p_args);
|
||||
virtual List<String> 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<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
|
||||
virtual Error create_instance(const List<String> &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
|
||||
524
platform/macos/os_macos.mm
Normal file
524
platform/macos/os_macos.mm
Normal file
|
|
@ -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 <dlfcn.h>
|
||||
#include <libproc.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <os/log.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
_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<DirAccess> 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<DirAccessMacOS>(DirAccess::ACCESS_RESOURCES);
|
||||
DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_USERDATA);
|
||||
DirAccess::make_default<DirAccessMacOS>(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<String> &p_args) {
|
||||
launch_service_args = p_args;
|
||||
}
|
||||
|
||||
List<String> 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<String> &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<String> &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<Logger *> 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);
|
||||
}
|
||||
34
platform/macos/platform_config.h
Normal file
34
platform/macos/platform_config.h
Normal file
|
|
@ -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 <alloca.h>
|
||||
|
||||
#define OPENGL_INCLUDE_H "thirdparty/glad/glad/glad.h"
|
||||
#define PTHREAD_RENAME_SELF
|
||||
21
platform/macos/platform_macos_builders.py
Normal file
21
platform/macos/platform_macos_builders.py
Normal file
|
|
@ -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())
|
||||
71
platform/macos/tts_macos.h
Normal file
71
platform/macos/tts_macos.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
|
||||
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
|
||||
#import <AVFAudio/AVSpeechSynthesis.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
@interface TTS_MacOS : NSObject <AVSpeechSynthesizerDelegate> {
|
||||
// AVSpeechSynthesizer
|
||||
bool speaking;
|
||||
HashMap<id, int> ids;
|
||||
|
||||
// NSSpeechSynthesizer
|
||||
bool paused;
|
||||
bool have_utterance;
|
||||
int last_utterance;
|
||||
|
||||
id synth; // NSSpeechSynthesizer or AVSpeechSynthesizer
|
||||
List<DisplayServer::TTSUtterance> 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
|
||||
266
platform/macos/tts_macos.mm
Normal file
266
platform/macos/tts_macos.mm
Normal file
|
|
@ -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
|
||||
47
platform/macos/vulkan_context_macos.h
Normal file
47
platform/macos/vulkan_context_macos.h
Normal file
|
|
@ -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 <AppKit/AppKit.h>
|
||||
|
||||
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
|
||||
59
platform/macos/vulkan_context_macos.mm
Normal file
59
platform/macos/vulkan_context_macos.mm
Normal file
|
|
@ -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 <volk.h>
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#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() {
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue