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
|
TSecureClipboard = class
|
||||||
private
|
private
|
||||||
FClearTimer: TTimer;
|
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);
|
procedure ClearTimerTick(Sender: TObject);
|
||||||
public
|
public
|
||||||
constructor Create;
|
constructor Create;
|
||||||
@@ -50,6 +53,10 @@ type
|
|||||||
// AClearAfterMs = 0 disables auto-clear; default is 30 seconds.
|
// AClearAfterMs = 0 disables auto-clear; default is 30 seconds.
|
||||||
procedure SetText(const AText: string; AClearAfterMs: Integer = 30000);
|
procedure SetText(const AText: string; AClearAfterMs: Integer = 30000);
|
||||||
procedure Clear;
|
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;
|
end;
|
||||||
|
|
||||||
// Distinguishes the "fill everything" hotkey (Ctrl+Shift+L) from the
|
// Distinguishes the "fill everything" hotkey (Ctrl+Shift+L) from the
|
||||||
@@ -341,7 +348,7 @@ end;
|
|||||||
procedure TSecureClipboard.ClearTimerTick(Sender: TObject);
|
procedure TSecureClipboard.ClearTimerTick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
FClearTimer.Enabled := False;
|
FClearTimer.Enabled := False;
|
||||||
Clear;
|
ClearIfOurs;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSecureClipboard.SetText(const AText: string; AClearAfterMs: Integer);
|
procedure TSecureClipboard.SetText(const AText: string; AClearAfterMs: Integer);
|
||||||
@@ -385,6 +392,10 @@ begin
|
|||||||
CloseClipboard;
|
CloseClipboard;
|
||||||
end;
|
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
|
if AClearAfterMs > 0 then
|
||||||
begin
|
begin
|
||||||
FClearTimer.Interval := AClearAfterMs;
|
FClearTimer.Interval := AClearAfterMs;
|
||||||
@@ -402,6 +413,12 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSecureClipboard.ClearIfOurs;
|
||||||
|
begin
|
||||||
|
if GetClipboardSequenceNumber = FSetSequence then
|
||||||
|
Clear;
|
||||||
|
end;
|
||||||
|
|
||||||
function TSecureClipboard.ReadText: string;
|
function TSecureClipboard.ReadText: string;
|
||||||
var
|
var
|
||||||
H: THandle;
|
H: THandle;
|
||||||
@@ -581,9 +598,10 @@ begin
|
|||||||
// out of sight. SKIPPED when AClearClipboard=False — the quick-search
|
// out of sight. SKIPPED when AClearClipboard=False — the quick-search
|
||||||
// copy-then-hide flow deliberately keeps the password on the clipboard
|
// 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
|
// (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
|
if AClearClipboard then
|
||||||
FSecureClipboard.Clear;
|
FSecureClipboard.ClearIfOurs;
|
||||||
|
|
||||||
LFormHwnd := MainFormHWND(FMainForm);
|
LFormHwnd := MainFormHWND(FMainForm);
|
||||||
LAppHwnd := FindFMXAppWindow;
|
LAppHwnd := FindFMXAppWindow;
|
||||||
|
|||||||
Reference in New Issue
Block a user