From 91fd502e50b6cd404f974345101def6053680c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Gon=C3=A7alves?= Date: Fri, 12 Jan 2024 18:45:11 -0300 Subject: [PATCH] Add --gpu in qt interface and better comments in code --- bigbashview/usr/lib/bbv/main.py | 79 ++++++++++++++++++-- bigbashview/usr/lib/bbv/server/bbv2server.py | 29 ++++++- bigbashview/usr/lib/bbv/server/views.py | 39 +++++++--- bigbashview/usr/lib/bbv/ui/gtk.py | 14 +++- bigbashview/usr/lib/bbv/ui/qt.py | 69 +++++++++++------ 5 files changed, 183 insertions(+), 47 deletions(-) diff --git a/bigbashview/usr/lib/bbv/main.py b/bigbashview/usr/lib/bbv/main.py index d73ffbf..43b1e9c 100644 --- a/bigbashview/usr/lib/bbv/main.py +++ b/bigbashview/usr/lib/bbv/main.py @@ -17,6 +17,41 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# +##################################################################### +# +# This program is a script to run Bash+HTML in a Desktop WebView. +# It uses the BigBashView library. +# +# The script takes command line arguments to configure the WebView window. +# It supports options such as URL/File, background color, work directory, +# window icon, window title, process name, window size, rendering toolkit, +# window state, and GPU rendering activation. +# +# The script initializes the Main class, which sets up the argument parser +# and parses the command line arguments. It then checks the specified +# directory for files that autoload with the -d/--directory option. +# If a matching file is found, it sets the URL to that file. +# +# The script then checks for the availability of the rendering toolkits, +# QtWebEngine and WebKitGTK2. If both are unavailable, it displays an error +# message and exits. If only one toolkit is available, it sets the toolkit +# to that one. If both toolkits are available, it sets the toolkit to 'auto' +# and tries to import the QtWebEngine toolkit first. If it fails, it imports +# the WebKitGTK2 toolkit. +# +# After setting up the toolkit, the script creates an instance of the +# corresponding window class (gtk.Window or qt.Window) and sets the window +# properties based on the command line arguments. It then runs the window, +# loading the specified URL and starting the server if specified. +# +# The Main class also includes a nested class, formatter, which is a helper +# class for formatting the help message of the argument parser. +# +# The script imports the necessary modules and defines the Main class. +# It then creates an instance of the Main class and calls its run() method +# to start the script. + import argparse import sys import os @@ -26,12 +61,14 @@ class Main: # Start bbv # def __init__(self): + # Helper class for formatting the help message def formatter(prog): return argparse.RawTextHelpFormatter( prog, max_help_position=100, width=None) + # Create the argument parser parser = argparse.ArgumentParser( prog='bigbashview', description=''' @@ -74,6 +111,7 @@ def formatter(prog): ''', formatter_class=formatter) + # Define the command line arguments parser.add_argument('url', default='/', nargs='?', help='URL/File') parser.add_argument( '-v', '--version', @@ -104,13 +142,19 @@ def formatter(prog): '-w', '--window_state', default=None, help='''Window state: fullscreen, maximized, fixed, frameless, alwaystop, framelesstop, maximizedframelesstop''') + parser.add_argument( + '-g', '--gpu', action='store_true', + help='Activate GPU rendering') + # Parse the command line arguments args = parser.parse_args() self.url = args.url + # Check if the specified directory exists if args.directory and os.path.isdir(args.directory): os.chdir(args.directory) if self.url == '/': + # Check for files that autoload with the -d/--directory option files = list(filter(os.path.isfile, os.listdir())) if files: for file in files: @@ -122,15 +166,19 @@ def formatter(prog): self.url = f'./{file}' break + # Set the global window title if specified if args.name: globaldata.TITLE = args.name + # Set the global window icon if the file exists if os.path.exists(args.icon): globaldata.ICON = args.icon + # Set the process name if specified if args.process: setproctitle(args.process) + # Parse the window size argument geom = args.size.split('x') try: width, height = geom @@ -140,18 +188,21 @@ def formatter(prog): parser.print_help() sys.exit(1) + # Parse the background color argument if args.color in ['black', 'transparent', None]: self.color = args.color else: parser.print_help() sys.exit(1) + # Parse the rendering toolkit argument if args.toolkit in ['auto', 'qt', 'gtk']: self.toolkit = args.toolkit else: parser.print_help() sys.exit(1) + # Parse the window state argument if args.window_state in [ 'fullscreen', 'maximized', 'fixed', 'frameless', @@ -163,7 +214,7 @@ def formatter(prog): sys.exit(1) check_qt = check_gtk = False - # construct window + # Construct the window if self.toolkit == 'auto': try: from bbv.ui import qt @@ -202,17 +253,28 @@ def formatter(prog): print(e) print('Please install PyQt5') sys.exit(1) - os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-logging --disable-gpu --no-sandbox --single-process --disable-gpu-compositing --autoplay-policy=no-user-gesture-required --font-render-hinting=none --disable-back-forward-cache --aggressive-cache-discard --disable-features=BackForwardCache,CacheCodeOnIdle,ConsumeCodeCacheOffThread --disable-breakpad --skia-resource-cache-limit-mb=1' - os.environ['QT_QUICK_BACKEND'] = 'software' - os.environ['QSG_RENDER_LOOP'] = 'basic' - os.environ['QT_XCB_GL_INTEGRATION'] = 'none' - os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = '1' + + if args.gpu: + # Enable GPU rendering, with good support in effects like blur + os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-logging --no-sandbox --single-process --autoplay-policy=no-user-gesture-required --disable-back-forward-cache --aggressive-cache-discard --disable-features=BackForwardCache,CacheCodeOnIdle,ConsumeCodeCacheOffThread --enable-features=Vulkan,VulkanFromANGLE,DefaultANGLEVulkan --use-gl=angle --disable-breakpad --skia-resource-cache-limit-mb=1' + os.environ['QSG_RENDER_LOOP'] = 'basic' + os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = '1' + else: + # Disable GPU rendering, open faster and use less memory + os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-logging --disable-gpu --no-sandbox --single-process --disable-gpu-compositing --autoplay-policy=no-user-gesture-required --disable-back-forward-cache --aggressive-cache-discard --disable-features=BackForwardCache,CacheCodeOnIdle,ConsumeCodeCacheOffThread --disable-breakpad --skia-resource-cache-limit-mb=1' + os.environ['QT_QUICK_BACKEND'] = 'software' + os.environ['QSG_RENDER_LOOP'] = 'basic' + os.environ['QT_XCB_GL_INTEGRATION'] = 'none' + os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = '1' self.window = qt.Window() def run(self, start_server=True): + # Import the run_server function from bbv2server module from bbv.server.bbv2server import run_server + # Start the server if specified server = run_server() if start_server else None + # Check if the URL is a local file if self.url.find('://') == -1: if not self.url.startswith('/'): self.url = '/'+self.url @@ -221,10 +283,13 @@ def run(self, start_server=True): globaldata.PORT, self.url) + # Set the window size, style, viewer, and load the URL self.window.set_size(self.width, self.height, self.window_state) self.window.style(self.color) self.window.viewer(self.window_state) self.window.load_url(self.url) + # Run the window self.window.run() + # Stop the server if started if server: - server.stop() + server.stop() \ No newline at end of file diff --git a/bigbashview/usr/lib/bbv/server/bbv2server.py b/bigbashview/usr/lib/bbv/server/bbv2server.py index d9aba98..66557c2 100644 --- a/bigbashview/usr/lib/bbv/server/bbv2server.py +++ b/bigbashview/usr/lib/bbv/server/bbv2server.py @@ -8,6 +8,7 @@ from . import views import os +# Define a custom URL handler class class FileHandler(views.url_handler): __url__ = "/api/file" @@ -17,13 +18,16 @@ def resolve_filename(self, filename): return filename.replace("$HOME", home_dir) def handle_request(self, method): + # Get the filename from the request parameters params = web.input() filename = params.get('filename') if not filename: + # If no filename is specified, return an error response web.ctx.status = '400 Bad Request' return json.dumps({"error": "No filename specified"}) - filename = self.resolve_filename(filename) # Resolve $HOME + # Resolve $HOME in the filename + filename = self.resolve_filename(filename) # Delegate to the actual HTTP method return method(filename) @@ -31,36 +35,45 @@ def handle_request(self, method): def GET(self): def method(filename): try: + # Read the contents of the file and return as JSON with open(filename, 'r') as f: data = json.load(f) return json.dumps(data) except FileNotFoundError: + # If the file is not found, return an error response web.ctx.status = '404 Not Found' return json.dumps({"error": f"File {filename} not found"}) except json.JSONDecodeError: + # If the file contains invalid JSON, return an error response web.ctx.status = '400 Bad Request' return json.dumps({"error": f"Could not decode JSON in {filename}"}) except Exception as e: + # If any other exception occurs, return an error response web.ctx.status = '500 Internal Server Error' return json.dumps({"error": str(e)}) return self.handle_request(method) def POST(self): def method(filename): + # Parse the JSON data from the request body data = json.loads(web.data().decode()) try: + # Write the JSON data to the file with open(filename, 'w') as f: json.dump(data, f) return json.dumps({"success": True}) except Exception as e: + # If any exception occurs, return an error response web.ctx.status = '500 Internal Server Error' return json.dumps({"error": str(e)}) return self.handle_request(method) def PUT(self): def method(filename): + # Parse the JSON data from the request body data = json.loads(web.data().decode()) try: + # Update the existing JSON data in the file with open(filename, 'r+') as f: existing_data = json.load(f) existing_data.update(data) @@ -69,12 +82,15 @@ def method(filename): f.truncate() return json.dumps({"success": True}) except FileNotFoundError: + # If the file is not found, return an error response web.ctx.status = '404 Not Found' return json.dumps({"error": f"File {filename} not found"}) except json.JSONDecodeError: + # If the file contains invalid JSON, return an error response web.ctx.status = '400 Bad Request' return json.dumps({"error": f"Could not decode JSON in {filename}"}) except Exception as e: + # If any other exception occurs, return an error response web.ctx.status = '500 Internal Server Error' return json.dumps({"error": str(e)}) return self.handle_request(method) @@ -82,16 +98,20 @@ def method(filename): def DELETE(self): def method(filename): try: + # Delete the file os.remove(filename) return json.dumps({"success": True}) except FileNotFoundError: + # If the file is not found, return an error response web.ctx.status = '404 Not Found' return json.dumps({"error": f"File {filename} not found"}) except Exception as e: + # If any other exception occurs, return an error response web.ctx.status = '500 Internal Server Error' return json.dumps({"error": str(e)}) return self.handle_request(method) +# Define a custom server class class Server(threading.Thread): def _get_subclasses(self, classes=None): """ Get subclasses recursively """ @@ -114,7 +134,7 @@ def get_urls(self): def get_classes(self): """ Return all view classes. """ classes = self._get_subclasses() - classes.append(FileHandler) # Adicionar a nova classe + classes.append(FileHandler) # Add the new class result = {} for cls in classes: result[cls.__name__] = cls @@ -135,8 +155,9 @@ def run(self): def stop(self): os.kill(os.getpid(), 15) - +# Function to run the server def run_server(ip='127.0.0.1', background=True): + # Find an available port to bind the server soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) for port in range(19000, 19100): try: @@ -176,6 +197,6 @@ def run_server(ip='127.0.0.1', background=True): return server - +# Run the server if the script is executed directly if __name__ == "__main__": run_server(background=False) diff --git a/bigbashview/usr/lib/bbv/server/views.py b/bigbashview/usr/lib/bbv/server/views.py index d61e3f6..1b9a80a 100644 --- a/bigbashview/usr/lib/bbv/server/views.py +++ b/bigbashview/usr/lib/bbv/server/views.py @@ -28,21 +28,20 @@ import web from shutil import which - - +# Handler for the root URL class url_handler(object): __url__ = '/' + # Handle GET requests def GET(self, name=''): return self.parse_and_call(web.ctx.query[1:], name) + # Handle POST requests def POST(self, name=''): return self.parse_and_call(web.data(), name) + # Parse the query string and call the appropriate method def parse_and_call(self, qs, name): - user_agent = web.ctx.env.get('HTTP_USER_AGENT') - if user_agent != "BigBashView-Agent": - raise web.Forbidden() if web.ctx.ip != '127.0.0.1': raise web.Forbidden() qs = parse_qs(qs) @@ -50,6 +49,7 @@ def parse_and_call(self, qs, name): html = self.called(options, content, qs) return html + # Get and set default options for the request def _get_set_default_options(self, options): optlist = options.split('$') if len(optlist) == 1: @@ -64,20 +64,25 @@ def _get_set_default_options(self, options): return optlist, content + # Placeholder method to be implemented by subclasses def called(self, options, content, query): raise NotImplementedError +# Handler for the favicon.ico URL class favicon_handler(url_handler): __url__ = '/favicon.ico' + # Do nothing for favicon requests def called(self, options, content, query): return +# Handler for the /content URL class content_handler(url_handler): __url__ = '/content(.*)' + # Process the content file and return the result def called(self, options, content, query): try: with open(content) as arq: @@ -91,6 +96,7 @@ def called(self, options, content, query): web.ctx.status = '404 Not Found' return "File not found" + # Process include statements in the HTML content def process_includes(self, html_content): pattern = r'<\?include (\w+)(.*?)\?>' matches = re.finditer(pattern, html_content, re.DOTALL) @@ -112,18 +118,20 @@ def process_includes(self, html_content): return html_content + # Include an HTML file def include_html(self, html_content, file_path, original_string): full_path = os.path.join(os.getcwd(), file_path) try: with open(full_path, 'r') as file: included_html = file.read() - # Processar includes no conteúdo incluído + # Process includes in the included content included_html = self.process_includes(included_html) html_content = html_content.replace(original_string, included_html) except FileNotFoundError: html_content = html_content.replace(original_string, f"File {full_path} not found") return html_content + # Include a bash script def include_bash(self, html_content, script, original_string): try: result = subprocess.check_output(['bash', '-c', script], stderr=subprocess.STDOUT) @@ -132,6 +140,7 @@ def include_bash(self, html_content, script, original_string): html_content = html_content.replace(original_string, f"Error executing bash script: {e.output.decode()}") return html_content + # Include a PHP script def include_php(self, html_content, script, original_string): try: result = subprocess.check_output(['php', '-r', script], stderr=subprocess.STDOUT) @@ -140,6 +149,7 @@ def include_php(self, html_content, script, original_string): html_content = html_content.replace(original_string, f"Error executing PHP script: {e.output.decode()}") return html_content + # Include a Python script def include_python(self, html_content, script, original_string): try: result = subprocess.check_output(['python', '-c', script], stderr=subprocess.STDOUT) @@ -148,6 +158,7 @@ def include_python(self, html_content, script, original_string): html_content = html_content.replace(original_string, f"Error executing Python script: {e.output.decode()}") return html_content + # Include a Node.js script def include_node(self, html_content, script, original_string): try: result = subprocess.check_output(['node', '-e', script], stderr=subprocess.STDOUT) @@ -156,9 +167,12 @@ def include_node(self, html_content, script, original_string): html_content = html_content.replace(original_string, f"Error executing Node.js script: {e.output.decode()}") return html_content + +# Handler for the /execute URL class execute_handler(url_handler): __url__ = '/execute(.*)' + # Execute a command and return the output def _execute(self, command, extra_env={}): env = os.environ.copy() env['bbv_ip'] = str(globaldata.ADDRESS) @@ -187,6 +201,7 @@ def _execute(self, command, extra_env={}): return po.communicate() + # Handle the execute request def called(self, options, content, query): (stdout, stderr) = self._execute( content, extra_env=get_env_for_shell(query)) @@ -195,22 +210,27 @@ def called(self, options, content, query): return stdout +# Handler for the default URL class default_handler(url_handler): __url__ = '(.*)' + # Handle the default request def called(self, options, content, query): if content not in ("", "/"): return self.bbv_compat_mode(options, content, query) else: return HTML + # Parse and call the appropriate method def parse_and_call(self, qs, name): self.original_qs = to_s(qs) return url_handler.parse_and_call(self, qs, name) + + # Handle the request in BBV compatibility mode def bbv_compat_mode(self, options, content, query): execute_ext = ('.sh', '.sh.html', '.sh.htm', '.sh.php', '.sh.py', '.sh.lua', '.sh.rb', '.sh.pl', - '.sh.lisp', '.sh.jl', '.run', '.sh.js', '.sh.css', '.sh.js') # Adicione .sh.js aqui + '.sh.lisp', '.sh.jl', '.run', '.sh.js', '.sh.css', '.sh.js') # Add .sh.js here content_ext = ('.htm', '.html') content_plain_ext = ('.txt') content_css_ext = ('.css') @@ -229,11 +249,11 @@ def bbv_compat_mode(self, options, content, query): os.chmod(content, os.stat(content).st_mode | stat.S_IEXEC) except Exception: globaldata.ROOT_FILE = True - if content.endswith('.sh.css'): # Já existente para .sh.css + if content.endswith('.sh.css'): # Existing condition for .sh.css web.header('Content-Type', 'text/css; charset=UTF-8') execute_content = " ".join((content, unquote(self.original_qs))) return execute_handler().called(options, execute_content, query) - if content.endswith('.sh.js'): # Nova condição para .sh.js + if content.endswith('.sh.js'): # New condition for .sh.js web.header('Content-Type', 'text/javascript; charset=UTF-8') execute_content = " ".join((content, unquote(self.original_qs))) return execute_handler().called(options, execute_content, query) @@ -257,4 +277,3 @@ def bbv_compat_mode(self, options, content, query): return content_handler().called(options, content, query) # Default option return content_handler().called(options, content, query) - diff --git a/bigbashview/usr/lib/bbv/ui/gtk.py b/bigbashview/usr/lib/bbv/ui/gtk.py index 99036b4..c3d902b 100644 --- a/bigbashview/usr/lib/bbv/ui/gtk.py +++ b/bigbashview/usr/lib/bbv/ui/gtk.py @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see + import os import gi gi.require_version("Gtk", "3.0") @@ -22,7 +23,6 @@ from gi.repository import Gtk, WebKit2, Gdk from bbv.globaldata import ICON, TITLE - class Window(Gtk.Window): def __init__(self): Gtk.Window.__init__(self) @@ -46,18 +46,22 @@ def __init__(self): self.connect("key-press-event", self.key) def key(self, webview, event): + # Reload the webview when the F5 key is pressed if event.keyval == 65474: self.webview.reload() + # Show the web inspector when the F12 key is pressed if event.keyval == 65481: inspector = self.webview.get_inspector() inspector.show() def add_script(self, webview, event): + # Add a JavaScript function to the webview that can be called from the loaded webpage script = "function _run(run){fetch('/execute$'+run);}" if event == WebKit2.LoadEvent.FINISHED: self.webview.run_javascript(script) def viewer(self, window_state): + # Set the window state based on the provided argument if window_state == "maximized": self.maximize() self.show() @@ -84,21 +88,26 @@ def viewer(self, window_state): @staticmethod def run(): + # Start the Gtk main loop Gtk.main() @staticmethod def close_window(webview): + # Quit the Gtk main loop when the webview is closed Gtk.main_quit() def title_changed(self, webview, title): + # Set the window title and WM_CLASS property based on the webview's title title = self.webview.get_title() os.system(f"xprop -id $(xprop -root '\t$0' _NET_ACTIVE_WINDOW|cut -f2) -f WM_CLASS 8s -set WM_CLASS '{title}'") self.set_title(title) def load_url(self, url): + # Load the specified URL in the webview self.webview.load_uri(url) def set_size(self, width, height, window_state): + # Set the window size and position based on the provided arguments display = Gdk.Display.get_primary_monitor(Gdk.Display.get_default()) size = display.get_geometry() if width <= 0: @@ -112,6 +121,7 @@ def set_size(self, width, height, window_state): self.set_resizable(False) def style(self, colorful): + # Set the window and webview background color based on the provided argument if colorful == "black": self.override_background_color( Gtk.StateFlags.NORMAL, @@ -150,4 +160,4 @@ def style(self, colorful): else: color = self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL) self.override_background_color(Gtk.StateFlags.NORMAL, color) - self.webview.set_background_color(color) + self.webview.set_background_color(color) \ No newline at end of file diff --git a/bigbashview/usr/lib/bbv/ui/qt.py b/bigbashview/usr/lib/bbv/ui/qt.py index d381abc..f01c157 100644 --- a/bigbashview/usr/lib/bbv/ui/qt.py +++ b/bigbashview/usr/lib/bbv/ui/qt.py @@ -5,18 +5,33 @@ # Copyright (C) 2009 Bruno Goncalves Araujo # Copyright (C) 2021 Elton Fabrício Ferreira # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# This file contains the implementation of a Qt-based UI for the BigBashView application. +# It provides a window with a web view and various features such as download handling, +# page inspection, and window customization. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# The code is divided into several sections: +# 1. Importing necessary modules and packages +# 2. Setting up translations for internationalization +# 3. Defining the main window class +# 4. Implementing various methods for handling events and functionality +# 5. Running the application # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# The main window class, Window, inherits from QWidget and contains the following features: +# - A QWebEngineView widget for displaying web content +# - A QWebEngineView widget for inspecting web pages +# - Shortcuts for reloading the web page and opening the developer tools +# - A QSplitter widget for dividing the window into two panes +# - Methods for handling download requests and feature permissions +# - Methods for customizing the window appearance and behavior +# - Methods for setting the window title and loading URLs +# - Methods for managing the window size and position +# - Methods for setting the window background color +# +# The Window class also includes a main method, run, which starts the application event loop. +# +# Note: This code is licensed under the GNU General Public License version 3 or later. +# For more details, see http://www.gnu.org/licenses/ + import sys import os from PySide6.QtCore import QUrl, Qt @@ -27,8 +42,7 @@ from bbv.globaldata import ICON, TITLE - -# Import gettext module +# Import gettext module for translations import gettext lang_translations = gettext.translation( "bigbashview", @@ -37,36 +51,39 @@ ) lang_translations.install() -# define _ shortcut for translations +# Define _ shortcut for translations _ = lang_translations.gettext class Window(QWidget): def __init__(self): + # Initialize the application and web view self.app = QApplication(sys.argv) self.web = QWebEngineView() self.web.page().profile().setHttpUserAgent("BigBashView-Agent") super().__init__() self.web = QWebEngineView() self.inspector = QWebEngineView() - # self.web.settings().setAttribute( - # self.web.settings().AutoLoadIconsForPage, False) + + # Set window icon and title self.setWindowIcon(QIcon(ICON)) if TITLE: self.app.setApplicationName(TITLE) self.setWindowTitle(TITLE) else: self.web.titleChanged.connect(self.title_changed) + + # Connect various signals to their respective slots self.web.page().windowCloseRequested.connect(self.close_window) self.web.page().featurePermissionRequested.connect(self.onFeature) - # self.web.iconChanged.connect(self.icon_changed) self.web.loadFinished.connect(self.add_script) self.key_f5 = QShortcut(QKeySequence(Qt.Key_F5), self.web) self.key_f5.activated.connect(self.web.reload) self.key_f12 = QShortcut(QKeySequence(Qt.Key_F12), self.web) self.key_f12.activated.connect(self.devpage) + self.web.page().profile().downloadRequested.connect(self.onDownloadRequested) - # pagesplitter + # Set up the layout and splitter for the main window self.hbox = QHBoxLayout(self) self.hbox.setContentsMargins(0, 0, 0, 0) self.splitter = QSplitter(Qt.Horizontal) @@ -74,11 +91,10 @@ def __init__(self): self.splitter2 = QSplitter(Qt.Horizontal) self.hbox.addWidget(self.splitter) self.setLayout(self.hbox) - self.web.page().profile().downloadRequested.connect(self.onDownloadRequested) def onDownloadRequested(self, download: QWebEngineDownloadRequest): + # Handle download requests by showing a file dialog for saving the file home_directory = os.path.expanduser('~') - save_path, _ = QFileDialog.getSaveFileName(self, download.suggestedFileName(), os.path.join(home_directory, download.suggestedFileName())) if save_path: save_directory = os.path.dirname(save_path) @@ -88,6 +104,7 @@ def onDownloadRequested(self, download: QWebEngineDownloadRequest): download.accept() def onFeature(self, url, feature): + # Handle feature permission requests by granting or denying the requested feature if feature in ( QWebEnginePage.Feature.MediaAudioCapture, QWebEnginePage.Feature.MediaVideoCapture, @@ -105,8 +122,8 @@ def onFeature(self, url, feature): QWebEnginePage.PermissionDeniedByUser ) - def devpage(self): + # Toggle between displaying the web view and the inspector view in the splitter if self.splitter.count() == 1 or self.splitter2.count() == 1: self.inspector.page().setInspectedPage(self.web.page()) self.splitter2.hide() @@ -124,10 +141,9 @@ def devpage(self): self.setLayout(self.hbox) def add_script(self, event): + # Add a JavaScript function to the web page for executing commands script = """ function _run(run){ - //https://dmitripavlutin.com/javascript-fetch-async-await/#4-canceling-a-fetch-request - // Step 1: instantiate the abort controller const controller = new AbortController(); setTimeout(() => controller.abort(), 1000); @@ -140,6 +156,7 @@ def add_script(self, event): self.web.page().runJavaScript(script) def viewer(self, window_state): + # Set the window state based on the provided argument if window_state == "maximized": self.setWindowState(Qt.WindowMaximized) self.show() @@ -163,20 +180,25 @@ def viewer(self, window_state): self.show() def run(self): + # Start the application event loop self.app.exec_() def close_window(self): + # Quit the application when the window is closed self.app.quit() def title_changed(self, title): + # Set the window title and WM_CLASS property when the web page title changes os.system(f"xprop -id $(xprop -root '\t$0' _NET_ACTIVE_WINDOW|cut -f2) -f WM_CLASS 8s -set WM_CLASS '{title}'") self.setWindowTitle(title) def load_url(self, url): + # Load the specified URL in the web view self.url = QUrl.fromEncoded(url.encode("utf-8")) self.web.load(self.url) def set_size(self, width, height, window_state): + # Set the window size and position based on the provided arguments display = self.app.screenAt(QCursor().pos()) size = display.availableGeometry() if width <= 0: @@ -193,13 +215,12 @@ def set_size(self, width, height, window_state): self.setFixedSize(width, height) def style(self, colorful): + # Set the window background color based on the provided argument if colorful == 'black': self.web.page().setBackgroundColor(QColor.fromRgbF(0, 0, 0, 1)) - elif colorful == "transparent": self.setAttribute(Qt.WA_TranslucentBackground) self.web.page().setBackgroundColor(QColor.fromRgbF(0, 0, 0, 0)) - elif os.environ.get('XDG_CURRENT_DESKTOP') == 'KDE': rgb = os.popen("kreadconfig5 --group WM --key activeBackground").read().split(',')