master
yutent 2023-08-31 16:57:55 +08:00
commit 7efeeed484
13 changed files with 1090 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
*.txt

0
Readme.md Normal file
View File

20
build.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
if [ -d unpack ]; then
sudo rm -rf unpack
fi
version="0.1.0"
mkdir -p unpack/DEBIAN
cp debian/control unpack/DEBIAN/
cp -r usr unpack/
cd unpack
find . -type f | xargs md5sum > DEBIAN/md5sums
cd ..
dpkg-deb -b unpack/ "python3-webengine-gtk3-${version}.deb"
sudo rm -rf unpack

12
debian/control vendored Normal file
View File

@ -0,0 +1,12 @@
Package: python3-webengine-gtk3
Version: 0.1.0
Section: develop
Priority: optional
Maintainer: Yutent <yutent.io@gmail.com>
Architecture: all
Depends: python3 (>=3.10), python3-gi, gir1.2-gtk-3.0, python3-pil, python3-gi-cairo, gir1.2-gdkpixbuf-2.0
Recommends: gir1.2-ayatanaappindicator3-0.1, gir1.2-notify-0.7, gir1.2-keybinder-3.0
Installed-Size: 5644
Homepage: https://git.wkit.fun/appcat/python3-webengine-gtk3
Description: 一个傻瓜式的webview定制库.
基于webkit2封装的webview库, 提供傻瓜式的定制, 和一系列的js方法的注入, 增加前端js直接与系统交互的能力.

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python3
# @author yutent<yutent.io@gmail.com>
# @date 2023/08/31 11:55:25
from webengine.gtk3._webengine import WebEngine
from webengine.gtk3._settings import create_setting
build = (0, 1, 0)
version = '.'.join(map(str, build))

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python3
# @author yutent<yutent.io@gmail.com>
# @date 2023/08/08 15:00:27
import os, gi
gi.require_version("WebKit2", "4.1")
from gi.repository import WebKit2
class Inject:
def __init__(self, webview):
self.manager = webview.get_user_content_manager()
script = open(self.abspath('./inject.js'), 'r').read()
frame = WebKit2.UserContentInjectedFrames.ALL_FRAMES
time = WebKit2.UserScriptInjectionTime.END
script = WebKit2.UserScript(script, frame, time, None, None)
self.manager.add_script(script)
def connect(self, callback):
self.manager.connect('script-message-received::app', callback)
self.manager.register_script_message_handler('app')
def abspath(self, filepath):
root = os.path.dirname(os.path.realpath(__file__))
return os.path.join(root, filepath)

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
mime_types = {
'html': 'text/html',
'txt': 'text/plain',
'css': 'text/css',
'xml': 'text/xml',
'gif': 'image/gif',
'jpg': 'image/jpeg',
'webp': 'image/webp',
'tiff': 'image/tiff',
'png': 'image/png',
'svg': 'image/svg+xml',
'ico': 'image/x-icon',
'bmp': 'image/x-ms-bmp',
'js': 'application/javascript',
'json': 'application/json',
'mp3': 'audio/mpeg',
'ogg': 'audio/ogg',
'm4a': 'audio/x-m4a',
'mp4': 'video/mp4',
'webm': 'video/webm',
'ttf': 'font/font-ttf',
'woff': 'font/font-woff',
'woff2': 'font/font-woff2',
'wast': 'application/wast',
'wasm': 'application/wasm',
'other': 'application/octet-stream'
}
def get_mimetype(name):
return mime_types.get(name) or mime_types['html']

View File

@ -0,0 +1,56 @@
import gi
try:
gi.require_version('Notify', '0.7') # gir1.2-notify-0.7
from gi.repository import Notify
except:
Notify = None
class Notification():
notify = None
def __init__(self, webview):
self.webview = webview
if Notify:
Notify.init(Notify.get_app_name() or 'webapp')
self.notify = Notify.Notification()
def create(self, title, summary, icon, progress = 0, urgency = 0, callback = None):
if not self.notify:
raise ImportError('Notify module not found. Need to install gir1.2-notify-0.7 if you use debian.')
return
self.notify.clear_actions()
self.notify.clear_hints()
self.notify.update(title, summary, icon)
if progress:
self.notify.set_hint('value', progress)
self.notify.set_urgency(urgency)
if callback:
self.notify.add_action("click", "click", self.action_callback, callback)
self.notify.show()
def action_callback(self, instance, action, callback):
if callback:
self.webview.call_js(callback)
instance.close()
def create_notify():
def wrapper(app, extra = None):
Notification(app)
return wrapper

View File

@ -0,0 +1,56 @@
import os
from gi.repository import Gio, WebKit2
from ._mimetypes import get_mimetype
class Protocal:
root = ''
def __init__(self, webroot = ''):
self.root = os.path.realpath(webroot)
def register(self, webview, name = 'app'):
self.protocal = name
ctx = webview.get_context()
ctx.register_uri_scheme(name, self.handle_response)
# 允许网页通知权限
ctx.initialize_notification_permissions([WebKit2.SecurityOrigin.new_for_uri('app:///index.html')], [])
def abspath(self, filepath):
return os.path.join(self.root, filepath)
def handle_response(self, req):
schema = req.get_scheme()
pathname = req.get_path()[1:]
ext = pathname.split('.')[-1]
# print('----------------------------------------')
# print(req.get_uri(),schema, pathname, ext, get_mimetype(ext))
# print('----------------------------------------')
if schema == self.protocal:
data = open(self.abspath(pathname)).read()
data = Gio.MemoryInputStream.new_from_data(data.encode())
# ------- 更多功能的reponse ----------------
# res = WebKit2.URISchemeResponse.new(data, -1)
# res.set_content_type(get_mimetype(ext))
# res.set_http_headers('text/html')
# res.set_status(200)
# req.finish_with_response(res)
# ----------------------------------------
# 简单的response
req.finish(data, -1, get_mimetype(ext))
def create_protocal(root):
proto = Protocal(root)
def wrapper(app, extra = None):
proto.register(app)
return wrapper

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# @author yutent<yutent.io@gmail.com>
# @date 2023/08/08 14:07:26
import gi, os
gi.require_version("WebKit2", "4.1")
from gi.repository import WebKit2
class Settings(WebKit2.Settings):
def __init__(self):
WebKit2.Settings.__init__(self)
self.set_enable_page_cache(True)
self.set_enable_offline_web_application_cache(True)
self.set_javascript_can_access_clipboard(True)
self.set_javascript_can_open_windows_automatically(True)
self.set_user_agent_with_application_details('WebEngine', '0.1.0')
# indexedDB 和 localStorage 和 离线缓存
self.set_enable_html5_database(True)
self.set_enable_html5_local_storage(True)
self.set_enable_offline_web_application_cache(True)
# 平滑滚动
self.set_enable_smooth_scrolling(True)
self.set_enable_fullscreen(True)
# 媒体播放是否需要 用户主动行为
self.set_media_playback_requires_user_gesture(False)
# 允许视频播放窗口不强制全屏
self.set_media_playback_allows_inline(True)
def set_useragent(self, str):
self.set_user_agent(str)
# 关闭之后, 可以随便跨域请求, 但是有安全隐患(不过, 只要保证你页面只运行你自己的代码, 及信任的代码, 就不会有任何问题)
def disable_security(self):
self.set_disable_web_security(True)
# 禁止全屏 (默认允许)
def disable_fullscreen(self):
self.set_enable_fullscreen(False)
# 启用开发者工具
def enable_devtools(self):
self.set_enable_developer_extras(True)
# 模拟设备
def mock_devices(self):
# 是用于启用或禁用模拟捕获设备的设置选项。
# 在开发网页应用程序时,经常需要使用摄像头或麦克风等捕获设备进行测试和调试。但是,在某些情况下,可能无法直接访问实际的物理捕获设备,或者不希望在开发过程中实际使用这些设备。
# 通过启用WebKit2.Settings:enable-mock-capture-devices设置选项可以使用虚拟的模拟捕获设备来替代实际的物理设备。这样开发人员可以在没有真实设备的情况下进行捕获设备相关的功能测试和调试提高开发效率并简化开发流程。
self.set_enable_mock_capture_devices(True)
def create_setting(options = None):
setting = Settings()
if options is not None:
if options.get('devtools'):
setting.enable_devtools()
if options.get('useragent'):
setting.set_useragent(options.get('useragent'))
if options.get('web_security') is False:
setting.disable_security()
if options.get('disable_fullscreen'):
setting.disable_fullscreen()
def wrapper(app, extra = None):
app.set_settings(setting)
return wrapper

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @author yutent<yutent.io@gmail.com>
# @date 2023/07/28 14:39:33
import gi
from gi.repository.GdkPixbuf import Pixbuf
def get_monitor_info(monitor):
return {
"model": monitor.props.model,
"scale_factor": monitor.props.scale_factor,
"manufacturer": monitor.props.manufacturer,
"refresh_rate": monitor.props.refresh_rate,
"is_primary": monitor.is_primary(),
"geometry": {
"width": monitor.props.geometry.width,
"height": monitor.props.geometry.height,
"x": monitor.props.geometry.x,
"y": monitor.props.geometry.y,
}
}
def pixbuf_to_dict(pixbuf, filename = ''):
if pixbuf:
has_alpha = pixbuf.get_has_alpha()
# 没有apha通道时, get_pixels() 得到像素矩阵会出现极个别像素出现alpha通道
# 所以, 这里强制设置alpha通道为false, 补全像素矩阵的数据
if not has_alpha:
pixbuf = pixbuf.add_alpha(False, 0, 0, 0)
data = {
"width": pixbuf.get_width(),
"height": pixbuf.get_height(),
"colorspace": pixbuf.get_colorspace(),
"has_alpha": has_alpha,
"bits_per_sample": pixbuf.get_bits_per_sample(),
"rowstride": pixbuf.get_rowstride(),
"filepath": filename,
"bytes": list(pixbuf.get_pixels())
}
else:
data = None
return data
def dict_to_pixbuf(data):
if data:
image = Pixbuf.new_from_data(
data = bytes(data['bytes']),
colorspace = data['colorspace'],
has_alpha = data['has_alpha'],
bits_per_sample = data['bits_per_sample'],
width = data['width'],
height = data['height'],
rowstride = data['rowstride']
)
else:
image = None
return image

View File

@ -0,0 +1,324 @@
#!/usr/bin/env python3
# @author yutent<yutent.io@gmail.com>
# @date 2023/08/08 14:07:26
import gi, os, json, shutil, hashlib
gi.require_version("WebKit2", "4.1")
from gi.repository import GObject, Gtk, Gdk, WebKit2, GLib, Gio, GdkPixbuf
# 优先尝试使用指示器, 没有再使用 Gtk.StatusIcon
try:
# gir1.2-ayatanaappindicator3-0.1
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AyatanaAppIndicator3 as AppIndicator3
except:
AppIndicator3 = None
try:
# gir1.2-keybinder-3.0
gi.require_version("Keybinder", "3.0")
from gi.repository import Keybinder
# 初始化 Keybinder
Keybinder.init()
except:
Keybinder = None
from ._settings import create_setting
from ._protocal import create_protocal
from ._notify import create_notify
from ._inject import Inject
from ._utils import get_monitor_info, pixbuf_to_dict, dict_to_pixbuf
def noop():
pass
class WebEngine(WebKit2.WebView):
__gsignals__ = {
'quit': (GObject.SignalFlags.RUN_FIRST, None, ())
}
root = None
window = None
def __init__(self, window):
WebKit2.WebView.__init__(self)
self.window = window
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.display = Gdk.Display.get_default()
self.use(create_notify())
setting = create_setting()
self.use(setting)
im = self.get_input_method_context()
im.set_enable_preedit(True)
# 解决输入法候选框跟随问题
im.connect('preedit-started', self.on_preedit_changed)
im.connect('preedit-changed', self.on_preedit_changed)
im.connect('preedit-finished', self.on_preedit_changed)
Inject(self).connect(self.called_by_js)
def set_root(self, root):
self.root = root
return self.use(create_protocal(root))
def use(self, middle_ware = noop, extra = None):
middle_ware(self, extra)
return self
def load(self, url = '/index.html'):
if self.root is None:
raise EnvironmentError('web root dir not set!')
else:
self.load_uri(f"app://{url}")
def on_preedit_changed(self, im):
seat = self.display.get_default_seat()
p = seat.get_pointer().get_position() # 光标位置
x, y = self.get_position() # 窗口位置
im.notify_focus_in()
im.notify_cursor_area(p.x - x, p.y - y, 0, 0) # 修正输入法跟随
def call_js(self, method, data = None):
scripts = 'native.$emit("' + method + '",' + json.dumps(data) + ')'
self.evaluate_javascript(scripts, -1)
def called_by_js(self, webview, message):
data = json.loads(message.get_js_value().to_json(0))
event = data.get('event')
callback = data.get('callback')
params = data.get('data')
output = None
match event:
case 'fs':
filepath = params.get('filepath')
if params['action'] == 'read':
with open(filepath, params.get('mode')) as file:
output = file.read()
if params.get('mode').find('b') > -1:
output = list(output)
if params['action'] == 'write':
# 调整以支持二进制数据写入
with open(filepath, params.get('mode')) as file:
buff = params['content']
if params.get('mode').find('b') > -1:
buff = bytes(buff)
output = file.write(buff)
if params['action'] == 'exists':
output = os.path.exists(filepath)
if params['action'] == 'list':
with os.scandir(filepath) as entries:
output = [{
"name": it.name,
"path": os.path.join(filepath, it.name),
"is_dir": it.is_dir(),
"size": it.stat().st_size,
"atime": int(it.stat().st_atime),
"mtime": int(it.stat().st_mtime),
} for it in entries]
if params['action'] == 'remove':
if os.path.isfile(filepath):
output = os.remove(filepath)
elif os.path.isdir(filepath):
output = os.removedirs(filename)
if params['action'] == 'rename':
if os.path.exists(filepath):
output = shutil.move(filepath, params['target'])
if params['action'] == 'copy':
if os.path.exists(filepath):
output = shutil.copy2(filepath, params['target'])
if params['action'] == 'isfile':
output = os.path.isfile(filepath)
if params['action'] == 'isdir':
output = os.path.isdir(filepath)
case 'clipboard':
# 读文本
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 = GdkPixbuf.Pixbuf.new_from_file(image)
else:
image = dict_to_pixbuf(image)
self.clipboard.set_image(image)
self.clipboard.store()
# 读图片
elif params['action'] == 'wait_for_image':
output = self.clipboard.wait_for_image()
output = pixbuf_to_dict(output, 'noname.png')
# 清除剪切板
elif params['action'] == 'clear':
self.clipboard.clear()
# 退出app
case 'quit':
self.emit('quit')
# 读取图片, 返回图片像素数据
case 'image':
filename = params['value']
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
output = pixbuf_to_dict(pixbuf, filename)
case 'monitor':
if params['action'] == 'get-all':
monitor_num = self.display.get_n_monitors()
monitors = [self.display.get_monitor(i) for i in range(monitor_num)]
output = [get_monitor_info(m) for m in monitors]
elif params['action'] == 'get-primary':
monitor = self.display.get_primary_monitor()
output = get_monitor_info(monitor)
case 'keybinder':
keymap = params.get('value')
shortcut_callback = params.get('shortcut_callback') or ''
if params['action'] == 'register':
# 绑定之前, 先解绑, 避免被重复绑定
if Keybinder:
Keybinder.unbind(keymap)
output = Keybinder.bind(
keymap,
lambda km : self.call_js(shortcut_callback)
)
elif params['action'] == 'unregister':
if Keybinder:
Keybinder.unbind(keymap)
output = True
elif params['action'] == 'supported':
if Keybinder:
output = Keybinder.supported()
else:
output = False
case 'tray':
if params['action'] == 'create':
pass
elif params['action'] == 'remove':
pass
case 'window':
if params['action'] == 'fullscreen':
self.window.fullscreen()
elif params['action'] == 'unfullscreen':
self.window.unfullscreen()
elif params['action'] == 'maximize':
self.window.maximize()
elif params['action'] == 'unmaximize':
self.window.unmaximize()
elif params['action'] == 'set_title':
self.window.set_title(params['value'] or '')
elif params['action'] == 'resize':
self.window.resize(params['value'].get('width'), params['value'].get('height'))
elif params['action'] == 'set_opacity':
self.window.set_opacity(params['value'])
elif params['action'] == 'set_keep_above':
self.window.set_keep_above(params['value'])
elif params['action'] == 'set_keep_below':
self.window.set_keep_below(params['value'])
elif params['action'] == 'move':
self.window.move(params['value'].get('x'), params['value'].get('y'))
elif params['action'] == 'toggle_visible':
if self.is_visible():
self.window.hide()
else:
self.window.present()
elif params['action'] == 'hide':
self.window.hide()
elif params['action'] == 'show':
self.window.present()
elif params['action'] == 'is_visible':
output = self.window.is_visible()
case 'notify':
title = params.get('title')
summary = params.get('summary')
icon = params.get('icon')
progress = params.get('progress')
urgency = params.get('urgency')
self.notify.create(title, summary, icon, progress, urgency, params.get('callback'))
case 'md5':
output = hashlib.md5(str(params.get('value'))).hexdigest()
# 有回调则返回结果
if callback:
self.call_js(callback, output)

View File

@ -0,0 +1,391 @@
/**
* {注入的js}
* @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'
}
const KEYS_MAP = {
shift: '<Shift>',
ctrl: '<Ctrl>',
alt: '<Alt>',
super: '<Super>'
}
function defer() {
let obj = {}
obj.promise = new Promise((resolve, reject) => {
obj.resolve = resolve
obj.reject = reject
})
return obj
}
function rand(prefix = 'cb_') {
return prefix + Math.random().toString().slice(2)
}
function handler(event, data = {}, once = true) {
let _ = defer()
let callback
if (typeof once === 'boolean') {
callback = rand()
native[once ? '$once' : '$on'](callback, _.resolve)
} else {
_.resolve(true)
}
window.webkit.messageHandlers.app.postMessage({
event,
data,
callback
})
return _.promise
}
function base64(str = '') {
return btoa(str).replace(/[+=\/]/g, '')
}
class NativeImage {
#origin
constructor(obj) {
this.#origin = obj
this.width = obj.width
this.height = obj.height
this.type = MIME_TYPES[obj.filepath.split('.').pop()]
}
toJSON() {
return this.#origin
}
export(type, base64) {
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)
if (base64) {
return canvas.toDataURL(type || this.type, 1)
} else {
canvas.toBlob(_.resolve, type || this.type, 1)
return _.promise
}
}
toPNG() {
return this.export('image/png')
}
toJPEG() {
return this.export('image/jpeg')
}
toDataURL(type) {
return this.export(type, true)
}
}
class EventEmitter {
//
__events__ = Object.create(null)
$on(name, fn) {
if (this.__events__[name]) {
this.__events__[name].push(fn)
} else {
this.__events__[name] = [fn]
}
}
$once(name, fn) {
fn.__once__ = true
this.$on(name, fn)
}
$off(name, fn) {
if (this.__events__[name]) {
if (fn) {
this.__events__[name] = this.__events__[name].filter(it => it !== fn)
} else {
this.__events__[name] = []
}
}
}
$emit(name, ...args) {
if (this.__events__[name]) {
for (let fn of this.__events__[name]) {
try {
fn.apply(this, args)
if (fn.__once__) {
this.$off(name, fn)
}
} catch (e) {
console.error(e)
}
}
}
}
$destroy() {
this.__events__ = Object.create(null)
}
}
window.native = new EventEmitter()
Object.assign(native, {
quit() {
return handler('quit', {}, null)
},
fs: {
read(filepath, mode = 'r') {
return handler('fs', { action: 'read', mode, filepath }).then(r =>
mode.includes('b') ? new Uint8Array(r) : r
)
},
write(filepath, content = '', mode = 'w') {
return handler('fs', {
action: 'write',
append: false,
filepath,
content
})
},
append(filepath, content = '', mode = 'w') {
return handler('fs', {
action: 'write',
append: true,
filepath,
content
})
},
exists(filepath) {
return handler('fs', { action: 'exists', filepath })
},
list(filepath) {
return handler('fs', { action: 'list', filepath })
},
isfile(filepath) {
return handler('fs', { action: 'isfile', filepath })
},
isdir(filepath) {
return handler('fs', { action: 'isdir', filepath })
},
remove(filepath) {
return handler('fs', { action: 'remove', filepath })
},
rename(filepath, target) {
return handler('fs', { action: 'rename', filepath, target })
},
copy(filepath, target) {
return handler('fs', { action: 'copy', filepath, target })
}
},
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' }).then(r =>
r ? new NativeImage(r) : r
)
},
writeImage(value) {
// 暂不知原因, postMessage传给Gtk后, JSON.stringify()并未读取toJSON的结果
if (typeof value === 'object') {
value = value.toJSON()
}
return handler('clipboard', { action: 'set_image', value }, null)
},
clear() {
return handler('clipboard', { action: 'clear' })
}
},
screen: {
getAllDisplays() {
return handler('monitor', { action: 'get-all' })
},
getPrimaryDisplay() {
return handler('monitor', { action: 'get-primary' })
}
},
globalShortcut: {
get enabled() {
return handler('keybinder', { action: 'supported' })
},
register(keyMap, callback) {
let shortcut_callback = base64(keyMap)
native.$off(shortcut_callback)
native.$on(shortcut_callback, callback)
return handler('keybinder', {
action: 'register',
value: keyMap,
shortcut_callback
})
},
unregister(keyMap) {
let shortcut_callback = base64(keyMap)
native.$off(shortcut_callback)
return handler('keybinder', { action: 'unregister', value: keyMap })
},
unregisterAll(keyMaps) {
for (let it of keyMaps) {
this.unregister(it)
}
}
},
tray: {
create() {
//
},
remove() {
//
},
/**
* 设置普通状态的tray图标, 只需要传名称, 自动会去当前主题下去找
*/
set_icon(name) {
return handler('tray', { action: 'set_icon', value: name })
},
/**
* 设置警示图标, 同上
*/
set_attention_icon(name) {
return handler('tray', { action: 'set_attention_icon', value: name })
},
/**
*
*/
set_title(title) {
return handler('tray', { action: 'set_title', value: title })
},
/**
* 修改tray图标状态
* @param status <Number> 0: 隐藏, 1: 显示, 2: 重要(对应上面的attention_icon)
*/
set_status(status) {
return handler('tray', { action: 'set_status', value: status })
}
},
window: {
isVisible() {
return handler('window', { action: 'is_visible' })
},
toggleVisible() {
handler('window', { action: 'toggle_visible' }, null)
},
hide() {
handler('window', { action: 'hide' }, null)
},
show() {
handler('window', { action: 'show' }, null)
},
fullscreen() {
handler('window', { action: 'fullscreen' }, null)
},
unfullscreen() {
handler('window', { action: 'unfullscreen' }, null)
},
maximize() {
handler('window', { action: 'maximize' }, null)
},
unmaximize() {
handler('window', { action: 'unmaximize' }, null)
},
setTitle(title = '') {
handler('window', { action: 'set_title', value: title }, null)
},
resize(width = 0, height = 0) {
handler('window', { action: 'resize', value: { width, height } }, null)
},
move(x = 0, y = 0) {
handler('window', { action: 'resize', value: { x, y } }, null)
},
setOpacity(opacity = 1) {
handler('window', { action: 'set_opacity', value: opacity }, null)
},
alwayOnTop(setting = true) {
handler('window', { action: 'set_keep_above', value: setting }, null)
},
alwayOnBotttom(setting = true) {
handler('window', { action: 'set_keep_below', value: setting }, null)
}
},
notify({ title, summary, icon, progress = 0, urgency = 0, callback }) {
let eventName
if (callback) {
eventName = rand()
native.$once(eventName, callback)
}
handler(
'notify',
{ title, summary, icon, progress, urgency, callback: eventName },
null
)
},
md5(value = '') {
return handler('md5', { value })
},
handler
})