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