# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging import subprocess from enum import Enum from odoo.addons.hw_drivers.tools import helpers _logger = logging.getLogger(__name__) MIN_IMAGE_VERSION = 24.10 CHROMIUM_ARGS = [ '--incognito', '--disable-infobars', '--noerrdialogs', '--no-first-run', '--bwsi', # Use chromium without signing in '--disable-extensions', # Disable extensions as they fill up /tmp '--disk-cache-dir=/dev/null', # Disable disk cache '--disk-cache-size=1', # Set disk cache size to 1 byte '--log-level=3', # Reduce amount of logs ] class BrowserState(Enum): """Enum to represent the state of the browser""" NORMAL = 'normal' KIOSK = 'kiosk' FULLSCREEN = 'fullscreen' class Browser: """Methods to interact with a browser""" def __init__(self, url, _x_screen, env): """ :param url: URL to open in the browser :param _x_screen: X screen number :param env: Environment variables (e.g. os.environ.copy()) :param kiosk: Whether the browser should be in kiosk mode """ self.url = url # helpers.get_version returns a string formatted as: (L: Linux, W: Windows) self.browser = 'chromium-browser' if float(helpers.get_version()[1:]) >= MIN_IMAGE_VERSION else 'firefox' self.browser_process_name = 'chromium' if self.browser == 'chromium-browser' else self.browser self.state = BrowserState.NORMAL self._x_screen = _x_screen self._set_environment(env) def _set_environment(self, env): """ Set the environment variables for the browser :param env: Environment variables (os.environ.copy()) """ self.env = env self.env['DISPLAY'] = f':0.{self._x_screen}' self.env['XAUTHORITY'] = '/run/lightdm/pi/xauthority' for key in ['HOME', 'XDG_RUNTIME_DIR', 'XDG_CACHE_HOME']: self.env[key] = '/tmp/' + self._x_screen def open_browser(self, url=None, state=BrowserState.NORMAL): """ open the browser with the given URL, or reopen it if it is already open :param url: URL to open in the browser :param state: State of the browser (normal, kiosk, fullscreen) """ self.url = url or self.url self.state = state # Reopen to take new url or additional args into account self.close_browser() browser_args = list(CHROMIUM_ARGS) if self.browser == 'chromium-browser' else [] if state == BrowserState.KIOSK: browser_args.append("--kiosk") elif state == BrowserState.FULLSCREEN: browser_args.append("--start-fullscreen") subprocess.Popen( [ self.browser, self.url, *browser_args, ], env=self.env, ) if self.browser == 'firefox' and state == BrowserState.FULLSCREEN: # Firefox does not support fullscreen via command line argument, so we use a keypress self.xdotool_keystroke('F11') helpers.save_browser_state(url=self.url) def close_browser(self): """close the browser""" # Kill browser instance (can't `instance.pkill()` as we can't keep the instance after Odoo service restarts) # We need to terminate it because Odoo will create a new instance each time it is restarted. subprocess.run(['pkill', self.browser_process_name], check=False) def xdotool_keystroke(self, keystroke): """ Execute a keystroke using xdotool :param keystroke: Keystroke to execute """ subprocess.run([ 'xdotool', 'search', '--sync', '--onlyvisible', '--screen', self._x_screen, '--class', self.browser_process_name, 'key', keystroke, ], check=False) def xdotool_type(self, text): """ Type text using xdotool :param text: Text to type """ subprocess.run([ 'xdotool', 'search', '--sync', '--onlyvisible', '--screen', self._x_screen, '--class', self.browser_process_name, 'type', text, ], check=False) def open_new_tab(self, url): """ Open a new tab with the given URL :param url: URL to open in the new tab """ self.url = url self.xdotool_keystroke('ctrl+t') self.xdotool_type(self.url) self.xdotool_keystroke('Return') def refresh(self): """Refresh the current tab""" self.xdotool_keystroke('ctrl+r') def disable_kiosk_mode(self): """Removes arguments to chromium-browser cli to open it without kiosk mode""" if self.state == BrowserState.KIOSK: self.open_browser(state=BrowserState.FULLSCREEN)