lanscape 2.2.0a1__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {lanscape-2.2.0a1/lanscape.egg-info → lanscape-2.2.1}/PKG-INFO +2 -2
  2. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/main.py +11 -7
  3. {lanscape-2.2.0a1 → lanscape-2.2.1/lanscape.egg-info}/PKG-INFO +2 -2
  4. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/SOURCES.txt +0 -1
  5. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/requires.txt +1 -1
  6. {lanscape-2.2.0a1 → lanscape-2.2.1}/pyproject.toml +2 -2
  7. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_api.py +1 -1
  8. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_utils.py +2 -2
  9. lanscape-2.2.0a1/lanscape/core/web_browser.py +0 -210
  10. {lanscape-2.2.0a1 → lanscape-2.2.1}/LICENSE +0 -0
  11. {lanscape-2.2.0a1 → lanscape-2.2.1}/MANIFEST.in +0 -0
  12. {lanscape-2.2.0a1 → lanscape-2.2.1}/README.md +0 -0
  13. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/__init__.py +0 -0
  14. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/__main__.py +0 -0
  15. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/__init__.py +0 -0
  16. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/app_scope.py +0 -0
  17. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/decorators.py +0 -0
  18. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/device_alive.py +0 -0
  19. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/errors.py +0 -0
  20. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/ip_parser.py +0 -0
  21. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/logger.py +0 -0
  22. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/mac_lookup.py +0 -0
  23. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/net_tools.py +0 -0
  24. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/port_manager.py +0 -0
  25. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/runtime_args.py +0 -0
  26. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/scan_config.py +0 -0
  27. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/service_scan.py +0 -0
  28. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/subnet_scan.py +0 -0
  29. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/version_manager.py +0 -0
  30. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/mac_addresses/convert_csv.py +0 -0
  31. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/mac_addresses/mac_db.json +0 -0
  32. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/convert_csv.py +0 -0
  33. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/full.json +0 -0
  34. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/large.json +0 -0
  35. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/medium.json +0 -0
  36. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/small.json +0 -0
  37. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/test_port_list_scan.json +0 -0
  38. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/services/definitions.jsonc +0 -0
  39. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/__init__.py +0 -0
  40. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/app.py +0 -0
  41. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/__init__.py +0 -0
  42. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/__init__.py +0 -0
  43. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/port.py +0 -0
  44. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/scan.py +0 -0
  45. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/tools.py +0 -0
  46. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/web/__init__.py +0 -0
  47. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/web/routes.py +0 -0
  48. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/shutdown_handler.py +0 -0
  49. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/css/style.css +0 -0
  50. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
  51. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
  52. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
  53. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
  54. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
  55. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon.ico +0 -0
  56. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/site.webmanifest +0 -0
  57. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/core.js +0 -0
  58. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/layout-sizing.js +0 -0
  59. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/main.js +0 -0
  60. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/on-tab-close.js +0 -0
  61. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/quietReload.js +0 -0
  62. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/scan-config.js +0 -0
  63. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/shutdown-server.js +0 -0
  64. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/subnet-info.js +0 -0
  65. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/subnet-selector.js +0 -0
  66. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/lanscape.webmanifest +0 -0
  67. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/base.html +0 -0
  68. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/core/head.html +0 -0
  69. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/core/scripts.html +0 -0
  70. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/error.html +0 -0
  71. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/info.html +0 -0
  72. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/main.html +0 -0
  73. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/config.html +0 -0
  74. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/device-detail.html +0 -0
  75. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/export.html +0 -0
  76. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/ip-table-row.html +0 -0
  77. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/ip-table.html +0 -0
  78. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/overview.html +0 -0
  79. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/scan-error.html +0 -0
  80. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan.html +0 -0
  81. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/shutdown.html +0 -0
  82. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/dependency_links.txt +0 -0
  83. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/entry_points.txt +0 -0
  84. {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/top_level.txt +0 -0
  85. {lanscape-2.2.0a1 → lanscape-2.2.1}/setup.cfg +0 -0
  86. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_decorators.py +0 -0
  87. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_env.py +0 -0
  88. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_globals.py +0 -0
  89. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_library.py +0 -0
  90. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_logging.py +0 -0
  91. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_port_scan.py +0 -0
  92. {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_service_scan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 2.2.0a1
3
+ Version: 2.2.1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -25,7 +25,7 @@ Requires-Dist: scapy<3.0,>=2.3.2
25
25
  Requires-Dist: tabulate==0.9.0
26
26
  Requires-Dist: pydantic
27
27
  Requires-Dist: icmplib
28
- Requires-Dist: pwa-launcher
28
+ Requires-Dist: pwa-launcher>=1.1.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: pytest>=8.0; extra == "dev"
31
31
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -1,20 +1,19 @@
1
1
  """Main entry point for the LANscape application when running as a module."""
2
2
  import socket
3
3
 
4
-
5
- import threading
6
4
  import time
7
5
  import logging
8
6
  import traceback
9
7
  import os
10
- import requests
11
8
  from subprocess import Popen
9
+ import webbrowser
10
+ import requests
11
+
12
+ from pwa_launcher import open_pwa, ChromiumNotFoundError
12
13
 
13
- from pwa_launcher import open_pwa
14
14
 
15
15
  from lanscape.core.logger import configure_logging
16
16
  from lanscape.core.runtime_args import parse_args
17
- from lanscape.core.web_browser import open_webapp
18
17
  from lanscape.core.version_manager import get_installed_version, is_update_available
19
18
  from lanscape.ui.app import start_webserver_daemon, start_webserver
20
19
  # do this so any logs generated on import are displayed
@@ -73,7 +72,7 @@ def try_check_update():
73
72
  log.warning('Unable to check for updates.')
74
73
 
75
74
 
76
- def open_browser(url: str, wait=2) -> Popen:
75
+ def open_browser(url: str, wait=2) -> Popen | None:
77
76
  """
78
77
  Open a browser window to the specified
79
78
  url after waiting for the server to start
@@ -83,6 +82,12 @@ def open_browser(url: str, wait=2) -> Popen:
83
82
  log.info(f'Starting UI - http://127.0.0.1:{args.port}')
84
83
  return open_pwa(url)
85
84
 
85
+ except ChromiumNotFoundError:
86
+ success = webbrowser.open(url)
87
+ if success:
88
+ log.warning("Chromium browser not found. Falling back to default web browser.")
89
+ else:
90
+ log.warning(f"Cannot find any web browser. LANScape UI running on {url}")
86
91
  except BaseException:
87
92
  log.debug(traceback.format_exc())
88
93
  log.info(f'Unable to open web browser, server running on {url}')
@@ -105,7 +110,6 @@ def start_webserver_ui():
105
110
  else:
106
111
  flask_thread = start_webserver_daemon(args)
107
112
  proc = open_browser(uri)
108
-
109
113
  if proc:
110
114
  app_closed = proc.wait()
111
115
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 2.2.0a1
3
+ Version: 2.2.1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -25,7 +25,7 @@ Requires-Dist: scapy<3.0,>=2.3.2
25
25
  Requires-Dist: tabulate==0.9.0
26
26
  Requires-Dist: pydantic
27
27
  Requires-Dist: icmplib
28
- Requires-Dist: pwa-launcher
28
+ Requires-Dist: pwa-launcher>=1.1.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: pytest>=8.0; extra == "dev"
31
31
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -25,7 +25,6 @@ lanscape/core/scan_config.py
25
25
  lanscape/core/service_scan.py
26
26
  lanscape/core/subnet_scan.py
27
27
  lanscape/core/version_manager.py
28
- lanscape/core/web_browser.py
29
28
  lanscape/resources/mac_addresses/convert_csv.py
30
29
  lanscape/resources/mac_addresses/mac_db.json
31
30
  lanscape/resources/ports/convert_csv.py
@@ -6,7 +6,7 @@ scapy<3.0,>=2.3.2
6
6
  tabulate==0.9.0
7
7
  pydantic
8
8
  icmplib
9
- pwa-launcher
9
+ pwa-launcher>=1.1.0
10
10
 
11
11
  [dev]
12
12
  pytest>=8.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lanscape"
3
- version = "2.2.0a1"
3
+ version = "2.2.1"
4
4
  authors = [
5
5
  { name="Michael Dennis", email="michael@dipduo.com" },
6
6
  ]
@@ -28,7 +28,7 @@ dependencies = [
28
28
  "tabulate==0.9.0",
29
29
  "pydantic",
30
30
  "icmplib",
31
- "pwa-launcher"
31
+ "pwa-launcher>=1.1.0"
32
32
  ]
33
33
 
34
34
  [project.optional-dependencies]
@@ -41,7 +41,7 @@ def test_scan_config():
41
41
  return {
42
42
  'subnet': TEST_SUBNET,
43
43
  'port_list': 'test_port_list_scan',
44
- 'lookup_type': ['ICMP','POKE_THEN_ARP'], # Use ICMP for reliable external IP detection
44
+ 'lookup_type': ['ICMP', 'POKE_THEN_ARP'], # Use ICMP for reliable external IP detection
45
45
  'ping_config': {'timeout': 0.8, 'attempts': 2} # Reasonable timeout for external IPs
46
46
  }
47
47
 
@@ -252,9 +252,9 @@ def test_is_internal_block_edge_cases():
252
252
  ('192.168.1.1-192.168.1.10', True, 'Private range'),
253
253
  ('1.1.1.1-1.1.1.5', False, 'Public range'),
254
254
  ('192.168.1.1,10.0.0.1', True, 'Multiple private'),
255
- ('192.168.1.1, 10.0.0.1', True, 'Multiple private (comma with space)'),
255
+ ('192.168.1.1, 10.0.0.1', True, 'Multiple private (comma with space)'),
256
256
  ('192.168.1.1,8.8.8.8', False, 'Mixed private/public'),
257
- ('192.168.1.1, 8.8.8.8', False, 'Mixed private/public (comma with space)'),
257
+ ('192.168.1.1, 8.8.8.8', False, 'Mixed private/public (comma with space)'),
258
258
  ('192.168.1.1', True, 'Single private IP'),
259
259
  ('8.8.8.8', False, 'Single public IP'),
260
260
  ('invalid', False, 'Invalid input'),
@@ -1,210 +0,0 @@
1
- """
2
- Get the executable path of the system’s default web browser.
3
-
4
- Supports:
5
- - Windows (reads from the registry)
6
- - Linux (uses xdg-mime / xdg-settings + .desktop file parsing)
7
- """
8
-
9
- import sys
10
- import os
11
- import subprocess
12
- import webbrowser
13
- import logging
14
- import re
15
- import time
16
- from typing import Optional
17
-
18
- log = logging.getLogger('WebBrowser')
19
-
20
-
21
- def open_webapp(url: str) -> bool:
22
- """
23
- will try to open the web page as an app
24
- on failure, will open as a tab in default browser
25
-
26
- returns:
27
- """
28
- start = time.time()
29
- try:
30
- exe = get_default_browser_executable()
31
- if not exe:
32
- raise RuntimeError('Unable to find browser binary')
33
- log.debug(f'Opening {url} with {exe}')
34
-
35
- cmd = f'"{exe}" --app="{url}"'
36
- subprocess.run(cmd, check=True, shell=True)
37
-
38
- if time.time() - start < 2:
39
- log.debug(
40
- 'Unable to hook into closure of UI, listening for flask shutdown')
41
- return False
42
- return True
43
-
44
- except Exception as e:
45
- log.warning(
46
- 'Failed to open webpage as app, falling back to browser tab')
47
- log.debug(f'As app error: {e}')
48
- try:
49
- success = webbrowser.open(url)
50
- log.debug(f'Opened {url} in browser tab: {success}')
51
- if not success:
52
- # pylint: disable=raise-missing-from
53
- raise RuntimeError(
54
- 'Unknown error while opening browser tab') from e
55
- except Exception as e2:
56
- log.warning(
57
- 'Exhausted all options to open browser, you need to open manually')
58
- log.debug(f'As tab error: {e2}')
59
- log.info(f'LANScape UI is running on {url}')
60
- return False
61
-
62
-
63
- def get_default_browser_executable() -> Optional[str]:
64
- """Platform-agnostic method to get the default browser executable path."""
65
- if sys.platform.startswith("win"):
66
- return windows_get_browser_from_registry()
67
-
68
- if sys.platform.startswith("linux"):
69
- return linux_get_browser_executable()
70
-
71
- if sys.platform.startswith("darwin"):
72
- # macOS: try to find Chrome first for app mode support, fallback to default
73
- try:
74
- p = subprocess.run(
75
- ["mdfind", "kMDItemCFBundleIdentifier == 'com.google.Chrome'"],
76
- capture_output=True, text=True, check=True
77
- )
78
- chrome_paths = p.stdout.strip().split('\n')
79
- if chrome_paths and chrome_paths[0]:
80
- return f"{chrome_paths[0]}/Contents/MacOS/Google Chrome"
81
- except subprocess.CalledProcessError:
82
- pass
83
-
84
- # Fallback to system default
85
- return "/usr/bin/open"
86
-
87
- # Unsupported platform
88
- return None
89
-
90
-
91
- def linux_get_browser_executable() -> Optional[str]:
92
- """Get the default web browser executable path on Linux."""
93
- # First, find the .desktop file name
94
- desktop_file = None
95
- try:
96
- # Try xdg-mime
97
- p = subprocess.run(
98
- ["xdg-mime", "query", "default", "x-scheme-handler/http"],
99
- capture_output=True, text=True,
100
- check=True
101
- )
102
- desktop_file = p.stdout.strip()
103
- except subprocess.CalledProcessError:
104
- pass
105
-
106
- if not desktop_file:
107
- # Fallback to xdg-settings
108
- try:
109
- p = subprocess.run(
110
- ["xdg-settings", "get", "default-web-browser"],
111
- capture_output=True, text=True,
112
- check=True
113
- )
114
- desktop_file = p.stdout.strip()
115
- except subprocess.CalledProcessError:
116
- pass
117
-
118
- # Final fallback: BROWSER environment variable
119
- if not desktop_file:
120
- return os.environ.get("BROWSER")
121
-
122
- # Look for that .desktop file in standard locations
123
- search_paths = [
124
- os.path.expanduser("~/.local/share/applications"),
125
- "/usr/local/share/applications",
126
- "/usr/share/applications",
127
- ]
128
-
129
- exec_cmd = None
130
- for path in search_paths:
131
- full_path = os.path.join(path, desktop_file)
132
- if os.path.isfile(full_path):
133
- with open(full_path, encoding="utf-8", errors="ignore") as f:
134
- for line in f:
135
- if line.startswith("Exec="):
136
- exec_cmd = line[len("Exec="):].strip()
137
- # strip arguments like "%u", "--flag", etc.
138
- exec_cmd = exec_cmd.split()[0]
139
- exec_cmd = exec_cmd.split("%")[0]
140
- return exec_cmd
141
-
142
- return exec_cmd
143
-
144
-
145
- def windows_get_browser_from_registry() -> Optional[str]:
146
- """Get the default web browser executable path on Windows."""
147
- # Import winreg only on Windows platforms
148
- if not sys.platform.startswith("win"):
149
- return None
150
-
151
- try:
152
- import winreg # pylint: disable=import-outside-toplevel
153
- except ImportError:
154
- log.debug("winreg module not available")
155
- return None
156
-
157
- def get_reg(base, path, key=None):
158
- """Helper function to read a registry key."""
159
- try:
160
- with winreg.OpenKey(base, path) as reg:
161
- return winreg.QueryValueEx(reg, key)[0]
162
- except FileNotFoundError:
163
- return None
164
-
165
- def extract_executable(cmd: str) -> Optional[str]:
166
- """Extract the executable path from a command string."""
167
- match = re.match(r'"?([^"]+)"?', cmd)
168
- return match.group(1) if match else None
169
-
170
- def get_user_preferred_browser():
171
- """Get the user preferred browser from the registry."""
172
- progid = get_reg(
173
- winreg.HKEY_CURRENT_USER,
174
- r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice',
175
- 'ProgId'
176
- )
177
- if not progid:
178
- log.debug('No user preferred browser found in registry')
179
- return None
180
-
181
- browser_path = get_reg(
182
- winreg.HKEY_CLASSES_ROOT,
183
- f'{progid}\\shell\\open\\command'
184
- )
185
-
186
- if not browser_path:
187
- log.debug(f'progid {progid} does not have a command in registry')
188
- return None
189
-
190
- return extract_executable(browser_path)
191
-
192
- def get_system_default_browser():
193
- """Get the system default browser from the registry."""
194
- reg = get_reg(
195
- winreg.HKEY_CLASSES_ROOT,
196
- r'http\shell\open\command'
197
- )
198
- if not reg:
199
- log.debug('No system default browser found in registry')
200
- return None
201
-
202
- return extract_executable(reg)
203
-
204
- user_browser = get_user_preferred_browser()
205
- if user_browser:
206
- return extract_executable(user_browser)
207
-
208
- system_browser = get_system_default_browser()
209
- if system_browser:
210
- return extract_executable(system_browser)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes