(
17 | value !== null
18 | ? Array.from({ length: cpus }, (_, index) =>
19 | value & (1 << index) ? index.toString() : null
20 | ).filter((index) => index !== null)
21 | : []
22 | );
23 |
24 | return (
25 |
26 |
{name}
27 | {
31 | if (value.length === 0) {
32 | window.pywebview.api
33 | .deleteRegistryValue("HKLM", path, name)
34 | .then(() => {
35 | setActiveCpus(value.sort((a, b) => parseInt(a) - parseInt(b)));
36 | })
37 | .catch((error: unknown) => {
38 | alert(error instanceof Error ? error.toString() : error);
39 | });
40 | } else {
41 | const binaryString = Array.from({ length: cpus }, (_, index) =>
42 | value.includes(index.toString()) ? "1" : "0"
43 | )
44 | .reverse()
45 | .join("");
46 |
47 | window.pywebview.api
48 | .writeRegistryValue(
49 | "HKLM",
50 | path,
51 | name,
52 | REGISTRY_DATA_TYPES.REG_BINARY,
53 | binaryString
54 | )
55 | .then(() => {
56 | setActiveCpus(value.sort((a, b) => parseInt(a) - parseInt(b)));
57 | })
58 | .catch((error: unknown) => {
59 | alert(error instanceof Error ? error.toString() : error);
60 | });
61 | }
62 | }}
63 | >
64 |
65 | {Array.from({ length: cpus }, (_, index) => (
66 |
67 | {index}
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
105 |
106 |
121 |
122 |
123 | );
124 | }
125 |
--------------------------------------------------------------------------------
/src/SCHEDULING/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Center, Menu, Stack } from "@mantine/core";
2 | import { IconChevronDown } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import s from "../App.module.css";
5 | import { formatBitMask } from "../shared";
6 | import AffinityMaskField from "./AffinityMask";
7 |
8 | export const KERNEL_PATH =
9 | "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel";
10 |
11 | export interface SchedulingInfo {
12 | ReservedCpuSets: number | null;
13 | SmallProcessorMask: number | null;
14 | cpu: { cpus: number };
15 | }
16 |
17 | function getExportData(schedulingInfo: SchedulingInfo, isReg: boolean) {
18 | const { ReservedCpuSets, SmallProcessorMask } = schedulingInfo;
19 |
20 | let content = "";
21 |
22 | if (isReg) {
23 | content =
24 | "Windows Registry Editor Version 5.00\n\n" +
25 | `[HKEY_LOCAL_MACHINE\\${KERNEL_PATH}]\n` +
26 | `"ReservedCpuSets"=${formatBitMask(ReservedCpuSets, true)}\n\n` +
27 | `[HKEY_LOCAL_MACHINE\\${KERNEL_PATH}\\KGroups\\00]\n` +
28 | `"SmallProcessorMask"=${formatBitMask(SmallProcessorMask, true)}`;
29 | } else {
30 | if (ReservedCpuSets !== null) {
31 | content += `reg add "HKEY_LOCAL_MACHINE\\${KERNEL_PATH}" /v ReservedCpuSets /t REG_BINARY /d ${formatBitMask(
32 | ReservedCpuSets,
33 | false
34 | )} /f\n`;
35 | } else {
36 | content += `reg delete "HKEY_LOCAL_MACHINE\\${KERNEL_PATH}" /v ReservedCpuSets /f\n`;
37 | }
38 |
39 | if (SmallProcessorMask !== null) {
40 | content += `reg add "HKEY_LOCAL_MACHINE\\${KERNEL_PATH}\\KGroups\\00" /v SmallProcessorMask /t REG_BINARY /d ${formatBitMask(
41 | SmallProcessorMask,
42 | false
43 | )} /f\n`;
44 | } else {
45 | content += `reg delete "HKEY_LOCAL_MACHINE\\${KERNEL_PATH}\\KGroups\\00" /v SmallProcessorMask /f\n`;
46 | }
47 | }
48 |
49 | return content;
50 | }
51 |
52 | export default function SCHEDULING() {
53 | const [schedulingInfo, setSchedulingInfo] = useState();
54 |
55 | useEffect(() => {
56 | window.pywebview.api
57 | .getSchedulingInfo()
58 | .then((data) => {
59 | setSchedulingInfo(data);
60 | })
61 | .catch((error: unknown) => {
62 | alert(error instanceof Error ? error.toString() : error);
63 | });
64 | }, []);
65 |
66 | if (!schedulingInfo) {
67 | return;
68 | }
69 |
70 | return (
71 | <>
72 |
73 |
115 |
116 |
117 |
118 |
119 |
125 |
126 |
132 |
133 |
134 | >
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/src/PCI_IRQs/AssignmentSetOverride.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Chip, Group, Modal } from "@mantine/core";
2 | import { useDisclosure } from "@mantine/hooks";
3 | import { useState } from "react";
4 | import { AFFINITY_PATH, type SystemInfo } from ".";
5 | import { REGISTRY_DATA_TYPES } from "../shared";
6 |
7 | export default function AssignmentSetOverrideField({
8 | device,
9 | cpus,
10 | }: {
11 | device: SystemInfo["devices"][number];
12 | cpus: number;
13 | }) {
14 | const [opened, { open, close }] = useDisclosure(false);
15 | const [activeCpus, setActiveCpus] = useState(
16 | device.AssignmentSetOverride !== null
17 | ? Array.from({ length: cpus }, (_, index) =>
18 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19 | device.AssignmentSetOverride! & (1 << index) ? index.toString() : null
20 | ).filter((index) => index !== null)
21 | : []
22 | );
23 |
24 | return (
25 | <>
26 |
27 | {
31 | if (value.length === 0) {
32 | window.pywebview.api
33 | .deleteRegistryValue(
34 | "HKLM",
35 | device.Path + AFFINITY_PATH,
36 | "AssignmentSetOverride"
37 | )
38 | .then(() => {
39 | setActiveCpus(
40 | value.sort((a, b) => parseInt(a) - parseInt(b))
41 | );
42 | })
43 | .catch((error: unknown) => {
44 | alert(error instanceof Error ? error.toString() : error);
45 | });
46 | } else {
47 | const binaryString = Array.from({ length: cpus }, (_, index) =>
48 | value.includes(index.toString()) ? "1" : "0"
49 | )
50 | .reverse()
51 | .join("");
52 |
53 | window.pywebview.api
54 | .writeRegistryValue(
55 | "HKLM",
56 | device.Path + AFFINITY_PATH,
57 | "AssignmentSetOverride",
58 | REGISTRY_DATA_TYPES.REG_BINARY,
59 | binaryString
60 | )
61 | .then(() => {
62 | setActiveCpus(
63 | value.sort((a, b) => parseInt(a) - parseInt(b))
64 | );
65 | })
66 | .catch((error: unknown) => {
67 | alert(error instanceof Error ? error.toString() : error);
68 | });
69 | }
70 | }}
71 | >
72 |
73 | {Array.from({ length: cpus }, (_, index) => (
74 |
75 | {index}
76 |
77 | ))}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
113 |
114 |
133 |
134 |
135 |
136 |
139 | >
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/main.pyw:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 | from os import path
3 | from traceback import format_exc
4 | from typing import Callable, Literal, ParamSpec, TypeVar
5 |
6 | from webview import SAVE_DIALOG, create_window, start
7 |
8 | P = ParamSpec("P")
9 | R = TypeVar("R")
10 |
11 |
12 | def handle_api_errors(func: Callable[P, R]) -> Callable[P, R]:
13 | @wraps(func)
14 | def wrapper(*args: P.args, **kwargs: P.kwargs):
15 | try:
16 | return func(*args, **kwargs)
17 | except Exception:
18 | raise Exception(format_exc())
19 |
20 | return wrapper
21 |
22 |
23 | class Api:
24 | @handle_api_errors
25 | def getSystemInfo(self):
26 | from py.PCI_IRQs import get_system_info
27 |
28 | return get_system_info()
29 |
30 | @handle_api_errors
31 | def getDisplayInfo(self):
32 | from py.DISPLAY_INFO import get_display_info
33 |
34 | return get_display_info()
35 |
36 | @handle_api_errors
37 | def writeRegistryValue(
38 | self,
39 | hkey_str: Literal["HKLM", "HKCU"],
40 | path: str,
41 | name: str,
42 | type: int,
43 | value: int | str,
44 | ):
45 | from py.utils import write_registry_value
46 |
47 | write_registry_value(hkey_str, path, name, type, value)
48 |
49 | @handle_api_errors
50 | def deleteRegistryValue(
51 | self, hkey_str: Literal["HKLM", "HKCU"], path: str, value: str
52 | ):
53 | from py.utils import delete_registry_value
54 |
55 | delete_registry_value(hkey_str, path, value)
56 |
57 | @handle_api_errors
58 | def deleteRegistryKey(self, path: str, key: str):
59 | from py.utils import delete_registry_key
60 |
61 | delete_registry_key(path, key)
62 |
63 | @handle_api_errors
64 | def createRegistryKey(self, path: str):
65 | from py.utils import create_registry_key
66 |
67 | create_registry_key(path)
68 |
69 | @handle_api_errors
70 | def openURL(self, url: str):
71 | from webbrowser import open
72 |
73 | open(url)
74 |
75 | @handle_api_errors
76 | def getIfeoData(self):
77 | from py.IFEO import get_ifeo_data
78 |
79 | return get_ifeo_data()
80 |
81 | @handle_api_errors
82 | def getSchedulingInfo(self):
83 | from py.SCHEDULING import get_scheduling_info
84 |
85 | return get_scheduling_info()
86 |
87 | @handle_api_errors
88 | def getPowerSettings(self):
89 | from py.POWER_SETTINGS import get_power_settings
90 |
91 | return get_power_settings()
92 |
93 | @handle_api_errors
94 | def writeValueIndex(
95 | self,
96 | scheme_guid_str: str,
97 | subgroup_guid_str: str,
98 | setting_guid_str: str,
99 | value_index: int,
100 | is_ac: bool,
101 | ):
102 | from py.POWER_SETTINGS import write_value_index
103 |
104 | write_value_index(
105 | scheme_guid_str, subgroup_guid_str, setting_guid_str, value_index, is_ac
106 | )
107 |
108 | @handle_api_errors
109 | def setActiveScheme(self, scheme_guid_str: str):
110 | from py.POWER_SETTINGS import set_active_scheme
111 |
112 | set_active_scheme(scheme_guid_str)
113 |
114 | @handle_api_errors
115 | def getCompatibilityOptions(self):
116 | from py.COMPATIBILITY_OPTIONS import get_compatibility_options
117 |
118 | return get_compatibility_options()
119 |
120 | @handle_api_errors
121 | def saveFile(self, file_name: str, file_types: tuple[str], content: str):
122 | path = window.create_file_dialog(
123 | SAVE_DIALOG,
124 | directory="/",
125 | save_filename=file_name,
126 | file_types=file_types,
127 | )
128 |
129 | if path is None:
130 | return
131 |
132 | with open(str(path), "w", encoding="utf-8") as file:
133 | file.write(content)
134 |
135 | @handle_api_errors
136 | def exportPowerScheme(self, guid: str, file_name: str, file_types: tuple[str]):
137 | path = window.create_file_dialog(
138 | SAVE_DIALOG,
139 | directory="/",
140 | save_filename=file_name,
141 | file_types=file_types,
142 | )
143 |
144 | if path is None:
145 | return
146 |
147 | from subprocess import CREATE_NO_WINDOW, run
148 |
149 | run(
150 | ["powercfg", "/export", str(path), guid],
151 | check=True,
152 | creationflags=CREATE_NO_WINDOW,
153 | )
154 |
155 |
156 | def on_shown():
157 | window.maximize()
158 |
159 |
160 | if __name__ == "__main__":
161 | isDevEnv = not path.exists(path.join(path.dirname(__file__), "gui/index.html"))
162 | entry_point = "http://localhost:5173/" if isDevEnv else "gui/index.html"
163 |
164 | window = create_window(
165 | "Windows MultiTool", entry_point, js_api=Api(), text_select=True
166 | )
167 | window.events.shown += on_shown
168 |
169 | start(ssl=True, debug=isDevEnv)
170 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { AppShell, Button, Center, NavLink } from "@mantine/core";
2 | import { IconDownload } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import packageJson from "../package.json";
5 | import COMPATIBILITY_OPTIONS, {
6 | type CompatibilityOptionsData,
7 | } from "./COMPATIBILITY_OPTIONS";
8 | import DISPLAY_INFO, { type DisplayInfo } from "./DISPLAY_INFO";
9 | import IFEO, { type IfeoData } from "./IFEO";
10 | import PCI_IRQs, { type SystemInfo } from "./PCI_IRQs";
11 | import POWER_SETTINGS, { type PowerSettings } from "./POWER_SETTINGS";
12 | import SCHEDULING, { type SchedulingInfo } from "./SCHEDULING";
13 |
14 | declare global {
15 | interface Window {
16 | pywebview: {
17 | api: {
18 | openURL: (url: string) => Promise;
19 | getSystemInfo: () => Promise;
20 | getDisplayInfo: () => Promise;
21 | getIfeoData: () => Promise;
22 | getSchedulingInfo: () => Promise;
23 | getPowerSettings: () => Promise;
24 | writeValueIndex: (
25 | schemeGuidStr: string,
26 | subgroupGuidStr: string,
27 | settingGuidStr: string,
28 | valueIndex: number,
29 | isAc: boolean
30 | ) => Promise;
31 | setActiveScheme: (schemeGuidStr: string) => Promise;
32 | writeRegistryValue: (
33 | hkeyStr: "HKLM" | "HKCU",
34 | path: string,
35 | name: string,
36 | type: number,
37 | value: number | string
38 | ) => Promise;
39 | deleteRegistryValue: (
40 | hkeyStr: "HKLM" | "HKCU",
41 | path: string,
42 | value: string
43 | ) => Promise;
44 | deleteRegistryKey: (path: string, key: string) => Promise;
45 | createRegistryKey: (path: string) => Promise;
46 | getCompatibilityOptions: () => Promise;
47 | saveFile: (
48 | fileName: string,
49 | fileTypes: string[],
50 | content: string
51 | ) => Promise;
52 | exportPowerScheme: (
53 | guid: string,
54 | fileName: string,
55 | fileTypes: string[]
56 | ) => Promise;
57 | };
58 | };
59 | }
60 | }
61 |
62 | const navItems = [
63 | { name: "PCI IRQs", component: },
64 | { name: "Display Info", component: },
65 | { name: "IFEO", component: },
66 | { name: "Scheduling", component: },
67 | { name: "Power Settings", component: },
68 | { name: "Compatibility Options", component: },
69 | ] as const;
70 |
71 | export default function App() {
72 | const [apiReady, setApiReady] = useState(false);
73 | const [active, setActive] = useState<
74 | (typeof navItems)[number]["name"] | undefined
75 | >();
76 | const [updateAvailable, setUpdateAvailable] = useState(false);
77 |
78 | useEffect(() => {
79 | window.addEventListener("pywebviewready", () => {
80 | setApiReady(true);
81 | });
82 |
83 | fetch(`https://api.github.com/repos/${packageJson.repo}/releases/latest`)
84 | .then((response) => {
85 | if (!response.ok) {
86 | throw new Error(response.statusText);
87 | }
88 |
89 | return response.json();
90 | })
91 | .then((data: { tag_name: string }) => {
92 | if (data.tag_name !== packageJson.version) {
93 | setUpdateAvailable(true);
94 | }
95 | })
96 | .catch((error: unknown) => {
97 | console.error(error);
98 | });
99 | }, []);
100 |
101 | if (!apiReady) {
102 | return;
103 | }
104 |
105 | return (
106 | <>
107 |
108 | {navItems.map((item) => {
109 | const { name } = item;
110 |
111 | return (
112 | {
117 | setActive(name);
118 | }}
119 | />
120 | );
121 | })}
122 |
123 |
124 |
125 | {active
126 | ? navItems.find((item) => item.name === active)?.component
127 | : updateAvailable && (
128 |
129 | }
132 | color="green"
133 | size="xl"
134 | onClick={() => {
135 | window.pywebview.api
136 | .openURL(
137 | "https://github.com/" +
138 | packageJson.repo +
139 | "/releases/latest"
140 | )
141 | .catch((error: unknown) => {
142 | alert(
143 | error instanceof Error ? error.toString() : error
144 | );
145 | });
146 | }}
147 | >
148 | Update available
149 |
150 |
151 | )}
152 |
153 | >
154 | );
155 | }
156 |
--------------------------------------------------------------------------------
/src/POWER_SETTINGS/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Menu, Table, Tabs } from "@mantine/core";
2 | import { IconChevronDown } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import s2 from "../App.module.css";
5 | import s from "./index.module.css";
6 | import PossibleValues from "./PossibleValues";
7 | import Values from "./Values";
8 |
9 | export interface PowerSettings {
10 | activeSchemeGuid: string;
11 | powerSchemes: PowerSchemes;
12 | }
13 |
14 | type PowerSchemes = {
15 | guid: string;
16 | name: string;
17 | settings: Setting[];
18 | }[];
19 |
20 | interface Subgroup {
21 | guid: string;
22 | name: string | null;
23 | }
24 |
25 | interface Option {
26 | index: number;
27 | name: string;
28 | description: string;
29 | }
30 |
31 | interface Range {
32 | min: number;
33 | max: number;
34 | increment: number;
35 | unit: string;
36 | }
37 |
38 | export interface Setting {
39 | guid: string;
40 | name: string;
41 | description: string;
42 | options: Option[] | null;
43 | range: Range | null;
44 | subgroup: Subgroup;
45 | ac: number;
46 | dc: number;
47 | }
48 |
49 | export default function POWER_SETTINGS() {
50 | const [powerSettings, setPowerSettings] = useState();
51 |
52 | useEffect(() => {
53 | window.pywebview.api
54 | .getPowerSettings()
55 | .then((data) => {
56 | setPowerSettings(data);
57 | })
58 | .catch((error: unknown) => {
59 | alert(error instanceof Error ? error.toString() : error);
60 | });
61 | }, []);
62 |
63 | if (!powerSettings) {
64 | return;
65 | }
66 |
67 | const { powerSchemes, activeSchemeGuid } = powerSettings;
68 |
69 | const activeSchemeIndex = powerSchemes.findIndex(
70 | (scheme) => scheme.guid === activeSchemeGuid
71 | );
72 |
73 | return (
74 | <>
75 |
76 |
105 |
106 |
107 |
108 |
109 | {powerSchemes.map((powerScheme, index) => (
110 | {
114 | window.pywebview.api
115 | .setActiveScheme(powerScheme.guid)
116 | .catch((error: unknown) => {
117 | alert(error instanceof Error ? error.toString() : error);
118 | });
119 | }}
120 | >
121 | {powerScheme.name}
122 |
123 | ))}
124 |
125 |
126 |
127 |
128 |
129 | Subgroup
130 | Setting
131 | Value
132 | Possible Values
133 | Description
134 |
135 |
136 |
137 |
138 | {powerSchemes[activeSchemeIndex].settings.map(
139 | (setting, settingIndex) => (
140 |
141 | {setting.subgroup.name}
142 |
143 | {setting.name}
144 |
145 |
146 | {powerSchemes.map((powerScheme, powerSchemeIndex) => (
147 |
151 |
156 |
157 | ))}
158 |
159 |
160 |
161 |
162 |
163 |
164 | {setting.description}
165 |
166 | )
167 | )}
168 |
169 |
170 |
171 | >
172 | );
173 | }
174 |
--------------------------------------------------------------------------------
/src/COMPATIBILITY_OPTIONS/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Menu, Table } from "@mantine/core";
2 | import { IconChevronDown } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import s from "../App.module.css";
5 | import TableComponent from "./Table";
6 |
7 | export type CompatibilityOptions = [
8 | null | "~",
9 | null | "DISABLEDXMAXIMIZEDWINDOWEDMODE",
10 | null | "RUNASADMIN",
11 | null | "640X480",
12 | null | "PERPROCESSSYSTEMDPIFORCEOFF" | "PERPROCESSSYSTEMDPIFORCEON",
13 | null | "HIGHDPIAWARE" | "DPIUNAWARE" | "GDIDPISCALING DPIUNAWARE",
14 | null | "256COLOR",
15 | null | "16BITCOLOR",
16 | null | "TRANSFORMLEGACYCOLORMANAGED",
17 | (
18 | | null
19 | | "WIN95"
20 | | "WIN98"
21 | | "WINXPSP2"
22 | | "WINXPSP3"
23 | | "VISTARTM"
24 | | "VISTASP1"
25 | | "VISTASP2"
26 | | "WIN7RTM"
27 | | "WIN8RTM"
28 | )
29 | ];
30 |
31 | export interface CompatibilityOptionsData {
32 | HKLM: HiveData;
33 | HKCU: HiveData;
34 | }
35 |
36 | type HiveData = Map;
37 |
38 | export const COMPAT_PATH =
39 | "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers";
40 |
41 | function processHive(
42 | hive: HiveData,
43 | hiveKey: "HKEY_LOCAL_MACHINE" | "HKEY_CURRENT_USER",
44 | isReg: boolean
45 | ) {
46 | if (hive.size === 0) {
47 | return "";
48 | }
49 |
50 | const regPath = `${hiveKey}\\${COMPAT_PATH}`;
51 |
52 | let content = "";
53 |
54 | if (isReg) {
55 | content = `[${regPath}]\n`;
56 | } else {
57 | content = `set "REG_PATH=${regPath}"\n\n`;
58 | }
59 |
60 | for (const [name, options] of hive) {
61 | const value = options.filter((x) => x !== null).join(" ");
62 |
63 | if (value) {
64 | if (isReg) {
65 | const escapedName = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
66 | content += `"${escapedName}"="${value}"\n`;
67 | } else {
68 | content += `reg add "%REG_PATH%" /v "${name}" /t REG_SZ /d "${value}" /f\n`;
69 | }
70 | }
71 | }
72 |
73 | return content + "\n";
74 | }
75 |
76 | export default function COMPATIBILITY_OPTIONS() {
77 | const [compatibilityOptionsData, setCompatibilityOptionsData] =
78 | useState();
79 |
80 | useEffect(() => {
81 | window.pywebview.api
82 | .getCompatibilityOptions()
83 | .then((data) => {
84 | setCompatibilityOptionsData({
85 | HKLM: new Map(Object.entries(data.HKLM)) as HiveData,
86 | HKCU: new Map(Object.entries(data.HKCU)) as HiveData,
87 | });
88 | })
89 | .catch((error: unknown) => {
90 | alert(error instanceof Error ? error.toString() : error);
91 | });
92 | }, []);
93 |
94 | if (!compatibilityOptionsData) {
95 | return;
96 | }
97 |
98 | return (
99 | <>
100 |
101 |
156 |
157 |
158 |
159 |
160 |
161 | Process Path
162 | ~
163 | Disable FSO
164 | Run as admin
165 | Run in 640x480
166 | Override system DPI
167 | Override high DPI scaling behavior
168 | Reduce color mode (8-bit 256)
169 | Reduce color mode (16-bit 65536)
170 | Use legacy display ICC color management
171 | Windows version
172 |
173 |
174 |
175 |
180 |
185 |
186 | >
187 | );
188 | }
189 |
--------------------------------------------------------------------------------
/src/IFEO/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Group, Menu, Table } from "@mantine/core";
2 | import { IconChevronDown, IconMinus } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import s from "../App.module.css";
5 | import { formatRegValue } from "../shared";
6 | import AddNewProcess from "./AddProcess";
7 | import CpuPriorityField from "./CpuPriority";
8 | import IoPriorityField from "./IoPriority";
9 | import PagePriorityField from "./PagePriority";
10 |
11 | export interface IfeoDataValue {
12 | Path: string;
13 | CpuPriorityClass: number | null;
14 | IoPriority: number | null;
15 | PagePriority: number | null;
16 | }
17 |
18 | export type IfeoData = Map;
19 |
20 | export const IFEO_PATH =
21 | "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options";
22 |
23 | function getExportData(ifeoData: IfeoData, isReg: boolean) {
24 | let content = "";
25 |
26 | if (isReg) {
27 | content += "Windows Registry Editor Version 5.00\n\n";
28 |
29 | for (const [name, data] of ifeoData) {
30 | content +=
31 | `[HKEY_LOCAL_MACHINE\\${IFEO_PATH}\\${name}\\PerfOptions]\n` +
32 | formatRegValue("CpuPriorityClass", data.CpuPriorityClass) +
33 | formatRegValue("IoPriority", data.IoPriority) +
34 | formatRegValue("PagePriority", data.PagePriority) +
35 | "\n";
36 | }
37 | } else {
38 | content +=
39 | "setlocal\n\n" + `set "IFEO_BASE=HKEY_LOCAL_MACHINE\\${IFEO_PATH}"\n\n`;
40 |
41 | for (const [name, data] of ifeoData) {
42 | content += `set "APP_KEY=%IFEO_BASE%\\${name}\\PerfOptions"\n`;
43 |
44 | const baseAdd = `reg add "%APP_KEY%"`;
45 | const baseDelete = `reg delete "%APP_KEY%"`;
46 |
47 | if (data.CpuPriorityClass !== null) {
48 | content += `${baseAdd} /v CpuPriorityClass /t REG_DWORD /d ${data.CpuPriorityClass.toString()} /f\n`;
49 | } else {
50 | content += `${baseDelete} /v CpuPriorityClass /f\n`;
51 | }
52 |
53 | if (data.IoPriority !== null) {
54 | content += `${baseAdd} /v IoPriority /t REG_DWORD /d ${data.IoPriority.toString()} /f\n`;
55 | } else {
56 | content += `${baseDelete} /v IoPriority /f\n`;
57 | }
58 |
59 | if (data.PagePriority !== null) {
60 | content += `${baseAdd} /v PagePriority /t REG_DWORD /d ${data.PagePriority.toString()} /f\n`;
61 | } else {
62 | content += `${baseDelete} /v PagePriority /f\n`;
63 | }
64 |
65 | content += "\n";
66 | }
67 |
68 | content += "endlocal";
69 | }
70 |
71 | return content;
72 | }
73 |
74 | export default function IFEO() {
75 | const [ifeoData, setIfeoData] = useState();
76 |
77 | useEffect(() => {
78 | window.pywebview.api
79 | .getIfeoData()
80 | .then((data) => {
81 | setIfeoData(new Map(Object.entries(data)));
82 | })
83 | .catch((error: unknown) => {
84 | alert(error instanceof Error ? error.toString() : error);
85 | });
86 | }, []);
87 |
88 | if (!ifeoData) {
89 | return;
90 | }
91 |
92 | return (
93 | <>
94 |
95 |
141 |
142 |
143 |
144 |
145 |
146 | Process
147 | CPU Priority Class
148 | IO Priority
149 | Memory Priority
150 |
151 |
152 |
153 |
154 | {[...ifeoData.entries()]
155 | .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
156 | .map(([process_name, data]) => (
157 |
158 |
159 |
160 | {
165 | window.pywebview.api
166 | .deleteRegistryKey(data.Path, "PerfOptions")
167 | .then(() => {
168 | setIfeoData((prev) => {
169 | if (!prev) {
170 | return prev;
171 | }
172 |
173 | const newData = new Map(prev);
174 | newData.delete(process_name);
175 |
176 | return newData;
177 | });
178 | })
179 | .catch((error: unknown) => {
180 | alert(
181 | error instanceof Error ? error.toString() : error
182 | );
183 | });
184 | }}
185 | >
186 |
187 |
188 | {process_name}
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | ))}
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 | >
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/src/DISPLAY_INFO/index.tsx:
--------------------------------------------------------------------------------
1 | import { Group, Table } from "@mantine/core";
2 | import { useEffect, useState } from "react";
3 | import s from "./index.module.css";
4 |
5 | const DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY = {
6 | [-1]: "Other",
7 | [0]: "HD-15 (VGA)",
8 | [1]: "S-Video",
9 | [2]: "Composite Video",
10 | [3]: "Component Video",
11 | [4]: "DVI",
12 | [5]: "HDMI",
13 | [6]: "LVDS",
14 | [8]: "D-Terminal",
15 | [9]: "SDI",
16 | [10]: "DisplayPort External",
17 | [11]: "DisplayPort Embedded",
18 | [12]: "UDI External",
19 | [13]: "UDI Embedded",
20 | [14]: "SDTV Dongle",
21 | [15]: "Miracast",
22 | [16]: "Indirect Wired",
23 | [17]: "Indirect Virtual",
24 | [18]: "DisplayPort USB Tunnel",
25 | [0x80000000]: "Internal",
26 | [0xffffffff]: "Force UInt32",
27 | } as const;
28 |
29 | const DISPLAYCONFIG_ROTATION = {
30 | [1]: "0 °",
31 | [2]: "90 °",
32 | [3]: "180 °",
33 | [4]: "270 °",
34 | [0xffffffff]: "Force UInt32",
35 | } as const;
36 |
37 | const DISPLAYCONFIG_SCALING = {
38 | [1]: "Identity",
39 | [2]: "Centered",
40 | [3]: "Stretched",
41 | [4]: "AspectRatioCenteredMax",
42 | [5]: "Custom",
43 | [128]: "Preferred",
44 | [0xffffffff]: "Force UInt32",
45 | } as const;
46 |
47 | const DISPLAYCONFIG_PIXELFORMAT = {
48 | [1]: "8 BPP",
49 | [2]: "16 BPP",
50 | [3]: "24 BPP",
51 | [4]: "32 BPP",
52 | [5]: "NONGDI",
53 | [0xffffffff]: "Force UInt32",
54 | } as const;
55 |
56 | export interface DisplayInfo {
57 | displays: {
58 | monitorFriendlyDeviceName: string;
59 | monitorDevicePath: string;
60 | adapterDevicePath: string;
61 | outputTechnology: number;
62 | rotation: number;
63 | refreshRate: number;
64 | horizontalFrequency: number;
65 | resolution: string;
66 | pixelRate: number;
67 | scaling: number;
68 | pixelFormat: number;
69 | mpo: {
70 | MaxPlanes: number;
71 | MaxRGBPlanes: number;
72 | MaxYUVPlanes: number;
73 | MaxStretchFactor: number;
74 | MaxShrinkFactor: number;
75 | caps: {
76 | Rotation: number;
77 | RotationWithoutIndependentFlip: number;
78 | VerticalFlip: number;
79 | HorizontalFlip: number;
80 | StretchRGB: number;
81 | StretchYUV: number;
82 | BilinearFilter: number;
83 | HighFilter: number;
84 | Shared: number;
85 | Immediate: number;
86 | Plane0ForVirtualModeOnly: number;
87 | Version3DDISupport: number;
88 | };
89 | };
90 | }[];
91 | }
92 |
93 | export default function DISPLAY_INFO() {
94 | const [displayInfo, setDisplayInfo] = useState();
95 |
96 | useEffect(() => {
97 | window.pywebview.api
98 | .getDisplayInfo()
99 | .then((data) => {
100 | setDisplayInfo(data);
101 | })
102 | .catch((error: unknown) => {
103 | alert(error instanceof Error ? error.toString() : error);
104 | });
105 | }, []);
106 |
107 | if (!displayInfo) {
108 | return;
109 | }
110 |
111 | return (
112 |
113 |
114 |
115 |
116 | {displayInfo.displays.map((display) => (
117 |
118 | {display.monitorFriendlyDeviceName}
119 | {display.monitorDevicePath}
120 |
121 | ))}
122 |
123 |
124 |
125 |
126 |
127 | Output Technology
128 | {displayInfo.displays.map((display) => (
129 |
130 | {
131 | DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY[
132 | display.outputTechnology as keyof typeof DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY
133 | ]
134 | }
135 |
136 | ))}
137 |
138 |
139 |
140 | Rotation
141 | {displayInfo.displays.map((display) => (
142 |
143 | {
144 | DISPLAYCONFIG_ROTATION[
145 | display.rotation as keyof typeof DISPLAYCONFIG_ROTATION
146 | ]
147 | }
148 |
149 | ))}
150 |
151 |
152 |
153 | Refresh Rate
154 | {displayInfo.displays.map((display) => (
155 |
156 | {display.refreshRate.toString() + " Hz"}
157 |
158 | ))}
159 |
160 |
161 |
162 | Horizontal Frequency
163 | {displayInfo.displays.map((display) => (
164 |
165 | {display.horizontalFrequency.toString() + " kHz"}
166 |
167 | ))}
168 |
169 |
170 |
171 | Resolution
172 | {displayInfo.displays.map((display) => (
173 |
174 | {display.resolution}
175 |
176 | ))}
177 |
178 |
179 |
180 | Pixel Rate
181 | {displayInfo.displays.map((display) => (
182 |
183 | {display.pixelRate.toString() + " MHz"}
184 |
185 | ))}
186 |
187 |
188 |
189 | Scaling
190 | {displayInfo.displays.map((display) => (
191 |
192 | {
193 | DISPLAYCONFIG_SCALING[
194 | display.scaling as keyof typeof DISPLAYCONFIG_SCALING
195 | ]
196 | }
197 |
198 | ))}
199 |
200 |
201 |
202 | Pixel Format
203 | {displayInfo.displays.map((display) => (
204 |
205 | {
206 | DISPLAYCONFIG_PIXELFORMAT[
207 | display.pixelFormat as keyof typeof DISPLAYCONFIG_PIXELFORMAT
208 | ]
209 | }
210 |
211 | ))}
212 |
213 |
214 |
215 | MPO Capabilities
216 | {displayInfo.displays.map((display) => {
217 | const { mpo } = display;
218 |
219 | return (
220 |
221 | {Object.entries(mpo).map(([key, value]) => {
222 | if (key !== "caps") {
223 | return (
224 |
225 | {key}
226 | {value as number}
227 |
228 | );
229 | }
230 | })}
231 |
232 |
233 |
234 | {Object.entries(mpo.caps).map(([key, value]) => (
235 |
236 | {key}
237 |
238 | {value ? "✓" : "✗"}
239 |
240 |
241 | ))}
242 |
243 | );
244 | })}
245 |
246 |
247 |
248 | );
249 | }
250 |
--------------------------------------------------------------------------------
/src/PCI_IRQs/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Menu, Table } from "@mantine/core";
2 | import { IconChevronDown } from "@tabler/icons-react";
3 | import { useEffect, useState } from "react";
4 | import s2 from "../App.module.css";
5 | import { formatBitMask, formatRegValue } from "../shared";
6 | import AssignmentSetOverrideField from "./AssignmentSetOverride";
7 | import DevicePolicyField from "./DevicePolicy";
8 | import DevicePriorityField from "./DevicePriority";
9 | import s from "./index.module.css";
10 | import MessageNumberLimitField from "./MessageNumberLimit";
11 | import MSISupportedField from "./MSISupported";
12 |
13 | export interface SystemInfo {
14 | cpu: { cpus: number };
15 | devices: {
16 | DeviceId: string;
17 | Path: string;
18 | DeviceName: string;
19 | DevicePriority: number | null;
20 | DevicePolicy: number | null;
21 | AssignmentSetOverride: number | null;
22 | MessageNumberLimit: number | null;
23 | MSISupported: number | null;
24 | InterruptSupport: number | null;
25 | MaximumMessageNumberLimit: number | null;
26 | }[];
27 | }
28 |
29 | export const AFFINITY_PATH =
30 | "\\Device Parameters\\Interrupt Management\\Affinity Policy";
31 | export const MSI_PATH =
32 | "\\Device Parameters\\Interrupt Management\\MessageSignaledInterruptProperties";
33 |
34 | function getExportData(systemInfo: SystemInfo, isReg: boolean) {
35 | let content = "";
36 |
37 | if (isReg) {
38 | content += "Windows Registry Editor Version 5.00\n\n";
39 |
40 | for (const device of systemInfo.devices) {
41 | content +=
42 | `[HKEY_LOCAL_MACHINE\\${device.Path}${AFFINITY_PATH}]\n` +
43 | formatRegValue("DevicePriority", device.DevicePriority) +
44 | formatRegValue("DevicePolicy", device.DevicePolicy) +
45 | `"AssignmentSetOverride"=${formatBitMask(
46 | device.AssignmentSetOverride,
47 | true
48 | )}\n\n` +
49 | `[HKEY_LOCAL_MACHINE\\${device.Path}${MSI_PATH}]\n` +
50 | formatRegValue("MSISupported", device.MSISupported) +
51 | formatRegValue("MessageNumberLimit", device.MessageNumberLimit) +
52 | "\n";
53 | }
54 | } else {
55 | content +=
56 | "setlocal\n\n" +
57 | `set "AFFINITY_PATH=${AFFINITY_PATH}"\n` +
58 | `set "MSI_PATH=${MSI_PATH}"\n\n`;
59 |
60 | for (const device of systemInfo.devices) {
61 | content += `set "BASE=${device.Path}"\n\n`;
62 |
63 | const baseAddAffinity = `reg add "%BASE%%AFFINITY_PATH%"`;
64 | const baseDeleteAffinity = `reg delete "%BASE%%AFFINITY_PATH%"`;
65 | const baseAddMsi = `reg add "%BASE%%MSI_PATH%"`;
66 | const baseDeleteMsi = `reg delete "%BASE%%MSI_PATH%"`;
67 |
68 | if (device.DevicePriority !== null) {
69 | content += `${baseAddAffinity} /v DevicePriority /t REG_DWORD /d ${device.DevicePriority.toString()} /f \n`;
70 | } else {
71 | content += `${baseDeleteAffinity} /v DevicePriority /f \n`;
72 | }
73 |
74 | if (device.DevicePolicy !== null) {
75 | content += `${baseAddAffinity} /v DevicePolicy /t REG_DWORD /d ${device.DevicePolicy.toString()} /f \n`;
76 | } else {
77 | content += `${baseDeleteAffinity} /v DevicePolicy /f \n`;
78 | }
79 |
80 | if (device.AssignmentSetOverride !== null) {
81 | content += `${baseAddAffinity} /v AssignmentSetOverride /t REG_BINARY /d ${formatBitMask(
82 | device.AssignmentSetOverride,
83 | false
84 | )} /f \n`;
85 | } else {
86 | content += `${baseDeleteAffinity} /v AssignmentSetOverride /f \n`;
87 | }
88 |
89 | if (device.MSISupported !== null) {
90 | content += `${baseAddMsi} /v MSISupported /t REG_DWORD /d ${device.MSISupported.toString()} /f \n`;
91 | } else {
92 | content += `${baseDeleteMsi} /v MSISupported /f \n`;
93 | }
94 |
95 | if (device.MessageNumberLimit !== null) {
96 | content += `${baseAddMsi} /v MessageNumberLimit /t REG_DWORD /d ${device.MessageNumberLimit.toString()} /f \n`;
97 | } else {
98 | content += `${baseDeleteMsi} /v MessageNumberLimit /f \n`;
99 | }
100 |
101 | content += "\n";
102 | }
103 |
104 | content += "endlocal";
105 | }
106 |
107 | return content;
108 | }
109 |
110 | function formatInterruptSupport(value: number | null) {
111 | if (value === null) {
112 | return;
113 | }
114 |
115 | const supportedTypes: string[] = [];
116 | if (value & 0x1) {
117 | supportedTypes.push("Line");
118 | }
119 | if (value & 0x2) {
120 | supportedTypes.push("MSI");
121 | }
122 | if (value & 0x4) {
123 | supportedTypes.push("MSI-X");
124 | }
125 |
126 | return supportedTypes.length > 0 ? supportedTypes.join(", ") : "-";
127 | }
128 |
129 | export default function PCI_IRQs() {
130 | const [systemInfo, setSystemInfo] = useState();
131 |
132 | useEffect(() => {
133 | window.pywebview.api
134 | .getSystemInfo()
135 | .then((data) => {
136 | setSystemInfo(data);
137 | })
138 | .catch((error: unknown) => {
139 | alert(error instanceof Error ? error.toString() : error);
140 | });
141 | }, []);
142 |
143 | if (!systemInfo) {
144 | return;
145 | }
146 |
147 | return (
148 | <>
149 |
150 |
192 |
193 |
194 |
195 |
196 |
197 | Device
198 | IRQ Priority
199 | IRQ Policy
200 | CPUs
201 | MSI
202 | Message Limit
203 | Max Limit
204 | Interrupt Type
205 |
206 |
207 |
208 |
209 | {systemInfo.devices.map((device) => (
210 |
211 |
212 | {device.DeviceName}
213 | {device.DeviceId}
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 | {device.MaximumMessageNumberLimit}
240 |
241 |
242 | {formatInterruptSupport(device.InterruptSupport)}
243 |
244 |
245 | ))}
246 |
247 |
248 | >
249 | );
250 | }
251 |
--------------------------------------------------------------------------------
/src/COMPATIBILITY_OPTIONS/Options.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox, Select, Table } from "@mantine/core";
2 | import { useState } from "react";
3 | import { COMPAT_PATH, type CompatibilityOptions } from ".";
4 | import { REGISTRY_DATA_TYPES } from "../shared";
5 |
6 | export default function Options({
7 | name,
8 | compatibilityOptions,
9 | hiveStr,
10 | }: {
11 | name: string;
12 | compatibilityOptions: CompatibilityOptions;
13 | hiveStr: "HKLM" | "HKCU";
14 | }) {
15 | const [data, setData] = useState([
16 | ...compatibilityOptions,
17 | ]);
18 |
19 | return (
20 | <>
21 |
22 | {
25 | const newData: CompatibilityOptions = [...data];
26 | newData[0] = ev.currentTarget.checked ? "~" : null;
27 |
28 | window.pywebview.api
29 | .writeRegistryValue(
30 | hiveStr,
31 | COMPAT_PATH,
32 | name,
33 | REGISTRY_DATA_TYPES.REG_SZ,
34 | newData.filter((x) => x !== null).join(" ")
35 | )
36 | .then(() => {
37 | setData(newData);
38 | })
39 | .catch((error: unknown) => {
40 | alert(error instanceof Error ? error.toString() : error);
41 | });
42 | }}
43 | />
44 |
45 |
46 |
47 | {
50 | const newData: CompatibilityOptions = [...data];
51 | newData[1] = ev.currentTarget.checked
52 | ? "DISABLEDXMAXIMIZEDWINDOWEDMODE"
53 | : null;
54 |
55 | window.pywebview.api
56 | .writeRegistryValue(
57 | hiveStr,
58 | COMPAT_PATH,
59 | name,
60 | REGISTRY_DATA_TYPES.REG_SZ,
61 | newData.filter((x) => x !== null).join(" ")
62 | )
63 | .then(() => {
64 | setData(newData);
65 | })
66 | .catch((error: unknown) => {
67 | alert(error instanceof Error ? error.toString() : error);
68 | });
69 | }}
70 | />
71 |
72 |
73 |
74 | {
77 | const newData: CompatibilityOptions = [...data];
78 | newData[2] = ev.currentTarget.checked ? "RUNASADMIN" : null;
79 |
80 | window.pywebview.api
81 | .writeRegistryValue(
82 | hiveStr,
83 | COMPAT_PATH,
84 | name,
85 | REGISTRY_DATA_TYPES.REG_SZ,
86 | newData.filter((x) => x !== null).join(" ")
87 | )
88 | .then(() => {
89 | setData(newData);
90 | })
91 | .catch((error: unknown) => {
92 | alert(error instanceof Error ? error.toString() : error);
93 | });
94 | }}
95 | />
96 |
97 |
98 |
99 | {
102 | const newData: CompatibilityOptions = [...data];
103 | newData[3] = ev.currentTarget.checked ? "640X480" : null;
104 |
105 | window.pywebview.api
106 | .writeRegistryValue(
107 | hiveStr,
108 | COMPAT_PATH,
109 | name,
110 | REGISTRY_DATA_TYPES.REG_SZ,
111 | newData.filter((x) => x !== null).join(" ")
112 | )
113 | .then(() => {
114 | setData(newData);
115 | })
116 | .catch((error: unknown) => {
117 | alert(error instanceof Error ? error.toString() : error);
118 | });
119 | }}
120 | />
121 |
122 |
123 |
124 |
150 |
151 |
152 |
178 |
179 |
180 | {
183 | const newData: CompatibilityOptions = [...data];
184 | newData[6] = ev.currentTarget.checked ? "256COLOR" : null;
185 |
186 | window.pywebview.api
187 | .writeRegistryValue(
188 | hiveStr,
189 | COMPAT_PATH,
190 | name,
191 | REGISTRY_DATA_TYPES.REG_SZ,
192 | newData.filter((x) => x !== null).join(" ")
193 | )
194 | .then(() => {
195 | setData(newData);
196 | })
197 | .catch((error: unknown) => {
198 | alert(error instanceof Error ? error.toString() : error);
199 | });
200 | }}
201 | />
202 |
203 |
204 |
205 | {
208 | const newData: CompatibilityOptions = [...data];
209 | newData[7] = ev.currentTarget.checked ? "16BITCOLOR" : null;
210 |
211 | window.pywebview.api
212 | .writeRegistryValue(
213 | hiveStr,
214 | COMPAT_PATH,
215 | name,
216 | REGISTRY_DATA_TYPES.REG_SZ,
217 | newData.filter((x) => x !== null).join(" ")
218 | )
219 | .then(() => {
220 | setData(newData);
221 | })
222 | .catch((error: unknown) => {
223 | alert(error instanceof Error ? error.toString() : error);
224 | });
225 | }}
226 | />
227 |
228 |
229 |
230 | {
233 | const newData: CompatibilityOptions = [...data];
234 | newData[8] = ev.currentTarget.checked
235 | ? "TRANSFORMLEGACYCOLORMANAGED"
236 | : null;
237 |
238 | window.pywebview.api
239 | .writeRegistryValue(
240 | hiveStr,
241 | COMPAT_PATH,
242 | name,
243 | REGISTRY_DATA_TYPES.REG_SZ,
244 | newData.filter((x) => x !== null).join(" ")
245 | )
246 | .then(() => {
247 | setData(newData);
248 | })
249 | .catch((error: unknown) => {
250 | alert(error instanceof Error ? error.toString() : error);
251 | });
252 | }}
253 | />
254 |
255 |
256 |
257 |
293 | >
294 | );
295 | }
296 |
--------------------------------------------------------------------------------
/py/PCI_IRQs/__init__.py:
--------------------------------------------------------------------------------
1 | from ctypes import (
2 | POINTER,
3 | GetLastError,
4 | Structure,
5 | WinError,
6 | byref,
7 | c_byte,
8 | c_long,
9 | c_ulong,
10 | c_wchar_p,
11 | cast,
12 | sizeof,
13 | windll,
14 | )
15 | from ctypes.wintypes import BOOL, DWORD, HANDLE, HWND
16 | from os import cpu_count
17 | from typing import Final, TypedDict
18 | from winreg import (
19 | HKEY_LOCAL_MACHINE,
20 | KEY_READ,
21 | KEY_WOW64_64KEY,
22 | EnumKey,
23 | OpenKeyEx,
24 | QueryInfoKey,
25 | QueryValueEx,
26 | )
27 |
28 | from py.utils import GUID
29 |
30 |
31 | class DEVPROPKEY(Structure):
32 | _fields_ = [
33 | ("fmtid", GUID),
34 | ("pid", c_ulong),
35 | ]
36 |
37 |
38 | class SP_DEVINFO_DATA(Structure):
39 | _fields_ = [
40 | ("cbSize", c_ulong),
41 | ("ClassGuid", GUID),
42 | ("DevInst", c_ulong),
43 | ("Reserved", POINTER(c_ulong)),
44 | ]
45 |
46 |
47 | class Device(TypedDict):
48 | DeviceId: str
49 | Path: str
50 | DeviceName: str
51 | DevicePriority: int | None
52 | DevicePolicy: int | None
53 | AssignmentSetOverride: int | None
54 | MessageNumberLimit: int | None
55 | MSISupported: int | None
56 | InterruptSupport: int | None
57 | MaximumMessageNumberLimit: int | None
58 |
59 |
60 | BASE_PATH: Final = r"SYSTEM\CurrentControlSet\Enum\PCI"
61 |
62 | CONFIGFLAG_DISABLED: Final = 0x1
63 | CONFIGFLAG_REMOVED: Final = 0x2
64 |
65 | DIGCF_ALLCLASSES: Final = 0x4
66 | DIGCF_DEVICEINTERFACE: Final = 0x10
67 |
68 | ERROR_INSUFFICIENT_BUFFER: Final = 122
69 | ERROR_NO_MORE_ITEMS: Final = 259
70 | ERROR_ELEMENT_NOT_FOUND: Final = 1168
71 |
72 | setupapi = windll.setupapi
73 |
74 | SetupDiGetClassDevsW = setupapi.SetupDiGetClassDevsW
75 | SetupDiGetClassDevsW.argtypes = [POINTER(GUID), c_wchar_p, HWND, DWORD]
76 | SetupDiGetClassDevsW.restype = HANDLE
77 |
78 | SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
79 | SetupDiEnumDeviceInfo.argtypes = [HANDLE, DWORD, POINTER(SP_DEVINFO_DATA)]
80 | SetupDiEnumDeviceInfo.restype = BOOL
81 |
82 | SetupDiGetDevicePropertyW = setupapi.SetupDiGetDevicePropertyW
83 | SetupDiGetDevicePropertyW.argtypes = [
84 | HANDLE,
85 | POINTER(SP_DEVINFO_DATA),
86 | POINTER(DEVPROPKEY),
87 | POINTER(c_ulong),
88 | POINTER(c_byte),
89 | DWORD,
90 | POINTER(DWORD),
91 | DWORD,
92 | ]
93 | SetupDiGetDevicePropertyW.restype = BOOL
94 |
95 | SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
96 | SetupDiDestroyDeviceInfoList.argtypes = [HANDLE]
97 | SetupDiDestroyDeviceInfoList.restype = BOOL
98 |
99 |
100 | def get_device_property(
101 | dev_info_handle: HANDLE,
102 | dev_info_data: SP_DEVINFO_DATA,
103 | DEVPKEY: DEVPROPKEY,
104 | ):
105 | property_type = c_ulong()
106 | required_size = c_ulong()
107 |
108 | result = SetupDiGetDevicePropertyW(
109 | dev_info_handle,
110 | byref(dev_info_data),
111 | byref(DEVPKEY),
112 | byref(property_type),
113 | None,
114 | 0,
115 | byref(required_size),
116 | 0,
117 | )
118 | last_error = GetLastError()
119 | if last_error == ERROR_ELEMENT_NOT_FOUND:
120 | return None
121 | if last_error != ERROR_INSUFFICIENT_BUFFER:
122 | raise WinError()
123 |
124 | property_buffer = (c_byte * required_size.value)()
125 | result = SetupDiGetDevicePropertyW(
126 | dev_info_handle,
127 | byref(dev_info_data),
128 | byref(DEVPKEY),
129 | byref(property_type),
130 | property_buffer,
131 | required_size.value,
132 | None,
133 | 0,
134 | )
135 | if result == 0:
136 | raise WinError()
137 | else:
138 | return cast(property_buffer, POINTER(c_long)).contents.value
139 |
140 |
141 | def get_additional_info(instance_id: str):
142 | extra_info: dict[str, int | None] = {
143 | "InterruptSupport": None,
144 | "MaximumMessageNumberLimit": None,
145 | }
146 |
147 | dev_info_handle = SetupDiGetClassDevsW(
148 | None,
149 | c_wchar_p(instance_id),
150 | None,
151 | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE,
152 | )
153 | if dev_info_handle == -1:
154 | raise WinError()
155 |
156 | dev_info_data = SP_DEVINFO_DATA()
157 | dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA)
158 |
159 | result = SetupDiEnumDeviceInfo(dev_info_handle, 0, byref(dev_info_data))
160 | if result == 0:
161 | if GetLastError() == ERROR_NO_MORE_ITEMS:
162 | SetupDiDestroyDeviceInfoList(dev_info_handle)
163 |
164 | return extra_info
165 |
166 | raise WinError()
167 |
168 | DEVPKEY_PciDevice_InterruptSupport = DEVPROPKEY(
169 | GUID("3ab22e31-8264-4b4e-9af5-a8d2d8e33e62"), 14
170 | )
171 | DEVPKEY_PciDevice_InterruptMessageMaximum = DEVPROPKEY(
172 | GUID("3ab22e31-8264-4b4e-9af5-a8d2d8e33e62"), 15
173 | )
174 |
175 | extra_info = {
176 | "InterruptSupport": get_device_property(
177 | dev_info_handle,
178 | dev_info_data,
179 | DEVPKEY_PciDevice_InterruptSupport,
180 | ),
181 | "MaximumMessageNumberLimit": get_device_property(
182 | dev_info_handle, dev_info_data, DEVPKEY_PciDevice_InterruptMessageMaximum
183 | ),
184 | }
185 |
186 | SetupDiDestroyDeviceInfoList(dev_info_handle)
187 |
188 | return extra_info
189 |
190 |
191 | def get_system_info():
192 | devices: list[Device] = []
193 |
194 | with OpenKeyEx(
195 | HKEY_LOCAL_MACHINE, BASE_PATH, 0, KEY_READ | KEY_WOW64_64KEY
196 | ) as base_key:
197 | for i in range(QueryInfoKey(base_key)[0]):
198 | partial_device_id = EnumKey(base_key, i)
199 | partial_device_path = f"{BASE_PATH}\\{partial_device_id}"
200 |
201 | with OpenKeyEx(
202 | HKEY_LOCAL_MACHINE, partial_device_path, 0, KEY_READ | KEY_WOW64_64KEY
203 | ) as partial_device_key:
204 | if QueryInfoKey(partial_device_key)[0] > 0:
205 | second_device_id = EnumKey(partial_device_key, 0)
206 | device_path = f"{partial_device_path}\\{second_device_id}"
207 | instance_id = f"PCI\\{partial_device_id}\\{second_device_id}"
208 |
209 | with OpenKeyEx(
210 | HKEY_LOCAL_MACHINE, device_path, 0, KEY_READ | KEY_WOW64_64KEY
211 | ) as device_key:
212 | config_flags = QueryValueEx(device_key, "ConfigFlags")[0]
213 | device_desc = QueryValueEx(device_key, "DeviceDesc")[0]
214 |
215 | if (config_flags & (CONFIGFLAG_DISABLED | CONFIGFLAG_REMOVED)) == 0:
216 | try:
217 | with OpenKeyEx(
218 | HKEY_LOCAL_MACHINE,
219 | device_path
220 | + r"\Device Parameters\Interrupt Management\Affinity Policy",
221 | 0,
222 | KEY_READ | KEY_WOW64_64KEY,
223 | ) as affinity_key:
224 | try:
225 | device_priority = QueryValueEx(
226 | affinity_key, "DevicePriority"
227 | )[0]
228 | except FileNotFoundError:
229 | device_priority = None
230 |
231 | try:
232 | device_policy = QueryValueEx(
233 | affinity_key, "DevicePolicy"
234 | )[0]
235 | except FileNotFoundError:
236 | device_policy = None
237 |
238 | try:
239 | assignment_set_override = int.from_bytes(
240 | QueryValueEx(
241 | affinity_key, "AssignmentSetOverride"
242 | )[0],
243 | "little",
244 | )
245 | except FileNotFoundError:
246 | assignment_set_override = None
247 | except FileNotFoundError:
248 | device_priority = None
249 | device_policy = None
250 | assignment_set_override = None
251 |
252 | try:
253 | with OpenKeyEx(
254 | HKEY_LOCAL_MACHINE,
255 | device_path
256 | + r"\Device Parameters\Interrupt Management\MessageSignaledInterruptProperties",
257 | 0,
258 | KEY_READ | KEY_WOW64_64KEY,
259 | ) as msi_key:
260 | try:
261 | message_number_limit = QueryValueEx(
262 | msi_key, "MessageNumberLimit"
263 | )[0]
264 | except FileNotFoundError:
265 | message_number_limit = None
266 |
267 | try:
268 | msi_supported = QueryValueEx(
269 | msi_key, "MSISupported"
270 | )[0]
271 | except FileNotFoundError:
272 | msi_supported = None
273 | except FileNotFoundError:
274 | message_number_limit = None
275 | msi_supported = None
276 |
277 | extra_info = get_additional_info(instance_id)
278 |
279 | devices.append(
280 | {
281 | "DeviceId": partial_device_id,
282 | "Path": device_path,
283 | "DeviceName": device_desc.split(";")[1],
284 | "DevicePriority": device_priority,
285 | "DevicePolicy": device_policy,
286 | "AssignmentSetOverride": assignment_set_override,
287 | "MessageNumberLimit": message_number_limit,
288 | "MSISupported": msi_supported,
289 | "InterruptSupport": extra_info.get("InterruptSupport"),
290 | "MaximumMessageNumberLimit": extra_info.get(
291 | "MaximumMessageNumberLimit"
292 | ),
293 | }
294 | )
295 |
296 | return {
297 | "cpu": {"cpus": cpu_count()},
298 | "devices": devices,
299 | }
300 |
--------------------------------------------------------------------------------
/py/POWER_SETTINGS/__init__.py:
--------------------------------------------------------------------------------
1 | from ctypes import (
2 | POINTER,
3 | WinError,
4 | byref,
5 | c_long,
6 | c_ulong,
7 | c_wchar_p,
8 | create_unicode_buffer,
9 | sizeof,
10 | windll,
11 | )
12 | from ctypes.wintypes import DWORD, HKEY, HLOCAL
13 | from typing import Final, TypedDict, cast
14 |
15 | from py.utils import GUID
16 |
17 |
18 | class Subgroup(TypedDict):
19 | guid: str
20 | name: str | None
21 |
22 |
23 | class Option(TypedDict):
24 | index: int
25 | name: str
26 | description: str
27 |
28 |
29 | class Range(TypedDict):
30 | min: int
31 | max: int
32 | increment: int
33 | unit: str
34 |
35 |
36 | class Setting(TypedDict):
37 | guid: str
38 | name: str
39 | description: str
40 | options: list[Option] | None
41 | range: Range | None
42 | subgroup: Subgroup
43 | ac: int
44 | dc: int
45 |
46 |
47 | class PowerScheme(TypedDict):
48 | guid: str
49 | name: str
50 | settings: list[Setting]
51 |
52 |
53 | class POWER_DATA_ACCESSOR(c_long):
54 | ACCESS_SCHEME = 16
55 | ACCESS_SUBGROUP = 17
56 | ACCESS_INDIVIDUAL_SETTING = 18
57 |
58 |
59 | ERROR_FILE_NOT_FOUND: Final = 2
60 | ERROR_NO_MORE_ITEMS: Final = 259
61 |
62 | powrprof = windll.powrprof
63 |
64 | PowerGetActiveScheme = powrprof.PowerGetActiveScheme
65 | PowerGetActiveScheme.argtypes = [HKEY, POINTER(POINTER(GUID))]
66 | PowerGetActiveScheme.restype = DWORD
67 |
68 | PowerEnumerate = powrprof.PowerEnumerate
69 | PowerEnumerate.argtypes = [
70 | HKEY,
71 | POINTER(GUID),
72 | POINTER(GUID),
73 | POWER_DATA_ACCESSOR,
74 | c_ulong,
75 | POINTER(GUID),
76 | POINTER(DWORD),
77 | ]
78 | PowerEnumerate.restype = DWORD
79 |
80 | PowerReadFriendlyName = powrprof.PowerReadFriendlyName
81 | PowerReadFriendlyName.argtypes = [
82 | HKEY,
83 | POINTER(GUID),
84 | POINTER(GUID),
85 | POINTER(GUID),
86 | c_wchar_p,
87 | POINTER(DWORD),
88 | ]
89 | PowerReadFriendlyName.restype = DWORD
90 |
91 | PowerReadACValueIndex = powrprof.PowerReadACValueIndex
92 | PowerReadACValueIndex.argtypes = [
93 | HKEY,
94 | POINTER(GUID),
95 | POINTER(GUID),
96 | POINTER(GUID),
97 | POINTER(DWORD),
98 | ]
99 | PowerReadACValueIndex.restype = DWORD
100 |
101 | PowerReadDCValueIndex = powrprof.PowerReadDCValueIndex
102 | PowerReadDCValueIndex.argtypes = [
103 | HKEY,
104 | POINTER(GUID),
105 | POINTER(GUID),
106 | POINTER(GUID),
107 | POINTER(DWORD),
108 | ]
109 | PowerReadDCValueIndex.restype = DWORD
110 |
111 | PowerReadValueMin = powrprof.PowerReadValueMin
112 | PowerReadValueMin.argtypes = [HKEY, POINTER(GUID), POINTER(GUID), POINTER(DWORD)]
113 | PowerReadValueMin.restype = DWORD
114 |
115 | PowerReadValueMax = powrprof.PowerReadValueMax
116 | PowerReadValueMax.argtypes = [HKEY, POINTER(GUID), POINTER(GUID), POINTER(DWORD)]
117 | PowerReadValueMax.restype = DWORD
118 |
119 | PowerReadValueIncrement = powrprof.PowerReadValueIncrement
120 | PowerReadValueIncrement.argtypes = [HKEY, POINTER(GUID), POINTER(GUID), POINTER(DWORD)]
121 | PowerReadValueIncrement.restype = DWORD
122 |
123 | PowerReadValueUnitsSpecifier = powrprof.PowerReadValueUnitsSpecifier
124 | PowerReadValueUnitsSpecifier.argtypes = [
125 | HKEY,
126 | POINTER(GUID),
127 | POINTER(GUID),
128 | c_wchar_p,
129 | POINTER(DWORD),
130 | ]
131 | PowerReadValueUnitsSpecifier.restype = DWORD
132 |
133 | PowerReadPossibleFriendlyName = powrprof.PowerReadPossibleFriendlyName
134 | PowerReadPossibleFriendlyName.argtypes = [
135 | HKEY,
136 | POINTER(GUID),
137 | POINTER(GUID),
138 | c_ulong,
139 | c_wchar_p,
140 | POINTER(DWORD),
141 | ]
142 | PowerReadPossibleFriendlyName.restype = DWORD
143 |
144 | PowerReadDescription = powrprof.PowerReadDescription
145 | PowerReadDescription.argtypes = [
146 | HKEY,
147 | POINTER(GUID),
148 | POINTER(GUID),
149 | POINTER(GUID),
150 | c_wchar_p,
151 | POINTER(DWORD),
152 | ]
153 | PowerReadDescription.restype = DWORD
154 |
155 | PowerReadPossibleDescription = powrprof.PowerReadPossibleDescription
156 | PowerReadPossibleDescription.argtypes = [
157 | HKEY,
158 | POINTER(GUID),
159 | POINTER(GUID),
160 | c_ulong,
161 | c_wchar_p,
162 | POINTER(DWORD),
163 | ]
164 | PowerReadPossibleDescription.restype = DWORD
165 |
166 | PowerWriteACValueIndex = powrprof.PowerWriteACValueIndex
167 | PowerWriteACValueIndex.argtypes = [
168 | HKEY,
169 | POINTER(GUID),
170 | POINTER(GUID),
171 | POINTER(GUID),
172 | DWORD,
173 | ]
174 | PowerWriteACValueIndex.restype = DWORD
175 |
176 | PowerWriteDCValueIndex = powrprof.PowerWriteDCValueIndex
177 | PowerWriteDCValueIndex.argtypes = [
178 | HKEY,
179 | POINTER(GUID),
180 | POINTER(GUID),
181 | POINTER(GUID),
182 | DWORD,
183 | ]
184 | PowerWriteDCValueIndex.restype = DWORD
185 |
186 | PowerSetActiveScheme = powrprof.PowerSetActiveScheme
187 | PowerSetActiveScheme.argtypes = [HKEY, POINTER(GUID)]
188 | PowerSetActiveScheme.restype = DWORD
189 |
190 | LocalFree = windll.kernel32.LocalFree
191 | LocalFree.argtypes = [HLOCAL]
192 | LocalFree.restype = HLOCAL
193 |
194 |
195 | def set_active_scheme(scheme_guid_str: str):
196 | result = PowerSetActiveScheme(
197 | None,
198 | byref(GUID(scheme_guid_str)),
199 | )
200 | if result != 0:
201 | raise WinError(result)
202 |
203 |
204 | def write_value_index(
205 | scheme_guid_str: str,
206 | subgroup_guid_str: str,
207 | setting_guid_str: str,
208 | value_index: int,
209 | is_ac: bool,
210 | ):
211 | if is_ac:
212 | result = PowerWriteACValueIndex(
213 | None,
214 | byref(GUID(scheme_guid_str)),
215 | byref(GUID(subgroup_guid_str)),
216 | byref(GUID(setting_guid_str)),
217 | value_index,
218 | )
219 | if result != 0:
220 | raise WinError(result)
221 | else:
222 | result = PowerWriteDCValueIndex(
223 | None,
224 | byref(GUID(scheme_guid_str)),
225 | byref(GUID(subgroup_guid_str)),
226 | byref(GUID(setting_guid_str)),
227 | value_index,
228 | )
229 | if result != 0:
230 | raise WinError(result)
231 |
232 | set_active_scheme(scheme_guid_str)
233 |
234 |
235 | def get_friendly_name(
236 | scheme_guid: GUID | None = None,
237 | subgroup_guid: GUID | None = None,
238 | setting_guid: GUID | None = None,
239 | ):
240 | buffer_size = c_ulong()
241 | result = PowerReadFriendlyName(
242 | None,
243 | byref(scheme_guid) if scheme_guid else None,
244 | byref(subgroup_guid) if subgroup_guid else None,
245 | byref(setting_guid) if setting_guid else None,
246 | None,
247 | byref(buffer_size),
248 | )
249 | if result == ERROR_FILE_NOT_FOUND:
250 | return ""
251 | if result != 0:
252 | raise WinError(result)
253 |
254 | buffer = create_unicode_buffer(buffer_size.value)
255 | result = PowerReadFriendlyName(
256 | None,
257 | byref(scheme_guid) if scheme_guid else None,
258 | byref(subgroup_guid) if subgroup_guid else None,
259 | byref(setting_guid) if setting_guid else None,
260 | buffer,
261 | byref(buffer_size),
262 | )
263 | if result != 0:
264 | raise WinError(result)
265 |
266 | return cast(str, buffer.value)
267 |
268 |
269 | def get_settings(
270 | scheme_guid: GUID,
271 | current_scheme_settings: list[Setting],
272 | current_subgroup: Subgroup,
273 | subgroup_guid: GUID | None = None,
274 | ):
275 | setting_index = 0
276 | while True:
277 | setting_guid = GUID()
278 |
279 | buffer_size = c_ulong(sizeof(setting_guid))
280 | result = PowerEnumerate(
281 | None,
282 | byref(scheme_guid),
283 | byref(subgroup_guid) if subgroup_guid else None,
284 | POWER_DATA_ACCESSOR.ACCESS_INDIVIDUAL_SETTING,
285 | setting_index,
286 | byref(setting_guid),
287 | byref(buffer_size),
288 | )
289 | setting_index += 1
290 |
291 | if result == ERROR_NO_MORE_ITEMS:
292 | break
293 | if result != 0:
294 | raise WinError(result)
295 |
296 | buffer_size = c_ulong()
297 | result = PowerReadDescription(
298 | None,
299 | byref(scheme_guid),
300 | byref(subgroup_guid) if subgroup_guid else None,
301 | byref(setting_guid),
302 | None,
303 | byref(buffer_size),
304 | )
305 | if result != 0:
306 | raise WinError(result)
307 |
308 | description_buffer = create_unicode_buffer(buffer_size.value)
309 | result = PowerReadDescription(
310 | None,
311 | byref(scheme_guid),
312 | byref(subgroup_guid) if subgroup_guid else None,
313 | byref(setting_guid),
314 | description_buffer,
315 | byref(buffer_size),
316 | )
317 | if result != 0:
318 | raise WinError(result)
319 |
320 | ac_value_index = c_ulong()
321 | result = PowerReadACValueIndex(
322 | None,
323 | byref(scheme_guid),
324 | byref(subgroup_guid) if subgroup_guid else None,
325 | byref(setting_guid),
326 | byref(ac_value_index),
327 | )
328 | if result != 0:
329 | raise WinError(result)
330 |
331 | dc_value_index = c_ulong()
332 | result = PowerReadDCValueIndex(
333 | None,
334 | byref(scheme_guid),
335 | byref(subgroup_guid) if subgroup_guid else None,
336 | byref(setting_guid),
337 | byref(dc_value_index),
338 | )
339 | if result != 0:
340 | raise WinError(result)
341 |
342 | setting_range: Range | None = None
343 | setting_options: list[Option] | None = None
344 |
345 | min_value = c_ulong()
346 | result = PowerReadValueMin(
347 | None,
348 | byref(subgroup_guid) if subgroup_guid else None,
349 | byref(setting_guid),
350 | byref(min_value),
351 | )
352 | if result == 0:
353 | max_value = c_ulong()
354 | result = PowerReadValueMax(
355 | None,
356 | byref(subgroup_guid) if subgroup_guid else None,
357 | byref(setting_guid),
358 | byref(max_value),
359 | )
360 | if result != 0:
361 | raise WinError(result)
362 |
363 | increment_value = c_ulong()
364 | result = PowerReadValueIncrement(
365 | None,
366 | byref(subgroup_guid) if subgroup_guid else None,
367 | byref(setting_guid),
368 | byref(increment_value),
369 | )
370 | if result != 0:
371 | raise WinError(result)
372 |
373 | buffer_size = c_ulong()
374 | result = PowerReadValueUnitsSpecifier(
375 | None,
376 | byref(subgroup_guid) if subgroup_guid else None,
377 | byref(setting_guid),
378 | None,
379 | byref(buffer_size),
380 | )
381 | if result != 0:
382 | raise WinError(result)
383 |
384 | unit_buffer = create_unicode_buffer(buffer_size.value)
385 | result = PowerReadValueUnitsSpecifier(
386 | None,
387 | byref(subgroup_guid) if subgroup_guid else None,
388 | byref(setting_guid),
389 | unit_buffer,
390 | byref(buffer_size),
391 | )
392 | if result != 0:
393 | raise WinError(result)
394 |
395 | setting_range = {
396 | "min": min_value.value,
397 | "max": max_value.value,
398 | "increment": increment_value.value,
399 | "unit": unit_buffer.value,
400 | }
401 | else:
402 | setting_options = []
403 |
404 | option_index = 0
405 | while True:
406 | buffer_size = c_ulong()
407 | result = PowerReadPossibleFriendlyName(
408 | None,
409 | byref(subgroup_guid) if subgroup_guid else None,
410 | byref(setting_guid),
411 | option_index,
412 | None,
413 | byref(buffer_size),
414 | )
415 | if result == ERROR_FILE_NOT_FOUND:
416 | break
417 | if result != 0:
418 | raise WinError(result)
419 |
420 | option_buffer = create_unicode_buffer(buffer_size.value)
421 | result = PowerReadPossibleFriendlyName(
422 | None,
423 | byref(subgroup_guid) if subgroup_guid else None,
424 | byref(setting_guid),
425 | option_index,
426 | option_buffer,
427 | byref(buffer_size),
428 | )
429 | if result != 0:
430 | raise WinError(result)
431 |
432 | buffer_size = c_ulong()
433 | result = PowerReadPossibleDescription(
434 | None,
435 | byref(subgroup_guid) if subgroup_guid else None,
436 | byref(setting_guid),
437 | option_index,
438 | None,
439 | byref(buffer_size),
440 | )
441 | if result != 0:
442 | raise WinError(result)
443 |
444 | description_buffer = create_unicode_buffer(buffer_size.value)
445 | result = PowerReadPossibleDescription(
446 | None,
447 | byref(subgroup_guid) if subgroup_guid else None,
448 | byref(setting_guid),
449 | option_index,
450 | description_buffer,
451 | byref(buffer_size),
452 | )
453 | if result != 0:
454 | raise WinError(result)
455 |
456 | setting_options.append(
457 | {
458 | "index": option_index,
459 | "name": option_buffer.value,
460 | "description": description_buffer.value,
461 | }
462 | )
463 |
464 | option_index += 1
465 |
466 | if option_index == 0:
467 | setting_options = None
468 |
469 | current_setting: Setting = {
470 | "guid": str(setting_guid),
471 | "name": get_friendly_name(
472 | scheme_guid=scheme_guid,
473 | subgroup_guid=subgroup_guid,
474 | setting_guid=setting_guid,
475 | ),
476 | "description": description_buffer.value,
477 | "subgroup": current_subgroup,
478 | "ac": ac_value_index.value,
479 | "dc": dc_value_index.value,
480 | "options": setting_options,
481 | "range": setting_range,
482 | }
483 |
484 | current_scheme_settings.append(current_setting)
485 |
486 |
487 | def get_power_settings():
488 | power_schemes: list[PowerScheme] = []
489 |
490 | scheme_index = 0
491 | while True:
492 | scheme_guid = GUID()
493 |
494 | buffer_size = c_ulong(sizeof(scheme_guid))
495 | result = PowerEnumerate(
496 | None,
497 | None,
498 | None,
499 | POWER_DATA_ACCESSOR.ACCESS_SCHEME,
500 | scheme_index,
501 | byref(scheme_guid),
502 | byref(buffer_size),
503 | )
504 | scheme_index += 1
505 |
506 | if result == ERROR_NO_MORE_ITEMS:
507 | break
508 | if result != 0:
509 | raise WinError(result)
510 |
511 | current_scheme_settings: list[Setting] = []
512 |
513 | current_subgroup: Subgroup = {
514 | "guid": "fea3413e-7e05-4911-9a71-700331f1c294",
515 | "name": "NO_SUBGROUP_GUID",
516 | }
517 |
518 | get_settings(scheme_guid, current_scheme_settings, current_subgroup)
519 |
520 | subgroup_index = 0
521 | while True:
522 | subgroup_guid = GUID()
523 |
524 | buffer_size = c_ulong(sizeof(subgroup_guid))
525 | result = PowerEnumerate(
526 | None,
527 | byref(scheme_guid),
528 | None,
529 | POWER_DATA_ACCESSOR.ACCESS_SUBGROUP,
530 | subgroup_index,
531 | byref(subgroup_guid),
532 | byref(buffer_size),
533 | )
534 | subgroup_index += 1
535 |
536 | if result == ERROR_NO_MORE_ITEMS:
537 | break
538 | if result != 0:
539 | raise WinError(result)
540 |
541 | current_subgroup = {
542 | "guid": str(subgroup_guid),
543 | "name": get_friendly_name(
544 | scheme_guid=scheme_guid, subgroup_guid=subgroup_guid
545 | ),
546 | }
547 |
548 | get_settings(
549 | scheme_guid, current_scheme_settings, current_subgroup, subgroup_guid
550 | )
551 |
552 | power_schemes.append(
553 | {
554 | "guid": str(scheme_guid),
555 | "name": get_friendly_name(scheme_guid=scheme_guid),
556 | "settings": current_scheme_settings,
557 | }
558 | )
559 |
560 | active_scheme_guid_ptr = POINTER(GUID)()
561 | result = PowerGetActiveScheme(None, byref(active_scheme_guid_ptr))
562 | if result != 0:
563 | raise WinError(result)
564 |
565 | active_guid_str = str(active_scheme_guid_ptr.contents)
566 | LocalFree(active_scheme_guid_ptr)
567 |
568 | return {"activeSchemeGuid": active_guid_str, "powerSchemes": power_schemes}
569 |
--------------------------------------------------------------------------------
/py/DISPLAY_INFO/__init__.py:
--------------------------------------------------------------------------------
1 | from ctypes import (
2 | POINTER,
3 | Structure,
4 | Union,
5 | WinError,
6 | byref,
7 | c_float,
8 | c_uint,
9 | c_uint16,
10 | c_uint32,
11 | c_uint64,
12 | sizeof,
13 | windll,
14 | )
15 | from ctypes.wintypes import BOOL, LONG, POINTL, RECTL, ULONG, WCHAR
16 | from typing import Final, TypedDict
17 |
18 |
19 | class LUID(Structure):
20 | _fields_ = [("LowPart", ULONG), ("HighPart", LONG)]
21 |
22 |
23 | class DUMMYSTRUCTNAME_PATH_SOURCE_INFO(Structure):
24 | _fields_ = [
25 | ("cloneGroupId", c_uint32, 16),
26 | ("sourceModeInfoIdx", c_uint32, 16),
27 | ]
28 |
29 |
30 | class DUMMYUNIONNAME_PATH_SOURCE_INFO(Union):
31 | _fields_ = [
32 | ("modeInfoIdx", c_uint32),
33 | ("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME_PATH_SOURCE_INFO),
34 | ]
35 |
36 |
37 | class DISPLAYCONFIG_PATH_SOURCE_INFO(Structure):
38 | _fields_ = [
39 | ("adapterId", LUID),
40 | ("id", c_uint32),
41 | ("DUMMYUNIONNAME", DUMMYUNIONNAME_PATH_SOURCE_INFO),
42 | ("statusFlags", c_uint32),
43 | ]
44 |
45 |
46 | class DUMMYSTRUCTNAME_PATH_TARGET_INFO(Structure):
47 | _fields_ = [
48 | ("desktopModeInfoIdx", c_uint32, 16),
49 | ("targetModeInfoIdx", c_uint32, 16),
50 | ]
51 |
52 |
53 | class DUMMYUNIONNAME_PATH_TARGET_INFO(Union):
54 | _fields_ = [
55 | ("modeInfoIdx", c_uint32),
56 | ("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME_PATH_TARGET_INFO),
57 | ]
58 |
59 |
60 | class DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY(c_uint32):
61 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1
62 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0
63 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO = 1
64 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO = 2
65 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO = 3
66 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI = 4
67 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5
68 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS = 6
69 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN = 8
70 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI = 9
71 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL = 10
72 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED = 11
73 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL = 12
74 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED = 13
75 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE = 14
76 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST = 15
77 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_WIRED = 16
78 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_VIRTUAL = 17
79 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_USB_TUNNEL = 18
80 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = 0x80000000
81 | DISPLAYCONFIG_OUTPUT_TECHNOLOGY_FORCE_UINT32 = 0xFFFFFFFF
82 |
83 |
84 | class DISPLAYCONFIG_ROTATION(c_uint32):
85 | DISPLAYCONFIG_ROTATION_IDENTITY = 1
86 | DISPLAYCONFIG_ROTATION_ROTATE90 = 2
87 | DISPLAYCONFIG_ROTATION_ROTATE180 = 3
88 | DISPLAYCONFIG_ROTATION_ROTATE270 = 4
89 | DISPLAYCONFIG_ROTATION_FORCE_UINT32 = 0xFFFFFFFF
90 |
91 |
92 | class DISPLAYCONFIG_SCALING(c_uint32):
93 | DISPLAYCONFIG_SCALING_IDENTITY = 1
94 | DISPLAYCONFIG_SCALING_CENTERED = 2
95 | DISPLAYCONFIG_SCALING_STRETCHED = 3
96 | DISPLAYCONFIG_SCALING_ASPECTRATIOCENTEREDMAX = 4
97 | DISPLAYCONFIG_SCALING_CUSTOM = 5
98 | DISPLAYCONFIG_SCALING_PREFERRED = 128
99 | DISPLAYCONFIG_SCALING_FORCE_UINT32 = 0xFFFFFFFF
100 |
101 |
102 | class DISPLAYCONFIG_RATIONAL(Structure):
103 | _fields_ = [("Numerator", c_uint32), ("Denominator", c_uint32)]
104 |
105 |
106 | class DISPLAYCONFIG_SCANLINE_ORDERING(c_uint32):
107 | DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED = 0
108 | DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE = 1
109 | DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED = 2
110 | DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST = 3
111 | DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST = 3
112 | DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32 = 0xFFFFFFFF
113 |
114 |
115 | class DISPLAYCONFIG_PATH_TARGET_INFO(Structure):
116 | _fields_ = [
117 | ("adapterId", LUID),
118 | ("id", c_uint32),
119 | ("DUMMYUNIONNAME", DUMMYUNIONNAME_PATH_TARGET_INFO),
120 | ("outputTechnology", DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY),
121 | ("rotation", DISPLAYCONFIG_ROTATION),
122 | ("scaling", DISPLAYCONFIG_SCALING),
123 | ("refreshRate", DISPLAYCONFIG_RATIONAL),
124 | ("scanLineOrdering", DISPLAYCONFIG_SCANLINE_ORDERING),
125 | ("targetAvailable", BOOL),
126 | ("statusFlags", c_uint32),
127 | ]
128 |
129 |
130 | class DISPLAYCONFIG_PATH_INFO(Structure):
131 | _fields_ = [
132 | ("sourceInfo", DISPLAYCONFIG_PATH_SOURCE_INFO),
133 | ("targetInfo", DISPLAYCONFIG_PATH_TARGET_INFO),
134 | ("flags", c_uint32),
135 | ]
136 |
137 |
138 | class DISPLAYCONFIG_MODE_INFO_TYPE(c_uint32):
139 | DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1
140 | DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2
141 | DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3
142 | DISPLAYCONFIG_MODE_INFO_TYPE_FORCE_UINT32 = 0xFFFFFFFF
143 |
144 |
145 | class DISPLAYCONFIG_DESKTOP_IMAGE_INFO(Structure):
146 | _fields_ = [
147 | ("PathSourceSize", POINTL),
148 | ("DesktopImageRegion", RECTL),
149 | ("DesktopImageClip", RECTL),
150 | ]
151 |
152 |
153 | class DISPLAYCONFIG_2DREGION(Structure):
154 | _fields_ = [("cx", c_uint32), ("cy", c_uint32)]
155 |
156 |
157 | class AdditionalSignalInfo(Structure):
158 | _fields_ = [
159 | ("videoStandard", c_uint32, 16),
160 | ("vSyncFreqDivider", c_uint32, 6),
161 | ("reserved", c_uint32, 10),
162 | ]
163 |
164 |
165 | class DUMMYUNIONNAME_VIDEO_SIGNAL_INFO(Union):
166 | _fields_ = [
167 | ("AdditionalSignalInfo", AdditionalSignalInfo),
168 | ("videoStandard", c_uint32),
169 | ]
170 |
171 |
172 | class DISPLAYCONFIG_VIDEO_SIGNAL_INFO(Structure):
173 | _fields_ = [
174 | ("pixelRate", c_uint64),
175 | ("hSyncFreq", DISPLAYCONFIG_RATIONAL),
176 | ("vSyncFreq", DISPLAYCONFIG_RATIONAL),
177 | ("activeSize", DISPLAYCONFIG_2DREGION),
178 | ("totalSize", DISPLAYCONFIG_2DREGION),
179 | ("DUMMYUNIONNAME", DUMMYUNIONNAME_VIDEO_SIGNAL_INFO),
180 | ("scanLineOrdering", DISPLAYCONFIG_SCANLINE_ORDERING),
181 | ]
182 |
183 |
184 | class DISPLAYCONFIG_TARGET_MODE(Structure):
185 | _fields_ = [("targetVideoSignalInfo", DISPLAYCONFIG_VIDEO_SIGNAL_INFO)]
186 |
187 |
188 | class DISPLAYCONFIG_PIXELFORMAT(c_uint32):
189 | DISPLAYCONFIG_PIXELFORMAT_8BPP = 1
190 | DISPLAYCONFIG_PIXELFORMAT_16BPP = 2
191 | DISPLAYCONFIG_PIXELFORMAT_24BPP = 3
192 | DISPLAYCONFIG_PIXELFORMAT_32BPP = 4
193 | DISPLAYCONFIG_PIXELFORMAT_NONGDI = 5
194 | DISPLAYCONFIG_PIXELFORMAT_FORCE_UINT32 = 0xFFFFFFFF
195 |
196 |
197 | class DISPLAYCONFIG_SOURCE_MODE(Structure):
198 | _fields_ = [
199 | ("width", c_uint32),
200 | ("height", c_uint32),
201 | ("pixelFormat", DISPLAYCONFIG_PIXELFORMAT),
202 | ("position", POINTL),
203 | ]
204 |
205 |
206 | class DUMMYUNIONNAME_MODE_INFO(Union):
207 | _fields_ = [
208 | ("targetMode", DISPLAYCONFIG_TARGET_MODE),
209 | ("sourceMode", DISPLAYCONFIG_SOURCE_MODE),
210 | ("desktopImageInfo", DISPLAYCONFIG_DESKTOP_IMAGE_INFO),
211 | ]
212 |
213 |
214 | class DISPLAYCONFIG_MODE_INFO(Structure):
215 | _fields_ = [
216 | ("infoType", DISPLAYCONFIG_MODE_INFO_TYPE),
217 | ("id", c_uint32),
218 | ("adapterId", LUID),
219 | ("DUMMYUNIONNAME", DUMMYUNIONNAME_MODE_INFO),
220 | ]
221 |
222 |
223 | class DISPLAYCONFIG_DEVICE_INFO_TYPE(c_uint32):
224 | DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1
225 | DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2
226 | DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3
227 | DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4
228 | DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5
229 | DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6
230 | DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7
231 | DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8
232 | DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9
233 | DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10
234 | DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11
235 | DISPLAYCONFIG_DEVICE_INFO_GET_MONITOR_SPECIALIZATION = 12
236 | DISPLAYCONFIG_DEVICE_INFO_SET_MONITOR_SPECIALIZATION = 13
237 | DISPLAYCONFIG_DEVICE_INFO_SET_RESERVED1 = 14
238 | DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2 = 15
239 | DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE = 16
240 | DISPLAYCONFIG_DEVICE_INFO_SET_WCG_STATE = 17
241 | DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF
242 |
243 |
244 | class DISPLAYCONFIG_DEVICE_INFO_HEADER(Structure):
245 | _fields_ = [
246 | ("type", DISPLAYCONFIG_DEVICE_INFO_TYPE),
247 | ("size", c_uint32),
248 | ("adapterId", LUID),
249 | ("id", c_uint32),
250 | ]
251 |
252 |
253 | class DUMMYSTRUCTNAME_TARGET_DEVICE_NAME_FLAGS(Structure):
254 | _fields_ = [
255 | ("friendlyNameFromEdid", c_uint32, 1),
256 | ("friendlyNameForced", c_uint32, 1),
257 | ("edidIdsValid", c_uint32, 1),
258 | ("reserved", c_uint32, 29),
259 | ]
260 |
261 |
262 | class DUMMYUNIONNAME_TARGET_DEVICE_NAME_FLAGS(Union):
263 | _fields_ = [
264 | ("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME_TARGET_DEVICE_NAME_FLAGS),
265 | ("value", c_uint32),
266 | ]
267 |
268 |
269 | class DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS(Structure):
270 | _fields_ = [("DUMMYUNIONNAME", DUMMYUNIONNAME_TARGET_DEVICE_NAME_FLAGS)]
271 |
272 |
273 | class DISPLAYCONFIG_TARGET_DEVICE_NAME(Structure):
274 | _fields_ = [
275 | ("header", DISPLAYCONFIG_DEVICE_INFO_HEADER),
276 | ("flags", DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS),
277 | ("outputTechnology", DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY),
278 | ("edidManufactureId", c_uint16),
279 | ("edidProductCodeId", c_uint16),
280 | ("connectorInstance", c_uint32),
281 | ("monitorFriendlyDeviceName", WCHAR * 64),
282 | ("monitorDevicePath", WCHAR * 128),
283 | ]
284 |
285 |
286 | class DISPLAYCONFIG_ADAPTER_NAME(Structure):
287 | _fields_ = [
288 | ("header", DISPLAYCONFIG_DEVICE_INFO_HEADER),
289 | ("adapterDevicePath", WCHAR * 128),
290 | ]
291 |
292 |
293 | class DISPLAYCONFIG_SOURCE_DEVICE_NAME(Structure):
294 | _fields_ = [
295 | ("header", DISPLAYCONFIG_DEVICE_INFO_HEADER),
296 | ("viewGdiDeviceName", WCHAR * 32),
297 | ]
298 |
299 |
300 | class D3DKMT_HANDLE(c_uint):
301 | pass
302 |
303 |
304 | class D3DKMT_OPENADAPTERFROMLUID(Structure):
305 | _fields_ = [("AdapterLuid", LUID), ("hAdapter", D3DKMT_HANDLE)]
306 |
307 |
308 | class D3DKMT_CLOSEADAPTER(Structure):
309 | _fields_ = [("hAdapter", D3DKMT_HANDLE)]
310 |
311 |
312 | class D3DDDI_VIDEO_PRESENT_SOURCE_ID(c_uint):
313 | pass
314 |
315 |
316 | class DUMMYSTRUCTNAME_MULTIPLANE_OVERLAY_CAPS(Structure):
317 | _fields_ = [
318 | ("Rotation", c_uint, 1),
319 | ("RotationWithoutIndependentFlip", c_uint, 1),
320 | ("VerticalFlip", c_uint, 1),
321 | ("HorizontalFlip", c_uint, 1),
322 | ("StretchRGB", c_uint, 1),
323 | ("StretchYUV", c_uint, 1),
324 | ("BilinearFilter", c_uint, 1),
325 | ("HighFilter", c_uint, 1),
326 | ("Shared", c_uint, 1),
327 | ("Immediate", c_uint, 1),
328 | ("Plane0ForVirtualModeOnly", c_uint, 1),
329 | ("Version3DDISupport", c_uint, 1),
330 | ("Reserved", c_uint, 20),
331 | ]
332 |
333 |
334 | class DUMMYUNIONNAME_MULTIPLANE_OVERLAY_CAPS(Union):
335 | _fields_ = [
336 | ("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME_MULTIPLANE_OVERLAY_CAPS),
337 | ("Value", c_uint),
338 | ]
339 |
340 |
341 | class D3DKMT_MULTIPLANE_OVERLAY_CAPS(Structure):
342 | _fields_ = [("DUMMYUNIONNAME", DUMMYUNIONNAME_MULTIPLANE_OVERLAY_CAPS)]
343 |
344 |
345 | class D3DKMT_GET_MULTIPLANE_OVERLAY_CAPS(Structure):
346 | _fields_ = [
347 | ("hAdapter", D3DKMT_HANDLE),
348 | ("VidPnSourceId", D3DDDI_VIDEO_PRESENT_SOURCE_ID),
349 | ("MaxPlanes", c_uint),
350 | ("MaxRGBPlanes", c_uint),
351 | ("MaxYUVPlanes", c_uint),
352 | ("OverlayCaps", D3DKMT_MULTIPLANE_OVERLAY_CAPS),
353 | ("MaxStretchFactor", c_float),
354 | ("MaxShrinkFactor", c_float),
355 | ]
356 |
357 |
358 | class DISPLAYCONFIG_TOPOLOGY_ID(c_uint32):
359 | DISPLAYCONFIG_TOPOLOGY_INTERNAL = 0x1
360 | DISPLAYCONFIG_TOPOLOGY_CLONE = 0x2
361 | DISPLAYCONFIG_TOPOLOGY_EXTEND = 0x4
362 | DISPLAYCONFIG_TOPOLOGY_EXTERNAL = 0x8
363 | DISPLAYCONFIG_TOPOLOGY_FORCE_UINT32 = 0xFFFFFFFF
364 |
365 |
366 | class MPOCaps(TypedDict):
367 | Rotation: int
368 | RotationWithoutIndependentFlip: int
369 | VerticalFlip: int
370 | HorizontalFlip: int
371 | StretchRGB: int
372 | StretchYUV: int
373 | BilinearFilter: int
374 | HighFilter: int
375 | Shared: int
376 | Immediate: int
377 | Plane0ForVirtualModeOnly: int
378 | Version3DDISupport: int
379 |
380 |
381 | class MPOCapabilities(TypedDict):
382 | MaxPlanes: int
383 | MaxRGBPlanes: int
384 | MaxYUVPlanes: int
385 | MaxStretchFactor: float
386 | MaxShrinkFactor: float
387 | caps: MPOCaps
388 |
389 |
390 | class Display(TypedDict):
391 | monitorFriendlyDeviceName: str
392 | monitorDevicePath: str
393 | adapterDevicePath: str
394 | outputTechnology: int
395 | rotation: int
396 | refreshRate: float
397 | horizontalFrequency: float
398 | resolution: str
399 | pixelRate: float
400 | scaling: int
401 | pixelFormat: int
402 | mpo: MPOCapabilities
403 |
404 |
405 | QDC_ONLY_ACTIVE_PATHS: Final = 0x2
406 | ERROR_INSUFFICIENT_BUFFER: Final = 0x7A
407 |
408 | user32 = windll.user32
409 |
410 | GetDisplayConfigBufferSizes = user32.GetDisplayConfigBufferSizes
411 | GetDisplayConfigBufferSizes.argtypes = [c_uint32, POINTER(c_uint32), POINTER(c_uint32)]
412 | GetDisplayConfigBufferSizes.restype = LONG
413 |
414 | QueryDisplayConfig = user32.QueryDisplayConfig
415 | QueryDisplayConfig.argtypes = [
416 | c_uint32,
417 | POINTER(c_uint32),
418 | POINTER(DISPLAYCONFIG_PATH_INFO),
419 | POINTER(c_uint32),
420 | POINTER(DISPLAYCONFIG_MODE_INFO),
421 | POINTER(DISPLAYCONFIG_TOPOLOGY_ID),
422 | ]
423 | QueryDisplayConfig.restype = LONG
424 |
425 | DisplayConfigGetDeviceInfo = user32.DisplayConfigGetDeviceInfo
426 | DisplayConfigGetDeviceInfo.argtypes = [POINTER(DISPLAYCONFIG_DEVICE_INFO_HEADER)]
427 | DisplayConfigGetDeviceInfo.restype = LONG
428 |
429 | gdi32 = windll.gdi32
430 |
431 | D3DKMTOpenAdapterFromLuid = gdi32.D3DKMTOpenAdapterFromLuid
432 | D3DKMTOpenAdapterFromLuid.argtypes = [POINTER(D3DKMT_OPENADAPTERFROMLUID)]
433 | D3DKMTOpenAdapterFromLuid.restype = LONG
434 |
435 | D3DKMTGetMultiPlaneOverlayCaps = gdi32.D3DKMTGetMultiPlaneOverlayCaps
436 | D3DKMTGetMultiPlaneOverlayCaps.argtypes = [POINTER(D3DKMT_GET_MULTIPLANE_OVERLAY_CAPS)]
437 | D3DKMTGetMultiPlaneOverlayCaps.restype = LONG
438 |
439 | D3DKMTCloseAdapter = gdi32.D3DKMTCloseAdapter
440 | D3DKMTCloseAdapter.argtypes = [POINTER(D3DKMT_CLOSEADAPTER)]
441 | D3DKMTCloseAdapter.restype = LONG
442 |
443 |
444 | def get_display_info():
445 | result = ERROR_INSUFFICIENT_BUFFER
446 |
447 | num_path_array_elements = c_uint32()
448 | num_mode_info_array_elements = c_uint32()
449 |
450 | # shut the linter up
451 | path_array = (DISPLAYCONFIG_PATH_INFO * num_path_array_elements.value)()
452 | mode_info_array = (DISPLAYCONFIG_MODE_INFO * num_mode_info_array_elements.value)()
453 |
454 | while result == ERROR_INSUFFICIENT_BUFFER:
455 | result = GetDisplayConfigBufferSizes(
456 | QDC_ONLY_ACTIVE_PATHS,
457 | byref(num_path_array_elements),
458 | byref(num_mode_info_array_elements),
459 | )
460 | if result != 0:
461 | raise WinError(result)
462 |
463 | path_array = (DISPLAYCONFIG_PATH_INFO * num_path_array_elements.value)()
464 | mode_info_array = (
465 | DISPLAYCONFIG_MODE_INFO * num_mode_info_array_elements.value
466 | )()
467 |
468 | result = QueryDisplayConfig(
469 | QDC_ONLY_ACTIVE_PATHS,
470 | byref(num_path_array_elements),
471 | path_array,
472 | byref(num_mode_info_array_elements),
473 | mode_info_array,
474 | None,
475 | )
476 |
477 | if result != 0:
478 | raise WinError(result)
479 |
480 | displays: list[Display] = []
481 |
482 | pixel_formats = {}
483 | horizontal_frequencies = {}
484 | pixel_rates = {}
485 | resolutions = {}
486 |
487 | for mode in mode_info_array:
488 | if (
489 | mode.infoType.value
490 | == DISPLAYCONFIG_MODE_INFO_TYPE.DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE
491 | ):
492 | pixel_formats[mode.id] = mode.DUMMYUNIONNAME.sourceMode.pixelFormat.value
493 | resolutions[mode.id] = (
494 | f"{mode.DUMMYUNIONNAME.sourceMode.width} x {mode.DUMMYUNIONNAME.sourceMode.height}"
495 | )
496 | elif (
497 | mode.infoType.value
498 | == DISPLAYCONFIG_MODE_INFO_TYPE.DISPLAYCONFIG_MODE_INFO_TYPE_TARGET
499 | ):
500 | horizontal_frequencies[mode.id] = (
501 | mode.DUMMYUNIONNAME.targetMode.targetVideoSignalInfo.hSyncFreq.Numerator
502 | / (
503 | mode.DUMMYUNIONNAME.targetMode.targetVideoSignalInfo.hSyncFreq.Denominator
504 | * 1000
505 | )
506 | )
507 | pixel_rates[mode.id] = (
508 | mode.DUMMYUNIONNAME.targetMode.targetVideoSignalInfo.pixelRate / 1000000
509 | )
510 |
511 | for path in path_array:
512 | target_name = DISPLAYCONFIG_TARGET_DEVICE_NAME()
513 | target_name.header.adapterId = path.targetInfo.adapterId
514 | target_name.header.id = path.targetInfo.id
515 | target_name.header.type = (
516 | DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME
517 | )
518 | target_name.header.size = sizeof(target_name)
519 | result = DisplayConfigGetDeviceInfo(byref(target_name.header))
520 | if result != 0:
521 | raise WinError(result)
522 |
523 | adapter_name = DISPLAYCONFIG_ADAPTER_NAME()
524 | adapter_name.header.adapterId = path.targetInfo.adapterId
525 | adapter_name.header.type = (
526 | DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME
527 | )
528 | adapter_name.header.size = sizeof(adapter_name)
529 | result = DisplayConfigGetDeviceInfo(byref(adapter_name.header))
530 | if result != 0:
531 | raise WinError(result)
532 |
533 | source_name = DISPLAYCONFIG_SOURCE_DEVICE_NAME()
534 | source_name.header.adapterId = path.sourceInfo.adapterId
535 | source_name.header.id = path.sourceInfo.id
536 | source_name.header.type = (
537 | DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME
538 | )
539 | source_name.header.size = sizeof(source_name)
540 | result = DisplayConfigGetDeviceInfo(byref(source_name.header))
541 | if result != 0:
542 | raise WinError(result)
543 |
544 | open_adapter = D3DKMT_OPENADAPTERFROMLUID()
545 | open_adapter.AdapterLuid = adapter_name.header.adapterId
546 | result = D3DKMTOpenAdapterFromLuid(byref(open_adapter))
547 | if result != 0:
548 | raise WinError(result)
549 |
550 | caps = D3DKMT_GET_MULTIPLANE_OVERLAY_CAPS()
551 | caps.hAdapter = open_adapter.hAdapter
552 | caps.VidPnSourceId = path.sourceInfo.id
553 | result = D3DKMTGetMultiPlaneOverlayCaps(byref(caps))
554 | if result != 0:
555 | raise WinError(result)
556 |
557 | displays.append(
558 | {
559 | "monitorFriendlyDeviceName": target_name.monitorFriendlyDeviceName,
560 | "monitorDevicePath": target_name.monitorDevicePath,
561 | "adapterDevicePath": adapter_name.adapterDevicePath,
562 | "outputTechnology": path.targetInfo.outputTechnology.value,
563 | "rotation": path.targetInfo.rotation.value,
564 | "refreshRate": path.targetInfo.refreshRate.Numerator
565 | / path.targetInfo.refreshRate.Denominator,
566 | "horizontalFrequency": horizontal_frequencies[path.targetInfo.id],
567 | "resolution": resolutions[path.sourceInfo.id],
568 | "pixelRate": pixel_rates[path.targetInfo.id],
569 | "scaling": path.targetInfo.scaling.value,
570 | "pixelFormat": pixel_formats[path.sourceInfo.id],
571 | "mpo": {
572 | "MaxPlanes": caps.MaxPlanes,
573 | "MaxRGBPlanes": caps.MaxRGBPlanes,
574 | "MaxYUVPlanes": caps.MaxYUVPlanes,
575 | "MaxStretchFactor": caps.MaxStretchFactor,
576 | "MaxShrinkFactor": caps.MaxShrinkFactor,
577 | "caps": {
578 | "Rotation": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Rotation,
579 | "RotationWithoutIndependentFlip": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.RotationWithoutIndependentFlip,
580 | "VerticalFlip": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.VerticalFlip,
581 | "HorizontalFlip": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.HorizontalFlip,
582 | "StretchRGB": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.StretchRGB,
583 | "StretchYUV": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.StretchYUV,
584 | "BilinearFilter": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.BilinearFilter,
585 | "HighFilter": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.HighFilter,
586 | "Shared": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Shared,
587 | "Immediate": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Immediate,
588 | "Plane0ForVirtualModeOnly": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Plane0ForVirtualModeOnly,
589 | "Version3DDISupport": caps.OverlayCaps.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Version3DDISupport,
590 | },
591 | },
592 | }
593 | )
594 |
595 | close_adapter = D3DKMT_CLOSEADAPTER()
596 | close_adapter.hAdapter = open_adapter.hAdapter
597 | result = D3DKMTCloseAdapter(byref(close_adapter))
598 | if result != 0:
599 | raise WinError(result)
600 |
601 | return {"displays": displays}
602 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 1
3 | requires-python = ">=3.13"
4 |
5 | [[package]]
6 | name = "altgraph"
7 | version = "0.17.4"
8 | source = { registry = "https://pypi.org/simple" }
9 | sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418 }
10 | wheels = [
11 | { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212 },
12 | ]
13 |
14 | [[package]]
15 | name = "bottle"
16 | version = "0.13.2"
17 | source = { registry = "https://pypi.org/simple" }
18 | sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/97839b95c2a2ea1ca91877a846988f90f4ca16ee42c0bb79e079171c0c06/bottle-0.13.2.tar.gz", hash = "sha256:e53803b9d298c7d343d00ba7d27b0059415f04b9f6f40b8d58b5bf914ba9d348", size = 98472 }
19 | wheels = [
20 | { url = "https://files.pythonhosted.org/packages/7e/0a/a5260c758ff813acc6967344339aa7ba15f815575f4d49141685c4345d39/bottle-0.13.2-py2.py3-none-any.whl", hash = "sha256:27569ab8d1332fbba3e400b3baab2227ab4efb4882ff147af05a7c00ed73409c", size = 104053 },
21 | ]
22 |
23 | [[package]]
24 | name = "cffi"
25 | version = "1.17.1"
26 | source = { registry = "https://pypi.org/simple" }
27 | dependencies = [
28 | { name = "pycparser" },
29 | ]
30 | sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 }
31 | wheels = [
32 | { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 },
33 | { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 },
34 | { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 },
35 | { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 },
36 | { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 },
37 | { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 },
38 | { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 },
39 | { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 },
40 | { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 },
41 | { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 },
42 | { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 },
43 | ]
44 |
45 | [[package]]
46 | name = "clr-loader"
47 | version = "0.2.7.post0"
48 | source = { registry = "https://pypi.org/simple" }
49 | dependencies = [
50 | { name = "cffi" },
51 | ]
52 | sdist = { url = "https://files.pythonhosted.org/packages/d5/b3/8ae917e458394e2cebdbf17bed0a8204f8d4ffc79a093a7b1141c7731d3c/clr_loader-0.2.7.post0.tar.gz", hash = "sha256:b7a8b3f8fbb1bcbbb6382d887e21d1742d4f10b5ea209e4ad95568fe97e1c7c6", size = 56701 }
53 | wheels = [
54 | { url = "https://files.pythonhosted.org/packages/9c/c0/06e64a54bced4e8b885c1e7ec03ee1869e52acf69e87da40f92391a214ad/clr_loader-0.2.7.post0-py3-none-any.whl", hash = "sha256:e0b9fcc107d48347a4311a28ffe3ae78c4968edb216ffb6564cb03f7ace0bb47", size = 50649 },
55 | ]
56 |
57 | [[package]]
58 | name = "cryptography"
59 | version = "44.0.2"
60 | source = { registry = "https://pypi.org/simple" }
61 | dependencies = [
62 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
63 | ]
64 | sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 }
65 | wheels = [
66 | { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 },
67 | { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 },
68 | { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 },
69 | { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 },
70 | { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 },
71 | { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 },
72 | { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 },
73 | { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 },
74 | { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 },
75 | { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 },
76 | { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 },
77 | { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 },
78 | { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 },
79 | { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 },
80 | { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 },
81 | { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 },
82 | { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 },
83 | { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 },
84 | { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 },
85 | { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 },
86 | { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 },
87 | { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 },
88 | { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 },
89 | { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
90 | ]
91 |
92 | [[package]]
93 | name = "macholib"
94 | version = "1.16.3"
95 | source = { registry = "https://pypi.org/simple" }
96 | dependencies = [
97 | { name = "altgraph" },
98 | ]
99 | sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309 }
100 | wheels = [
101 | { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094 },
102 | ]
103 |
104 | [[package]]
105 | name = "packaging"
106 | version = "24.2"
107 | source = { registry = "https://pypi.org/simple" }
108 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
109 | wheels = [
110 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
111 | ]
112 |
113 | [[package]]
114 | name = "pefile"
115 | version = "2023.2.7"
116 | source = { registry = "https://pypi.org/simple" }
117 | sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854 }
118 | wheels = [
119 | { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791 },
120 | ]
121 |
122 | [[package]]
123 | name = "proxy-tools"
124 | version = "0.1.0"
125 | source = { registry = "https://pypi.org/simple" }
126 | sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978 }
127 |
128 | [[package]]
129 | name = "pycparser"
130 | version = "2.22"
131 | source = { registry = "https://pypi.org/simple" }
132 | sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
133 | wheels = [
134 | { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
135 | ]
136 |
137 | [[package]]
138 | name = "pyinstaller"
139 | version = "6.12.0"
140 | source = { registry = "https://pypi.org/simple" }
141 | dependencies = [
142 | { name = "altgraph" },
143 | { name = "macholib", marker = "sys_platform == 'darwin'" },
144 | { name = "packaging" },
145 | { name = "pefile", marker = "sys_platform == 'win32'" },
146 | { name = "pyinstaller-hooks-contrib" },
147 | { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
148 | { name = "setuptools" },
149 | ]
150 | sdist = { url = "https://files.pythonhosted.org/packages/10/c0/001e86a13f9f6104613f198721c72d377fa1fc2a09550cfe1ac9a1d12406/pyinstaller-6.12.0.tar.gz", hash = "sha256:1834797be48ce1b26015af68bdeb3c61a6c7500136f04e0fc65e468115dec777", size = 4267132 }
151 | wheels = [
152 | { url = "https://files.pythonhosted.org/packages/b2/73/b897a3fda99a14130111abdb978d63da14cbc9932497b5e5064c5fe28187/pyinstaller-6.12.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:68f1e4cecf88a6272063977fa2a2c69ad37cf568e5901769d7206d0314c74f47", size = 997954 },
153 | { url = "https://files.pythonhosted.org/packages/4a/bc/0929ed6aca3c5ff3f20f8cfd4f2f7e90f18c9465440e0d151d56d8170851/pyinstaller-6.12.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fea76fc9b55ffa730fcf90beb897cce4399938460b0b6f40507fbebfc752c753", size = 714097 },
154 | { url = "https://files.pythonhosted.org/packages/e1/9a/422d5eb04132e4a4735ca9099a53511324ff7d387b80231fe8dbd67bf322/pyinstaller-6.12.0-py3-none-manylinux2014_i686.whl", hash = "sha256:dac8a27988dbc33cdc34f2046803258bc3f6829de24de52745a5daa22bdba0f1", size = 724471 },
155 | { url = "https://files.pythonhosted.org/packages/64/1c/5028ba2e09f5b57f6792e9d88e888725224f8f016a07666e48664f6a9fcf/pyinstaller-6.12.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:83c7f3bde9871b4a6aa71c66a96e8ba5c21668ce711ed97f510b9382d10aac6c", size = 722753 },
156 | { url = "https://files.pythonhosted.org/packages/6f/d9/e7742caf4c4dc07d13e355ad2c14c7844c9bb2e66dea4f3386b4644bd106/pyinstaller-6.12.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:a69818815c6e0711c727edc30680cb1f81c691b59de35db81a2d9e0ae26a9ef1", size = 720906 },
157 | { url = "https://files.pythonhosted.org/packages/80/2b/14404f2dc95d1ec94d08879c62a76d5f26a176fab99fb023c2c70d2ff500/pyinstaller-6.12.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a2abf5fde31a8b38b6df7939bcef8ac1d0c51e97e25317ce3555cd675259750f", size = 716959 },
158 | { url = "https://files.pythonhosted.org/packages/11/a6/5c3a233cf19aa6d4caacf62f7ee1c728486cc20b73f5817be17485d7b7ff/pyinstaller-6.12.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:8e92e9873a616547bbabbb5a3a9843d5f2ab40c3d8b26810acdf0fe257bee4cf", size = 719414 },
159 | { url = "https://files.pythonhosted.org/packages/24/57/069d35236806b281a3331ef00ff94e43f3b91e4b36350de8b40b4baf9fd3/pyinstaller-6.12.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:aefe502d55c9cf6aeaed7feba80b5f8491ce43f8f2b5fe2d9aadca3ee5a05bc4", size = 715903 },
160 | { url = "https://files.pythonhosted.org/packages/4d/5f/857de8798836f9d16a620bd0a7c8899bba05b5fda7b3b4432762f148a86d/pyinstaller-6.12.0-py3-none-win32.whl", hash = "sha256:138856a5a503bb69c066377e0a22671b0db063e9cc14d5cf5c798a53561200d3", size = 1290980 },
161 | { url = "https://files.pythonhosted.org/packages/99/6e/d7d76d4d15f6351f1f942256633b795eec3d6c691d985869df1bf319cd9d/pyinstaller-6.12.0-py3-none-win_amd64.whl", hash = "sha256:0e62d3906309248409f215b386f33afec845214e69cc0f296b93222b26a88f43", size = 1348786 },
162 | { url = "https://files.pythonhosted.org/packages/47/c2/298ad6a3aa2cacb55cbc1f845068dc1e4a6c966082ffa0e19c69084cbc42/pyinstaller-6.12.0-py3-none-win_arm64.whl", hash = "sha256:0c271896a3a168f4f91827145702543db9c5427f4c7372a6df8c75925a3ac18a", size = 1289617 },
163 | ]
164 |
165 | [[package]]
166 | name = "pyinstaller-hooks-contrib"
167 | version = "2025.2"
168 | source = { registry = "https://pypi.org/simple" }
169 | dependencies = [
170 | { name = "packaging" },
171 | { name = "setuptools" },
172 | ]
173 | sdist = { url = "https://files.pythonhosted.org/packages/99/14/6725808804b52cc2420a659c8935e511221837ad863e3bbc4269897d5b4d/pyinstaller_hooks_contrib-2025.2.tar.gz", hash = "sha256:ccdd41bc30290f725f3e48f4a39985d11855af81d614d167e3021e303acb9102", size = 150122 }
174 | wheels = [
175 | { url = "https://files.pythonhosted.org/packages/5e/14/aaadab954b4d78b2de9368eabda3327f8cdd05bce12712263e1100266bca/pyinstaller_hooks_contrib-2025.2-py3-none-any.whl", hash = "sha256:0b2bc7697075de5eb071ff13ef4a156d3beae6c19c7cbdcd70f37978d2013e30", size = 351020 },
176 | ]
177 |
178 | [[package]]
179 | name = "pyobjc-core"
180 | version = "11.0"
181 | source = { registry = "https://pypi.org/simple" }
182 | sdist = { url = "https://files.pythonhosted.org/packages/5c/94/a111239b98260869780a5767e5d74bfd3a8c13a40457f479c28dcd91f89d/pyobjc_core-11.0.tar.gz", hash = "sha256:63bced211cb8a8fb5c8ff46473603da30e51112861bd02c438fbbbc8578d9a70", size = 994931 }
183 | wheels = [
184 | { url = "https://files.pythonhosted.org/packages/72/16/0c468e73dbecb821e3da8819236fe832dfc53eb5f66a11775b055a7589ea/pyobjc_core-11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c338c1deb7ab2e9436d4175d1127da2eeed4a1b564b3d83b9f3ae4844ba97e86", size = 743900 },
185 | { url = "https://files.pythonhosted.org/packages/f3/88/cecec88fd51f62a6cd7775cc4fb6bfde16652f97df88d28c84fb77ca0c18/pyobjc_core-11.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b4e9dc4296110f251a4033ff3f40320b35873ea7f876bd29a1c9705bb5e08c59", size = 791905 },
186 | ]
187 |
188 | [[package]]
189 | name = "pyobjc-framework-cocoa"
190 | version = "11.0"
191 | source = { registry = "https://pypi.org/simple" }
192 | dependencies = [
193 | { name = "pyobjc-core" },
194 | ]
195 | sdist = { url = "https://files.pythonhosted.org/packages/c5/32/53809096ad5fc3e7a2c5ddea642590a5f2cb5b81d0ad6ea67fdb2263d9f9/pyobjc_framework_cocoa-11.0.tar.gz", hash = "sha256:00346a8cb81ad7b017b32ff7bf596000f9faa905807b1bd234644ebd47f692c5", size = 6173848 }
196 | wheels = [
197 | { url = "https://files.pythonhosted.org/packages/1d/a5/609281a7e89efefbef9db1d8fe66bc0458c3b4e74e2227c644f9c18926fa/pyobjc_framework_Cocoa-11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15b2bd977ed340074f930f1330f03d42912d5882b697d78bd06f8ebe263ef92e", size = 385889 },
198 | { url = "https://files.pythonhosted.org/packages/93/f6/2d5a863673ef7b85a3cba875c43e6c495fb1307427a6801001ae94bb5e54/pyobjc_framework_Cocoa-11.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5750001db544e67f2b66f02067d8f0da96bb2ef71732bde104f01b8628f9d7ea", size = 389831 },
199 | ]
200 |
201 | [[package]]
202 | name = "pyobjc-framework-quartz"
203 | version = "11.0"
204 | source = { registry = "https://pypi.org/simple" }
205 | dependencies = [
206 | { name = "pyobjc-core" },
207 | { name = "pyobjc-framework-cocoa" },
208 | ]
209 | sdist = { url = "https://files.pythonhosted.org/packages/a5/ad/f00f3f53387c23bbf4e0bb1410e11978cbf87c82fa6baff0ee86f74c5fb6/pyobjc_framework_quartz-11.0.tar.gz", hash = "sha256:3205bf7795fb9ae34747f701486b3db6dfac71924894d1f372977c4d70c3c619", size = 3952463 }
210 | wheels = [
211 | { url = "https://files.pythonhosted.org/packages/a6/9e/54c48fe8faab06ee5eb80796c8c17ec61fc313d84398540ee70abeaf7070/pyobjc_framework_Quartz-11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:973b4f9b8ab844574461a038bd5269f425a7368d6e677e3cc81fcc9b27b65498", size = 212478 },
212 | { url = "https://files.pythonhosted.org/packages/4a/28/456b54a59bfe11a91b7b4e94f8ffdcf174ffd1efa169f4283e5b3bc10194/pyobjc_framework_Quartz-11.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:66ab58d65348863b8707e63b2ec5cdc54569ee8189d1af90d52f29f5fdf6272c", size = 217973 },
213 | ]
214 |
215 | [[package]]
216 | name = "pyobjc-framework-security"
217 | version = "11.0"
218 | source = { registry = "https://pypi.org/simple" }
219 | dependencies = [
220 | { name = "pyobjc-core" },
221 | { name = "pyobjc-framework-cocoa" },
222 | ]
223 | sdist = { url = "https://files.pythonhosted.org/packages/c5/75/4b916bff8c650e387077a35916b7a7d331d5ff03bed7275099d96dcc6cd9/pyobjc_framework_security-11.0.tar.gz", hash = "sha256:ac078bb9cc6762d6f0f25f68325dcd7fe77acdd8c364bf4378868493f06a0758", size = 347059 }
224 | wheels = [
225 | { url = "https://files.pythonhosted.org/packages/ab/9f/79c1713be83d58199e5379e928c2c94bb3ca44d294de2a0a0edefc6b3ba8/pyobjc_framework_Security-11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dda83260c5638dd0470c01ca9d37eccedbce15d0642d9c28b357329e4145528f", size = 41530 },
226 | { url = "https://files.pythonhosted.org/packages/80/f2/d71306d4431b5492a1c178a44ae922caabc40b884b081aa428bb06f642e6/pyobjc_framework_Security-11.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:51dd6fb24235f4623d68a02bda4dabd85f48bce00f9b0b306016cf2c891392c4", size = 42057 },
227 | ]
228 |
229 | [[package]]
230 | name = "pyobjc-framework-webkit"
231 | version = "11.0"
232 | source = { registry = "https://pypi.org/simple" }
233 | dependencies = [
234 | { name = "pyobjc-core" },
235 | { name = "pyobjc-framework-cocoa" },
236 | ]
237 | sdist = { url = "https://files.pythonhosted.org/packages/79/4f/02a6270acf225c2a34339677e796002c77506238475059ae6e855358a40c/pyobjc_framework_webkit-11.0.tar.gz", hash = "sha256:fa6bedf9873786b3376a74ce2ea9dcd311f2a80f61e33dcbd931cc956aa29644", size = 767210 }
238 | wheels = [
239 | { url = "https://files.pythonhosted.org/packages/dc/8b/e880680429fbac494687626c1338758e70b5dfb75883d9cb78f66635f381/pyobjc_framework_WebKit-11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:22d09bb22c3c48d9243f300f8264a68ecc0bdfe09d25794ee86ab2239eae7da2", size = 44938 },
240 | { url = "https://files.pythonhosted.org/packages/ec/8f/f0ba035f682038264b1e05bde8fb538e8fa61267dc3ac22e3c2e3d3001bc/pyobjc_framework_WebKit-11.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6141a416f1eb33ded2c6685931d1b4d5f17c83814f2d17b7e2febff03c6f6bee", size = 45443 },
241 | ]
242 |
243 | [[package]]
244 | name = "pythonnet"
245 | version = "3.0.5"
246 | source = { registry = "https://pypi.org/simple" }
247 | dependencies = [
248 | { name = "clr-loader" },
249 | ]
250 | sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212 }
251 | wheels = [
252 | { url = "https://files.pythonhosted.org/packages/cd/f1/bfb6811df4745f92f14c47a29e50e89a36b1533130fcc56452d4660bd2d6/pythonnet-3.0.5-py3-none-any.whl", hash = "sha256:f6702d694d5d5b163c9f3f5cc34e0bed8d6857150237fae411fefb883a656d20", size = 297506 },
253 | ]
254 |
255 | [[package]]
256 | name = "pywebview"
257 | version = "5.4"
258 | source = { registry = "https://pypi.org/simple" }
259 | dependencies = [
260 | { name = "bottle" },
261 | { name = "proxy-tools" },
262 | { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
263 | { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
264 | { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
265 | { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" },
266 | { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" },
267 | { name = "pythonnet", marker = "sys_platform == 'win32'" },
268 | { name = "qtpy", marker = "sys_platform == 'openbsd6'" },
269 | { name = "typing-extensions" },
270 | ]
271 | sdist = { url = "https://files.pythonhosted.org/packages/7a/c7/645f3b0bb190bf58c5cea9cfbcaa337ce3b54531ad207d3748676eb8cdc9/pywebview-5.4.tar.gz", hash = "sha256:b5e2c6c7502aaf72a9ae6034daf83785f5fad874fac7fa82bf4fcf854f1f083a", size = 466398 }
272 | wheels = [
273 | { url = "https://files.pythonhosted.org/packages/4c/5a/7af3b9f8fb51d544b31f2f6c12635eac77d1e9a99f72dd12c9364062d59d/pywebview-5.4-py3-none-any.whl", hash = "sha256:0559c47db543556498dd38604a2a0479896c320f86c9b23499b8e580b58b699d", size = 475504 },
274 | ]
275 |
276 | [[package]]
277 | name = "pywin32-ctypes"
278 | version = "0.2.3"
279 | source = { registry = "https://pypi.org/simple" }
280 | sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 }
281 | wheels = [
282 | { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 },
283 | ]
284 |
285 | [[package]]
286 | name = "qtpy"
287 | version = "2.4.3"
288 | source = { registry = "https://pypi.org/simple" }
289 | dependencies = [
290 | { name = "packaging" },
291 | ]
292 | sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982 }
293 | wheels = [
294 | { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045 },
295 | ]
296 |
297 | [[package]]
298 | name = "setuptools"
299 | version = "78.1.0"
300 | source = { registry = "https://pypi.org/simple" }
301 | sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 }
302 | wheels = [
303 | { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 },
304 | ]
305 |
306 | [[package]]
307 | name = "typing-extensions"
308 | version = "4.13.0"
309 | source = { registry = "https://pypi.org/simple" }
310 | sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 }
311 | wheels = [
312 | { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
313 | ]
314 |
315 | [[package]]
316 | name = "windows-multitool"
317 | version = "0.0.0"
318 | source = { virtual = "." }
319 | dependencies = [
320 | { name = "cryptography" },
321 | { name = "pyinstaller" },
322 | { name = "pywebview" },
323 | ]
324 |
325 | [package.metadata]
326 | requires-dist = [
327 | { name = "cryptography", specifier = ">=44.0.2" },
328 | { name = "pyinstaller", specifier = ">=6.12.0" },
329 | { name = "pywebview", specifier = ">=5.4" },
330 | ]
331 |
--------------------------------------------------------------------------------