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