fix(clipboard): auto-clear must not wipe content copied since our SetText
Copy a password (30s auto-clear armed), then copy something else from another app before the timer fires: the tick emptied the clipboard anyway, destroying the user's newer content. Guard with the Win32 clipboard sequence number: SetText snapshots GetClipboardSequenceNumber, ClearIfOurs only empties when it hasn't moved. Applied to the auto-clear timer AND the clear-on-minimize path (same bug class); the explicit JS clipboard/clear command stays unconditional (user-initiated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,9 @@ type
|
||||
TSecureClipboard = class
|
||||
private
|
||||
FClearTimer: TTimer;
|
||||
// Clipboard sequence number right after OUR SetText. If it moved, the
|
||||
// user copied something else since — that content is theirs, not ours.
|
||||
FSetSequence: DWORD;
|
||||
procedure ClearTimerTick(Sender: TObject);
|
||||
public
|
||||
constructor Create;
|
||||
@@ -50,6 +53,10 @@ type
|
||||
// AClearAfterMs = 0 disables auto-clear; default is 30 seconds.
|
||||
procedure SetText(const AText: string; AClearAfterMs: Integer = 30000);
|
||||
procedure Clear;
|
||||
// Clear only if the clipboard still holds what WE put there (sequence
|
||||
// unchanged). Auto-clear timer + minimize-to-tray use this so they never
|
||||
// wipe something the user copied from another app in the meantime.
|
||||
procedure ClearIfOurs;
|
||||
end;
|
||||
|
||||
// Distinguishes the "fill everything" hotkey (Ctrl+Shift+L) from the
|
||||
@@ -341,7 +348,7 @@ end;
|
||||
procedure TSecureClipboard.ClearTimerTick(Sender: TObject);
|
||||
begin
|
||||
FClearTimer.Enabled := False;
|
||||
Clear;
|
||||
ClearIfOurs;
|
||||
end;
|
||||
|
||||
procedure TSecureClipboard.SetText(const AText: string; AClearAfterMs: Integer);
|
||||
@@ -385,6 +392,10 @@ begin
|
||||
CloseClipboard;
|
||||
end;
|
||||
|
||||
// Snapshot the sequence AFTER our write — any later copy (ours or another
|
||||
// app's) bumps it, which is exactly the "is it still ours?" signal.
|
||||
FSetSequence := GetClipboardSequenceNumber;
|
||||
|
||||
if AClearAfterMs > 0 then
|
||||
begin
|
||||
FClearTimer.Interval := AClearAfterMs;
|
||||
@@ -402,6 +413,12 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSecureClipboard.ClearIfOurs;
|
||||
begin
|
||||
if GetClipboardSequenceNumber = FSetSequence then
|
||||
Clear;
|
||||
end;
|
||||
|
||||
function TSecureClipboard.ReadText: string;
|
||||
var
|
||||
H: THandle;
|
||||
@@ -581,9 +598,10 @@ begin
|
||||
// out of sight. SKIPPED when AClearClipboard=False — the quick-search
|
||||
// copy-then-hide flow deliberately keeps the password on the clipboard
|
||||
// (the 30s auto-clear timer still guards it) so the user can paste it
|
||||
// into their target app after we minimise.
|
||||
// into their target app after we minimise. ClearIfOurs: never wipe
|
||||
// content the user copied from another app since.
|
||||
if AClearClipboard then
|
||||
FSecureClipboard.Clear;
|
||||
FSecureClipboard.ClearIfOurs;
|
||||
|
||||
LFormHwnd := MainFormHWND(FMainForm);
|
||||
LAppHwnd := FindFMXAppWindow;
|
||||
|
||||
Reference in New Issue
Block a user