File: browser.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (143 lines) | stat: -rw-r--r-- 4,978 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# 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|W><version> (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)