bc89ec0010
It was in French because the conversation is, and I never asked. The desktop app the client mirrors says Add field, Attach file, Show previous versions - someone moving between the two would notice the seam. Every visible string moved: buttons, statuses, detail row captions, log lines and the biometric prompt, plus the messages the modules surface through LastStoreError and LastBiometricError. Code comments stay in French. They address the author, not the user, and that is already the language of CLAUDE.md and much of the repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
4.2 KiB
ObjectPascal
137 lines
4.2 KiB
ObjectPascal
unit PMA.Clipboard;
|
|
{
|
|
Copie dans le presse-papiers, en marquant le contenu SENSIBLE.
|
|
|
|
POURQUOI PAS IFMXClipboardService : depuis Android 13, le systeme affiche un
|
|
apercu de ce qui vient d'etre copie, en bas de l'ecran. Pour un mot de passe,
|
|
c'est exactement ce qu'on cherche a eviter - il s'affiche en clair, hors de
|
|
l'application, et reste visible quelques secondes.
|
|
|
|
Le drapeau qui supprime cet apercu est un extra du ClipDescription, que le
|
|
service FMX n'expose pas. D'ou ce passage direct par ClipboardManager.
|
|
|
|
Cela ne remplace pas l'effacement differe : le drapeau cache l'apercu, il ne
|
|
vide pas le presse-papiers.
|
|
}
|
|
|
|
interface
|
|
|
|
/// Copie ATexte. Retourne False si la plateforme n'a rien fourni - l'appelant
|
|
/// peut alors retomber sur le service FMX.
|
|
function CopySensitive(const AText: string): Boolean;
|
|
|
|
/// Contenu courant, ou chaine vide. Sert a ne vider le presse-papiers que
|
|
/// s'il contient toujours ce qu'on y a mis.
|
|
function CurrentClipboard: string;
|
|
|
|
/// Vide le presse-papiers.
|
|
procedure ClearClipboard;
|
|
|
|
implementation
|
|
|
|
{$IF DEFINED(ANDROID)}
|
|
uses
|
|
System.SysUtils, Androidapi.Helpers, Androidapi.JNI.JavaTypes,
|
|
Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Os, Androidapi.JNIBridge;
|
|
|
|
const
|
|
// android.content.ClipDescription.EXTRA_IS_SENSITIVE, ecrit en dur : la
|
|
// constante n'existe qu'a partir de l'API 33 dans les en-tetes, alors que la
|
|
// valeur est reconnue par le systeme des qu'il sait la lire, et ignoree
|
|
// ailleurs. Un extra inconnu ne derange personne.
|
|
EXTRA_IS_SENSITIVE = 'android.content.extra.IS_SENSITIVE';
|
|
|
|
/// Jcontent_ClipboardManager et NON JClipboardManager : ce dernier est
|
|
/// l'android.text.ClipboardManager deprecie, qui ne connait que du texte nu et
|
|
/// aucun ClipData - donc aucun extra ou poser le drapeau.
|
|
function Manager: Jcontent_ClipboardManager;
|
|
var
|
|
LObj: JObject;
|
|
begin
|
|
LObj := TAndroidHelper.Context.getSystemService(
|
|
TJContext.JavaClass.CLIPBOARD_SERVICE);
|
|
if LObj = nil then Exit(nil);
|
|
Result := TJcontent_ClipboardManager.Wrap((LObj as ILocalObject).GetObjectID);
|
|
end;
|
|
|
|
function CopySensitive(const AText: string): Boolean;
|
|
var
|
|
LMgr: Jcontent_ClipboardManager;
|
|
LClip: JClipData;
|
|
LExtras: JPersistableBundle;
|
|
begin
|
|
Result := False;
|
|
try
|
|
LMgr := Manager;
|
|
if LMgr = nil then Exit;
|
|
// L'etiquette est visible du systeme : elle ne doit rien decrire du
|
|
// contenu, seulement son origine.
|
|
// Les DEUX arguments sont des CharSequence : passer un JString au premier
|
|
// compile ailleurs mais pas ici.
|
|
LClip := TJClipData.JavaClass.newPlainText(StrToJCharSequence('vault'),
|
|
StrToJCharSequence(AText));
|
|
LExtras := TJPersistableBundle.JavaClass.init;
|
|
LExtras.putBoolean(StringToJString(EXTRA_IS_SENSITIVE), True);
|
|
LClip.getDescription.setExtras(LExtras);
|
|
LMgr.setPrimaryClip(LClip);
|
|
Result := True;
|
|
except
|
|
Result := False; // l'appelant retombera sur le service FMX
|
|
end;
|
|
end;
|
|
|
|
function CurrentClipboard: string;
|
|
var
|
|
LMgr: Jcontent_ClipboardManager;
|
|
LClip: JClipData;
|
|
begin
|
|
Result := '';
|
|
try
|
|
LMgr := Manager;
|
|
if (LMgr = nil) or not LMgr.hasPrimaryClip then Exit;
|
|
LClip := LMgr.getPrimaryClip;
|
|
if (LClip = nil) or (LClip.getItemCount < 1) then Exit;
|
|
Result := JCharSequenceToStr(LClip.getItemAt(0).getText);
|
|
except
|
|
Result := '';
|
|
end;
|
|
end;
|
|
|
|
procedure ClearClipboard;
|
|
begin
|
|
// Un contenu vide plutot qu'un appel a clearPrimaryClip, absent avant l'API
|
|
// 28 : le resultat visible est le meme.
|
|
CopySensitive('');
|
|
end;
|
|
{$ELSE}
|
|
uses
|
|
System.SysUtils, System.Rtti, FMX.Platform;
|
|
|
|
function CopySensitive(const AText: string): Boolean;
|
|
var
|
|
LClip: IFMXClipboardService;
|
|
begin
|
|
// Hors Android il n'y a pas d'apercu systeme a supprimer : le service
|
|
// standard suffit.
|
|
Result := TPlatformServices.Current.SupportsPlatformService(
|
|
IFMXClipboardService, LClip);
|
|
if Result then LClip.SetClipboard(AText);
|
|
end;
|
|
|
|
function CurrentClipboard: string;
|
|
var
|
|
LClip: IFMXClipboardService;
|
|
begin
|
|
Result := '';
|
|
if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, LClip) then
|
|
Result := LClip.GetClipboard.ToString;
|
|
end;
|
|
|
|
procedure ClearClipboard;
|
|
begin
|
|
CopySensitive('');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
end.
|