2023-07-21 19:13:51 +08:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
2023-07-31 17:09:27 +08:00
|
|
|
import gi, json, os, shutil
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
gi.require_version("Gtk", "3.0")
|
|
|
|
gi.require_version("WebKit2", "4.1")
|
2023-07-27 20:25:43 +08:00
|
|
|
gi.require_version("Keybinder", "3.0")
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
|
|
|
|
from gi.repository import Gtk, Gdk, WebKit2, GLib, Gio, Keybinder
|
2023-07-25 18:53:52 +08:00
|
|
|
from gi.repository.GdkPixbuf import Pixbuf
|
2023-07-24 15:05:01 +08:00
|
|
|
|
2023-07-28 17:51:37 +08:00
|
|
|
from notes.utils import *
|
2023-07-31 16:19:19 +08:00
|
|
|
from notes.mimetypes import get_mimetype
|
2023-08-01 14:20:51 +08:00
|
|
|
from notes.notify import Notification
|
2023-07-28 17:51:37 +08:00
|
|
|
|
2023-07-26 13:28:11 +08:00
|
|
|
# 优先尝试使用指示器, 没有再使用 Gtk.StatusIcon
|
|
|
|
try:
|
|
|
|
gi.require_version('AyatanaAppIndicator3', '0.1')
|
|
|
|
# 需要安装这个包 gir1.2-ayatanaappindicator3-0.1
|
|
|
|
from gi.repository import AyatanaAppIndicator3 as AppIndicator3
|
|
|
|
except (ValueError, ImportError):
|
|
|
|
AppIndicator3 = None
|
|
|
|
|
2023-07-27 20:25:43 +08:00
|
|
|
# 初始化 Keybinder
|
|
|
|
Keybinder.init()
|
2023-08-01 14:20:51 +08:00
|
|
|
|
2023-07-27 20:25:43 +08:00
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
# im_context = Gtk.IMContext()
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-26 17:55:29 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
class WebKitWindow(Gtk.Window):
|
|
|
|
def __init__(self):
|
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
Gtk.Window.__init__(self, title="WebKit Example")
|
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
self.notify = Notification(self)
|
|
|
|
# Notification.__init__(self.notify, )
|
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
self.set_default_size(800, 600)
|
|
|
|
|
|
|
|
settings = WebKit2.Settings()
|
|
|
|
settings.set_enable_page_cache(True)
|
|
|
|
settings.set_enable_offline_web_application_cache(True)
|
|
|
|
|
2023-07-26 13:28:11 +08:00
|
|
|
settings.set_enable_developer_extras(True)
|
2023-07-31 16:19:19 +08:00
|
|
|
# settings.set_disable_web_security(True)
|
2023-07-26 13:28:11 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
settings.set_enable_html5_database(True)
|
|
|
|
settings.set_enable_html5_local_storage(True)
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-26 13:28:11 +08:00
|
|
|
settings.set_javascript_can_access_clipboard(True)
|
|
|
|
settings.set_javascript_can_open_windows_automatically(True)
|
|
|
|
|
|
|
|
settings.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) weapp/1.0.0 Version/16.4 Safari/605.1.15")
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
manager = WebKit2.UserContentManager()
|
|
|
|
|
|
|
|
script = open(self.file_path('./inject.js'), 'r').read()
|
|
|
|
frame = WebKit2.UserContentInjectedFrames.ALL_FRAMES
|
|
|
|
time = WebKit2.UserScriptInjectionTime.END
|
|
|
|
script = WebKit2.UserScript(script, frame, time, None, None)
|
|
|
|
|
|
|
|
manager.add_script(script)
|
|
|
|
|
|
|
|
manager.connect('script-message-received::app', self.on_script_message)
|
|
|
|
manager.register_script_message_handler('app')
|
|
|
|
|
|
|
|
self.webview = WebKit2.WebView.new_with_user_content_manager(manager)
|
|
|
|
self.webview.set_settings(settings)
|
|
|
|
|
|
|
|
|
2023-07-31 16:19:19 +08:00
|
|
|
context = self.webview.get_context()
|
|
|
|
context.register_uri_scheme('app', self.resource_request_callback)
|
2023-08-01 14:20:51 +08:00
|
|
|
# 允许网页通知权限
|
|
|
|
context.initialize_notification_permissions([WebKit2.SecurityOrigin.new_for_uri('app:///index.html')], [])
|
2023-07-31 19:28:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
im = self.webview.get_input_method_context()
|
|
|
|
im.set_enable_preedit(True)
|
2023-07-31 16:19:19 +08:00
|
|
|
|
|
|
|
# self.webview.load_uri("http://127.0.0.1:10086/index.html")
|
|
|
|
self.webview.load_uri("app:///index.html")
|
2023-07-26 17:55:29 +08:00
|
|
|
# self.webview.load_uri("https://benchmark.wkit.fun")
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-31 19:56:08 +08:00
|
|
|
|
2023-07-31 20:14:01 +08:00
|
|
|
# 解决输入法候选框跟随问题
|
|
|
|
im.connect('preedit-started', self.on_preedit_changed)
|
2023-07-31 19:56:08 +08:00
|
|
|
im.connect('preedit-changed', self.on_preedit_changed)
|
2023-07-31 20:14:01 +08:00
|
|
|
im.connect('preedit-finished', self.on_preedit_changed)
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
|
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
2023-07-27 20:25:43 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
|
|
|
|
self.add(self.webview)
|
|
|
|
|
2023-07-24 15:05:01 +08:00
|
|
|
|
2023-07-31 19:56:08 +08:00
|
|
|
def on_preedit_changed(self, im):
|
2023-07-31 20:14:01 +08:00
|
|
|
display = Gdk.Display.get_default()
|
|
|
|
p = display.get_pointer() # 光标位置
|
|
|
|
x, y = self.get_position() # 窗口位置
|
|
|
|
|
2023-07-31 19:56:08 +08:00
|
|
|
im.notify_focus_in()
|
2023-07-31 20:14:01 +08:00
|
|
|
im.notify_cursor_area(p.x - x, p.y - y, 0, 0) # 修正输入法跟随
|
2023-07-31 19:56:08 +08:00
|
|
|
|
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
|
2023-07-31 16:19:19 +08:00
|
|
|
def resource_request_callback(self, req):
|
|
|
|
|
|
|
|
schema = req.get_scheme()
|
|
|
|
pathname = req.get_path()
|
|
|
|
ext = pathname.split('.')[-1]
|
|
|
|
|
|
|
|
if schema == 'app':
|
|
|
|
data = open(self.file_path('./webview' + pathname)).read()
|
|
|
|
data = Gio.MemoryInputStream.new_from_data(data.encode())
|
|
|
|
req.finish(data, -1, get_mimetype(ext))
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
2023-07-26 17:55:29 +08:00
|
|
|
|
2023-07-24 15:05:01 +08:00
|
|
|
def create_tray(self):
|
2023-07-26 13:28:11 +08:00
|
|
|
|
2023-07-31 16:19:19 +08:00
|
|
|
if AppIndicator3 :
|
2023-07-26 13:28:11 +08:00
|
|
|
indicator = AppIndicator3.Indicator.new(
|
|
|
|
"youtube",
|
|
|
|
"youtube",
|
|
|
|
AppIndicator3.IndicatorCategory.APPLICATION_STATUS
|
|
|
|
)
|
2023-07-28 19:38:24 +08:00
|
|
|
|
|
|
|
# indicator.set_title('alacritty 6666')
|
|
|
|
# indicator.set_label('alacritty 8888', '')
|
|
|
|
# indicator.set_icon_full('alacritty','alacritty')
|
|
|
|
# indicator.set_attention_icon_full('alacritty', 'alacritty')
|
|
|
|
# indicator.set_ordering_index(99)
|
2023-07-26 13:28:11 +08:00
|
|
|
indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
|
2023-07-28 19:38:24 +08:00
|
|
|
# indicator.set_status(AppIndicator3.IndicatorStatus.ATTENTION)
|
2023-07-31 12:12:29 +08:00
|
|
|
menu = Gtk.Menu()
|
|
|
|
menu_items = Gtk.MenuItem.new_with_label("Toggle Floater")
|
|
|
|
menu.append(menu_items)
|
|
|
|
menu_items.connect("activate", self.toggle_visible)
|
|
|
|
|
|
|
|
menu.show_all()
|
|
|
|
indicator.set_menu(menu)
|
|
|
|
indicator.set_secondary_activate_target(menu_items)
|
2023-07-26 13:28:11 +08:00
|
|
|
else:
|
|
|
|
# windows 和 macos 必须传二进制图标, linux可传图标名称(自会去主题中找)
|
2023-07-31 12:12:29 +08:00
|
|
|
# indicator = Gtk.StatusIcon.new_from_pixbuf(get_logo(32))
|
2023-07-26 13:28:11 +08:00
|
|
|
# linux
|
2023-07-31 12:12:29 +08:00
|
|
|
# indicator = Gtk.StatusIcon.new_from_icon_name('youtube')
|
2023-07-26 13:28:11 +08:00
|
|
|
|
2023-07-31 12:12:29 +08:00
|
|
|
# return indicator
|
|
|
|
indicator = Gtk.StatusIcon.new_from_icon_name('youtube')
|
|
|
|
indicator.connect('activate', self.toggle_visible)
|
2023-07-24 15:05:01 +08:00
|
|
|
return indicator
|
2023-07-24 19:37:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
def toggle_visible(self, icon):
|
|
|
|
|
2023-07-31 12:12:29 +08:00
|
|
|
if self.is_visible():
|
2023-07-24 19:37:20 +08:00
|
|
|
self.hide()
|
|
|
|
else:
|
|
|
|
self.present()
|
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
|
2023-07-28 19:38:24 +08:00
|
|
|
def call_js(self, method, data = None):
|
|
|
|
scripts = 'native.$emit("' + method + '",' + json.dumps(data) + ')'
|
|
|
|
self.webview.evaluate_javascript(scripts, -1)
|
2023-07-25 18:53:52 +08:00
|
|
|
|
2023-07-24 15:05:01 +08:00
|
|
|
|
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
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):
|
2023-07-25 18:53:52 +08:00
|
|
|
|
|
|
|
data = json.loads(message.get_js_value().to_json(0))
|
|
|
|
|
2023-07-25 12:29:31 +08:00
|
|
|
event = data.get('event')
|
|
|
|
callback = data.get('callback')
|
2023-07-25 18:53:52 +08:00
|
|
|
params = data.get('data')
|
2023-07-31 12:12:29 +08:00
|
|
|
output = None
|
2023-07-25 18:53:52 +08:00
|
|
|
|
2023-07-25 12:29:31 +08:00
|
|
|
|
|
|
|
match event:
|
|
|
|
case 'fs':
|
2023-08-01 14:20:51 +08:00
|
|
|
filepath = params.get('filepath')
|
|
|
|
|
2023-07-31 17:09:27 +08:00
|
|
|
if params['action'] == 'read':
|
2023-08-01 15:25:13 +08:00
|
|
|
with open(filepath, params.get('mode')) as file:
|
|
|
|
output = file.read()
|
|
|
|
if params.get('mode').find('b') > -1:
|
|
|
|
output = list(output)
|
2023-07-31 17:09:27 +08:00
|
|
|
|
|
|
|
if params['action'] == 'write':
|
2023-08-01 15:25:13 +08:00
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
# 调整以支持二进制数据写入
|
2023-08-01 15:25:13 +08:00
|
|
|
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)
|
2023-07-31 17:09:27 +08:00
|
|
|
|
|
|
|
if params['action'] == 'exists':
|
|
|
|
output = os.path.exists(filepath)
|
|
|
|
|
|
|
|
if params['action'] == 'list':
|
2023-08-01 15:25:13 +08:00
|
|
|
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]
|
2023-07-31 17:09:27 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
if params['action'] == 'upload':
|
|
|
|
print(params)
|
2023-07-31 17:09:27 +08:00
|
|
|
|
|
|
|
|
2023-07-25 12:29:31 +08:00
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
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 = Pixbuf.new_from_file(image)
|
|
|
|
else:
|
2023-07-28 19:38:24 +08:00
|
|
|
image = dict_to_pixbuf(image)
|
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
self.clipboard.set_image(image)
|
|
|
|
self.clipboard.store()
|
|
|
|
|
2023-07-28 17:51:37 +08:00
|
|
|
# 读图片
|
|
|
|
elif params['action'] == 'wait_for_image':
|
|
|
|
output = self.clipboard.wait_for_image()
|
|
|
|
output = pixbuf_to_dict(output, 'noname.png')
|
|
|
|
|
2023-07-25 18:53:52 +08:00
|
|
|
# 清除剪切板
|
|
|
|
elif params['action'] == 'clear':
|
|
|
|
self.clipboard.clear()
|
|
|
|
|
|
|
|
|
|
|
|
# 退出app
|
|
|
|
case 'quit':
|
2023-07-27 20:25:43 +08:00
|
|
|
all_quit(self)
|
2023-07-25 18:53:52 +08:00
|
|
|
|
|
|
|
# 读取图片, 返回图片像素数据
|
|
|
|
case 'image':
|
|
|
|
filename = params['value']
|
|
|
|
pixbuf = Pixbuf.new_from_file(filename)
|
2023-07-31 12:12:29 +08:00
|
|
|
output = pixbuf_to_dict(pixbuf, filename)
|
2023-07-25 18:53:52 +08:00
|
|
|
|
2023-07-26 17:55:29 +08:00
|
|
|
|
|
|
|
case 'monitor':
|
|
|
|
if params['action'] == 'get-all':
|
|
|
|
display = Gdk.Display.get_default()
|
|
|
|
monitor_num = display.get_n_monitors()
|
|
|
|
monitors = [display.get_monitor(i) for i in range(monitor_num)]
|
2023-07-31 12:12:29 +08:00
|
|
|
output = [get_monitor_info(m) for m in monitors]
|
2023-07-28 19:38:24 +08:00
|
|
|
|
2023-07-26 17:55:29 +08:00
|
|
|
elif params['action'] == 'get-primary':
|
|
|
|
display = Gdk.Display.get_default()
|
|
|
|
monitor = display.get_primary_monitor()
|
2023-07-31 12:12:29 +08:00
|
|
|
output = get_monitor_info(monitor)
|
2023-07-26 17:55:29 +08:00
|
|
|
|
2023-07-27 20:25:43 +08:00
|
|
|
case 'keybinder':
|
|
|
|
keymap = params.get('value')
|
|
|
|
shortcut_callback = params.get('shortcut_callback') or ''
|
|
|
|
|
|
|
|
if params['action'] == 'register':
|
|
|
|
# 绑定之前, 先解绑, 避免被重复绑定
|
|
|
|
Keybinder.unbind(keymap)
|
|
|
|
output = Keybinder.bind(
|
|
|
|
keymap,
|
2023-07-28 19:38:24 +08:00
|
|
|
lambda km : self.call_js(shortcut_callback)
|
2023-07-27 20:25:43 +08:00
|
|
|
)
|
|
|
|
|
2023-07-28 19:38:24 +08:00
|
|
|
elif params['action'] == 'unregister':
|
|
|
|
Keybinder.unbind(keymap)
|
|
|
|
output = True
|
2023-07-27 20:25:43 +08:00
|
|
|
|
|
|
|
elif params['action'] == 'supported':
|
|
|
|
output = Keybinder.supported()
|
|
|
|
|
2023-07-28 19:38:24 +08:00
|
|
|
|
|
|
|
case 'tray':
|
|
|
|
if params['action'] == 'create':
|
|
|
|
pass
|
|
|
|
|
|
|
|
elif params['action'] == 'remove':
|
|
|
|
pass
|
|
|
|
|
2023-07-31 12:12:29 +08:00
|
|
|
|
|
|
|
case 'window':
|
|
|
|
if params['action'] == 'fullscreen':
|
|
|
|
self.fullscreen()
|
|
|
|
|
|
|
|
elif params['action'] == 'unfullscreen':
|
|
|
|
self.unfullscreen()
|
|
|
|
|
|
|
|
elif params['action'] == 'maximize':
|
|
|
|
self.maximize()
|
|
|
|
|
|
|
|
elif params['action'] == 'unmaximize':
|
|
|
|
self.unmaximize()
|
|
|
|
|
|
|
|
elif params['action'] == 'set_title':
|
|
|
|
self.set_title(params['value'] or '')
|
|
|
|
|
|
|
|
elif params['action'] == 'resize':
|
|
|
|
self.resize(params['value'].get('width'), params['value'].get('height'))
|
|
|
|
|
|
|
|
elif params['action'] == 'set_opacity':
|
|
|
|
self.set_opacity(params['value'])
|
|
|
|
|
|
|
|
elif params['action'] == 'set_keep_above':
|
|
|
|
self.set_keep_above(params['value'])
|
|
|
|
|
|
|
|
elif params['action'] == 'set_keep_below':
|
|
|
|
self.set_keep_below(params['value'])
|
|
|
|
|
|
|
|
elif params['action'] == 'move':
|
|
|
|
self.move(params['value'].get('x'), params['value'].get('y'))
|
|
|
|
|
|
|
|
elif params['action'] == 'toggle_visible':
|
|
|
|
if self.is_visible():
|
|
|
|
self.hide()
|
|
|
|
else:
|
|
|
|
self.present()
|
|
|
|
|
|
|
|
elif params['action'] == 'hide':
|
|
|
|
self.hide()
|
|
|
|
|
|
|
|
elif params['action'] == 'show':
|
|
|
|
self.present()
|
|
|
|
|
|
|
|
elif params['action'] == 'is_visible':
|
|
|
|
output = self.is_visible()
|
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
case 'notify':
|
|
|
|
title = params.get('title')
|
|
|
|
summary = params.get('summary')
|
|
|
|
icon = params.get('icon')
|
|
|
|
progress = params.get('progress')
|
|
|
|
urgency = params.get('urgency')
|
|
|
|
|
2023-08-01 14:20:51 +08:00
|
|
|
self.notify.create(title, summary, icon, progress, urgency, params.get('callback'))
|
2023-07-31 12:12:29 +08:00
|
|
|
|
|
|
|
# 有回调则返回结果
|
2023-08-01 14:20:51 +08:00
|
|
|
if callback:
|
2023-07-31 12:12:29 +08:00
|
|
|
self.call_js(callback, output)
|
2023-07-25 12:29:31 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-27 20:25:43 +08:00
|
|
|
def all_quit(win):
|
|
|
|
print('朕要休息了~~~')
|
|
|
|
Gtk.main_quit()
|
2023-07-21 19:13:51 +08:00
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
win = WebKitWindow()
|
2023-07-27 20:25:43 +08:00
|
|
|
win.connect("destroy", all_quit)
|
2023-07-21 19:13:51 +08:00
|
|
|
win.show_all()
|
|
|
|
|
2023-07-26 17:55:29 +08:00
|
|
|
|
2023-07-31 19:28:17 +08:00
|
|
|
|
2023-07-24 15:05:01 +08:00
|
|
|
tray = win.create_tray()
|
|
|
|
|
2023-07-21 19:13:51 +08:00
|
|
|
Gtk.main()
|