diff options
32 files changed, 872 insertions, 28 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 0d5c1906b5..74935bb618 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -32,7 +32,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ github.event_name }}" == "pull_request" ]; then - files=$(gh pr diff ${{ github.event.pull_request.number }} --name-only) + files=$(git diff-tree --no-commit-id --name-only -r HEAD~${{ github.event.pull_request.commits }}..HEAD 2> /dev/null || true) elif [ "${{ github.event_name }}" == "push" -a "${{ github.event.forced }}" == "false" -a "${{ github.event.created }}" == "false" ]; then files=$(git diff-tree --no-commit-id --name-only -r ${{ github.event.before }}..${{ github.event.after }} 2> /dev/null || true) fi diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 6927db002b..8c85030783 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -283,8 +283,8 @@ String OS::read_string_from_stdin() { int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr, bool p_open_console) { List<String> args; - for (int i = 0; i < p_arguments.size(); i++) { - args.push_back(p_arguments[i]); + for (const String &arg : p_arguments) { + args.push_back(arg); } String pipe; int exitcode = 0; @@ -296,10 +296,18 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r return exitcode; } +Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) { + List<String> args; + for (const String &arg : p_arguments) { + args.push_back(arg); + } + return ::OS::get_singleton()->execute_with_pipe(p_path, args); +} + int OS::create_instance(const Vector<String> &p_arguments) { List<String> args; - for (int i = 0; i < p_arguments.size(); i++) { - args.push_back(p_arguments[i]); + for (const String &arg : p_arguments) { + args.push_back(arg); } ::OS::ProcessID pid = 0; Error err = ::OS::get_singleton()->create_instance(args, &pid); @@ -311,8 +319,8 @@ int OS::create_instance(const Vector<String> &p_arguments) { int OS::create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console) { List<String> args; - for (int i = 0; i < p_arguments.size(); i++) { - args.push_back(p_arguments[i]); + for (const String &arg : p_arguments) { + args.push_back(arg); } ::OS::ProcessID pid = 0; Error err = ::OS::get_singleton()->create_process(p_path, args, &pid, p_open_console); @@ -587,6 +595,7 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin); ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance); ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill); diff --git a/core/core_bind.h b/core/core_bind.h index 305f616cef..d46321cf5c 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -156,6 +156,7 @@ public: String get_executable_path() const; String read_string_from_stdin(); int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false); + Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments); int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false); int create_instance(const Vector<String> &p_arguments); Error kill(int p_pid); diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 55286277fa..d2a5103953 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -47,6 +47,7 @@ thread_local Error FileAccess::last_file_open_error = OK; Ref<FileAccess> FileAccess::create(AccessType p_access) { ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr); + ERR_FAIL_NULL_V(create_func[p_access], nullptr); Ref<FileAccess> ret = create_func[p_access](); ret->_set_access_type(p_access); @@ -75,7 +76,8 @@ Ref<FileAccess> FileAccess::create_for_path(const String &p_path) { ret = create(ACCESS_RESOURCES); } else if (p_path.begins_with("user://")) { ret = create(ACCESS_USERDATA); - + } else if (p_path.begins_with("pipe://")) { + ret = create(ACCESS_PIPE); } else { ret = create(ACCESS_FILESYSTEM); } @@ -209,6 +211,9 @@ String FileAccess::fix_path(const String &p_path) const { } } break; + case ACCESS_PIPE: { + return r_path; + } break; case ACCESS_FILESYSTEM: { return r_path; } break; diff --git a/core/io/file_access.h b/core/io/file_access.h index 122ae3b190..0b631f68b1 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -50,6 +50,7 @@ public: ACCESS_RESOURCES, ACCESS_USERDATA, ACCESS_FILESYSTEM, + ACCESS_PIPE, ACCESS_MAX }; diff --git a/core/os/os.h b/core/os/os.h index 4f0df1543f..370e724c53 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -170,6 +170,7 @@ public: virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }; virtual String get_executable_path() const; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); } virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0; virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); }; virtual Error kill(const ProcessID &p_pid) = 0; diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index ae0dc53f5d..9eb5f9e334 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -103,7 +103,7 @@ <param index="3" name="callback" type="Callable" /> <description> Shows a text input dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [String] parameter which contains the text field's contents. - [b]Note:[/b] This method is implemented only on macOS and Windows. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_INPUT] feature. Supported platforms include macOS and Windows. </description> </method> <method name="dialog_show"> @@ -114,7 +114,7 @@ <param index="3" name="callback" type="Callable" /> <description> Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button. - [b]Note:[/b] This method is implemented only on macOS and Windows. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS and Windows. </description> </method> <method name="enable_for_stealing_focus"> @@ -138,7 +138,7 @@ Displays OS native dialog for selecting files or directories in the file system. Each filter string in the [param filters] array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. See also [member FileDialog.filters]. Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. [b]Note:[/b] On Linux, [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. @@ -164,7 +164,7 @@ - [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used. - [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]). Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code]. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. [b]Note:[/b] On Linux (X11), [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. @@ -1784,7 +1784,7 @@ Display server supports setting the mouse cursor shape to a custom image. [b]Windows, macOS, Linux (X11/Wayland), Web[/b] </constant> <constant name="FEATURE_NATIVE_DIALOG" value="9" enum="Feature"> - Display server supports spawning dialogs using the operating system's native look-and-feel. [b]Windows, macOS, Linux (X11/Wayland)[/b] + Display server supports spawning text dialogs using the operating system's native look-and-feel. See [method dialog_show]. [b]Windows, macOS[/b] </constant> <constant name="FEATURE_IME" value="10" enum="Feature"> Display server supports [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url], which is commonly used for inputting Chinese/Japanese/Korean text. This is handled by the operating system, rather than by Godot. [b]Windows, macOS, Linux (X11)[/b] @@ -1825,6 +1825,12 @@ <constant name="FEATURE_NATIVE_HELP" value="23" enum="Feature"> Display server supports native help system search callbacks. See [method help_set_search_callbacks]. </constant> + <constant name="FEATURE_NATIVE_DIALOG_INPUT" value="24" enum="Feature"> + Display server supports spawning text input dialogs using the operating system's native look-and-feel. See [method dialog_input_text]. [b]Windows, macOS[/b] + </constant> + <constant name="FEATURE_NATIVE_DIALOG_FILE" value="25" enum="Feature"> + Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b] + </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/OS.xml b/doc/classes/OS.xml index be7394a30b..bd1bd9afa7 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -50,9 +50,9 @@ <param index="1" name="arguments" type="PackedStringArray" /> <param index="2" name="open_console" type="bool" default="false" /> <description> - Creates a new process that runs independently of Godot. It will not terminate when Godot terminates. The path specified in [param path] must exist and be executable file or macOS .app bundle. Platform path resolution will be used. The [param arguments] are used in the given order and separated by a space. + Creates a new process that runs independently of Godot. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space. On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened. - If the process is successfully created, this method returns its process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). Otherwise this method returns [code]-1[/code]. + If the process is successfully created, this method returns its process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). Otherwise, this method returns [code]-1[/code]. For example, running another instance of the project: [codeblocks] [gdscript] @@ -63,7 +63,7 @@ [/csharp] [/codeblocks] See [method execute] if you wish to run an external command and retrieve the results. - [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + [b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows. [b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments. </description> </method> @@ -120,7 +120,7 @@ OS.Execute("CMD.exe", new string[] {"/C", "cd %TEMP% && dir"}, output); [/csharp] [/codeblocks] - [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + [b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows. [b]Note:[/b] To execute a Windows command interpreter built-in command, specify [code]cmd.exe[/code] in [param path], [code]/c[/code] as the first argument, and the desired command as the second argument. [b]Note:[/b] To execute a PowerShell built-in command, specify [code]powershell.exe[/code] in [param path], [code]-Command[/code] as the first argument, and the desired command as the second argument. [b]Note:[/b] To execute a Unix shell built-in command, specify shell executable name in [param path], [code]-c[/code] as the first argument, and the desired command as the second argument. @@ -128,6 +128,23 @@ [b]Note:[/b] On Android, system commands such as [code]dumpsys[/code] can only be run on a rooted device. </description> </method> + <method name="execute_with_pipe"> + <return type="Dictionary" /> + <param index="0" name="path" type="String" /> + <param index="1" name="arguments" type="PackedStringArray" /> + <description> + Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space. + If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys: + - [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write). + - [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only). + - [code]"pid"[/code] - Process ID as an [int], which you can use to monitor the process (and potentially terminate it with [method kill]). + [b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows. + [b]Note:[/b] To execute a Windows command interpreter built-in command, specify [code]cmd.exe[/code] in [param path], [code]/c[/code] as the first argument, and the desired command as the second argument. + [b]Note:[/b] To execute a PowerShell built-in command, specify [code]powershell.exe[/code] in [param path], [code]-Command[/code] as the first argument, and the desired command as the second argument. + [b]Note:[/b] To execute a Unix shell built-in command, specify shell executable name in [param path], [code]-c[/code] as the first argument, and the desired command as the second argument. + [b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments. + </description> + </method> <method name="find_keycode_from_string" qualifiers="const"> <return type="int" enum="Key" /> <param index="0" name="string" type="String" /> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 4735091f20..e40d73862b 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -740,7 +740,7 @@ <param index="1" name="axis" type="int" enum="Vector3.Axis" /> <param index="2" name="flag" type="int" enum="PhysicsServer3D.G6DOFJointAxisFlag" /> <description> - Gets a generic_6_DOF_joint flag (see [enum G6DOFJointAxisFlag] constants). + Returns the value of a generic 6DOF joint flag. See [enum G6DOFJointAxisFlag] for the list of available flags. </description> </method> <method name="generic_6dof_joint_get_param" qualifiers="const"> @@ -749,7 +749,7 @@ <param index="1" name="axis" type="int" enum="Vector3.Axis" /> <param index="2" name="param" type="int" enum="PhysicsServer3D.G6DOFJointAxisParam" /> <description> - Gets a generic_6_DOF_joint parameter (see [enum G6DOFJointAxisParam] constants). + Returns the value of a generic 6DOF joint parameter. See [enum G6DOFJointAxisParam] for the list of available parameters. </description> </method> <method name="generic_6dof_joint_set_flag"> @@ -759,7 +759,7 @@ <param index="2" name="flag" type="int" enum="PhysicsServer3D.G6DOFJointAxisFlag" /> <param index="3" name="enable" type="bool" /> <description> - Sets a generic_6_DOF_joint flag (see [enum G6DOFJointAxisFlag] constants). + Sets the value of a given generic 6DOF joint flag. See [enum G6DOFJointAxisFlag] for the list of available flags. </description> </method> <method name="generic_6dof_joint_set_param"> @@ -769,7 +769,7 @@ <param index="2" name="param" type="int" enum="PhysicsServer3D.G6DOFJointAxisParam" /> <param index="3" name="value" type="float" /> <description> - Sets a generic_6_DOF_joint parameter (see [enum G6DOFJointAxisParam] constants). + Sets the value of a given generic 6DOF joint parameter. See [enum G6DOFJointAxisParam] for the list of available parameters. </description> </method> <method name="get_process_info"> @@ -876,6 +876,7 @@ <param index="3" name="body_B" type="RID" /> <param index="4" name="local_ref_B" type="Transform3D" /> <description> + Make the joint a generic six degrees of freedom (6DOF) joint. Use [method generic_6dof_joint_set_flag] and [method generic_6dof_joint_set_param] to set the joint's flags and parameters respectively. </description> </method> <method name="joint_make_hinge"> @@ -1497,6 +1498,12 @@ <constant name="G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT" value="6" enum="G6DOFJointAxisParam"> The maximum force that the linear motor can apply while trying to reach the target velocity. </constant> + <constant name="G6DOF_JOINT_LINEAR_SPRING_STIFFNESS" value="7" enum="G6DOFJointAxisParam"> + </constant> + <constant name="G6DOF_JOINT_LINEAR_SPRING_DAMPING" value="8" enum="G6DOFJointAxisParam"> + </constant> + <constant name="G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT" value="9" enum="G6DOFJointAxisParam"> + </constant> <constant name="G6DOF_JOINT_ANGULAR_LOWER_LIMIT" value="10" enum="G6DOFJointAxisParam"> The minimum rotation in negative direction to break loose and rotate around the axes. </constant> @@ -1524,18 +1531,34 @@ <constant name="G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT" value="18" enum="G6DOFJointAxisParam"> Maximum acceleration for the motor at the axes. </constant> + <constant name="G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS" value="19" enum="G6DOFJointAxisParam"> + </constant> + <constant name="G6DOF_JOINT_ANGULAR_SPRING_DAMPING" value="20" enum="G6DOFJointAxisParam"> + </constant> + <constant name="G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT" value="21" enum="G6DOFJointAxisParam"> + </constant> + <constant name="G6DOF_JOINT_MAX" value="22" enum="G6DOFJointAxisParam"> + Represents the size of the [enum G6DOFJointAxisParam] enum. + </constant> <constant name="G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT" value="0" enum="G6DOFJointAxisFlag"> If set, linear motion is possible within the given limits. </constant> <constant name="G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT" value="1" enum="G6DOFJointAxisFlag"> If set, rotational motion is possible. </constant> + <constant name="G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING" value="2" enum="G6DOFJointAxisFlag"> + </constant> + <constant name="G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING" value="3" enum="G6DOFJointAxisFlag"> + </constant> <constant name="G6DOF_JOINT_FLAG_ENABLE_MOTOR" value="4" enum="G6DOFJointAxisFlag"> If set, there is a rotational motor across these axes. </constant> <constant name="G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR" value="5" enum="G6DOFJointAxisFlag"> If set, there is a linear motor on this axis that targets a specific velocity. </constant> + <constant name="G6DOF_JOINT_FLAG_MAX" value="6" enum="G6DOFJointAxisFlag"> + Represents the size of the [enum G6DOFJointAxisFlag] enum. + </constant> <constant name="SHAPE_WORLD_BOUNDARY" value="0" enum="ShapeType"> The [Shape3D] is a [WorldBoundaryShape3D]. </constant> diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index c5b86f079e..91df90580c 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -93,7 +93,7 @@ <return type="int" /> <param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <description> - Returns the tile's terrain bit for the given [param peering_bit] direction. + Returns the tile's terrain bit for the given [param peering_bit] direction. To check that a direction is valid, use [method is_valid_terrain_peering_bit]. </description> </method> <method name="is_collision_polygon_one_way" qualifiers="const"> @@ -104,6 +104,13 @@ Returns whether one-way collisions are enabled for the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id]. </description> </method> + <method name="is_valid_terrain_peering_bit" qualifiers="const"> + <return type="bool" /> + <param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> + <description> + Returns whether the given [param peering_bit] direction is valid for this tile. + </description> + </method> <method name="remove_collision_polygon"> <return type="void" /> <param index="0" name="layer_id" type="int" /> @@ -200,7 +207,7 @@ <param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <param index="1" name="terrain" type="int" /> <description> - Sets the tile's terrain bit for the given [param peering_bit] direction. + Sets the tile's terrain bit for the given [param peering_bit] direction. To check that a direction is valid, use [method is_valid_terrain_peering_bit]. </description> </method> </methods> diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp new file mode 100644 index 0000000000..5d9a27ad05 --- /dev/null +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -0,0 +1,185 @@ +/**************************************************************************/ +/* file_access_unix_pipe.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "file_access_unix_pipe.h" + +#if defined(UNIX_ENABLED) + +#include "core/os/os.h" +#include "core/string/print_string.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) { + // Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe. + _close(); + + path_src = String(); + unlink_on_close = false; + ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); + fd[0] = p_rfd; + fd[1] = p_wfd; + + last_error = OK; + return OK; +} + +Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) { + _close(); + + path_src = p_path; + ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); + + path = String("/tmp/") + p_path.replace("pipe://", "").replace("/", "_"); + struct stat st = {}; + int err = stat(path.utf8().get_data(), &st); + if (err) { + if (mkfifo(path.utf8().get_data(), 0666) != 0) { + last_error = ERR_FILE_CANT_OPEN; + return last_error; + } + unlink_on_close = true; + } else { + ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file."); + } + + int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC); + if (f < 0) { + switch (errno) { + case ENOENT: { + last_error = ERR_FILE_NOT_FOUND; + } break; + default: { + last_error = ERR_FILE_CANT_OPEN; + } break; + } + return last_error; + } + + // Set close on exec to avoid leaking it to subprocesses. + fd[0] = f; + fd[1] = f; + + last_error = OK; + return OK; +} + +void FileAccessUnixPipe::_close() { + if (fd[0] < 0) { + return; + } + + if (fd[1] != fd[0]) { + ::close(fd[1]); + } + ::close(fd[0]); + fd[0] = -1; + fd[1] = -1; + + if (unlink_on_close) { + ::unlink(path.utf8().ptr()); + } + unlink_on_close = false; +} + +bool FileAccessUnixPipe::is_open() const { + return (fd[0] >= 0 || fd[1] >= 0); +} + +String FileAccessUnixPipe::get_path() const { + return path_src; +} + +String FileAccessUnixPipe::get_path_absolute() const { + return path_src; +} + +uint8_t FileAccessUnixPipe::get_8() const { + ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use."); + + uint8_t b; + if (::read(fd[0], &b, 1) == 0) { + last_error = ERR_FILE_CANT_READ; + b = '\0'; + } else { + last_error = OK; + } + return b; +} + +uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use."); + + uint64_t read = ::read(fd[0], p_dst, p_length); + if (read == p_length) { + last_error = ERR_FILE_CANT_READ; + } else { + last_error = OK; + } + return read; +} + +Error FileAccessUnixPipe::get_error() const { + return last_error; +} + +void FileAccessUnixPipe::store_8(uint8_t p_src) { + ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use."); + if (::write(fd[1], &p_src, 1) != 1) { + last_error = ERR_FILE_CANT_WRITE; + } else { + last_error = OK; + } +} + +void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use."); + ERR_FAIL_COND(!p_src && p_length > 0); + if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) { + last_error = ERR_FILE_CANT_WRITE; + } else { + last_error = OK; + } +} + +void FileAccessUnixPipe::close() { + _close(); +} + +FileAccessUnixPipe::~FileAccessUnixPipe() { + _close(); +} + +#endif // UNIX_ENABLED diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h new file mode 100644 index 0000000000..d14f897d8f --- /dev/null +++ b/drivers/unix/file_access_unix_pipe.h @@ -0,0 +1,96 @@ +/**************************************************************************/ +/* file_access_unix_pipe.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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 FILE_ACCESS_UNIX_PIPE_H +#define FILE_ACCESS_UNIX_PIPE_H + +#include "core/io/file_access.h" +#include "core/os/memory.h" + +#include <stdio.h> + +#if defined(UNIX_ENABLED) + +class FileAccessUnixPipe : public FileAccess { + bool unlink_on_close = false; + + int fd[2] = { -1, -1 }; + + mutable Error last_error = OK; + String path; + String path_src; + + void _close(); + +public: + Error open_existing(int p_rfd, int p_wfd); + virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file + + virtual bool is_open() const override; ///< true when file is open + + virtual String get_path() const override; /// returns the path for the current open file + virtual String get_path_absolute() const override; /// returns the absolute path for the current open file + + virtual void seek(uint64_t p_position) override {} + virtual void seek_end(int64_t p_position = 0) override {} + virtual uint64_t get_position() const override { return 0; } + virtual uint64_t get_length() const override { return 0; } + + virtual bool eof_reached() const override { return false; } + + virtual uint8_t get_8() const override; ///< get a byte + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; + + virtual Error get_error() const override; ///< get last error + + virtual void flush() override {} + virtual void store_8(uint8_t p_src) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes + + virtual bool file_exists(const String &p_path) override { return false; } + + virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return ERR_UNAVAILABLE; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } + + virtual void close() override; + + FileAccessUnixPipe() {} + virtual ~FileAccessUnixPipe(); +}; + +#endif // UNIX_ENABLED + +#endif // FILE_ACCESS_UNIX_PIPE_H diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 74b703b09e..597e41fd22 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -37,6 +37,7 @@ #include "core/debugger/script_debugger.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" +#include "drivers/unix/file_access_unix_pipe.h" #include "drivers/unix/net_socket_posix.h" #include "drivers/unix/thread_posix.h" #include "servers/rendering_server.h" @@ -160,6 +161,7 @@ void OS_Unix::initialize_core() { FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM); + FileAccess::make_default<FileAccessUnixPipe>(FileAccess::ACCESS_PIPE); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM); @@ -489,6 +491,106 @@ Dictionary OS_Unix::get_memory_info() const { return meminfo; } +Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +#define CLEAN_PIPES \ + if (pipe_in[0] >= 0) { \ + ::close(pipe_in[0]); \ + } \ + if (pipe_in[1] >= 0) { \ + ::close(pipe_in[1]); \ + } \ + if (pipe_out[0] >= 0) { \ + ::close(pipe_out[0]); \ + } \ + if (pipe_out[1] >= 0) { \ + ::close(pipe_out[1]); \ + } \ + if (pipe_err[0] >= 0) { \ + ::close(pipe_err[0]); \ + } \ + if (pipe_err[1] >= 0) { \ + ::close(pipe_err[1]); \ + } + + Dictionary ret; +#ifdef __EMSCRIPTEN__ + // Don't compile this code at all to avoid undefined references. + // Actual virtual call goes to OS_Web. + ERR_FAIL_V(ret); +#else + // Create pipes. + int pipe_in[2] = { -1, -1 }; + int pipe_out[2] = { -1, -1 }; + int pipe_err[2] = { -1, -1 }; + + ERR_FAIL_COND_V(pipe(pipe_in) != 0, ret); + if (pipe(pipe_out) != 0) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (pipe(pipe_err) != 0) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + + // Create process. + pid_t pid = fork(); + if (pid < 0) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + + if (pid == 0) { + // The child process. + Vector<CharString> cs; + cs.push_back(p_path.utf8()); + for (int i = 0; i < p_arguments.size(); i++) { + cs.push_back(p_arguments[i].utf8()); + } + + Vector<char *> args; + for (int i = 0; i < cs.size(); i++) { + args.push_back((char *)cs[i].get_data()); + } + args.push_back(0); + + ::close(STDIN_FILENO); + ::dup2(pipe_in[0], STDIN_FILENO); + + ::close(STDOUT_FILENO); + ::dup2(pipe_out[1], STDOUT_FILENO); + + ::close(STDERR_FILENO); + ::dup2(pipe_err[1], STDERR_FILENO); + + CLEAN_PIPES + + execvp(p_path.utf8().get_data(), &args[0]); + // The execvp() function only returns if an error occurs. + ERR_PRINT("Could not create child process: " + p_path); + raise(SIGKILL); + } + ::close(pipe_in[0]); + ::close(pipe_out[1]); + ::close(pipe_err[1]); + + Ref<FileAccessUnixPipe> main_pipe; + main_pipe.instantiate(); + main_pipe->open_existing(pipe_out[0], pipe_in[1]); + + Ref<FileAccessUnixPipe> err_pipe; + err_pipe.instantiate(); + err_pipe->open_existing(pipe_err[0], 0); + + ret["stdio"] = main_pipe; + ret["stderr"] = err_pipe; + ret["pid"] = pid; + +#undef CLEAN_PIPES + return ret; +#endif +} + Error OS_Unix::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { #ifdef __EMSCRIPTEN__ // Don't compile this code at all to avoid undefined references. diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index d3393c98ec..abbaf9b8cd 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -76,6 +76,7 @@ public: virtual Dictionary get_memory_info() const override; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp new file mode 100644 index 0000000000..7902c8e1d8 --- /dev/null +++ b/drivers/windows/file_access_windows_pipe.cpp @@ -0,0 +1,159 @@ +/**************************************************************************/ +/* file_access_windows_pipe.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifdef WINDOWS_ENABLED + +#include "file_access_windows_pipe.h" + +#include "core/os/os.h" +#include "core/string/print_string.h" + +Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) { + // Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe. + _close(); + + path_src = String(); + ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); + fd[0] = p_rfd; + fd[1] = p_wfd; + + last_error = OK; + return OK; +} + +Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flags) { + _close(); + + path_src = p_path; + ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); + + path = String("\\\\.\\pipe\\LOCAL\\") + p_path.replace("pipe://", "").replace("/", "_"); + + HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr); + if (h == INVALID_HANDLE_VALUE) { + last_error = ERR_FILE_CANT_OPEN; + return last_error; + } + ConnectNamedPipe(h, NULL); + } + fd[0] = h; + fd[1] = h; + + last_error = OK; + return OK; +} + +void FileAccessWindowsPipe::_close() { + if (fd[0] == 0) { + return; + } + if (fd[1] != fd[0]) { + CloseHandle(fd[1]); + } + CloseHandle(fd[0]); + fd[0] = 0; + fd[1] = 0; +} + +bool FileAccessWindowsPipe::is_open() const { + return (fd[0] != 0 || fd[1] != 0); +} + +String FileAccessWindowsPipe::get_path() const { + return path_src; +} + +String FileAccessWindowsPipe::get_path_absolute() const { + return path_src; +} + +uint8_t FileAccessWindowsPipe::get_8() const { + ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use."); + + uint8_t b; + if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) { + last_error = ERR_FILE_CANT_READ; + b = '\0'; + } else { + last_error = OK; + } + return b; +} + +uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use."); + + DWORD read = -1; + if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) { + last_error = ERR_FILE_CANT_READ; + } else { + last_error = OK; + } + return read; +} + +Error FileAccessWindowsPipe::get_error() const { + return last_error; +} + +void FileAccessWindowsPipe::store_8(uint8_t p_src) { + ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use."); + if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) { + last_error = ERR_FILE_CANT_WRITE; + } else { + last_error = OK; + } +} + +void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use."); + ERR_FAIL_COND(!p_src && p_length > 0); + + DWORD read = -1; + bool ok = WriteFile(fd[1], p_src, p_length, &read, nullptr); + if (!ok || read != p_length) { + last_error = ERR_FILE_CANT_WRITE; + } else { + last_error = OK; + } +} + +void FileAccessWindowsPipe::close() { + _close(); +} + +FileAccessWindowsPipe::~FileAccessWindowsPipe() { + _close(); +} + +#endif // WINDOWS_ENABLED diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h new file mode 100644 index 0000000000..e6abe61fa3 --- /dev/null +++ b/drivers/windows/file_access_windows_pipe.h @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* file_access_windows_pipe.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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 FILE_ACCESS_WINDOWS_PIPE_H +#define FILE_ACCESS_WINDOWS_PIPE_H + +#ifdef WINDOWS_ENABLED + +#include "core/io/file_access.h" +#include "core/os/memory.h" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +class FileAccessWindowsPipe : public FileAccess { + HANDLE fd[2] = { 0, 0 }; + + mutable Error last_error = OK; + + String path; + String path_src; + + void _close(); + +public: + Error open_existing(HANDLE p_rfd, HANDLE p_wfd); + + virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file + virtual bool is_open() const override; ///< true when file is open + + virtual String get_path() const override; /// returns the path for the current open file + virtual String get_path_absolute() const override; /// returns the absolute path for the current open file + + virtual void seek(uint64_t p_position) override {} + virtual void seek_end(int64_t p_position = 0) override {} + virtual uint64_t get_position() const override { return 0; } + virtual uint64_t get_length() const override { return 0; } + + virtual bool eof_reached() const override { return false; } + + virtual uint8_t get_8() const override; ///< get a byte + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; + + virtual Error get_error() const override; ///< get last error + + virtual void flush() override {} + virtual void store_8(uint8_t p_src) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes + + virtual bool file_exists(const String &p_name) override { return false; } + + uint64_t _get_modified_time(const String &p_file) override { return 0; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return ERR_UNAVAILABLE; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } + + virtual void close() override; + + FileAccessWindowsPipe() {} + virtual ~FileAccessWindowsPipe(); +}; + +#endif // WINDOWS_ENABLED + +#endif // FILE_ACCESS_WINDOWS_PIPE_H diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 90759810b1..c6f2f82117 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -71,6 +71,8 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { case FEATURE_MOUSE: //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: + //case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_ICON: //case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index e1c3dcd372..f84fb01ad0 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -328,6 +328,8 @@ bool DisplayServerIOS::has_feature(Feature p_feature) const { // case FEATURE_MOUSE: // case FEATURE_MOUSE_WARP: // case FEATURE_NATIVE_DIALOG: + // case FEATURE_NATIVE_DIALOG_INPUT: + // case FEATURE_NATIVE_DIALOG_FILE: // case FEATURE_NATIVE_ICON: // case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 80b6029c9d..d00d5deb2c 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -210,8 +210,10 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { return true; } break; + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: #ifdef DBUS_ENABLED - case FEATURE_NATIVE_DIALOG: { + case FEATURE_NATIVE_DIALOG_FILE: { return true; } break; #endif diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 226e123648..9069dd423f 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -128,8 +128,10 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { //case FEATURE_HIDPI: case FEATURE_ICON: #ifdef DBUS_ENABLED - case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_FILE: #endif + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: //case FEATURE_NATIVE_ICON: case FEATURE_SWAP_BUFFERS: #ifdef DBUS_ENABLED @@ -5210,7 +5212,7 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { if (g_set_icon_error) { g_set_icon_error = false; - WARN_PRINT("Icon too large, attempting to resize icon."); + WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h)); int new_width, new_height; if (w > h) { diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 99e9d20f2b..3d26054219 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -755,6 +755,8 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const { case FEATURE_CURSOR_SHAPE: case FEATURE_CUSTOM_CURSOR_SHAPE: case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_INPUT: + case FEATURE_NATIVE_DIALOG_FILE: case FEATURE_IME: case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_HIDPI: diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index 281f312000..06f5eb82f7 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -1128,6 +1128,8 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const { return true; //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: + //case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_ICON: //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index 45671ca491..4b93ce9e75 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -105,6 +105,10 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str return create_process(p_path, p_arguments); } +Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { + ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform."); +} + Error OS_Web::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { Array args; for (const String &E : p_arguments) { diff --git a/platform/web/os_web.h b/platform/web/os_web.h index e578c93925..16f8ea8550 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -80,6 +80,7 @@ public: bool main_loop_iterate(); Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; Error kill(const ProcessID &p_pid) override; int get_process_id() const override; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 79078456a5..e540b7617f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -114,6 +114,8 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_ICON: case FEATURE_NATIVE_ICON: case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_INPUT: + case FEATURE_NATIVE_DIALOG_FILE: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 152b9ac10f..c39b327953 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -42,6 +42,7 @@ #include "drivers/unix/net_socket_posix.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" +#include "drivers/windows/file_access_windows_pipe.h" #include "main/main.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_default.h" @@ -178,6 +179,7 @@ void OS_Windows::initialize() { FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM); + FileAccess::make_default<FileAccessWindowsPipe>(FileAccess::ACCESS_PIPE); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM); @@ -727,6 +729,105 @@ Dictionary OS_Windows::get_memory_info() const { return meminfo; } +Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +#define CLEAN_PIPES \ + if (pipe_in[0] != 0) { \ + CloseHandle(pipe_in[0]); \ + } \ + if (pipe_in[1] != 0) { \ + CloseHandle(pipe_in[1]); \ + } \ + if (pipe_out[0] != 0) { \ + CloseHandle(pipe_out[0]); \ + } \ + if (pipe_out[1] != 0) { \ + CloseHandle(pipe_out[1]); \ + } \ + if (pipe_err[0] != 0) { \ + CloseHandle(pipe_err[0]); \ + } \ + if (pipe_err[1] != 0) { \ + CloseHandle(pipe_err[1]); \ + } + + Dictionary ret; + + String path = p_path.replace("/", "\\"); + String command = _quote_command_line_argument(path); + for (const String &E : p_arguments) { + command += " " + _quote_command_line_argument(E); + } + + // Create pipes. + HANDLE pipe_in[2] = { 0, 0 }; + HANDLE pipe_out[2] = { 0, 0 }; + HANDLE pipe_err[2] = { 0, 0 }; + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = nullptr; + + ERR_FAIL_COND_V(!CreatePipe(&pipe_in[0], &pipe_in[1], &sa, 0), ret); + if (!SetHandleInformation(pipe_in[1], HANDLE_FLAG_INHERIT, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!CreatePipe(&pipe_out[0], &pipe_out[1], &sa, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!SetHandleInformation(pipe_out[0], HANDLE_FLAG_INHERIT, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!CreatePipe(&pipe_err[0], &pipe_err[1], &sa, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + ERR_FAIL_COND_V(!SetHandleInformation(pipe_err[0], HANDLE_FLAG_INHERIT, 0), ret); + + // Create process. + ProcessInfo pi; + ZeroMemory(&pi.si, sizeof(pi.si)); + pi.si.cb = sizeof(pi.si); + ZeroMemory(&pi.pi, sizeof(pi.pi)); + LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; + + pi.si.dwFlags |= STARTF_USESTDHANDLES; + pi.si.hStdInput = pipe_in[0]; + pi.si.hStdOutput = pipe_out[1]; + pi.si.hStdError = pipe_err[1]; + + DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; + + if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) { + CLEAN_PIPES + ERR_FAIL_V_MSG(ret, "Could not create child process: " + command); + } + CloseHandle(pipe_in[0]); + CloseHandle(pipe_out[1]); + CloseHandle(pipe_err[1]); + + ProcessID pid = pi.pi.dwProcessId; + process_map->insert(pid, pi); + + Ref<FileAccessWindowsPipe> main_pipe; + main_pipe.instantiate(); + main_pipe->open_existing(pipe_out[0], pipe_in[1]); + + Ref<FileAccessWindowsPipe> err_pipe; + err_pipe.instantiate(); + err_pipe->open_existing(pipe_err[0], 0); + + ret["stdio"] = main_pipe; + ret["stderr"] = err_pipe; + ret["pid"] = pid; + +#undef CLEAN_PIPES + return ret; +} + Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 351cb32c9b..c703a3ddca 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -181,6 +181,7 @@ public: virtual Dictionary get_memory_info() const override; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index c344272f38..1163c0e390 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -68,7 +68,7 @@ void FileDialog::popup(const Rect2i &p_rect) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { String root; if (access == ACCESS_RESOURCES) { root = ProjectSettings::get_singleton()->get_resource_path(); @@ -91,7 +91,7 @@ void FileDialog::set_visible(bool p_visible) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { if (p_visible) { String root; if (access == ACCESS_RESOURCES) { diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index 66d97398b9..6ae97944e0 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -6918,6 +6918,7 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_terrain"), &TileData::get_terrain); ClassDB::bind_method(D_METHOD("set_terrain_peering_bit", "peering_bit", "terrain"), &TileData::set_terrain_peering_bit); ClassDB::bind_method(D_METHOD("get_terrain_peering_bit", "peering_bit"), &TileData::get_terrain_peering_bit); + ClassDB::bind_method(D_METHOD("is_valid_terrain_peering_bit", "peering_bit"), &TileData::is_valid_terrain_peering_bit); // Navigation ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 8e6c659565..9ceb6909fe 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -1014,6 +1014,8 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE); BIND_ENUM_CONSTANT(FEATURE_STATUS_INDICATOR); BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP); + BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT); + BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE); 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 39a80ae7c1..f1a98c2c17 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -140,6 +140,8 @@ public: FEATURE_SCREEN_CAPTURE, FEATURE_STATUS_INDICATOR, FEATURE_NATIVE_HELP, + FEATURE_NATIVE_DIALOG_INPUT, + FEATURE_NATIVE_DIALOG_FILE, }; virtual bool has_feature(Feature p_feature) const = 0; diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index 15d43ff5dd..f56ef11c14 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -985,6 +985,9 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_DAMPING); BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY); BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT); + BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_SPRING_STIFFNESS); + BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_SPRING_DAMPING); + BIND_ENUM_CONSTANT(G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT); BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_LOWER_LIMIT); BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_UPPER_LIMIT); BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS); @@ -994,11 +997,18 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_ERP); BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_MOTOR_TARGET_VELOCITY); BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT); + BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS); + BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_SPRING_DAMPING); + BIND_ENUM_CONSTANT(G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT); + BIND_ENUM_CONSTANT(G6DOF_JOINT_MAX); BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT); BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT); + BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING); + BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING); BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_MOTOR); BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR); + BIND_ENUM_CONSTANT(G6DOF_JOINT_FLAG_MAX); ClassDB::bind_method(D_METHOD("joint_get_type", "joint"), &PhysicsServer3D::joint_get_type); |