configure_url_forwarder.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. #!/usr/bin/python3
  2. # Copyright 2021 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import argparse
  6. import hashlib
  7. import json
  8. import logging
  9. import os
  10. import shutil
  11. import socket
  12. import subprocess
  13. import sys
  14. from pathlib import Path
  15. from typing import Callable
  16. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  17. HOST_HASH = hashlib.md5(socket.gethostname().encode()).hexdigest()
  18. URL_FORWARDER_DESKTOP_ENTRY = 'crd-url-forwarder.desktop'
  19. # XFCE4's default settings app changes default browser setting on Cinnamon/GNOME
  20. # to xfce4-web-browser, which redirects GNOME apps to use the XFCE default
  21. # browser setting.
  22. XFCE4_WEB_BROWSER_DESKTOP_ENTRY = 'xfce4-web-browser.desktop'
  23. HOST_SETTINGS_PATH = os.path.join(
  24. os.environ['HOME'],
  25. '.config/chrome-remote-desktop/host#{}.settings.json'.format(HOST_HASH))
  26. X_SESSIONS_DIR = '/usr/share/xsessions/'
  27. XDG_SETTING_DEFAULT_WEB_BROWSER = 'default-web-browser'
  28. def env_with_current_desktop(desktop_env: str) -> dict[str, str]:
  29. """Returns an environment variable dictionary with XDG_CURRENT_DESKTOP set to
  30. |desktop_env|."""
  31. env = os.environ.copy()
  32. env['XDG_CURRENT_DESKTOP'] = desktop_env
  33. return env
  34. def get_default_browser(desktop_env: str) -> str:
  35. """Returns the XDG default-web-browser setting for the given desktop
  36. environment."""
  37. args = ['xdg-settings', 'get', XDG_SETTING_DEFAULT_WEB_BROWSER]
  38. env = env_with_current_desktop(desktop_env)
  39. try:
  40. return subprocess.check_output(args, env=env).decode('utf-8').strip()
  41. except CalledProcessError as e:
  42. print('Failed to execute', args, ':', e, file=sys.stderr)
  43. return ''
  44. def set_default_browser(desktop_env: str, desktop_entry: str) -> None:
  45. """Sets the XDG default-web-browser setting for the given desktop
  46. environment."""
  47. args = [
  48. 'xdg-settings', 'set', XDG_SETTING_DEFAULT_WEB_BROWSER, desktop_entry]
  49. env = env_with_current_desktop(desktop_env)
  50. subprocess.run(args, env=env)
  51. def set_default_browser_to_url_forwarder(
  52. desktop_env: str,
  53. backup_dict: dict[str, str],
  54. backup_key: str) -> None:
  55. """Sets default browser on the given desktop environment to the remote URL
  56. forwarder and backs up the previous default browser.
  57. Args:
  58. desktop_env: The desktop environment to be configured.
  59. backup_dict: The dictionary to backup the previous default browser.
  60. backup_key: The key that the previous default browser will be stored in
  61. backup_dict with.
  62. """
  63. current_entry = get_default_browser(desktop_env)
  64. if not current_entry:
  65. print('Cannot get default browser for', desktop_env, file=sys.stderr)
  66. return
  67. if current_entry == URL_FORWARDER_DESKTOP_ENTRY:
  68. print('Default browser for', desktop_env, 'is already',
  69. URL_FORWARDER_DESKTOP_ENTRY, file=sys.stderr)
  70. return
  71. backup_dict[backup_key] = current_entry
  72. if current_entry == XFCE4_WEB_BROWSER_DESKTOP_ENTRY:
  73. print('Default browser for', desktop_env, 'has been set to',
  74. XFCE4_WEB_BROWSER_DESKTOP_ENTRY, ', which effectively forces',
  75. desktop_env, 'to use XFCE\'s default browser setting.')
  76. # We can't back up XFCE's default browser here for local fallback, since it
  77. # might have been changed to the URL forwarder already.
  78. return
  79. set_default_browser(desktop_env, URL_FORWARDER_DESKTOP_ENTRY)
  80. print('Default browser for', desktop_env, 'has been successfully set to',
  81. URL_FORWARDER_DESKTOP_ENTRY)
  82. def load_host_settings_file() -> dict[str, str]:
  83. """Loads and returns the host settings JSON."""
  84. if not os.path.isfile(HOST_SETTINGS_PATH):
  85. return {}
  86. with open(HOST_SETTINGS_PATH, 'r') as settings_file:
  87. try:
  88. return json.load(settings_file)
  89. except JSONDecodeError as e:
  90. print('Failed to load JSON file:', e, file=sys.stderr)
  91. return {}
  92. def save_host_settings_file(settings: dict[str, str]) -> None:
  93. """Saves the host settings JSON to the file."""
  94. with open(HOST_SETTINGS_PATH, 'w') as settings_file:
  95. json.dump(settings, settings_file)
  96. def get_supported_desktop_envs_and_settings_key() -> dict[str, str]:
  97. desktop_envs_and_settings_keys = dict()
  98. for x_session_desktop_path in Path(X_SESSIONS_DIR).glob('*.desktop'):
  99. desktop_name = os.path.basename(x_session_desktop_path)
  100. if desktop_name.startswith('xfce'):
  101. desktop_envs_and_settings_keys['XFCE'] = 'previous_default_browser_xfce'
  102. elif desktop_name.startswith('cinnamon'):
  103. desktop_envs_and_settings_keys['X-Cinnamon'] = \
  104. 'previous_default_browser_cinnamon'
  105. elif desktop_name.startswith('gnome'):
  106. desktop_envs_and_settings_keys['GNOME'] = 'previous_default_browser_gnome'
  107. if not desktop_envs_and_settings_keys:
  108. # Add X-Generic for generic fallback.
  109. desktop_envs_and_settings_keys['X-Generic'] = \
  110. 'previous_default_browser_generic'
  111. return desktop_envs_and_settings_keys
  112. def setup_url_forwarder() -> None:
  113. settings = load_host_settings_file()
  114. desktop_envs_and_setting_keys = get_supported_desktop_envs_and_settings_key()
  115. for desktop_env, setting_key in desktop_envs_and_setting_keys.items():
  116. set_default_browser_to_url_forwarder(desktop_env, settings, setting_key)
  117. # For previous default browsers that have been set to the XFCE4 web browser,
  118. # replace them with the actual previous default browser configured for XFCE4
  119. # so that the URL forwarder doesn't launch itself recursively in case of local
  120. # fallback.
  121. for desktop_env, setting_key in desktop_envs_and_setting_keys.items():
  122. if (setting_key in settings and
  123. settings[setting_key] == XFCE4_WEB_BROWSER_DESKTOP_ENTRY):
  124. if 'XFCE' not in desktop_envs_and_setting_keys:
  125. print('Default browser for', desktop_env, 'is set to',
  126. XFCE4_WEB_BROWSER_DESKTOP_ENTRY, 'but XFCE is not found',
  127. file=sys.stderr)
  128. break
  129. xfce_setting_key = desktop_envs_and_setting_keys['XFCE']
  130. if xfce_setting_key not in settings:
  131. print('Cannot find', xfce_setting_key, 'in host settings.')
  132. break
  133. settings[setting_key] = settings[xfce_setting_key]
  134. save_host_settings_file(settings)
  135. # There are also x-www-browser and gnome-www-browser in the Debian Alternative
  136. # system. Most apps don't use them directly. xdg-open uses them if the session
  137. # does not have a display (i.e. interactive shell). Configuring them requires
  138. # sudo permission, and we always have a desktop environment, so we are not
  139. # changing them for now.
  140. # There is also a BROWSER environment variable. xdg-open may also use it when
  141. # the session does not have a display. We can't export a environment variable
  142. # back to the parent process anyway, so we don't change it here.
  143. def restore_default_browser(
  144. desktop_env: str,
  145. backup_dict: dict[str, str],
  146. backup_key: str) -> None:
  147. """Restores XDG default-web-browser to backup_dict[backup_key] on the given
  148. desktop environment.
  149. Args:
  150. desktop_env: The desktop environment to be configured.
  151. backup_dict: The dictionary where the previous configuration can be found.
  152. backup_key: The dictionary key to find the previous configuration.
  153. """
  154. if (backup_key not in backup_dict) or not backup_dict[backup_key]:
  155. print("No setting to restore from", backup_key, file=sys.stderr)
  156. return
  157. previous_setting = backup_dict[backup_key]
  158. if (previous_setting == URL_FORWARDER_DESKTOP_ENTRY or
  159. previous_setting == XFCE4_WEB_BROWSER_DESKTOP_ENTRY):
  160. print('Setting to restore from', backup_key, 'is', previous_setting,
  161. '. Ignored.', file=sys.stderr)
  162. return
  163. current_entry = get_default_browser(desktop_env)
  164. if current_entry == XFCE4_WEB_BROWSER_DESKTOP_ENTRY:
  165. print('Default browser for', desktop_env, 'is',
  166. XFCE4_WEB_BROWSER_DESKTOP_ENTRY, '. Ignored.', file=sys.stderr)
  167. return
  168. if current_entry != URL_FORWARDER_DESKTOP_ENTRY:
  169. print('Default browser for', desktop_env, 'is no longer',
  170. URL_FORWARDER_DESKTOP_ENTRY,
  171. '. Previously stored setting will not be restored.', file=sys.stderr)
  172. return
  173. set_default_browser(desktop_env, previous_setting)
  174. print('Default browser for', desktop_env, 'has been successfully restored to',
  175. previous_setting)
  176. def restore_previous_settings() -> None:
  177. settings = load_host_settings_file()
  178. for desktop_env, setting_key in (
  179. get_supported_desktop_envs_and_settings_key().items()):
  180. restore_default_browser(desktop_env, settings, setting_key)
  181. def check_default_browser_is_url_forwarder(desktop_env: str) -> None:
  182. """Checks if the default browser is set to the remote URL forwarder on the
  183. given desktop environment. Exits with 1 if it's not the case."""
  184. if (desktop_env != 'XFCE' and
  185. get_default_browser(desktop_env) == XFCE4_WEB_BROWSER_DESKTOP_ENTRY):
  186. print('Default browser for', desktop_env, 'is',
  187. XFCE4_WEB_BROWSER_DESKTOP_ENTRY,
  188. '. Checking default browser for XFCE instead...')
  189. check_default_browser_is_url_forwarder('XFCE')
  190. return
  191. if get_default_browser(desktop_env) != URL_FORWARDER_DESKTOP_ENTRY:
  192. print('Default browser for', desktop_env, 'is not',
  193. URL_FORWARDER_DESKTOP_ENTRY)
  194. sys.exit(1)
  195. def check_url_forwarder_setup() -> None:
  196. for desktop_env in get_supported_desktop_envs_and_settings_key().keys():
  197. check_default_browser_is_url_forwarder(desktop_env)
  198. print('Chrome Remote Desktop URL forwarder is properly set up.')
  199. sys.exit(0)
  200. def main() -> None:
  201. parser = argparse.ArgumentParser(
  202. usage='Usage: %(prog)s [options]',
  203. description='Set up a URL forwarder so that URLs will be opened on the '
  204. 'Chrome Remote Desktop client. This script must be run within the remote '
  205. 'desktop\'s session.')
  206. parser.add_argument('--setup', dest='setup', default=False,
  207. action='store_true',
  208. help='Set up the URL forwarder.')
  209. parser.add_argument('--restore', dest='restore', default=False,
  210. action='store_true',
  211. help='Remove the URL forwarder and restore default '
  212. 'browser settings.')
  213. parser.add_argument('--check-setup', dest='check_setup', default=False,
  214. action='store_true',
  215. help='Exit with 0 if the URL forwarder is properly set '
  216. 'up, or 1 otherwise.')
  217. options = parser.parse_args()
  218. if options.setup:
  219. setup_url_forwarder()
  220. elif options.restore:
  221. restore_previous_settings()
  222. elif options.check_setup:
  223. check_url_forwarder_setup()
  224. else:
  225. parser.print_usage()
  226. if __name__ == '__main__':
  227. logging.basicConfig(level=logging.DEBUG,
  228. format='%(asctime)s:%(levelname)s:%(message)s')
  229. sys.exit(main())