~ubuntu-branches/ubuntu/precise/virt-manager/precise-proposed

« back to all changes in this revision

Viewing changes to .pc/fix_grep_portability.patch/src/virtManager/console.py

  • Committer: Bazaar Package Importer
  • Author(s): Marc Deslauriers
  • Date: 2011-08-16 13:34:42 UTC
  • Revision ID: james.westby@ubuntu.com-20110816133442-zt4zrj4scczm6xvq
Tags: 0.9.0-1ubuntu2
* debian/patches/fix_grep_portability.patch: support target systems where
  the grep utility has no -q option. (LP: #792985)
* debian/patches/fix_net_stats.patch: fix broken net stats gathering.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright (C) 2006-2008 Red Hat, Inc.
 
4
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
 
5
# Copyright (C) 2010 Marc-André Lureau <marcandre.lureau@redhat.com>
 
6
#
 
7
# This program is free software; you can redistribute it and/or modify
 
8
# it under the terms of the GNU General Public License as published by
 
9
# the Free Software Foundation; either version 2 of the License, or
 
10
# (at your option) any later version.
 
11
#
 
12
# This program is distributed in the hope that it will be useful,
 
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
# GNU General Public License for more details.
 
16
#
 
17
# You should have received a copy of the GNU General Public License
 
18
# along with this program; if not, write to the Free Software
 
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
20
# MA 02110-1301 USA.
 
21
#
 
22
 
 
23
import gtk
 
24
import gobject
 
25
 
 
26
import libvirt
 
27
 
 
28
import gtkvnc
 
29
 
 
30
try:
 
31
    import SpiceClientGtk as spice
 
32
except:
 
33
    spice = None
 
34
 
 
35
import os
 
36
import signal
 
37
import socket
 
38
import logging
 
39
 
 
40
import virtManager.util as util
 
41
import virtManager.uihelpers as uihelpers
 
42
from virtManager.autodrawer import AutoDrawer
 
43
from virtManager.baseclass import vmmGObjectUI, vmmGObject
 
44
from virtManager.error import vmmErrorDialog
 
45
 
 
46
# Console pages
 
47
PAGE_UNAVAILABLE = 0
 
48
PAGE_AUTHENTICATE = 1
 
49
PAGE_VIEWER = 2
 
50
 
 
51
def has_property(obj, setting):
 
52
    try:
 
53
        obj.get_property(setting)
 
54
    except TypeError:
 
55
        return False
 
56
    return True
 
57
 
 
58
 
 
59
class Tunnel(object):
 
60
    def __init__(self):
 
61
        self.outfd = None
 
62
        self.errfd = None
 
63
        self.pid = None
 
64
 
 
65
    def open(self, connhost, connuser, connport, gaddr, gport, gsocket):
 
66
        if self.outfd is not None:
 
67
            return -1
 
68
 
 
69
        # Build SSH cmd
 
70
        argv = ["ssh", "ssh"]
 
71
        if connport:
 
72
            argv += ["-p", str(connport)]
 
73
 
 
74
        if connuser:
 
75
            argv += ['-l', connuser]
 
76
 
 
77
        argv += [connhost]
 
78
 
 
79
        # Build 'nc' command run on the remote host
 
80
        #
 
81
        # This ugly thing is a shell script to detect availability of
 
82
        # the -q option for 'nc': debian and suse based distros need this
 
83
        # flag to ensure the remote nc will exit on EOF, so it will go away
 
84
        # when we close the VNC tunnel. If it doesn't go away, subsequent
 
85
        # VNC connection attempts will hang.
 
86
        #
 
87
        # Fedora's 'nc' doesn't have this option, and apparently defaults
 
88
        # to the desired behavior.
 
89
        #
 
90
        if gsocket:
 
91
            nc_params = "-U %s" % gsocket
 
92
        else:
 
93
            nc_params = "%s %s" % (gaddr, gport)
 
94
 
 
95
        nc_cmd = (
 
96
            """nc -q 2>&1 | grep -q "requires an argument";"""
 
97
            """if [ $? -eq 0 ] ; then"""
 
98
            """   CMD="nc -q 0 %(nc_params)s";"""
 
99
            """else"""
 
100
            """   CMD="nc %(nc_params)s";"""
 
101
            """fi;"""
 
102
            """eval "$CMD";""" %
 
103
            {'nc_params': nc_params})
 
104
 
 
105
        argv.append("sh -c")
 
106
        argv.append("'%s'" % nc_cmd)
 
107
 
 
108
        argv_str = reduce(lambda x, y: x + " " + y, argv[1:])
 
109
        logging.debug("Creating SSH tunnel: %s" % argv_str)
 
110
 
 
111
        fds      = socket.socketpair()
 
112
        errorfds = socket.socketpair()
 
113
 
 
114
        pid = os.fork()
 
115
        if pid == 0:
 
116
            fds[0].close()
 
117
            errorfds[0].close()
 
118
 
 
119
            os.close(0)
 
120
            os.close(1)
 
121
            os.close(2)
 
122
            os.dup(fds[1].fileno())
 
123
            os.dup(fds[1].fileno())
 
124
            os.dup(errorfds[1].fileno())
 
125
            os.execlp(*argv)
 
126
            os._exit(1)
 
127
        else:
 
128
            fds[1].close()
 
129
            errorfds[1].close()
 
130
 
 
131
        logging.debug("Tunnel PID=%d OUTFD=%d ERRFD=%d" %
 
132
                      (pid, fds[0].fileno(), errorfds[0].fileno()))
 
133
        errorfds[0].setblocking(0)
 
134
 
 
135
        self.outfd = fds[0]
 
136
        self.errfd = errorfds[0]
 
137
        self.pid = pid
 
138
 
 
139
        fd = fds[0].fileno()
 
140
        if fd < 0:
 
141
            raise SystemError("can't open a new tunnel: fd=%d" % fd)
 
142
        return fd
 
143
 
 
144
    def close(self):
 
145
        if self.outfd is None:
 
146
            return
 
147
 
 
148
        logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
 
149
                      (self.pid, self.outfd.fileno(),
 
150
                       self.errfd.fileno()))
 
151
        self.outfd.close()
 
152
        self.outfd = None
 
153
        self.errfd.close()
 
154
        self.errfd = None
 
155
 
 
156
        os.kill(self.pid, signal.SIGKILL)
 
157
        self.pid = None
 
158
 
 
159
    def get_err_output(self):
 
160
        errout = ""
 
161
        while True:
 
162
            try:
 
163
                new = self.errfd.recv(1024)
 
164
            except:
 
165
                break
 
166
 
 
167
            if not new:
 
168
                break
 
169
 
 
170
            errout += new
 
171
 
 
172
        return errout
 
173
 
 
174
 
 
175
class Tunnels(object):
 
176
    def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
 
177
        self.connhost = connhost
 
178
        self.connuser = connuser
 
179
        self.connport = connport
 
180
 
 
181
        self.gaddr = gaddr
 
182
        self.gport = gport
 
183
        self.gsocket = gsocket
 
184
        self._tunnels = []
 
185
 
 
186
    def open_new(self):
 
187
        t = Tunnel()
 
188
        fd = t.open(self.connhost, self.connuser, self.connport,
 
189
                    self.gaddr, self.gport, self.gsocket)
 
190
        self._tunnels.append(t)
 
191
        return fd
 
192
 
 
193
    def close_all(self):
 
194
        for l in self._tunnels:
 
195
            l.close()
 
196
 
 
197
    def get_err_output(self):
 
198
        errout = ""
 
199
        for l in self._tunnels:
 
200
            errout += l.get_err_output()
 
201
        return errout
 
202
 
 
203
 
 
204
class Viewer(vmmGObject):
 
205
    def __init__(self, console):
 
206
        vmmGObject.__init__(self)
 
207
        self.console = console
 
208
        self.display = None
 
209
 
 
210
    def close(self):
 
211
        raise NotImplementedError()
 
212
 
 
213
    def _cleanup(self):
 
214
        self.close()
 
215
 
 
216
        if self.display:
 
217
            self.display.destroy()
 
218
        self.display = None
 
219
        self.console = None
 
220
 
 
221
    def get_widget(self):
 
222
        return self.display
 
223
 
 
224
    def get_pixbuf(self):
 
225
        return self.display.get_pixbuf()
 
226
 
 
227
    def get_grab_keys_from_config(self):
 
228
        keys = []
 
229
        grab_keys = self.config.get_keys_combination(True)
 
230
        if grab_keys is not None:
 
231
            # If somebody edited this in GConf it would fail so
 
232
            # we encapsulate this into try/except block
 
233
            try:
 
234
                keys = map(int, grab_keys.split(','))
 
235
            except:
 
236
                logging.debug("Error in grab_keys configuration in GConf")
 
237
 
 
238
        return keys
 
239
 
 
240
    def get_grab_keys(self):
 
241
        keystr = None
 
242
        try:
 
243
            keys = self.display.get_grab_keys()
 
244
            for k in keys:
 
245
                if keystr is None:
 
246
                    keystr = gtk.gdk.keyval_name(k)
 
247
                else:
 
248
                    keystr = keystr + "+" + gtk.gdk.keyval_name(k)
 
249
        except:
 
250
            pass
 
251
 
 
252
        return keystr
 
253
 
 
254
    def send_keys(self, keys):
 
255
        return self.display.send_keys(keys)
 
256
 
 
257
    def set_grab_keys(self):
 
258
        try:
 
259
            keys = self.get_grab_keys_from_config()
 
260
            if keys:
 
261
                self.display.set_grab_keys(keys)
 
262
        except Exception, e:
 
263
            logging.debug("Error when getting the grab keys combination: %s" %
 
264
                          str(e))
 
265
 
 
266
    def open_host(self, host, user, port, socketpath, password=None):
 
267
        raise NotImplementedError()
 
268
 
 
269
    def get_desktop_resolution(self):
 
270
        raise NotImplementedError()
 
271
 
 
272
class VNCViewer(Viewer):
 
273
    def __init__(self, console):
 
274
        Viewer.__init__(self, console)
 
275
        self.display = gtkvnc.Display()
 
276
        self.sockfd = None
 
277
 
 
278
        # Last noticed desktop resolution
 
279
        self.desktop_resolution = None
 
280
 
 
281
    def init_widget(self):
 
282
        # Set default grab key combination if found and supported
 
283
        if self.config.vnc_grab_keys_supported():
 
284
            self.set_grab_keys()
 
285
 
 
286
        self.display.realize()
 
287
 
 
288
        # Make sure viewer doesn't force resize itself
 
289
        self.display.set_force_size(False)
 
290
 
 
291
        self.console.refresh_scaling()
 
292
 
 
293
        self.display.set_keyboard_grab(False)
 
294
        self.display.set_pointer_grab(True)
 
295
 
 
296
        self.display.connect("vnc-pointer-grab", self.console.pointer_grabbed)
 
297
        self.display.connect("vnc-pointer-ungrab", self.console.pointer_ungrabbed)
 
298
        self.display.connect("vnc-auth-credential", self._auth_credential)
 
299
        self.display.connect("vnc-initialized",
 
300
                             lambda src: self.console.connected())
 
301
        self.display.connect("vnc-disconnected",
 
302
                             lambda src: self.console.disconnected())
 
303
        self.display.connect("vnc-desktop-resize", self._desktop_resize)
 
304
        self.display.connect("focus-in-event", self.console.viewer_focus_changed)
 
305
        self.display.connect("focus-out-event", self.console.viewer_focus_changed)
 
306
 
 
307
        self.display.show()
 
308
 
 
309
    def _desktop_resize(self, src_ignore, w, h):
 
310
        self.desktop_resolution = (w, h)
 
311
        self.console.window.get_widget("console-vnc-scroll").queue_resize()
 
312
 
 
313
    def get_desktop_resolution(self):
 
314
        return self.desktop_resolution
 
315
 
 
316
    def _auth_credential(self, src_ignore, credList):
 
317
        for cred in credList:
 
318
            if cred in [gtkvnc.CREDENTIAL_PASSWORD,
 
319
                        gtkvnc.CREDENTIAL_USERNAME,
 
320
                        gtkvnc.CREDENTIAL_CLIENTNAME]:
 
321
                continue
 
322
 
 
323
            self.console.err.show_err(
 
324
                summary=_("Unable to provide requested credentials to "
 
325
                          "the VNC server"),
 
326
                details=(_("The credential type %s is not supported") %
 
327
                         (str(cred))),
 
328
                title=_("Unable to authenticate"),
 
329
                async=True)
 
330
 
 
331
            # schedule_retry will error out
 
332
            self.console.viewerRetriesScheduled = 10
 
333
            self.close()
 
334
            self.console.activate_unavailable_page(
 
335
                            _("Unsupported console authentication type"))
 
336
            return
 
337
 
 
338
        withUsername = False
 
339
        withPassword = False
 
340
        for cred in credList:
 
341
            logging.debug("Got credential request %s" % cred)
 
342
            if cred == gtkvnc.CREDENTIAL_PASSWORD:
 
343
                withPassword = True
 
344
            elif cred == gtkvnc.CREDENTIAL_USERNAME:
 
345
                withUsername = True
 
346
            elif cred == gtkvnc.CREDENTIAL_CLIENTNAME:
 
347
                self.display.set_credential(cred, "libvirt-vnc")
 
348
 
 
349
        if withUsername or withPassword:
 
350
            self.console.activate_auth_page(withPassword, withUsername)
 
351
 
 
352
    def get_scaling(self):
 
353
        return self.display.get_scaling()
 
354
 
 
355
    def set_scaling(self, scaling):
 
356
        return self.display.set_scaling(scaling)
 
357
 
 
358
    def close(self):
 
359
        self.display.close()
 
360
        if not self.sockfd:
 
361
            return
 
362
 
 
363
        self.sockfd.close()
 
364
        self.sockfd = None
 
365
 
 
366
    def is_open(self):
 
367
        return self.display.is_open()
 
368
 
 
369
    def open_host(self, host, user, port, socketpath, password=None):
 
370
        ignore = password
 
371
        ignore = user
 
372
 
 
373
        if not socketpath:
 
374
            self.display.open_host(host, port)
 
375
            return
 
376
 
 
377
        try:
 
378
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
379
            sock.connect(socketpath)
 
380
            self.sockfd = sock
 
381
        except Exception, e:
 
382
            raise RuntimeError(_("Error opening socket path '%s': %s") %
 
383
                               (socketpath, e))
 
384
 
 
385
        fd = self.sockfd.fileno()
 
386
        if fd < 0:
 
387
            raise RuntimeError((_("Error opening socket path '%s'") %
 
388
                                socketpath) + " fd=%s" % fd)
 
389
        self.open_fd(fd)
 
390
 
 
391
    def open_fd(self, fd):
 
392
        self.display.open_fd(fd)
 
393
 
 
394
    def get_grab_keys(self):
 
395
        keystr = None
 
396
        if self.config.vnc_grab_keys_supported():
 
397
            keystr = super(VNCViewer, self).get_grab_keys()
 
398
 
 
399
        # If grab keys are set to None then preserve old behaviour since
 
400
        # the GTK-VNC - we're using older version of GTK-VNC
 
401
        if keystr is None:
 
402
            keystr = "Control_L+Alt_L"
 
403
        return keystr
 
404
 
 
405
    def set_credential_username(self, cred):
 
406
        self.display.set_credential(gtkvnc.CREDENTIAL_USERNAME, cred)
 
407
 
 
408
    def set_credential_password(self, cred):
 
409
        self.display.set_credential(gtkvnc.CREDENTIAL_PASSWORD, cred)
 
410
 
 
411
 
 
412
class SpiceViewer(Viewer):
 
413
    def __init__(self, console):
 
414
        Viewer.__init__(self, console)
 
415
        self.spice_session = None
 
416
        self.display = None
 
417
        self.audio = None
 
418
        self.display_channel = None
 
419
 
 
420
    def _init_widget(self):
 
421
        self.set_grab_keys()
 
422
        self.console.refresh_scaling()
 
423
 
 
424
        self.display.realize()
 
425
        self.display.connect("mouse-grab", lambda src, g: g and self.console.pointer_grabbed(src))
 
426
        self.display.connect("mouse-grab", lambda src, g: g or self.console.pointer_ungrabbed(src))
 
427
        self.display.show()
 
428
 
 
429
    def close(self):
 
430
        if self.spice_session is not None:
 
431
            self.spice_session.disconnect()
 
432
        self.spice_session = None
 
433
        self.audio = None
 
434
        if self.display:
 
435
            self.display.destroy()
 
436
        self.display = None
 
437
        self.display_channel = None
 
438
 
 
439
    def is_open(self):
 
440
        return self.spice_session != None
 
441
 
 
442
    def _main_channel_event_cb(self, channel, event):
 
443
        if event == spice.CHANNEL_CLOSED:
 
444
            if self.console:
 
445
                self.console.disconnected()
 
446
        elif event == spice.CHANNEL_ERROR_AUTH:
 
447
            if self.console:
 
448
                self.console.activate_auth_page()
 
449
 
 
450
    def _channel_open_fd_request(self, channel, tls_ignore):
 
451
        if not self.console.tunnels:
 
452
            raise SystemError("Got fd request with no configured tunnel!")
 
453
 
 
454
        fd = self.console.tunnels.open_new()
 
455
        channel.open_fd(fd)
 
456
 
 
457
    def _channel_new_cb(self, session, channel):
 
458
        gobject.GObject.connect(channel, "open-fd",
 
459
                                self._channel_open_fd_request)
 
460
 
 
461
        if type(channel) == spice.MainChannel:
 
462
            channel.connect_after("channel-event", self._main_channel_event_cb)
 
463
            return
 
464
 
 
465
        if type(channel) == spice.DisplayChannel:
 
466
            channel_id = channel.get_property("channel-id")
 
467
 
 
468
            if channel_id != 0:
 
469
                logging.debug("Spice multi-head unsupported")
 
470
                return
 
471
 
 
472
            self.display_channel = channel
 
473
            self.display = spice.Display(self.spice_session, channel_id)
 
474
            self.console.window.get_widget("console-vnc-viewport").add(self.display)
 
475
            self._init_widget()
 
476
            self.console.connected()
 
477
            return
 
478
 
 
479
        if (type(channel) in [spice.PlaybackChannel, spice.RecordChannel] and
 
480
            not self.audio):
 
481
            self.audio = spice.Audio(self.spice_session)
 
482
            return
 
483
 
 
484
    def get_desktop_resolution(self):
 
485
        if (not self.display_channel or
 
486
            not has_property(self.display_channel, "width")):
 
487
            return None
 
488
        return self.display_channel.get_properties("width", "height")
 
489
 
 
490
    def open_host(self, host, user, port, socketpath, password=None):
 
491
        ignore = socketpath
 
492
 
 
493
        uri = "spice://"
 
494
        uri += (user and str(user) or "")
 
495
        uri += str(host) + "?port=" + str(port)
 
496
        logging.debug("spice uri: %s" % uri)
 
497
 
 
498
        self.spice_session = spice.Session()
 
499
        self.spice_session.set_property("uri", uri)
 
500
        if password:
 
501
            self.spice_session.set_property("password", password)
 
502
        gobject.GObject.connect(self.spice_session, "channel-new",
 
503
                                self._channel_new_cb)
 
504
        self.spice_session.connect()
 
505
 
 
506
    def open_fd(self, fd, password=None):
 
507
        self.spice_session = spice.Session()
 
508
        if password:
 
509
            self.spice_session.set_property("password", password)
 
510
        gobject.GObject.connect(self.spice_session, "channel-new",
 
511
                                self._channel_new_cb)
 
512
        self.spice_session.open_fd(fd)
 
513
 
 
514
    def set_credential_password(self, cred):
 
515
        self.spice_session.set_property("password", cred)
 
516
        if self.console.tunnels:
 
517
            fd = self.console.tunnels.open_new()
 
518
            self.spice_session.open_fd(fd)
 
519
        else:
 
520
            self.spice_session.connect()
 
521
 
 
522
    def get_scaling(self):
 
523
        return self.display.get_property("resize-guest")
 
524
 
 
525
    def set_scaling(self, scaling):
 
526
        self.display.set_property("resize-guest", scaling)
 
527
 
 
528
 
 
529
class vmmConsolePages(vmmGObjectUI):
 
530
    def __init__(self, vm, window):
 
531
        vmmGObjectUI.__init__(self, None, None)
 
532
 
 
533
        self.vm = vm
 
534
 
 
535
        self.windowname = "vmm-details"
 
536
        self.window = window
 
537
        self.topwin = self.widget(self.windowname)
 
538
        self.err = vmmErrorDialog(self.topwin)
 
539
 
 
540
        self.pointer_is_grabbed = False
 
541
        self.change_title()
 
542
        self.vm.connect("config-changed", self.change_title)
 
543
 
 
544
        # State for disabling modifiers when keyboard is grabbed
 
545
        self.accel_groups = gtk.accel_groups_from_object(self.topwin)
 
546
        self.gtk_settings_accel = None
 
547
        self.gtk_settings_mnemonic = None
 
548
 
 
549
        # Initialize display widget
 
550
        self.viewer = None
 
551
        self.tunnels = None
 
552
        self.viewerRetriesScheduled = 0
 
553
        self.viewerRetryDelay = 125
 
554
        self._viewer_connected = False
 
555
        self.viewer_connecting = False
 
556
        self.scale_type = self.vm.get_console_scaling()
 
557
 
 
558
        # Fullscreen toolbar
 
559
        self.fs_toolbar = None
 
560
        self.fs_drawer = None
 
561
        self.keycombo_menu = uihelpers.build_keycombo_menu(self.send_key)
 
562
        self.init_fs_toolbar()
 
563
 
 
564
        finish_img = gtk.image_new_from_stock(gtk.STOCK_YES,
 
565
                                              gtk.ICON_SIZE_BUTTON)
 
566
        self.widget("console-auth-login").set_image(finish_img)
 
567
 
 
568
        # Make viewer widget background always be black
 
569
        black = gtk.gdk.Color(0, 0, 0)
 
570
        self.widget("console-vnc-viewport").modify_bg(gtk.STATE_NORMAL,
 
571
                                                      black)
 
572
 
 
573
        # Signals are added by vmmDetails. Don't use signal_autoconnect here
 
574
        # or it changes will be overwritten
 
575
        # Set console scaling
 
576
        self.add_gconf_handle(
 
577
            self.vm.on_console_scaling_changed(self.refresh_scaling))
 
578
 
 
579
        scroll = self.widget("console-vnc-scroll")
 
580
        scroll.connect("size-allocate", self.scroll_size_allocate)
 
581
        self.add_gconf_handle(
 
582
            self.config.on_console_accels_changed(self.set_enable_accel))
 
583
 
 
584
        self.page_changed()
 
585
 
 
586
    def is_visible(self):
 
587
        if self.topwin.flags() & gtk.VISIBLE:
 
588
            return 1
 
589
        return 0
 
590
 
 
591
    def _cleanup(self):
 
592
        self.vm = None
 
593
 
 
594
        if self.viewer:
 
595
            self.viewer.cleanup()
 
596
        self.viewer = None
 
597
 
 
598
        self.keycombo_menu.destroy()
 
599
        self.keycombo_menu = None
 
600
        self.fs_toolbar.destroy()
 
601
        self.fs_toolbar = None
 
602
        self.fs_drawer.destroy()
 
603
        self.fs_drawer = None
 
604
 
 
605
    ##########################
 
606
    # Initialization helpers #
 
607
    ##########################
 
608
 
 
609
    def init_fs_toolbar(self):
 
610
        scroll = self.widget("console-vnc-scroll")
 
611
        pages = self.widget("console-pages")
 
612
        pages.remove(scroll)
 
613
 
 
614
        self.fs_toolbar = gtk.Toolbar()
 
615
        self.fs_toolbar.set_show_arrow(False)
 
616
        self.fs_toolbar.set_no_show_all(True)
 
617
        self.fs_toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
 
618
 
 
619
        # Exit fullscreen button
 
620
        button = gtk.ToolButton(gtk.STOCK_LEAVE_FULLSCREEN)
 
621
        util.tooltip_wrapper(button, _("Leave fullscreen"))
 
622
        button.show()
 
623
        self.fs_toolbar.add(button)
 
624
        button.connect("clicked", self.leave_fullscreen)
 
625
 
 
626
        def keycombo_menu_clicked(src):
 
627
            ignore = src
 
628
            def menu_location(menu, toolbar):
 
629
                ignore = menu
 
630
                x, y = toolbar.window.get_origin()
 
631
                ignore, height = toolbar.window.get_size()
 
632
 
 
633
                return x, y + height, True
 
634
 
 
635
            self.keycombo_menu.popup(None, None, menu_location, 0,
 
636
                                     gtk.get_current_event_time(),
 
637
                                     self.fs_toolbar)
 
638
 
 
639
        item = gtk.ToolButton()
 
640
        item.set_icon_name("preferences-desktop-keyboard-shortcuts")
 
641
        util.tooltip_wrapper(item, _("Send key combination"))
 
642
        item.show_all()
 
643
        item.connect("clicked", keycombo_menu_clicked)
 
644
        self.fs_toolbar.add(item)
 
645
 
 
646
        self.fs_drawer = AutoDrawer()
 
647
        self.fs_drawer.set_active(False)
 
648
        self.fs_drawer.set_over(self.fs_toolbar)
 
649
        self.fs_drawer.set_under(scroll)
 
650
        self.fs_drawer.set_offset(-1)
 
651
        self.fs_drawer.set_fill(False)
 
652
        self.fs_drawer.set_overlap_pixels(1)
 
653
        self.fs_drawer.set_nooverlap_pixels(0)
 
654
        self.fs_drawer.show_all()
 
655
 
 
656
        pages.add(self.fs_drawer)
 
657
 
 
658
    def change_title(self, ignore1=None):
 
659
        title = self.vm.get_name() + " " + _("Virtual Machine")
 
660
 
 
661
        if self.pointer_is_grabbed and self.viewer:
 
662
            keystr = self.viewer.get_grab_keys()
 
663
            keymsg = _("Press %s to release pointer.") % keystr
 
664
 
 
665
            title = keymsg + " " + title
 
666
 
 
667
        self.topwin.set_title(title)
 
668
 
 
669
    def viewer_focus_changed(self, ignore1=None, ignore2=None):
 
670
        has_focus = self.viewer and self.viewer.get_widget() and \
 
671
            self.viewer.get_widget().get_property("has-focus")
 
672
        force_accel = self.config.get_console_accels()
 
673
 
 
674
        if force_accel:
 
675
            self._enable_modifiers()
 
676
        elif has_focus and self.viewer_connected:
 
677
            self._disable_modifiers()
 
678
        else:
 
679
            self._enable_modifiers()
 
680
 
 
681
    def pointer_grabbed(self, src_ignore):
 
682
        self.pointer_is_grabbed = True
 
683
        self.change_title()
 
684
 
 
685
    def pointer_ungrabbed(self, src_ignore):
 
686
        self.pointer_is_grabbed = False
 
687
        self.change_title()
 
688
 
 
689
    def _disable_modifiers(self):
 
690
        if self.gtk_settings_accel is not None:
 
691
            return
 
692
 
 
693
        for g in self.accel_groups:
 
694
            self.topwin.remove_accel_group(g)
 
695
 
 
696
        settings = gtk.settings_get_default()
 
697
        self.gtk_settings_accel = settings.get_property('gtk-menu-bar-accel')
 
698
        settings.set_property('gtk-menu-bar-accel', None)
 
699
 
 
700
        if has_property(settings, "gtk-enable-mnemonics"):
 
701
            self.gtk_settings_mnemonic = settings.get_property(
 
702
                                                        "gtk-enable-mnemonics")
 
703
            settings.set_property("gtk-enable-mnemonics", False)
 
704
 
 
705
    def _enable_modifiers(self):
 
706
        if self.gtk_settings_accel is None:
 
707
            return
 
708
 
 
709
        settings = gtk.settings_get_default()
 
710
        settings.set_property('gtk-menu-bar-accel', self.gtk_settings_accel)
 
711
        self.gtk_settings_accel = None
 
712
 
 
713
        if self.gtk_settings_mnemonic is not None:
 
714
            settings.set_property("gtk-enable-mnemonics",
 
715
                                  self.gtk_settings_mnemonic)
 
716
 
 
717
        for g in self.accel_groups:
 
718
            self.topwin.add_accel_group(g)
 
719
 
 
720
    def set_enable_accel(self, ignore=None, ignore1=None,
 
721
                         ignore2=None, ignore3=None):
 
722
        # Make sure modifiers are up to date
 
723
        self.viewer_focus_changed()
 
724
 
 
725
    def refresh_scaling(self, ignore1=None, ignore2=None, ignore3=None,
 
726
                        ignore4=None):
 
727
        self.scale_type = self.vm.get_console_scaling()
 
728
        self.widget("details-menu-view-scale-always").set_active(
 
729
            self.scale_type == self.config.CONSOLE_SCALE_ALWAYS)
 
730
        self.widget("details-menu-view-scale-never").set_active(
 
731
            self.scale_type == self.config.CONSOLE_SCALE_NEVER)
 
732
        self.widget("details-menu-view-scale-fullscreen").set_active(
 
733
            self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN)
 
734
 
 
735
        self.update_scaling()
 
736
 
 
737
    def set_scale_type(self, src):
 
738
        if not src.get_active():
 
739
            return
 
740
 
 
741
        if src == self.widget("details-menu-view-scale-always"):
 
742
            self.scale_type = self.config.CONSOLE_SCALE_ALWAYS
 
743
        elif src == self.widget("details-menu-view-scale-fullscreen"):
 
744
            self.scale_type = self.config.CONSOLE_SCALE_FULLSCREEN
 
745
        elif src == self.widget("details-menu-view-scale-never"):
 
746
            self.scale_type = self.config.CONSOLE_SCALE_NEVER
 
747
 
 
748
        self.vm.set_console_scaling(self.scale_type)
 
749
        self.update_scaling()
 
750
 
 
751
    def update_scaling(self):
 
752
        if not self.viewer:
 
753
            return
 
754
 
 
755
        curscale = self.viewer.get_scaling()
 
756
        fs = self.widget("control-fullscreen").get_active()
 
757
        vnc_scroll = self.widget("console-vnc-scroll")
 
758
 
 
759
        if (self.scale_type == self.config.CONSOLE_SCALE_NEVER
 
760
            and curscale == True):
 
761
            self.viewer.set_scaling(False)
 
762
        elif (self.scale_type == self.config.CONSOLE_SCALE_ALWAYS
 
763
              and curscale == False):
 
764
            self.viewer.set_scaling(True)
 
765
        elif (self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN
 
766
              and curscale != fs):
 
767
            self.viewer.set_scaling(fs)
 
768
 
 
769
        # Refresh viewer size
 
770
        vnc_scroll.queue_resize()
 
771
 
 
772
    def auth_login(self, ignore):
 
773
        self.set_credentials()
 
774
        self.activate_viewer_page()
 
775
 
 
776
    def toggle_fullscreen(self, src):
 
777
        do_fullscreen = src.get_active()
 
778
        self._change_fullscreen(do_fullscreen)
 
779
 
 
780
    def leave_fullscreen(self, ignore=None):
 
781
        self._change_fullscreen(False)
 
782
 
 
783
    def _change_fullscreen(self, do_fullscreen):
 
784
        self.widget("control-fullscreen").set_active(do_fullscreen)
 
785
 
 
786
        if do_fullscreen:
 
787
            self.topwin.fullscreen()
 
788
            self.fs_toolbar.show()
 
789
            self.fs_drawer.set_active(True)
 
790
            self.widget("toolbar-box").hide()
 
791
            self.widget("details-menubar").hide()
 
792
        else:
 
793
            self.fs_toolbar.hide()
 
794
            self.fs_drawer.set_active(False)
 
795
            self.topwin.unfullscreen()
 
796
 
 
797
            if self.widget("details-menu-view-toolbar").get_active():
 
798
                self.widget("toolbar-box").show()
 
799
            self.widget("details-menubar").show()
 
800
 
 
801
        self.update_scaling()
 
802
 
 
803
    def size_to_vm(self, src_ignore):
 
804
        # Resize the console to best fit the VM resolution
 
805
        if not self.viewer:
 
806
            return
 
807
        if not self.viewer.get_desktop_resolution():
 
808
            return
 
809
 
 
810
        w, h = self.viewer.get_desktop_resolution()
 
811
        self.topwin.unmaximize()
 
812
        self.topwin.resize(1, 1)
 
813
        self.queue_scroll_resize_helper(w, h)
 
814
 
 
815
    def send_key(self, src, keys):
 
816
        ignore = src
 
817
 
 
818
        if keys != None:
 
819
            self.viewer.send_keys(keys)
 
820
 
 
821
 
 
822
    ##########################
 
823
    # State tracking methods #
 
824
    ##########################
 
825
 
 
826
    def view_vm_status(self):
 
827
        status = self.vm.status()
 
828
        if status == libvirt.VIR_DOMAIN_SHUTOFF:
 
829
            self.activate_unavailable_page(_("Guest not running"))
 
830
        else:
 
831
            if status == libvirt.VIR_DOMAIN_CRASHED:
 
832
                self.activate_unavailable_page(_("Guest has crashed"))
 
833
 
 
834
    def close_viewer(self):
 
835
        viewport = self.widget("console-vnc-viewport")
 
836
        if self.viewer is None:
 
837
            return
 
838
 
 
839
        v = self.viewer # close_viewer() can be reentered
 
840
        self.viewer = None
 
841
        w = v.get_widget()
 
842
 
 
843
        if w and w in viewport.get_children():
 
844
            viewport.remove(w)
 
845
 
 
846
        v.close()
 
847
        v.cleanup()
 
848
        self.viewer_connected = False
 
849
        self.leave_fullscreen()
 
850
 
 
851
    def update_widget_states(self, vm, status_ignore):
 
852
        runable = vm.is_runable()
 
853
        pages   = self.widget("console-pages")
 
854
        page    = pages.get_current_page()
 
855
 
 
856
        if runable:
 
857
            if page != PAGE_UNAVAILABLE:
 
858
                pages.set_current_page(PAGE_UNAVAILABLE)
 
859
 
 
860
            self.view_vm_status()
 
861
            return
 
862
 
 
863
        elif page in [PAGE_UNAVAILABLE, PAGE_VIEWER]:
 
864
            if self.viewer and self.viewer.is_open():
 
865
                self.activate_viewer_page()
 
866
            else:
 
867
                self.viewerRetriesScheduled = 0
 
868
                self.viewerRetryDelay = 125
 
869
                self.try_login()
 
870
 
 
871
        return
 
872
 
 
873
    ###################
 
874
    # Page Navigation #
 
875
    ###################
 
876
 
 
877
    def activate_unavailable_page(self, msg):
 
878
        """
 
879
        This function is passed to serialcon.py at least, so change
 
880
        with care
 
881
        """
 
882
        self.close_viewer()
 
883
        self.widget("console-pages").set_current_page(PAGE_UNAVAILABLE)
 
884
        self.widget("details-menu-vm-screenshot").set_sensitive(False)
 
885
        self.widget("console-unavailable").set_label("<b>" + msg + "</b>")
 
886
 
 
887
    def activate_auth_page(self, withPassword=True, withUsername=False):
 
888
        (pw, username) = self.config.get_console_password(self.vm)
 
889
        self.widget("details-menu-vm-screenshot").set_sensitive(False)
 
890
 
 
891
        if withPassword:
 
892
            self.widget("console-auth-password").show()
 
893
            self.widget("label-auth-password").show()
 
894
        else:
 
895
            self.widget("console-auth-password").hide()
 
896
            self.widget("label-auth-password").hide()
 
897
 
 
898
        if withUsername:
 
899
            self.widget("console-auth-username").show()
 
900
            self.widget("label-auth-username").show()
 
901
        else:
 
902
            self.widget("console-auth-username").hide()
 
903
            self.widget("label-auth-username").hide()
 
904
 
 
905
        self.widget("console-auth-username").set_text(username)
 
906
        self.widget("console-auth-password").set_text(pw)
 
907
 
 
908
        if self.config.has_keyring():
 
909
            self.widget("console-auth-remember").set_sensitive(True)
 
910
            if pw != "" or username != "":
 
911
                self.widget("console-auth-remember").set_active(True)
 
912
            else:
 
913
                self.widget("console-auth-remember").set_active(False)
 
914
        else:
 
915
            self.widget("console-auth-remember").set_sensitive(False)
 
916
        self.widget("console-pages").set_current_page(PAGE_AUTHENTICATE)
 
917
        if withUsername:
 
918
            self.widget("console-auth-username").grab_focus()
 
919
        else:
 
920
            self.widget("console-auth-password").grab_focus()
 
921
 
 
922
 
 
923
    def activate_viewer_page(self):
 
924
        self.widget("console-pages").set_current_page(PAGE_VIEWER)
 
925
        self.widget("details-menu-vm-screenshot").set_sensitive(True)
 
926
        if self.viewer and self.viewer.get_widget():
 
927
            self.viewer.get_widget().grab_focus()
 
928
 
 
929
    def page_changed(self, ignore1=None, ignore2=None, ignore3=None):
 
930
        self.set_allow_fullscreen()
 
931
 
 
932
    def set_allow_fullscreen(self):
 
933
        cpage = self.widget("console-pages").get_current_page()
 
934
        dpage = self.widget("details-pages").get_current_page()
 
935
 
 
936
        allow_fullscreen = (dpage == 0 and
 
937
                            cpage == PAGE_VIEWER and
 
938
                            self.viewer_connected)
 
939
 
 
940
        self.widget("control-fullscreen").set_sensitive(allow_fullscreen)
 
941
        self.widget("details-menu-view-fullscreen").set_sensitive(allow_fullscreen)
 
942
 
 
943
    def disconnected(self):
 
944
        errout = ""
 
945
        if self.tunnels is not None:
 
946
            errout = self.tunnels.get_err_output()
 
947
            self.tunnels.close_all()
 
948
            self.tunnels = None
 
949
 
 
950
        self.close_viewer()
 
951
        logging.debug("Viewer disconnected")
 
952
 
 
953
        # Make sure modifiers are set correctly
 
954
        self.viewer_focus_changed()
 
955
 
 
956
        if (self.skip_connect_attempt() or
 
957
            self.guest_not_avail()):
 
958
            # Exit was probably for legitimate reasons
 
959
            self.view_vm_status()
 
960
            return
 
961
 
 
962
        error = _("Error: viewer connection to hypervisor host got refused "
 
963
                  "or disconnected!")
 
964
        if errout:
 
965
            logging.debug("Error output from closed console: %s" % errout)
 
966
            error += "\n\nError: %s" % errout
 
967
 
 
968
        self.activate_unavailable_page(error)
 
969
 
 
970
    def _set_viewer_connected(self, val):
 
971
        self._viewer_connected = val
 
972
        self.set_allow_fullscreen()
 
973
    def _get_viewer_connected(self):
 
974
        return self._viewer_connected
 
975
    viewer_connected = property(_get_viewer_connected, _set_viewer_connected)
 
976
 
 
977
    def connected(self):
 
978
        self.viewer_connected = True
 
979
        logging.debug("Viewer connected")
 
980
        self.activate_viewer_page()
 
981
 
 
982
        # Had a succesfull connect, so reset counters now
 
983
        self.viewerRetriesScheduled = 0
 
984
        self.viewerRetryDelay = 125
 
985
 
 
986
        # Make sure modifiers are set correctly
 
987
        self.viewer_focus_changed()
 
988
 
 
989
    def schedule_retry(self):
 
990
        if self.viewerRetriesScheduled >= 10:
 
991
            logging.error("Too many connection failures, not retrying again")
 
992
            return
 
993
 
 
994
        self.safe_timeout_add(self.viewerRetryDelay, self.try_login)
 
995
 
 
996
        if self.viewerRetryDelay < 2000:
 
997
            self.viewerRetryDelay = self.viewerRetryDelay * 2
 
998
 
 
999
    def skip_connect_attempt(self):
 
1000
        return (self.viewer or
 
1001
                not self.is_visible())
 
1002
 
 
1003
    def guest_not_avail(self):
 
1004
        return (self.vm.is_shutoff() or self.vm.is_crashed())
 
1005
 
 
1006
    def try_login(self, src_ignore=None):
 
1007
        if self.viewer_connecting:
 
1008
            return
 
1009
 
 
1010
        try:
 
1011
            self.viewer_connecting = True
 
1012
            self._try_login()
 
1013
        finally:
 
1014
            self.viewer_connecting = False
 
1015
 
 
1016
    def _try_login(self):
 
1017
        if self.skip_connect_attempt():
 
1018
            # Don't try and login for these cases
 
1019
            return
 
1020
 
 
1021
        if self.guest_not_avail():
 
1022
            # Guest isn't running, schedule another try
 
1023
            self.activate_unavailable_page(_("Guest not running"))
 
1024
            self.schedule_retry()
 
1025
            return
 
1026
 
 
1027
        try:
 
1028
            (protocol, transport,
 
1029
             connhost, connuser, connport,
 
1030
             gaddr, gport, gsocket) = self.vm.get_graphics_console()
 
1031
        except Exception, e:
 
1032
            # We can fail here if VM is destroyed: xen is a bit racy
 
1033
            # and can't handle domain lookups that soon after
 
1034
            logging.exception("Getting graphics console failed: %s" % str(e))
 
1035
            return
 
1036
 
 
1037
        if protocol is None:
 
1038
            logging.debug("No graphics configured for guest")
 
1039
            self.activate_unavailable_page(
 
1040
                            _("Graphical console not configured for guest"))
 
1041
            return
 
1042
 
 
1043
        if protocol not in self.config.embeddable_graphics():
 
1044
            logging.debug("Don't know how to show graphics type '%s' "
 
1045
                          "disabling console page" % protocol)
 
1046
 
 
1047
            msg = (_("Cannot display graphical console type '%s'")
 
1048
                     % protocol)
 
1049
            if protocol == "spice":
 
1050
                msg += ":\n %s" % self.config.get_spice_error()
 
1051
 
 
1052
            self.activate_unavailable_page(msg)
 
1053
            return
 
1054
 
 
1055
        if (gport == -1 and not gsocket):
 
1056
            self.activate_unavailable_page(
 
1057
                            _("Graphical console is not yet active for guest"))
 
1058
            self.schedule_retry()
 
1059
            return
 
1060
 
 
1061
        self.activate_unavailable_page(
 
1062
                _("Connecting to graphical console for guest"))
 
1063
 
 
1064
        logging.debug("Starting connect process for "
 
1065
                      "proto=%s trans=%s connhost=%s connuser=%s "
 
1066
                      "connport=%s gaddr=%s gport=%s gsocket=%s" %
 
1067
                      (protocol, transport, connhost, connuser, connport,
 
1068
                       gaddr, gport, gsocket))
 
1069
        try:
 
1070
            if protocol == "vnc":
 
1071
                self.viewer = VNCViewer(self)
 
1072
                self.widget("console-vnc-viewport").add(
 
1073
                                                    self.viewer.get_widget())
 
1074
                self.viewer.init_widget()
 
1075
            elif protocol == "spice":
 
1076
                self.viewer = SpiceViewer(self)
 
1077
 
 
1078
            self.set_enable_accel()
 
1079
 
 
1080
            if transport in ("ssh", "ext"):
 
1081
                if self.tunnels:
 
1082
                    # Tunnel already open, no need to continue
 
1083
                    return
 
1084
 
 
1085
                self.tunnels = Tunnels(connhost, connuser, connport,
 
1086
                                       gaddr, gport, gsocket)
 
1087
                fd = self.tunnels.open_new()
 
1088
                if fd >= 0:
 
1089
                    self.viewer.open_fd(fd)
 
1090
 
 
1091
            else:
 
1092
                self.viewer.open_host(connhost, connuser,
 
1093
                                      str(gport), gsocket)
 
1094
 
 
1095
        except Exception, e:
 
1096
            logging.exception("Error connection to graphical console")
 
1097
            self.activate_unavailable_page(
 
1098
                    _("Error connecting to graphical console") + ":\n%s" % e)
 
1099
 
 
1100
    def set_credentials(self, src_ignore=None):
 
1101
        passwd = self.widget("console-auth-password")
 
1102
        if passwd.flags() & gtk.VISIBLE:
 
1103
            self.viewer.set_credential_password(passwd.get_text())
 
1104
        username = self.widget("console-auth-username")
 
1105
        if username.flags() & gtk.VISIBLE:
 
1106
            self.viewer.set_credential_username(username.get_text())
 
1107
 
 
1108
        if self.widget("console-auth-remember").get_active():
 
1109
            self.config.set_console_password(self.vm, passwd.get_text(),
 
1110
                                             username.get_text())
 
1111
 
 
1112
    def queue_scroll_resize_helper(self, w, h):
 
1113
        """
 
1114
        Resize the VNC container widget to the requested size. The new size
 
1115
        isn't a hard requirment so the user can still shrink the window
 
1116
        again, as opposed to set_size_request
 
1117
        """
 
1118
        widget = self.widget("console-vnc-scroll")
 
1119
        signal_holder = []
 
1120
 
 
1121
        def restore_scroll(src):
 
1122
            is_scale = self.viewer.get_scaling()
 
1123
 
 
1124
            if is_scale:
 
1125
                w_policy = gtk.POLICY_NEVER
 
1126
                h_policy = gtk.POLICY_NEVER
 
1127
            else:
 
1128
                w_policy = gtk.POLICY_AUTOMATIC
 
1129
                h_policy = gtk.POLICY_AUTOMATIC
 
1130
 
 
1131
            src.set_policy(w_policy, h_policy)
 
1132
            return False
 
1133
 
 
1134
        def unset_cb(src):
 
1135
            src.queue_resize_no_redraw()
 
1136
            self.safe_idle_add(restore_scroll, src)
 
1137
            return False
 
1138
 
 
1139
        def request_cb(src, req):
 
1140
            signal_id = signal_holder[0]
 
1141
            req.width = w
 
1142
            req.height = h
 
1143
 
 
1144
            src.disconnect(signal_id)
 
1145
 
 
1146
            self.safe_idle_add(unset_cb, widget)
 
1147
            return False
 
1148
 
 
1149
        # Disable scroll bars while we resize, since resizing to the VM's
 
1150
        # dimensions can erroneously show scroll bars when they aren't needed
 
1151
        widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
 
1152
 
 
1153
        signal_id = widget.connect("size-request", request_cb)
 
1154
        signal_holder.append(signal_id)
 
1155
 
 
1156
        widget.queue_resize()
 
1157
 
 
1158
    def scroll_size_allocate(self, src_ignore, req):
 
1159
        if not self.viewer or not self.viewer.get_desktop_resolution():
 
1160
            return
 
1161
 
 
1162
        scroll = self.widget("console-vnc-scroll")
 
1163
        is_scale = self.viewer.get_scaling()
 
1164
 
 
1165
        dx = 0
 
1166
        dy = 0
 
1167
        align_ratio = float(req.width) / float(req.height)
 
1168
 
 
1169
        desktop_w, desktop_h = self.viewer.get_desktop_resolution()
 
1170
        if desktop_h == 0:
 
1171
            return
 
1172
        desktop_ratio = float(desktop_w) / float(desktop_h)
 
1173
 
 
1174
        if not is_scale:
 
1175
            # Scaling disabled is easy, just force the VNC widget size. Since
 
1176
            # we are inside a scrollwindow, it shouldn't cause issues.
 
1177
            scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
1178
            self.viewer.get_widget().set_size_request(desktop_w, desktop_h)
 
1179
            return
 
1180
 
 
1181
        # Make sure we never show scrollbars when scaling
 
1182
        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
 
1183
 
 
1184
        # Make sure there is no hard size requirement so we can scale down
 
1185
        self.viewer.get_widget().set_size_request(-1, -1)
 
1186
 
 
1187
        # Make sure desktop aspect ratio is maintained
 
1188
        if align_ratio > desktop_ratio:
 
1189
            desktop_w = int(req.height * desktop_ratio)
 
1190
            desktop_h = req.height
 
1191
            dx = (req.width - desktop_w) / 2
 
1192
 
 
1193
        else:
 
1194
            desktop_w = req.width
 
1195
            desktop_h = int(req.width / desktop_ratio)
 
1196
            dy = (req.height - desktop_h) / 2
 
1197
 
 
1198
        viewer_alloc = gtk.gdk.Rectangle(x=dx,
 
1199
                                         y=dy,
 
1200
                                         width=desktop_w,
 
1201
                                         height=desktop_h)
 
1202
 
 
1203
        self.viewer.get_widget().size_allocate(viewer_alloc)
 
1204
 
 
1205
vmmGObjectUI.type_register(vmmConsolePages)