Skip to content

Commit 3d67548

Browse files
authored
Merge pull request #726 from blackbartblues/fix/clipper-v2.4.1-qt-btoa
fix(clipper): replace deprecated Qt.btoa(string) with array-like overload (v2.4.1)
2 parents 8c4a23d + e3ac901 commit 3d67548

3 files changed

Lines changed: 53 additions & 11 deletions

File tree

clipper/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Clipper Plugin - Comprehensive Changelog
22

3+
## Version 2.4.1 (2026-04-21)
4+
5+
### Fix Qt.btoa deprecation warnings
6+
7+
- Replaced three deprecated `Qt.btoa(string)` calls in `Main.qml` (`savePinnedFile`, `exportNoteCard`, `saveNoteCard`) with a new `stringToBase64(str)` helper that encodes the string to UTF-8 bytes as a `Uint8Array` and passes it to the non-deprecated `Qt.btoa(array-like)` overload.
8+
- Simplified the matching `Qt.atob()` call: the array-like overload returns a `Uint8Array` directly, so the intermediate string + `charCodeAt` loop was removed.
9+
- Side benefit: `exportNoteCard()` now writes properly encoded UTF-8 `.txt` files — previously, notes containing non-ASCII characters (Polish, emoji, Cyrillic, etc.) were written with Latin-1 byte encoding.
10+
- No behavior change for existing `pinned.json` / `notecards/*.json` files: `JSON.stringify` already escapes non-ASCII to `\uNNNN`, so the base64 output is effectively identical for ASCII-safe JSON.
11+
312
## Version 2.4.0 (2026-04-20)
413

514
### Live-Preview Settings, Panel Size Controls, i18n Cleanup

clipper/Main.qml

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,42 @@ Item {
350350
}
351351
}
352352

353+
// Helper: encode a UTF-8 string to base64 using the non-deprecated Qt.btoa(array-like) overload.
354+
// Qt.btoa(string) is deprecated since Qt 6.8 and its output differs for non-ASCII characters
355+
// (Latin-1 byte-per-char vs proper UTF-8). This helper encodes to UTF-8 bytes first.
356+
function stringToBase64(str) {
357+
var s = String(str);
358+
var bytes = [];
359+
for (var i = 0; i < s.length; i++) {
360+
var code = s.charCodeAt(i);
361+
if (code < 0x80) {
362+
bytes.push(code);
363+
} else if (code < 0x800) {
364+
bytes.push(0xC0 | (code >> 6));
365+
bytes.push(0x80 | (code & 0x3F));
366+
} else if (code >= 0xD800 && code <= 0xDBFF && i + 1 < s.length) {
367+
// UTF-16 surrogate pair → Unicode code point
368+
var high = code;
369+
var low = s.charCodeAt(i + 1);
370+
if (low >= 0xDC00 && low <= 0xDFFF) {
371+
var cp = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00);
372+
bytes.push(0xF0 | (cp >> 18));
373+
bytes.push(0x80 | ((cp >> 12) & 0x3F));
374+
bytes.push(0x80 | ((cp >> 6) & 0x3F));
375+
bytes.push(0x80 | (cp & 0x3F));
376+
i++;
377+
} else {
378+
bytes.push(0xEF, 0xBF, 0xBD); // U+FFFD replacement character for lone surrogate
379+
}
380+
} else {
381+
bytes.push(0xE0 | (code >> 12));
382+
bytes.push(0x80 | ((code >> 6) & 0x3F));
383+
bytes.push(0x80 | (code & 0x3F));
384+
}
385+
}
386+
return Qt.btoa(new Uint8Array(bytes));
387+
}
388+
353389
// Function to save pinned items to file
354390
function savePinnedFile() {
355391
const data = {
@@ -358,9 +394,9 @@ Item {
358394
const json = JSON.stringify(data, null, 2);
359395

360396
// Use base64 encoding to safely pass JSON through shell
361-
// Qt.btoa() produces valid base64 (A-Z, a-z, 0-9, +, /, =) - no shell metacharacters
397+
// stringToBase64() produces valid base64 (A-Z, a-z, 0-9, +, /, =) - no shell metacharacters
362398
// File path is constant, not user-controlled
363-
const base64 = Qt.btoa(json);
399+
const base64 = stringToBase64(json);
364400
const filePath = Quickshell.env("HOME") + "/.config/noctalia/plugins/clipper/pinned.json";
365401

366402
Quickshell.execDetached(["sh", "-c", `echo "${base64}" | base64 -d > "${filePath}"`]);
@@ -531,7 +567,7 @@ Item {
531567
const filePath = Quickshell.env("HOME") + "/Documents/" + fileName;
532568

533569
// Use base64 encoding to safely pass content through shell
534-
const base64 = Qt.btoa(note.content || "");
570+
const base64 = stringToBase64(note.content || "");
535571
Quickshell.execDetached(["sh", "-c", `echo "${base64}" | base64 -d > "${filePath}"`]);
536572

537573
// Store exported filename - append to list so all exports are tracked
@@ -575,7 +611,7 @@ Item {
575611
const json = JSON.stringify(note, null, 2);
576612

577613
// Use base64 encoding to safely pass JSON through shell
578-
const base64 = Qt.btoa(json);
614+
const base64 = stringToBase64(json);
579615
Quickshell.execDetached(["sh", "-c", `echo "${base64}" | base64 -d > "${filePath}"`]);
580616
}
581617

@@ -662,12 +698,9 @@ Item {
662698
const mimeType = matches[1];
663699
const base64Data = matches[2];
664700

665-
// Decode base64 to binary in JavaScript (no shell commands)
666-
const binaryStr = Qt.atob(base64Data);
667-
const bytes = new Uint8Array(binaryStr.length);
668-
for (let i = 0; i < binaryStr.length; i++) {
669-
bytes[i] = binaryStr.charCodeAt(i);
670-
}
701+
// Decode base64 to binary bytes (no shell commands).
702+
// Qt.atob() with array-like overload returns a Uint8Array directly (non-deprecated form).
703+
const bytes = new Uint8Array(Qt.atob(base64Data));
671704

672705
// Copy binary data directly via Process stdin
673706
copyPinnedImageProc.running = true;

clipper/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "clipper",
33
"name": "Clipper",
4-
"version": "2.4.0",
4+
"version": "2.4.1",
55
"minNoctaliaVersion": "4.1.2",
66
"author": "blackbartblues",
77
"contributors": [

0 commit comments

Comments
 (0)