├── .gitignore ├── icc-brightness.desktop ├── Makefile ├── LICENSE ├── icc-brightness-gen.c ├── README.md └── icc-brightness /.gitignore: -------------------------------------------------------------------------------- 1 | icc-brightness-gen 2 | venv/ 3 | -------------------------------------------------------------------------------- /icc-brightness.desktop: -------------------------------------------------------------------------------- 1 | # Copyright 2017 - 2019, Udi Fuchs (original author) 2 | # Copyright 2021, Kahlil Hodgson (modifications) 3 | # SPDX-License-Identifier: MIT 4 | 5 | [Desktop Entry] 6 | Type=Application 7 | Name=ICC Brightness 8 | Comment=Control OLED display brightness by applying ICC color profiles. 9 | Exec=icc-brightness watch 10 | Terminal=false 11 | X-GNOME-Autostart-enabled=true 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 - 2019, Udi Fuchs (original author) 2 | # Copyright 2021, Kahlil Hodgson (modifications) 3 | # SPDX-License-Identifier: MIT 4 | 5 | BIN_PATH=/usr/local/bin/ 6 | AUTO_START_PATH=/usr/share/gnome/autostart/ 7 | 8 | all: icc-brightness-gen 9 | 10 | icc-brightness-gen: icc-brightness-gen.c 11 | $(CC) -W -Wall $(CFLAGS) $^ -l lcms2 $(LDFLAGS) -o $@ 12 | 13 | clean: 14 | rm -f icc-brightness-gen 15 | 16 | install: all 17 | mkdir -p $(DESTDIR)$(BIN_PATH) 18 | install -m 755 icc-brightness-gen $(DESTDIR)$(BIN_PATH) 19 | install -m 755 icc-brightness $(DESTDIR)$(BIN_PATH) 20 | mkdir -p $(DESTDIR)$(AUTO_START_PATH) 21 | install -m 644 icc-brightness.desktop $(DESTDIR)$(AUTO_START_PATH) 22 | 23 | uninstall: 24 | rm -f $(DESTDIR)$(BIN_PATH)icc-brightness-gen 25 | rm -f $(DESTDIR)$(BIN_PATH)icc-brightness 26 | rm -f $(DESTDIR)$(AUTO_START_PATH)icc-brightness.desktop 27 | 28 | local-install: BIN_PATH=~/.local/bin/ 29 | local-install: AUTO_START_PATH=~/.config/autostart/ 30 | local-install: install 31 | 32 | local-uninstall: BIN_PATH=~/.local/bin/ 33 | local-uninstall: AUTO_START_PATH=~/.config/autostart/ 34 | local-uninstall: uninstall 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 udifuchs (original author) 4 | Copyright (c) 2021 tartansandal (modifications) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /icc-brightness-gen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 - 2019, Udi Fuchs 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | cmsHPROFILE create_srgb_profile(double brightness) 13 | { 14 | cmsHPROFILE hsRGB = cmsCreate_sRGBProfile(); 15 | 16 | cmsMLU *mlu = cmsMLUalloc(NULL, 1); 17 | char description[20]; 18 | snprintf(description, 20, "Brightness %.2f", brightness); 19 | cmsMLUsetASCII(mlu, "en", "US", description); 20 | cmsWriteTag(hsRGB, cmsSigProfileDescriptionTag, mlu); 21 | cmsMLUfree(mlu); 22 | 23 | cmsContext context_id = cmsCreateContext(NULL, NULL); 24 | double curve[] = {1.0, brightness, 0.0}; // gamma, a, b for (a X +b)^gamma 25 | cmsToneCurve *tone_curve[3] = { 26 | cmsBuildParametricToneCurve(context_id, 2, curve), 27 | cmsBuildParametricToneCurve(context_id, 2, curve), 28 | cmsBuildParametricToneCurve(context_id, 2, curve), 29 | }; 30 | cmsWriteTag(hsRGB, cmsSigVcgtTag, tone_curve); 31 | cmsFreeToneCurve(tone_curve[0]); 32 | cmsFreeToneCurve(tone_curve[1]); 33 | cmsFreeToneCurve(tone_curve[2]); 34 | 35 | return hsRGB; 36 | } 37 | 38 | int main(int argc, const char *argv[]) { 39 | if (argc == 4 && 40 | strspn(argv[2], "0123456789") == strlen(argv[2]) && 41 | strspn(argv[3], "0123456789") == strlen(argv[3])) { 42 | 43 | const char *filename = argv[1]; 44 | int brightness = atoi(argv[2]); 45 | int max_brightness = atoi(argv[3]); 46 | double ratio = (double)brightness / max_brightness; 47 | cmsHPROFILE hsRGB = create_srgb_profile(ratio); 48 | cmsSaveProfileToFile(hsRGB, filename); 49 | return 0; 50 | } 51 | fprintf(stderr, "%s filename brightness max-brightness\n", argv[0]); 52 | fprintf(stderr, "brightness and max-brightness must be integers.\n"); 53 | return 1; 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ICC Brightness 2 | 3 | Control display brightness by applying ICC color profiles. 4 | 5 | *This is a maintained fork of the 6 | [icc-brightness](https://github.com/udifuchs/icc-brightness) project by Udi 7 | Fuchs.* 8 | 9 | ## Introduction 10 | 11 | This tool is a work-around for displays whose brightness control is not 12 | supported by the Linux kernel (e.g. OLED displays before version 5.12). It 13 | performs well on OLED displays, since these have very dark black point and their 14 | power consumption is relative to the brightness of the viewed content. 15 | It can also be used on an LCD display, but in that case you really want 16 | to control the brightness directly using the display backlight. 17 | 18 | Embedded laptop displays are the default target, however, you can use the 19 | `--target` option described below to target external displays instead. 20 | 21 | This tool was developed by @udifuchs for the Lenovo ThinkPad X1 Yoga OLED 22 | display. Later development by @tartansandal was performed on a Dell XPS 15 7590 23 | with an OLED display. Support for Razer Blade/Stealth and other non-Intel 24 | systems was suggested by @midnex. 25 | 26 | ## Build 27 | 28 | The tool consists of an Python script, `icc-brightness`, which is a wrapper 29 | around a compiled executable, `icc-brightness-gen`. To build 30 | `icc-brightness-gen` you will need to install the `lcms2` development package. 31 | 32 | For Ubuntu run 33 | 34 | ```console 35 | sudo apt install liblcms2-dev 36 | ``` 37 | 38 | For Fedora run 39 | 40 | ```console 41 | sudo dnf install lcms2-devel 42 | ``` 43 | 44 | To build the executable simply run 45 | 46 | ```console 47 | make 48 | ``` 49 | 50 | If all has gone well, then you should be able to run 51 | 52 | ```console 53 | $ ./icc-brightness-gen 54 | ./icc-brightness-gen filename brightness max-brightness 55 | brightness and max-brightness must be integers. 56 | ``` 57 | 58 | Given appropriate parameters, this command generates a new ICC color profile 59 | that has its gamma value set relative to the ration between 'brightness' and 60 | 'max-brightness'. 61 | 62 | ## Running 63 | 64 | The `icc-brightness` script is a convenience wrapper that manages the color 65 | profiles created by the `icc-brightness-gen` command. It has a number of modes 66 | and options: 67 | 68 | ```console 69 | $ ./icc-brightness 70 | usage: icc-brightness [-h] [--target TARGET] [--loglevel LOGLEVEL] 71 | [--logfile LOGFILE] 72 | {apply,watch,clean,list,set} ... 73 | 74 | Control OLED display brightness by applying ICC color profiles 75 | 76 | positional arguments: 77 | {apply,watch,clean,list,set} 78 | apply apply brightness from system setting 79 | watch continuously update to system setting 80 | clean remove all profiles generated by us 81 | list list visible device models 82 | set set brightness manually 83 | 84 | optional arguments: 85 | -h, --help show this help message and exit 86 | --target TARGET prefix of device models to target 87 | --loglevel LOGLEVEL set the logging level 88 | --logfile LOGFILE log to the specified file 89 | ``` 90 | 91 | The `apply` mode looks at the current system brightness settings and attempts to 92 | apply a color profile that matches the intended brightness, creating a new 93 | profile if necessary. 94 | 95 | The `watch` mode operates as a daemon, continuously watching for changes to the 96 | system brightness settings and updating the current color profile as 97 | appropriate. The installation instructions below use a auto-start file to 98 | launch this daemon when logging in under Gnome. 99 | 100 | The `set` mode applies a profile corresponding to the given `brightness` and 101 | `max-brightness` values, creating a new profile if necessary. For example, the 102 | following set the brightness level to approximately 67% of the maximum. 103 | 104 | ```console 105 | ./icc-brightness set 67 100 106 | ``` 107 | 108 | The `clean` mode removes all the profiles generated by this tool. By default, up 109 | to 20 profiles are generated to cover potential brightness values. If this tool 110 | is not working as expected, say after an upgrade, you can use this command to 111 | force the profiles to be regenerated with each new setting. 112 | 113 | ```console 114 | $ ./icc-brightness clean 115 | 2021-08-17 11:34:56,362 - INFO: Removing: ~/.local/share/icc/brightness_102_512.icc 116 | 2021-08-17 11:34:56,376 - INFO: Removing: ~/.local/share/icc/brightness_128_512.icc 117 | ... 118 | 2021-08-17 11:34:56,700 - INFO: Removing: ~/.local/share/icc/brightness_486_512.icc 119 | 2021-08-17 11:34:56,730 - INFO: Removing: ~/.local/share/icc/brightness_435_512.icc 120 | ``` 121 | 122 | The `list` mode lists the device models that the script can currently 'see' and 123 | create profiles for. This is provided as a helper for setting the `--target` 124 | option. 125 | 126 | ```console 127 | $ ./icc-brightness list 128 | XPS 15 7590 129 | LG Ultra HD 130 | ``` 131 | 132 | The `--target` option allows you to target a specific display rather than the 133 | default embedded one. This may be useful if the embedded display is not being 134 | reliably detected. It may also be useful if you want to target an external 135 | display. Note that we select the first display whose model name (as shown by 136 | `list`) starts with the value of this option, so this may not be useful if you 137 | have more than one external display with the same model name. (This is a fringe 138 | feature, so if you want it improved, please submit an issue). 139 | 140 | ```console 141 | ./icc-brightness --target XPS apply 142 | ``` 143 | 144 | The `--loglevel` ands `--logfile` options may be useful for tracking down bugs 145 | and submitting bug reports. 146 | 147 | ## Installation 148 | 149 | You can install this tool globally with: 150 | 151 | ```console 152 | $ sudo make install 153 | cp icc-brightness-gen /usr/local/bin/ 154 | cp icc-brightness /usr/local/bin/ 155 | cp icc-brightness.desktop /usr/share/gnome/autostart/ 156 | ``` 157 | 158 | This install includes an auto-start file to start a `watch` daemon when 159 | logging-in to a Gnome session. The daemon will start on your next login. You 160 | can change brightness using the brightness key or any other method that controls 161 | the display "backlight". 162 | 163 | To remove this global installation: 164 | 165 | ```console 166 | $ sudo make uninstall 167 | rm -f /usr/local/bin/icc-brightness-gen 168 | rm -f /usr/local/bin/icc-brightness 169 | rm -f /usr/share/gnome/autostart/icc-brightness.desktop 170 | ``` 171 | 172 | If you prefer to install this daemon as a local user: 173 | 174 | ```console 175 | $ make local-install 176 | mkdir -p ~/.local/bin/ 177 | install -m 755 icc-brightness-gen ~/.local/bin/ 178 | install -m 755 icc-brightness ~/.local/bin/ 179 | mkdir -p ~/.config/autostart/ 180 | install -m 644 icc-brightness.desktop ~/.config/autostart/ 181 | ``` 182 | 183 | And you can remove this local installation with: 184 | 185 | ```console 186 | $ make local-uninstall 187 | rm -f ~/.local/bin/icc-brightness-gen 188 | rm -f ~/.local/bin/icc-brightness 189 | rm -f ~/.config/autostart/icc-brightness.desktop 190 | ``` 191 | 192 | ## Troubleshooting 193 | 194 | If things are not working as you expect, you might want to check the system 195 | logs. On systems that use `systemd` journal you can do this with the following 196 | command: 197 | 198 | ```console 199 | journalctl --user --identifier icc-brightness.desktop --boot 200 | ``` 201 | 202 | Note that log lines containing 203 | 204 | ```text 205 | WARNING: No matching device found yet 206 | ``` 207 | 208 | immediately after logging in are to be expected. They are due to the 209 | `icc-brightness` autostart application being started before the `colord` service 210 | is ready. If these messages persist, you may have and issue with the `colord` 211 | service. 212 | 213 | ## Thanks 214 | 215 | Huge thanks to Udi Fuchs (@udifuchs) for creating the initial project and making 216 | my shiny new laptop usable. 217 | 218 | Thanks to Llane Rost (@midnex) for code clean up and non-Intel support 219 | suggestions :smile: 220 | -------------------------------------------------------------------------------- /icc-brightness: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright 2017 - 2019, Udi Fuchs (original author) 4 | # Copyright 2021, Kahlil Hodgson (modifications) 5 | # SPDX-License-Identifier: MIT 6 | 7 | """Control display brightness by applying ICC color profiles. 8 | 9 | icc-brightness set brightness max-brightness - set brightness manually 10 | icc-brightness apply - apply brightness from system setting 11 | icc-brightness watch - continuously update to system setting 12 | icc-brightness clean - remove all profiles generated by us 13 | icc-brightness list - list device models we can see 14 | """ 15 | 16 | import argparse 17 | import fcntl 18 | import logging 19 | import os 20 | import re 21 | import signal 22 | import subprocess 23 | import sys 24 | import threading 25 | import time 26 | import unicodedata 27 | from types import SimpleNamespace 28 | 29 | TEMP_FOLDER = "/tmp" 30 | BACKLIGHT_PATH = "/sys/class/backlight/intel_backlight" 31 | 32 | # Try some known working locations for the backlight device 33 | if os.path.exists("/sys/class/backlight/intel_backlight"): 34 | BACKLIGHT_PATH = "/sys/class/backlight/intel_backlight" 35 | elif os.path.exists("/sys/class/backlight/acpi_video0"): 36 | BACKLIGHT_PATH = "/sys/class/backlight/acpi_video0" 37 | else: 38 | logging.error("Could not find sys path to backlight device") 39 | sys.exit(1) 40 | 41 | BRIGHTNESS_PATH = os.path.join(BACKLIGHT_PATH, "brightness") 42 | MAX_BRIGHTNESS_PATH = os.path.join(BACKLIGHT_PATH, "max_brightness") 43 | 44 | CWD = os.path.dirname(__file__) 45 | ICC_BRIGHTNESS_GEN = os.path.join(CWD, "icc-brightness-gen") 46 | 47 | LOCK = threading.Lock() 48 | 49 | target = SimpleNamespace(device=None, slug="") 50 | 51 | 52 | def clean(): 53 | """Find all profile generated by us and remove them.""" 54 | out = subprocess.check_output(["colormgr", "get-profiles"]) 55 | 56 | profile_path = None 57 | filename = None 58 | for line in out.decode("utf8").split("\n"): 59 | if line.startswith("Object Path:"): 60 | profile_path = line.split(":")[1].lstrip() 61 | continue 62 | if line.startswith("Filename:"): 63 | filename = line.split(":")[1].lstrip() 64 | if filename.find(f"icc/{target.slug}brightness_") < 0: 65 | continue 66 | logging.info("Removing: %s", filename) 67 | subprocess.run(["colormgr", "delete-profile", profile_path], check=True) 68 | subprocess.run(["rm", filename], check=True) 69 | 70 | 71 | def list_devices(): 72 | """List all device models visible to us""" 73 | out = subprocess.check_output(["colormgr", "get-devices-by-kind", "display"]) 74 | for line in out.decode("utf8").split("\n"): 75 | if line.startswith("Model:"): 76 | print(line.split(":")[1].lstrip()) 77 | 78 | 79 | def find_profile_path(filename): 80 | """ 81 | Query colormgr for a profile via its filename 82 | 83 | Returns the corresponding object path. 84 | """ 85 | try: 86 | out = subprocess.check_output( 87 | ["colormgr", "find-profile-by-filename", filename] 88 | ) 89 | except subprocess.CalledProcessError: 90 | return None 91 | 92 | object_path = None 93 | for line in out.decode("utf8").split("\n"): 94 | if line.startswith("Object Path:"): 95 | object_path = line.split(":")[1].lstrip() 96 | break 97 | return object_path 98 | 99 | 100 | def find_device_path(): 101 | """ 102 | Query colormgr for the id of the target device 103 | 104 | Returns the corresponding object path. 105 | """ 106 | out = subprocess.check_output(["colormgr", "get-devices-by-kind", "display"]) 107 | 108 | # If there is more than one device being managed, there will be multiple data blocks 109 | # separated by blank lines. In each block the 'Object Path' line will always occur 110 | # before the 'Model' or 'Embedded' line, so we repeatedly set the object_path and 111 | # only break when we find an appropriate match. If we are not targeting a specific 112 | # device, we just pick the first embedded device we find (i.e. the laptops screen). 113 | 114 | object_path = None 115 | for line in out.decode("utf8").split("\n"): 116 | if line.startswith("Object Path:"): 117 | object_path = line.split(":")[1].lstrip() 118 | elif target.device is None: 119 | if line.startswith("Embedded:"): 120 | embedded = line.split(":")[1].lstrip() 121 | if embedded == "Yes": 122 | break 123 | else: 124 | if line.startswith("Model:"): 125 | model_name = line.split(":")[1].lstrip() 126 | if model_name.startswith(target.device): 127 | break 128 | 129 | return object_path 130 | 131 | 132 | def rounded(brightness, max_brightness, factor=0.05): 133 | """ 134 | return BRIGHTNESS rounded to FACTOR increments of MAX_BRIGHTNESS 135 | 136 | This avoids strange brightness settings like 0.61, due to float rounding anomalies. 137 | """ 138 | return int( 139 | (((int((brightness / max_brightness) * 100)) // (factor * 100)) * factor) 140 | * max_brightness 141 | ) 142 | 143 | 144 | def icc_brightness(brightness, max_brightness): 145 | """Set brightness using an appropriate color profile""" 146 | 147 | logging.debug("Searching for device") 148 | device_path = find_device_path() 149 | if device_path is None: 150 | # This could happen during startup if the colord is not running yet 151 | logging.warning("No matching device found yet") 152 | return 153 | 154 | brightness = rounded(brightness, max_brightness) 155 | logging.info("Setting brightness ratio to %.2f", brightness / max_brightness) 156 | 157 | icc_filename = "%sbrightness_%d_%d.icc" % (target.slug, brightness, max_brightness) 158 | logging.debug("Searching for profile %s", icc_filename) 159 | profile_path = find_profile_path(icc_filename) 160 | 161 | if profile_path is None: 162 | logging.debug("Create new profile %s", icc_filename) 163 | icc_filepath = os.path.join(TEMP_FOLDER, icc_filename) 164 | try: 165 | subprocess.run( 166 | [ 167 | ICC_BRIGHTNESS_GEN, 168 | icc_filepath, 169 | str(brightness), 170 | str(max_brightness), 171 | ], 172 | check=True, 173 | ) 174 | except subprocess.CalledProcessError as ex: 175 | # We should never get here ... but log the reason if we do 176 | logging.error("Failed to create new profile: %s", ex.stdout) 177 | 178 | logging.debug("Import new profile %s", icc_filepath) 179 | try: 180 | subprocess.run(["colormgr", "import-profile", icc_filepath], check=True) 181 | except subprocess.CalledProcessError as ex: 182 | # We should never get here ... but log the reason if we do 183 | logging.error("Failed to import new profile: %s", ex.stdout) 184 | 185 | logging.debug("Retrieve new profile %s", icc_filename) 186 | profile_path = find_profile_path(icc_filename) 187 | 188 | logging.debug("apply new profile to device %s", device_path) 189 | try: 190 | subprocess.run( 191 | ["colormgr", "device-add-profile", device_path, profile_path], 192 | check=True, 193 | ) 194 | except subprocess.CalledProcessError as ex: 195 | # We should never get here ... but log the reason if we do 196 | logging.error("Failed to add profile: %s", ex.stdout) 197 | 198 | logging.debug("Make profile default for device %s", device_path) 199 | try: 200 | subprocess.run( 201 | ["colormgr", "device-make-profile-default", device_path, profile_path], 202 | check=True, 203 | ) 204 | except subprocess.CalledProcessError as ex: 205 | # We should never get here ... but log the reason if we do 206 | logging.error("Failed to add profile: %s", ex.stdout) 207 | 208 | 209 | def icc_brightness_apply(): 210 | """Apply a color profile corresponding to the sytsem brightness settings""" 211 | with open(BRIGHTNESS_PATH) as infile: 212 | brightness = int(infile.readline()) 213 | with open(MAX_BRIGHTNESS_PATH) as infile: 214 | max_brightness = int(infile.readline()) 215 | icc_brightness(brightness, max_brightness) 216 | 217 | 218 | def handler(signum, frame): 219 | """Respond to changes to the system brightness settings""" 220 | logging.debug("Running handler for signum %d and frame %s", signum, frame) 221 | if LOCK.acquire(blocking=False): 222 | try: 223 | icc_brightness_apply() 224 | except subprocess.CalledProcessError as ex: 225 | logging.exception("Error during call to icc_brightness") 226 | logging.error(ex.stdout) 227 | except BaseException: 228 | logging.exception("Error during call to icc_brightness") 229 | finally: 230 | LOCK.release() 231 | 232 | 233 | # Borrowed from Django 234 | def slugify(value): 235 | """ 236 | Converts to lowercase, removes non-word characters (alphanumerics and 237 | underscores) and converts spaces to hyphens. Also strips leading and 238 | trailing whitespace. 239 | """ 240 | value = ( 241 | unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii") 242 | ) 243 | value = re.sub(r"[^\w\s-]", "", value).strip().lower() 244 | return re.sub(r"[-\s]+", "-", value) 245 | 246 | 247 | def main(): 248 | log_format = "%(asctime)s - %(levelname)s: %(message)s" 249 | logging.basicConfig(level=logging.INFO, format=log_format) 250 | logger = logging.getLogger() 251 | 252 | parser = argparse.ArgumentParser( 253 | description="Control display brightness by applying ICC color profiles" 254 | ) 255 | 256 | subparsers = parser.add_subparsers(dest="action") 257 | parser.add_argument("--target", help="prefix of device models to target") 258 | parser.add_argument("--loglevel", help="set the logging level") 259 | parser.add_argument("--logfile", help="log to the specified file") 260 | subparsers.add_parser("apply", help="apply brightness from system setting") 261 | subparsers.add_parser("watch", help="continuously update to system setting") 262 | subparsers.add_parser("clean", help="remove all profiles generated by us") 263 | subparsers.add_parser("list", help="list visible device models") 264 | set_parser = subparsers.add_parser("set", help="set brightness manually") 265 | set_parser.add_argument("brightness", type=int) 266 | set_parser.add_argument("max_brightness", type=int) 267 | 268 | args = parser.parse_args() 269 | 270 | if args.target is not None: 271 | target.device = args.target 272 | target.slug = slugify(target.device) + "-" 273 | 274 | if args.loglevel is not None: 275 | loglevel = args.loglevel.upper() 276 | numeric_level = getattr(logging, loglevel, None) 277 | if not isinstance(numeric_level, int): 278 | raise ValueError("Invalid log level: %s" % args.loglevel) 279 | logger.setLevel(numeric_level) 280 | logger.info("Setting log level: %s", loglevel) 281 | 282 | if args.logfile is not None: 283 | logfile = args.logfile 284 | fh = logging.FileHandler(logfile, mode="a") 285 | fh.setFormatter(logging.Formatter(log_format)) 286 | logger.addHandler(fh) 287 | logger.info("Logging to file: %s", logfile) 288 | 289 | if args.action == "clean": 290 | clean() 291 | 292 | elif args.action == "apply": 293 | icc_brightness_apply() 294 | 295 | elif args.action == "watch": 296 | try: 297 | icc_brightness_apply() 298 | except BaseException: 299 | logging.exception("device-make-profile-default") 300 | 301 | # Watch for changes to the system brightness settings 302 | signal.signal(signal.SIGIO, handler) 303 | fd = os.open(BACKLIGHT_PATH, os.O_RDONLY) 304 | fcntl.fcntl(fd, fcntl.F_SETSIG, 0) 305 | fcntl.fcntl(fd, fcntl.F_NOTIFY, fcntl.DN_MODIFY | fcntl.DN_MULTISHOT) 306 | 307 | while True: 308 | time.sleep(1000000) 309 | 310 | elif args.action == "list": 311 | list_devices() 312 | elif args.action == "set": 313 | icc_brightness(args.brightness, args.max_brightness) 314 | else: 315 | parser.print_help() 316 | 317 | 318 | if __name__ == "__main__": 319 | main() 320 | --------------------------------------------------------------------------------