This repository has been archived on 2023-09-06. You can view files and clone it, but cannot push or open issues/pull-requests.
yutent
/
py-gtk-notes
Archived
1
0
Fork 0

增加剪切板操作和图片操作的笔记

master
yutent 2023-07-25 18:53:52 +08:00
parent af29a63061
commit a57caef220
8 changed files with 314 additions and 23 deletions

46
app.js
View File

@ -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
}
})

BIN
debian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

5
demo.js Normal file
View File

@ -0,0 +1,5 @@
import fs from 'iofs'
let buf = fs.cat('./debian.png')
console.log(buf)
console.log([...buf], buf.byteLength)

View File

@ -16,6 +16,9 @@
}
}
</script>
<style>
#output {max-width:100%;white-space: pre-wrap;word-break: break-all;}
</style>
</head>
<body>
@ -26,6 +29,14 @@
<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>
</body>

107
inject.js
View File

@ -3,6 +3,40 @@
* @author yutent<yutent.io@gmail.com>
* @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
})

87
main.py
View File

@ -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
@ -70,25 +74,94 @@ class WebKitWindow(Gtk.Window):
def file_path(self, filepath):
root = os.path.dirname(os.path.realpath(__file__))
return os.path.join(root, filepath)
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()

41
notes/clipboard.py Normal file
View File

@ -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()

40
notes/image.py Normal file
View File

@ -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']
)