driverclient 0.2.23__tar.gz → 0.2.24__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.
- {driverclient-0.2.23 → driverclient-0.2.24}/PKG-INFO +1 -1
- {driverclient-0.2.23 → driverclient-0.2.24}/pyproject.toml +1 -1
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/__init__.py +1 -1
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/download.py +3 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/install.py +1 -77
- {driverclient-0.2.23 → driverclient-0.2.24}/.gitignore +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/README.md +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/__main__.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/config.json +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/config.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/core/__init__.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/core/hardware.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/core/http.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/core/hwid.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/core/proc.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/events.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/main.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/__init__.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/automate.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/capture.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/diagnose.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/offline.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/resolve.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/ops/scan.py +0 -0
- {driverclient-0.2.23 → driverclient-0.2.24}/src/driverclient/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: driverclient
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.24
|
|
4
4
|
Summary: Driver Server client with an embeddable connector for config injection at runtime.
|
|
5
5
|
Project-URL: Homepage, https://example.com/driverclient
|
|
6
6
|
Author-email: Raja Sanaullah <sanaullah@99technologies.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "driverclient"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.24"
|
|
8
8
|
description = "Driver Server client with an embeddable connector for config injection at runtime."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -29,7 +29,10 @@ def download_driver(driver: dict, base: Path, repo_url: str,
|
|
|
29
29
|
ok = True
|
|
30
30
|
for f in driver.get("files", []):
|
|
31
31
|
url = f"{repo_url}/files/{pkg_id}/{f['filename']}"
|
|
32
|
+
# filename may carry subdirs (package layout preserved) — recreate them so
|
|
33
|
+
# the INF's CopyFiles references resolve and the driver actually binds.
|
|
32
34
|
path = dest / f["filename"]
|
|
35
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
33
36
|
if not http_download_retry(url, path, f["sha256"],
|
|
34
37
|
timeout=cfg["timeout_long"]):
|
|
35
38
|
ok = False
|
|
@@ -26,11 +26,9 @@ from pathlib import Path
|
|
|
26
26
|
from driverclient import events
|
|
27
27
|
from driverclient.config import get
|
|
28
28
|
from driverclient.core import proc
|
|
29
|
-
from driverclient.core import hwid as hwid_mod
|
|
30
29
|
from driverclient.core.http import http_post_retry
|
|
31
30
|
from driverclient.ops.download import download_driver
|
|
32
31
|
from driverclient.ops.resolve import ResolveResult, resolve
|
|
33
|
-
from driverclient.ops.scan import scan
|
|
34
32
|
|
|
35
33
|
# Install order: lower number first (hardware dependency)
|
|
36
34
|
_CATEGORY_ORDER: dict[str, int] = {
|
|
@@ -169,11 +167,9 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
169
167
|
idx: dict = _installed_inf_index() if skip_installed else {}
|
|
170
168
|
|
|
171
169
|
drivers = result.drivers
|
|
172
|
-
already_in_store: list[dict] = []
|
|
173
170
|
if skip_installed:
|
|
174
|
-
already_in_store = [d for d in drivers if _already_in_store(d, idx)]
|
|
175
171
|
kept = [d for d in drivers if not _already_in_store(d, idx)]
|
|
176
|
-
skipped = len(
|
|
172
|
+
skipped = len(drivers) - len(kept)
|
|
177
173
|
if skipped:
|
|
178
174
|
events.progress(
|
|
179
175
|
"install",
|
|
@@ -182,15 +178,6 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
182
178
|
)
|
|
183
179
|
drivers = kept
|
|
184
180
|
if not drivers:
|
|
185
|
-
# A display driver can be sitting in the store (e.g. staged offline
|
|
186
|
-
# via DISM during imaging) without ever being BOUND to the device —
|
|
187
|
-
# the primary adapter doesn't reliably switch itself over. Refresh it
|
|
188
|
-
# now instead of leaving that gap for Windows Update to close later.
|
|
189
|
-
_refresh_bound_devices(
|
|
190
|
-
already_in_store,
|
|
191
|
-
{(d.get("binding_id") or d.get("package_id", "")) for d in already_in_store},
|
|
192
|
-
cfg,
|
|
193
|
-
)
|
|
194
181
|
events.done("install",
|
|
195
182
|
"[install] Nothing to install — all resolved drivers already current",
|
|
196
183
|
total=0)
|
|
@@ -214,16 +201,6 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
214
201
|
# Skipped drivers ARE satisfied on the machine — report them upstream as success.
|
|
215
202
|
_confirm(result.trace_id, installed_ok + skipped_now, install_failed, cfg)
|
|
216
203
|
|
|
217
|
-
# Force-bind any display driver now confirmed in the store — freshly
|
|
218
|
-
# installed here, or already present from an earlier offline/DISM pass —
|
|
219
|
-
# so the device actually switches over instead of staying on whatever it
|
|
220
|
-
# booted with until Windows Update eventually forces a real reinstall.
|
|
221
|
-
ready_ids = (
|
|
222
|
-
{r.binding_id for r in installed_ok}
|
|
223
|
-
| {(d.get("binding_id") or d.get("package_id", "")) for d in already_in_store}
|
|
224
|
-
)
|
|
225
|
-
_refresh_bound_devices(result.drivers, ready_ids, cfg)
|
|
226
|
-
|
|
227
204
|
reboot_required = any(r.reboot_required for r in installed_ok)
|
|
228
205
|
reboot_tag = " — reboot required" if reboot_required else ""
|
|
229
206
|
skip_tag = f" ({len(skipped_now)} already in store)" if skipped_now else ""
|
|
@@ -392,59 +369,6 @@ def _install_one_exe(driver: dict, driver_dir: Path, cfg: dict) -> DriverResult:
|
|
|
392
369
|
# the offline / pre-boot path.
|
|
393
370
|
|
|
394
371
|
|
|
395
|
-
# ── Device refresh (force-bind display drivers) ─────────────────────────────────
|
|
396
|
-
|
|
397
|
-
def _refresh_bound_devices(drivers: list[dict], ready_ids: set[str], cfg: dict) -> None:
|
|
398
|
-
"""Force Windows to actually bind a display driver that's confirmed in the
|
|
399
|
-
driver store (just installed, or already there from an earlier offline/DISM
|
|
400
|
-
injection) but not necessarily applied to the device yet.
|
|
401
|
-
|
|
402
|
-
pnputil only stages a package into the store — the primary display adapter
|
|
403
|
-
is already rendering the current session with whatever driver it booted
|
|
404
|
-
with and doesn't reliably re-evaluate/switch itself over on its own. Left
|
|
405
|
-
alone, the device stays on the fallback driver until something forces a
|
|
406
|
-
real reinstall — which is what Windows' own automatic Windows Update
|
|
407
|
-
driver search eventually does, closing the gap our own pipeline left open.
|
|
408
|
-
Disabling + re-enabling the device here forces Windows to redo PnP install
|
|
409
|
-
for it immediately, picking up the driver that's already in the store.
|
|
410
|
-
"""
|
|
411
|
-
display_hwids = {
|
|
412
|
-
d["hwid"] for d in drivers
|
|
413
|
-
if d.get("category") == "display" and d.get("hwid")
|
|
414
|
-
and (d.get("binding_id") or d.get("package_id", "")) in ready_ids
|
|
415
|
-
}
|
|
416
|
-
if not display_hwids:
|
|
417
|
-
return
|
|
418
|
-
|
|
419
|
-
canon_targets = {hwid_mod.canonicalize(h) for h in display_hwids}
|
|
420
|
-
scan_result = scan(force=False)
|
|
421
|
-
for dev in scan_result.devices:
|
|
422
|
-
if any(hwid_mod.canonicalize(h) in canon_targets for h in dev.hwids):
|
|
423
|
-
_refresh_device(dev.instance_id, cfg)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
def _refresh_device(instance_id: str, cfg: dict) -> None:
|
|
427
|
-
"""Disable then re-enable one device by instance ID — forces Windows to redo
|
|
428
|
-
PnP install for it, so a driver already in the store but not yet bound
|
|
429
|
-
takes effect immediately, without a reboot."""
|
|
430
|
-
events.progress("install",
|
|
431
|
-
f"[install] Refreshing device to bind new driver: {instance_id}",
|
|
432
|
-
instance_id=instance_id)
|
|
433
|
-
timeout = cfg.get("timeout_short", 30)
|
|
434
|
-
try:
|
|
435
|
-
proc.run(["pnputil", "/disable-device", instance_id],
|
|
436
|
-
capture_output=True, text=True, timeout=timeout)
|
|
437
|
-
r = proc.run(["pnputil", "/enable-device", instance_id],
|
|
438
|
-
capture_output=True, text=True, timeout=timeout)
|
|
439
|
-
ok = r.returncode == 0
|
|
440
|
-
(events.ok if ok else events.error)(
|
|
441
|
-
"install", f" {'[ok]' if ok else '[x]'} refreshed {instance_id}",
|
|
442
|
-
instance_id=instance_id, error="" if ok else r.stdout.strip()[:200])
|
|
443
|
-
except Exception as e:
|
|
444
|
-
events.error("install", f" [x] refresh failed for {instance_id}: {e}",
|
|
445
|
-
instance_id=instance_id, error=str(e))
|
|
446
|
-
|
|
447
|
-
|
|
448
372
|
# ── Driver installation ─────────────────────────────────────────────────────────
|
|
449
373
|
|
|
450
374
|
def _classify(returncode: int, stdout: str = "", stderr: str = "") -> tuple[bool, str, bool]:
|
|
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
|