Files
2025-10-12 13:48:41 +02:00

205 lines
6.4 KiB
Python

# -*- coding: UTF-8 -*-
# CPU Priority Manager for NVDA
# Allows setting CPU affinity and process priority for NVDA processes
import os
import sys
# Add psutil to the path
addonDir = os.path.dirname(__file__)
sys.path.insert(0, addonDir)
try:
import psutil
except ImportError:
psutil = None
import globalPluginHandler
import gui
from gui import guiHelper, nvdaControls
from gui.settingsDialogs import SettingsPanel
import config
import wx
import addonHandler
from logHandler import log
addonHandler.initTranslation()
confspec = {
"enabled": "boolean(default=False)",
"cpuCores": "string(default='')",
"priorityLevel": "string(default='HIGH')",
}
config.conf.spec["cpuPriority"] = confspec
class CPUPrioritySettingsPanel(SettingsPanel):
# Translators: Title of the CPU Priority settings panel
title = _("CPU Priority Manager")
def makeSettings(self, settingsSizer):
sHelper = guiHelper.BoxSizerHelper(self, sizer=settingsSizer)
# Translators: Label for checkbox to enable/disable CPU priority management
self.enabledCheckbox = sHelper.addItem(
wx.CheckBox(self, label=_("&Enable CPU priority management"))
)
self.enabledCheckbox.SetValue(config.conf["cpuPriority"]["enabled"])
self.enabledCheckbox.Bind(wx.EVT_CHECKBOX, self.onEnableToggle)
# Translators: Label for text field to specify CPU cores
cpuCoresLabel = _("CPU &cores (comma-separated, e.g., 4,5,6,7):")
self.cpuCoresEdit = sHelper.addLabeledControl(cpuCoresLabel, wx.TextCtrl)
self.cpuCoresEdit.SetValue(config.conf["cpuPriority"]["cpuCores"])
# Translators: Label for priority level dropdown
priorityLabel = _("Process &priority:")
self.priorityChoice = sHelper.addLabeledControl(
priorityLabel,
wx.Choice,
choices=[
# Translators: Normal priority level
_("Normal"),
# Translators: Above normal priority level
_("Above Normal"),
# Translators: High priority level
_("High"),
# Translators: Realtime priority level
_("Realtime (Use with caution)")
]
)
# Map config values to choice indices
priorityMap = {
"NORMAL": 0,
"ABOVE_NORMAL": 1,
"HIGH": 2,
"REALTIME": 3
}
currentPriority = config.conf["cpuPriority"]["priorityLevel"]
self.priorityChoice.SetSelection(priorityMap.get(currentPriority, 2))
if psutil:
try:
cpu_count = psutil.cpu_count(logical=True)
# Translators: Information about available CPU cores
infoText = _("Your system has {count} logical CPU cores (0-{max}).").format(
count=cpu_count,
max=cpu_count - 1
)
sHelper.addItem(wx.StaticText(self, label=infoText))
except Exception:
pass
# Translators: Warning about realtime priority
warningText = _(
"Warning: Setting realtime priority may make your system unstable if NVDA uses too much CPU. "
"Use high priority for most cases. Changes require NVDA restart to take effect."
)
warningLabel = wx.StaticText(self, label=warningText)
warningLabel.Wrap(self.GetSize()[0] - 20)
sHelper.addItem(warningLabel)
self.onEnableToggle(None)
def onEnableToggle(self, evt):
"""Enable or disable controls based on enabled checkbox"""
enabled = self.enabledCheckbox.GetValue()
self.cpuCoresEdit.Enable(enabled)
self.priorityChoice.Enable(enabled)
def onSave(self):
"""Save settings to configuration"""
config.conf["cpuPriority"]["enabled"] = self.enabledCheckbox.GetValue()
config.conf["cpuPriority"]["cpuCores"] = self.cpuCoresEdit.GetValue().strip()
# Map choice indices back to config values
priorityMap = {
0: "NORMAL",
1: "ABOVE_NORMAL",
2: "HIGH",
3: "REALTIME"
}
config.conf["cpuPriority"]["priorityLevel"] = priorityMap[self.priorityChoice.GetSelection()]
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
def __init__(self, *args, **kwargs):
super(GlobalPlugin, self).__init__(*args, **kwargs)
if psutil is None:
log.error("CPU Priority Manager: psutil library not available")
return
gui.settingsDialogs.NVDASettingsDialog.categoryClasses.append(CPUPrioritySettingsPanel)
if config.conf["cpuPriority"]["enabled"]:
self.applyCPUSettings()
def terminate(self):
super(GlobalPlugin, self).terminate()
try:
gui.settingsDialogs.NVDASettingsDialog.categoryClasses.remove(CPUPrioritySettingsPanel)
except (ValueError, AttributeError):
pass
def applyCPUSettings(self):
if psutil is None:
log.error("CPU Priority Manager: psutil not available, cannot apply settings")
return
try:
current_process = psutil.Process()
self.applyToProcess(current_process)
try:
children = current_process.children(recursive=True)
for child in children:
try:
self.applyToProcess(child)
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
log.debug(f"CPU Priority Manager: Could not apply settings to child process {child.pid}: {e}")
except Exception as e:
log.error(f"CPU Priority Manager: Error getting child processes: {e}")
log.info("CPU Priority Manager: Settings applied successfully")
except Exception as e:
log.error(f"CPU Priority Manager: Error applying settings: {e}")
def applyToProcess(self, process):
cpuCoresStr = config.conf["cpuPriority"]["cpuCores"]
if cpuCoresStr:
try:
# Parse CPU cores from comma-separated string
cpuCores = [int(core.strip()) for core in cpuCoresStr.split(',') if core.strip()]
if cpuCores:
process.cpu_affinity(cpuCores)
log.debug(f"CPU Priority Manager: Set CPU affinity for process {process.pid} to cores {cpuCores}")
except ValueError as e:
log.error(f"CPU Priority Manager: Invalid CPU cores format: {e}")
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
log.debug(f"CPU Priority Manager: Could not set CPU affinity for process {process.pid}: {e}")
except Exception as e:
log.error(f"CPU Priority Manager: Error setting CPU affinity: {e}")
priorityLevel = config.conf["cpuPriority"]["priorityLevel"]
try:
priorityClass = {
"NORMAL": psutil.NORMAL_PRIORITY_CLASS,
"ABOVE_NORMAL": psutil.ABOVE_NORMAL_PRIORITY_CLASS,
"HIGH": psutil.HIGH_PRIORITY_CLASS,
"REALTIME": psutil.REALTIME_PRIORITY_CLASS
}.get(priorityLevel, psutil.HIGH_PRIORITY_CLASS)
process.nice(priorityClass)
log.debug(f"CPU Priority Manager: Set priority for process {process.pid} to {priorityLevel}")
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
log.debug(f"CPU Priority Manager: Could not set priority for process {process.pid}: {e}")
except Exception as e:
log.error(f"CPU Priority Manager: Error setting priority: {e}")