summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/io/ip.cpp8
-rw-r--r--core/os/thread.cpp6
-rw-r--r--core/os/thread.h2
-rw-r--r--doc/classes/DisplayServer.xml12
-rw-r--r--doc/classes/EditorPaths.xml4
-rw-r--r--doc/classes/Thread.xml4
-rw-r--r--editor/editor_paths.cpp14
-rw-r--r--editor/project_converter_3_to_4.cpp23
-rw-r--r--editor/project_converter_3_to_4.h2
-rw-r--r--editor/property_selector.cpp6
-rw-r--r--editor/renames_map_3_to_4.cpp2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj2
-rw-r--r--modules/mono/godotsharp_dirs.cpp22
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp24
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h1
-rw-r--r--platform/macos/display_server_macos.h1
-rw-r--r--platform/macos/display_server_macos.mm30
-rw-r--r--platform/windows/display_server_windows.cpp24
-rw-r--r--platform/windows/display_server_windows.h5
-rw-r--r--scene/gui/code_edit.cpp6
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--servers/display_server.cpp2
-rw-r--r--servers/display_server.h2
24 files changed, 176 insertions, 37 deletions
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index 28b7037120..65728f34f6 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -75,7 +75,7 @@ struct _IP_ResolverPrivate {
Semaphore sem;
Thread thread;
- bool thread_abort = false;
+ SafeFlag thread_abort;
void resolve_queues() {
for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) {
@@ -111,7 +111,7 @@ struct _IP_ResolverPrivate {
static void _thread_function(void *self) {
_IP_ResolverPrivate *ipr = static_cast<_IP_ResolverPrivate *>(self);
- while (!ipr->thread_abort) {
+ while (!ipr->thread_abort.is_set()) {
ipr->sem.wait();
ipr->resolve_queues();
}
@@ -343,12 +343,12 @@ IP::IP() {
singleton = this;
resolver = memnew(_IP_ResolverPrivate);
- resolver->thread_abort = false;
+ resolver->thread_abort.clear();
resolver->thread.start(_IP_ResolverPrivate::_thread_function, resolver);
}
IP::~IP() {
- resolver->thread_abort = true;
+ resolver->thread_abort.set();
resolver->sem.post();
resolver->thread.wait_to_finish();
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index 9d16392b2a..92865576f3 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -50,8 +50,8 @@ void Thread::_set_platform_functions(const PlatformFunctions &p_functions) {
platform_functions = p_functions;
}
-void Thread::callback(Thread *p_self, const Settings &p_settings, Callback p_callback, void *p_userdata) {
- Thread::caller_id = _thread_id_hash(p_self->thread.get_id());
+void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) {
+ Thread::caller_id = p_caller_id;
if (platform_functions.set_priority) {
platform_functions.set_priority(p_settings.priority);
}
@@ -79,7 +79,7 @@ void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_
std::thread empty_thread;
thread.swap(empty_thread);
}
- std::thread new_thread(&Thread::callback, this, p_settings, p_callback, p_user);
+ std::thread new_thread(&Thread::callback, _thread_id_hash(thread.get_id()), p_settings, p_callback, p_user);
thread.swap(new_thread);
id = _thread_id_hash(thread.get_id());
}
diff --git a/core/os/thread.h b/core/os/thread.h
index 3d4c48a760..6eb21fba65 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -82,7 +82,7 @@ private:
static thread_local ID caller_id;
std::thread thread;
- static void callback(Thread *p_self, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata);
+ static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata);
static PlatformFunctions platform_functions;
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 4c2200ae9a..8bf58d5cd3 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -865,6 +865,15 @@
[b]Note:[/b] This method is implemented on Android and iOS.
</description>
</method>
+ <method name="screen_get_pixel" qualifiers="const">
+ <return type="Color" />
+ <param index="0" name="position" type="Vector2i" />
+ <description>
+ Returns color of the display pixel at the [param position].
+ [b]Note:[/b] This method is implemented on Linux (X11), macOS, and Windows.
+ [b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color.
+ </description>
+ </method>
<method name="screen_get_position" qualifiers="const">
<return type="Vector2i" />
<param index="0" name="screen" type="int" default="-1" />
@@ -1543,6 +1552,9 @@
<constant name="FEATURE_EXTEND_TO_TITLE" value="20" enum="Feature">
Display server supports expanding window content to the title. See [constant WINDOW_FLAG_EXTEND_TO_TITLE]. [b]macOS[/b]
</constant>
+ <constant name="FEATURE_SCREEN_CAPTURE" value="21" enum="Feature">
+ Display server supports reading screen pixels. See [method screen_get_pixel].
+ </constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
diff --git a/doc/classes/EditorPaths.xml b/doc/classes/EditorPaths.xml
index eacb366d84..c4b9eda24c 100644
--- a/doc/classes/EditorPaths.xml
+++ b/doc/classes/EditorPaths.xml
@@ -64,7 +64,9 @@
<return type="bool" />
<description>
Returns [code]true[/code] if the editor is marked as self-contained, [code]false[/code] otherwise. When self-contained mode is enabled, user configuration, data and cache files are saved in an [code]editor_data/[/code] folder next to the editor binary. This makes portable usage easier and ensures the Godot editor minimizes file writes outside its own folder. Self-contained mode is not available for exported projects.
- Self-contained mode can be enabled by creating a file named [code]._sc_[/code] or [code]_sc_[/code] in the same folder as the editor binary while the editor is not running. See also [method get_self_contained_file].
+ Self-contained mode can be enabled by creating a file named [code]._sc_[/code] or [code]_sc_[/code] in the same folder as the editor binary or macOS .app bundle while the editor is not running. See also [method get_self_contained_file].
+ [b]Note:[/b] On macOS, quarantine flag should be manually removed before using self-contained mode, see [url=https://docs.godotengine.org/en/stable/tutorials/export/running_on_macos.html]Running on macOS[/url].
+ [b]Note:[/b] On macOS, placing [code]_sc_[/code] or any other file inside .app bundle will break digital signature and make it non-portable, consider placing it in the same folder as the .app bundle instead.
[b]Note:[/b] The Steam release of Godot uses self-contained mode by default.
</description>
</method>
diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml
index f1dd78d85d..2b4ef65f0c 100644
--- a/doc/classes/Thread.xml
+++ b/doc/classes/Thread.xml
@@ -16,13 +16,13 @@
<method name="get_id" qualifiers="const">
<return type="String" />
<description>
- Returns the current [Thread]'s ID, uniquely identifying it among all threads. If the [Thread] is not running this returns an empty string.
+ Returns the current [Thread]'s ID, uniquely identifying it among all threads. If the [Thread] has not started running or if [method wait_to_finish] has been called, this returns an empty string.
</description>
</method>
<method name="is_alive" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if this [Thread] is currently running. This is useful for determining if [method wait_to_finish] can be called without blocking the calling thread.
+ Returns [code]true[/code] if this [Thread] is currently running the provided function. This is useful for determining if [method wait_to_finish] can be called without blocking the calling thread.
To check if a [Thread] is joinable, use [method is_started].
</description>
</method>
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
index 389c16fd66..d5ba841801 100644
--- a/editor/editor_paths.cpp
+++ b/editor/editor_paths.cpp
@@ -117,14 +117,20 @@ EditorPaths::EditorPaths() {
// Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir.
String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
+ Ref<DirAccess> d = DirAccess::create_for_path(exe_path);
+ if (d->file_exists(exe_path + "/._sc_")) {
+ self_contained = true;
+ self_contained_file = exe_path + "/._sc_";
+ } else if (d->file_exists(exe_path + "/_sc_")) {
+ self_contained = true;
+ self_contained_file = exe_path + "/_sc_";
+ }
// On macOS, look outside .app bundle, since .app bundle is read-only.
+ // Note: This will not work if Gatekeeper path randomization is active.
if (OS::get_singleton()->has_feature("macos") && exe_path.ends_with("MacOS") && exe_path.path_join("..").simplify_path().ends_with("Contents")) {
exe_path = exe_path.path_join("../../..").simplify_path();
- }
- {
- Ref<DirAccess> d = DirAccess::create_for_path(exe_path);
-
+ d = DirAccess::create_for_path(exe_path);
if (d->file_exists(exe_path + "/._sc_")) {
self_contained = true;
self_contained_file = exe_path + "/._sc_";
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index 5a45fd9c4b..a1dfbac719 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -371,6 +371,8 @@ bool ProjectConverter3To4::convert() {
if (file_size < uint64_t(maximum_file_size)) {
// ".tscn" must work exactly the same as ".gd" files because they may contain built-in Scripts.
if (file_name.ends_with(".gd")) {
+ fix_tool_declaration(source_lines, reg_container);
+
rename_classes(source_lines, reg_container); // Using only specialized function.
rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, source_lines);
@@ -740,6 +742,10 @@ bool ProjectConverter3To4::test_conversion_basic(String name, String expected, c
bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
bool valid = true;
+ valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n tool", "\n tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\ntool", "@tool\n\n", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+
valid = valid && test_conversion_basic("TYPE_REAL", "TYPE_FLOAT", RenamesMap3To4::enum_renames, reg_container.enum_regexes, "enum");
valid = valid && test_conversion_basic("can_instance", "can_instantiate", RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, "gdscript function");
@@ -850,9 +856,6 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
valid = valid && test_conversion_with_regex("\texport_dialog", "\texport_dialog", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("export", "@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex(" export", " export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n tool", "\n tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\ntool", "\n\n@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
@@ -1423,6 +1426,17 @@ Vector<String> ProjectConverter3To4::check_for_rename_colors(Vector<String> &lin
return found_renames;
}
+void ProjectConverter3To4::fix_tool_declaration(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+ // In godot4, "tool" became "@tool" and must be located at the top of the file
+ for (int i = 0; i < source_lines.size(); ++i) {
+ if (source_lines[i].line == "tool") {
+ source_lines.remove_at(i);
+ source_lines.insert(0, { "@tool", false });
+ return; // assuming there's at most 1 tool declaration
+ }
+ }
+}
+
void ProjectConverter3To4::rename_classes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
for (SourceLine &source_line : source_lines) {
if (source_line.is_comment) {
@@ -2419,9 +2433,6 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_l
String &line = source_line.line;
if (uint64_t(line.length()) <= maximum_line_length) {
- if (line.contains("tool")) {
- line = reg_container.keyword_gdscript_tool.sub(line, "@tool", true);
- }
if (line.contains("export")) {
line = reg_container.keyword_gdscript_export_single.sub(line, "@export", true);
}
diff --git a/editor/project_converter_3_to_4.h b/editor/project_converter_3_to_4.h
index b2adfac395..134273b4be 100644
--- a/editor/project_converter_3_to_4.h
+++ b/editor/project_converter_3_to_4.h
@@ -71,6 +71,8 @@ class ProjectConverter3To4 {
uint64_t maximum_file_size;
uint64_t maximum_line_length;
+ void fix_tool_declaration(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
+
void rename_colors(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
Vector<String> check_for_rename_colors(Vector<String> &lines, const RegExContainer &reg_container);
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index b756cdb8ed..ec323b5853 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -124,7 +124,7 @@ void PropertySelector::_update_search() {
bool found = false;
- Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = {
+ Ref<Texture2D> type_icons[] = {
search_options->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("int"), SNAME("EditorIcons")),
@@ -137,11 +137,14 @@ void PropertySelector::_update_search() {
search_options->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")),
+ search_options->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")),
+ search_options->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), // Vector4i, needs icon.
search_options->get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")),
+ search_options->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), // Projection, needs icon.
search_options->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")),
@@ -161,6 +164,7 @@ void PropertySelector::_update_search() {
search_options->get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")),
search_options->get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons"))
};
+ static_assert((sizeof(type_icons) / sizeof(type_icons[0])) == Variant::VARIANT_MAX, "Number of type icons doesn't match the number of Variant types.");
for (const PropertyInfo &E : props) {
if (E.usage == PROPERTY_USAGE_CATEGORY) {
diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp
index 3c61b81e5d..eb63a244a6 100644
--- a/editor/renames_map_3_to_4.cpp
+++ b/editor/renames_map_3_to_4.cpp
@@ -387,7 +387,6 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = {
{ "get_unit_db", "get_volume_db" }, // AudioStreamPlayer3D
{ "get_unit_offset", "get_progress_ratio" }, // PathFollow2D, PathFollow3D
{ "get_use_in_baked_light", "is_baking_navigation" }, // GridMap
- { "get_used_cells_by_id", "get_used_cells" }, // TileMap
{ "get_v_scrollbar", "get_v_scroll_bar" }, // ScrollContainer
{ "get_visible_name", "_get_visible_name" }, // EditorImportPlugin
{ "get_window_layout", "_get_window_layout" }, // EditorPlugin
@@ -795,7 +794,6 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = {
{ "GetUnitDb", "GetVolumeDb" }, // AudioStreamPlayer3D
{ "GetUnitOffset", "GetProgressRatio" }, // PathFollow2D, PathFollow3D
{ "GetUseInBakedLight", "IsBakingNavigation" }, // GridMap
- { "GetUsedCellsById", "GetUsedCells" }, // TileMap
{ "GetVScrollbar", "GetVScrollBar" }, // ScrollContainer
{ "GetVisibleName", "_GetVisibleName" }, // EditorImportPlugin
{ "GetWindowLayout", "_GetWindowLayout" }, // EditorPlugin
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index eea7520b05..894053c5de 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -14,7 +14,7 @@
<GodotProjectDir Condition=" '$(GodotProjectDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
<GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
- <GodotProjectDirBase64>$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64>
+ <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64>
<!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. -->
<BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath>
@@ -30,6 +30,13 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
+ <Target Condition=" $([MSBuild]::VersionLessThan($(MSBuildAssemblyVersion), '17.3')) " Name="GodotProjectDir" BeforeTargets="Build">
+ <Error Text="Cannot build from path containing '%23', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%23'))" /> <!-- # -->
+ <Error Text="Cannot build from path containing '%3B', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%3B'))" /> <!-- ; -->
+ <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0A'))" /> <!-- \n -->
+ <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0D'))" /> <!-- \r -->
+ </Target>
+
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
index 2df838cfb8..3f569ebac3 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
@@ -7,7 +7,7 @@
<PropertyGroup>
<!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
<GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
- <GodotProjectDirBase64>$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64>
+ <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64>
<!-- For compiling GetGodotPropertyDefaultValues. -->
<DefineConstants>$(DefineConstants);TOOLS</DefineConstants>
</PropertyGroup>
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index d08dedcfcb..bff7d04b55 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -66,23 +66,25 @@ String _get_mono_user_dir() {
if (EditorPaths::get_singleton()) {
return EditorPaths::get_singleton()->get_data_dir().path_join("mono");
} else {
- String settings_path;
+ String settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name());
// Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir.
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
-
- // On macOS, look outside .app bundle, since .app bundle is read-only.
- if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) {
- exe_dir = exe_dir.path_join("../../..").simplify_path();
- }
-
Ref<DirAccess> d = DirAccess::create_for_path(exe_dir);
-
if (d->file_exists("._sc_") || d->file_exists("_sc_")) {
// contain yourself
settings_path = exe_dir.path_join("editor_data");
- } else {
- settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name());
+ }
+
+ // On macOS, look outside .app bundle, since .app bundle is read-only.
+ // Note: This will not work if Gatekeeper path randomization is active.
+ if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) {
+ exe_dir = exe_dir.path_join("../../..").simplify_path();
+ d = DirAccess::create_for_path(exe_dir);
+ if (d->file_exists("._sc_") || d->file_exists("_sc_")) {
+ // contain yourself
+ settings_path = exe_dir.path_join("editor_data");
+ }
}
return settings_path.path_join("mono");
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index d1f1115aad..70e5ac8561 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -128,6 +128,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
#endif
case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_TEXT_TO_SPEECH:
+ case FEATURE_SCREEN_CAPTURE:
return true;
default: {
}
@@ -1169,6 +1170,29 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const {
return 96;
}
+Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {
+ Point2i pos = p_position;
+
+ int number_of_screens = XScreenCount(x11_display);
+ for (int i = 0; i < number_of_screens; i++) {
+ Window root = XRootWindow(x11_display, i);
+ XWindowAttributes root_attrs;
+ XGetWindowAttributes(x11_display, root, &root_attrs);
+ if ((pos.x >= root_attrs.x) && (pos.x <= root_attrs.x + root_attrs.width) && (pos.y >= root_attrs.y) && (pos.y <= root_attrs.y + root_attrs.height)) {
+ XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);
+ if (image) {
+ XColor c;
+ c.pixel = XGetPixel(image, 0, 0);
+ XFree(image);
+ XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);
+ return Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);
+ }
+ }
+ }
+
+ return Color();
+}
+
float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index dbe8a0ce2b..c98409253e 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -409,6 +409,7 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Color screen_get_pixel(const Point2i &p_position) const override;
#if defined(DBUS_ENABLED)
virtual void screen_set_keep_on(bool p_enable) override;
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index fb9bcdfe56..f7c5b0b847 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -334,6 +334,7 @@ public:
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 Color screen_get_pixel(const Point2i &p_position) const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index e8eb5b419b..eba69f8954 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -765,6 +765,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_SWAP_BUFFERS:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
+ case FEATURE_SCREEN_CAPTURE:
return true;
default: {
}
@@ -2249,6 +2250,35 @@ Rect2i DisplayServerMacOS::screen_get_usable_rect(int p_screen) const {
return Rect2i();
}
+Color DisplayServerMacOS::screen_get_pixel(const Point2i &p_position) const {
+ Point2i position = p_position;
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot passes a positive value.
+ position.y *= -1;
+ position += _get_screens_origin();
+ position /= screen_get_max_scale();
+
+ for (NSScreen *screen in [NSScreen screens]) {
+ NSRect frame = [screen frame];
+ if (NSMouseInRect(NSMakePoint(position.x, position.y), frame, NO)) {
+ NSDictionary *screenDescription = [screen deviceDescription];
+ CGDirectDisplayID display_id = [[screenDescription objectForKey:@"NSScreenNumber"] unsignedIntValue];
+ CGImageRef image = CGDisplayCreateImageForRect(display_id, CGRectMake(position.x - frame.origin.x, frame.size.height - (position.y - frame.origin.y), 1, 1));
+ if (image) {
+ NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCGImage:image];
+ CGImageRelease(image);
+ NSColor *color = [bitmap colorAtX:0 y:0];
+ if (color) {
+ CGFloat components[4];
+ [color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
+ return Color(components[0], components[1], components[2], components[3]);
+ }
+ }
+ }
+ }
+ return Color();
+}
+
float DisplayServerMacOS::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 360e446de7..ad96a82df7 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -90,6 +90,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
+ case FEATURE_SCREEN_CAPTURE:
return true;
default:
return false;
@@ -631,6 +632,26 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const {
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}
+
+Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const {
+ Point2i pos = p_position + _get_screens_origin();
+
+ POINT p;
+ p.x = pos.x;
+ p.y = pos.y;
+ if (win81p_LogicalToPhysicalPointForPerMonitorDPI) {
+ win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p);
+ }
+ HDC dc = GetDC(0);
+ COLORREF col = GetPixel(dc, p.x, p.y);
+ if (col != CLR_INVALID) {
+ return Color(float(col & 0x000000FF) / 256.0, float((col & 0x0000FF00) >> 8) / 256.0, float((col & 0x00FF0000) >> 16) / 256.0, 1.0);
+ }
+ ReleaseDC(NULL, dc);
+
+ return Color();
+}
+
float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -4023,6 +4044,7 @@ GetImmersiveUserColorSetPreferencePtr DisplayServerWindows::GetImmersiveUserColo
bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
+LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;
typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
@@ -4151,10 +4173,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
// Note: Windows Ink API for pen input, available on Windows 8+ only.
+ // Note: DPI conversion API, available on Windows 8.1+ 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");
+ win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI");
winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo;
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 0d2137d048..1b36b0951e 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -259,6 +259,7 @@ typedef struct tagPOINTER_PEN_INFO {
typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
+typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
typedef struct {
BYTE bWidth; // Width, in pixels, of the image
@@ -305,6 +306,9 @@ class DisplayServerWindows : public DisplayServer {
static GetPointerTypePtr win8p_GetPointerType;
static GetPointerPenInfoPtr win8p_GetPointerPenInfo;
+ // DPI conversion API
+ static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI;
+
void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
String tablet_driver;
Vector<String> tablet_drivers;
@@ -524,6 +528,7 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Color screen_get_pixel(const Point2i &p_position) const override;
virtual void screen_set_keep_on(bool p_enable) override; //disable screensaver
virtual bool screen_is_kept_on() const override;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index ee39327d4d..91c4fa3761 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -2926,6 +2926,12 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
code_completion_options.clear();
code_completion_base = string_to_complete;
+ /* Don't autocomplete setting numerical values. */
+ if (code_completion_base.is_numeric()) {
+ cancel_code_completion();
+ return;
+ }
+
Vector<ScriptLanguage::CodeCompletionOption> completion_options_casei;
Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr;
Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr_casei;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 0c073e67c4..f8a1345576 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1263,7 +1263,7 @@ void TextEdit::_notification(int p_what) {
if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
- } else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) {
+ } else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL)) {
int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2;
space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 2d65cea432..1e60094106 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -658,6 +658,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale);
ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+ ClassDB::bind_method(D_METHOD("screen_get_pixel", "position"), &DisplayServer::screen_get_pixel);
ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_orientation", "screen"), &DisplayServer::screen_get_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
@@ -785,6 +786,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_CLIPBOARD_PRIMARY);
BIND_ENUM_CONSTANT(FEATURE_TEXT_TO_SPEECH);
BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE);
+ BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
diff --git a/servers/display_server.h b/servers/display_server.h
index aa30b25b65..fa37a694e6 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -124,6 +124,7 @@ public:
FEATURE_CLIPBOARD_PRIMARY,
FEATURE_TEXT_TO_SPEECH,
FEATURE_EXTEND_TO_TITLE,
+ FEATURE_SCREEN_CAPTURE,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -275,6 +276,7 @@ public:
return scale;
}
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
+ virtual Color screen_get_pixel(const Point2i &p_position) const { return Color(); };
virtual bool is_touchscreen_available() const;
// Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation`