Ticket #1659: 1659.patch
File 1659.patch, 9.8 KB (added by sascha_silbe, 14 years ago) |
---|
-
src/jarabe/util/emulator.py
From: Sascha Silbe <sascha@silbe.org> Subject: [PATCH] sugar-emulator: use vnc instead of Xephyr (#1659) Use VNC4 instead of Xephyr so non-US keyboards work fine. On Ubuntu it requires lsb_release for detecting Ubuntu so it can use a workaround for Ubuntu bug #110263. The workaround breaks other systems so we really need it to be conditional. Tested on: - Debian squeeze - Ubuntu Intrepid - Ubuntu Jaunty - Fedora 12 Signed-off-by: Sascha Silbe <sascha@silbe.org> --- src/jarabe/util/emulator.py | 216 ++++++++++++++++++++++++++++++------------ 1 files changed, 154 insertions(+), 62 deletions(-) diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py index c00f1db..e118cba 100644
a b 14 14 # along with this program; if not, write to the Free Software 15 15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 16 16 17 import errno 17 18 import os 18 import random19 19 import signal 20 20 import subprocess 21 21 import time 22 22 from optparse import OptionParser 23 23 24 24 import gtk 25 import gobject26 25 27 26 from sugar import env 28 27 28 DEFAULT_DIMENSIONS = (800, 600) 29 _DEV_NULL = open(os.devnull, 'w') 29 30 30 default_dimensions = (800, 600) 31 def _run_xephyr(display, dpi, dimensions, fullscreen): 32 cmd = [ 'Xephyr' ] 33 cmd.append(':%d' % display) 34 cmd.append('-ac') 31 server = None 35 32 36 screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())37 33 38 if (not dimensions) and (fullscreen is None) and \ 39 (screen_size < default_dimensions) : 40 # no forced settings, screen too small => fit screen 41 fullscreen = True 42 elif (not dimensions) : 43 # screen is big enough or user has en/disabled fullscreen manually 44 # => use default size (will get ignored for fullscreen) 45 dimensions = '%dx%d' % default_dimensions 46 47 if not dpi : 48 dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024 34 def _run_pipe(command, stdin=None): 35 """Run a program with optional input and return output. 49 36 50 if fullscreen : 51 cmd.append('-fullscreen') 37 Will raise CalledProcessError if program exits with non-zero return 38 code. 39 """ 40 pipe = subprocess.Popen(command, close_fds=True, stdin=subprocess.PIPE, 41 stdout=subprocess.PIPE) 42 stdout, stderr_ = pipe.communicate(stdin) 43 if pipe.returncode: 44 raise subprocess.CalledProcessError(pipe.returncode, command) 52 45 53 if dimensions : 54 cmd.append('-screen') 55 cmd.append(dimensions) 46 return stdout 56 47 57 if dpi :58 cmd.append('-dpi')59 cmd.append('%d' % dpi)60 48 61 cmd.append('-noreset') 49 def _get_distro(): 50 """Run lsb_release to get distribution name. 51 Return None if distribution name cannot be determined. 52 """ 53 try: 54 distro = ''.join(_run_pipe(['lsb_release', '-is'])).strip() 55 except subprocess.CalledProcessError: 56 return None 62 57 63 pipe = subprocess.Popen(cmd)58 return distro or None 64 59 65 os.environ['DISPLAY'] = ":%d" % (display)66 os.environ['SUGAR_EMULATOR_PID'] = str(pipe.pid)67 return pipe68 60 61 def _run_xauth(display): 62 """Set up Xauthority file for new display. 69 63 70 def _check_server(display): 71 result = subprocess.call(['xdpyinfo', '-display', ':%d' % display], 72 stdout=open(os.devnull, "w"), 73 stderr=open(os.devnull, "w")) 74 return result == 0 64 Returns name of Xauthority file.""" 65 # pylint: disable-msg=E1103,W0612 66 xauth_file = os.environ.get('XAUTHORITY', 67 os.path.expanduser('~/.Xauthority')) 68 host = _run_pipe(['uname', '-n']).strip() 69 cookie = _run_pipe(['mcookie']).strip() 70 xauth_pipe = subprocess.Popen(['xauth', '-f', xauth_file], 71 stdin=subprocess.PIPE, close_fds=True) 72 xauth_pipe.communicate('add %(host)s:%(display)s . %(cookie)s\n' 73 'add %(host)s/unix:%(display)s . %(cookie)s\n' % locals()) 74 return xauth_file 75 76 77 def _run_server(display, dpi, dimensions, fullscreen): 78 """Start the X server.""" 79 screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height()) 80 81 if (not dimensions) and (fullscreen is None) and \ 82 (screen_size < DEFAULT_DIMENSIONS): 83 dimensions = '%dx%d' % screen_size 84 elif fullscreen: 85 dimensions = '%dx%d' % screen_size 86 elif not dimensions: 87 dimensions = '%dx%d' % DEFAULT_DIMENSIONS 88 89 if not dpi: 90 dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024 91 92 xauth_file = _run_xauth(display) 93 command = ['Xvnc', '-DisconnectClients', '-NeverShared', '-localhost', 94 '-SecurityTypes', 'None', '-auth', xauth_file] 95 if dimensions: 96 command.append('-geometry') 97 command.append(dimensions) 98 if dpi: 99 command.append('-dpi') 100 command.append('%d' % dpi) 101 102 if _get_distro() == 'Ubuntu': 103 # workaround for Ubuntu bug #110263 104 command += ['-extension', 'XFIXES'] 105 106 command.append(':%d' % (display, )) 107 pipe = subprocess.Popen(command, close_fds=True) 108 return pipe 75 109 76 110 77 111 def _kill_pipe(pipe): 78 """Terminate and wait for child process ."""112 """Terminate and wait for child process (if any).""" 79 113 try: 80 114 os.kill(pipe.pid, signal.SIGTERM) 81 except OSError: 82 pass 115 except OSError, exception: 116 if exception.errno != errno.ESRCH: 117 raise 83 118 84 pipe.wait() 119 try: 120 pipe.wait() 121 except OSError, exception: 122 if exception.errno != errno.ECHILD: 123 raise 124 125 126 def _wait_pipe(pipe): 127 """Wait for pipe to finish. 85 128 129 Retries on EINTR to work around <http://bugs.python.org/issue1068268>. 130 """ 131 while pipe.returncode is None: 132 try: 133 pipe.wait() 134 except OSError, exception: 135 if exception.errno != errno.EINTR: 136 raise 86 137 87 def _start_xephyr(dpi, dimensions, fullscreen): 138 139 def _start_server(dpi, dimensions, fullscreen): 140 """Try running the X server on a free display.""" 88 141 for display in range(30, 40): 89 142 if not _check_server(display): 90 pipe = _run_ xephyr(display, dpi, dimensions, fullscreen)143 pipe = _run_server(display, dpi, dimensions, fullscreen) 91 144 92 145 for i_ in range(10): 93 146 if _check_server(display): 94 return pipe147 return display, pipe 95 148 96 149 time.sleep(0.1) 97 150 98 151 _kill_pipe(pipe) 99 152 100 153 101 def _start_window_manager(): 102 cmd = ['metacity'] 154 def _start_viewer(display, fullscreen): 155 """Start the VNC viewer.""" 156 command = ['vncviewer'] 157 if fullscreen: 158 command.append('-fullscreen') 103 159 104 cmd.extend(['--no-force-fullscreen']) 160 command.append(':%d' % (display, )) 161 pipe = subprocess.Popen(command, close_fds=True) 162 return pipe 163 164 165 def _check_server(display): 166 """Check the X server on the given display is reachable.""" 167 result = subprocess.call(['xdpyinfo', '-display', ':%d' % (display, )], 168 stdout=_DEV_NULL, stderr=_DEV_NULL) 169 return result == 0 105 170 106 gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)107 171 108 def _setup_env(): 172 def _start_window_manager(): 173 """Start the window manager inside the new X server.""" 174 command = ['metacity', '--no-force-fullscreen'] 175 pipe_ = subprocess.Popen(command) 176 177 178 def _setup_env(display, scaling): 179 """Set up environment variables for running Sugar inside the new X server. 180 """ 109 181 os.environ['SUGAR_EMULATOR'] = 'yes' 110 182 os.environ['GABBLE_LOGFILE'] = os.path.join( 111 183 env.get_profile_path(), 'logs', 'telepathy-gabble.log') … … def _setup_env(): 113 185 env.get_profile_path(), 'logs', 'telepathy-salut.log') 114 186 os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join( 115 187 env.get_profile_path(), 'logs', 'telepathy-stream-engine.log') 188 os.environ['DISPLAY'] = ':%d' % (display, ) 189 if scaling: 190 os.environ['SUGAR_SCALING'] = scaling 116 191 117 192 118 def main(): 119 """Script-level operations""" 120 193 def _parse_args(): 194 """Parse command line arguments.""" 121 195 parser = OptionParser() 122 196 parser.add_option('-d', '--dpi', dest='dpi', type="int", 123 197 help='Emulator dpi') … … def main(): 131 205 parser.add_option('-F', '--no-fullscreen', dest='fullscreen', 132 206 action='store_false', 133 207 help='Do not run emulator in fullscreen mode') 134 (options, args) = parser.parse_args() 208 return parser.parse_args() 209 210 211 def _sigchld_handler(number_, frame_): 212 """Kill server when any (direct) child exits. 213 214 So closing the viewer will close the server as well.""" 215 if server.returncode is not None: 216 return 217 218 signal.signal(signal.SIGCHLD, signal.SIG_DFL) 135 219 136 _setup_env() 220 print 'sugar-emulator: Child exited, shutting down server' 221 _kill_pipe(server) 222 return 137 223 138 server = _start_xephyr(options.dpi, options.dimensions, options.fullscreen)139 224 140 if options.scaling: 141 os.environ['SUGAR_SCALING'] = options.scaling 225 def main(): 226 """Script-level operations""" 227 global server 228 229 options, args = _parse_args() 230 display, server = _start_server(options.dpi, options.dimensions, 231 options.fullscreen) 232 viewer = _start_viewer(display, options.fullscreen) 233 _setup_env(display, options.scaling) 142 234 143 235 command = ['dbus-launch', '--exit-with-session'] 144 236 … … def main(): 146 238 command.append('sugar') 147 239 else: 148 240 _start_window_manager() 241 command += args 149 242 150 if args[0].endswith('.py'): 151 command.append('python') 152 153 command.append(args[0]) 243 signal.signal(signal.SIGCHLD, _sigchld_handler) 244 session = subprocess.Popen(command, close_fds=True) 245 _wait_pipe(session) 154 246 155 subprocess.call(command)247 _kill_pipe(viewer) 156 248 _kill_pipe(server)