Rename OSX to macOS and iPhoneOS to iOS.

This commit is contained in:
bruvzg 2022-07-20 09:28:22 +03:00
parent 292c952e3b
commit 8823eae328
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
245 changed files with 1151 additions and 1149 deletions

30
platform/macos/SCsub Normal file
View 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))

View 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

View 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
View 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)

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

View 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);
}

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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();
}

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View 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

View 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
View 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
View 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);
}

View 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

View 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())

View 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
View 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

View 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

View 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() {
}