增加剪切板操作和图片操作的笔记
parent
af29a63061
commit
a57caef220
46
app.js
46
app.js
|
@ -7,22 +7,42 @@
|
||||||
import 'es.shim'
|
import 'es.shim'
|
||||||
import { $, bind } from 'wkit'
|
import { $, bind } from 'wkit'
|
||||||
|
|
||||||
function test1() {
|
async function test1() {
|
||||||
try {
|
let txt = await native.clipboard.readText()
|
||||||
window.webkit.messageHandlers.app.postMessage({
|
$('input').value = txt
|
||||||
data: { foo: 'Test 1' }
|
|
||||||
})
|
|
||||||
$('#output').innerHTML = '这是没有回调的 '
|
|
||||||
} catch (err) {
|
|
||||||
alert(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function test2() {
|
async function test2() {
|
||||||
native.handler('blabla', { foo: 'bar' }).then(r => {
|
native.clipboard.writeText('这是一段写进剪切板的文本')
|
||||||
$('#output').innerHTML = JSON.stringify(r)
|
// native.handler('blabla', { foo: 'bar' }).then(r => {
|
||||||
})
|
// $('#output').innerHTML = JSON.stringify(r)
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
bind($('.btn1'), 'click', test1)
|
bind($('.btn1'), 'click', test1)
|
||||||
bind($('.btn2'), 'click', test2)
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
import fs from 'iofs'
|
||||||
|
|
||||||
|
let buf = fs.cat('./debian.png')
|
||||||
|
console.log(buf)
|
||||||
|
console.log([...buf], buf.byteLength)
|
11
index.html
11
index.html
|
@ -16,6 +16,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
#output {max-width:100%;white-space: pre-wrap;word-break: break-all;}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
@ -25,6 +28,14 @@
|
||||||
<a target="_blank" href="about:blank">打开控制台</a>
|
<a target="_blank" href="about:blank">打开控制台</a>
|
||||||
|
|
||||||
<div id="output">loading...</div>
|
<div id="output">loading...</div>
|
||||||
|
|
||||||
|
<input type="text">
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<button class="btn3">写图片到剪切板</button>
|
||||||
|
<button class="btn4">退出</button>
|
||||||
|
<img style="width:100px;border:1px solid #09f;" src="" alt="">
|
||||||
|
<textarea></textarea>
|
||||||
|
|
||||||
<script type="module" src="/app.js"></script>
|
<script type="module" src="/app.js"></script>
|
||||||
|
|
||||||
|
|
107
inject.js
107
inject.js
|
@ -3,6 +3,40 @@
|
||||||
* @author yutent<yutent.io@gmail.com>
|
* @author yutent<yutent.io@gmail.com>
|
||||||
* @date 2023/07/21 17:38:11
|
* @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() {
|
function defer() {
|
||||||
let obj = {}
|
let obj = {}
|
||||||
|
@ -19,8 +53,14 @@ function rand(prefix = 'cb_') {
|
||||||
|
|
||||||
function handler(event, data = {}, once = true) {
|
function handler(event, data = {}, once = true) {
|
||||||
let _ = defer()
|
let _ = defer()
|
||||||
let callback = rand()
|
let callback
|
||||||
native[once ? '$once' : '$on'](callback, _.resolve)
|
|
||||||
|
if (typeof once === 'boolean') {
|
||||||
|
callback = rand()
|
||||||
|
native[once ? '$once' : '$on'](callback, _.resolve)
|
||||||
|
} else {
|
||||||
|
_.resolve(true)
|
||||||
|
}
|
||||||
window.webkit.messageHandlers.app.postMessage({
|
window.webkit.messageHandlers.app.postMessage({
|
||||||
event,
|
event,
|
||||||
data,
|
data,
|
||||||
|
@ -29,6 +69,43 @@ function handler(event, data = {}, once = true) {
|
||||||
return _.promise
|
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 {
|
class EventEmitter {
|
||||||
//
|
//
|
||||||
__events__ = Object.create(null)
|
__events__ = Object.create(null)
|
||||||
|
@ -79,8 +156,34 @@ class EventEmitter {
|
||||||
window.native = new EventEmitter()
|
window.native = new EventEmitter()
|
||||||
|
|
||||||
Object.assign(native, {
|
Object.assign(native, {
|
||||||
|
quit() {
|
||||||
|
return handler('quit')
|
||||||
|
},
|
||||||
fs: {
|
fs: {
|
||||||
read(filepath) {}
|
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
|
handler
|
||||||
})
|
})
|
||||||
|
|
87
main.py
87
main.py
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
import gi, json, os
|
import gi, json, os
|
||||||
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
gi.require_version("WebKit2", "4.1")
|
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):
|
class WebKitWindow(Gtk.Window):
|
||||||
|
@ -46,6 +48,9 @@ class WebKitWindow(Gtk.Window):
|
||||||
# self.webview.load_uri("https://benchmark.wkit.fun")
|
# self.webview.load_uri("https://benchmark.wkit.fun")
|
||||||
|
|
||||||
|
|
||||||
|
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
self.add(self.webview)
|
self.add(self.webview)
|
||||||
|
@ -54,7 +59,6 @@ class WebKitWindow(Gtk.Window):
|
||||||
|
|
||||||
def create_tray(self):
|
def create_tray(self):
|
||||||
indicator = Gtk.StatusIcon.new_from_icon_name('youtube')
|
indicator = Gtk.StatusIcon.new_from_icon_name('youtube')
|
||||||
|
|
||||||
indicator.connect('activate', self.toggle_visible)
|
indicator.connect('activate', self.toggle_visible)
|
||||||
return indicator
|
return indicator
|
||||||
|
|
||||||
|
@ -68,6 +72,8 @@ class WebKitWindow(Gtk.Window):
|
||||||
else:
|
else:
|
||||||
self.present()
|
self.present()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def file_path(self, filepath):
|
def file_path(self, filepath):
|
||||||
|
@ -76,19 +82,86 @@ class WebKitWindow(Gtk.Window):
|
||||||
|
|
||||||
|
|
||||||
def on_script_message(self, webview, message):
|
def on_script_message(self, webview, message):
|
||||||
data = message.get_js_value()
|
|
||||||
data = json.loads(data.to_json(0))
|
data = json.loads(message.get_js_value().to_json(0))
|
||||||
print('这是py收到的值: ',data)
|
|
||||||
event = data.get('event')
|
event = data.get('event')
|
||||||
callback = data.get('callback')
|
callback = data.get('callback')
|
||||||
res = {"foo": 123, "bar": (11,22,33)}
|
params = data.get('data')
|
||||||
|
|
||||||
|
|
||||||
match event:
|
match event:
|
||||||
case 'fs':
|
case 'fs':
|
||||||
pass
|
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 _:
|
case _:
|
||||||
if callback :
|
if callback :
|
||||||
|
res = {"foo": 123, "bar": (11,22,33)}
|
||||||
scripts = 'native.$emit("' + callback + '",' + json.dumps(res) + ')'
|
scripts = 'native.$emit("' + callback + '",' + json.dumps(res) + ')'
|
||||||
print(scripts)
|
print(scripts)
|
||||||
self.webview.evaluate_javascript(scripts, -1)
|
self.webview.evaluate_javascript(scripts, -1)
|
||||||
|
@ -115,6 +188,4 @@ win.show_all()
|
||||||
|
|
||||||
tray = win.create_tray()
|
tray = win.create_tray()
|
||||||
|
|
||||||
print(tray)
|
|
||||||
|
|
||||||
Gtk.main()
|
Gtk.main()
|
|
@ -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()
|
|
@ -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']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in New Issue