fix(sync): QR plus court (adresse du depot, sans sur-echappement) + TRY_HARDER au decodage

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-17 18:48:04 +01:00
parent dd9fd62dcb
commit 23967ddfca
2 changed files with 64 additions and 7 deletions
+17 -2
View File
@@ -1353,8 +1353,23 @@ begin
try try
LQr.Encoding := qrUTF8NoBOM; LQr.Encoding := qrUTF8NoBOM;
LQr.QuietZone := 4; LQr.QuietZone := 4;
LQr.Data := 'pmserver://sync?u=' + TNetEncoding.URL.Encode(LQrUrl) + // LE PLUS COURT POSSIBLE : moins de caracteres = modules plus gros,
'&t=' + TNetEncoding.URL.Encode(LQrTok); // lisibles par une camera. L'adresse du depot plutot que l'URL d'API
// (le telephone la rallonge a l'enregistrement), et seuls les
// caracteres qui casseraient la requete sont echappes - TNetEncoding
// changeait chaque ":" et "/" en trois caracteres.
var LShort := LQrUrl;
var LApi := Pos('/api/v1/repos/', LShort);
if (LApi > 0) and EndsText('/contents/vault-sync.json', LShort) then
LShort := Copy(LShort, 1, LApi) +
Copy(LShort, LApi + Length('/api/v1/repos/'),
Length(LShort) - LApi - Length('/api/v1/repos/') - Length('/contents/vault-sync.json') + 1);
LShort := StringReplace(LShort, '%', '%25', [rfReplaceAll]);
LShort := StringReplace(LShort, '&', '%26', [rfReplaceAll]);
LShort := StringReplace(LShort, '#', '%23', [rfReplaceAll]);
LShort := StringReplace(LShort, '+', '%2B', [rfReplaceAll]);
LShort := StringReplace(LShort, ' ', '%20', [rfReplaceAll]);
LQr.Data := 'pmserver://sync?u=' + LShort + '&t=' + TNetEncoding.URL.Encode(LQrTok);
LSize := LQr.Rows; LSize := LQr.Rows;
for var R := 0 to LQr.Rows - 1 do for var R := 0 to LQr.Rows - 1 do
for var C := 0 to LQr.Columns - 1 do for var C := 0 to LQr.Columns - 1 do
+47 -5
View File
@@ -348,10 +348,13 @@ type
FScanLay: TLayout; FScanLay: TLayout;
FScanImg: TImage; FScanImg: TImage;
FCam: TCameraComponent; FCam: TCameraComponent;
FScanLbl: TLabel;
FScanWatch: TTimer;
procedure StartScan; procedure StartScan;
procedure BeginScan; procedure BeginScan;
procedure StopScan; procedure StopScan;
procedure DoCancelScan(Sender: TObject); procedure DoCancelScan(Sender: TObject);
procedure OnScanWatch(Sender: TObject);
procedure OnCamFrame(Sender: TObject; const ATime: TMediaTime); procedure OnCamFrame(Sender: TObject; const ATime: TMediaTime);
function ApplySyncLink(const ALink: string): Boolean; function ApplySyncLink(const ALink: string): Boolean;
{$IFDEF ANDROID} {$IFDEF ANDROID}
@@ -397,7 +400,7 @@ uses
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp, PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
PMA.ScreenGuard, PMA.Clipboard, PMA.Theme, PMA.WebUI, PMA.VaultPatch, PMA.ScreenGuard, PMA.Clipboard, PMA.Theme, PMA.WebUI, PMA.VaultPatch,
PMA.SaveFile, PMA.Favicon, System.JSON, System.Permissions, PMA.SaveFile, PMA.Favicon, System.JSON, System.Permissions,
ZXing.ScanManager, ZXing.BarcodeFormat, ZXing.ReadResult ZXing.ScanManager, ZXing.BarcodeFormat, ZXing.ReadResult, ZXing.DecodeHintType
{$IFDEF ANDROID} {$IFDEF ANDROID}
, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, , Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes,
Androidapi.Helpers, FMX.Platform.Android, Androidapi.JNI.Os Androidapi.Helpers, FMX.Platform.Android, Androidapi.JNI.Os
@@ -3290,7 +3293,15 @@ begin
LLbl.TextSettings.FontColor := TAlphaColors.White; LLbl.TextSettings.FontColor := TAlphaColors.White;
LLbl.TextSettings.Font.Size := 16; LLbl.TextSettings.Font.Size := 16;
LLbl.TextSettings.HorzAlign := TTextAlign.Center; LLbl.TextSettings.HorzAlign := TTextAlign.Center;
LLbl.Text := 'Point the camera at the QR shown on the PC'; LLbl.WordWrap := True;
FScanLbl := LLbl;
// AUCUNE IMAGE = CAMERA TENUE AILLEURS. Ouverte par l'appareil photo du
// systeme, meme en arriere-plan, elle ne nous est pas donnee - et sans ce
// guet l'ecran restait noir sans un mot.
FScanWatch := TTimer.Create(Self);
FScanWatch.Enabled := False;
FScanWatch.Interval := 3000;
FScanWatch.OnTimer := OnScanWatch;
LBtn := TButton.Create(FScanLay); LBtn := TButton.Create(FScanLay);
LBtn.Parent := FScanLay; LBtn.Parent := FScanLay;
LBtn.Align := TAlignLayout.Bottom; LBtn.Align := TAlignLayout.Bottom;
@@ -3321,7 +3332,9 @@ begin
except except
// Pas d'autofocus continu : l'image restera nette a bonne distance. // Pas d'autofocus continu : l'image restera nette a bonne distance.
end; end;
FScanLbl.Text := 'Point the camera at the QR shown on the PC';
FCam.Active := True; FCam.Active := True;
FScanWatch.Enabled := True;
Log('QR scan started'); Log('QR scan started');
end; end;
@@ -3329,6 +3342,7 @@ procedure TForm8.StopScan;
begin begin
if not FScanning then Exit; if not FScanning then Exit;
FScanning := False; FScanning := False;
if FScanWatch <> nil then FScanWatch.Enabled := False;
if FCam <> nil then FCam.Active := False; if FCam <> nil then FCam.Active := False;
if FScanLay <> nil then FScanLay.Visible := False; if FScanLay <> nil then FScanLay.Visible := False;
FLayWeb.Visible := True; FLayWeb.Visible := True;
@@ -3339,6 +3353,16 @@ begin
StopScan; StopScan;
end; end;
procedure TForm8.OnScanWatch(Sender: TObject);
begin
FScanWatch.Enabled := False;
if FScanning and (FScanFrame = 0) then
begin
FScanLbl.Text := 'Camera busy - close the camera app, then Cancel and scan again';
Log('QR scan: no camera frame after 3 s');
end;
end;
/// Une image sur quatre part au decodage, jamais deux a la fois : ZXing prend /// Une image sur quatre part au decodage, jamais deux a la fois : ZXing prend
/// quelques dizaines de ms, et empiler les images figerait l'apercu. /// quelques dizaines de ms, et empiler les images figerait l'apercu.
procedure TForm8.OnCamFrame(Sender: TObject; const ATime: TMediaTime); procedure TForm8.OnCamFrame(Sender: TObject; const ATime: TMediaTime);
@@ -3360,17 +3384,30 @@ begin
var var
LMgr: TScanManager; LMgr: TScanManager;
LRes: TReadResult; LRes: TReadResult;
LText: string; LText, LDiag: string;
LHints: TDictionary<TDecodeHintType, TObject>;
LW, LH: Integer;
begin begin
LText := ''; LText := '';
LDiag := '';
LRes := nil; LRes := nil;
LMgr := TScanManager.Create(TBarcodeFormat.QR_CODE, nil); LW := LBmp.Width;
LH := LBmp.Height;
// TRY_HARDER : le chercheur de motifs balaie chaque ligne au lieu
// d'en sauter - indispensable pour un QR photographie sur un ecran.
LHints := TDictionary<TDecodeHintType, TObject>.Create;
LHints.Add(TDecodeHintType.TRY_HARDER, nil);
LMgr := TScanManager.Create(TBarcodeFormat.QR_CODE, LHints); // libere les indices
try try
try try
LRes := LMgr.Scan(LBmp); LRes := LMgr.Scan(LBmp);
if LRes <> nil then LText := LRes.Text; if LRes <> nil then LText := LRes.Text;
except except
LText := ''; on E: Exception do
begin
LText := '';
LDiag := E.ClassName + ': ' + E.Message;
end;
end; end;
finally finally
LRes.Free; LRes.Free;
@@ -3381,6 +3418,11 @@ begin
procedure procedure
begin begin
FScanBusy := False; FScanBusy := False;
// DIAG scan (temporaire) : les premiers essais, puis un sur vingt.
if (FScanFrame <= 40) or (FScanFrame mod 80 = 0) then
Log(Format('QR scan frame %d: %dx%d, %s', [FScanFrame, LW, LH,
IfThen(LDiag <> '', LDiag,
IfThen(LText = '', 'nothing found', 'read ' + IntToStr(Length(LText)) + ' chars'))]));
if (not FScanning) or (LText = '') then Exit; if (not FScanning) or (LText = '') then Exit;
if StartsText('pmserver://sync?', LText) then if StartsText('pmserver://sync?', LText) then
begin begin