diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 22b63a09290e..40c1a539588b 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -546,6 +546,10 @@ bool DirAccess::get_include_hidden() const { return include_hidden; } +bool DirAccess::is_case_sensitive(const String &p_path) const { + return true; +} + void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open); ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); @@ -583,6 +587,8 @@ void DirAccess::_bind_methods() { ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden); ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden); + ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden"); } diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 52ed688debf3..4ee69571f256 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -159,6 +159,8 @@ public: void set_include_hidden(bool p_enable); bool get_include_hidden() const; + virtual bool is_case_sensitive(const String &p_path) const; + DirAccess() {} virtual ~DirAccess() {} }; diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index 9c72bc2247e5..204bd89aa4cb 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -204,6 +204,14 @@ Returns the available space on the current directory's disk, in bytes. Returns [code]0[/code] if the platform-specific method to query the available space fails. + + + + + Returns [code]true[/code] if the file system or directory use case sensitive file names. + [b]Note:[/b] This method is implemented on macOS and Windows. On other platforms, it always returns [code]true[/code]. + + diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 26b8881c39fa..8bf83823a03e 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -32,6 +32,7 @@ #include "dir_access_windows.h" +#include "core/config/project_settings.h" #include "core/os/memory.h" #include "core/string/print_string.h" @@ -40,6 +41,26 @@ #define WIN32_LEAN_AND_MEAN #include +typedef struct _NT_IO_STATUS_BLOCK { + union { + LONG Status; + PVOID Pointer; + } DUMMY; + ULONG_PTR Information; +} NT_IO_STATUS_BLOCK; + +typedef struct _NT_FILE_CASE_SENSITIVE_INFO { + ULONG Flags; +} NT_FILE_CASE_SENSITIVE_INFO; + +typedef enum _NT_FILE_INFORMATION_CLASS { + FileCaseSensitiveInformation = 71, +} NT_FILE_INFORMATION_CLASS; + +#define NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 + +extern "C" NTSYSAPI LONG NTAPI NtQueryInformationFile(HANDLE FileHandle, NT_IO_STATUS_BLOCK *IoStatusBlock, PVOID FileInformation, ULONG Length, NT_FILE_INFORMATION_CLASS FileInformationClass); + struct DirAccessWindowsPrivate { HANDLE h; // handle for FindFirstFile. WIN32_FIND_DATA f; @@ -340,6 +361,33 @@ String DirAccessWindows::get_filesystem_type() const { ERR_FAIL_V(""); } +bool DirAccessWindows::is_case_sensitive(const String &p_path) const { + String f = p_path; + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h_file == INVALID_HANDLE_VALUE) { + return false; + } + + NT_IO_STATUS_BLOCK io_status_block; + NT_FILE_CASE_SENSITIVE_INFO file_info; + LONG out = NtQueryInformationFile(h_file, &io_status_block, &file_info, sizeof(NT_FILE_CASE_SENSITIVE_INFO), FileCaseSensitiveInformation); + ::CloseHandle(h_file); + + if (out >= 0) { + return file_info.Flags & NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR; + } else { + return false; + } +} + DirAccessWindows::DirAccessWindows() { p = memnew(DirAccessWindowsPrivate); p->h = INVALID_HANDLE_VALUE; diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h index 1e55917756ad..1dcab84c9d53 100644 --- a/drivers/windows/dir_access_windows.h +++ b/drivers/windows/dir_access_windows.h @@ -84,6 +84,7 @@ public: uint64_t get_space_left() override; virtual String get_filesystem_type() const override; + virtual bool is_case_sensitive(const String &p_path) const override; DirAccessWindows(); ~DirAccessWindows(); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index be06a3932c3d..2bedb090758b 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1776,12 +1776,12 @@ void FileSystemDock::_rename_operation_confirm() { // Present a more user friendly warning for name conflict. Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); -#if defined(WINDOWS_ENABLED) - // Workaround case insensitivity on Windows. - if ((da->file_exists(new_path) || da->dir_exists(new_path)) && new_path.to_lower() != old_path.to_lower()) { -#else - if (da->file_exists(new_path) || da->dir_exists(new_path)) { -#endif + + bool new_exist = (da->file_exists(new_path) || da->dir_exists(new_path)); + if (!da->is_case_sensitive(new_path.get_base_dir())) { + new_exist = new_exist && (new_path.to_lower() != old_path.to_lower()); + } + if (new_exist) { EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); s->set_text(col_index, old_name); return; diff --git a/platform/macos/dir_access_macos.h b/platform/macos/dir_access_macos.h index 64556999a78a..167c162200db 100644 --- a/platform/macos/dir_access_macos.h +++ b/platform/macos/dir_access_macos.h @@ -49,6 +49,7 @@ protected: virtual String get_drive(int p_drive) override; virtual bool is_hidden(const String &p_name) override; + virtual bool is_case_sensitive(const String &p_path) const override; }; #endif // UNIX ENABLED diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm index 2413d7bcf344..66d81f2687e8 100644 --- a/platform/macos/dir_access_macos.mm +++ b/platform/macos/dir_access_macos.mm @@ -30,6 +30,8 @@ #include "dir_access_macos.h" +#include "core/config/project_settings.h" + #if defined(UNIX_ENABLED) #include @@ -78,4 +80,19 @@ bool DirAccessMacOS::is_hidden(const String &p_name) { return [hidden boolValue]; } +bool DirAccessMacOS::is_case_sensitive(const String &p_path) const { + String f = p_path; + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; + NSNumber *cs = nil; + if (![url getResourceValue:&cs forKey:NSURLVolumeSupportsCaseSensitiveNamesKey error:nil]) { + return false; + } + return [cs boolValue]; +} + #endif // UNIX_ENABLED diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 7caa0153d7b2..bdacdbb9ba32 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -419,6 +419,7 @@ def configure_msvc(env, vcvars_msvc_config): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] if env.debug_features: @@ -610,6 +611,7 @@ def configure_mingw(env): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] )