├── requirements.txt ├── setup.py ├── LICENSE ├── CHANGELOG.md ├── screenconfig ├── screenconfig.toml └── __init__.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | toml 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import setuptools 5 | import screenconfig 6 | 7 | with open("README.md", "r") as fh: 8 | long_description = fh.read() 9 | 10 | setuptools.setup( 11 | name="screenconfig", 12 | version=screenconfig.version, 13 | author="Jan Christoph Ebersbach", 14 | author_email="jceb@e-jc.de", 15 | description="Tool automate the configuration of connected monitors", 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url="https://github.com/jceb/screensconfig", 19 | packages=setuptools.find_packages(), 20 | install_requires=['toml'], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'screenconfig = screenconfig:main', 24 | ] 25 | }, 26 | package_data={ 27 | '': ['screenconfig.toml'], 28 | }, 29 | classifiers=[ 30 | "Programming Language :: Python :: 3", 31 | "License :: OSI Approved :: MIT License", 32 | "Environment :: X11 Applications", 33 | "Operating System :: POSIX", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jan Christoph Ebersbach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - Outline the motivation for screenconfig 10 | - Add configuration options exec_on_connect and exec_on_disconnect that 11 | take a list of commands that are executed at the various events 12 | - Add more details on how to install screenconfig 13 | - Add configuration option xrandr_args that accepts arbitrary parameters 14 | for xrandr 15 | - Add work setup to configuration 16 | - Add description field for monitors 17 | - Add setup.py and proper python packaging 18 | 19 | ### Changed 20 | - Make edid and output optional for get_mon_configuration_for_edid 21 | - Move script and configuration to screenconfig package 22 | - Improve variable names for finding the reference output 23 | - Update configuration 24 | - Change file format to TOML 25 | - Require position parameters to be real xrandr arguments, e.g. 26 | "--left-of" instead of "left-of" 27 | - Rename field edid to edids 28 | - Change indentation to spaces 29 | 30 | ### Removed 31 | - Remove rotate option 32 | - Remove YAML configuration 33 | - Remove support for YAML file format 34 | - Remove debug output 35 | 36 | ### Fixed 37 | - Add missing parameter event to get_commands 38 | - Set missing entrypoint for screenconfig 39 | - Fix broken import in setup.py 40 | -------------------------------------------------------------------------------- /screenconfig/screenconfig.toml: -------------------------------------------------------------------------------- 1 | title = "Screen configuration" 2 | 3 | [monitors] 4 | 5 | # The name of the top-level keys you can choose to your liking but they have to 6 | # start with "monitors". 7 | [monitors.laptop] 8 | description = "Laptop" 9 | # EDID is an identifier that is unique to each screen. If you call 10 | # `srandrd list` it will provide an overview of the connected screens with their 11 | # EDIDs 12 | edids = ["E430044600000000", "7038000000000000"] 13 | # The path to a wallpaper can be specified for each screen. The wallpaper is 14 | # set through the tool "feh" 15 | wallpaper = "~/wallpaper1920x1080.png" 16 | # List of commands that are executed when a monitor is connected or disconnected 17 | # - All SRANDRD_* environment variables of the event are available to the 18 | # command and can be used by it. If the command is prefixed with 19 | # ["sh", "-c", ...] the variables can be used directly in the command's 20 | # arguments. 21 | # - The event parameters can also be used in the command's arguments with the 22 | # python string formatting syntax: {.ATTRIBUTE}. The following attributes are 23 | # available: 24 | # - event: either connected or disconnected 25 | # - output: name of the xrandr output that triggered the event 26 | # - edid: EDID of the monitor 27 | # - screenid: XINERAMA screen id 28 | # exec_on_connect = [ 29 | # ["touch", "/tmp/test/file"], 30 | # ["sh", "-x", "-c", "touch /tmp/test/$SRANDRD_EVENT"], 31 | # Don't specify exec_on_disconnect here, it will never be triggered 32 | # because EDID is not available! 33 | # ["touch", "/tmp/test/{.output}"] 34 | # ] 35 | 36 | [monitors.iiyama] 37 | description = "Iiyama" 38 | edids = ["CD2646B40000306A"] 39 | wallpaper = "~/wallpaper1280x1024.png" 40 | # The position of this screen relative to another screen, in this case 41 | # relative to the screen "laptop". Position is one of 42 | # --right-of, --left-of, --above, --below or --same-as 43 | # (see xrandr(1) for more information) 44 | position = ["--left-of", "laptop"] 45 | 46 | [monitors.benq] 47 | description = "Benq BL1152" 48 | edids = ["D109801B00005445"] 49 | position = ["--left-of", "laptop"] 50 | wallpaper = "~/wallpaper2560x1440.png" 51 | 52 | # It is possible to provide specific configuration details depending on the 53 | # output that the monitor is connected to. The string ".outputs." followed 54 | # name of the output (run xrandr to get the name) is appended to the monitor 55 | # configuration section. All configurations options can be used in this 56 | # subsection 57 | [monitors.benq.outputs.DP-1] 58 | 59 | [monitors.benq.outputs.DP1-1] 60 | 61 | [monitors.benq.outputs.DP-1-1] 62 | 63 | [monitors.benq.outputs.HDMI1] 64 | 65 | [monitors.benq.outputs.HDMI2] 66 | # If you have multiple monitors with the same EDID that are connected at the 67 | # same time add the name of the output as the last element of the position to 68 | # set this monitor relative to it 69 | position = ["--left-of", "benq", "HDMI1"] 70 | xrandr_args = ["--rotate", "left"] 71 | wallpaper = "~/wallpaper1440x2560.png" 72 | 73 | [monitors.benq.outputs.DP1-2] 74 | position = ["--left-of", "benq", "DP1-1"] 75 | xrandr_args = ["--rotate", "left"] 76 | wallpaper = "~/wallpaper1440x2560.png" 77 | 78 | [monitors.benq.outputs.DP-1-2] 79 | position = ["--left-of", "benq", "DP-1-1"] 80 | xrandr_args = ["--rotate", "left"] 81 | wallpaper = "~/wallpaper1440x2560.png" 82 | 83 | [monitors.samsung] 84 | description = "Samsung" 85 | edids = ["2D4C03E800000000", "2D4C03E654573236"] 86 | position = ["--left-of", "laptop"] 87 | wallpaper = "~/wallpaper1920x1200.png" 88 | 89 | [monitors.dell] 90 | description = "Dell" 91 | edids = ["AC1040613239334C"] 92 | wallpaper = "~/wallpaper1920x1080.png" 93 | 94 | [monitors.univentionbeamer] 95 | description = "Acer" 96 | edids = ["7204870100001893"] 97 | # Resolution of this screen, either "auto" or a concrete resolution like 98 | # "800x600" 99 | resolution = "1280x1024" 100 | wallpaper = "~/wallpaper1280x1024.png" 101 | 102 | # The default configuration that's applied if no other configuration matches. A 103 | # different section can be selected by setting the environment variable 104 | # SCREENCONFIG_DEFAULT to the name of a different section. This might be 105 | # useful to use the same configuration file for multiple computers. 106 | # 107 | # E.g. put the following in your work computer's bashrc 108 | # export SCREENCONFIG_DEFAULT="monitors.default-work" 109 | [monitors.default] 110 | position = ["--right-of", "laptop"] 111 | resolution = "auto" 112 | wallpaper = "~/wallpaper1920x1200.png" 113 | # exec_on_connect = [ 114 | # ["touch", "/tmp/test/file"], 115 | # ["sh", "-x", "-c", "touch /tmp/test/$SRANDRD_EVENT"], 116 | # ["touch", "/tmp/test/{.output}"] 117 | # ] 118 | # The disconnect commands will only be triggered from the default 119 | # configuration because EDID is not available! 120 | # exec_on_disconnect = [ 121 | # ] 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # screenconfig 2 | 3 | `screenconfig` is a tool automate the configuration of connected 4 | monitors. 5 | 6 | Why is that useful? Suppose you have a laptop that you use at work, at 7 | home, and on the road and you frequently connect it to various 8 | monitors. The monitors might be in different positions relative to your 9 | laptop. This will cause you to repeatedly run `xrandr` and potentially 10 | other commands for setting your wallpaper also. 11 | `screenconfig` set out to fix this by providing a simple file format 12 | that stores your preferences and an automated integration with `xrandr` 13 | that will do the hard work of executing the necessary commands to 14 | adapt the monitors to your preferences. 15 | 16 | ## Installation 17 | 18 | * Install [`feh`](https://feh.finalrewind.org/) 19 | * Install [`srandrd`](https://github.com/jceb/srandrd) 20 | * Install [`screenconfig`](https://github.com/jceb/screenconfig), e.g. 21 | run `./setup.py install --user` 22 | * Create a personal configuration in 23 | `~/.config/screenconfig/screenconfig.toml`. Here is an 24 | [example](screenconfig/screenconfig.toml). 25 | 26 | ## Usage 27 | 28 | `screenconfig` is used as a command for the simple randr daemon 29 | [`srandrd`](https://github.com/jceb/srandrd). It shall be started 30 | through `srandrd` as follows: 31 | 32 | srandrd -e screenconfig 33 | 34 | For testing purposes it's helpful to run `srandrd` in foreground and in 35 | verbose mode: 36 | 37 | srandrd -v -n -e screenconfig 38 | 39 | ## Configuration 40 | 41 | The configuration is stored in file 42 | `~/.config/screenconfig/screenconfig.toml`. My [personal 43 | configuration](screenconfig/screenconfig.toml) should provide a good starting 44 | point: 45 | 46 | title = "Screen configuration" 47 | 48 | [monitors] 49 | 50 | # The name of the top-level keys you can choose to your liking but they have to 51 | # start with "monitors". 52 | [monitors.laptop] 53 | description = "Laptop" 54 | # EDID is an identifier that is unique to each screen. If you call 55 | # `srandrd list` it will provide an overview of the connected screens with their 56 | # EDIDs 57 | edids = ["E430044600000000", "7038000000000000"] 58 | # The path to a wallpaper can be specified for each screen. The wallpaper is 59 | # set through the tool "feh" 60 | wallpaper = "~/wallpaper1920x1080.png" 61 | # List of commands that are executed when a monitor is connected or disconnected 62 | # - All SRANDRD_* environment variables of the event are available to the 63 | # command and can be used by it. If the command is prefixed with 64 | # ["sh", "-c", ...] the variables can be used directly in the command's 65 | # arguments. 66 | # - The event parameters can also be used in the command's arguments with the 67 | # python string formatting syntax: {.ATTRIBUTE}. The following attributes are 68 | # available: 69 | # - event: either connected or disconnected 70 | # - output: name of the xrandr output that triggered the event 71 | # - edid: EDID of the monitor 72 | # - screenid: XINERAMA screen id 73 | # exec_on_connect = [ 74 | # ["touch", "/tmp/test/file"], 75 | # ["sh", "-x", "-c", "touch /tmp/test/$SRANDRD_EVENT"], 76 | # ["touch", "/tmp/test/{.output}"] 77 | # ] 78 | # Don't specify exec_on_disconnect here, it will never be triggered 79 | # because EDID is not available! 80 | # exec_on_disconnect = [ 81 | # ] 82 | 83 | [monitors.iiyama] 84 | description = "Iiyama" 85 | edids = ["CD2646B40000306A"] 86 | wallpaper = "~/wallpaper1280x1024.png" 87 | # The position of this screen relative to another screen, in this case 88 | # relative to the screen "laptop". Position is one of 89 | # --right-of, --left-of, --above, --below or --same-as 90 | # (see xrandr(1) for more information) 91 | position = ["--left-of", "laptop"] 92 | 93 | [monitors.benq] 94 | description = "Benq BL1152" 95 | edids = ["D109801B00005445"] 96 | position = ["--left-of", "laptop"] 97 | wallpaper = "~/wallpaper2560x1440.png" 98 | 99 | # It is possible to provide specific configuration details depending on the 100 | # output that the monitor is connected to. The string ".outputs." followed 101 | # name of the output (run xrandr to get the name) is appended to the monitor 102 | # configuration section. All configurations options can be used in this 103 | # subsection 104 | [monitors.benq.outputs.DP-1] 105 | 106 | [monitors.benq.outputs.DP1-1] 107 | 108 | [monitors.benq.outputs.DP-1-1] 109 | 110 | [monitors.benq.outputs.HDMI1] 111 | 112 | [monitors.benq.outputs.HDMI2] 113 | # If you have multiple monitors with the same EDID that are connected at the 114 | # same time add the name of the output as the last element of the position to 115 | # set this monitor relative to it 116 | position = ["--left-of", "benq", "HDMI1"] 117 | xrandr_args = ["--rotate", "left"] 118 | wallpaper = "~/wallpaper1440x2560.png" 119 | 120 | [monitors.benq.outputs.DP1-2] 121 | position = ["--left-of", "benq", "DP1-1"] 122 | xrandr_args = ["--rotate", "left"] 123 | wallpaper = "~/wallpaper1440x2560.png" 124 | 125 | [monitors.benq.outputs.DP-1-2] 126 | position = ["--left-of", "benq", "DP-1-1"] 127 | xrandr_args = ["--rotate", "left"] 128 | wallpaper = "~/wallpaper1440x2560.png" 129 | 130 | [monitors.samsung] 131 | description = "Samsung" 132 | edids = ["2D4C03E800000000", "2D4C03E654573236"] 133 | position = ["--left-of", "laptop"] 134 | wallpaper = "~/wallpaper1920x1200.png" 135 | 136 | [monitors.dell] 137 | description = "Dell" 138 | edids = ["AC1040613239334C"] 139 | wallpaper = "~/wallpaper1920x1080.png" 140 | 141 | [monitors.univentionbeamer] 142 | description = "Acer" 143 | edids = ["7204870100001893"] 144 | # Resolution of this screen, either "auto" or a concrete resolution like 145 | # "800x600" 146 | resolution = "1280x1024" 147 | wallpaper = "~/wallpaper1280x1024.png" 148 | 149 | # The default configuration that's applied if no other configuration matches. A 150 | # different section can be selected by setting the environment variable 151 | # SCREENCONFIG_DEFAULT to the name of a different section. This might be 152 | # useful to use the same configuration file for multiple computers. 153 | # 154 | # E.g. put the following in your work computer's bashrc 155 | # export SCREENCONFIG_DEFAULT="monitors.default-work" 156 | [monitors.default] 157 | position = ["--right-of", "laptop"] 158 | resolution = "auto" 159 | wallpaper = "~/wallpaper1920x1200.png" 160 | # exec_on_connect = [ 161 | # ["touch", "/tmp/test/file"], 162 | # ["sh", "-x", "-c", "touch /tmp/test/$SRANDRD_EVENT"], 163 | # ["touch", "/tmp/test/{.output}"] 164 | # ] 165 | # The disconnect commands will only be triggered from the default 166 | # configuration because EDID is not available! 167 | # exec_on_disconnect = [ 168 | # ] 169 | 170 | ## Related projects 171 | 172 | - [`autorandr`](https://github.com/phillipberndt/autorandr) Auto-detect the connected display hardware and load the appropriate X11 setup using xrandr 173 | - [`srandrd`](https://github.com/jceb/srandrd) simple notification daemon of screen events 174 | -------------------------------------------------------------------------------- /screenconfig/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # See LICENSE for copyright and license details 5 | 6 | import toml 7 | import os 8 | import sys 9 | import subprocess 10 | import functools 11 | 12 | version = "2018.10" 13 | 14 | CONNECTED = "connected" 15 | DISCONNECTED = "disconnected" 16 | 17 | 18 | CONFIG_FILE = os.path.expandvars( 19 | os.path.join( 20 | os.environ.get("XDG_CONFIG_HOME", os.path.join("$HOME", ".config")), 21 | "screenconfig", 22 | "screenconfig.toml", 23 | ) 24 | ) 25 | 26 | DEFAULT_MONITOR = "default" 27 | 28 | 29 | class Event: 30 | def __init__(self, output=None, event=None, edid=None, screenid=None): 31 | self.event = event 32 | self.output = output 33 | self.edid = edid 34 | self.screenid = screenid 35 | 36 | 37 | class MonitorConfiguration: 38 | def __init__(self, monitor_config): 39 | """ 40 | @param :monitor_config: monitor configuration 41 | """ 42 | self.monitor_config = monitor_config 43 | 44 | def __str__(self): 45 | return str(self.monitor_config) 46 | 47 | def __getattr__(self, name): 48 | if self.monitor_config: 49 | return self.monitor_config.get(name, None) 50 | 51 | def get(self, name, default=None): 52 | if name in self.monitor_config: 53 | return self.monitor_config[name] 54 | else: 55 | return default 56 | 57 | 58 | class GlobalConfiguration: 59 | def __init__(self, outputs, monitors_config, default_monitor): 60 | """ 61 | @param :outputs: dict of outputs and edids of the connected monitors 62 | @param :monitors_config: monitor configuration 63 | @param :default_monitor: default monitor in the configuration 64 | """ 65 | self.outputs = outputs 66 | self.monitors_config = monitors_config 67 | self.default_monitor = default_monitor 68 | 69 | def __str__(self): 70 | return str(self.outputs, self.monitor_config, self.default_monitor) 71 | 72 | def __getattr__(self, name): 73 | if self.monitors: 74 | return self.monitors.get(name, None) 75 | 76 | @property 77 | def monitors(self): 78 | return self.monitors_config["monitors"] 79 | 80 | @property 81 | def default(self): 82 | if self.monitors: 83 | return self.monitors[self.default_monitor] 84 | 85 | 86 | def first(l): 87 | if l is not None: 88 | for e in l: 89 | return e 90 | 91 | 92 | def compose(*funcs): 93 | """Return a new function s.t. 94 | compose(f,g,...)(x) == f(g(...(x)))""" 95 | 96 | def inner(*args): 97 | result = args 98 | for f in reversed(funcs): 99 | if type(result) in (list, tuple): 100 | result = f(*result) 101 | else: 102 | result = f(result) 103 | return result 104 | 105 | return inner 106 | 107 | 108 | def jam(*funcs): 109 | """Return a new function s.t. 110 | jam(f,g,...)(x) == g(x), f(x)""" 111 | 112 | def inner(*args): 113 | for f in reversed(funcs): 114 | f(*args) 115 | 116 | return inner 117 | 118 | 119 | def _execute(f, cmd, *args, **kwargs): 120 | # print("_execute: ", " ".join(cmd)) 121 | return f(cmd, *args, **kwargs) 122 | 123 | 124 | def execute(cmd, *args, **kwargs): 125 | return _execute( 126 | subprocess.call, 127 | cmd, 128 | *args, 129 | **kwargs, 130 | env={ 131 | # apparently required to make xrandr succeed https://github.com/libsdl-org/SDL/issues/4561#issuecomment-895552640 132 | "SDL_VIDEO_X11_XRANDR": "0", 133 | **os.environ, 134 | } 135 | ) 136 | 137 | 138 | def check_output(cmd, *args, **kwargs): 139 | return _execute(subprocess.check_output, cmd, *args, **kwargs) 140 | 141 | 142 | def get_connected_outputs(): 143 | """ 144 | @returns dict {output: edid} 145 | """ 146 | return { 147 | output_edid[0]: output_edid[1] if len(output_edid) == 2 else None 148 | for line in check_output(["srandrd", "list"]) 149 | .decode("utf-8") 150 | .strip() 151 | .split(os.linesep) 152 | for output_edid in (line.split(" ", 1),) 153 | } 154 | 155 | 156 | def merge_monitor_config(default_config, monitor_config, output=None): 157 | merged_config = default_config.copy() 158 | if monitor_config: 159 | merged_config.update(monitor_config) 160 | 161 | if output: 162 | merged_config.update(monitor_config.get("outputs", {}).get(output, {})) 163 | 164 | if "outputs" in merged_config: 165 | del merged_config["outputs"] 166 | return merged_config 167 | 168 | 169 | def get_monitor_configuration(config, monitor, output=None): 170 | assert config and monitor 171 | 172 | return MonitorConfiguration( 173 | merge_monitor_config( 174 | config.default, 175 | first( 176 | map( 177 | config.monitors.get, 178 | filter(lambda m: m == monitor, config.monitors.keys()), 179 | ) 180 | ), 181 | output, 182 | ) 183 | ) 184 | 185 | 186 | def get_mon_configuration_for_edid(config, edid=None, output=None): 187 | if not config: 188 | return None 189 | 190 | # find the right configuration, might be multiple if the configuration 191 | # specifies the edid multiple times 192 | monitor_config = first( 193 | filter( 194 | None, 195 | [ 196 | c 197 | if edid in c.get("edids", []) 198 | or edid in c.get("outputs", {}).get(output, {}).get("edids", []) 199 | else None 200 | for c in config.monitors.values() 201 | ], 202 | ) 203 | ) 204 | 205 | if not monitor_config: 206 | return MonitorConfiguration(config.default) 207 | 208 | # use the first configuration merge configuration 209 | return MonitorConfiguration( 210 | merge_monitor_config(config.default, monitor_config, output) 211 | ) 212 | 213 | 214 | def get_wallpaper(monitor_config): 215 | if monitor_config and monitor_config.wallpaper: 216 | return os.path.expandvars(os.path.expanduser(monitor_config.wallpaper)) 217 | 218 | 219 | def set_wallpapers(config, event, commands): 220 | wallpapers = list( 221 | filter( 222 | None, 223 | map( 224 | lambda output, edid: compose( 225 | get_wallpaper, get_mon_configuration_for_edid 226 | )(config, edid, output), 227 | config.outputs.keys(), 228 | config.outputs.values(), 229 | ), 230 | ) 231 | ) 232 | 233 | if wallpapers: 234 | commands.append(["feh", "--bg-fill", "--no-fehbg"] + wallpapers) 235 | return config, event, commands 236 | 237 | 238 | def find_reference_output(config, reference_monitor, _reference_output=None): 239 | if _reference_output: 240 | return _reference_output 241 | 242 | reference_monitor_config = get_monitor_configuration( 243 | config, reference_monitor, _reference_output 244 | ) 245 | reference_output = first( 246 | filter( 247 | None, 248 | map( 249 | lambda output, edid: output 250 | if reference_monitor_config.edids is not None 251 | and edid in reference_monitor_config.edids 252 | else None, 253 | config.outputs.keys(), 254 | config.outputs.values(), 255 | ), 256 | ) 257 | ) 258 | return reference_output 259 | 260 | 261 | def format_cmd(event, cmd): 262 | return [arg.format(event) for arg in cmd] 263 | 264 | 265 | def get_commands(event, cmdlist): 266 | if cmdlist: 267 | return [format_cmd(event, cmd) for cmd in cmdlist] 268 | return [] 269 | 270 | 271 | def activate_crtc(config, event, commands): 272 | monitor_config = get_mon_configuration_for_edid(config, event.edid, event.output) 273 | if not monitor_config: 274 | return None 275 | 276 | xrandr_cmd = ["xrandr", "--output", event.output] 277 | resolution = monitor_config.resolution 278 | if not resolution or resolution == "auto": 279 | xrandr_cmd.append("--auto") 280 | else: 281 | xrandr_cmd.extend(("--mode", resolution)) 282 | 283 | if monitor_config.xrandr_args: 284 | xrandr_cmd.extend(monitor_config.xrandr_args) 285 | 286 | if monitor_config.position and len(monitor_config.position) >= 2: 287 | reference_output = find_reference_output(config, *monitor_config.position[1:3]) 288 | if reference_output and reference_output != event.output: 289 | xrandr_cmd.extend((monitor_config.position[0], reference_output)) 290 | else: 291 | xrandr_cmd.extend(monitor_config.position) 292 | elif monitor_config.position is not None: 293 | xrandr_cmd.extend(monitor_config.position) 294 | 295 | commands.append(xrandr_cmd) 296 | commands.extend(get_commands(event, monitor_config.exec_on_connect)) 297 | return config, event, commands 298 | 299 | 300 | def deactivate_crtc(config, event, commands): 301 | monitor_config = get_mon_configuration_for_edid(config, event.edid, event.output) 302 | 303 | commands.append(["xrandr", "--output", event.output, "--off"]) 304 | if monitor_config: 305 | commands.extend(get_commands(event, monitor_config.exec_on_disconnect)) 306 | return config, event, commands 307 | 308 | 309 | def process(config, event): 310 | assert event and event.event and config 311 | 312 | commands = [] 313 | 314 | if event.event == CONNECTED: 315 | _, _, commands = compose(set_wallpapers, activate_crtc)(config, event, []) 316 | elif event.event == DISCONNECTED: 317 | _, _, commands = deactivate_crtc(config, event, []) 318 | 319 | # [print(" ".join(command)) for command in commands] 320 | # map(jam(execute, print), commands) 321 | return functools.reduce( 322 | lambda r1, r2: r1 if r1 and r1 > 0 else r2, map(execute, commands) 323 | ) 324 | 325 | 326 | def main(): 327 | event = Event( 328 | output=os.environ.get("SRANDRD_OUTPUT", None), 329 | event=os.environ.get("SRANDRD_EVENT", None), 330 | edid=os.environ.get("SRANDRD_EDID", None), 331 | screenid=os.environ.get("SRANDRD_SCREENID", None), 332 | ) 333 | global_config = GlobalConfiguration( 334 | outputs=get_connected_outputs(), 335 | monitors_config=toml.load(CONFIG_FILE), 336 | default_monitor=os.environ.get("SCREENCONFIG_DEFAULT", DEFAULT_MONITOR), 337 | ) 338 | return process(global_config, event) 339 | 340 | 341 | if __name__ == "__main__": 342 | sys.exit(main()) 343 | --------------------------------------------------------------------------------