Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 14 additions & 19 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
from pathlib import Path

sys.path.insert(0, str(Path('..', 'src').resolve()))
sys.path.insert(0, str(Path("..", "src").resolve()))

# Mock platform-specific dependencies so autodoc works off-host
autodoc_mock_imports = []
Expand All @@ -23,12 +23,7 @@
]

if sys.platform != "darwin":
autodoc_mock_imports += [
"objc",
"CoreFoundation",
"Foundation",
"PyObjCTools"
]
autodoc_mock_imports += ["objc", "CoreFoundation", "Foundation", "PyObjCTools"]

# Configuration file for the Sphinx documentation builder.
#
Expand All @@ -41,9 +36,9 @@

import hwprobe

project = 'HWProbe'
copyright = '2025, Mahasvan Mohan'
author = 'Mahasvan Mohan'
project = "HWProbe"
copyright = "2025, Mahasvan Mohan"
author = "Mahasvan Mohan"
release = hwprobe.__version__

autodoc_class_signature = "separated"
Expand All @@ -55,25 +50,25 @@
autoclass_content = "class"

autodoc_default_options = {
'member-order': 'bysource',
'special-members': '__init__',
'undoc-members': True,
'exclude-members': '__weakref__, __init___'
"member-order": "bysource",
"special-members": "__init__",
"undoc-members": True,
"exclude-members": "__weakref__, __init___",
}

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
'sphinx.ext.autodoc',
'sphinxcontrib.autodoc_pydantic',
"sphinx.ext.autodoc",
"sphinxcontrib.autodoc_pydantic",
]

templates_path = ['_templates']
templates_path = ["_templates"]
exclude_patterns = []

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = 'alabaster'
html_static_path = ['_static']
html_theme = "alabaster"
html_static_path = ["_static"]
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ where = ["src"]
[project.urls]
Homepage = "https://github.com/Mahasvan/HWProbe"
Issues = "https://github.com/Mahasvan/HWProbe/issues"

[tool.ruff]
target-version = "py39"
line-length = 120
36 changes: 13 additions & 23 deletions src/hwprobe/core/common/edid.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
from hwprobe.models.display_models import DisplayModuleInfo, ResolutionInfo

BIT_DEPTH_ENUM = {
1: 6,
2: 8,
3: 10,
4: 12,
5: 14,
6: 16
}
BIT_DEPTH_ENUM = {1: 6, 2: 8, 3: 10, 4: 12, 5: 14, 6: 16}

INTERFACE_ENUM = {
0: "Undefined",
1: "DVI",
2: "HDMI", # Standard HDMI-A
3: "HDMI (B)",
4: "MDDI",
5: "DisplayPort"
5: "DisplayPort",
}

DESCRIPTOR_TAG_ENUM = {
Expand Down Expand Up @@ -72,30 +65,30 @@ def parse_edid(edid_data: bytes) -> DisplayModuleInfo:
if input_type >> 7 == 1: # MSB is 1 => Digital output
if edid_version >= (1, 4):
module.resolution.bit_depth = BIT_DEPTH_ENUM.get(
_get_bits(input_type.to_bytes(1, byteorder="little"), 1, 4),
0)
_get_bits(input_type.to_bytes(1, byteorder="little"), 1, 4), 0
)
module.interface = INTERFACE_ENUM.get(input_type & 7, "Unknown")
else:
module.interface = "Analog"

resolution = (0, 0, 0) # Width, Height, Refresh Rate
# We will use this tuple to find the max resolution and refresh rate, and update it in `module.resolution`.

for block_start in range(0x36, 0x6d, 18):
block = edid_data[block_start:block_start + 18]
zeros = 0x00.to_bytes(1, byteorder='little') * 2
if block[:2] == zeros:
for block_start in range(0x36, 0x6D, 18):
block = edid_data[block_start : block_start + 18]
if block[:2] == b"\x00\x00":
tag = block[3]
if tag in DESCRIPTOR_TAG_ENUM:
# Refer to DESCRIPTOR_TAG_ENUM for valid block type codes
if tag == 0xFF:
# todo: test if this works
module.serial_number = block[5:].decode("ascii").strip()
if tag == 0xFC:
elif tag == 0xFC:
module.name = block[5:].decode("ascii").strip()

else:
if not module.resolution: continue
if not module.resolution:
continue

pixel_clock_hz = (block[0] | (block[1] << 8)) * 10_000

Expand All @@ -106,11 +99,7 @@ def parse_edid(edid_data: bytes) -> DisplayModuleInfo:
v_blank = ((block[7] & 0x0F) << 8) | block[6]
refresh_rate = pixel_clock_hz / ((horiz + h_blank) * (vert + v_blank))

resolution = max(
resolution,
(horiz, vert, round(refresh_rate, 2)),
key=lambda x: (x[0] * x[1], x[2])
)
resolution = max(resolution, (horiz, vert, round(refresh_rate, 2)), key=lambda x: (x[0] * x[1], x[2]))

if resolution != (0, 0, 0):
if not module.resolution:
Expand All @@ -125,4 +114,5 @@ def parse_edid(edid_data: bytes) -> DisplayModuleInfo:

return module

# todo: parse extension blocks

# todo: parse extension blocks
22 changes: 11 additions & 11 deletions src/hwprobe/core/linux/cpu.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from __future__ import annotations

import re
import subprocess
from typing import Optional, List
from typing import Optional

from hwprobe.models.cpu_models import CPUInfo
from hwprobe.models.status_models import StatusType


def _arm_cpu_cores() -> Optional[int]:
try:
result = subprocess.run(["lscpu", "-p"], capture_output=True, text=True).stdout
result = subprocess.run(["lscpu", "-p"], capture_output=True, text=True, check=True).stdout
lines = [x for x in result.splitlines() if not x.startswith("#")]
# Format: CPU,Core,Socket,Node,,L1d,L1i,L2,L3
core_ids = [x.split(",")[1] for x in lines]
# The number of distinct Core IDs is the number of cores
return len(set(core_ids))
except Exception as e:
except (subprocess.CalledProcessError, FileNotFoundError):
return None


Expand Down Expand Up @@ -55,20 +57,18 @@ def _cpu_threads(raw_cpu_info: str) -> Optional[int]:
try:
count = len(re.findall(r"^processor\s+:", raw_cpu_info, re.MULTILINE))
return count if count > 0 else None
except:
except Exception:
return None


def _x86_flags(cpu_lines: str) -> Optional[List[str]]:
def _x86_flags(cpu_lines: str) -> Optional[list[str]]:
flags_match = re.search(r"flags\s+:\s+(.+)", cpu_lines)
if not flags_match:
return None

flags = flags_match.group(1)
flags = [x.lower().strip() for x in flags.split(" ")]
flags = [
flag.replace("_", ".").upper() for flag in flags if flag
]
flags = [flag.replace("_", ".").upper() for flag in flags if flag]
return flags


Expand Down Expand Up @@ -156,19 +156,19 @@ def fetch_cpu_info() -> CPUInfo:

# todo: Check if any of the regexes may suffer from string having two `\t`s
try:
with open('/proc/cpuinfo') as f:
with open("/proc/cpuinfo") as f:
raw_cpu_info = f.read()
except Exception as e:
cpu_info.status.type = StatusType.FAILED
cpu_info.status.messages.append(f"Could not open /proc/cpuinfo: {str(e)}")
cpu_info.status.messages.append(f"Could not open /proc/cpuinfo: {e!s}")
return cpu_info

if not raw_cpu_info:
cpu_info.status.type = StatusType.FAILED
cpu_info.status.messages.append("/proc/cpuinfo has no content")
return cpu_info

architecture = subprocess.run(['uname', '-m'], capture_output=True, text=True)
architecture = subprocess.run(["uname", "-m"], capture_output=True, text=True, check=True)

if ("aarch64" in architecture.stdout) or ("arm" in architecture.stdout):
return fetch_arm_cpu_info(raw_cpu_info)
Expand Down
28 changes: 15 additions & 13 deletions src/hwprobe/core/linux/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
import re
from typing import Optional

from hwprobe.core.common.edid import parse_edid, INTERFACE_ENUM
from hwprobe.core.common.edid import INTERFACE_ENUM, parse_edid
from hwprobe.core.linux.common import pci_path_linux
from hwprobe.models.display_models import DisplayInfo, DisplayModuleInfo
from hwprobe.models.status_models import StatusType

_PCI_BDF_PATTERN = re.compile(r"^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$")

DRM_CONNECTOR_TYPE = {
"eDP": INTERFACE_ENUM[5], # DisplayPort
"DP": INTERFACE_ENUM[5], # DisplayPort
"eDP": INTERFACE_ENUM[5], # DisplayPort
"DP": INTERFACE_ENUM[5], # DisplayPort
"HDMI-A": INTERFACE_ENUM[2], # HDMI
"HDMI-B": INTERFACE_ENUM[3], # HDMI (B)
"DVI-D": INTERFACE_ENUM[1], # DVI
"DVI-I": INTERFACE_ENUM[1], # DVI
"DVI-A": INTERFACE_ENUM[1], # DVI
"VGA": "Analog",
"LVDS": "LVDS",
"DSI": "DSI",
"DVI-D": INTERFACE_ENUM[1], # DVI
"DVI-I": INTERFACE_ENUM[1], # DVI
"DVI-A": INTERFACE_ENUM[1], # DVI
"VGA": "Analog",
"LVDS": "LVDS",
"DSI": "DSI",
}


Expand All @@ -41,15 +41,17 @@ def _parse_connector_type(device_path: str) -> Optional[str]:

def _fetch_individual_monitor_info(device_path: str) -> Optional[DisplayModuleInfo]:
edid_path = os.path.join(device_path, "edid")
if not os.path.exists(edid_path): return None
if not os.path.exists(edid_path):
return None
parent_path = os.path.join(device_path, "device")

# todo: populate parent graphics card info
# we have vendor and device ids of the parent gpu. When PCI-IDs integration is done, use it to get name

with open(edid_path, "rb") as f:
edid_data = f.read()
if len(edid_data) == 0: return None
if len(edid_data) == 0:
return None

monitor_data = parse_edid(edid_data)

Expand All @@ -63,7 +65,7 @@ def _fetch_individual_monitor_info(device_path: str) -> Optional[DisplayModuleIn

acpi_file = os.path.join(device_path, "firmware_node", "path")
if os.path.exists(acpi_file):
with open(acpi_file, "r") as f:
with open(acpi_file) as f:
monitor_data.acpi_path = f.read().strip()

return monitor_data
Expand Down Expand Up @@ -91,6 +93,6 @@ def fetch_display_info():
display_info.modules.append(response)
except Exception as e:
display_info.status.type = StatusType.PARTIAL
display_info.status.messages.append(f"Display Info ({child}): {str(e)}")
display_info.status.messages.append(f"Display Info ({child}): {e!s}")

return display_info
2 changes: 1 addition & 1 deletion src/hwprobe/core/linux/dmi_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ def get_string_entry(string, n):
0x1A: "DDR4",
0x1B: "LPDDR",
0x1C: "LPDDR2",
0x1D: "LPDDR3"
0x1D: "LPDDR3",
}
Loading
Loading