import json import logging import os import pathlib import sys import webbrowser from threading import Semaphore, Thread, main_thread from typing import Any from uuid import uuid1 from webview import FileDialog, _state, settings, windows from webview.dom import _dnd_state from webview.menu import Menu, MenuAction, MenuSeparator from webview.models import Request, Response from webview.screen import Screen from webview.util import ( DEFAULT_HTML, create_cookie, inject_pywebview, js_bridge_call, parse_file_type, ) from webview.window import FixPoint, Window logger = logging.getLogger('pywebview') os.environ['EGL_LOG_LEVEL'] = 'fatal' import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') try: gi.require_version('WebKit2', '4.1') gi.require_version('Soup', '3.0') except ValueError: logger.debug('WebKit2 4.1 not found. Using 4.0.') gi.require_version('WebKit2', '4.0') gi.require_version('Soup', '2.4') from gi.repository import Gdk, Gio from gi.repository import GLib as glib from gi.repository import Gtk as gtk from gi.repository import WebKit2 as webkit renderer = 'gtkwebkit2' webkit_ver = webkit.get_major_version(), webkit.get_minor_version(), webkit.get_micro_version() _app = None _app_actions = {} # action_label: function cert = None class BrowserView: instances = {} class JSBridge: def __init__(self, window: Window) -> None: self.window = window self.uid = uuid1().hex[:8] def call(self, func_name: str, param: Any, value_id: str): if param == 'undefined': param = None return js_bridge_call(self.window, func_name, param, value_id) def __init__(self, window: Window) -> None: # Note: _app won't be None because BrowserView() is called after _app is made in `create_window` global _app BrowserView.instances[window.uid] = self self.uid = window.uid self.pywebview_window = window self.is_fullscreen = False self.js_results = {} self.window = gtk.ApplicationWindow(title=window.title, application=_app) self.pywebview_window.native = self.window self.shown = window.events.shown self.loaded = window.events.loaded self.localization = window.localization self._last_width = window.initial_width self._last_height = window.initial_height if window.screen: self.screen = window.screen.frame else: display = Gdk.Display.get_default() monitor = Gdk.Display.get_monitor(display, 0) self.screen = Gdk.Monitor.get_geometry(monitor) if window.resizable: self.window.set_size_request(window.min_size[0], window.min_size[1]) self.window.resize(window.initial_width, window.initial_height) else: self.window.set_size_request(window.initial_width, window.initial_height) if window.maximized: self.window.maximize() elif window.minimized: self.window.iconify() if window.initial_x is not None and window.initial_y is not None: self.move(window.initial_x, window.initial_y) else: window_width, window_height = self.window.get_size() x = (self.screen.width - window_width) // 2 y = (self.screen.height - window_height) // 2 self.move(x, y) self.window.set_resizable(window.resizable) self.window.set_accept_focus(window.focus) # Set window background color style_provider = gtk.CssProvider() style_provider.load_from_data( f'GtkWindow {{ background-color: {window.background_color}; }}'.encode() ) gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) if window.menu: logger.warning('Window specific menu is not supported on GTK') scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.PolicyType.NEVER, gtk.PolicyType.NEVER) self.window.add(scrolled_window) self.window.connect('delete-event', self.close_window) self.window.connect('window-state-event', self.on_window_state_change) self.window.connect('size-allocate', self.on_window_resize) self.window.connect('configure-event', self.on_window_configure) self.js_bridge = BrowserView.JSBridge(window) if _state['private_mode']: # Create ephemeral context for private mode web_context = webkit.WebContext.new_ephemeral() elif _state['storage_path']: storage_path = _state['storage_path'] # Create storage directory if it doesn't exist if not os.path.exists(storage_path): os.makedirs(storage_path) # Create website data manager with storage directories self.website_data_manager = webkit.WebsiteDataManager( base_data_directory=storage_path, base_cache_directory=os.path.join(storage_path, 'cache'), local_storage_directory=os.path.join(storage_path, 'localstorage'), indexeddb_directory=os.path.join(storage_path, 'indexeddb'), websql_directory=os.path.join(storage_path, 'websql'), hsts_cache_directory=os.path.join(storage_path, 'hsts'), itp_directory=os.path.join(storage_path, 'itp'), service_worker_registrations_directory=os.path.join(storage_path, 'serviceworkers'), dom_cache_directory=os.path.join(storage_path, 'domcache'), ) web_context = webkit.WebContext.new_with_website_data_manager(self.website_data_manager) else: web_context = webkit.WebContext.get_default() self.cookie_manager = web_context.get_cookie_manager() if not _state['private_mode']: script_name = pathlib.Path(sys.argv[0]).name if script_name in ['python', 'python3', '-c', '']: script_name = 'pywebview' storage_path = _state['storage_path'] or os.path.expanduser(f'~/.cache/{script_name}/') self.cookie_manager.set_persistent_storage( os.path.join(storage_path, 'cookies'), webkit.CookiePersistentStorage.TEXT ) if cert: web_context.allow_tls_certificate_for_host(cert, '127.0.0.1') self.manager = webkit.UserContentManager() self.manager.register_script_message_handler('jsBridge') self.manager.connect('script-message-received', self.on_js_bridge_call) self.request_headers_mutated = False # Create WebView with the configured context self.webview = webkit.WebView(user_content_manager=self.manager, web_context=web_context) self.webview.connect('notify::visible', self.on_webview_ready) self.webview.connect('load_changed', self.on_load_finish) self.webview.connect('decide-policy', self.on_navigation) self.webview.connect('drag-data-received', self.on_drag_data) self.webview.connect('resource-load-started', self.on_request) if settings['IGNORE_SSL_ERRORS']: web_context.set_tls_errors_policy(webkit.TLSErrorsPolicy.IGNORE) if settings['ALLOW_DOWNLOADS']: web_context.connect('download-started', self.on_download_started) webkit_settings = self.webview.get_settings().props user_agent = settings.get('user_agent') or _state['user_agent'] if user_agent: webkit_settings.user_agent = user_agent webkit_settings.enable_media_stream = True webkit_settings.enable_mediasource = True webkit_settings.enable_webaudio = True webkit_settings.enable_webgl = True webkit_settings.javascript_can_access_clipboard = True webkit_settings.allow_file_access_from_file_urls = settings['ALLOW_FILE_URLS'] if window.frameless: self.window.set_decorated(False) if window.easy_drag: self.move_progress = False self.webview.connect('button-release-event', self.on_mouse_release) self.webview.connect('button-press-event', self.on_mouse_press) self.window.connect('motion-notify-event', self.on_mouse_move) if window.on_top: self.window.set_keep_above(True) self.transparent = window.transparent if window.transparent: configure_transparency(self.window) configure_transparency(self.webview) wvbg = self.webview.get_background_color() wvbg.alpha = 0.0 self.webview.set_background_color(wvbg) if _state['debug']: webkit_settings.enable_developer_extras = True if settings['OPEN_DEVTOOLS_IN_DEBUG']: self.webview.get_inspector().show() else: self.webview.connect('context-menu', lambda a, b, c, d: True) # Disable context menu if _state['private_mode']: webkit_settings.enable_html5_database = False webkit_settings.enable_html5_local_storage = False self.webview.set_opacity(0.0) scrolled_window.add(self.webview) if _state['icon']: self.window.set_icon_from_file(_state['icon']) self.window.set_default_icon_from_file(_state['icon']) self.pywebview_window.events.before_show.set() if window.fullscreen: self.toggle_fullscreen() if window.real_url is not None: self.webview.load_uri(window.real_url) elif window.html: self.webview.load_html(window.html, '') else: self.webview.load_html(DEFAULT_HTML, '') def close_window(self, *data): should_cancel = self.pywebview_window.events.closing.set() if should_cancel: return True if self.pywebview_window.confirm_close: dialog = gtk.MessageDialog( parent=self.window, flags=gtk.DialogFlags.MODAL & gtk.DialogFlags.DESTROY_WITH_PARENT, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL, message_format=self.localization['global.quitConfirmation'], ) result = dialog.run() dialog.destroy() if result == gtk.ResponseType.CANCEL: return True for res in self.js_results.values(): res['semaphore'].release() self.window.destroy() del BrowserView.instances[self.uid] if self.pywebview_window in windows: windows.remove(self.pywebview_window) self.pywebview_window.events.closed.set() return False def on_drag_data(self, widget, drag_context, x, y, data, info, time): text = data.get_text() if _dnd_state['num_listeners'] > 0 and text: files = [] for value in text.split('\n'): value = value.strip() if value.startswith('file://'): # Handle 'file://' URIs (e.g., `file:///home/user/file.txt`) path = value.replace('file://', '') files.append((os.path.basename(path), path)) elif value.startswith('/') and os.path.exists(value): # Handle direct paths (e.g., `/home/user/file.txt`) files.append((os.path.basename(value), value)) _dnd_state['paths'] += files return False def on_window_state_change(self, window, window_state): if window_state.changed_mask == Gdk.WindowState.ICONIFIED: if ( Gdk.WindowState.ICONIFIED & window_state.new_window_state == Gdk.WindowState.ICONIFIED ): self.pywebview_window.events.minimized.set() else: self.pywebview_window.events.restored.set() elif window_state.changed_mask == Gdk.WindowState.MAXIMIZED: if ( Gdk.WindowState.MAXIMIZED & window_state.new_window_state == Gdk.WindowState.MAXIMIZED ): self.pywebview_window.events.maximized.set() else: self.pywebview_window.events.restored.set() def on_js_bridge_call(self, manager, message): body = json.loads(message.get_js_value().to_string()) if body['funcName'] == '_pywebviewAlert': self.message_box(body['params']) else: js_bridge_call(self.pywebview_window, body['funcName'], body['params'], body['id']) def on_window_resize(self, window, allocation): if allocation.width != self._last_width or allocation.height != self._last_height: self._last_width = allocation.width self._last_height = allocation.height self.pywebview_window.events.resized.set(allocation.width, allocation.height) def on_window_configure(self, window, event): self.pywebview_window.events.moved.set(event.x, event.y) def on_webview_ready(self, arg1, arg2): # in webkit2 notify:visible fires after the window was closed and BrowserView object destroyed. # for a lack of better solution we check that BrowserView has 'webview_ready' attribute if 'shown' in dir(self): self.shown.set() def on_response(self, resource, _): response = resource.get_response() headers = response.get_http_headers() original_headers = self._headers_to_dict(headers) url = resource.get_uri() response_ = Response(url, response.get_status_code(), original_headers) self.pywebview_window.events.response_received.set(response_) def on_request(self, webview, resource, request): if len(self.pywebview_window.events.request_sent) == 0: return if len(self.pywebview_window.events.response_received) > 0: resource.connect('notify::response', self.on_response) headers = request.get_http_headers() original_headers = self._headers_to_dict(headers) url = request.get_uri() method = request.get_http_method() request_ = Request(url, method, original_headers) self.pywebview_window.events.request_sent.set(request_) if ( request_.headers == original_headers or not headers or self.request_headers_mutated or url != self.pywebview_window.real_url ): return missing_headers = { k: v for k, v in request_.headers.items() if k not in original_headers or original_headers[k] != v } extra_headers = { k: str(v) for k, v in original_headers.items() if k not in request_.headers } for k, v in missing_headers.items(): headers.append(k, v) for k in extra_headers: headers.remove(k) # headers.append('X-Handled', 'true') webview.stop_loading() self.request_headers_mutated = True webview.load_request(request) def on_load_finish(self, webview, status): # Show the webview if it's not already visible if not webview.props.opacity: glib.idle_add(webview.set_opacity, 1.0) if status == webkit.LoadEvent.FINISHED and not self.request_headers_mutated: inject_pywebview(renderer, self.js_bridge.window) if self.request_headers_mutated: self.request_headers_mutated = False def on_download_started(self, session, download): download.connect('decide-destination', self.on_download_decide_destination) def on_download_decide_destination(self, download, suggested_filename): destination = self.create_file_dialog( FileDialog.SAVE, glib.get_user_special_dir(glib.UserDirectory.DIRECTORY_DOWNLOAD), False, suggested_filename, (), ) if destination: destination_uri = glib.filename_to_uri(destination[0]) download.set_destination(destination_uri) else: download.cancel() def on_navigation(self, webview, decision, decision_type): if type(decision) == webkit.NavigationPolicyDecision: uri = decision.get_navigation_action().get_request().get_uri() if decision.get_navigation_action().get_frame_name() == '_blank': if settings['OPEN_EXTERNAL_LINKS_IN_BROWSER']: webbrowser.open(uri, 2, True) decision.ignore() else: self.load_url(uri) elif type(decision) == webkit.ResponsePolicyDecision: if not decision.is_mime_type_supported(): self._download_filename = decision.get_response().get_suggested_filename() decision.download() else: decision.use() def on_mouse_release(self, sender, event): self.move_progress = False def on_mouse_press(self, _, event): self.point_diff = [ x - y for x, y in zip(self.window.get_position(), [event.x_root, event.y_root]) ] self.move_progress = True def on_mouse_move(self, _, event): if self.move_progress: point = [x + y for x, y in zip((event.x_root, event.y_root), self.point_diff)] self.window.move(point[0], point[1]) def show(self): self.window.show_all() if gtk.main_level() == 0: if self.pywebview_window.hidden: self.window.hide() else: glib.idle_add(self.window.show_all) def hide(self): glib.idle_add(self.window.hide) def destroy(self): self.window.emit('delete-event', Gdk.Event()) def set_title(self, title): self.window.set_title(title) def toggle_fullscreen(self): if self.is_fullscreen: self.window.unfullscreen() else: self.window.fullscreen() self.is_fullscreen = not self.is_fullscreen 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): self.window.move(self.screen.x + x, self.screen.y + y) def maximize(self): glib.idle_add(self.window.maximize) def minimize(self): glib.idle_add(self.window.iconify) def restore(self): def _restore(): self.window.deiconify() self.window.present() glib.idle_add(_restore) def create_confirmation_dialog(self, title, message): dialog = gtk.MessageDialog( parent=self.window, flags=gtk.DialogFlags.MODAL & gtk.DialogFlags.DESTROY_WITH_PARENT, type=gtk.MessageType.QUESTION, text=title, message_format=message, buttons=gtk.ButtonsType.OK_CANCEL, ) response = dialog.run() dialog.destroy() if response == gtk.ResponseType.OK: return True return False def create_file_dialog(self, dialog_type, directory, allow_multiple, save_filename, file_types): if dialog_type == FileDialog.FOLDER: gtk_dialog_type = gtk.FileChooserAction.SELECT_FOLDER title = self.localization['linux.openFolder'] button = gtk.STOCK_OPEN elif dialog_type == FileDialog.OPEN: gtk_dialog_type = gtk.FileChooserAction.OPEN if allow_multiple: title = self.localization['linux.openFiles'] else: title = self.localization['linux.openFile'] button = gtk.STOCK_OPEN elif dialog_type == FileDialog.SAVE: gtk_dialog_type = gtk.FileChooserAction.SAVE title = self.localization['global.saveFile'] button = gtk.STOCK_SAVE dialog = gtk.FileChooserDialog( title, self.window, gtk_dialog_type, (gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, button, gtk.ResponseType.OK), ) dialog.set_select_multiple(allow_multiple) dialog.set_current_folder(directory) self._add_file_filters(dialog, file_types) if dialog_type == FileDialog.SAVE: dialog.set_current_name(save_filename) response = dialog.run() if response == gtk.ResponseType.OK: if dialog_type == FileDialog.SAVE: file_name = (dialog.get_filename(),) else: file_name = dialog.get_filenames() else: file_name = None dialog.destroy() return file_name def _add_file_filters(self, dialog, file_types): for s in file_types: description, extensions = parse_file_type(s) f = gtk.FileFilter() f.set_name(description) for e in extensions.split(';'): f.add_pattern(e) dialog.add_filter(f) def clear_cookies(self): def _clear_cookies(): self.cookie_manager.delete_all_cookies() glib.idle_add(_clear_cookies) def get_cookies(self): def _get_cookies(): self.cookie_manager.get_cookies(self.webview.get_uri(), None, callback, None) def callback(source, task, data): results = source.get_cookies_finish(task) for c in results: cookie = create_cookie(c.to_set_cookie_header()) cookies.append(cookie) semaphore.release() cookies = [] semaphore = Semaphore(0) glib.idle_add(_get_cookies) semaphore.acquire() return cookies def get_current_url(self): uri = self.webview.get_uri() return uri if uri != 'about:blank' else None def load_url(self, url): self.webview.load_uri(url) def load_html(self, content, base_uri): self.webview.load_html(content, base_uri) def evaluate_js(self, script, parse_json): def _evaluate_js(): try: self.webview.evaluate_javascript( script=script, length=len(script), world_name=None, source_uri=None, cancellable=None, callback=_callback, ) except Exception: logger.exception('Error evaluating JavaScript') result_semaphore.release() def _callback(webview, task): nonlocal result try: value = webview.evaluate_javascript_finish(task) res = self._convert_js_value(value) if parse_json and res: try: result = json.loads(res) except Exception: pass else: result = res except Exception as e: logger.exception(e) result_semaphore.release() result_semaphore = Semaphore(0) result = None glib.idle_add(_evaluate_js) result_semaphore.acquire() return result def message_box(self, message): dialog = gtk.MessageDialog( parent=self.window, flags=gtk.DialogFlags.MODAL & gtk.DialogFlags.DESTROY_WITH_PARENT, type=gtk.MessageType.INFO, buttons=gtk.ButtonsType.OK, message_format=message, ) dialog.run() dialog.destroy() def _convert_js_value(self, js_value): if not js_value or js_value.is_null() or js_value.is_undefined(): return None elif js_value.is_boolean(): return js_value.to_boolean() elif js_value.is_number(): return js_value.to_double() elif js_value.is_string(): return js_value.to_string() elif js_value.is_object(): return json.loads(js_value.to_json(2)) else: logger.error(f'Unsupported JavaScriptCore.Value type: {js_value}') return js_value.to_string() def _headers_to_dict(self, headers): def _assign(k, v): headers_dict[k] = v headers_dict = {} if headers: headers.foreach(_assign) return headers_dict def setup_app(): def set_menubar(app): app.set_menubar(app_menu) global _app if _app is not None: return _app = gtk.Application.new(None, 0) if _state['menu']: app_menu = create_menu(_state['menu']) _app.connect('startup', set_menubar) def create_window(window): global _app def create(): browser = BrowserView(window) browser.show() def create_master_callback(app): create() if window.uid == 'master': main_thread().pydev_do_not_trace = True # vs code debugger hang fix _app.connect('activate', create_master_callback) _app.run() _app = None else: # _app will already have been activated by this point glib.idle_add(create) def set_title(title, uid): def _set_title(): i.set_title(title) i = BrowserView.instances.get(uid) if i: glib.idle_add(_set_title) def destroy_window(uid): def _destroy_window(): i.close_window() i = BrowserView.instances.get(uid) if i: glib.idle_add(_destroy_window) def toggle_fullscreen(uid): def _toggle_fullscreen(): i.toggle_fullscreen() i = BrowserView.instances.get(uid) if i: glib.idle_add(_toggle_fullscreen) def add_tls_cert(certfile): global cert cert = Gio.TlsCertificate.new_from_file(certfile) def set_on_top(uid, top): def _set_on_top(): i.window.set_keep_above(top) i = BrowserView.instances.get(uid) if i: glib.idle_add(_set_on_top) def resize(width, height, uid, fix_point): def _resize(): i.resize(width, height, fix_point) i = BrowserView.instances.get(uid) if i: glib.idle_add(_resize) def move(x, y, uid): def _move(): i.move(x, y) i = BrowserView.instances.get(uid) if i: glib.idle_add(_move) def hide(uid): i = BrowserView.instances.get(uid) if i: glib.idle_add(i.hide) def show(uid): i = BrowserView.instances.get(uid) if i: glib.idle_add(i.show) def maximize(uid): i = BrowserView.instances.get(uid) if i: glib.idle_add(i.maximize) def minimize(uid): i = BrowserView.instances.get(uid) if i: glib.idle_add(i.minimize) def restore(uid): i = BrowserView.instances.get(uid) if i: glib.idle_add(i.restore) def clear_cookies(uid): i = BrowserView.instances.get(uid) if i: i.clear_cookies() def get_cookies(uid): i = BrowserView.instances.get(uid) if i: cookies = i.get_cookies() return cookies def get_current_url(uid): def _get_current_url(): result['url'] = i.get_current_url() semaphore.release() result = {} semaphore = Semaphore(0) i = BrowserView.instances.get(uid) if not i: return glib.idle_add(_get_current_url) semaphore.acquire() return result['url'] def load_url(url, uid): def _load_url(): i.load_url(url) i = BrowserView.instances.get(uid) if i: glib.idle_add(_load_url) def load_html(content, base_uri, uid): def _load_html(): i.load_html(content, base_uri) i = BrowserView.instances.get(uid) if i: glib.idle_add(_load_html) def create_confirmation_dialog(title, message, uid): def _create(): nonlocal result result = i.create_confirmation_dialog(title, message) result_semaphore.release() i = BrowserView.instances.get(uid) result_semaphore = Semaphore(0) result = -1 if i: glib.idle_add(_create) result_semaphore.acquire() return result def create_menu(app_menu_list): def action_callback(action, parameter): function = _app_actions.get(action.get_name()) if function is None: return # Don't run action function on main thread Thread(target=function).start() def create_submenu(title, line_items, supermenu, action_prepend=''): m = Gio.Menu.new() current_section = Gio.Menu.new() action_prepend = f'{action_prepend}_{title}' for menu_line_item in line_items: if isinstance(menu_line_item, MenuSeparator): m.append_section(None, current_section) current_section = Gio.Menu.new() elif isinstance(menu_line_item, MenuAction): action_label = f'{action_prepend}_{menu_line_item.title}'.replace(' ', '_') while action_label in _app_actions.keys(): action_label += '_' _app_actions[action_label] = menu_line_item.function new_action = Gio.SimpleAction.new(action_label, None) new_action.connect('activate', action_callback) _app.add_action(new_action) current_section.append(menu_line_item.title, 'app.' + action_label) elif isinstance(menu_line_item, Menu): create_submenu( menu_line_item.title, menu_line_item.items, current_section, action_prepend=action_prepend, ) m.append_section(None, current_section) supermenu.append_submenu(title, m) menubar = Gio.Menu() for app_menu in app_menu_list: # Ignore '__app__' menus (macOS-only feature) if app_menu.title == '__app__': continue create_submenu(app_menu.title, app_menu.items, menubar) return menubar def get_active_window(): active_window = None try: active_window = _app.get_active_window() except Exception: return None active_window_number = active_window.get_id() for uid, browser_view_instance in BrowserView.instances.items(): if browser_view_instance.window.get_id() == active_window_number: return browser_view_instance.pywebview_window return None def create_file_dialog(dialog_type, directory, allow_multiple, save_filename, file_types, uid): i = BrowserView.instances.get(uid) file_name_semaphore = Semaphore(0) file_names = [] def _create(): result = i.create_file_dialog( dialog_type, directory, allow_multiple, save_filename, file_types ) if result is None: file_names.append(None) else: file_names.append(tuple(result)) file_name_semaphore.release() glib.idle_add(_create) file_name_semaphore.acquire() return file_names[0] def evaluate_js(script, uid, parse_json=True): i = BrowserView.instances.get(uid) if i: return i.evaluate_js(script, parse_json) def get_position(uid): def _get_position(): result['position'] = i.window.get_position() semaphore.release() i = BrowserView.instances.get(uid) if not i: return result = {} semaphore = Semaphore(0) glib.idle_add(_get_position) semaphore.acquire() return result['position'] def get_size(uid): def _get_size(): result['size'] = i.window.get_size() semaphore.release() i = BrowserView.instances.get(uid) if not i: return result = {} semaphore = Semaphore(0) glib.idle_add(_get_size) semaphore.acquire() return result['size'] def get_screens(): display = Gdk.Display.get_default() n = display.get_n_monitors() monitors = [Gdk.Display.get_monitor(display, i) for i in range(n)] geometries = [Gdk.Monitor.get_geometry(m) for m in monitors] screens = [Screen(geom.x, geom.y, geom.width, geom.height, geom) for geom in geometries] return screens def configure_transparency(c): c.set_visual(c.get_screen().get_rgba_visual()) c.override_background_color(gtk.StateFlags.ACTIVE, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.BACKDROP, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.DIR_LTR, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.DIR_RTL, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.FOCUSED, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.INCONSISTENT, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.INSENSITIVE, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.PRELIGHT, Gdk.RGBA(0, 0, 0, 0)) c.override_background_color(gtk.StateFlags.SELECTED, Gdk.RGBA(0, 0, 0, 0)) transparentWindowStyleProvider = gtk.CssProvider() transparentWindowStyleProvider.load_from_data( b""" GtkWindow { background-color:rgba(0,0,0,0); background-image:none; }""" ) c.get_style_context().add_provider( transparentWindowStyleProvider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION )