From 14c05d47594c36b43bd7bc115071980ee78ba49b Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 21 Mar 2024 18:05:20 -0700 Subject: [PATCH 01/22] App correctly gets display names from display EDID and sets the correct connection for xrandr in self.displays. self.displays now is a nested array contaning the connection and the monitor name (ex: ["dp-1", "monitor"]) --- .../brightness_controller_linux/init.py | 152 +++++++--------- .../util/check_displays.py | 75 +++++++- .../util/resource_provider.py | 2 +- brightness-controller-linux/poetry.lock | 171 +++++++++--------- 4 files changed, 217 insertions(+), 183 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index e84468c..03aff71 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -23,16 +23,16 @@ from qtpy import QtGui, QtCore, QtWidgets from qtpy.QtCore import QSize, Qt from qtpy.QtGui import QIcon -from brightness_controller_linux.util.QtSingleApplication import QtSingleApplication -from brightness_controller_linux.ui.mainwindow import Ui_MainWindow -from brightness_controller_linux.ui.license import Ui_Form as License_Ui_Form -from brightness_controller_linux.ui.about import Ui_Form as About_Ui_Form -from brightness_controller_linux.ui.help import Ui_Form as Help_Ui_Form -from brightness_controller_linux.util import executor as Executor -from brightness_controller_linux.util import check_displays as CDisplay -from brightness_controller_linux.util import write_config as WriteConfig -from brightness_controller_linux.util import read_config as ReadConfig -from brightness_controller_linux.util import resource_provider as rp +from util.QtSingleApplication import QtSingleApplication +from ui.mainwindow import Ui_MainWindow +from ui.license import Ui_Form as License_Ui_Form +from ui.about import Ui_Form as About_Ui_Form +from ui.help import Ui_Form as Help_Ui_Form +from util import executor as Executor +from util import check_displays as CDisplay +from util import write_config as WriteConfig +from util import read_config as ReadConfig +from util import resource_provider as rp # import util.filepath_handler as Filepath_handler import subprocess import threading @@ -43,33 +43,35 @@ class MyApplication(QtWidgets.QMainWindow): displayMaxes = [] displayValues = [] - displayNames = [] + # ["connection", "name"] ordered the same as ddcutil lists + ddcDisplays = [] def __assign_displays(self): """assigns display name """ - self.displays = CDisplay.detect_display_devices() + self.displays = CDisplay.extract_display_names() # returns ['connection', 'name'] self.no_of_displays = len(self.displays) self.no_of_connected_dev = self.no_of_displays if self.no_of_displays == 1: - self.display1 = self.displays[0] + self.display1 = self.displays[0][0] elif self.no_of_displays >= 2: - self.display1 = self.displays[0] - self.display2 = self.displays[1] + self.display1 = self.displays[0][0] + self.display2 = self.displays[1][0] def directlySetMaxBrightness(self, displayNum, percentage): percentage = round(percentage) / 100 subprocess.run(["ddcutil", "setvcp", "10", str(int( - self.displayMaxes[displayNum - 1] * percentage)), "-d", - str(displayNum)]) + self.displayMaxes[displayNum] * percentage)), "-d", + str(displayNum + 1)]) def __init__(self, parent=None): """Initializes""" QtWidgets.QMainWindow.__init__(self, parent) # check if ddcutil is installed + try: if "ddcutil" in str( subprocess.check_output(["ddcutil", "--version"]), 'utf-8'): @@ -81,38 +83,10 @@ def __init__(self, parent=None): else: self.ddcutil_Installed = True except: - self.ddcutil_Installed = False + self.ddcutil_Installed = False - try: - getNames = str(subprocess.check_output(["ddcutil", "detect"]), - 'utf-8').split("\n") - - for i in range(len(getNames)): - if "Model:" in getNames[i]: - - if not getNames[i].split(":")[1].strip() == "": - self.displayNames.append( - getNames[i].split(":")[1].strip()) - - if "Invalid display" in getNames[i]: - self.displayNames.append(getNames[i].strip()) - - for i in range(len(self.displayNames)): - if not self.displayNames[i] == "Invalid display": - brightnessValue = str(subprocess.check_output( - ["ddcutil", "getvcp", "10", "-d", str(i + 1)]), 'utf-8') - - self.displayMaxes.append(int( - brightnessValue.split(",")[1].split("=")[1].strip())) - - self.displayValues.append(int( - brightnessValue.split(',')[0].split('=')[1].strip())) - else: - self.displayMaxes.append(1) - self.displayValues.append(1) - - except Exception as e: - print("error: " + str(e)) + + self.updatingMode = False self.tray_menu = None self.tray_icon = None @@ -151,8 +125,28 @@ def __init__(self, parent=None): if path.exists(self.default_config): self.load_settings(self.default_config) + + if self.ddcutil_Installed: + self.displays = CDisplay.match_ddc_order(self.displays) + + for i in range(len(self.displays)): + if not self.displays[i] == "Invalid display": + brightnessValue = str(subprocess.check_output( + ["ddcutil", "getvcp", "10", "-d", str(i + 1)]), 'utf-8') + + self.displayMaxes.append(int( + brightnessValue.split(",")[1].split("=")[1].strip())) + + self.displayValues.append(int( + brightnessValue.split(',')[0].split('=')[1].strip())) + else: + self.displayMaxes.append(1) + self.displayValues.append(1) + + + if self.ddcutil_Installed: - res = all(ele == "Invalid display" for ele in self.displayNames) + res = all(ele == "Invalid display" for ele in self.displays) if not res: self.ui.directControlBox.setEnabled(True) self.ui.ddcutilsNotInstalled.setVisible(False) @@ -160,7 +154,7 @@ def __init__(self, parent=None): self.ui.ddcutilsNotInstalled.setText( "Laptop Displays Not Supported") - print(self.displayNames) + #print(self.displayNames) print(self.ddcutil_Installed) @@ -277,15 +271,15 @@ def generate_brightness_sources(self): self.ui.primary_combobox.setEnabled(False) return - if self.ddcutil_Installed: + """if self.ddcutil_Installed: for i in range(self.no_of_connected_dev): self.ui.secondary_combo.addItem(self.displayNames[i]) self.ui.primary_combobox.addItem(self.displayNames[i]) pass - else: - for display in self.displays: - self.ui.secondary_combo.addItem(display) - self.ui.primary_combobox.addItem(display) + else:""" + for display in self.displays: + self.ui.secondary_combo.addItem(display[1]) + self.ui.primary_combobox.addItem(display[1]) def connect_handlers(self): """Connects the handlers of GUI widgets""" @@ -327,6 +321,8 @@ def connect_handlers(self): self.ui.actionLoad.triggered.connect(self.load_settings) def directControlUpdate(self, value): + self.updatingMode = True + if self.ui.directControlBox.isChecked(): self.ui.primary_brightness.setMaximum(100) self.ui.primary_brightness.setValue(int(round( @@ -334,11 +330,6 @@ def directControlUpdate(self, value): self.ui.primary_brightness.setFocusPolicy(Qt.NoFocus) self.ui.primary_brightness.setTracking(False) - print("Update: " + self.ui.primary_combobox.currentText()) - - if self.ui.primary_combobox.currentText() == "Invalid display": - self.ui.primary_brightness.setEnabled(False) - if self.no_of_displays > 1: self.ui.secondary_brightness.setMaximum(100) self.ui.secondary_brightness.setValue(int(round( @@ -359,11 +350,8 @@ def directControlUpdate(self, value): self.ui.secondary_brightness.setFocusPolicy(Qt.StrongFocus) self.ui.secondary_brightness.setTracking(True) - if self.no_of_displays == 1: - self.ui.primary_brightness.setEnabled(True) - else: - self.ui.primary_brightness.setEnabled(True) - self.ui.secondary_brightness.setEnabled(True) + self.updatingMode = False + def enable_secondary_widgets(self, boolean): """ @@ -390,14 +378,12 @@ def connect_secondary_widgets(self): def change_value_pbr(self): """Changes Primary Display Brightness""" - if self.ui.directControlBox.isChecked(): + if self.updatingMode: return - setValue = threading.Thread(target=self.directlySetMaxBrightness, - args=( - self.ui.primary_combobox.currentIndex() + 1, - self.ui.primary_brightness.value())) + if self.ui.directControlBox.isChecked(): - setValue.start() + self.directlySetMaxBrightness(self.ui.primary_combobox.currentIndex(), + self.ui.primary_brightness.value()) self.displayValues[self.ui.primary_combobox.currentIndex()] = int( round(self.ui.primary_brightness.value() / 100 * @@ -461,15 +447,12 @@ def change_value_sbr(self): """ Changes Secondary Display Brightness """ + if self.updatingMode: return if self.ui.directControlBox.isChecked(): - setValue = threading.Thread(target=self.directlySetMaxBrightness, - args=( - self.ui.secondary_combo.currentIndex() + 1, - self.ui.secondary_brightness.value())) - - setValue.start() + self.directlySetMaxBrightness(self.ui.secondary_combo.currentIndex(), + self.ui.secondary_brightness.value()) self.displayValues[self.ui.secondary_combo.currentIndex()] = int( round((self.ui.secondary_brightness.value() / 100 * @@ -545,7 +528,7 @@ def secondary_source_combo_activated(self, text): assigns combo value to display """ self.display2 = self.displays[ - self.ui.secondary_combo.currentIndex()] # text + self.ui.secondary_combo.currentIndex()][0] # text print(self.ui.secondary_combo.currentText()) if self.ui.directControlBox.isChecked(): if self.ui.secondary_combo.currentText() == "Invalid display": @@ -560,18 +543,7 @@ def secondary_source_combo_activated(self, text): def primary_source_combo_activated(self, text): """assigns combo value to display""" self.display1 = self.displays[ - self.ui.primary_combobox.currentIndex()] # text - - print(self.ui.primary_combobox.currentText()) - if self.ui.directControlBox.isChecked(): - if self.ui.primary_combobox.currentText() == "Invalid display": - self.ui.primary_brightness.setEnabled(False) - print("primary disabled") - else: - self.ui.primary_brightness.setEnabled(True) - print("primary enabled") - else: - self.ui.secondary_brightness.setEnabled(True) + self.ui.primary_combobox.currentIndex()][0] # text def combo_activated(self, text): """ Designates values to display and to sliders """ diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index d87aaff..ef60cd5 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -20,7 +20,6 @@ import shlex import re - def query_xrandr(): query = "xrandr --query" xrandr_output = subprocess.Popen(shlex.split(query), stdout=subprocess.PIPE, @@ -38,6 +37,77 @@ def extract_displays(output): return connected_displays +def extract_monitor_name(edid_hex): + try: + display_name = edid_hex[edid_hex.find('fc00') + 4:] + display_name = display_name[:display_name.find('0a')] + + return bytes.fromhex(display_name).decode() + + except Exception as e: + print("Error:", e) + return None + + +def extract_display_names(): + xrandr_output = subprocess.check_output(["xrandr", "--verbose"]).decode().splitlines() + + displayVerboseInfo = [] + display = [] + + #get verbose data for displays + for line in xrandr_output: + + if line.startswith("Screen"): continue + + if not line.startswith("\t") and "connected" in line: + if len(display) > 0: + if "disconnected" not in display[0]: displayVerboseInfo.append(display) + display = [] + display.append(line) + else: + display.append(line) + + displays = [] + for monitor in displayVerboseInfo: + monitorInfo = [] + gettingEDID = False + currentEdid = "" + for line in monitor: + + if "connected" in line: + monitorInfo.append(line[:line.find(' ')]) + + if gettingEDID and line.startswith("\t\t"): + currentEdid += line[2:] + else: + gettingEDID = False + + if line == "\tEDID: ": + gettingEDID = True + + monitorInfo.append(extract_monitor_name(currentEdid)) + displays.append(monitorInfo) + + return displays + + +def match_ddc_order(monitorNames): + + detectedMonitors = subprocess.check_output(["ddcutil", "detect"]).decode().splitlines() + + reorderedMonitors = [] + + for line in detectedMonitors: + if "Model" in line: + for monitor in monitorNames: + if monitor[1] in line: + reorderedMonitors.append(monitor) + break + + return reorderedMonitors + + def detect_display_devices(): """ Detects available displays. @@ -48,4 +118,5 @@ def detect_display_devices(): if __name__ == '__main__': - print(detect_display_devices()) + #print(detect_display_devices()) + print(len(extract_display_names())) diff --git a/brightness-controller-linux/brightness_controller_linux/util/resource_provider.py b/brightness-controller-linux/brightness_controller_linux/util/resource_provider.py index 3650012..e9eca67 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/resource_provider.py +++ b/brightness-controller-linux/brightness_controller_linux/util/resource_provider.py @@ -1,7 +1,7 @@ from importlib import resources from pathlib import Path -import brightness_controller_linux.icons as icons +import icons as icons def icon_path(module_name=icons): diff --git a/brightness-controller-linux/poetry.lock b/brightness-controller-linux/poetry.lock index 58f2a9b..68ad105 100644 --- a/brightness-controller-linux/poetry.lock +++ b/brightness-controller-linux/poetry.lock @@ -1,40 +1,54 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + [[package]] name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -43,9 +57,12 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.extras] dev = ["pre-commit", "tox"] @@ -55,17 +72,23 @@ testing = ["pytest", "pytest-benchmark"] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] [[package]] name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -74,9 +97,15 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "PyQt5" version = "5.15.7" description = "Python bindings for the Qt cross platform application toolkit" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, + {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, + {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, + {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, + {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, +] [package.dependencies] PyQt5-Qt5 = ">=5.15.0" @@ -86,25 +115,55 @@ PyQt5-sip = ">=12.11,<13" name = "PyQt5-Qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." -category = "main" optional = false python-versions = "*" +files = [ + {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, +] [[package]] name = "PyQt5-sip" version = "12.11.0" description = "The sip module support for PyQt5" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd733667098cac70e89279d9c239106d543fb480def62a44e6366ccb8f68510b"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec1d8ce50be76c5c1d1c86c6dc0ccacc2446172dde98b663a17871f532f9bd44"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-win32.whl", hash = "sha256:43dfe6dd409e713edeb67019b85419a7a0dc9923bfc451d6cf3778471c122532"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:afa4ffffc54e306669bf2b66ea37abbc56c5cdda4f3f474d20324e3634302b12"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, + {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, +] [[package]] name = "pytest" version = "7.1.3" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, + {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, +] [package.dependencies] attrs = ">=19.2.0" @@ -122,9 +181,12 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "QtPy" version = "2.2.0" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "QtPy-2.2.0-py3-none-any.whl", hash = "sha256:d283cfba378b0dbe36a55b68aea8ee2f86cd6ccf06c023af25bbe705ffbb29e5"}, + {file = "QtPy-2.2.0.tar.gz", hash = "sha256:d85f1b121f24a41ad26c55c446e66abdb7c528839f8c4f11f156ec4541903914"}, +] [package.dependencies] packaging = "*" @@ -136,85 +198,14 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "6cb55fbae7c75eef595c187aa6c8b5487a0875c8feddee2d8d9697dd55a5748e" - -[metadata.files] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -PyQt5 = [ - {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, - {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, - {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, - {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, - {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, -] -PyQt5-Qt5 = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] -PyQt5-sip = [ - {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, - {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, -] -pytest = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, -] -QtPy = [ - {file = "QtPy-2.2.0-py3-none-any.whl", hash = "sha256:d283cfba378b0dbe36a55b68aea8ee2f86cd6ccf06c023af25bbe705ffbb29e5"}, - {file = "QtPy-2.2.0.tar.gz", hash = "sha256:d85f1b121f24a41ad26c55c446e66abdb7c528839f8c4f11f156ec4541903914"}, -] -tomli = [ +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "f9db7aab91a3ce3666d74a685330a389ce052d2aadc35266d12518d9edd4f71d" From a8be596237fcdd76bbe343c52545d7fd8c30eb95 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 21 Mar 2024 18:44:17 -0700 Subject: [PATCH 02/22] Remove extra print statement --- .../brightness_controller_linux/init.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 03aff71..73aefa0 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -154,9 +154,6 @@ def __init__(self, parent=None): self.ui.ddcutilsNotInstalled.setText( "Laptop Displays Not Supported") - #print(self.displayNames) - - print(self.ddcutil_Installed) self.canCloseToTray = False From a24580bb4896d805ae0a00bf9cf8f88456eaa69a Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Wed, 27 Mar 2024 12:54:50 -0700 Subject: [PATCH 03/22] Added fallback to display connection name if monitor name extraction fails --- .../brightness_controller_linux/util/check_displays.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index ef60cd5..0f47415 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -80,13 +80,21 @@ def extract_display_names(): if gettingEDID and line.startswith("\t\t"): currentEdid += line[2:] + elif gettingEDID: + break else: gettingEDID = False if line == "\tEDID: ": gettingEDID = True - monitorInfo.append(extract_monitor_name(currentEdid)) + + monitorName = extract_monitor_name(currentEdid) + if monitorName: + monitorInfo.append(extract_monitor_name(currentEdid)) + else: + monitorInfo.append(monitorInfo[0]) + displays.append(monitorInfo) return displays From 893ceafca8f8d06e2846550e54fdbc2ef43a6d13 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 1 Aug 2024 16:45:13 -0700 Subject: [PATCH 04/22] Add verbosity arguements for debugging without shoving things in console by default --- .../brightness_controller_linux/init.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 73aefa0..c15d095 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -18,7 +18,7 @@ # . import sys -import getpass +import getpass, argparse from os import path, remove, makedirs from qtpy import QtGui, QtCore, QtWidgets from qtpy.QtCore import QSize, Qt @@ -38,6 +38,8 @@ import threading +verbosity = 1 + class MyApplication(QtWidgets.QMainWindow): ddcutil_Installed = False @@ -46,12 +48,20 @@ class MyApplication(QtWidgets.QMainWindow): # ["connection", "name"] ordered the same as ddcutil lists ddcDisplays = [] + global parser + + def verbose(self, verbosityLevel : int, message: str) -> None: + if verbosityLevel >= verbosity: + print(message) + def __assign_displays(self): """assigns display name """ self.displays = CDisplay.extract_display_names() # returns ['connection', 'name'] self.no_of_displays = len(self.displays) self.no_of_connected_dev = self.no_of_displays + self.verbose(2, str(self.displays) + " " + str(self.no_of_displays)) + if self.no_of_displays == 1: self.display1 = self.displays[0][0] elif self.no_of_displays >= 2: @@ -143,9 +153,6 @@ def __init__(self, parent=None): self.displayMaxes.append(1) self.displayValues.append(1) - - - if self.ddcutil_Installed: res = all(ele == "Invalid display" for ele in self.displays) if not res: self.ui.directControlBox.setEnabled(True) @@ -864,6 +871,16 @@ def set_main_window(self, main_win): """assigns main_win as main_window""" self.main_window = main_win + +parser = argparse.ArgumentParser(prog='ProgramName', + description='What the program does', + epilog='use --help to show cli arguments') +parser.add_argument('-v', '--verbose', action='store_const', const=2, default=1) + +args = parser.parse_args() +verbosity = args.verbose + + def main(): UUID = 'PHIR-HWOH-MEIZ-AHTA' APP = QtSingleApplication(UUID, sys.argv) From db19306116dc7873d6d841c06b82ad7b3e57cfb7 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 1 Aug 2024 17:29:52 -0700 Subject: [PATCH 05/22] Updated display validity check in init to match new display array setup ["connection name", "display name"] --- .../brightness_controller_linux/init.py | 28 +++++++++++-------- .../util/check_displays.py | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index c15d095..e02cc09 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -46,7 +46,6 @@ class MyApplication(QtWidgets.QMainWindow): displayMaxes = [] displayValues = [] # ["connection", "name"] ordered the same as ddcutil lists - ddcDisplays = [] global parser @@ -56,7 +55,7 @@ def verbose(self, verbosityLevel : int, message: str) -> None: def __assign_displays(self): """assigns display name """ - self.displays = CDisplay.extract_display_names() # returns ['connection', 'name'] + self.displays = CDisplay.extract_display_names() # returns ['connection', 'display name'] self.no_of_displays = len(self.displays) self.no_of_connected_dev = self.no_of_displays @@ -68,7 +67,8 @@ def __assign_displays(self): self.display1 = self.displays[0][0] self.display2 = self.displays[1][0] - def directlySetMaxBrightness(self, displayNum, percentage): + def directlySetBrightness(self, displayNum, percentage): + self.verbose(2, f"Updating brightness for display {self.displays[displayNum][1]}") percentage = round(percentage) / 100 @@ -140,7 +140,7 @@ def __init__(self, parent=None): self.displays = CDisplay.match_ddc_order(self.displays) for i in range(len(self.displays)): - if not self.displays[i] == "Invalid display": + if not self.displays[i][0].startswith("eDP"): brightnessValue = str(subprocess.check_output( ["ddcutil", "getvcp", "10", "-d", str(i + 1)]), 'utf-8') @@ -150,16 +150,20 @@ def __init__(self, parent=None): self.displayValues.append(int( brightnessValue.split(',')[0].split('=')[1].strip())) else: - self.displayMaxes.append(1) - self.displayValues.append(1) + if not len(self.displays) > 1: + self.ui.directControlBox.setEnabled(True) + self.ui.ddcutilsNotInstalled.setVisible(False) + self.ui.ddcutilsNotInstalled.setText("Laptop Displays Not Supported") + + self.displayMaxes.append(0) + self.displayValues.append(0) + """ res = all(ele == "Invalid display" for ele in self.displays) if not res: - self.ui.directControlBox.setEnabled(True) - self.ui.ddcutilsNotInstalled.setVisible(False) + else: - self.ui.ddcutilsNotInstalled.setText( - "Laptop Displays Not Supported") + """ self.canCloseToTray = False @@ -386,7 +390,7 @@ def change_value_pbr(self): if self.ui.directControlBox.isChecked(): - self.directlySetMaxBrightness(self.ui.primary_combobox.currentIndex(), + self.directlySetBrightness(self.ui.primary_combobox.currentIndex(), self.ui.primary_brightness.value()) self.displayValues[self.ui.primary_combobox.currentIndex()] = int( @@ -455,7 +459,7 @@ def change_value_sbr(self): if self.ui.directControlBox.isChecked(): - self.directlySetMaxBrightness(self.ui.secondary_combo.currentIndex(), + self.directlySetBrightness(self.ui.secondary_combo.currentIndex(), self.ui.secondary_brightness.value()) self.displayValues[self.ui.secondary_combo.currentIndex()] = int( diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index 0f47415..b9a68db 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -93,6 +93,7 @@ def extract_display_names(): if monitorName: monitorInfo.append(extract_monitor_name(currentEdid)) else: + print(f"Failed to get display name from monitor {monitor[0]}") monitorInfo.append(monitorInfo[0]) displays.append(monitorInfo) From ad8e634a40feb691072889d74143ace6eb4105fc Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 1 Aug 2024 20:25:44 -0700 Subject: [PATCH 06/22] Display reordering is now directly after discovering displays to prevent combo boxes being filled with the original xrandr screen order. I also fixed the display update to respect combo box selections as well as updating display sliders after changing either combo box. --- .../brightness_controller_linux/init.py | 97 +++++++++++-------- .../util/check_displays.py | 16 ++- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index e02cc09..ac8327c 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -49,7 +49,7 @@ class MyApplication(QtWidgets.QMainWindow): global parser - def verbose(self, verbosityLevel : int, message: str) -> None: + def verbose(self, verbosityLevel : int, message : str) -> None: if verbosityLevel >= verbosity: print(message) @@ -119,28 +119,19 @@ def __init__(self, parent=None): self.temperature = 'Default' self.no_of_connected_dev = 0 self.__assign_displays() - self.setup_default_directory() - self.generate_dynamic_items() - self.default_config = '/home/{}/.config/' \ - 'brightness_controller/settings' \ - .format(getpass.getuser()) - self.values = [] - self.array_value = 0.01 - for i in range(0, 100): - self.values.append(self.array_value) - self.array_value += 0.01 - self.connect_handlers() - self.setup_widgets() - - if path.exists(self.default_config): - self.load_settings(self.default_config) - + #moved to directly after __assign_displays to prevent comboboxes having items added in the original order from xrandr if self.ddcutil_Installed: self.displays = CDisplay.match_ddc_order(self.displays) + + self.verbose(2, str(self.displays) + " : reordered displays") + + self.ui.ddcutilsNotInstalled.setVisible(False) for i in range(len(self.displays)): if not self.displays[i][0].startswith("eDP"): + self.ui.directControlBox.setEnabled(True) + brightnessValue = str(subprocess.check_output( ["ddcutil", "getvcp", "10", "-d", str(i + 1)]), 'utf-8') @@ -150,14 +141,30 @@ def __init__(self, parent=None): self.displayValues.append(int( brightnessValue.split(',')[0].split('=')[1].strip())) else: - if not len(self.displays) > 1: - self.ui.directControlBox.setEnabled(True) - self.ui.ddcutilsNotInstalled.setVisible(False) - + self.ui.ddcutilsNotInstalled.setVisible(True) self.ui.ddcutilsNotInstalled.setText("Laptop Displays Not Supported") self.displayMaxes.append(0) self.displayValues.append(0) + + self.setup_default_directory() + self.generate_dynamic_items() + self.default_config = '/home/{}/.config/' \ + 'brightness_controller/settings' \ + .format(getpass.getuser()) + self.values = [] + self.array_value = 0.01 + for i in range(0, 100): + self.values.append(self.array_value) + self.array_value += 0.01 + self.connect_handlers() + self.setup_widgets() + + if path.exists(self.default_config): + self.load_settings(self.default_config) + + + """ res = all(ele == "Invalid display" for ele in self.displays) if not res: @@ -333,19 +340,31 @@ def directControlUpdate(self, value): if self.ui.directControlBox.isChecked(): self.ui.primary_brightness.setMaximum(100) - self.ui.primary_brightness.setValue(int(round( - (self.displayValues[0] / self.displayMaxes[0]) * 100))) - self.ui.primary_brightness.setFocusPolicy(Qt.NoFocus) - self.ui.primary_brightness.setTracking(False) - if self.no_of_displays > 1: - self.ui.secondary_brightness.setMaximum(100) - self.ui.secondary_brightness.setValue(int(round( - (self.displayValues[1] / self.displayMaxes[1]) * 100))) - self.ui.secondary_brightness.setFocusPolicy(Qt.NoFocus) - self.ui.secondary_brightness.setTracking(False) + primaryComboIndex = self.ui.primary_combobox.currentIndex() + secondaryComboIndex = self.ui.secondary_combo.currentIndex() + + # if display has valid max value, set the brightness slider's max value to display max + # otherwise we disable the slider since it either errored or is a laptop display and cant be controlled + if self.displayMaxes[primaryComboIndex] > 0: + self.ui.primary_brightness.setEnabled(True) + self.ui.primary_brightness.setValue(int(round( + (self.displayValues[primaryComboIndex] / self.displayMaxes[primaryComboIndex]) * 100))) + + self.ui.primary_brightness.setFocusPolicy(Qt.NoFocus) + self.ui.primary_brightness.setTracking(False) + else: + self.ui.primary_brightness.setEnabled(False) - if self.ui.secondary_combo.currentText() == "Invalid display": + if self.no_of_displays > 1: + if self.displayMaxes[secondaryComboIndex] > 0: + self.ui.secondary_brightness.setEnabled(True) + self.ui.secondary_brightness.setMaximum(100) + self.ui.secondary_brightness.setValue(int(round( + (self.displayValues[secondaryComboIndex] / self.displayMaxes[secondaryComboIndex]) * 100))) + self.ui.secondary_brightness.setFocusPolicy(Qt.NoFocus) + self.ui.secondary_brightness.setTracking(False) + else: self.ui.secondary_brightness.setEnabled(False) else: @@ -537,22 +556,18 @@ def secondary_source_combo_activated(self, text): """ self.display2 = self.displays[ self.ui.secondary_combo.currentIndex()][0] # text - print(self.ui.secondary_combo.currentText()) + if self.ui.directControlBox.isChecked(): - if self.ui.secondary_combo.currentText() == "Invalid display": - self.ui.secondary_brightness.setEnabled(False) - print("secondary disabled") - else: - self.ui.secondary_brightness.setEnabled(True) - print("secondary enabled") - else: - self.ui.secondary_brightness.setEnabled(True) + self.directControlUpdate(0) def primary_source_combo_activated(self, text): """assigns combo value to display""" self.display1 = self.displays[ self.ui.primary_combobox.currentIndex()][0] # text + if self.ui.directControlBox.isChecked(): + self.directControlUpdate(0) + def combo_activated(self, text): """ Designates values to display and to sliders """ self.temperature = text diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index b9a68db..110fbec 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -104,16 +104,28 @@ def extract_display_names(): def match_ddc_order(monitorNames): detectedMonitors = subprocess.check_output(["ddcutil", "detect"]).decode().splitlines() - + reorderedMonitors = [] + #laptopMonitorNames = [['eDP-1', 'eDP-1'], ['HDMI-1', 'VG279']] + + #laptopTestCase = ['Display 1', ' I2C bus: /dev/i2c-1', ' EDID synopsis:', ' Mfg id: AUS', ' Model: VG279', ' Serial number: Redacted', ' Manufacture year: 2020', ' EDID version: 1.3', ' VCP version: 2.2', '', 'Invalid display', ' I2C bus: /dev/i2c-4', ' EDID synopsis:', ' Mfg id: BOE', ' Model: ', ' Serial number: ', ' Manufacture year: 2015', ' EDID version: 1.4', ' DDC communication failed', ' This is an eDP laptop display. Laptop displays do not support DDC/CI.', ''] + for line in detectedMonitors: if "Model" in line: for monitor in monitorNames: - if monitor[1] in line: + modelName = line.split(":")[1].strip() + if modelName == '': + if monitor[1].startswith('eDP'): + reorderedMonitors.append(monitor) + break + + if monitor[1] in modelName: reorderedMonitors.append(monitor) break + if len(monitorNames) != len(reorderedMonitors): + print("ERROR IN MONITOR REORDERING please create an issue on the github with 'ddcutil detect' and xrandr --verbose outputs") return reorderedMonitors From ab675fc3eedc65716fa7fb655db676a36bbcfe22 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 1 Aug 2024 20:30:40 -0700 Subject: [PATCH 07/22] Fixed laptop brightness slider not being reenabled after leaving ddcutil mode. --- .../brightness_controller_linux/init.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index ac8327c..e3cfae7 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -368,6 +368,8 @@ def directControlUpdate(self, value): self.ui.secondary_brightness.setEnabled(False) else: + self.ui.primary_brightness.setEnabled(True) + self.ui.primary_brightness.setEnabled(True) self.ui.primary_brightness.setMaximum(99) self.ui.secondary_brightness.setMaximum(99) self.ui.primary_brightness.setValue(99) @@ -556,7 +558,7 @@ def secondary_source_combo_activated(self, text): """ self.display2 = self.displays[ self.ui.secondary_combo.currentIndex()][0] # text - + if self.ui.directControlBox.isChecked(): self.directControlUpdate(0) From 05d7555ca5f8835e714f2a4538ec1c3f635187fa Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Thu, 1 Aug 2024 20:33:36 -0700 Subject: [PATCH 08/22] Secondary Brightness slider enabled after leaving ddcutil mode --- brightness-controller-linux/brightness_controller_linux/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index e3cfae7..3f5a818 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -369,7 +369,7 @@ def directControlUpdate(self, value): else: self.ui.primary_brightness.setEnabled(True) - self.ui.primary_brightness.setEnabled(True) + self.ui.secondary_brightness.setEnabled(True) self.ui.primary_brightness.setMaximum(99) self.ui.secondary_brightness.setMaximum(99) self.ui.primary_brightness.setValue(99) From f8908284a1b075a207ffbfa3952a710d374a4900 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 7 Sep 2024 19:46:59 -0700 Subject: [PATCH 09/22] Fixed crashing on Wayland! Monitor names are missing now since xrandr doesnt give edid anymore. --- .../brightness_controller_linux/init.py | 3 +-- .../util/check_displays.py | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 3f5a818..a360250 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -88,8 +88,7 @@ def __init__(self, parent=None): if "sudo modprobe" in str( subprocess.check_output(["ddcutil", "environment"]), 'utf-8'): - self.ui.ddcutilsNotInstalled.setText( - "add i2c-dev to etc/modules-load.d") + self.ui.ddcutilsNotInstalled.setText("add i2c-dev to etc/modules-load.d") else: self.ddcutil_Installed = True except: diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index 110fbec..3ebaf40 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Brightness Controller. If not, see . -import subprocess +import subprocess, os import shlex import re @@ -56,19 +56,39 @@ def extract_display_names(): display = [] #get verbose data for displays + i = -1 for line in xrandr_output: + i += 1 if line.startswith("Screen"): continue + if line.startswith("DP"): + print("WE FOUND ONE") + + if i == len(xrandr_output) - 1: + display.append(line) + if "disconnected" not in display[0]: + if not display[0].startswith("Unknown"): + displayVerboseInfo.append(display) + if not line.startswith("\t") and "connected" in line: if len(display) > 0: - if "disconnected" not in display[0]: displayVerboseInfo.append(display) + if "disconnected" not in display[0]: + if not display[0].startswith("Unknown"): + displayVerboseInfo.append(display) + display = [] display.append(line) else: display.append(line) - + + + displays = [] + + if os.getenv("XDG_SESSION_TYPE") == "wayland": + print("WAYLAND SESSION!") + for monitor in displayVerboseInfo: monitorInfo = [] gettingEDID = False @@ -126,6 +146,7 @@ def match_ddc_order(monitorNames): if len(monitorNames) != len(reorderedMonitors): print("ERROR IN MONITOR REORDERING please create an issue on the github with 'ddcutil detect' and xrandr --verbose outputs") + return monitorNames # fall back to unreordered output return reorderedMonitors From d3a53af0b245bbee963f8650b93c2d8a86758302 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 7 Sep 2024 20:51:44 -0700 Subject: [PATCH 10/22] The old software brightness setting is broken on wayland, but display names are now gotten on wayland! DDC control now functions on both x11 and wayland. --- .../util/check_displays.py | 129 ++++++++++++++---- 1 file changed, 99 insertions(+), 30 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index 3ebaf40..b998955 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -37,7 +37,7 @@ def extract_displays(output): return connected_displays -def extract_monitor_name(edid_hex): +def extract_edid_name(edid_hex): try: display_name = edid_hex[edid_hex.find('fc00') + 4:] display_name = display_name[:display_name.find('0a')] @@ -48,6 +48,95 @@ def extract_monitor_name(edid_hex): print("Error:", e) return None +def x11_Monitor_Name_Extractor(xrandr_monitors : list): + + displays = [] + + for monitor in xrandr_monitors: + monitorInfo = [] + gettingEDID = False + currentEdid = "" + for line in monitor: + + if "connected" in line: + monitorInfo.append(line[:line.find(' ')]) + + if gettingEDID and line.startswith("\t\t"): + currentEdid += line[2:] + elif gettingEDID: + break + else: + gettingEDID = False + + if line == "\tEDID: ": + gettingEDID = True + + + monitorName = extract_edid_name(currentEdid) + if monitorName: + monitorInfo.append(monitorName) + else: + print(f"Failed to get display name from monitor {monitor[0]}") + monitorInfo.append(monitorInfo[0]) + + displays.append(monitorInfo) + + return displays + +def wayland_Monitor_Info_Extractor(monitorInfo): + + connectionName = None + modelName = None + + for line in monitorInfo: + #print(line) + + if line.startswith('\tname:'): + connectionName = line.split(':')[1].strip() + + if line.startswith("\tmake:"): + model = line.split(',')[1] + modelName = model[ model.find("'") + 1 : model.rfind("/") ] + #print(modelName) + + if modelName and connectionName: + return [connectionName, modelName] + else: + return None + + +def wayland_Monitor_Name_Extractor(): + + waylandInfo = [] + displays = [] + currentDisplay = [] + + try: + waylandInfo = subprocess.check_output(["wayland-info"]).decode().splitlines() + print("test") + except: + print("ERROR: Please install the package \"wayland-utils\" for the wayland-info command!") + print("Monitor names will not be shown as they can't be labeled") + return None + + i = -1 + for line in waylandInfo: + #print(line) + i += 1 + + if line.startswith("interface: 'wl_output',") or i == len(waylandInfo) - 1: + if currentDisplay[0].startswith("interface: 'wl_output',"): + #get display info out + monitorInfo = wayland_Monitor_Info_Extractor(currentDisplay) + if not monitorInfo[0].startswith("Unknown"): + displays.append(monitorInfo) + #print(currentDisplay) + currentDisplay = [] + + currentDisplay.append(line) + + return displays + def extract_display_names(): xrandr_output = subprocess.check_output(["xrandr", "--verbose"]).decode().splitlines() @@ -82,41 +171,21 @@ def extract_display_names(): else: display.append(line) - - displays = [] if os.getenv("XDG_SESSION_TYPE") == "wayland": print("WAYLAND SESSION!") + waylandDisplayNames = wayland_Monitor_Name_Extractor() + if waylandDisplayNames == None: + # fall back to old nameing + print("ERROR Falling back to x11 monitor name extractor! Names may not be extracted!") + return x11_Monitor_Name_Extractor(displayVerboseInfo) - for monitor in displayVerboseInfo: - monitorInfo = [] - gettingEDID = False - currentEdid = "" - for line in monitor: + # we got display names from wayland! + return waylandDisplayNames - if "connected" in line: - monitorInfo.append(line[:line.find(' ')]) - - if gettingEDID and line.startswith("\t\t"): - currentEdid += line[2:] - elif gettingEDID: - break - else: - gettingEDID = False - - if line == "\tEDID: ": - gettingEDID = True - - - monitorName = extract_monitor_name(currentEdid) - if monitorName: - monitorInfo.append(extract_monitor_name(currentEdid)) - else: - print(f"Failed to get display name from monitor {monitor[0]}") - monitorInfo.append(monitorInfo[0]) - - displays.append(monitorInfo) + else: + return x11_Monitor_Name_Extractor(displayVerboseInfo) return displays From a08d30400056eb05d13ab7bd085dc157065acff6 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 7 Sep 2024 21:24:38 -0700 Subject: [PATCH 11/22] restored original poetry imports. --- .../brightness_controller_linux/init.py | 30 ++++++++++--------- .../util/check_displays.py | 1 - 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index a360250..767ec1f 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -19,20 +19,20 @@ import sys import getpass, argparse -from os import path, remove, makedirs +from os import path, remove, makedirs, getenv from qtpy import QtGui, QtCore, QtWidgets from qtpy.QtCore import QSize, Qt from qtpy.QtGui import QIcon -from util.QtSingleApplication import QtSingleApplication -from ui.mainwindow import Ui_MainWindow -from ui.license import Ui_Form as License_Ui_Form -from ui.about import Ui_Form as About_Ui_Form -from ui.help import Ui_Form as Help_Ui_Form -from util import executor as Executor -from util import check_displays as CDisplay -from util import write_config as WriteConfig -from util import read_config as ReadConfig -from util import resource_provider as rp +from brightness_controller_linux.util.QtSingleApplication import QtSingleApplication +from brightness_controller_linux.ui.mainwindow import Ui_MainWindow +from brightness_controller_linux.ui.license import Ui_Form as License_Ui_Form +from brightness_controller_linux.ui.about import Ui_Form as About_Ui_Form +from brightness_controller_linux.ui.help import Ui_Form as Help_Ui_Form +from brightness_controller_linux.util import executor as Executor +from brightness_controller_linux.util import check_displays as CDisplay +from brightness_controller_linux.util import write_config as WriteConfig +from brightness_controller_linux.util import read_config as ReadConfig +from brightness_controller_linux.util import resource_provider as rp # import util.filepath_handler as Filepath_handler import subprocess import threading @@ -50,7 +50,7 @@ class MyApplication(QtWidgets.QMainWindow): global parser def verbose(self, verbosityLevel : int, message : str) -> None: - if verbosityLevel >= verbosity: + if verbosity>= verbosityLevel: print(message) def __assign_displays(self): @@ -80,8 +80,11 @@ def __init__(self, parent=None): """Initializes""" QtWidgets.QMainWindow.__init__(self, parent) + # warn if wayland is installed + if os.getenv("XDG_SESSION_TYPE") == "wayland": + print("Warning: Wayland session detected! Wayland is in experimental support! Expect buggy behavior") + # check if ddcutil is installed - try: if "ddcutil" in str( subprocess.check_output(["ddcutil", "--version"]), 'utf-8'): @@ -900,7 +903,6 @@ def set_main_window(self, main_win): args = parser.parse_args() verbosity = args.verbose - def main(): UUID = 'PHIR-HWOH-MEIZ-AHTA' APP = QtSingleApplication(UUID, sys.argv) diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index b998955..910f586 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -113,7 +113,6 @@ def wayland_Monitor_Name_Extractor(): try: waylandInfo = subprocess.check_output(["wayland-info"]).decode().splitlines() - print("test") except: print("ERROR: Please install the package \"wayland-utils\" for the wayland-info command!") print("Monitor names will not be shown as they can't be labeled") From fd883f832a1c85b9528738bcd42b688bf8daf01d Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Tue, 17 Sep 2024 11:56:52 -0700 Subject: [PATCH 12/22] Added warning message for wayland users. I should probably make this a ui popup instead of only in the terminal, but at least there will be a more definitive way of telling if users are on wayland. --- brightness-controller-linux/brightness_controller_linux/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 767ec1f..dedbf5c 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -18,7 +18,7 @@ # . import sys -import getpass, argparse +import getpass, argparse, os from os import path, remove, makedirs, getenv from qtpy import QtGui, QtCore, QtWidgets from qtpy.QtCore import QSize, Qt From be90b304983beb273c15d50cf9d256d2c2d3158a Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Tue, 17 Sep 2024 13:41:25 -0700 Subject: [PATCH 13/22] Added checks to prevent laptop displays from being set. The sliders will be disabled if it is a laptop display and there is another check in the setBrightness function as well just in case. The subprocess.run is now also wrapped in a try block as a last resort. --- .../brightness_controller_linux/init.py | 62 +++++++++++++++---- .../util/check_displays.py | 3 - 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index dedbf5c..661a3b3 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -42,6 +42,7 @@ class MyApplication(QtWidgets.QMainWindow): ddcutil_Installed = False + waylandEnvironment = False displayMaxes = [] displayValues = [] @@ -67,14 +68,17 @@ def __assign_displays(self): self.display1 = self.displays[0][0] self.display2 = self.displays[1][0] - def directlySetBrightness(self, displayNum, percentage): - self.verbose(2, f"Updating brightness for display {self.displays[displayNum][1]}") + def directlySetBrightness(self, displayNum, value): + self.verbose(2, f"Updating brightness for display {self.displays[displayNum][1]} with value {value}") - percentage = round(percentage) / 100 + if self.displays[displayNum][0].startswith("eDP"): + print("ATTEMPTED TO SET LAPTOP DISPLAY: ABORTING") + return - subprocess.run(["ddcutil", "setvcp", "10", str(int( - self.displayMaxes[displayNum] * percentage)), "-d", - str(displayNum + 1)]) + try: + subprocess.run(["ddcutil", "setvcp", "10", str(int(value)), "-d", str(displayNum + 1)]) + except: + print(f"Error while setting display {self.displays[displayNum][1]} with value {value}") def __init__(self, parent=None): """Initializes""" @@ -83,6 +87,8 @@ def __init__(self, parent=None): # warn if wayland is installed if os.getenv("XDG_SESSION_TYPE") == "wayland": print("Warning: Wayland session detected! Wayland is in experimental support! Expect buggy behavior") + waylandEnvironment = True + QtWidgets.QMessageBox.warning(self, "Wayland Environment", "Wayland is in EXPERIMENTAL support. Software brightness is currently broken.") # check if ddcutil is installed try: @@ -97,6 +103,15 @@ def __init__(self, parent=None): except: self.ddcutil_Installed = False + if (not self.ddcutil_Installed) and waylandEnvironment: + # Just exit entirely if we are on wayland and dont have ddcutil since it just won't do anything + errorBox = QtWidgets.QMessageBox.critical(None, + "DDCUtil Missing", + "Software brightness is broken on wayland and ddcutil doesn't appear to be installed! Please install the package `ddcutil` to use this program on wayland.", + QtWidgets.QMessageBox.StandardButton.Close) + sys.exit() + + self.updatingMode = False @@ -349,9 +364,14 @@ def directControlUpdate(self, value): # if display has valid max value, set the brightness slider's max value to display max # otherwise we disable the slider since it either errored or is a laptop display and cant be controlled if self.displayMaxes[primaryComboIndex] > 0: - self.ui.primary_brightness.setEnabled(True) - self.ui.primary_brightness.setValue(int(round( - (self.displayValues[primaryComboIndex] / self.displayMaxes[primaryComboIndex]) * 100))) + + if not self.displays[primaryComboIndex][0].startswith("eDP"): + self.ui.primary_brightness.setEnabled(True) + self.ui.primary_brightness.setMaximum(self.displayMaxes[primaryComboIndex]) + self.ui.primary_brightness.setValue(int(round( + (self.displayValues[primaryComboIndex] / self.displayMaxes[primaryComboIndex]) * 100))) + else: + self.ui.primary_brightness.setEnabled(False) self.ui.primary_brightness.setFocusPolicy(Qt.NoFocus) self.ui.primary_brightness.setTracking(False) @@ -360,10 +380,14 @@ def directControlUpdate(self, value): if self.no_of_displays > 1: if self.displayMaxes[secondaryComboIndex] > 0: - self.ui.secondary_brightness.setEnabled(True) - self.ui.secondary_brightness.setMaximum(100) - self.ui.secondary_brightness.setValue(int(round( - (self.displayValues[secondaryComboIndex] / self.displayMaxes[secondaryComboIndex]) * 100))) + if not self.displays[secondaryComboIndex][0].startswith("eDP"): + self.ui.secondary_brightness.setEnabled(True) + self.ui.secondary_brightness.setMaximum(self.displayMaxes[secondaryComboIndex]) + self.ui.secondary_brightness.setValue(int(round( + (self.displayValues[secondaryComboIndex] / self.displayMaxes[secondaryComboIndex]) * 100))) + else: + self.ui.secondary_brightness.setEnabled(False) + self.ui.secondary_brightness.setFocusPolicy(Qt.NoFocus) self.ui.secondary_brightness.setTracking(False) else: @@ -561,6 +585,12 @@ def secondary_source_combo_activated(self, text): self.display2 = self.displays[ self.ui.secondary_combo.currentIndex()][0] # text + if self.ddcutil_Installed and self.displays[self.ui.secondary_combo.currentIndex()][0].startswith("eDP"): + self.ui.primary_brightness.setEnabled(False) + else: + self.ui.primary_brightness.setEnabled(True) + + if self.ui.directControlBox.isChecked(): self.directControlUpdate(0) @@ -569,6 +599,12 @@ def primary_source_combo_activated(self, text): self.display1 = self.displays[ self.ui.primary_combobox.currentIndex()][0] # text + # Disable slider if laptop display + if self.ddcutil_Installed and self.displays[self.ui.primary_combobox.currentIndex()][0].startswith("eDP"): + self.ui.primary_brightness.setEnabled(False) + else: + self.ui.primary_brightness.setEnabled(True) + if self.ui.directControlBox.isChecked(): self.directControlUpdate(0) diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index 910f586..a6f3b60 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -150,9 +150,6 @@ def extract_display_names(): if line.startswith("Screen"): continue - if line.startswith("DP"): - print("WE FOUND ONE") - if i == len(xrandr_output) - 1: display.append(line) if "disconnected" not in display[0]: From 798e157a7f05b0507d789ced464c45cf40137f92 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Tue, 17 Sep 2024 14:25:36 -0700 Subject: [PATCH 14/22] added try block to xrandr execution as well. --- .../brightness_controller_linux/util/executor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/util/executor.py b/brightness-controller-linux/brightness_controller_linux/util/executor.py index 5c109ac..c19ddb5 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/executor.py +++ b/brightness-controller-linux/brightness_controller_linux/util/executor.py @@ -20,4 +20,7 @@ def execute_command(string_cmd): - subprocess.check_output(string_cmd, shell=True) + try: + subprocess.check_output(string_cmd, shell=True) + except: + None \ No newline at end of file From d5e7597e72f21b4a1258e27cc2a83dbc0bb075cf Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Tue, 17 Sep 2024 14:36:30 -0700 Subject: [PATCH 15/22] Now forcing ddcutil on wayland. --- .../brightness_controller_linux/init.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 661a3b3..aa1c7f2 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -87,7 +87,7 @@ def __init__(self, parent=None): # warn if wayland is installed if os.getenv("XDG_SESSION_TYPE") == "wayland": print("Warning: Wayland session detected! Wayland is in experimental support! Expect buggy behavior") - waylandEnvironment = True + self.waylandEnvironment = True QtWidgets.QMessageBox.warning(self, "Wayland Environment", "Wayland is in EXPERIMENTAL support. Software brightness is currently broken.") # check if ddcutil is installed @@ -103,7 +103,7 @@ def __init__(self, parent=None): except: self.ddcutil_Installed = False - if (not self.ddcutil_Installed) and waylandEnvironment: + if (not self.ddcutil_Installed) and self.waylandEnvironment: # Just exit entirely if we are on wayland and dont have ddcutil since it just won't do anything errorBox = QtWidgets.QMessageBox.critical(None, "DDCUtil Missing", @@ -177,11 +177,13 @@ def __init__(self, parent=None): self.connect_handlers() self.setup_widgets() + if self.ddcutil_Installed and self.waylandEnvironment: + self.ui.directControlBox.setChecked(True) # Auto turn on ddc control since xrandr doesnt work on wayland + self.ui.directControlBox.setEnabled(False) + if path.exists(self.default_config): self.load_settings(self.default_config) - - """ res = all(ele == "Invalid display" for ele in self.displays) if not res: @@ -353,6 +355,10 @@ def connect_handlers(self): self.ui.actionLoad.triggered.connect(self.load_settings) def directControlUpdate(self, value): + + if self.ddcutil_Installed and self.waylandEnvironment and not self.ui.directControlBox.isChecked(): + self.ui.directControlBox.setChecked(True) # Force wayland users to only use ddc + self.updatingMode = True if self.ui.directControlBox.isChecked(): From 2a396300d0e011297be00e826a3567a60f39a65d Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 21 Sep 2024 15:33:47 -0700 Subject: [PATCH 16/22] Fix pyproject using lowercase readme. --- brightness-controller-linux/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brightness-controller-linux/pyproject.toml b/brightness-controller-linux/pyproject.toml index 05dcfbb..642d5cd 100644 --- a/brightness-controller-linux/pyproject.toml +++ b/brightness-controller-linux/pyproject.toml @@ -3,7 +3,7 @@ name = "brightness-controller-linux" version = "2.4" description = "Using Brightness Controller, you can control brightness of both primary and external displays in Linux. Check it out!" authors = ["Amit "] -readme = "readme.md" +readme = "README.md" homepage = "https://github.com/LordAmit/Brightness" repository = "https://github.com/LordAmit/Brightness" keywords = [ From 972f51a8a654e7c2dce0aefeabf12c5ae0eb5297 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 21 Sep 2024 15:46:15 -0700 Subject: [PATCH 17/22] allow PyQt5 to be imported instead if qtpy fails to import. --- .../brightness_controller_linux/init.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index aa1c7f2..12b2d58 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -20,9 +20,14 @@ import sys import getpass, argparse, os from os import path, remove, makedirs, getenv -from qtpy import QtGui, QtCore, QtWidgets -from qtpy.QtCore import QSize, Qt -from qtpy.QtGui import QIcon +try: + from qtpy import QtGui, QtCore, QtWidgets + from qtpy.QtCore import QSize, Qt + from qtpy.QtGui import QIcon +except: + from PyQt5 import QtGui, QtCore, QtWidgets + from PyQt5.QtCore import QSize, Qt + from PyQt5.QtGui import QIcon from brightness_controller_linux.util.QtSingleApplication import QtSingleApplication from brightness_controller_linux.ui.mainwindow import Ui_MainWindow from brightness_controller_linux.ui.license import Ui_Form as License_Ui_Form From 8cd05ff0dbbc2d9ef857058c9a469f6a9a1359a6 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 21 Sep 2024 15:53:19 -0700 Subject: [PATCH 18/22] Undo pyqt5 import --- .../brightness_controller_linux/init.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index 12b2d58..aa1c7f2 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -20,14 +20,9 @@ import sys import getpass, argparse, os from os import path, remove, makedirs, getenv -try: - from qtpy import QtGui, QtCore, QtWidgets - from qtpy.QtCore import QSize, Qt - from qtpy.QtGui import QIcon -except: - from PyQt5 import QtGui, QtCore, QtWidgets - from PyQt5.QtCore import QSize, Qt - from PyQt5.QtGui import QIcon +from qtpy import QtGui, QtCore, QtWidgets +from qtpy.QtCore import QSize, Qt +from qtpy.QtGui import QIcon from brightness_controller_linux.util.QtSingleApplication import QtSingleApplication from brightness_controller_linux.ui.mainwindow import Ui_MainWindow from brightness_controller_linux.ui.license import Ui_Form as License_Ui_Form From 59061b6b922b9afc37cc8501964949df796615e7 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 21 Sep 2024 18:17:46 -0700 Subject: [PATCH 19/22] Created logging functions. --- .../brightness_controller_linux/util/log.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 brightness-controller-linux/brightness_controller_linux/util/log.py diff --git a/brightness-controller-linux/brightness_controller_linux/util/log.py b/brightness-controller-linux/brightness_controller_linux/util/log.py new file mode 100644 index 0000000..d2d4bbe --- /dev/null +++ b/brightness-controller-linux/brightness_controller_linux/util/log.py @@ -0,0 +1,57 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# This file is part of Brightness Controller. +# +# Brightness Controller is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Brightness Controller is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Brightness Controller. If not, see +# . + +import os, sys +from datetime import datetime + +logPath = f"/home/{os.getlogin()}/.config/brightness_controller/log.txt" + +def write(logString, level): + currentTime = datetime.now() + with open(logPath, 'a') as file: + if type(logString) is list: + for line in logString: + if type(line) is list: + write(line, level) + else: + file.write(f"[{currentTime}] [{level}] {line}\n") + else: + file.write(f"[{currentTime}] [{level}] {logString}\n") + +def info(logString): + write(logString, "info") + +def warning(logString): + write(logString, "warning") + +def error(logString): + write(logString, "ERROR") + +def fatal(logString): + write(logString, "FATAL") + +def begin(): + if not os.path.exists(logPath): + try: + os.makedirs(f"/home/{os.getlogin()}/.config/brightness_controller") + except: + None + open(logPath, 'w').close() + + info("Application start!") \ No newline at end of file From 6b25fee5bbc0aa1461139961a435ec61848f9757 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake Date: Sat, 21 Sep 2024 18:57:37 -0700 Subject: [PATCH 20/22] Added logging EVERYWHERE --- .gitignore | 4 +- .../brightness_controller_linux/init.py | 61 ++++++++++++++----- .../util/check_displays.py | 42 ++++++++++--- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 3516a57..e3c2527 100755 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,6 @@ ENV/ .mypy* # idea -.idea* \ No newline at end of file +.idea* +brightness-controller-linux/test.txt +brightness-controller-linux/testDdc.txt diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index aa1c7f2..c59b929 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -18,7 +18,7 @@ # . import sys -import getpass, argparse, os +import getpass, argparse, os, traceback from os import path, remove, makedirs, getenv from qtpy import QtGui, QtCore, QtWidgets from qtpy.QtCore import QSize, Qt @@ -33,6 +33,8 @@ from brightness_controller_linux.util import write_config as WriteConfig from brightness_controller_linux.util import read_config as ReadConfig from brightness_controller_linux.util import resource_provider as rp + +import brightness_controller_linux.util.log as log # import util.filepath_handler as Filepath_handler import subprocess import threading @@ -60,6 +62,10 @@ def __assign_displays(self): self.no_of_displays = len(self.displays) self.no_of_connected_dev = self.no_of_displays + log.info(f"{self.no_of_displays} detected displays:") + log.info(str(self.displays)) + log.info("") + self.verbose(2, str(self.displays) + " " + str(self.no_of_displays)) if self.no_of_displays == 1: @@ -70,6 +76,7 @@ def __assign_displays(self): def directlySetBrightness(self, displayNum, value): self.verbose(2, f"Updating brightness for display {self.displays[displayNum][1]} with value {value}") + log.info(f"Updating brightness for display {displayNum} {self.displays[displayNum][1]} with value {value}") if self.displays[displayNum][0].startswith("eDP"): print("ATTEMPTED TO SET LAPTOP DISPLAY: ABORTING") @@ -79,15 +86,20 @@ def directlySetBrightness(self, displayNum, value): subprocess.run(["ddcutil", "setvcp", "10", str(int(value)), "-d", str(displayNum + 1)]) except: print(f"Error while setting display {self.displays[displayNum][1]} with value {value}") + log.error(f"Error while setting display {displayNum} {self.displays[displayNum][1]} with value {value}") + def __init__(self, parent=None): """Initializes""" QtWidgets.QMainWindow.__init__(self, parent) + log.begin() + # warn if wayland is installed if os.getenv("XDG_SESSION_TYPE") == "wayland": print("Warning: Wayland session detected! Wayland is in experimental support! Expect buggy behavior") self.waylandEnvironment = True + log.warning("Wayland session detected!") QtWidgets.QMessageBox.warning(self, "Wayland Environment", "Wayland is in EXPERIMENTAL support. Software brightness is currently broken.") # check if ddcutil is installed @@ -103,8 +115,11 @@ def __init__(self, parent=None): except: self.ddcutil_Installed = False + log.info(f"DDCUtils installed: {self.ddcutil_Installed}") + if (not self.ddcutil_Installed) and self.waylandEnvironment: # Just exit entirely if we are on wayland and dont have ddcutil since it just won't do anything + log.fatal("Wayland environment detected and ddcutils is not installed! Exiting") errorBox = QtWidgets.QMessageBox.critical(None, "DDCUtil Missing", "Software brightness is broken on wayland and ddcutil doesn't appear to be installed! Please install the package `ddcutil` to use this program on wayland.", @@ -145,24 +160,33 @@ def __init__(self, parent=None): self.ui.ddcutilsNotInstalled.setVisible(False) - for i in range(len(self.displays)): - if not self.displays[i][0].startswith("eDP"): - self.ui.directControlBox.setEnabled(True) + log.info("Getting display brightness ranges.") - brightnessValue = str(subprocess.check_output( - ["ddcutil", "getvcp", "10", "-d", str(i + 1)]), 'utf-8') + for i in range(len(self.displays)): + self.ui.directControlBox.setEnabled(True) - self.displayMaxes.append(int( - brightnessValue.split(",")[1].split("=")[1].strip())) + brightnessValue = "" - self.displayValues.append(int( - brightnessValue.split(',')[0].split('=')[1].strip())) - else: + brightnessValue = subprocess.getoutput(f"ddcutil getvcp 10 -d {i + 1}") # Ignores the return value + log.info(brightnessValue) + if "Display not found" in brightnessValue: + log.error(f"Display wasn't found for command `ddcutil getvcp 10 -d {i + 1}`") self.ui.ddcutilsNotInstalled.setVisible(True) self.ui.ddcutilsNotInstalled.setText("Laptop Displays Not Supported") + self.displayMaxes.append(1) + self.displayValues.append(1) + continue + + self.displayMaxes.append(int( + brightnessValue.split(",")[1].split("=")[1].strip())) + + self.displayValues.append(int( + brightnessValue.split(',')[0].split('=')[1].strip())) - self.displayMaxes.append(0) - self.displayValues.append(0) + log.info(f"current display values {self.displayValues}") + log.info(f"display maxes: {self.displayMaxes}") + + self.setup_default_directory() self.generate_dynamic_items() @@ -198,6 +222,8 @@ def __init__(self, parent=None): self.canCloseToTray = True self.setup_tray(parent) + log.info("Init finished!") + def setup_default_directory(self): """ Create default settings directory if it doesnt exist """ directory = '/home/{}/.config/' \ @@ -219,6 +245,7 @@ def closeEvent(self, event): QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: event.accept() + log.info("Application Exiting!") sys.exit(self.APP.exec_()) else: event.ignore() @@ -236,6 +263,7 @@ def trayClose(self): QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: + log.info("Application Exiting!") sys.exit(self.APP.exec_()) def setup_tray(self, parent): @@ -359,6 +387,8 @@ def directControlUpdate(self, value): if self.ddcutil_Installed and self.waylandEnvironment and not self.ui.directControlBox.isChecked(): self.ui.directControlBox.setChecked(True) # Force wayland users to only use ddc + log.info(f"ddc mode toggled to: {self.ui.directControlBox.isChecked()}") + self.updatingMode = True if self.ui.directControlBox.isChecked(): @@ -957,4 +987,7 @@ def main(): sys.exit(APP.exec_()) if __name__ == "__main__": - main() + try: + main() + except: + log.fatal(traceback.format_exc()) diff --git a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py index a6f3b60..c5df6d6 100644 --- a/brightness-controller-linux/brightness_controller_linux/util/check_displays.py +++ b/brightness-controller-linux/brightness_controller_linux/util/check_displays.py @@ -20,6 +20,13 @@ import shlex import re +debug = False +try: + import brightness_controller_linux.util.log as log +except: + import log #used in testing + debug = True + def query_xrandr(): query = "xrandr --query" xrandr_output = subprocess.Popen(shlex.split(query), stdout=subprocess.PIPE, @@ -75,12 +82,16 @@ def x11_Monitor_Name_Extractor(xrandr_monitors : list): monitorName = extract_edid_name(currentEdid) if monitorName: monitorInfo.append(monitorName) + log.info(f"{monitor[0]} name is {monitorName}") else: print(f"Failed to get display name from monitor {monitor[0]}") + log.info(f"Failed to get display name from monitor {monitor[0]}") monitorInfo.append(monitorInfo[0]) displays.append(monitorInfo) + log.info(f"[x11] Monitor names extracted: [{displays}]") + return displays def wayland_Monitor_Info_Extractor(monitorInfo): @@ -120,7 +131,6 @@ def wayland_Monitor_Name_Extractor(): i = -1 for line in waylandInfo: - #print(line) i += 1 if line.startswith("interface: 'wl_output',") or i == len(waylandInfo) - 1: @@ -133,11 +143,13 @@ def wayland_Monitor_Name_Extractor(): currentDisplay = [] currentDisplay.append(line) + + log.info(f"[wayland] Monitor names extracted: {displays}") return displays -def extract_display_names(): +def extract_display_names(testInfo = None): xrandr_output = subprocess.check_output(["xrandr", "--verbose"]).decode().splitlines() displayVerboseInfo = [] @@ -167,13 +179,15 @@ def extract_display_names(): else: display.append(line) - displays = [] + log.info(f"Display info : {len(displayVerboseInfo)} displays.") + log.info(displayVerboseInfo) + log.info("") if os.getenv("XDG_SESSION_TYPE") == "wayland": - print("WAYLAND SESSION!") waylandDisplayNames = wayland_Monitor_Name_Extractor() if waylandDisplayNames == None: # fall back to old nameing + log.warning("Fell back to x11 monitor name extraction!") print("ERROR Falling back to x11 monitor name extractor! Names may not be extracted!") return x11_Monitor_Name_Extractor(displayVerboseInfo) @@ -182,13 +196,14 @@ def extract_display_names(): else: return x11_Monitor_Name_Extractor(displayVerboseInfo) - - return displays def match_ddc_order(monitorNames): - + detectedMonitors = subprocess.check_output(["ddcutil", "detect"]).decode().splitlines() + + log.info("ddcutil detect output:") + log.info(detectedMonitors) reorderedMonitors = [] @@ -200,17 +215,21 @@ def match_ddc_order(monitorNames): if "Model" in line: for monitor in monitorNames: modelName = line.split(":")[1].strip() + log.info(f"[ddcReorder] Model name output {modelName}") if modelName == '': if monitor[1].startswith('eDP'): reorderedMonitors.append(monitor) + log.info(f"[ddcReorder] added {monitor} from {modelName}") break if monitor[1] in modelName: reorderedMonitors.append(monitor) + log.info(f"[ddcReorder] added {monitor} from {modelName}") break if len(monitorNames) != len(reorderedMonitors): - print("ERROR IN MONITOR REORDERING please create an issue on the github with 'ddcutil detect' and xrandr --verbose outputs") + print(f"ERROR IN MONITOR REORDERING please create an issue on the github with your log file at ~/.config/brightness_controller/log.txt") + log.error(f"Failed attempting to reorder monitors, input: {monitorNames} attempted output: {reorderedMonitors}") return monitorNames # fall back to unreordered output return reorderedMonitors @@ -224,6 +243,9 @@ def detect_display_devices(): return extract_displays(query_xrandr()) -if __name__ == '__main__': +if debug: #print(detect_display_devices()) - print(len(extract_display_names())) + with open("test.txt", 'r') as file: + testInfo = file.readlines() + + extract_display_names(testInfo) From 8f9aabca7dc1d1c7540759d52ba4c12cac7ff0cc Mon Sep 17 00:00:00 2001 From: Soggy_Pancake <54160598+Soggy-Pancake@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:05:32 -0700 Subject: [PATCH 21/22] Add extra check for user missing i2c group and alert user with instructions to fix. --- brightness-controller-linux/brightness_controller_linux/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index c59b929..7fd66e0 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -112,7 +112,7 @@ def __init__(self, parent=None): self.ui.ddcutilsNotInstalled.setText("add i2c-dev to etc/modules-load.d") else: self.ddcutil_Installed = True - except: + except Exception: self.ddcutil_Installed = False log.info(f"DDCUtils installed: {self.ddcutil_Installed}") From 951d132c5af8401eb044bac9cdaf5293f40a16a8 Mon Sep 17 00:00:00 2001 From: Soggy_Pancake <54160598+Soggy-Pancake@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:07:04 -0700 Subject: [PATCH 22/22] Add extra check for user missing i2c group and alert user with instructions to fix. --- .../brightness_controller_linux/init.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/brightness-controller-linux/brightness_controller_linux/init.py b/brightness-controller-linux/brightness_controller_linux/init.py index c59b929..e3e6959 100755 --- a/brightness-controller-linux/brightness_controller_linux/init.py +++ b/brightness-controller-linux/brightness_controller_linux/init.py @@ -110,9 +110,20 @@ def __init__(self, parent=None): subprocess.check_output(["ddcutil", "environment"]), 'utf-8'): self.ui.ddcutilsNotInstalled.setText("add i2c-dev to etc/modules-load.d") + + envCheck = str(subprocess.check_output(["ddcutil", "environment"]), 'utf-8') + + if "not a member of group i2c" in envCheck: + + log.fatal(f"[DDCUtil] User is not part of i2c group! Run 'sudo usermod -G i2c -a {getpass.getuser()}'") + errorBox = QtWidgets.QMessageBox.critical(None, + "DDCUtil User config error!", + f"User is not part of i2c group! Run 'sudo usermod -G i2c -a {getpass.getuser()}'", + QtWidgets.QMessageBox.StandardButton.Close) + exit() else: self.ddcutil_Installed = True - except: + except Exception: self.ddcutil_Installed = False log.info(f"DDCUtils installed: {self.ddcutil_Installed}")