From a57caef220db6671acb9548127792e13699c0744 Mon Sep 17 00:00:00 2001 From: yutent Date: Tue, 25 Jul 2023 18:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=89=AA=E5=88=87=E6=9D=BF?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=92=8C=E5=9B=BE=E7=89=87=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E7=9A=84=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 46 +++++++++++++------ debian.png | Bin 0 -> 3290 bytes demo.js | 5 +++ index.html | 11 +++++ inject.js | 107 ++++++++++++++++++++++++++++++++++++++++++++- main.py | 87 ++++++++++++++++++++++++++++++++---- notes/clipboard.py | 41 +++++++++++++++++ notes/image.py | 40 +++++++++++++++++ 8 files changed, 314 insertions(+), 23 deletions(-) create mode 100644 debian.png create mode 100644 demo.js create mode 100644 notes/clipboard.py create mode 100644 notes/image.py diff --git a/app.js b/app.js index 2bde6c8..a6254b7 100644 --- a/app.js +++ b/app.js @@ -7,22 +7,42 @@ import 'es.shim' import { $, bind } from 'wkit' -function test1() { - try { - window.webkit.messageHandlers.app.postMessage({ - data: { foo: 'Test 1' } - }) - $('#output').innerHTML = '这是没有回调的 ' - } catch (err) { - alert(err) - } +async function test1() { + let txt = await native.clipboard.readText() + $('input').value = txt } -function test2() { - native.handler('blabla', { foo: 'bar' }).then(r => { - $('#output').innerHTML = JSON.stringify(r) - }) +async function test2() { + native.clipboard.writeText('这是一段写进剪切板的文本') + // native.handler('blabla', { foo: 'bar' }).then(r => { + // $('#output').innerHTML = JSON.stringify(r) + // }) } bind($('.btn1'), 'click', test1) bind($('.btn2'), 'click', test2) +bind($('.btn3'), 'click', async function () { + // window.open('about:blank') + // let img = await native.clipboard.writeImage('/code/gtk/webkit/debian.png') + let img = await native.image('/code/gtk/webkit/debian.png') + native.clipboard.writeImage(img) + // native.clipboard.writeImage('/code/gtk/webkit/debian.png') + try { + $('img').src = URL.createObjectURL(await img.export()) + } catch (err) { + alert(err) + } +}) +bind($('.btn4'), 'click', async function () { + native.quit() + // native.clipboard.clear() +}) + +bind($('textarea'), 'paste', async function (ev) { + let items = ev.clipboardData.items + for (let it of items) { + let file = it.getAsFile() + $('img').src = URL.createObjectURL(file) + break + } +}) diff --git a/debian.png b/debian.png new file mode 100644 index 0000000000000000000000000000000000000000..249de87c150154ad1a7a376307bef6f5d37fc46a GIT binary patch literal 3290 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 Y2ezx0=p|IsG5`Po07*qoM6N<$f>5*>CIA2c literal 0 HcmV?d00001 diff --git a/demo.js b/demo.js new file mode 100644 index 0000000..3b501dd --- /dev/null +++ b/demo.js @@ -0,0 +1,5 @@ +import fs from 'iofs' + +let buf = fs.cat('./debian.png') +console.log(buf) +console.log([...buf], buf.byteLength) diff --git a/index.html b/index.html index 5312038..7b4e8b5 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,9 @@ } } + @@ -25,6 +28,14 @@ 打开控制台
loading...
+ + + +
+ + + + diff --git a/inject.js b/inject.js index 0840957..a3e7149 100644 --- a/inject.js +++ b/inject.js @@ -3,6 +3,40 @@ * @author yutent * @date 2023/07/21 17:38:11 */ +const MIME_TYPES = { + html: 'text/html', + json: 'application/json', + js: 'application/javascript', + htm: 'text/html', + txt: 'text/plain', + css: 'text/css', + webp: 'image/webp', + jpg: 'image/jpg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + svg: 'image/svg+xml', + ico: 'image/ico', + mp3: 'audio/mpeg', + ogg: 'audio/ogg', + m4a: 'audio/m4a', + amr: 'audio/amr', + mp4: 'video/mp4', + webm: 'video/webm', + wasm: 'application/wasm', + asm: 'application/asm', + zip: 'application/zip', + '7z': 'application/x-7z-compressed', + eot: 'application/vnd.ms-fontobject', + ttf: 'font/ttf', + otf: 'font/otf', + woff: 'font/woff', + woff2: 'font/woff2', + xls: 'application/vnd.ms-excel', + doc: 'application/msword', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' +} function defer() { let obj = {} @@ -19,8 +53,14 @@ function rand(prefix = 'cb_') { function handler(event, data = {}, once = true) { let _ = defer() - let callback = rand() - native[once ? '$once' : '$on'](callback, _.resolve) + let callback + + if (typeof once === 'boolean') { + callback = rand() + native[once ? '$once' : '$on'](callback, _.resolve) + } else { + _.resolve(true) + } window.webkit.messageHandlers.app.postMessage({ event, data, @@ -29,6 +69,43 @@ function handler(event, data = {}, once = true) { return _.promise } +class NativeImage { + #origin + + constructor(obj) { + this.#origin = obj + this.width = obj.width + this.height = obj.height + this.type = MIME_TYPES[obj.filepath.split('.').pop()] + } + + toPixbuf() { + return this.#origin + } + + export(type) { + let _ = defer() + let canvas = document.createElement('canvas') + canvas.width = this.width + canvas.height = this.height + let ctx = canvas.getContext('2d') + let imgData = ctx.getImageData(0, 0, this.width, this.height) + let data = imgData.data + + for (let i = 0; i < this.#origin.bytes.length; i += 4) { + imgData.data[i] = this.#origin.bytes[i] + imgData.data[i + 1] = this.#origin.bytes[i + 1] + imgData.data[i + 2] = this.#origin.bytes[i + 2] + imgData.data[i + 3] = this.#origin.bytes[i + 3] + } + + ctx.putImageData(imgData, 0, 0) + canvas.toBlob(_.resolve, type || this.type, 1) + + return _.promise + } +} + class EventEmitter { // __events__ = Object.create(null) @@ -79,8 +156,34 @@ class EventEmitter { window.native = new EventEmitter() Object.assign(native, { + quit() { + return handler('quit') + }, fs: { read(filepath) {} }, + image(filepath) { + return handler('image', { value: filepath }).then(r => new NativeImage(r)) + }, + clipboard: { + readText() { + return handler('clipboard', { action: 'wait_for_text' }) + }, + writeText(value) { + return handler('clipboard', { action: 'set_text', value }, null) + }, + readImage() { + return handler('clipboard', { action: 'wait_for_image' }) + }, + writeImage(value) { + if (typeof value === 'object') { + value = value.toPixbuf() + } + return handler('clipboard', { action: 'set_image', value }, null) + }, + clear() { + return handler('clipboard', { action: 'clear' }) + } + }, handler }) diff --git a/main.py b/main.py index dd51dda..b9183a1 100755 --- a/main.py +++ b/main.py @@ -2,10 +2,12 @@ import gi, json, os + gi.require_version("Gtk", "3.0") gi.require_version("WebKit2", "4.1") -from gi.repository import Gtk, WebKit2 +from gi.repository import Gtk, Gdk, WebKit2, GLib +from gi.repository.GdkPixbuf import Pixbuf class WebKitWindow(Gtk.Window): @@ -46,6 +48,9 @@ class WebKitWindow(Gtk.Window): # self.webview.load_uri("https://benchmark.wkit.fun") + self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + + self.add(self.webview) @@ -54,7 +59,6 @@ class WebKitWindow(Gtk.Window): def create_tray(self): indicator = Gtk.StatusIcon.new_from_icon_name('youtube') - indicator.connect('activate', self.toggle_visible) return indicator @@ -68,6 +72,8 @@ class WebKitWindow(Gtk.Window): else: self.present() + + def file_path(self, filepath): @@ -76,19 +82,86 @@ class WebKitWindow(Gtk.Window): def on_script_message(self, webview, message): - data = message.get_js_value() - data = json.loads(data.to_json(0)) - print('这是py收到的值: ',data) + + data = json.loads(message.get_js_value().to_json(0)) + event = data.get('event') callback = data.get('callback') - res = {"foo": 123, "bar": (11,22,33)} + params = data.get('data') + match event: case 'fs': pass + case 'clipboard': + output = None + + # 读文本 + if params['action'] == 'wait_for_text': + output = self.clipboard.wait_for_text() + + # 写文本 + elif params['action'] == 'set_text': + self.clipboard.set_text(params['value'], -1) + + # 写图片 + elif params['action'] == 'set_image': + image = params['value'] + # 前端传进来的值, 如果是路径的话, 直接读取 + if type(image) == str: + image = Pixbuf.new_from_file(image) + else: + image = Pixbuf.new_from_data( + data = bytes(image['bytes']), + colorspace = image['colorspace'], + has_alpha = image['has_alpha'], + bits_per_sample = image['bits_per_sample'], + width = image['width'], + height = image['height'], + rowstride = image['rowstride'] + ) + + self.clipboard.set_image(image) + self.clipboard.store() + + # 清除剪切板 + elif params['action'] == 'clear': + self.clipboard.clear() + + # 回调给前端 + if callback and output: + scripts = 'native.$emit("' + callback + '",' + json.dumps(output) + ')' + print(scripts) + self.webview.evaluate_javascript(scripts, -1) + + # 退出app + case 'quit': + Gtk.main_quit() + + # 读取图片, 返回图片像素数据 + case 'image': + filename = params['value'] + pixbuf = Pixbuf.new_from_file(filename) + image = { + "width": pixbuf.get_width(), + "height": pixbuf.get_height(), + "colorspace": pixbuf.get_colorspace(), + "has_alpha": pixbuf. get_has_alpha(), + "bits_per_sample": pixbuf.get_bits_per_sample(), + "rowstride": pixbuf.get_rowstride(), + "filepath": filename, + "bytes": list(pixbuf.get_pixels()) + } + + + scripts = 'native.$emit("' + callback + '",' + json.dumps(image) + ')' + + self.webview.evaluate_javascript(scripts, -1) + case _: if callback : + res = {"foo": 123, "bar": (11,22,33)} scripts = 'native.$emit("' + callback + '",' + json.dumps(res) + ')' print(scripts) self.webview.evaluate_javascript(scripts, -1) @@ -115,6 +188,4 @@ win.show_all() tray = win.create_tray() -print(tray) - Gtk.main() \ No newline at end of file diff --git a/notes/clipboard.py b/notes/clipboard.py new file mode 100644 index 0000000..c5808da --- /dev/null +++ b/notes/clipboard.py @@ -0,0 +1,41 @@ + +import gi + +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, Gdk +from gi.repository.GdkPixbuf import Pixbuf + +clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + + +# 读剪切板中的文本 +output = clipboard.wait_for_text() + +# 写文本进剪切板 +clipboard.set_text('blabla', -1) + + + +# 从文件中读取 +filepath = 'xxx.png' +image = Pixbuf.new_from_file(filepath) + +# 从其他地方拿到的图片像素对象 +image = Pixbuf.new_from_data( + data = bytes(image['bytes']), + colorspace = image['colorspace'], + has_alpha = image['has_alpha'], + bits_per_sample = image['bits_per_sample'], + width = image['width'], + height = image['height'], + rowstride = image['rowstride'] +) + +clipboard.set_image(image) +clipboard.store() + + + +# 清除 +clipboard.clear() \ No newline at end of file diff --git a/notes/image.py b/notes/image.py new file mode 100644 index 0000000..a63b25f --- /dev/null +++ b/notes/image.py @@ -0,0 +1,40 @@ +import gi + +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, Gdk +from gi.repository.GdkPixbuf import Pixbuf + +# 读取图片, 返回图片像素数据 +filename = 'xxx.png' +pixbuf = Pixbuf.new_from_file(filename) + +# 得到这个对象, 就是可以传给前端的, + +image = { + "width": pixbuf.get_width(), + "height": pixbuf.get_height(), + "colorspace": pixbuf.get_colorspace(), + "has_alpha": pixbuf. get_has_alpha(), + "bits_per_sample": pixbuf.get_bits_per_sample(), + "rowstride": pixbuf.get_rowstride(), + "filepath": filename, + "bytes": list(pixbuf.get_pixels()) +} + +# scripts = 'native.$emit("' + callback + '",' + json.dumps(image) + ')' + +# 倒扣到前端传回来的数据, 调用 Pixbuf.new_from_data 可转回 Pixbuf对象 +pixbuf = Pixbuf.new_from_data( + data = bytes(image['bytes']), + colorspace = image['colorspace'], + has_alpha = image['has_alpha'], + bits_per_sample = image['bits_per_sample'], + width = image['width'], + height = image['height'], + rowstride = image['rowstride'] +) + + + +