From ea47deeb5c67a86ee00d4c28260a273d9a03c639 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 03:38:57 +0000 Subject: [PATCH 1/2] Handle case conflicts in DnD t=k remote file/dir transfer Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/8c155dc2-c7ee-448f-9300-825f74411ddd Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com> --- kitty/dnd.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/kitty/dnd.c b/kitty/dnd.c index 086312fea..5e2c90256 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -11,6 +11,7 @@ #include "safe-wrappers.h" #include "iqsort.h" #include "png-reader.h" +#include #include #include #include @@ -137,6 +138,32 @@ mktempdir_in_cache(const char *prefix, int *fd) { return NULL; } +// -1 = unknown, 0 = case-sensitive, 1 = case-insensitive +static int tempdir_case_insensitive = -1; + +static void +detect_tempdir_case_sensitivity(const char *tempdir_path) { + if (tempdir_case_insensitive >= 0) return; + tempdir_case_insensitive = 0; // default: case-sensitive + const char *slash = strrchr(tempdir_path, '/'); + if (!slash) return; + size_t dirname_len = (size_t)(slash - tempdir_path); + const char *basename = slash + 1; + size_t basename_len = strlen(basename); + if (!basename_len) return; + char upper_basename[PATH_MAX]; + if (basename_len >= sizeof(upper_basename)) return; + for (size_t i = 0; i <= basename_len; i++) upper_basename[i] = (char)toupper((unsigned char)basename[i]); + if (strcmp(upper_basename, basename) == 0) return; // already all-uppercase, cannot distinguish + char upper_path[PATH_MAX]; + if (dirname_len + 1 + basename_len + 1 > sizeof(upper_path)) return; + memcpy(upper_path, tempdir_path, dirname_len); + upper_path[dirname_len] = '/'; + memcpy(upper_path + dirname_len + 1, upper_basename, basename_len + 1); + struct stat st; + if (stat(upper_path, &st) == 0) tempdir_case_insensitive = 1; +} + static char* as_file_url(const char *wd, const char *middle, const char *filename) { RAII_PyObject(mname, PyUnicode_DecodeFSDefault("kitty.utils")); @@ -1818,6 +1845,65 @@ finish_remote_data(Window *w, size_t item_idx) { #define mi ds.items[mime_item_idx] +static char* +lowercase_copy(const char *s) { + size_t len = strlen(s); + char *ans = malloc(len + 1); + if (!ans) return NULL; + for (size_t i = 0; i <= len; i++) ans[i] = (char)tolower((unsigned char)s[i]); + return ans; +} + +static bool +has_lowercase_conflict(const char *lower_name, char **seen_lower, size_t count) { + for (size_t i = 0; i < count; i++) { + if (strcmp(lower_name, seen_lower[i]) == 0) return true; + } + return false; +} + +static void +uniqify_dir_entries_for_case_insensitive_fs(DragRemoteItem *children, size_t count) { + if (!count) return; + char **seen_lower = calloc(count, sizeof(char*)); + if (!seen_lower) return; + size_t seen_count = 0; + for (size_t i = 0; i < count; i++) { + if (!children[i].dir_entry_name) continue; + const char *orig_name = children[i].dir_entry_name; + char *lower = lowercase_copy(orig_name); + if (!lower) continue; + if (!has_lowercase_conflict(lower, seen_lower, seen_count)) { + seen_lower[seen_count++] = lower; + continue; + } + free(lower); + bool renamed = false; + for (int q = 1; q <= 1000; q++) { + size_t buflen = 26 + strlen(orig_name); // "case-conflict-" + 10 digits + "-" + name + null + char *new_name = malloc(buflen); + if (!new_name) break; + snprintf(new_name, buflen, "case-conflict-%d-%s", q, orig_name); + lower = lowercase_copy(new_name); + if (!lower) { free(new_name); break; } + if (!has_lowercase_conflict(lower, seen_lower, seen_count)) { + free(children[i].dir_entry_name); + children[i].dir_entry_name = new_name; + seen_lower[seen_count++] = lower; + renamed = true; + break; + } + free(lower); free(new_name); + } + if (!renamed) { + lower = lowercase_copy(children[i].dir_entry_name); + if (lower) seen_lower[seen_count++] = lower; + } + } + for (size_t i = 0; i < seen_count; i++) free(seen_lower[i]); + free(seen_lower); +} + static void populate_dir_entries(Window *w, DragRemoteItem *ri) { size_t num = count_occurrences((char*)ri->data, ri->data_sz, 0) + 1; @@ -1836,6 +1922,8 @@ populate_dir_entries(Window *w, DragRemoteItem *ri) { } ptr = p ? p + 1 : end; } + if (tempdir_case_insensitive == 1) + uniqify_dir_entries_for_case_insensitive_fs(ri->children, ri->children_sz); } static void @@ -1914,6 +2002,7 @@ toplevel_data_for_drag( mi.base_dir_for_remote_items = mktempdir_in_cache("dnd-drag-", &fd); if (!mi.base_dir_for_remote_items) abrt(errno); mi.base_dir_fd_plus_one = fd + 1; + detect_tempdir_case_sensitivity(mi.base_dir_for_remote_items); } if (uri_item_idx >= mi.num_remote_items) abrt(EINVAL); DragRemoteItem *ri = mi.remote_items + uri_item_idx; From 4a0b189bcef87decd07f7c764fc0ca189ae9c4cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 03:42:19 +0000 Subject: [PATCH 2/2] Fix loop style in detect_tempdir_case_sensitivity and lowercase_copy Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/8c155dc2-c7ee-448f-9300-825f74411ddd Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com> --- kitty/dnd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kitty/dnd.c b/kitty/dnd.c index 5e2c90256..6fed8d749 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -153,7 +153,8 @@ detect_tempdir_case_sensitivity(const char *tempdir_path) { if (!basename_len) return; char upper_basename[PATH_MAX]; if (basename_len >= sizeof(upper_basename)) return; - for (size_t i = 0; i <= basename_len; i++) upper_basename[i] = (char)toupper((unsigned char)basename[i]); + for (size_t i = 0; i < basename_len; i++) upper_basename[i] = (char)toupper((unsigned char)basename[i]); + upper_basename[basename_len] = '\0'; if (strcmp(upper_basename, basename) == 0) return; // already all-uppercase, cannot distinguish char upper_path[PATH_MAX]; if (dirname_len + 1 + basename_len + 1 > sizeof(upper_path)) return; @@ -1850,7 +1851,8 @@ lowercase_copy(const char *s) { size_t len = strlen(s); char *ans = malloc(len + 1); if (!ans) return NULL; - for (size_t i = 0; i <= len; i++) ans[i] = (char)tolower((unsigned char)s[i]); + for (size_t i = 0; i < len; i++) ans[i] = (char)tolower((unsigned char)s[i]); + ans[len] = '\0'; return ans; }