--- src/uterm.cc.orig 2026-04-27 01:49:24.547631740 +0000 +++ src/uterm.cc 2026-04-27 02:08:19.459112499 +0000 @@ -73,6 +73,8 @@ return 1; } + m_pty = &pty; + ReaderThread reader{&pty}; m_current_reader = &reader; @@ -102,6 +104,7 @@ m_window.set_resize_cb(std::bind(&Uterm::HandleResize, this, _1, _2)); m_window.set_selection_cb(std::bind(&Uterm::HandleSelection, this, _1, _2, _3)); m_window.set_scroll_cb(std::bind(&Uterm::HandleScroll, this, _1, _2)); + m_window.set_drop_cb(std::bind(&Uterm::HandleDrop, this, _1)); double mark = 0; double fps = m_config.fps(); @@ -140,6 +143,7 @@ std::unique_lock lock{m_current_reader_lock}; m_current_reader = nullptr; + m_pty = nullptr; reader.Stop(); return 0; @@ -190,3 +194,46 @@ void Uterm::HandleTitle(const string &title) { m_window.SetTitle(title); } + +// Check if path needs shell escaping +static bool NeedsEscape(const string &path) { + for (char c : path) { + // Characters that need escaping in shell + if (c == ' ' || c == '\'' || c == '"' || c == '\\' || c == '$' || + c == '`' || c == '!' || c == '&' || c == '|' || c == ';' || + c == '(' || c == ')' || c == '<' || c == '>' || c == '*' || + c == '?' || c == '[' || c == ']' || c == '{' || c == '}' || + c == '#' || c == '~' || c == '\n' || c == '\t') { + return true; + } + } + return false; +} + +// Escape a path for safe use in shell (only if needed) +static string ShellEscape(const string &path) { + if (!NeedsEscape(path)) { + return path; + } + string result = "'"; + for (char c : path) { + if (c == '\'') { + result += "'\\''"; // End quote, escaped quote, start quote + } else { + result += c; + } + } + result += "'"; + return result; +} + +void Uterm::HandleDrop(const std::vector &paths) { + if (m_pty == nullptr) return; + + string text; + for (size_t i = 0; i < paths.size(); i++) { + if (i > 0) text += " "; + text += ShellEscape(paths[i]); + } + m_pty->Write(text); +}