summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/audio_driver_jandroid.cpp4
-rw-r--r--platform/android/display_server_android.cpp4
-rw-r--r--platform/android/display_server_android.h2
-rw-r--r--platform/android/export/export.cpp148
-rw-r--r--platform/android/java/app/AndroidManifest.xml4
-rw-r--r--platform/android/java/app/build.gradle29
-rw-r--r--platform/android/java/app/config.gradle73
-rw-r--r--platform/android/java/build.gradle3
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java39
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java11
-rw-r--r--platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java2
-rw-r--r--platform/android/java_godot_io_wrapper.cpp6
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/plugin/godot_plugin_config.h251
-rw-r--r--platform/haiku/audio_driver_media_kit.cpp4
-rw-r--r--platform/iphone/os_iphone.cpp2
-rw-r--r--platform/iphone/os_iphone.h2
-rw-r--r--platform/javascript/audio_driver_javascript.cpp49
-rw-r--r--platform/javascript/audio_driver_javascript.h1
-rw-r--r--platform/osx/display_server_osx.mm49
-rw-r--r--platform/osx/export/export.cpp329
-rw-r--r--platform/uwp/os_uwp.cpp2
-rw-r--r--platform/uwp/os_uwp.h2
-rw-r--r--platform/uwp/thread_uwp.cpp8
-rw-r--r--platform/uwp/thread_uwp.h8
-rw-r--r--platform/windows/display_server_windows.cpp143
-rw-r--r--platform/windows/display_server_windows.h15
-rw-r--r--platform/windows/key_mapping_windows.cpp13
-rw-r--r--platform/windows/key_mapping_windows.h1
-rw-r--r--platform/windows/os_windows.cpp106
-rw-r--r--platform/windows/os_windows.h10
33 files changed, 979 insertions, 357 deletions
diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp
index 0ace7d03a1..09c981b3fa 100644
--- a/platform/android/audio_driver_jandroid.cpp
+++ b/platform/android/audio_driver_jandroid.cpp
@@ -73,9 +73,9 @@ Error AudioDriverAndroid::init() {
// __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
JNIEnv *env = ThreadAndroid::get_env();
- int mix_rate = GLOBAL_DEF_RST("audio/mix_rate", 44100);
+ int mix_rate = GLOBAL_GET("audio/mix_rate");
- int latency = GLOBAL_DEF_RST("audio/output_latency", 25);
+ int latency = GLOBAL_GET("audio/output_latency");
unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000);
print_verbose("Audio buffer size: " + itos(buffer_size));
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 48c23d3c31..c5b06827b2 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -155,12 +155,12 @@ bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
return true;
}
-void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length) {
+void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_COND(!godot_io_java);
if (godot_io_java->has_vk()) {
- godot_io_java->show_vk(p_existing_text, p_max_length);
+ godot_io_java->show_vk(p_existing_text, p_max_length, p_cursor_start, p_cursor_end);
} else {
ERR_PRINT("Virtual keyboard not available");
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index b2909f258b..199371d08d 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -106,7 +106,7 @@ public:
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1);
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void virtual_keyboard_hide();
virtual int virtual_keyboard_get_height() const;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 0b6c6990f3..dfaaf68b69 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "platform/android/logo.gen.h"
+#include "platform/android/plugin/godot_plugin_config.h"
#include "platform/android/run_icon.gen.h"
#include <string.h>
@@ -252,16 +253,45 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
EditorProgress *ep;
};
+ Vector<PluginConfig> plugins;
+ volatile bool plugins_changed;
+ Mutex plugins_lock;
Vector<Device> devices;
volatile bool devices_changed;
Mutex device_lock;
- Thread *device_thread;
+ Thread *check_for_changes_thread;
volatile bool quit_request;
- static void _device_poll_thread(void *ud) {
+ static void _check_for_changes_poll_thread(void *ud) {
EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
while (!ea->quit_request) {
+ // Check for plugins updates
+ {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed) {
+ Vector<PluginConfig> loaded_plugins = get_plugins();
+
+ MutexLock lock(ea->plugins_lock);
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed = true;
+ } else {
+ for (int i = 0; i < ea->plugins.size(); i++) {
+ if (ea->plugins[i].name != loaded_plugins[i].name) {
+ ea->plugins_changed = true;
+ break;
+ }
+ }
+ }
+
+ if (ea->plugins_changed) {
+ ea->plugins = loaded_plugins;
+ }
+ }
+ }
+
+ // Check for devices updates
String adb = EditorSettings::get_singleton()->get("export/android/adb");
if (FileAccess::exists(adb)) {
String devices;
@@ -573,6 +603,73 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return abis;
}
+ /// List the gdap files in the directory specified by the p_path parameter.
+ static Vector<String> list_gdap_files(const String &p_path) {
+ Vector<String> dir_files;
+ DirAccessRef da = DirAccess::open(p_path);
+ if (da) {
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+
+ if (da->current_is_dir() || da->current_is_hidden()) {
+ continue;
+ }
+
+ if (file.ends_with(PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+ }
+
+ static Vector<PluginConfig> get_plugins() {
+ Vector<PluginConfig> loaded_plugins;
+
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
+
+ // Add the prebuilt plugins
+ loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir));
+
+ if (DirAccess::exists(plugins_dir)) {
+ Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
+
+ if (!plugins_filenames.empty()) {
+ Ref<ConfigFile> config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+ if (config.valid_config) {
+ loaded_plugins.push_back(config);
+ } else {
+ print_error("Invalid plugin config file " + plugins_filenames[i]);
+ }
+ }
+ }
+ }
+
+ return loaded_plugins;
+ }
+
+ static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+ Vector<PluginConfig> enabled_plugins;
+ Vector<PluginConfig> all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfig plugin = all_plugins[i];
+ bool enabled = p_presets->get("plugins/" + plugin.name);
+ if (enabled) {
+ enabled_plugins.push_back(plugin);
+ }
+ }
+
+ return enabled_plugins;
+ }
+
static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) {
zip_fileinfo zipfi = get_zip_fileinfo();
zipOpenNewFileInZip(ed->apk,
@@ -674,7 +771,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int xr_mode_index = p_preset->get("xr_features/xr_mode");
- String plugins = p_preset->get("custom_template/plugins");
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
Vector<String> perms;
@@ -829,9 +926,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
- if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") {
+ if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) {
// Update the meta-data 'android:value' attribute with the list of enabled plugins.
- string_table.write[attr_value] = plugins;
+ string_table.write[attr_value] = plugins_names;
}
iofs += 20;
@@ -865,8 +962,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
feature_required_list.push_back(hand_tracking_index == 2);
feature_versions.push_back(-1); // no version attribute should be added.
- if (perms.find("oculus.permission.handtracking") == -1) {
- perms.push_back("oculus.permission.handtracking");
+ if (perms.find("com.oculus.permission.HAND_TRACKING") == -1) {
+ perms.push_back("com.oculus.permission.HAND_TRACKING");
}
}
}
@@ -1354,7 +1451,14 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/plugins", PROPERTY_HINT_PLACEHOLDER_TEXT, "Plugin1,Plugin2,..."), ""));
+
+ Vector<PluginConfig> plugins_configs = get_plugins();
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ print_verbose("Found Android plugin " + plugins_configs[i].name);
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false));
+ }
+ plugins_changed = false;
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
@@ -1409,6 +1513,15 @@ public:
return logo;
}
+ virtual bool should_update_export_options() {
+ bool export_options_changed = plugins_changed;
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed = false;
+ }
+ return export_options_changed;
+ }
+
virtual bool poll_export() {
bool dc = devices_changed;
if (dc) {
@@ -1755,18 +1868,22 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
- String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
- String plugins = p_preset->get("custom_template/plugins");
+
+ Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
+ String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
+ String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins);
+ String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins);
List<String> cmdline;
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
- cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory.
- cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable.
+ cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
+ cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
+ cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory.
/*{ used for debug
@@ -2283,14 +2400,15 @@ public:
run_icon->create_from_image(img);
devices_changed = true;
+ plugins_changed = true;
quit_request = false;
- device_thread = Thread::create(_device_poll_thread, this);
+ check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
}
~EditorExportPlatformAndroid() {
quit_request = true;
- Thread::wait_to_finish(device_thread);
- memdelete(device_thread);
+ Thread::wait_to_finish(check_for_changes_thread);
+ memdelete(check_for_changes_thread);
}
};
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index cc480d1c84..f5b1d29f22 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -32,8 +32,8 @@
<!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
<meta-data
- android:name="custom_template_plugins"
- android:value="custom_template_plugins_value"/>
+ android:name="plugins"
+ android:value="plugins_value"/>
<activity
android:name=".GodotApp"
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 9ae47d6174..ea341b37b1 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -21,6 +21,16 @@ allprojects {
mavenCentral()
google()
jcenter()
+
+ // Godot user plugins custom maven repos
+ String[] mavenRepos = getGodotPluginsMavenRepos()
+ if (mavenRepos != null && mavenRepos.size() > 0) {
+ for (String repoUrl : mavenRepos) {
+ maven {
+ url repoUrl
+ }
+ }
+ }
}
}
@@ -40,15 +50,18 @@ dependencies {
releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
}
- // Godot prebuilt plugins
- implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"])
+ // Godot user plugins remote dependencies
+ String[] remoteDeps = getGodotPluginsRemoteBinaries()
+ if (remoteDeps != null && remoteDeps.size() > 0) {
+ for (String dep : remoteDeps) {
+ implementation dep
+ }
+ }
- // Godot user plugins dependencies
- String pluginsDir = getGodotPluginsDirectory()
- String[] pluginsBinaries = getGodotPluginsBinaries()
- if (pluginsDir != null && !pluginsDir.isEmpty() &&
- pluginsBinaries != null && pluginsBinaries.size() > 0) {
- implementation fileTree(dir: pluginsDir, include: pluginsBinaries)
+ // Godot user plugins local dependencies
+ String[] pluginsBinaries = getGodotPluginsLocalBinaries()
+ if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
+ implementation files(pluginsBinaries)
}
}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 68ec93ed47..5251bc3066 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -28,39 +28,68 @@ ext.getExportPackageName = { ->
return appId
}
+final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+
/**
- * Parse the project properties for the 'custom_template_plugins' property and return
- * their binaries for inclusion in the build dependencies.
- *
- * The listed plugins must have their binaries in the project plugins directory.
+ * Parse the project properties for the 'plugins_maven_repos' property and return the list
+ * of maven repos.
*/
-ext.getGodotPluginsBinaries = { ->
- String[] binDeps = []
+ext.getGodotPluginsMavenRepos = { ->
+ Set<String> mavenRepos = []
- // Retrieve the list of enabled plugins.
- if (project.hasProperty("custom_template_plugins")) {
- String pluginsList = project.property("custom_template_plugins")
- if (pluginsList != null && !pluginsList.trim().isEmpty()) {
- for (String plugin : pluginsList.split(",")) {
- binDeps += plugin.trim() + "*.aar"
+ // Retrieve the list of maven repos.
+ if (project.hasProperty("plugins_maven_repos")) {
+ String mavenReposProperty = project.property("plugins_maven_repos")
+ if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
+ for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ mavenRepos += mavenRepoUrl.trim()
}
}
}
- return binDeps
+ return mavenRepos
+}
+
+/**
+ * Parse the project properties for the 'plugins_remote_binaries' property and return
+ * it for inclusion in the build dependencies.
+ */
+ext.getGodotPluginsRemoteBinaries = { ->
+ Set<String> remoteDeps = []
+
+ // Retrieve the list of remote plugins binaries.
+ if (project.hasProperty("plugins_remote_binaries")) {
+ String remoteDepsList = project.property("plugins_remote_binaries")
+ if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
+ for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ remoteDeps += dep.trim()
+ }
+ }
+ }
+ return remoteDeps
}
/**
- * Parse the project properties for the 'custom_template_plugins_dir' property and return
- * its value.
+ * Parse the project properties for the 'plugins_local_binaries' property and return
+ * their binaries for inclusion in the build dependencies.
*
- * The returned value is the directory containing user plugins.
+ * Returns the prebuilt plugins if the 'plugins_local_binaries' property is unavailable.
*/
-ext.getGodotPluginsDirectory = { ->
- // The plugins directory is provided by the 'custom_template_plugins_dir' property.
- String pluginsDir = project.hasProperty("custom_template_plugins_dir")
- ? project.property("custom_template_plugins_dir")
- : ""
+ext.getGodotPluginsLocalBinaries = { ->
+ // Set the prebuilt plugins as default. If custom build is enabled,
+ // the 'plugins_local_binaries' will be defined so we can use it instead.
+ Set<String> binDeps = ["libs/plugins/GodotPayment.release.aar"]
+
+ // Retrieve the list of local plugins binaries.
+ if (project.hasProperty("plugins_local_binaries")) {
+ binDeps.clear()
+ String pluginsList = project.property("plugins_local_binaries")
+ if (pluginsList != null && !pluginsList.trim().isEmpty()) {
+ for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ binDeps += plugin.trim()
+ }
+ }
+ }
- return pluginsDir
+ return binDeps
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 865b61956c..01a3607b20 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -140,7 +140,7 @@ task generateGodotTemplates(type: GradleBuild) {
startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
}
- tasks = ["copyGodotPaymentPluginToAppModule"]
+ tasks = []
// Only build the apks and aar files for which we have native shared libraries.
for (String target : supportedTargets) {
@@ -161,6 +161,7 @@ task generateGodotTemplates(type: GradleBuild) {
}
}
+ dependsOn 'copyGodotPaymentPluginToAppModule'
finalizedBy 'zipCustomBuild'
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 410b93824d..93f4786e83 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -460,9 +460,9 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
- public void showKeyboard(String p_existing_text, int p_max_input_length) {
+ public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
- edit.showKeyboard(p_existing_text, p_max_input_length);
+ edit.showKeyboard(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index 547c093419..7f596575a8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -58,6 +58,7 @@ public class GodotEditText extends EditText {
private GodotTextInputWrapper mInputWrapper;
private EditHandler sHandler = new EditHandler(this);
private String mOriginText;
+ private int mMaxInputLength;
private static class EditHandler extends Handler {
private final WeakReference<GodotEditText> mEdit;
@@ -104,11 +105,18 @@ public class GodotEditText extends EditText {
String text = edit.mOriginText;
if (edit.requestFocus()) {
edit.removeTextChangedListener(edit.mInputWrapper);
+ setMaxInputLength(edit);
edit.setText("");
edit.append(text);
+ if (msg.arg2 != -1) {
+ edit.setSelection(msg.arg1, msg.arg2);
+ edit.mInputWrapper.setSelection(true);
+ } else {
+ edit.mInputWrapper.setSelection(false);
+ }
+
edit.mInputWrapper.setOriginText(text);
edit.addTextChangedListener(edit.mInputWrapper);
- setMaxInputLength(edit, msg.arg1);
final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(edit, 0);
}
@@ -125,14 +133,10 @@ public class GodotEditText extends EditText {
}
}
- private void setMaxInputLength(EditText p_edit_text, int p_max_input_length) {
- if (p_max_input_length > 0) {
- InputFilter[] filters = new InputFilter[1];
- filters[0] = new InputFilter.LengthFilter(p_max_input_length);
- p_edit_text.setFilters(filters);
- } else {
- p_edit_text.setFilters(new InputFilter[] {});
- }
+ private void setMaxInputLength(EditText p_edit_text) {
+ InputFilter[] filters = new InputFilter[1];
+ filters[0] = new InputFilter.LengthFilter(this.mMaxInputLength);
+ p_edit_text.setFilters(filters);
}
// ===========================================================
@@ -164,13 +168,24 @@ public class GodotEditText extends EditText {
// ===========================================================
// Methods
// ===========================================================
- public void showKeyboard(String p_existing_text, int p_max_input_length) {
- mOriginText = p_existing_text;
+ public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
+ if (p_cursor_start == -1) { // cursor position not given
+ this.mOriginText = p_existing_text;
+ this.mMaxInputLength = maxInputLength;
+ } else if (p_cursor_end == -1) { // not text selection
+ this.mOriginText = p_existing_text.substring(0, p_cursor_start);
+ this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_start);
+ } else {
+ this.mOriginText = p_existing_text.substring(0, p_cursor_end);
+ this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
+ }
final Message msg = new Message();
msg.what = HANDLER_OPEN_IME_KEYBOARD;
msg.obj = this;
- msg.arg1 = p_max_input_length;
+ msg.arg1 = p_cursor_start;
+ msg.arg2 = p_cursor_end;
sHandler.sendMessage(msg);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 9cd08de529..9c7cf9f341 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -53,6 +53,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
private final GodotRenderView mRenderView;
private final GodotEditText mEdit;
private String mOriginText;
+ private boolean mHasSelection;
// ===========================================================
// Constructors
@@ -77,6 +78,10 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
mOriginText = originText;
}
+ public void setSelection(boolean selection) {
+ mHasSelection = selection;
+ }
+
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@@ -95,6 +100,11 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
for (int i = 0; i < count; ++i) {
GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false);
+
+ if (mHasSelection) {
+ mHasSelection = false;
+ break;
+ }
}
}
});
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
index 17f5a7469e..12d2ed09fb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -58,7 +58,9 @@ public final class GodotPluginRegistry {
/**
* Name for the metadata containing the list of Godot plugins to enable.
*/
- private static final String GODOT_ENABLED_PLUGINS_LABEL = "custom_template_plugins";
+ private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins";
+
+ private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|";
private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry;
@@ -128,13 +130,13 @@ public final class GodotPluginRegistry {
}
// When using the Godot editor for building and exporting the apk, this is used to check
- // which plugins to enable since the custom build template may contain prebuilt plugins.
+ // which plugins to enable.
// When using a custom process to generate the apk, the metadata is not needed since
// it's assumed that the developer is aware of the dependencies included in the apk.
final Set<String> enabledPluginsSet;
if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
- String[] enabledPluginsList = enabledPlugins.split(",");
+ String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX);
if (enabledPluginsList.length == 0) {
// No plugins to enable. Aborting early.
return;
@@ -158,6 +160,8 @@ public final class GodotPluginRegistry {
continue;
}
+ Log.i(TAG, "Initializing Godot plugin " + pluginName);
+
// Retrieve the plugin class full name.
String pluginHandleClassFullName = metaData.getString(metaDataName);
if (!TextUtils.isEmpty(pluginHandleClassFullName)) {
@@ -177,6 +181,7 @@ public final class GodotPluginRegistry {
"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
}
registry.put(pluginName, pluginHandle);
+ Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName());
} catch (ClassNotFoundException e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
} catch (IllegalAccessException e) {
diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java
index 2742bb15bf..f894bd5132 100644
--- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java
+++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java
@@ -56,7 +56,7 @@ abstract public class PurchaseTask {
PaymentsCache pc = new PaymentsCache(context);
Boolean isBlocked = pc.getConsumableFlag("block", sku);
/*
- if(isBlocked){
+ if(isBlocked) {
Log.d("XXX", "Is awaiting payment confirmation");
error("Awaiting payment confirmation");
return;
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 0da0bd6387..0a42adeaf2 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
- _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;I)V");
+ _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;III)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
@@ -132,11 +132,11 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != 0) && (_hide_keyboard != 0);
}
-void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length) {
+void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = ThreadAndroid::get_env();
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
- env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length);
+ env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length, p_cursor_start, p_cursor_end);
}
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index dbb3b564f6..1742021379 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -70,7 +70,7 @@ public:
int get_screen_dpi();
String get_unique_id();
bool has_vk();
- void show_vk(const String &p_existing, int p_max_input_length);
+ void show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk();
int get_vk_height();
void set_vk_height(int p_height);
diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h
new file mode 100644
index 0000000000..9ad7de1202
--- /dev/null
+++ b/platform/android/plugin/godot_plugin_config.h
@@ -0,0 +1,251 @@
+/*************************************************************************/
+/* godot_plugin_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error_list.h"
+#include "core/io/config_file.h"
+#include "core/ustring.h"
+
+static const char *PLUGIN_CONFIG_EXT = ".gdap";
+
+static const char *CONFIG_SECTION = "config";
+static const char *CONFIG_NAME_KEY = "name";
+static const char *CONFIG_BINARY_TYPE_KEY = "binary_type";
+static const char *CONFIG_BINARY_KEY = "binary";
+
+static const char *DEPENDENCIES_SECTION = "dependencies";
+static const char *DEPENDENCIES_LOCAL_KEY = "local";
+static const char *DEPENDENCIES_REMOTE_KEY = "remote";
+static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos";
+
+static const char *BINARY_TYPE_LOCAL = "local";
+static const char *BINARY_TYPE_REMOTE = "remote";
+
+static const char *PLUGIN_VALUE_SEPARATOR = "|";
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field
+- **binary**:
+ - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`).
+ - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0").
+
+The `dependencies` section and fields are optional and defined as follow:
+- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory.
+- **remote**: contains a list of remote binary gradle dependencies for the plugin.
+- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies.
+
+ See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871
+ */
+struct PluginConfig {
+ // Set to true when the config file is properly loaded.
+ bool valid_config = false;
+
+ // Required config section
+ String name;
+ String binary_type;
+ String binary;
+
+ // Optional dependencies section
+ Vector<String> local_dependencies;
+ Vector<String> remote_dependencies;
+ Vector<String> custom_maven_repos;
+};
+
+/*
+ * Set of prebuilt plugins.
+ */
+static const PluginConfig GODOT_PAYMENT = {
+ /*.valid_config =*/true,
+ /*.name =*/"GodotPayment",
+ /*.binary_type =*/"local",
+ /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
+ /*.local_dependencies =*/{},
+ /*.remote_dependencies =*/{},
+ /*.custom_maven_repos =*/{}
+};
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+ if (!dependency_path.empty()) {
+ if (dependency_path.is_abs_path()) {
+ absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path);
+ } else {
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+ }
+ }
+
+ return absolute_path;
+}
+
+static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, String plugin_config_dir) {
+ PluginConfig resolved = prebuilt_plugin;
+ resolved.binary = resolved.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary;
+ if (!prebuilt_plugin.local_dependencies.empty()) {
+ resolved.local_dependencies.clear();
+ for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) {
+ resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i]));
+ }
+ }
+ return resolved;
+}
+
+static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) {
+ Vector<PluginConfig> prebuilt_plugins;
+ prebuilt_plugins.push_back(resolve_prebuilt_plugin(GODOT_PAYMENT, plugins_base_dir));
+ return prebuilt_plugins;
+}
+
+static inline bool is_plugin_config_valid(PluginConfig plugin_config) {
+ bool valid_name = !plugin_config.name.empty();
+ bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL ||
+ plugin_config.binary_type == BINARY_TYPE_REMOTE;
+
+ bool valid_binary = false;
+ if (valid_binary_type) {
+ valid_binary = !plugin_config.binary.empty() &&
+ (plugin_config.binary_type == BINARY_TYPE_REMOTE ||
+ FileAccess::exists(plugin_config.binary));
+ }
+
+ bool valid_local_dependencies = true;
+ if (!plugin_config.local_dependencies.empty()) {
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ if (!FileAccess::exists(plugin_config.local_dependencies[i])) {
+ valid_local_dependencies = false;
+ break;
+ }
+ }
+ }
+ return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
+}
+
+static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+ PluginConfig plugin_config = {};
+
+ if (config_file.is_valid()) {
+ Error err = config_file->load(path);
+ if (err == OK) {
+ String config_base_dir = path.get_base_dir();
+
+ plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String());
+ plugin_config.binary_type = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_TYPE_KEY, String());
+
+ String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
+ plugin_config.binary = plugin_config.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path;
+
+ if (config_file->has_section(DEPENDENCIES_SECTION)) {
+ Vector<String> local_dependencies_paths = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LOCAL_KEY, Vector<String>());
+ if (!local_dependencies_paths.empty()) {
+ for (int i = 0; i < local_dependencies_paths.size(); i++) {
+ plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i]));
+ }
+ }
+
+ plugin_config.remote_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_REMOTE_KEY, Vector<String>());
+ plugin_config.custom_maven_repos = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>());
+ }
+
+ plugin_config.valid_config = is_plugin_config_valid(plugin_config);
+ }
+ }
+
+ return plugin_config;
+}
+
+static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) {
+ String plugins_binaries;
+ if (!plugins_configs.empty()) {
+ Vector<String> binaries;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ if (config.binary_type == binary_type) {
+ binaries.push_back(config.binary);
+ }
+
+ if (binary_type == BINARY_TYPE_LOCAL) {
+ binaries.append_array(config.local_dependencies);
+ }
+
+ if (binary_type == BINARY_TYPE_REMOTE) {
+ binaries.append_array(config.remote_dependencies);
+ }
+ }
+
+ plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries);
+ }
+
+ return plugins_binaries;
+}
+
+static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) {
+ String custom_maven_repos;
+ if (!plugins_configs.empty()) {
+ Vector<String> repos_urls;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ repos_urls.append_array(config.custom_maven_repos);
+ }
+
+ custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls);
+ }
+ return custom_maven_repos;
+}
+
+static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) {
+ String plugins_names;
+ if (!plugins_configs.empty()) {
+ Vector<String> names;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfig config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ names.push_back(config.name);
+ }
+ plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names);
+ }
+
+ return plugins_names;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp
index 94c9e83368..2fbbeeb176 100644
--- a/platform/haiku/audio_driver_media_kit.cpp
+++ b/platform/haiku/audio_driver_media_kit.cpp
@@ -39,11 +39,11 @@ int32_t *AudioDriverMediaKit::samples_in = nullptr;
Error AudioDriverMediaKit::init() {
active = false;
- mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE);
+ mix_rate = GLOBAL_GET("audio/mix_rate");
speaker_mode = SPEAKER_MODE_STEREO;
channels = 2;
- int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY);
+ int latency = GLOBAL_GET("audio/output_latency");
buffer_size = next_power_of_2(latency * mix_rate / 1000);
samples_in = memnew_arr(int32_t, buffer_size * channels);
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
index 107d142757..41dd623e69 100644
--- a/platform/iphone/os_iphone.cpp
+++ b/platform/iphone/os_iphone.cpp
@@ -441,7 +441,7 @@ extern Error _shell_open(String p_uri);
extern void _set_keep_screen_on(bool p_enabled);
extern void _vibrate();
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) {
+void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
_show_keyboard(p_existing_text);
};
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index a57b8eeb5d..955eb15d57 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -170,7 +170,7 @@ public:
virtual bool can_draw() const;
virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1);
+ virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual int get_virtual_keyboard_height() const;
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index 03ca5f78d1..b52bd4ce60 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -30,6 +30,8 @@
#include "audio_driver_javascript.h"
+#include "core/project_settings.h"
+
#include <emscripten.h>
AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr;
@@ -62,10 +64,15 @@ void AudioDriverJavaScript::process_capture(float sample) {
}
Error AudioDriverJavaScript::init() {
+ int mix_rate = GLOBAL_GET("audio/mix_rate");
+ int latency = GLOBAL_GET("audio/output_latency");
+
/* clang-format off */
_driver_id = EM_ASM_INT({
+ const MIX_RATE = $0;
+ const LATENCY = $1;
return Module.IDHandler.add({
- 'context': new (window.AudioContext || window.webkitAudioContext),
+ 'context': new (window.AudioContext || window.webkitAudioContext)({ sampleRate: MIX_RATE, latencyHint: LATENCY}),
'input': null,
'stream': null,
'script': null
@@ -74,26 +81,19 @@ Error AudioDriverJavaScript::init() {
/* clang-format on */
int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
+ buffer_length = closest_power_of_2((latency * mix_rate / 1000) * channel_count);
/* clang-format off */
buffer_length = EM_ASM_INT({
var ref = Module.IDHandler.get($0);
- var ctx = ref['context'];
- var CHANNEL_COUNT = $1;
-
- var channelCount = ctx.destination.channelCount;
- var script = null;
- try {
- // Try letting the browser recommend a buffer length.
- script = ctx.createScriptProcessor(0, 2, channelCount);
- } catch (e) {
- // ...otherwise, default to 4096.
- script = ctx.createScriptProcessor(4096, 2, channelCount);
- }
+ const ctx = ref['context'];
+ const BUFFER_LENGTH = $1;
+ const CHANNEL_COUNT = $2;
+
+ var script = ctx.createScriptProcessor(BUFFER_LENGTH, 2, CHANNEL_COUNT);
script.connect(ctx.destination);
ref['script'] = script;
-
return script.bufferSize;
- }, _driver_id, channel_count);
+ }, _driver_id, buffer_length, channel_count);
/* clang-format on */
if (!buffer_length) {
return FAILED;
@@ -156,6 +156,25 @@ void AudioDriverJavaScript::resume() {
/* clang-format on */
}
+float AudioDriverJavaScript::get_latency() {
+ /* clang-format off */
+ return EM_ASM_DOUBLE({
+ const ref = Module.IDHandler.get($0);
+ var latency = 0;
+ if (ref && ref['context']) {
+ const ctx = ref['context'];
+ if (ctx.baseLatency) {
+ latency += ctx.baseLatency;
+ }
+ if (ctx.outputLatency) {
+ latency += ctx.outputLatency;
+ }
+ }
+ return latency;
+ }, _driver_id);
+ /* clang-format on */
+}
+
int AudioDriverJavaScript::get_mix_rate() const {
/* clang-format off */
return EM_ASM_INT({
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index 7d5237998a..9b26be001e 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -50,6 +50,7 @@ public:
virtual Error init();
virtual void start();
void resume();
+ virtual float get_latency();
virtual int get_mix_rate() const;
virtual SpeakerMode get_speaker_mode() const;
virtual void lock();
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index 1cdcdbcaf5..9a1191490c 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -278,13 +278,17 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (BOOL)windowShouldClose:(id)sender {
- ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), YES);
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return YES;
+ }
DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
return NO;
}
- (void)windowWillClose:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
while (wd.transient_children.size()) {
@@ -310,7 +314,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
wd.fullscreen = true;
@@ -320,8 +326,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidExitFullScreen:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id))
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
wd.fullscreen = false;
@@ -383,8 +390,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidResize:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id))
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
#if defined(OPENGL_ENABLED)
@@ -425,11 +433,26 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidMove:(NSNotification *)notification {
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
+ DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+
DS_OSX->_release_pressed_events();
+
+ if (!wd.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
}
- (void)windowDidBecomeKey:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [wd.window_view backingScaleFactor] : 1.0;
@@ -441,7 +464,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidResignKey:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
DS_OSX->window_focused = false;
@@ -451,7 +476,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidMiniaturize:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
DS_OSX->window_focused = false;
@@ -461,7 +488,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
- (void)windowDidDeminiaturize:(NSNotification *)notification {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
+ if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ return;
+ }
DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
DS_OSX->window_focused = true;
@@ -2148,7 +2177,7 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale - _get_screens_origin();
position.y *= -1;
- Size2i size = Size2i(nsrect.size.width, nsrect.size.height) / displayScale;
+ Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * displayScale;
return Rect2i(position, size);
}
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index b2ef9c0038..784fba75ec 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -57,6 +57,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
+ void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
#ifdef OSX_ENABLED
bool use_codesign() const { return true; }
@@ -363,6 +364,7 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
**/
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+#ifdef OSX_ENABLED
List<String> args;
if (p_preset->get("codesign/timestamp")) {
@@ -373,8 +375,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
args.push_back("runtime");
}
- if (p_preset->get("codesign/entitlements") != "") {
- /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */
+ if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) {
args.push_back("--entitlements");
args.push_back(p_preset->get("codesign/entitlements"));
}
@@ -407,6 +408,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
EditorNode::add_io_error("codesign: invalid entitlements file");
return FAILED;
}
+#endif
return OK;
}
@@ -500,53 +502,42 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
Error err = OK;
String tmp_app_path_name = "";
- zlib_filefunc_def io2 = io;
- FileAccess *dst_f = nullptr;
- io2.opaque = &dst_f;
- zipFile dst_pkg_zip = nullptr;
DirAccess *tmp_app_path = nullptr;
String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip";
- if (export_format == "dmg") {
- // We're on OSX so we can export to DMG, but first we create our application bundle
- tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app");
- print_line("Exporting to " + tmp_app_path_name);
- tmp_app_path = DirAccess::create_for_path(tmp_app_path_name);
- if (!tmp_app_path) {
- err = ERR_CANT_CREATE;
- }
- // Create our folder structure or rely on unzip?
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
- }
+ // Create our application bundle.
+ tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app");
+ print_line("Exporting to " + tmp_app_path_name);
+ tmp_app_path = DirAccess::create_for_path(tmp_app_path_name);
+ if (!tmp_app_path) {
+ err = ERR_CANT_CREATE;
+ }
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
- }
+ // Create our folder structure.
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
+ }
- if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
- err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
- }
- } else {
- // Open our destination zip file
- dst_pkg_zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
- if (!dst_pkg_zip) {
- err = ERR_CANT_CREATE;
- }
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
}
- // Now process our template
+ if (err == OK) {
+ print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
+ err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
+ }
+
+ // Now process our template.
bool found_binary = false;
int total_size = 0;
while (ret == UNZ_OK && err == OK) {
bool is_execute = false;
- //get filename
+ // Get filename.
unz_file_info info;
char fname[16384];
ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
@@ -556,13 +547,12 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
Vector<uint8_t> data;
data.resize(info.uncompressed_size);
- //read
+ // Read.
unzOpenCurrentFile(src_pkg_zip);
unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size());
unzCloseCurrentFile(src_pkg_zip);
- //write
-
+ // Write.
file = file.replace_first("osx_template.app/", "");
if (file == "Contents/Info.plist") {
@@ -572,7 +562,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (file.begins_with("Contents/MacOS/godot_")) {
if (file != "Contents/MacOS/" + binary_to_use) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //ignore!
+ continue; // skip
}
found_binary = true;
is_execute = true;
@@ -580,7 +570,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (file == "Contents/Resources/icon.icns") {
- //see if there is an icon
+ // See if there is an icon.
String iconpath;
if (p_preset->get("application/icon") != "") {
iconpath = p_preset->get("application/icon");
@@ -612,14 +602,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (file.find("/data.mono.osx.64.release_debug/") != -1) {
if (!p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //skip
+ continue; // skip
}
file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/");
}
if (file.find("/data.mono.osx.64.release/") != -1) {
if (p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
- continue; //skip
+ continue; // skip
}
file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/");
}
@@ -627,62 +617,31 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
print_line("ADDING: " + file + " size: " + itos(data.size()));
total_size += data.size();
- if (export_format == "dmg") {
- // write it into our application bundle
- file = tmp_app_path_name.plus_file(file);
- if (err == OK) {
- err = tmp_app_path->make_dir_recursive(file.get_base_dir());
- }
- if (err == OK) {
- // write the file, need to add chmod
- FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
- if (f) {
- f->store_buffer(data.ptr(), data.size());
- f->close();
- if (is_execute) {
- // Chmod with 0755 if the file is executable
- FileAccess::set_unix_permissions(file, 0755);
- }
- memdelete(f);
- } else {
- err = ERR_CANT_CREATE;
+ // Write it into our application bundle.
+ file = tmp_app_path_name.plus_file(file);
+ if (err == OK) {
+ err = tmp_app_path->make_dir_recursive(file.get_base_dir());
+ }
+ if (err == OK) {
+ FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
+ if (f) {
+ f->store_buffer(data.ptr(), data.size());
+ f->close();
+ if (is_execute) {
+ // chmod with 0755 if the file is executable.
+ FileAccess::set_unix_permissions(file, 0755);
}
+ memdelete(f);
+ } else {
+ err = ERR_CANT_CREATE;
}
- } else {
- // add it to our zip file
- file = pkg_name + ".app/" + file;
-
- zip_fileinfo fi;
- fi.tmz_date.tm_hour = info.tmu_date.tm_hour;
- fi.tmz_date.tm_min = info.tmu_date.tm_min;
- fi.tmz_date.tm_sec = info.tmu_date.tm_sec;
- fi.tmz_date.tm_mon = info.tmu_date.tm_mon;
- fi.tmz_date.tm_mday = info.tmu_date.tm_mday;
- fi.tmz_date.tm_year = info.tmu_date.tm_year;
- fi.dosDate = info.dosDate;
- fi.internal_fa = info.internal_fa;
- fi.external_fa = info.external_fa;
-
- zipOpenNewFileInZip(dst_pkg_zip,
- file.utf8().get_data(),
- &fi,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(dst_pkg_zip, data.ptr(), data.size());
- zipCloseFileInZip(dst_pkg_zip);
}
}
ret = unzGoToNextFile(src_pkg_zip);
}
- // we're done with our source zip
+ // We're done with our source zip.
unzClose(src_pkg_zip);
if (!found_binary) {
@@ -695,122 +654,130 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
return ERR_SKIP;
}
- if (export_format == "dmg") {
- String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
- Vector<SharedObject> shared_objects;
- err = save_pack(p_preset, pack_path, &shared_objects);
+ String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
+ Vector<SharedObject> shared_objects;
+ err = save_pack(p_preset, pack_path, &shared_objects);
- // see if we can code sign our new package
- bool sign_enabled = p_preset->get("codesign/enable");
+ // See if we can code sign our new package.
+ bool sign_enabled = p_preset->get("codesign/enable");
- if (err == OK) {
- DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- for (int i = 0; i < shared_objects.size(); i++) {
- err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
- if (err == OK && sign_enabled) {
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
- }
+ if (err == OK) {
+ DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ for (int i = 0; i < shared_objects.size(); i++) {
+ err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
+ if (err == OK && sign_enabled) {
+ err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file());
}
- memdelete(da);
}
+ memdelete(da);
+ }
- if (err == OK && sign_enabled) {
- if (ep.step("Code signing bundle", 2)) {
- return ERR_SKIP;
- }
-
- // the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP
-
- // start with our application
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name);
-
- ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign
+ if (err == OK && sign_enabled) {
+ if (ep.step("Code signing bundle", 2)) {
+ return ERR_SKIP;
}
+ err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name);
+ }
- // and finally create a DMG
+ if (export_format == "dmg") {
+ // Create a DMG.
if (err == OK) {
if (ep.step("Making DMG", 3)) {
return ERR_SKIP;
}
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
}
-
- // Clean up temporary .app dir
- OS::get_singleton()->move_to_trash(tmp_app_path_name);
-
- } else { // pck
-
- String pack_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".pck");
-
- Vector<SharedObject> shared_objects;
- err = save_pack(p_preset, pack_path, &shared_objects);
-
- if (err == OK) {
- zipOpenNewFileInZip(dst_pkg_zip,
- (pkg_name + ".app/Contents/Resources/" + pkg_name + ".pck").utf8().get_data(),
- nullptr,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- FileAccess *pf = FileAccess::open(pack_path, FileAccess::READ);
- if (pf) {
- const int BSIZE = 16384;
- uint8_t buf[BSIZE];
-
- while (true) {
- int r = pf->get_buffer(buf, BSIZE);
- if (r <= 0) {
- break;
- }
- zipWriteInFileInZip(dst_pkg_zip, buf, r);
- }
-
- zipCloseFileInZip(dst_pkg_zip);
- memdelete(pf);
- } else {
- err = ERR_CANT_OPEN;
+ // Sign DMG.
+ if (err == OK && sign_enabled) {
+ if (ep.step("Code signing DMG", 3)) {
+ return ERR_SKIP;
}
+ err = _code_sign(p_preset, p_path);
}
-
+ } else {
+ // Create ZIP.
if (err == OK) {
- //add shared objects
- for (int i = 0; i < shared_objects.size(); i++) {
- Vector<uint8_t> file = FileAccess::get_file_as_array(shared_objects[i].path);
- ERR_CONTINUE(file.empty());
-
- zipOpenNewFileInZip(dst_pkg_zip,
- (pkg_name + ".app/Contents/Frameworks/").plus_file(shared_objects[i].path.get_file()).utf8().get_data(),
- nullptr,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(dst_pkg_zip, file.ptr(), file.size());
- zipCloseFileInZip(dst_pkg_zip);
+ if (ep.step("Making ZIP", 3)) {
+ return ERR_SKIP;
+ }
+ if (FileAccess::exists(p_path)) {
+ OS::get_singleton()->move_to_trash(p_path);
}
- }
- // Clean up generated file.
- DirAccess::remove_file_or_error(pack_path);
+ FileAccess *dst_f = nullptr;
+ zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f);
+ zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
+
+ _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
+
+ zipClose(zip, nullptr);
+ }
}
- }
- if (dst_pkg_zip) {
- zipClose(dst_pkg_zip, nullptr);
+ // Clean up temporary .app dir.
+ OS::get_singleton()->move_to_trash(tmp_app_path_name);
}
return err;
}
+void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
+ String dir = p_root_path.plus_file(p_folder);
+
+ DirAccess *da = DirAccess::open(dir);
+ da->list_dir_begin();
+ String f;
+ while ((f = da->get_next()) != "") {
+ if (f == "." || f == "..") {
+ continue;
+ }
+ if (da->current_is_dir()) {
+ _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name);
+ } else {
+ bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name));
+
+ OS::Time time = OS::get_singleton()->get_time();
+ OS::Date date = OS::get_singleton()->get_date();
+
+ zip_fileinfo zipfi;
+ zipfi.tmz_date.tm_hour = time.hour;
+ zipfi.tmz_date.tm_mday = date.day;
+ zipfi.tmz_date.tm_min = time.min;
+ zipfi.tmz_date.tm_mon = date.month;
+ zipfi.tmz_date.tm_sec = time.sec;
+ zipfi.tmz_date.tm_year = date.year;
+ zipfi.dosDate = 0;
+ zipfi.external_fa = (is_executable ? 0755 : 0644) << 16L;
+ zipfi.internal_fa = 0;
+
+ zipOpenNewFileInZip4(p_zip,
+ p_folder.plus_file(f).utf8().get_data(),
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION,
+ 0,
+ -MAX_WBITS,
+ DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY,
+ nullptr,
+ 0,
+ 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions
+ 0);
+
+ Vector<uint8_t> array = FileAccess::get_file_as_array(dir.plus_file(f));
+ zipWriteInFileInZip(p_zip, array.ptr(), array.size());
+ zipCloseFileInZip(p_zip);
+ }
+ }
+ da->list_dir_end();
+ memdelete(da);
+}
+
bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index 7e4d17f5e7..7bd67d3726 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -698,7 +698,7 @@ bool OS_UWP::has_virtual_keyboard() const {
return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;
}
-void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) {
+void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
InputPane ^ pane = InputPane::GetForCurrentView();
pane->TryShow();
}
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 1cab38cabe..95359c68b0 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -234,7 +234,7 @@ public:
virtual bool has_touchscreen_ui_hint() const;
virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1);
+ virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
diff --git a/platform/uwp/thread_uwp.cpp b/platform/uwp/thread_uwp.cpp
index 35a366a173..8e7bb144be 100644
--- a/platform/uwp/thread_uwp.cpp
+++ b/platform/uwp/thread_uwp.cpp
@@ -59,11 +59,3 @@ void ThreadUWP::make_default() {
get_thread_id_func = get_thread_id_func_uwp;
wait_to_finish_func = wait_to_finish_func_uwp;
};
-
-ThreadUWP::ThreadUWP(){
-
-};
-
-ThreadUWP::~ThreadUWP(){
-
-};
diff --git a/platform/uwp/thread_uwp.h b/platform/uwp/thread_uwp.h
index 1f1d85a66a..9b2a2590a8 100644
--- a/platform/uwp/thread_uwp.h
+++ b/platform/uwp/thread_uwp.h
@@ -44,16 +44,16 @@ class ThreadUWP : public Thread {
static ID get_thread_id_func_uwp();
static void wait_to_finish_func_uwp(Thread *p_thread);
- ThreadUWP();
+ ThreadUWP() {}
public:
virtual ID get_id() const;
static void make_default();
- ~ThreadUWP();
+ ~ThreadUWP() {}
};
-#endif
+#endif // UWP_ENABLED
-#endif
+#endif // THREAD_UWP_H
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 114b64855e..9d8344fa7e 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -521,7 +521,7 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
}
#endif
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[p_window].wtctx) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
wintab_WTClose(windows[p_window].wtctx);
windows[p_window].wtctx = 0;
}
@@ -1695,6 +1695,12 @@ void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event
}
void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ _THREAD_SAFE_METHOD_
+ if (in_dispatch_input_event) {
+ return;
+ }
+
+ in_dispatch_input_event = true;
Variant ev = p_event;
Variant *evp = &ev;
Variant ret;
@@ -1706,6 +1712,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event)
ERR_FAIL_COND(!windows.has(event_from_window->get_window_id()));
Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
if (callable.is_null()) {
+ in_dispatch_input_event = false;
return;
}
callable.call((const Variant **)&evp, 1, ret, ce);
@@ -1719,6 +1726,8 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event)
callable.call((const Variant **)&evp, 1, ret, ce);
}
}
+
+ in_dispatch_input_event = false;
}
LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
@@ -1782,7 +1791,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
alt_mem = false;
};
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
wintab_WTEnable(windows[window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam));
}
@@ -1915,7 +1924,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WT_CSRCHANGE:
case WT_PROXIMITY: {
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
AXIS pressure;
if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
windows[window_id].min_pressure = int(pressure.axMin);
@@ -1929,7 +1938,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
} break;
case WT_PACKET: {
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
PACKET packet;
if (wintab_WTPacket(windows[window_id].wtctx, wParam, &packet)) {
float pressure = float(packet.pkNormalPressure - windows[window_id].min_pressure) / float(windows[window_id].max_pressure - windows[window_id].min_pressure);
@@ -1998,17 +2007,43 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
old_x = mm->get_position().x;
old_y = mm->get_position().y;
if (windows[window_id].window_has_focus)
- Input::get_singleton()->parse_input_event(mm);
+ Input::get_singleton()->accumulate_input_event(mm);
}
return 0;
}
} break;
+ case WM_POINTERENTER: {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
+ break;
+ }
+
+ if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) {
+ break;
+ }
+
+ uint32_t pointer_id = LOWORD(wParam);
+ POINTER_INPUT_TYPE pointer_type = PT_POINTER;
+ if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
+ break;
+ }
+
+ if (pointer_type != PT_PEN) {
+ break;
+ }
+
+ windows[window_id].block_mm = true;
+ return 0;
+ } break;
+ case WM_POINTERLEAVE: {
+ windows[window_id].block_mm = false;
+ return 0;
+ } break;
case WM_POINTERUPDATE: {
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
- if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) {
+ if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) {
break;
}
@@ -2118,12 +2153,16 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
old_x = mm->get_position().x;
old_y = mm->get_position().y;
if (windows[window_id].window_has_focus) {
- Input::get_singleton()->parse_input_event(mm);
+ Input::get_singleton()->accumulate_input_event(mm);
}
return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event
} break;
case WM_MOUSEMOVE: {
+ if (windows[window_id].block_mm) {
+ break;
+ }
+
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
@@ -2168,7 +2207,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_shift((wParam & MK_SHIFT) != 0);
mm->set_alt(alt_mem);
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
// Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not update recently.
if (windows[window_id].last_pressure_update < 10) {
windows[window_id].last_pressure_update++;
@@ -2649,7 +2688,8 @@ void DisplayServerWindows::_process_key_events() {
KeyEvent &ke = key_event_buffer[i];
switch (ke.uMsg) {
case WM_CHAR: {
- if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) {
+ // extended keys should only be processed as WM_KEYDOWN message.
+ if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) {
Ref<InputEventKey> k;
k.instance();
@@ -2719,6 +2759,45 @@ void DisplayServerWindows::_process_key_events() {
key_event_pos = 0;
}
+void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const String &p_new_driver) {
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ WindowData &wd = E->get();
+ wd.block_mm = false;
+ if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) {
+ wintab_WTEnable(wd.wtctx, false);
+ wintab_WTClose(wd.wtctx);
+ wd.wtctx = 0;
+ }
+ if ((p_new_driver == "wintab") && wintab_available) {
+ wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
+ wd.wtlc.lcOptions |= CXO_MESSAGES;
+ wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+ wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+ wd.wtlc.lcPktMode = 0;
+ wd.wtlc.lcOutOrgX = 0;
+ wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
+ wd.wtlc.lcOutOrgY = 0;
+ wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
+ wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
+ if (wd.wtctx) {
+ wintab_WTEnable(wd.wtctx, true);
+ AXIS pressure;
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+ wd.min_pressure = int(pressure.axMin);
+ wd.max_pressure = int(pressure.axMax);
+ }
+ AXIS orientation[3];
+ if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+ wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+ }
+ wintab_WTEnable(wd.wtctx, true);
+ } else {
+ print_verbose("WinTab context creation failed.");
+ }
+ }
+ }
+}
+
DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) {
DWORD dwExStyle;
DWORD dwStyle;
@@ -2775,7 +2854,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
DragAcceptFiles(wd.hWnd, true);
- if (!OS::get_singleton()->is_wintab_disabled() && wintab_available) {
+ if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available) {
wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
wd.wtlc.lcOptions |= CXO_MESSAGES;
wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
@@ -2834,6 +2913,7 @@ WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr;
WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr;
// Windows Ink API
+bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
@@ -2844,25 +2924,6 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS {
} SHC_PROCESS_DPI_AWARENESS;
DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
- //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
- HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
- if (wintab_lib) {
- wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW");
- wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose");
- wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW");
- wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket");
- wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable");
-
- wintab_available = wintab_WTOpen && wintab_WTClose && wintab_WTInfo && wintab_WTPacket && wintab_WTEnable;
- }
-
- //Note: Windows Ink API for pen input, available on Windows 8+ only.
- HMODULE user32_lib = LoadLibraryW(L"user32.dll");
- if (user32_lib) {
- win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
- win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
- }
-
drop_events = false;
key_event_pos = 0;
@@ -3044,18 +3105,6 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
-#if defined(VULKAN_ENABLED)
- if (rendering_driver == "vulkan") {
- if (rendering_device_vulkan) {
- rendering_device_vulkan->finalize();
- memdelete(rendering_device_vulkan);
- }
-
- if (context_vulkan)
- memdelete(context_vulkan);
- }
-#endif
-
if (user_proc) {
SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
};
@@ -3072,4 +3121,16 @@ DisplayServerWindows::~DisplayServerWindows() {
}
DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
}
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ }
+
+ if (context_vulkan)
+ memdelete(context_vulkan);
+ }
+#endif
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index ea08b1899f..caf8598dc2 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -229,6 +229,14 @@ typedef struct tagPOINTER_PEN_INFO {
#define WM_POINTERUPDATE 0x0245
#endif
+#ifndef WM_POINTERENTER
+#define WM_POINTERENTER 0x0249
+#endif
+
+#ifndef WM_POINTERLEAVE
+#define WM_POINTERLEAVE 0x024A
+#endif
+
typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
@@ -256,6 +264,7 @@ class DisplayServerWindows : public DisplayServer {
_THREAD_SAFE_CLASS_
+public:
// WinTab API
static bool wintab_available;
static WTOpenPtr wintab_WTOpen;
@@ -265,9 +274,13 @@ class DisplayServerWindows : public DisplayServer {
static WTEnablePtr wintab_WTEnable;
// Windows Ink API
+ static bool winink_available;
static GetPointerTypePtr win8p_GetPointerType;
static GetPointerPenInfoPtr win8p_GetPointerPenInfo;
+ void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
+
+private:
void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap);
enum {
@@ -328,6 +341,7 @@ class DisplayServerWindows : public DisplayServer {
int min_pressure;
int max_pressure;
bool tilt_supported;
+ bool block_mm = false;
int last_pressure_update;
float last_pressure;
@@ -388,6 +402,7 @@ class DisplayServerWindows : public DisplayServer {
uint32_t last_button_state = 0;
bool use_raw_input = false;
bool drop_events = false;
+ bool in_dispatch_input_event = false;
bool console_visible = false;
WNDCLASSEXW wc;
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index 1fe7551445..d8d0b13068 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -411,3 +411,16 @@ unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended
return keycode;
}
+
+bool KeyMappingWindows::is_extended_key(unsigned int p_code) {
+ return p_code == VK_INSERT ||
+ p_code == VK_DELETE ||
+ p_code == VK_HOME ||
+ p_code == VK_END ||
+ p_code == VK_PRIOR ||
+ p_code == VK_NEXT ||
+ p_code == VK_LEFT ||
+ p_code == VK_UP ||
+ p_code == VK_RIGHT ||
+ p_code == VK_DOWN;
+}
diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h
index 4d0c12c16e..f64f1feb9f 100644
--- a/platform/windows/key_mapping_windows.h
+++ b/platform/windows/key_mapping_windows.h
@@ -43,6 +43,7 @@ class KeyMappingWindows {
public:
static unsigned int get_keysym(unsigned int p_code);
static unsigned int get_scansym(unsigned int p_code, bool p_extended);
+ static bool is_extended_key(unsigned int p_code);
};
#endif // KEY_MAPPING_WINDOWS_H
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 7c06e93a8a..f11888b26c 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -415,23 +415,30 @@ uint64_t OS_Windows::get_ticks_usec() const {
return time;
}
+String OS_Windows::_quote_command_line_argument(const String &p_text) const {
+ for (int i = 0; i < p_text.size(); i++) {
+ CharType c = p_text[i];
+ if (c == ' ' || c == '&' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '=' || c == ';' || c == '!' || c == '\'' || c == '+' || c == ',' || c == '`' || c == '~') {
+ return "\"" + p_text + "\"";
+ }
+ }
+ return p_text;
+}
+
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
if (p_blocking && r_pipe) {
- String argss;
- argss = "\"\"" + p_path + "\"";
-
+ String argss = _quote_command_line_argument(p_path);
for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
- argss += " \"" + E->get() + "\"";
+ argss += " " + _quote_command_line_argument(E->get());
}
- argss += "\"";
-
if (read_stderr) {
argss += " 2>&1"; // Read stderr too
}
+ // Note: _wpopen is calling command as "cmd.exe /c argss", instead of executing it directly, add extra quotes around full command, to prevent it from stripping quotes in the command.
+ argss = _quote_command_line_argument(argss);
FILE *f = _wpopen(argss.c_str(), L"r");
-
ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
char buf[65535];
@@ -446,19 +453,19 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
}
int rv = _pclose(f);
- if (r_exitcode)
+ if (r_exitcode) {
*r_exitcode = rv;
+ }
return OK;
}
- String cmdline = "\"" + p_path + "\"";
+ String cmdline = _quote_command_line_argument(p_path);
const List<String>::Element *I = p_arguments.front();
while (I) {
- cmdline += " \"" + I->get() + "\"";
-
+ cmdline += " " + _quote_command_line_argument(I->get());
I = I->next();
- };
+ }
ProcessInfo pi;
ZeroMemory(&pi.si, sizeof(pi.si));
@@ -466,17 +473,20 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
ZeroMemory(&pi.pi, sizeof(pi.pi));
LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
- Vector<CharType> modstr; //windows wants to change this no idea why
+ Vector<CharType> modstr; // Windows wants to change this no idea why.
modstr.resize(cmdline.size());
- for (int i = 0; i < cmdline.size(); i++)
+ for (int i = 0; i < cmdline.size(); i++) {
modstr.write[i] = cmdline[i];
+ }
+
int ret = CreateProcessW(nullptr, modstr.ptrw(), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi);
ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK);
if (p_blocking) {
DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE);
- if (r_exitcode)
+ if (r_exitcode) {
*r_exitcode = ret2;
+ }
CloseHandle(pi.pi.hProcess);
CloseHandle(pi.pi.hThread);
@@ -484,9 +494,9 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
ProcessID pid = pi.pi.dwProcessId;
if (r_child_id) {
*r_child_id = pid;
- };
+ }
process_map->insert(pid, pi);
- };
+ }
return OK;
};
@@ -768,7 +778,69 @@ Error OS_Windows::move_to_trash(const String &p_path) {
return OK;
}
+int OS_Windows::get_tablet_driver_count() const {
+ return tablet_drivers.size();
+}
+
+String OS_Windows::get_tablet_driver_name(int p_driver) const {
+ if (p_driver < 0 || p_driver >= tablet_drivers.size()) {
+ return "";
+ } else {
+ return tablet_drivers[p_driver];
+ }
+}
+
+String OS_Windows::get_current_tablet_driver() const {
+ return tablet_driver;
+}
+
+void OS_Windows::set_current_tablet_driver(const String &p_driver) {
+ bool found = false;
+ for (int i = 0; i < get_tablet_driver_count(); i++) {
+ if (p_driver == get_tablet_driver_name(i)) {
+ found = true;
+ }
+ }
+ if (found) {
+ if (DisplayServerWindows::get_singleton()) {
+ ((DisplayServerWindows *)DisplayServerWindows::get_singleton())->_update_tablet_ctx(tablet_driver, p_driver);
+ }
+ tablet_driver = p_driver;
+ } else {
+ ERR_PRINT("Unknown tablet driver " + p_driver + ".");
+ }
+}
+
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
+ //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
+ HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
+ if (wintab_lib) {
+ DisplayServerWindows::wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW");
+ DisplayServerWindows::wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose");
+ DisplayServerWindows::wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW");
+ DisplayServerWindows::wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket");
+ DisplayServerWindows::wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable");
+
+ DisplayServerWindows::wintab_available = DisplayServerWindows::wintab_WTOpen && DisplayServerWindows::wintab_WTClose && DisplayServerWindows::wintab_WTInfo && DisplayServerWindows::wintab_WTPacket && DisplayServerWindows::wintab_WTEnable;
+ }
+
+ if (DisplayServerWindows::wintab_available) {
+ tablet_drivers.push_back("wintab");
+ }
+
+ //Note: Windows Ink API for pen input, available on Windows 8+ only.
+ HMODULE user32_lib = LoadLibraryW(L"user32.dll");
+ if (user32_lib) {
+ DisplayServerWindows::win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
+ DisplayServerWindows::win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
+
+ DisplayServerWindows::winink_available = DisplayServerWindows::win8p_GetPointerType && DisplayServerWindows::win8p_GetPointerPenInfo;
+ }
+
+ if (DisplayServerWindows::winink_available) {
+ tablet_drivers.push_back("winink");
+ }
+
force_quit = false;
hInstance = _hInstance;
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 480a9faa87..11e3533bfd 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -73,6 +73,9 @@ class OS_Windows : public OS {
HINSTANCE hInstance;
MainLoop *main_loop;
+ String tablet_driver;
+ Vector<String> tablet_drivers;
+
#ifdef WASAPI_ENABLED
AudioDriverWASAPI driver_wasapi;
#endif
@@ -99,6 +102,8 @@ protected:
virtual void finalize_core();
virtual String get_stdin_string(bool p_block);
+ String _quote_command_line_argument(const String &p_text) const;
+
struct ProcessInfo {
STARTUPINFO si;
PROCESS_INFORMATION pi;
@@ -114,6 +119,11 @@ public:
virtual String get_name() const;
+ virtual int get_tablet_driver_count() const;
+ virtual String get_tablet_driver_name(int p_driver) const;
+ virtual String get_current_tablet_driver() const;
+ virtual void set_current_tablet_driver(const String &p_driver);
+
virtual void initialize_joypads() {}
virtual Date get_date(bool utc) const;