diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e43cdf79..767d904c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 3.5 + +_Released 02/08/2021_ +- `New` [All] Get information about available screens via new `webview.screens` property. +- `New` [All] Per window localization. Thanks @fizzadar. +- `New` [All] Window closing can be cancelled by returning False from a closing event handler. #744. +- `Fix` [All] Debug mode cannot be set under certain conditions. #628 +- `Improvement` [All] Selected web renderer printed in Python console in debug mode. +- `Improvement` [All] JS API serialization logic. Thanks @peter23 +- `Improvement` [EdgeChromium] Chromium runtime updated to version 1.0.774.44. Thanks @sbbosco. +- `Improvement` [EdgeChromium] Custom user agent support. +- `Fix` [WinForms] Icon handling logic to make pywebview compatible with pystray. #720. Thanks @simonrob +- `Fix` [EdgeChromium] Change webview component to transparent. Thanks @ODtian +- `Fix` [CEF] Fix exception when destroying window +- `Fix` [Cocoa] cmd+w bypasses exit confirmation dialogue. #698. Thanks @fizzadar +- `Fix` [Cocoa] Fix window coordinate calculation logic when moving a window. +- `Fix` [MSHTML] Fix drag_region +- `Fix` [MSHTML] Fix window.alert + + ## 3.4: Second wave _Released 04/12/2020_ diff --git a/docs/README.md b/docs/README.md index d71540bb..119bf66a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ actionLink: /guide/ footer: BSD Licensed | Copyright © 2014-present Roman Sirokov ---
-Current version: 3.4
+Current version: 3.5
What's new
diff --git a/setup.py b/setup.py index 40f0768c..2c019490 100644 --- a/setup.py +++ b/setup.py @@ -30,11 +30,11 @@ long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/r0x0r/pywebview', - download_url='https://github.com/r0x0r/pywebview/archive/3.4.tar.gz', + download_url='https://github.com/r0x0r/pywebview/archive/3.5.tar.gz', keywords=['gui', 'webkit', 'html', 'web'], install_requires=install_requires, extras_require=extras_require, - version='3.4', + version='3.5', include_package_data=True, packages=['webview', 'webview.js', 'webview.platforms'], package_dir={'webview': 'webview'}, diff --git a/webview/__init__.py b/webview/__init__.py index 92db3b9e..27afdf95 100644 --- a/webview/__init__.py +++ b/webview/__init__.py @@ -142,7 +142,7 @@ def _create_children(other_windows): def create_window(title, url=None, html=None, js_api=None, width=800, height=600, x=None, y=None, resizable=True, fullscreen=False, min_size=(200, 100), hidden=False, - frameless=False, easy_drag=True, + icon=None, frameless=False, easy_drag=True, minimized=False, on_top=False, confirm_close=False, background_color='#FFFFFF', transparent=False, text_select=False, localization=None): """ @@ -156,6 +156,7 @@ def create_window(title, url=None, html=None, js_api=None, width=800, height=600 :param fullscreen: True if start in fullscreen mode. Default is False :param min_size: a (width, height) tuple that specifies a minimum window size. Default is 200x100 :param hidden: Whether the window should be hidden. + :param icon: Window icon name. :param frameless: Whether the window should have a frame. :param easy_drag: Easy window drag mode when window is frameless. :param minimized: Display window minimized @@ -176,7 +177,7 @@ def create_window(title, url=None, html=None, js_api=None, width=800, height=600 window = Window(uid, make_unicode(title), url, html, width, height, x, y, resizable, fullscreen, min_size, hidden, frameless, easy_drag, minimized, on_top, confirm_close, background_color, - js_api, text_select, transparent, localization) + js_api, text_select, transparent, localization, icon) windows.append(window) @@ -191,4 +192,4 @@ def create_window(title, url=None, html=None, js_api=None, width=800, height=600 def screens(): guilib = initialize() screens = guilib.get_screens() - return screens \ No newline at end of file + return screens diff --git a/webview/platforms/cef.py b/webview/platforms/cef.py index 73f2f3e7..91f0921a 100644 --- a/webview/platforms/cef.py +++ b/webview/platforms/cef.py @@ -4,8 +4,9 @@ import shutil import sys import webbrowser +import platform -from ctypes import windll +from ctypes import windll, wintypes from functools import wraps from uuid import uuid1 from threading import Event @@ -17,6 +18,7 @@ from webview.js import dom from webview import _debug, _user_agent from webview.util import parse_api_js, default_html, js_bridge_call +from webview.window import FixPoint sys.excepthook = cef.ExceptHook @@ -252,6 +254,20 @@ def _create(): instances[window.uid] = browser window.shown.set() + + if window.icon: + icon = window.icon+".ico" + if os.path.isfile(icon): + smallx = windll.user32.GetSystemMetrics(49) #SM_CXSMICON + smally = windll.user32.GetSystemMetrics(50) #SM_CYSMICON + small_icon = windll.user32.LoadImageW(0, icon, 1, smallx, smally, 0x00000010) + windll.user32.SendMessageW(handle, 0x0080, 0, small_icon) + + bigx = windll.user32.GetSystemMetrics(11) #SM_CXICON + bigy = windll.user32.GetSystemMetrics(12) #SM_CYICON + big_icon = windll.user32.LoadImageW(0, icon, 1, bigx, bigy, 0x00000010) + windll.user32.SendMessageW(handle, 0x0080, 1, big_icon) + window_info = cef.WindowInfo() window_info.SetAsChild(handle) diff --git a/webview/platforms/cocoa.py b/webview/platforms/cocoa.py index e1d2d10d..70068384 100755 --- a/webview/platforms/cocoa.py +++ b/webview/platforms/cocoa.py @@ -20,6 +20,7 @@ from webview.util import parse_api_js, default_html, js_bridge_call from webview.js.css import disable_text_select from webview.screen import Screen +from webview.window import FixPoint settings = {} @@ -475,13 +476,17 @@ def toggle(): AppHelper.callAfter(toggle) self.is_fullscreen = not self.is_fullscreen - def resize(self, width, height): + def resize(self, width, height, fix_point): def _resize(): frame = self.window.frame() - # Keep the top left of the window in the same place - frame.origin.y += frame.size.height - frame.origin.y -= height + if fix_point & FixPoint.EAST: + # Keep the right of the window in the same place + frame.origin.x += frame.size.width - width + + if fix_point & FixPoint.NORTH: + # Keep the top of the window in the same place + frame.origin.y += frame.size.height - height frame.size.width = width frame.size.height = height @@ -838,8 +843,8 @@ def _set_on_top(): AppHelper.callAfter(_set_on_top) -def resize(width, height, uid): - BrowserView.instances[uid].resize(width, height) +def resize(width, height, uid, fix_point): + BrowserView.instances[uid].resize(width, height, fix_point) def minimize(uid): diff --git a/webview/platforms/edgechromium.py b/webview/platforms/edgechromium.py index 43c64d40..0979de2b 100644 --- a/webview/platforms/edgechromium.py +++ b/webview/platforms/edgechromium.py @@ -8,17 +8,14 @@ """ import os -import sys import logging import json -import shutil -import tempfile import webbrowser -from threading import Event, Semaphore +from threading import Semaphore from ctypes import windll from platform import architecture -from webview import WebViewException, _debug, _user_agent +from webview import _debug, _user_agent from webview.serving import resolve_url from webview.util import parse_api_js, interop_dll_path, parse_file_type, inject_base_uri, default_html, js_bridge_call from webview.js import alert @@ -63,10 +60,10 @@ def __init__(self, form, window): self.web_view.NavigationStarting += self.on_navigation_start self.web_view.NavigationCompleted += self.on_navigation_completed self.web_view.WebMessageReceived += self.on_script_notify - + if window.transparent: self.web_view.DefaultBackgroundColor = Color.Transparent - + self.url = None self.ishtml = False self.html = None @@ -146,8 +143,12 @@ def on_webview_ready(self, sender, args): settings.IsWebMessageEnabled = True settings.IsStatusBarEnabled = _debug['mode'] settings.IsZoomControlEnabled = True - if _user_agent: settings.UserAgent = _user_agent - if self.html: sender.CoreWebView2.NavigateToString(self.html) + + if _user_agent: + settings.UserAgent = _user_agent + + if self.html: + sender.CoreWebView2.NavigateToString(self.html) def on_navigation_start(self, sender, args): pass diff --git a/webview/platforms/gtk.py b/webview/platforms/gtk.py index 23413261..9959ed3b 100755 --- a/webview/platforms/gtk.py +++ b/webview/platforms/gtk.py @@ -19,6 +19,7 @@ from webview.util import parse_api_js, default_html, js_bridge_call from webview.js.css import disable_text_select from webview.screen import Screen +from webview.window import FixPoint logger = logging.getLogger('pywebview') @@ -286,7 +287,24 @@ def toggle_fullscreen(self): self.is_fullscreen = not self.is_fullscreen - def resize(self, width, height): + def resize(self, width, height, fix_point): + if fix_point & FixPoint.NORTH and fix_point & FixPoint.WEST: + self.window.set_gravity(Gdk.Gravity.NORTH_WEST) + elif fix_point & FixPoint.NORTH and fix_point & FixPoint.EAST: + self.window.set_gravity(Gdk.Gravity.NORTH_EAST) + elif fix_point & FixPoint.SOUTH and fix_point & FixPoint.EAST: + self.window.set_gravity(Gdk.Gravity.SOUTH_EAST) + elif fix_point & FixPoint.SOUTH and fix_point & FixPoint.WEST: + self.window.set_gravity(Gdk.Gravity.SOUTH_WEST) + elif fix_point & FixPoint.SOUTH: + self.window.set_gravity(Gdk.Gravity.SOUTH) + elif fix_point & FixPoint.NORTH: + self.window.set_gravity(Gdk.Gravity.NORTH) + elif fix_point & FixPoint.WEST: + self.window.set_gravity(Gdk.Gravity.WEST) + elif fix_point & FixPoint.EAST: + self.window.set_gravity(Gdk.Gravity.EAST) + self.window.resize(width, height) def move(self, x, y): @@ -448,9 +466,9 @@ def _set_on_top(): glib.idle_add(_set_on_top) -def resize(width, height, uid): +def resize(width, height, uid, fix_point): def _resize(): - BrowserView.instances[uid].resize(width,height) + BrowserView.instances[uid].resize(width, height, fix_point) glib.idle_add(_resize) diff --git a/webview/platforms/qt.py b/webview/platforms/qt.py index 753b85a0..82735c53 100755 --- a/webview/platforms/qt.py +++ b/webview/platforms/qt.py @@ -20,6 +20,7 @@ from webview.util import convert_string, default_html, parse_api_js, js_bridge_call from webview.js.css import disable_text_select from webview.screen import Screen +from webview.window import FixPoint logger = logging.getLogger('pywebview') @@ -32,7 +33,7 @@ logger.debug('Using Qt %s' % QT_VERSION_STR) from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QAction -from PyQt5.QtGui import QColor, QScreen +from PyQt5.QtGui import QColor, QScreen, QIcon try: from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView, QWebEnginePage as QWebPage @@ -58,6 +59,7 @@ class BrowserView(QMainWindow): create_window_trigger = QtCore.pyqtSignal(object) set_title_trigger = QtCore.pyqtSignal(str) + set_icon_trigger = QtCore.pyqtSignal(str) load_url_trigger = QtCore.pyqtSignal(str) html_trigger = QtCore.pyqtSignal(str, str) dialog_trigger = QtCore.pyqtSignal(int, str, bool, str, str) @@ -129,7 +131,7 @@ def show_inspector(self): url = 'http://localhost:{}'.format(BrowserView.inspector_port) print(url) window = Window('web_inspector', title, url, '', 700, 500, None, None, True, False, - (300, 200), False, False, False, False, False, False, '#fff', None, False, False, None) + (300, 200), False, False, False, False, False, False, '#fff', None, False, False, '', '') window.localization = self.parent().localization inspector = BrowserView(window) @@ -232,6 +234,8 @@ def __init__(self, window): self.title = window.title self.setWindowTitle(window.title) + self.setWindowIcon(QIcon(window.icon)) + # Set window background color self.background_color = QColor() self.background_color.setNamedColor(window.background_color) @@ -299,6 +303,7 @@ def __init__(self, window): self.current_url_trigger.connect(self.on_current_url) self.evaluate_js_trigger.connect(self.on_evaluate_js) self.set_title_trigger.connect(self.on_set_title) + self.set_icon_trigger.connect(self.on_set_icon) self.on_top_trigger.connect(self.on_set_on_top) if is_webengine and platform.system() != 'OpenBSD': @@ -332,6 +337,10 @@ def __init__(self, window): def on_set_title(self, title): self.setWindowTitle(title) + + def on_set_icon(self, icon): + self.setWindowIcon(QIcon(icon)) + def on_file_dialog(self, dialog_type, directory, allow_multiple, save_filename, file_filter): if dialog_type == FOLDER_DIALOG: self._file_name = QFileDialog.getExistingDirectory(self, self.localization['linux.openFolder'], options=QFileDialog.ShowDirsOnly) @@ -413,7 +422,18 @@ def on_fullscreen(self): self.is_fullscreen = not self.is_fullscreen - def on_window_size(self, width, height): + def on_window_size(self, width, height, fix_point): + geo = self.geometry() + + if fix_point & FixPoint.EAST: + # Keep the right of the window in the same place + geo.setX(geo.x() + geo.width() - width) + + if fix_point & FixPoint.SOUTH: + # Keep the top of the window in the same place + geo.setY(geo.y() + geo.height() - height) + + self.setGeometry(geo) self.setFixedSize(width, height) def on_window_move(self, x, y): @@ -462,6 +482,9 @@ def on_load_finished(self): def set_title(self, title): self.set_title_trigger.emit(title) + def set_icon(self, icon): + self.set_icon_trigger.emit(icon) + def get_current_url(self): self.loaded.wait() self.current_url_trigger.emit() @@ -506,8 +529,8 @@ def destroy_(self): def toggle_fullscreen(self): self.fullscreen_trigger.emit() - def resize_(self, width, height): - self.window_size_trigger.emit(width, height) + def resize_(self, width, height, fix_point): + self.window_size_trigger.emit(width, height, fix_point) def move_window(self, x, y): self.window_move_trigger.emit(x, y) @@ -631,6 +654,10 @@ def _create(): def set_title(title, uid): BrowserView.instances[uid].set_title(title) +def set_icon(icon, uid): + BrowserView.instances[uid].set_icon(icon) + + def get_current_url(uid): return BrowserView.instances[uid].get_current_url() @@ -672,8 +699,8 @@ def set_on_top(uid, top): BrowserView.instances[uid].set_on_top(top) -def resize(width, height, uid): - BrowserView.instances[uid].resize_(width, height) +def resize(width, height, uid, fix_point): + BrowserView.instances[uid].resize_(width, height, fix_point) def move(x, y, uid): @@ -701,6 +728,9 @@ def get_position(uid): def get_size(uid): window = BrowserView.instances[uid] return window.width(), window.height() + +def get_instance(uid): + return BrowserView.instances[uid] def get_screens(): diff --git a/webview/platforms/winforms.py b/webview/platforms/winforms.py index 547f0772..7e320ac1 100644 --- a/webview/platforms/winforms.py +++ b/webview/platforms/winforms.py @@ -21,6 +21,7 @@ from webview.util import parse_file_type, inject_base_uri from webview.js import alert from webview.screen import Screen +from webview.window import FixPoint try: import _winreg as winreg # Python 2 @@ -334,9 +335,18 @@ def _set(): else: _set() - def resize(self, width, height): - windll.user32.SetWindowPos(self.Handle.ToInt32(), None, self.Location.X, self.Location.Y, - width, height, 64) + def resize(self, width, height, fix_point): + + x = self.Location.X + y = self.Location.Y + + if fix_point & FixPoint.EAST: + x = x + self.Width - width + + if fix_point & FixPoint.SOUTH: + y = y + self.Height - height + + windll.user32.SetWindowPos(self.Handle.ToInt32(), None, x, y, width, height, 64) def move(self, x, y): SWP_NOSIZE = 0x0001 # Retains the current size @@ -588,9 +598,9 @@ def set_on_top(uid, on_top): window.on_top = on_top -def resize(width, height, uid): +def resize(width, height, uid, fix_point): window = BrowserView.instances[uid] - window.resize(width, height) + window.resize(width, height, fix_point) def move(x, y, uid): diff --git a/webview/window.py b/webview/window.py index 88bdde8e..47fc38ac 100644 --- a/webview/window.py +++ b/webview/window.py @@ -1,6 +1,7 @@ import inspect import logging import os +from enum import Flag, auto from functools import wraps from webview.event import Event @@ -44,12 +45,20 @@ def _loaded_call(function): return _api_call(function, 'loaded') +class FixPoint(Flag): + NORTH = auto() + WEST = auto() + EAST = auto() + SOUTH = auto() + + class Window: def __init__(self, uid, title, url, html, width, height, x, y, resizable, fullscreen, min_size, hidden, frameless, easy_drag, minimized, on_top, confirm_close, - background_color, js_api, text_select, transparent, localization): + background_color, js_api, text_select, transparent, localization, icon): self.uid = uid self.title = make_unicode(title) + self.icon = icon self.original_url = None if html else url # original URL provided by user self.real_url = None # transformed URL for internal HTTP server self.html = html @@ -160,6 +169,10 @@ def get_elements(self, selector): return self.evaluate_js(code) + @_shown_call + def get_instance(self): + return self.gui.get_instance(self.uid) + @_shown_call def load_url(self, url): """ @@ -198,6 +211,14 @@ def set_title(self, title): """ self.gui.set_title(title, self.uid) + @_shown_call + def set_icon(self, icon): + """ + Set a new title of the window + """ + self.gui.set_icon(icon, self.uid) + + @_loaded_call def get_current_url(self): """ @@ -234,16 +255,20 @@ def set_window_size(self, width, height): :param height: desired height of target window """ logger.warning('This function is deprecated and will be removed in future releases. Use resize() instead') - self.gui.resize(width, height, self.uid) + self.resize(width, height) @_shown_call - def resize(self, width, height): + def resize(self, width, height, fix_point=FixPoint.NORTH | FixPoint.WEST): """ Resize window :param width: desired width of target window :param height: desired height of target window + :param fix_point: Fix window to specified point during resize. + Must be of type FixPoint. Different points can be combined + with bitwise operators. + Example: FixPoint.NORTH | FixPoint.WEST """ - self.gui.resize(width, height, self.uid) + self.gui.resize(width, height, self.uid, fix_point) @_shown_call def minimize(self):