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.
- {lanscape-2.2.0a1/lanscape.egg-info → lanscape-2.2.1}/PKG-INFO +2 -2
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/main.py +11 -7
- {lanscape-2.2.0a1 → lanscape-2.2.1/lanscape.egg-info}/PKG-INFO +2 -2
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/SOURCES.txt +0 -1
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/requires.txt +1 -1
- {lanscape-2.2.0a1 → lanscape-2.2.1}/pyproject.toml +2 -2
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_api.py +1 -1
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_utils.py +2 -2
- lanscape-2.2.0a1/lanscape/core/web_browser.py +0 -210
- {lanscape-2.2.0a1 → lanscape-2.2.1}/LICENSE +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/MANIFEST.in +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/README.md +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/__main__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/app_scope.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/decorators.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/device_alive.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/errors.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/ip_parser.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/logger.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/mac_lookup.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/net_tools.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/port_manager.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/runtime_args.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/scan_config.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/service_scan.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/subnet_scan.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/core/version_manager.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/mac_addresses/convert_csv.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/mac_addresses/mac_db.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/convert_csv.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/full.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/large.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/medium.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/small.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/ports/test_port_list_scan.json +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/resources/services/definitions.jsonc +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/app.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/port.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/scan.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/api/tools.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/web/__init__.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/blueprints/web/routes.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/shutdown_handler.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/css/style.css +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/favicon.ico +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/img/ico/site.webmanifest +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/core.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/layout-sizing.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/main.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/on-tab-close.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/quietReload.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/scan-config.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/shutdown-server.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/subnet-info.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/js/subnet-selector.js +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/static/lanscape.webmanifest +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/base.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/core/head.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/core/scripts.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/error.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/info.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/main.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/config.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/device-detail.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/export.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/ip-table-row.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/ip-table.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/overview.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan/scan-error.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/scan.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape/ui/templates/shutdown.html +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/dependency_links.txt +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/entry_points.txt +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/lanscape.egg-info/top_level.txt +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/setup.cfg +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_decorators.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_env.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_globals.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_library.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_logging.py +0 -0
- {lanscape-2.2.0a1 → lanscape-2.2.1}/tests/test_port_scan.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lanscape"
|
|
3
|
-
version = "2.2.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|