Files
eepp/include/eepp/window/draganddrop.hpp
T
Martín Lucas Golini 0e7e12ba76 fix(vfs): data safety, SSH policy and liveness fixes from the pre-merge review
Data safety (VFS):
- Copies write a temporary sibling and replace the destination only
  once it is complete (as writeAll() already did): a failed overwrite
  kept nothing of the old destination, and copying onto a hard link to
  the source truncated the source itself. A destination symlink is
  written through, and one that resolves to the source fails as the
  same file.
- Moving a symlink to another file system is refused: the fallback copy
  followed it and then removed the copied entries through the link,
  emptying its target.
- Saves keep the exact mode of the file they replace: the temporary was
  created with it, but filtered through the umask.

Deadlocks and blocking:
- writeAll( createParents ) waited on makeDirs() queued on its own
  orchestration pool: one thread deadlocked at once, two with two
  concurrent saves. Orchestration jobs now create parents directly.
- Copies and moves run on their own pool (Config::transferThreads), so
  long transfers no longer delay saves, reads and recursive removals.
- Local watches are registered and removed on the I/O pool: efsw and the
  directory checks touch the filesystem, which blocked the UI thread on
  slow mounts.

SSH security:
- "@revoked" known_hosts keys are rejected; they were skipped as
  unknown, so StrictHostKeyChecking accept-new trusted them.
- Sessions are shared only between configurations with the same
  security policy (known hosts, host key checking, algorithms,
  authentication settings), not just the same destination: a strict
  alias reused a session a permissive one had accepted.
- A configured algorithm restriction that permits nothing this libssh2
  supports fails the connection instead of using libssh2's defaults.

SSH liveness:
- Operations submitted to a failed or closed connection fail with
  ConnectionLost instead of staying pending forever.
- ssh-agent requests run on a worker thread (SSHAgent, Unix-domain
  sockets), with libssh2_userauth_publickey() and a sign callback that
  waits without blocking: an agent waiting for a hardware key touch no
  longer stalls every other session. Windows keeps libssh2's agent.
- Connecting no longer holds a helper thread while the user answers a
  host key or password prompt: the setup continues from the session's
  ready task, one ProxyJump hop at a time. Jump hosts only need to
  authenticate, not to offer SFTP.
- Idle connections close after SFTPProviderConfig::idleTimeout. The old
  check compared use_count() with 1 and never passed: connections now
  track their users (open handles, pumps, jump children).
- A remote watch agent that never answers falls back to polling after
  5 seconds; the directory listing waited for it indefinitely.

Core:
- CancellationSource::cancel() keeps the shared state alive while its
  callbacks run: a callback destroying the source freed it mid-cancel
  (heap use after free under AddressSanitizer).
- MainThreadExecutor::shutdown() detaches the executor from its
  dispatcher node and drops queued and later functions.
- String::icompare(): case-insensitive comparison without allocations.

VFSDirectoryModel (UI thread):
- Names compare with String::icompare() instead of lowercased copies,
  which made every sort, merge and insertion allocate (about 2x slower
  when sorting).
- Sorting by type asks the type provider once per entry instead of
  twice per comparison.
- The case-insensitive plain text filter keeps a search table instead
  of lowercasing every name on each row rebuild.
- Watch events update the visible rows in place instead of rebuilding
  them from every entry per event.

Drag and drop:
- The X11 drag source showed the pending badge over the source window
  for every application, which broke the X11 drag and drop integration
  tests in CI. DragOffer::sourceHandlesDrops now asks for it; eefm sets
  it, since it completes those drops as its own drag.

eefm:
- Shutdown destroyed the main thread executor before the VFS and the
  thread pool while continuations still held it: closing during a
  transfer could dispatch through freed memory. Transfers are cancelled,
  the VFS is destroyed, the executor is detached, and only then are the
  scene, its pool and the executor destroyed.
- Remote files opened locally were downloaded to
  /tmp/eefm/<scheme>/<host>/<name>: files with the same name from other
  folders, users or ports overwrote each other, in a directory shared by
  every user. They now go to a per-file folder (a hash of the complete
  URI) in the user's cache.

Every fix comes with a regression test.
2026-10-15 20:33:13 -03:00

120 lines
4.1 KiB
C++

#ifndef EE_WINDOW_DRAGANDDROP_HPP
#define EE_WINDOW_DRAGANDDROP_HPP
#include <eepp/config.hpp>
#include <eepp/core/small_vector.hpp>
#include <eepp/math/vector2.hpp>
#include <functional>
#include <string>
namespace EE { namespace Graphics {
class Image;
}} // namespace EE::Graphics
namespace EE { namespace Window {
class Window;
enum class DragAction : Uint8 { None = 0, Copy = 1, Move = 2, Link = 4, Ask = 8 };
using DragActions = Uint32;
enum class DragEndReason : Uint8 { Dropped, Cancelled, Unsupported, Failed };
enum class DragStartStatus : Uint8 {
Started,
Busy,
InvalidOffer,
InvalidGesture,
Unsupported,
Failed
};
struct DragFile {
/** Absolute local filesystem path, encoded as UTF-8. */
std::string path;
};
struct DragPreview {
/** Optional RGB/RGBA snapshot, borrowed only during startDrag(). If absent, a file/count icon
* is generated. The native session copies the pixels; it never retains a UI widget. */
const Graphics::Image* image{ nullptr };
/** Pointer position relative to the image's top-left corner, in desktop pixels (-2048..2048).
*/
Math::Vector2i hotspot{ -12, -16 };
};
struct DragOffer {
SmallVector<DragFile, 1> files;
DragActions actions{ static_cast<DragActions>( DragAction::Copy ) };
DragPreview preview;
/** The application completes drops on its own window (DragResult::releasedOverSource on
* X11): the preview shows a pending drop there instead of a rejected one. */
bool sourceHandlesDrops{ false };
};
struct DragResult {
std::string error;
DragEndReason reason{ DragEndReason::Cancelled };
DragAction performedAction{ DragAction::None };
/** The gesture was released over its own source window, which X11 never offers as a native
* target: the session ends Cancelled, and the application can complete it as an internal
* drop. Elsewhere the source window receives ordinary FileDropped events instead. */
bool releasedOverSource{ false };
};
struct DragStartResult {
std::string error;
DragStartStatus status{ DragStartStatus::Unsupported };
};
struct DragCapabilities {
bool fileSource{ false };
bool dataSource{ false };
/** Legacy SDL file reception; does not imply MIME/action negotiation. */
bool fileTarget{ false };
bool negotiatedTarget{ false };
bool dragImage{ false };
};
using DragCompletionCallback = std::function<void( DragResult )>;
/** Native cross-application file dragging. All methods must run on the window/UI thread.
* This lightweight facade borrows its Window and must not outlive it. A started session owns its
* serialized payload and completion callback. Rejected starts never invoke completion.
* Completions run once, outside native event hooks, when input is updated; they may start another
* drag. Cancellation/closing a source window also completes the session. Callbacks must not borrow
* objects that may be destroyed before completion (use weak ownership when appropriate).
* Engine teardown cancels and releases pending sessions without invoking their callbacks.
* Initially only Copy is supported. Dropped means the native transfer finished, not that a browser
* uploaded or a file manager copied a file. Existing internal UI dragging is independent.
*/
class EE_API DragAndDrop {
public:
static DragAndDrop forWindow( Window& window );
DragCapabilities getCapabilities() const;
/** Call after a movement threshold, while the initiating left mouse button is still down.
* Preview images must be RGB/RGBA, at most 2048 pixels per side and 1,048,576 pixels total.
* The offer is read synchronously; the session retains only its own native representation. */
DragStartResult startDrag( const DragOffer& offer, DragCompletionCallback completion = {} );
void cancelDrag();
bool isDragging() const;
/** Serialize an offer as text/uri-list (CRLF). Rejects empty, relative, invalid UTF-8 and NUL
* paths. Preserves order and duplicate entries. On failure output is empty. */
static bool encodeFileUriList( const DragOffer& offer, std::string& output,
std::string* error = nullptr );
private:
explicit DragAndDrop( Window& window );
Window* mWindow;
};
}} // namespace EE::Window
#endif