Coding style: execute black + isort and integrate flake8, isort + black in build...
[grml-x.git] / grml-x
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3 # vim: set et ts=4:
4
5 # Filename:      grml-x
6 # Purpose:       wrapper for startx on grml [providing new xconfiguration tool]
7 # Authors:       grml-team (grml.org), (c) Christian Hofstaedtler <ch@grml.org>
8 # Bug-Reports:   see http://grml.org/bugs/
9 # License:       This file is licensed under the GPL v2.
10 ###############################################################################
11
12 import fileinput
13 import os
14 import subprocess
15 import sys
16 import tempfile
17 import time
18 import traceback
19 from optparse import OptionParser
20
21
22 class Section(object):
23     def __init__(self, name, identifier, data):
24         self.name = name
25         self.identifer = identifier
26         self.data = data
27         self.subsect = ""
28
29     def __str__(self):
30         s = 'Section "%s"\n\tIdentifier "%s"\n' % (self.name, self.identifer)
31         for k in self.data:
32             v = self.data[k]
33             if isinstance(v, list):
34                 v = '" "'.join(v)
35             elif not isinstance(v, str):  # int, others
36                 v = str(v)
37             elif "-" in v:  # sync range
38                 pass
39             else:
40                 v = '"%s"' % v
41             s += "\t%s %s\n" % (k, v)
42         s += self.subsect
43         s += "EndSection\n"
44         return s
45
46
47 def get_monitor_section(options, force):
48     if not options.hsync and not options.vsync and not force:
49         return None
50     d = {}
51     d["HorizSync"] = options.hsync or "28.0 - 96.0"
52     d["VertRefresh"] = options.vsync or "50.0 - 60.0"
53     return Section("Monitor", "Monitor0", d)
54
55
56 def get_device_section(options):
57     if not options.module:
58         return None
59     d = {}
60     d["Driver"] = options.module
61     d["VendorName"] = "All"
62     d["BoardName"] = "All"
63     return Section("Device", "Card0", d)
64
65
66 def build_bootparams():
67     lines = []
68
69     def walk_bootparams_path(p):
70         try:
71             if not os.path.exists(p):
72                 return
73             for root, dirs, files in os.walk(p):
74                 for name in files:
75                     f = open(os.path.join(root, name))
76                     lines.extend(f.readlines())
77                     f.close()
78         except:
79             print("W: Error while getting bootparams from %s" % p)
80
81     f = open("/proc/cmdline")
82     lines.append(f.readline())
83     f.close()
84     walk_bootparams_path("/lib/live/mount/medium/bootparams")
85     walk_bootparams_path("/run/live/medium/bootparams")
86     params = {}
87     for p in " ".join(lines).split(" "):
88         if "=" in p:
89             (k, v) = p.split("=", 1)
90             params[k] = v
91         else:
92             params[p] = True
93     return params
94
95
96 def detect_qemu():
97     f = open("/proc/cpuinfo")
98     x = "".join(f.readlines())
99     f.close()
100     if "QEMU" in x:
101         return True
102     return False
103
104
105 def get_program_output(args):
106     p = subprocess.Popen(
107         args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True
108     )
109     return p.communicate()[0]
110
111
112 def run_program(args):
113     subprocess.Popen(args, close_fds=True).wait()
114
115
116 def which(program):
117     def is_exe(fpath):
118         return os.path.exists(fpath) and os.access(fpath, os.X_OK)
119
120     fpath, fname = os.path.split(program)
121     if fpath:
122         if is_exe(program):
123             return program
124     else:
125         for path in os.environ["PATH"].split(os.pathsep):
126             exe_file = os.path.join(path, program)
127             if is_exe(exe_file):
128                 return exe_file
129
130     return None
131
132
133 XORG_CONF_HEADER = "# Automatically generated by grml-x.\n"
134
135
136 def check_old_xorg_conf(filename, overwrite):
137     # True: no problem, we can create/overwrite the config file
138     # False: pre-existing config file, and we are not to overwrite it
139     if overwrite:
140         return True
141     if not os.path.exists(filename):
142         return True
143     try:
144         f = open(filename, "r")
145         lines = f.readlines()
146         f.close()
147         return not XORG_CONF_HEADER in lines
148     except IOError:
149         return False
150
151
152 parser = OptionParser(usage="usage: %prog [options] [window-manager]")
153 parser.add_option(
154     "--nostart",
155     action="store_false",
156     dest="start_server",
157     default=True,
158     help="Don't start X server",
159 )
160 parser.add_option(
161     "--display",
162     action="store",
163     type="string",
164     dest="display",
165     help="Start X server on display DISPLAY",
166 )
167 parser.add_option(
168     "--hsync",
169     action="store",
170     type="string",
171     dest="hsync",
172     help="Force writing a HorizSync range",
173 )
174 parser.add_option(
175     "--vsync",
176     action="store",
177     type="string",
178     dest="vsync",
179     help="Force writing a VertRefresh range",
180 )
181 parser.add_option(
182     "--mode",
183     action="store",
184     type="string",
185     dest="mode",
186     help="Force a specific resolution",
187 )
188 parser.add_option(
189     "--module",
190     action="store",
191     type="string",
192     dest="module",
193     help="Force driver MODULE instead of Xorg autodetection",
194 )
195 parser.add_option(
196     "-o",
197     action="store",
198     type="string",
199     dest="xorg_conf",
200     default="/etc/X11/xorg.conf",
201     help="Specify alternate xorg.conf file [default: %default]",
202 )
203 parser.add_option(
204     "-f",
205     "--force",
206     action="store_true",
207     dest="overwrite",
208     default=False,
209     help="Overwrite xorg.conf if it exists [default: %default]",
210 )
211
212
213 def main():
214     (options, args) = parser.parse_args()
215     bootparams = build_bootparams()
216
217     if os.getuid() == 0 and options.start_server:
218         print("W: running as root is unsupported and may not work.")
219         time.sleep(1)
220
221     if not check_old_xorg_conf(options.xorg_conf, options.overwrite):
222         print("E: Not overwriting existing %r without --force." % options.xorg_conf)
223         print("I: If you previously ran grml-x, use startx /usr/bin/x-window-manager")
224         return 1
225
226     if "xmode" in bootparams and not options.mode:
227         options.mode = bootparams["xmode"]
228     if "xmodule" in bootparams and not options.module:
229         options.module = bootparams["xmodule"]
230
231     force_monitor = False
232     # cirrus driver for QEMU doesn't do 1024x768 without HorizSync set
233     if detect_qemu():
234         force_monitor = True
235
236     monitor = get_monitor_section(options, force_monitor)
237     device = get_device_section(options)
238
239     # build Screen section ourselves
240     d = {}
241     if monitor:
242         d["Monitor"] = monitor.identifer
243     if device:
244         d["Device"] = device.identifer
245     screen = Section("Screen", "Screen0", d)
246     if options.mode:
247         d["DefaultColorDepth"] = 16
248         for depth in [8, 15, 16, 24, 32]:
249             screen.subsect += (
250                 'SubSection "Display"\n\tDepth %d\n\tModes "%s"\t\nEndSubSection\n'
251                 % (depth, options.mode)
252             )
253
254     xinitrc = "~/.xinitrc"
255     if "XINITRC" in os.environ:
256         xinitrc = os.environ["XINITRC"]
257     xinitrc = os.path.expanduser(xinitrc)
258
259     window_manager = "x-window-manager"
260     if len(args) == 1:
261         window_manager = args[0]
262     window_manager_path = which(window_manager)
263     if not window_manager_path:
264         print("E: Cannot find window manager %r, aborting." % window_manager)
265         return 2
266
267     wm_exec = "exec %s\n" % window_manager_path
268     if not os.path.exists(xinitrc):
269         f = open(xinitrc, "w")
270         f.write("#!/bin/sh\n")
271         f.write(wm_exec)
272         f.close()
273     else:
274         f = open(xinitrc, "r")
275         lines = f.readlines()
276         f.close()
277         f = open(xinitrc, "w")
278         for line in lines:
279             if line.strip().startswith("exec "):
280                 line = wm_exec
281             f.write(line)
282         os.fchmod(f.fileno(), 0o750)
283         f.close()
284
285     # write new config
286     if monitor or device or len(screen.data) > 0 or screen.subsect != "":
287         try:
288             f = tempfile.NamedTemporaryFile(mode="w+", delete=False)
289             f.write(XORG_CONF_HEADER)
290             f.write(
291                 "# DO NOT MODIFY, YOUR CHANGES WILL BE LOST - OR REMOVE ALL HEADER LINES\n"
292             )
293             f.write("# See man xorg.conf or /etc/X11/xorg.conf.example for more\n")
294             if monitor:
295                 f.write(str(monitor))
296             if device:
297                 f.write(str(device))
298             f.write(str(screen))
299             f.flush()
300             os.fchmod(f.fileno(), 0o644)
301             run_program(["sudo", "mv", "-f", f.name, options.xorg_conf])
302         finally:
303             f.close()
304
305     if options.start_server:
306         startx = ["startx", xinitrc, "--"]
307         if options.display:
308             startx.append(":" + options.display)
309         print("Starting X: %r" % startx)
310         run_program(startx)
311
312     return 0
313
314
315 if __name__ == "__main__":
316     rc = 1
317     try:
318         rc = main()
319     except Exception:
320         print("E: Exception: ", end=" ")
321         traceback.print_exc()
322     sys.exit(1)