mithwire 0.51.0__tar.gz → 0.51.2__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.
- mithwire-0.51.2/MANIFEST.in +5 -0
- {mithwire-0.51.0/mithwire.egg-info → mithwire-0.51.2}/PKG-INFO +83 -44
- mithwire-0.51.2/README.md +230 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/__init__.py +1 -1
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/browser.py +1 -1
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/config.py +2 -1
- mithwire-0.51.2/mithwire/core/virtual_display.py +123 -0
- mithwire-0.51.2/mithwire/fingerprint_gen.py +171 -0
- mithwire-0.51.2/mithwire/proxy/__init__.py +27 -0
- mithwire-0.51.2/mithwire/proxy/config.py +308 -0
- mithwire-0.51.2/mithwire/proxy/health.py +333 -0
- mithwire-0.51.2/mithwire/proxy/relay.py +191 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth/__init__.py +46 -38
- mithwire-0.51.2/mithwire/stealth/cloakbrowser.py +200 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth/controller.py +22 -11
- {mithwire-0.51.0 → mithwire-0.51.2/mithwire.egg-info}/PKG-INFO +83 -44
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire.egg-info/SOURCES.txt +8 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire.egg-info/requires.txt +6 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/pyproject.toml +4 -2
- {mithwire-0.51.0 → mithwire-0.51.2}/tests/test_compute_launch_args.py +28 -0
- mithwire-0.51.2/tests/test_proxy.py +167 -0
- mithwire-0.51.0/MANIFEST.in +0 -6
- mithwire-0.51.0/README.md +0 -195
- {mithwire-0.51.0 → mithwire-0.51.2}/LICENSE.txt +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/NOTICE +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/__main__.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/README.md +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/__init__.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/accessibility.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/animation.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/audits.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/autofill.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/background_service.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/bluetooth_emulation.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/browser.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/cache_storage.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/cast.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/console.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/crash_report_context.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/css.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/database.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/debugger.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/device_access.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/device_orientation.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/dom.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/dom_debugger.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/dom_snapshot.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/dom_storage.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/emulation.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/event_breakpoints.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/extensions.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/fed_cm.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/fetch.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/file_system.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/headless_experimental.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/heap_profiler.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/indexed_db.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/input_.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/inspector.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/io.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/layer_tree.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/log.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/media.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/memory.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/network.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/overlay.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/page.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/performance.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/performance_timeline.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/preload.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/profiler.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/pwa.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/py.typed +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/runtime.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/schema.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/security.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/service_worker.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/smart_card_emulation.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/storage.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/system_info.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/target.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/tethering.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/tracing.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/util.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/web_audio.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/web_authn.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/cdp/web_mcp.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/_contradict.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/cf_templates/cf_dark_checkbox.png +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/cf_templates/cf_light_checkbox.png +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/connection.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/element.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/tab.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/core/util.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth/fingerprint.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth_diagnostic/__init__.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth_diagnostic/probes.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth_diagnostic/report.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire/stealth_diagnostic/runner.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire.egg-info/dependency_links.txt +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire.egg-info/entry_points.txt +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/mithwire.egg-info/top_level.txt +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/setup.cfg +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/tests/test_connect_retry.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/tests/test_stealth_diagnostic_cli.py +0 -0
- {mithwire-0.51.0 → mithwire-0.51.2}/tests/test_stealth_diagnostic_report.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mithwire
|
|
3
|
-
Version: 0.51.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.51.2
|
|
4
|
+
Summary: Dual-mode anti-detect browser framework — CDP-native stealth for local machines, binary-level patching for production servers. No WebDriver, no chromedriver.
|
|
5
5
|
Author: Elendil Technologies
|
|
6
6
|
Maintainer: Elendil Technologies
|
|
7
7
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -643,6 +643,10 @@ License-File: LICENSE.txt
|
|
|
643
643
|
Requires-Dist: mss
|
|
644
644
|
Requires-Dist: websockets>=14
|
|
645
645
|
Requires-Dist: deprecated
|
|
646
|
+
Provides-Extra: fingerprints
|
|
647
|
+
Requires-Dist: browserforge; extra == "fingerprints"
|
|
648
|
+
Provides-Extra: stealth
|
|
649
|
+
Requires-Dist: cloakbrowser; extra == "stealth"
|
|
646
650
|
Provides-Extra: dev
|
|
647
651
|
Requires-Dist: black; extra == "dev"
|
|
648
652
|
Requires-Dist: build; extra == "dev"
|
|
@@ -660,8 +664,9 @@ Dynamic: license-file
|
|
|
660
664
|
</p>
|
|
661
665
|
|
|
662
666
|
<p align="center">
|
|
663
|
-
<b
|
|
664
|
-
|
|
667
|
+
<b>A dual-mode anti-detect browser framework for Python.</b><br>
|
|
668
|
+
CDP-native stealth for local machines. Binary-level patching for production servers.<br>
|
|
669
|
+
<b>No WebDriver, no Selenium, no chromedriver.</b>
|
|
665
670
|
</p>
|
|
666
671
|
|
|
667
672
|
<p align="center">
|
|
@@ -673,42 +678,67 @@ Dynamic: license-file
|
|
|
673
678
|
|
|
674
679
|
---
|
|
675
680
|
|
|
676
|
-
##
|
|
681
|
+
## How it works
|
|
677
682
|
|
|
678
|
-
|
|
679
|
-
Chromium-based browser (Chrome, Brave, Edge…) and controls it by talking directly
|
|
680
|
-
to the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) (CDP).
|
|
683
|
+
mithwire is an anti-detect browser automation framework with **two stealth modes** that cover different deployment scenarios.
|
|
681
684
|
|
|
682
|
-
|
|
683
|
-
anti-bot systems look for to spot WebDriver/Selenium simply **aren't there**. You
|
|
684
|
-
get a browser that behaves like a real one, a short and pleasant async API, and
|
|
685
|
-
the practical stealth fixes you actually need against modern anti-bot stacks.
|
|
685
|
+
### CDP mode (default)
|
|
686
686
|
|
|
687
|
-
|
|
687
|
+
The default. mithwire launches a normal Chromium-based browser (Chrome, Brave, Edge) and controls it directly over the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). There is no automation driver bolted on the side — the tell-tale signals anti-bot systems use to spot WebDriver/Selenium simply aren't there.
|
|
688
688
|
|
|
689
|
-
|
|
689
|
+
CDP `Emulation` commands handle timezone, locale, geolocation, user agent, device metrics, and more. This is lightweight, works with any Chromium browser on any OS, and is the right choice when the browser runs on a real desktop where the host machine's fingerprint aligns with the browser profile — no deep lies to maintain.
|
|
690
690
|
|
|
691
|
-
|
|
691
|
+
**Best for:** local development, macOS/Windows workstations, scripts where the machine's real GPU/fonts/hardware match the presented identity.
|
|
692
692
|
|
|
693
|
-
|
|
693
|
+
### Stealth mode (patched binary)
|
|
694
694
|
|
|
695
|
-
|
|
695
|
+
For production Linux/VPS environments where the host machine's fingerprint (SwiftShader GPU, minimal fonts, headless signals) would be trivially detected. Stealth mode swaps in a patched Chromium binary ([CloakBrowser](https://github.com/CloakHQ/CloakBrowser)) that embeds anti-fingerprinting at the C++ level: canvas, WebGL, AudioContext, fonts, GPU strings, screen dimensions, TLS fingerprint, and CDP detection are all modified in the Chromium source code before compilation.
|
|
696
696
|
|
|
697
|
-
|
|
697
|
+
The browser's JavaScript sees only native values — there are no runtime overrides to detect. mithwire still applies CDP timezone, locale, geolocation, and Accept-Language on top (these complement rather than conflict with the binary's patches).
|
|
698
698
|
|
|
699
|
-
|
|
699
|
+
**Best for:** headless Linux servers, VPS production workloads, advanced anti-bot targets, situations requiring spoofed fingerprints that can't rely on the host machine's real hardware.
|
|
700
700
|
|
|
701
|
-
|
|
701
|
+
### Which mode to use
|
|
702
702
|
|
|
703
|
-
|
|
703
|
+
| Scenario | Mode | Why |
|
|
704
|
+
| --- | --- | --- |
|
|
705
|
+
| Scripting on your Mac/Windows laptop | **CDP** | Host hardware is real — no need to lie about GPU, fonts, or canvas |
|
|
706
|
+
| Headless server, no spoofing needed | **CDP** | CDP stealth is sufficient when the profile matches the machine |
|
|
707
|
+
| Production VPS scraping/automation | **Stealth** | Linux servers expose SwiftShader, minimal fonts, headless signals — the binary patches these at the source |
|
|
708
|
+
| Advanced anti-bot targets (CreepJS, DAB) | **Stealth** | Deep fingerprint surfaces (canvas hash, WebGL renderer, audio context) need native-level randomization |
|
|
709
|
+
| AI agent fleet via mithwire-mcp | **Either** | CDP for local agents, stealth for cloud/VPS agents — [mithwire-mcp](https://github.com/codeisalifestyle/mithwire-mcp) supports both via `engine` parameter |
|
|
704
710
|
|
|
705
|
-
|
|
711
|
+
> Stealth mode requires the `cloakbrowser` package and is currently Linux-only for the latest free builds. See [mithwire-mcp](https://github.com/codeisalifestyle/mithwire-mcp) for the full stealth integration (session management, proxy alignment, profile persistence, and fleet orchestration).
|
|
706
712
|
|
|
707
|
-
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## Features
|
|
716
|
+
|
|
717
|
+
**Anti-detect by design** — speaks raw CDP: no `navigator.webdriver`, no chromedriver binary, no Selenium surface to fingerprint
|
|
718
|
+
|
|
719
|
+
**Dual stealth engine** — CDP mode for local machines (CDP/JS overrides), stealth mode for production servers (C++ binary-level patches on canvas, WebGL, audio, fonts, GPU, TLS)
|
|
720
|
+
|
|
721
|
+
**Real Chromium browsers** — works with Chrome, Chromium, Brave, Edge, and patched CloakBrowser binaries
|
|
722
|
+
|
|
723
|
+
**Headful or headless** — visible window or invisible server mode; pairs with `Xvfb` for headed-on-headless Linux deployments
|
|
724
|
+
|
|
725
|
+
**Identity & fingerprint control** — timezone, locale, geolocation, user agent, device metrics, screen dimensions, and more via CDP Emulation
|
|
726
|
+
|
|
727
|
+
**Proxy support** — HTTP/HTTPS/SOCKS upstream proxies with automatic authenticating relay for credential-bearing HTTP proxies
|
|
728
|
+
|
|
729
|
+
**Cloudflare Turnstile bypass** — one call (`tab.verify_cf()`) solves the checkbox challenge, light and dark mode
|
|
730
|
+
|
|
731
|
+
**Smart DOM access** — find elements by text, CSS selector, or XPath, including inside iframes; lookups double as wait conditions
|
|
732
|
+
|
|
733
|
+
**Full network & event access** — the entire CDP surface is available: requests, responses, events, interception
|
|
734
|
+
|
|
735
|
+
**Stateful when you want it** — cookies, localStorage, multi-tab & multi-window, screenshots, external-debugger attach
|
|
736
|
+
|
|
737
|
+
**Tiny, async API** — up and running in ~2 lines, with best-practice defaults and automatic profile cleanup
|
|
708
738
|
|
|
709
739
|
---
|
|
710
740
|
|
|
711
|
-
##
|
|
741
|
+
## mithwire-mcp — give your AI agents a browser fleet
|
|
712
742
|
|
|
713
743
|
<p align="center">
|
|
714
744
|
<a href="https://github.com/codeisalifestyle/mithwire-mcp">
|
|
@@ -721,16 +751,17 @@ needs to run dozens of them**? That's [**mithwire-mcp**](https://github.com/code
|
|
|
721
751
|
a [Model Context Protocol](https://modelcontextprotocol.io) server that turns
|
|
722
752
|
mithwire into a tool your LLM can drive directly.
|
|
723
753
|
|
|
724
|
-
-
|
|
725
|
-
-
|
|
726
|
-
-
|
|
727
|
-
-
|
|
754
|
+
- **Agents launch & control their own browsers** — `session_start`, navigate, click, type, screenshot, evaluate — all as MCP tools
|
|
755
|
+
- **Fleet management** — spin up many isolated sessions at once, each its own browser process, with full lifecycle control
|
|
756
|
+
- **Durable identities** — persistent profiles with their own cookies, proxies, and fingerprints that survive across runs
|
|
757
|
+
- **Consistent stealth at scale** — proxy-aligned timezone/locale/geo, fingerprint spoofing, and WebRTC leak protection kept in sync per worker
|
|
758
|
+
- **Dual engine support** — `engine=cdp` or `engine=stealth` per session, mixing modes across a fleet
|
|
728
759
|
|
|
729
|
-
|
|
760
|
+
[**Get started with mithwire-mcp**](https://github.com/codeisalifestyle/mithwire-mcp)
|
|
730
761
|
|
|
731
762
|
---
|
|
732
763
|
|
|
733
|
-
##
|
|
764
|
+
## Install
|
|
734
765
|
|
|
735
766
|
```bash
|
|
736
767
|
pip install mithwire
|
|
@@ -744,7 +775,7 @@ or use headless mode. To upgrade:
|
|
|
744
775
|
pip install -U mithwire
|
|
745
776
|
```
|
|
746
777
|
|
|
747
|
-
##
|
|
778
|
+
## Quick start
|
|
748
779
|
|
|
749
780
|
```python
|
|
750
781
|
import mithwire as uc
|
|
@@ -757,12 +788,12 @@ async def main():
|
|
|
757
788
|
uc.loop().run_until_complete(main())
|
|
758
789
|
```
|
|
759
790
|
|
|
760
|
-
>
|
|
791
|
+
> Use `uc.loop().run_until_complete(...)` instead of `asyncio.run(...)` for
|
|
761
792
|
> reliable event-loop handling.
|
|
762
793
|
|
|
763
|
-
##
|
|
794
|
+
## Usage
|
|
764
795
|
|
|
765
|
-
###
|
|
796
|
+
### Custom launch options
|
|
766
797
|
|
|
767
798
|
```python
|
|
768
799
|
from mithwire import start
|
|
@@ -788,7 +819,7 @@ config.user_data_dir = "/path/to/profile"
|
|
|
788
819
|
config.browser_args = ["--some-flag=true"]
|
|
789
820
|
```
|
|
790
821
|
|
|
791
|
-
###
|
|
822
|
+
### Finding things on the page
|
|
792
823
|
|
|
793
824
|
```python
|
|
794
825
|
# by text — returns the closest match by text length, not the first hit
|
|
@@ -803,7 +834,7 @@ imgs = await tab.select_all("a[href] > div > img")
|
|
|
803
834
|
node = await tab.xpath("//button[contains(., 'Next')]", timeout=2.5)
|
|
804
835
|
```
|
|
805
836
|
|
|
806
|
-
###
|
|
837
|
+
### Solving Cloudflare Turnstile
|
|
807
838
|
|
|
808
839
|
```python
|
|
809
840
|
page = await browser.get("https://site-behind-turnstile.example")
|
|
@@ -813,7 +844,7 @@ await page.verify_cf(max_retries=3, timeout=20, retry_interval=2)
|
|
|
813
844
|
Bundled with light- and dark-mode widget templates and HiDPI-correct coordinates.
|
|
814
845
|
Requires `opencv-python` (`pip install opencv-python`).
|
|
815
846
|
|
|
816
|
-
###
|
|
847
|
+
### Handy tab helpers
|
|
817
848
|
|
|
818
849
|
| Method | Does |
|
|
819
850
|
| --- | --- |
|
|
@@ -826,17 +857,20 @@ Requires `opencv-python` (`pip install opencv-python`).
|
|
|
826
857
|
| `tab.open_external_debugger()` | inspect a tab without breaking your connection |
|
|
827
858
|
|
|
828
859
|
A fuller, runnable script (automating account creation end to end) lives in the
|
|
829
|
-
[`example/`](example/) folder.
|
|
860
|
+
[`example/`](example/) folder.
|
|
830
861
|
|
|
831
|
-
##
|
|
862
|
+
## How it compares
|
|
832
863
|
|
|
833
864
|
Most "stealth" tools either drive the browser through WebDriver/Selenium (which
|
|
834
865
|
leaks an obvious automation surface) or patch over it with injected JavaScript
|
|
835
|
-
(which is itself detectable). mithwire skips both:
|
|
836
|
-
|
|
837
|
-
|
|
866
|
+
(which is itself detectable). mithwire skips both:
|
|
867
|
+
|
|
868
|
+
- **CDP mode** speaks the browser's own debug protocol directly — no driver to fingerprint, no injected shim to catch.
|
|
869
|
+
- **Stealth mode** goes further with a patched Chromium binary that modifies fingerprint surfaces at the C++ level — canvas hashes, WebGL renderers, audio contexts, and more are natively randomized, invisible to any JavaScript introspection.
|
|
838
870
|
|
|
839
|
-
|
|
871
|
+
No other open-source Python framework combines both approaches under a single API.
|
|
872
|
+
|
|
873
|
+
## Credits & license
|
|
840
874
|
|
|
841
875
|
mithwire is a maintained fork of [**nodriver**](https://github.com/UltrafunkAmsterdam/nodriver)
|
|
842
876
|
by UltrafunkAmsterdam, itself the successor to undetected-chromedriver. It's
|
|
@@ -845,8 +879,13 @@ copyright and license are preserved in [`LICENSE.txt`](LICENSE.txt); attribution
|
|
|
845
879
|
details are in [`NOTICE`](NOTICE). mithwire is not affiliated with or endorsed by
|
|
846
880
|
the original author.
|
|
847
881
|
|
|
882
|
+
Stealth mode uses [CloakBrowser](https://github.com/CloakHQ/CloakBrowser), a third-party
|
|
883
|
+
patched Chromium binary (free to use, not redistributable). The `cloakbrowser` Python
|
|
884
|
+
wrapper is MIT-licensed. See the [CloakBrowser project](https://github.com/CloakHQ/CloakBrowser)
|
|
885
|
+
for binary license details.
|
|
886
|
+
|
|
848
887
|
This fork addresses real-world limitations hit when running against modern
|
|
849
888
|
anti-bot systems; fixes land as they're discovered during active use.
|
|
850
889
|
|
|
851
|
-
>
|
|
890
|
+
> **Use responsibly.** Only automate sites and accounts you're authorized to use,
|
|
852
891
|
> respect Terms of Service and local law, and avoid abusive request rates.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/mithwire-banner.png" alt="mithwire" width="760">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<b>A dual-mode anti-detect browser framework for Python.</b><br>
|
|
7
|
+
CDP-native stealth for local machines. Binary-level patching for production servers.<br>
|
|
8
|
+
<b>No WebDriver, no Selenium, no chromedriver.</b>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://pypi.org/project/mithwire/"><img src="https://img.shields.io/pypi/v/mithwire?style=for-the-badge&color=d62839&label=pip%20install%20mithwire" alt="PyPI"></a>
|
|
13
|
+
<a href="https://pypi.org/project/mithwire/"><img src="https://img.shields.io/pypi/pyversions/mithwire?style=for-the-badge&color=3776ab&logo=python&logoColor=white" alt="Python versions"></a>
|
|
14
|
+
<a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-AGPL--3.0-2ea44f?style=for-the-badge" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/codeisalifestyle/mithwire-mcp"><img src="https://img.shields.io/badge/🤖_agents-mithwire--mcp-d62839?style=for-the-badge" alt="mithwire-mcp"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## How it works
|
|
21
|
+
|
|
22
|
+
mithwire is an anti-detect browser automation framework with **two stealth modes** that cover different deployment scenarios.
|
|
23
|
+
|
|
24
|
+
### CDP mode (default)
|
|
25
|
+
|
|
26
|
+
The default. mithwire launches a normal Chromium-based browser (Chrome, Brave, Edge) and controls it directly over the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). There is no automation driver bolted on the side — the tell-tale signals anti-bot systems use to spot WebDriver/Selenium simply aren't there.
|
|
27
|
+
|
|
28
|
+
CDP `Emulation` commands handle timezone, locale, geolocation, user agent, device metrics, and more. This is lightweight, works with any Chromium browser on any OS, and is the right choice when the browser runs on a real desktop where the host machine's fingerprint aligns with the browser profile — no deep lies to maintain.
|
|
29
|
+
|
|
30
|
+
**Best for:** local development, macOS/Windows workstations, scripts where the machine's real GPU/fonts/hardware match the presented identity.
|
|
31
|
+
|
|
32
|
+
### Stealth mode (patched binary)
|
|
33
|
+
|
|
34
|
+
For production Linux/VPS environments where the host machine's fingerprint (SwiftShader GPU, minimal fonts, headless signals) would be trivially detected. Stealth mode swaps in a patched Chromium binary ([CloakBrowser](https://github.com/CloakHQ/CloakBrowser)) that embeds anti-fingerprinting at the C++ level: canvas, WebGL, AudioContext, fonts, GPU strings, screen dimensions, TLS fingerprint, and CDP detection are all modified in the Chromium source code before compilation.
|
|
35
|
+
|
|
36
|
+
The browser's JavaScript sees only native values — there are no runtime overrides to detect. mithwire still applies CDP timezone, locale, geolocation, and Accept-Language on top (these complement rather than conflict with the binary's patches).
|
|
37
|
+
|
|
38
|
+
**Best for:** headless Linux servers, VPS production workloads, advanced anti-bot targets, situations requiring spoofed fingerprints that can't rely on the host machine's real hardware.
|
|
39
|
+
|
|
40
|
+
### Which mode to use
|
|
41
|
+
|
|
42
|
+
| Scenario | Mode | Why |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| Scripting on your Mac/Windows laptop | **CDP** | Host hardware is real — no need to lie about GPU, fonts, or canvas |
|
|
45
|
+
| Headless server, no spoofing needed | **CDP** | CDP stealth is sufficient when the profile matches the machine |
|
|
46
|
+
| Production VPS scraping/automation | **Stealth** | Linux servers expose SwiftShader, minimal fonts, headless signals — the binary patches these at the source |
|
|
47
|
+
| Advanced anti-bot targets (CreepJS, DAB) | **Stealth** | Deep fingerprint surfaces (canvas hash, WebGL renderer, audio context) need native-level randomization |
|
|
48
|
+
| AI agent fleet via mithwire-mcp | **Either** | CDP for local agents, stealth for cloud/VPS agents — [mithwire-mcp](https://github.com/codeisalifestyle/mithwire-mcp) supports both via `engine` parameter |
|
|
49
|
+
|
|
50
|
+
> Stealth mode requires the `cloakbrowser` package and is currently Linux-only for the latest free builds. See [mithwire-mcp](https://github.com/codeisalifestyle/mithwire-mcp) for the full stealth integration (session management, proxy alignment, profile persistence, and fleet orchestration).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
**Anti-detect by design** — speaks raw CDP: no `navigator.webdriver`, no chromedriver binary, no Selenium surface to fingerprint
|
|
57
|
+
|
|
58
|
+
**Dual stealth engine** — CDP mode for local machines (CDP/JS overrides), stealth mode for production servers (C++ binary-level patches on canvas, WebGL, audio, fonts, GPU, TLS)
|
|
59
|
+
|
|
60
|
+
**Real Chromium browsers** — works with Chrome, Chromium, Brave, Edge, and patched CloakBrowser binaries
|
|
61
|
+
|
|
62
|
+
**Headful or headless** — visible window or invisible server mode; pairs with `Xvfb` for headed-on-headless Linux deployments
|
|
63
|
+
|
|
64
|
+
**Identity & fingerprint control** — timezone, locale, geolocation, user agent, device metrics, screen dimensions, and more via CDP Emulation
|
|
65
|
+
|
|
66
|
+
**Proxy support** — HTTP/HTTPS/SOCKS upstream proxies with automatic authenticating relay for credential-bearing HTTP proxies
|
|
67
|
+
|
|
68
|
+
**Cloudflare Turnstile bypass** — one call (`tab.verify_cf()`) solves the checkbox challenge, light and dark mode
|
|
69
|
+
|
|
70
|
+
**Smart DOM access** — find elements by text, CSS selector, or XPath, including inside iframes; lookups double as wait conditions
|
|
71
|
+
|
|
72
|
+
**Full network & event access** — the entire CDP surface is available: requests, responses, events, interception
|
|
73
|
+
|
|
74
|
+
**Stateful when you want it** — cookies, localStorage, multi-tab & multi-window, screenshots, external-debugger attach
|
|
75
|
+
|
|
76
|
+
**Tiny, async API** — up and running in ~2 lines, with best-practice defaults and automatic profile cleanup
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## mithwire-mcp — give your AI agents a browser fleet
|
|
81
|
+
|
|
82
|
+
<p align="center">
|
|
83
|
+
<a href="https://github.com/codeisalifestyle/mithwire-mcp">
|
|
84
|
+
<img src="assets/mithwire-mcp-banner.jpg" alt="mithwire-mcp" width="640">
|
|
85
|
+
</a>
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
One mithwire browser is great for a script. But what about an **AI agent that
|
|
89
|
+
needs to run dozens of them**? That's [**mithwire-mcp**](https://github.com/codeisalifestyle/mithwire-mcp) —
|
|
90
|
+
a [Model Context Protocol](https://modelcontextprotocol.io) server that turns
|
|
91
|
+
mithwire into a tool your LLM can drive directly.
|
|
92
|
+
|
|
93
|
+
- **Agents launch & control their own browsers** — `session_start`, navigate, click, type, screenshot, evaluate — all as MCP tools
|
|
94
|
+
- **Fleet management** — spin up many isolated sessions at once, each its own browser process, with full lifecycle control
|
|
95
|
+
- **Durable identities** — persistent profiles with their own cookies, proxies, and fingerprints that survive across runs
|
|
96
|
+
- **Consistent stealth at scale** — proxy-aligned timezone/locale/geo, fingerprint spoofing, and WebRTC leak protection kept in sync per worker
|
|
97
|
+
- **Dual engine support** — `engine=cdp` or `engine=stealth` per session, mixing modes across a fleet
|
|
98
|
+
|
|
99
|
+
[**Get started with mithwire-mcp**](https://github.com/codeisalifestyle/mithwire-mcp)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Install
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install mithwire
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
You'll need a Chromium-based browser (Chrome/Brave/Edge) installed, ideally in the
|
|
110
|
+
default location. On a headless server, run under [`Xvfb`](https://en.wikipedia.org/wiki/Xvfb)
|
|
111
|
+
or use headless mode. To upgrade:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install -U mithwire
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Quick start
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import mithwire as uc
|
|
121
|
+
|
|
122
|
+
async def main():
|
|
123
|
+
browser = await uc.start()
|
|
124
|
+
page = await browser.get("https://www.nowsecure.nl")
|
|
125
|
+
await page.save_screenshot()
|
|
126
|
+
|
|
127
|
+
uc.loop().run_until_complete(main())
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> Use `uc.loop().run_until_complete(...)` instead of `asyncio.run(...)` for
|
|
131
|
+
> reliable event-loop handling.
|
|
132
|
+
|
|
133
|
+
## Usage
|
|
134
|
+
|
|
135
|
+
### Custom launch options
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from mithwire import start
|
|
139
|
+
|
|
140
|
+
browser = await start(
|
|
141
|
+
headless=False,
|
|
142
|
+
user_data_dir="/path/to/profile", # pass one and it won't be auto-cleaned on exit
|
|
143
|
+
browser_executable_path="/path/to/some/other/browser",
|
|
144
|
+
browser_args=["--some-flag=true"],
|
|
145
|
+
lang="en-US",
|
|
146
|
+
)
|
|
147
|
+
tab = await browser.get("https://example.com")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or configure via a `Config` object:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from mithwire import Config
|
|
154
|
+
|
|
155
|
+
config = Config()
|
|
156
|
+
config.headless = False
|
|
157
|
+
config.user_data_dir = "/path/to/profile"
|
|
158
|
+
config.browser_args = ["--some-flag=true"]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Finding things on the page
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# by text — returns the closest match by text length, not the first hit
|
|
165
|
+
accept = await tab.find("accept all", best_match=True)
|
|
166
|
+
await accept.click()
|
|
167
|
+
|
|
168
|
+
# by CSS selector (retries until found or <timeout>, so it doubles as a wait)
|
|
169
|
+
email = await tab.select("input[type=email]")
|
|
170
|
+
imgs = await tab.select_all("a[href] > div > img")
|
|
171
|
+
|
|
172
|
+
# by XPath
|
|
173
|
+
node = await tab.xpath("//button[contains(., 'Next')]", timeout=2.5)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Solving Cloudflare Turnstile
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
page = await browser.get("https://site-behind-turnstile.example")
|
|
180
|
+
await page.verify_cf(max_retries=3, timeout=20, retry_interval=2)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Bundled with light- and dark-mode widget templates and HiDPI-correct coordinates.
|
|
184
|
+
Requires `opencv-python` (`pip install opencv-python`).
|
|
185
|
+
|
|
186
|
+
### Handy tab helpers
|
|
187
|
+
|
|
188
|
+
| Method | Does |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `tab.get_content()` | current page HTML |
|
|
191
|
+
| `tab.save_screenshot()` | screenshot to a temp file |
|
|
192
|
+
| `tab.scroll_down(n)` / `tab.scroll_up(n)` | scroll the page |
|
|
193
|
+
| `tab.get_local_storage()` / `tab.set_local_storage(dict)` | read/write localStorage |
|
|
194
|
+
| `tab.add_handler(event, cb)` | subscribe to CDP events (`cb(event)` or `cb(event, tab)`) |
|
|
195
|
+
| `tab.bypass_insecure_connection_warning()` | click through invalid-cert warnings |
|
|
196
|
+
| `tab.open_external_debugger()` | inspect a tab without breaking your connection |
|
|
197
|
+
|
|
198
|
+
A fuller, runnable script (automating account creation end to end) lives in the
|
|
199
|
+
[`example/`](example/) folder.
|
|
200
|
+
|
|
201
|
+
## How it compares
|
|
202
|
+
|
|
203
|
+
Most "stealth" tools either drive the browser through WebDriver/Selenium (which
|
|
204
|
+
leaks an obvious automation surface) or patch over it with injected JavaScript
|
|
205
|
+
(which is itself detectable). mithwire skips both:
|
|
206
|
+
|
|
207
|
+
- **CDP mode** speaks the browser's own debug protocol directly — no driver to fingerprint, no injected shim to catch.
|
|
208
|
+
- **Stealth mode** goes further with a patched Chromium binary that modifies fingerprint surfaces at the C++ level — canvas hashes, WebGL renderers, audio contexts, and more are natively randomized, invisible to any JavaScript introspection.
|
|
209
|
+
|
|
210
|
+
No other open-source Python framework combines both approaches under a single API.
|
|
211
|
+
|
|
212
|
+
## Credits & license
|
|
213
|
+
|
|
214
|
+
mithwire is a maintained fork of [**nodriver**](https://github.com/UltrafunkAmsterdam/nodriver)
|
|
215
|
+
by UltrafunkAmsterdam, itself the successor to undetected-chromedriver. It's
|
|
216
|
+
distributed under the **GNU AGPL-3.0**, the same license as upstream. Original
|
|
217
|
+
copyright and license are preserved in [`LICENSE.txt`](LICENSE.txt); attribution
|
|
218
|
+
details are in [`NOTICE`](NOTICE). mithwire is not affiliated with or endorsed by
|
|
219
|
+
the original author.
|
|
220
|
+
|
|
221
|
+
Stealth mode uses [CloakBrowser](https://github.com/CloakHQ/CloakBrowser), a third-party
|
|
222
|
+
patched Chromium binary (free to use, not redistributable). The `cloakbrowser` Python
|
|
223
|
+
wrapper is MIT-licensed. See the [CloakBrowser project](https://github.com/CloakHQ/CloakBrowser)
|
|
224
|
+
for binary license details.
|
|
225
|
+
|
|
226
|
+
This fork addresses real-world limitations hit when running against modern
|
|
227
|
+
anti-bot systems; fixes land as they're discovered during active use.
|
|
228
|
+
|
|
229
|
+
> **Use responsibly.** Only automate sites and accounts you're authorized to use,
|
|
230
|
+
> respect Terms of Service and local law, and avoid abusive request rates.
|
|
@@ -440,7 +440,7 @@ class Browser(Connection):
|
|
|
440
440
|
webrtc_leak_protection=getattr(config, "webrtc_leak_protection", "auto"),
|
|
441
441
|
headless=bool(getattr(config, "headless", False)),
|
|
442
442
|
proxied=proxied,
|
|
443
|
-
engine=getattr(config, "engine", "
|
|
443
|
+
engine=getattr(config, "engine", "cdp"),
|
|
444
444
|
)
|
|
445
445
|
# A freshly attached tab needs a brief moment before CDP overrides and
|
|
446
446
|
# new-document scripts reliably register on the about:blank target.
|
|
@@ -122,7 +122,7 @@ class Config:
|
|
|
122
122
|
else:
|
|
123
123
|
self.fingerprint = FingerprintConfig.from_dict(fingerprint)
|
|
124
124
|
self.webrtc_leak_protection = webrtc_leak_protection
|
|
125
|
-
self.engine = kwargs.pop("engine", "
|
|
125
|
+
self.engine = kwargs.pop("engine", "cdp")
|
|
126
126
|
|
|
127
127
|
# other keyword args will be accessible by attribute
|
|
128
128
|
self.__dict__.update(kwargs)
|
|
@@ -151,6 +151,7 @@ class Config:
|
|
|
151
151
|
fingerprint=self.fingerprint,
|
|
152
152
|
headless=self.headless,
|
|
153
153
|
browser_executable_path=self.browser_executable_path,
|
|
154
|
+
engine=self.engine,
|
|
154
155
|
):
|
|
155
156
|
if arg not in self._browser_args:
|
|
156
157
|
self._browser_args.append(arg)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Automatic Xvfb (virtual display) management for headed browser mode.
|
|
2
|
+
|
|
3
|
+
On a headless Linux server, Chrome cannot run in headed mode without a display.
|
|
4
|
+
Xvfb provides a virtual X11 framebuffer that makes Chrome behave exactly like a
|
|
5
|
+
real desktop browser — natural toolbar height, proper window chrome, and correct
|
|
6
|
+
storage/permission API behaviour — while consuming minimal resources.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
display = ensure_virtual_display()
|
|
11
|
+
# display is ":99" and DISPLAY env var is set
|
|
12
|
+
|
|
13
|
+
The module is idempotent: calling ``ensure_virtual_display`` multiple times
|
|
14
|
+
reuses the existing Xvfb process.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import atexit
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_xvfb_process: subprocess.Popen | None = None
|
|
30
|
+
_display: str | None = None
|
|
31
|
+
|
|
32
|
+
XVFB_DISPLAY = ":99"
|
|
33
|
+
XVFB_SCREEN = "1920x1080x24"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_display_available() -> bool:
|
|
37
|
+
"""Return True if a usable DISPLAY is already set."""
|
|
38
|
+
display = os.environ.get("DISPLAY")
|
|
39
|
+
return bool(display and display.strip())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _xvfb_available() -> bool:
|
|
43
|
+
"""Return True if the Xvfb binary is on PATH."""
|
|
44
|
+
return shutil.which("Xvfb") is not None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _cleanup() -> None:
|
|
48
|
+
"""Kill the managed Xvfb process on interpreter exit."""
|
|
49
|
+
global _xvfb_process
|
|
50
|
+
if _xvfb_process is not None:
|
|
51
|
+
try:
|
|
52
|
+
_xvfb_process.terminate()
|
|
53
|
+
_xvfb_process.wait(timeout=3)
|
|
54
|
+
except Exception:
|
|
55
|
+
try:
|
|
56
|
+
_xvfb_process.kill()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
_xvfb_process = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ensure_virtual_display(
|
|
63
|
+
*,
|
|
64
|
+
display: str = XVFB_DISPLAY,
|
|
65
|
+
screen: str = XVFB_SCREEN,
|
|
66
|
+
) -> str | None:
|
|
67
|
+
"""Start Xvfb if needed and return the DISPLAY string.
|
|
68
|
+
|
|
69
|
+
Returns None on non-Linux platforms, when a display already exists,
|
|
70
|
+
or when Xvfb is not installed.
|
|
71
|
+
"""
|
|
72
|
+
global _xvfb_process, _display
|
|
73
|
+
|
|
74
|
+
if not sys.platform.startswith("linux"):
|
|
75
|
+
return os.environ.get("DISPLAY")
|
|
76
|
+
|
|
77
|
+
if _is_display_available():
|
|
78
|
+
return os.environ["DISPLAY"]
|
|
79
|
+
|
|
80
|
+
if _display is not None and _xvfb_process is not None:
|
|
81
|
+
if _xvfb_process.poll() is None:
|
|
82
|
+
return _display
|
|
83
|
+
|
|
84
|
+
if not _xvfb_available():
|
|
85
|
+
logger.warning(
|
|
86
|
+
"Xvfb not found — headed browser sessions on a displayless server "
|
|
87
|
+
"will fail. Install with: apt-get install xvfb"
|
|
88
|
+
)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
_xvfb_process = subprocess.Popen(
|
|
93
|
+
[
|
|
94
|
+
"Xvfb", display,
|
|
95
|
+
"-screen", "0", screen,
|
|
96
|
+
"-ac",
|
|
97
|
+
"+extension", "GLX",
|
|
98
|
+
"-noreset",
|
|
99
|
+
],
|
|
100
|
+
stdout=subprocess.DEVNULL,
|
|
101
|
+
stderr=subprocess.DEVNULL,
|
|
102
|
+
)
|
|
103
|
+
time.sleep(1.0)
|
|
104
|
+
|
|
105
|
+
if _xvfb_process.poll() is not None:
|
|
106
|
+
logger.warning("Xvfb exited immediately (display %s may be in use)", display)
|
|
107
|
+
_xvfb_process = None
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
os.environ["DISPLAY"] = display
|
|
111
|
+
_display = display
|
|
112
|
+
atexit.register(_cleanup)
|
|
113
|
+
logger.info("Xvfb started on %s (%s)", display, screen)
|
|
114
|
+
return display
|
|
115
|
+
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
logger.warning("Failed to start Xvfb: %s", exc)
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def stop_virtual_display() -> None:
|
|
122
|
+
"""Stop the managed Xvfb process."""
|
|
123
|
+
_cleanup()
|