mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00
Go back to using QClipboard
This commit is contained in:
parent
0bdeab974f
commit
dd673851db
3 changed files with 73 additions and 42 deletions
|
@ -46,6 +46,10 @@ service FrontendService {
|
|||
// Metadata
|
||||
rpc GetMetaJson(generic.String) returns (generic.Json);
|
||||
rpc SetMetaJson(SetSettingJsonRequest) returns (generic.Empty);
|
||||
|
||||
// Clipboard
|
||||
rpc ReadClipboard(ReadClipboardRequest) returns (ReadClipboardResponse);
|
||||
rpc WriteClipboard(WriteClipboardRequest) returns (generic.Empty);
|
||||
}
|
||||
|
||||
service BackendFrontendService {}
|
||||
|
@ -85,3 +89,15 @@ message openFilePickerRequest {
|
|||
string filter_description = 3;
|
||||
repeated string extensions = 4;
|
||||
}
|
||||
|
||||
message ReadClipboardRequest {
|
||||
repeated string types = 1;
|
||||
}
|
||||
|
||||
message ReadClipboardResponse {
|
||||
map<string, bytes> data = 1;
|
||||
}
|
||||
|
||||
message WriteClipboardRequest {
|
||||
map<string, bytes> data = 1;
|
||||
}
|
|
@ -761,6 +761,28 @@ async def record_audio() -> bytes:
|
|||
return generic_pb2.String(val=path if path else "").SerializeToString()
|
||||
|
||||
|
||||
def read_clipboard() -> bytes:
|
||||
req = frontend_pb2.ReadClipboardRequest()
|
||||
req.ParseFromString(request.data)
|
||||
data = {}
|
||||
clipboard = aqt.mw.app.clipboard()
|
||||
mime_data = clipboard.mimeData(QClipboard.Mode.Clipboard)
|
||||
for type in req.types:
|
||||
data[type] = bytes(mime_data.data(type))
|
||||
|
||||
return frontend_pb2.ReadClipboardResponse(data=data).SerializeToString()
|
||||
|
||||
|
||||
def write_clipboard() -> bytes:
|
||||
req = frontend_pb2.WriteClipboardRequest()
|
||||
req.ParseFromString(request.data)
|
||||
clipboard = aqt.mw.app.clipboard()
|
||||
mime_data = clipboard.mimeData(QClipboard.Mode.Clipboard)
|
||||
for type, data in req.data.items():
|
||||
mime_data.setData(type, data)
|
||||
return b""
|
||||
|
||||
|
||||
post_handler_list = [
|
||||
congrats_info,
|
||||
get_deck_configs_for_update,
|
||||
|
@ -788,6 +810,8 @@ post_handler_list = [
|
|||
open_media,
|
||||
show_in_media_folder,
|
||||
record_audio,
|
||||
read_clipboard,
|
||||
write_clipboard,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import { ConfigKey_Bool } from "@generated/anki/config_pb";
|
||||
import type { ReadClipboardResponse } from "@generated/anki/frontend_pb";
|
||||
import {
|
||||
addMediaFile,
|
||||
convertPastedImage,
|
||||
|
@ -9,7 +10,9 @@ import {
|
|||
getAbsoluteMediaPath,
|
||||
getConfigBool,
|
||||
openFilePicker,
|
||||
readClipboard,
|
||||
retrieveUrl as retrieveUrlBackend,
|
||||
writeClipboard,
|
||||
} from "@generated/backend";
|
||||
import * as tr from "@generated/ftl";
|
||||
import { shiftPressed } from "@tslib/keys";
|
||||
|
@ -41,6 +44,19 @@ const audioSuffixes = [
|
|||
"webm",
|
||||
];
|
||||
const mediaSuffixes = [...imageSuffixes, ...audioSuffixes];
|
||||
const QIMAGE_FORMATS = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/svg+xml",
|
||||
"image/bmp",
|
||||
"image/x-portable-bitmap",
|
||||
"image/x-portable-graymap",
|
||||
"image/x-portable-pixmap",
|
||||
"image/x-xbitmap",
|
||||
"image/x-xpixmap",
|
||||
];
|
||||
const URI_LIST_MIME = "text/uri-list";
|
||||
|
||||
let isShiftPressed = false;
|
||||
let lastInternalFieldText = "";
|
||||
|
@ -67,17 +83,12 @@ function escapeHtml(text: string, quote = true): string {
|
|||
return text;
|
||||
}
|
||||
|
||||
async function getUrls(data: DataTransfer | ClipboardItem): Promise<string[]> {
|
||||
const mime = "text/uri-list";
|
||||
async function getUrls(data: DataTransfer | ReadClipboardResponse): Promise<string[]> {
|
||||
let dataString: string;
|
||||
if (data instanceof DataTransfer) {
|
||||
dataString = data.getData(mime);
|
||||
dataString = data.getData(URI_LIST_MIME);
|
||||
} else {
|
||||
try {
|
||||
dataString = await (await data.getType(mime)).text();
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
dataString = new TextDecoder().decode(data.data[URI_LIST_MIME]);
|
||||
}
|
||||
const urls = dataString.split("\n");
|
||||
return urls[0] ? urls : [];
|
||||
|
@ -91,25 +102,12 @@ function getHtml(data: DataTransfer): string {
|
|||
return data.getData("text/html") ?? "";
|
||||
}
|
||||
|
||||
const QIMAGE_FORMATS = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/svg+xml",
|
||||
"image/bmp",
|
||||
"image/x-portable-bitmap",
|
||||
"image/x-portable-graymap",
|
||||
"image/x-portable-pixmap",
|
||||
"image/x-xbitmap",
|
||||
"image/x-xpixmap",
|
||||
];
|
||||
|
||||
async function getImageData(data: DataTransfer | ClipboardItem): Promise<ImageData | null> {
|
||||
async function getImageData(data: DataTransfer | ReadClipboardResponse): Promise<ImageData | null> {
|
||||
for (const type of QIMAGE_FORMATS) {
|
||||
try {
|
||||
const image = data instanceof DataTransfer
|
||||
? data.getData(type)
|
||||
: new Uint8Array(await (await data.getType(type)).arrayBuffer());
|
||||
: data.data[type];
|
||||
if (image) {
|
||||
return image;
|
||||
} else if (data instanceof DataTransfer) {
|
||||
|
@ -233,7 +231,7 @@ function isURL(s: string): boolean {
|
|||
}
|
||||
|
||||
async function processUrls(
|
||||
data: DataTransfer | ClipboardItem,
|
||||
data: DataTransfer | ReadClipboardResponse,
|
||||
_extended: Promise<boolean>,
|
||||
allowedSuffixes: string[] = mediaSuffixes,
|
||||
): Promise<string | null> {
|
||||
|
@ -419,7 +417,7 @@ export async function extractImagePathFromHtml(html: string): Promise<string | n
|
|||
}
|
||||
return decodeURI(images[0]);
|
||||
}
|
||||
export async function extractImagePathFromData(data: DataTransfer | ClipboardItem): Promise<string | null> {
|
||||
export async function extractImagePathFromData(data: DataTransfer | ReadClipboardResponse): Promise<string | null> {
|
||||
const html = await processUrls(data, Promise.resolve(false), imageSuffixes);
|
||||
if (html) {
|
||||
return await extractImagePathFromHtml(html);
|
||||
|
@ -428,26 +426,19 @@ export async function extractImagePathFromData(data: DataTransfer | ClipboardIte
|
|||
}
|
||||
|
||||
export async function readImageFromClipboard(): Promise<string | null> {
|
||||
// TODO: check browser support and available formats
|
||||
for (const item of await navigator.clipboard.read()) {
|
||||
let path = await extractImagePathFromData(item);
|
||||
const data = await readClipboard({ types: QIMAGE_FORMATS.concat(URI_LIST_MIME) });
|
||||
let path = await extractImagePathFromData(data);
|
||||
if (!path) {
|
||||
const image = await getImageData(item);
|
||||
const image = await getImageData(data);
|
||||
if (!image) {
|
||||
continue;
|
||||
return null;
|
||||
}
|
||||
const ext = await getPreferredImageExtension();
|
||||
path = await addPastedImage(image, ext, true);
|
||||
}
|
||||
return (await getAbsoluteMediaPath({ val: path })).val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function writeBlobToClipboard(blob: Blob) {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob,
|
||||
}),
|
||||
]);
|
||||
await writeClipboard({ data: { [blob.type]: new Uint8Array(await blob.arrayBuffer()) } });
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue