refactor pretty much everything. some features are still missing.
[lilass.git] / dsl.py
1 #!/usr/bin/python3
2 # DSL - easy Display Setup for Laptops
3 # Copyright (C) 2012-2015 Ralf Jung <post@ralfj.de>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 import argparse, sys, os, re, subprocess
20 from enum import Enum
21 import gui, screen
22 frontend = gui.getFrontend("cli") # the fallback, until we got a proper frontend. This is guaranteed to be available.
23
24 # for auto-config: common names of internal connectors
25 commonInternalConnectorPrefixes = ['LVDS', 'eDP']
26 commonInternalConnectorSuffices = ['', '0', '1', '-0', '-1']
27 def commonInternalConnectorNames():
28     for prefix in commonInternalConnectorPrefixes:
29         for suffix in commonInternalConnectorSuffices:
30             yield prefix+suffix
31
32
33 # Load a section-less config file: maps parameter names to space-separated lists of strings (with shell quotation)
34 def loadConfigFile(filename):
35     import shlex
36     result = {}
37     if not os.path.exists(filename):
38         return result # no config file
39     # read config file
40     linenr = 0
41     with open(filename) as f:
42         for line in f:
43             linenr += 1
44             line = line.strip()
45             if not len(line) or line.startswith("#"): continue # skip empty and comment lines
46             try:
47                 # parse line
48                 pos = line.index("=") # will raise exception when substring is not found
49                 curKey = line[:pos].strip()
50                 result[curKey] = shlex.split(line[pos+1:]) # shlex.split also strips
51             except Exception:
52                 raise Exception("Invalid config, line %d: Error parsing line (may be a quoting issue)." % linenr)
53     # add some convencience get functions
54     return result
55
56
57 # Make sure the backlight is turned on
58 def turnOnBacklight():
59     try:
60         backlight = float(subprocess.check_output(["xbacklight", "-get"]).strip())
61         if backlight == 0: # it's completely turned off, we better enable it
62             subprocess.check_call(["xbacklight", "-set", "100"])
63     except FileNotFoundError:
64         print("xbacklight has not been found, unable to turn your laptop backlight on.")
65     except subprocess.CalledProcessError:
66         print("xbacklight returned an error while attempting to turn your laptop backlight on.")
67
68
69 # if we run top-level
70 if __name__ == "__main__":
71     # parse command-line arguments
72     parser = argparse.ArgumentParser(description='easy Display Setup for Laptops')
73     parser.add_argument("-f", "--frontend",
74                         dest="frontend",
75                         help="The frontend to be used for user interaction")
76     parser.add_argument("-r", "--relative-position",
77                         dest="rel_position", choices=list(map(str.lower, screen.RelativeScreenPosition.__members__.keys())),
78                         help="Position of external screen relative to internal one")
79     parser.add_argument("-i", "--internal-only",
80                         dest="internal_only", action='store_true',
81                         help="Enable internal screen, disable all the others (as if no external screen was connected")
82     cmdArgs = parser.parse_args()
83     
84     # load frontend early (for error mssages)
85     frontend = gui.getFrontend(cmdArgs.frontend)
86     try:
87         # see what situation we are in
88         situation = screen.ScreenSituation(commonInternalConnectorNames())
89         
90         # construct the ScreenSetup
91         setup = None
92         if situation.externalResolutions() is not None:
93             setup = frontend.setup(situation)
94             if setup is None: sys.exit(1) # the user canceled
95         else:
96             # use first resolution of internal connector
97             setup = ScreenSetup(intResolution = situation.internalResolutions()[0], extResolution = None)
98         
99         # call xrandr
100         xrandrCall = situation.forXrandr(setup)
101         print("Call that will be made:",xrandrCall)
102         subprocess.check_call(xrandrCall)
103         
104         # make sure the internal screen is really, *really* turned on if there is no external screen
105         if setup.extResolution is None:
106             turnOnBacklight()
107     except Exception as e:
108         frontend.error(str(e))
109         raise