#!/usr/bin/python # # Copyright (C) 2007 Daniel P. Berrange # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # # # A VNC viewer program displaying multiple VNC sessions # on a 3-d spinning cube.... # import sys import gobject import gtk import gtkvnc import math import clutter import clutter.keysyms import clutter.cluttergtk if len(sys.argv) < 2: print "syntax: gvncclutter.py DISPLAY-1 [DISPLAY-2....]" sys.exit(1) class ClutterVNC: def __init__(self, layout, group, host, port, password): self.vnc = gtkvnc.Display() self.tex = None self.group = group layout.add(self.vnc) self.vnc.realize() self.vnc.set_credential(gtkvnc.CREDENTIAL_PASSWORD, password) self.vnc.open_host(host, port) self.vnc.connect("vnc-desktop-resize", self.reinit_texture) self.vnc.connect("vnc-desktop-update", self.refresh_texture) def size(self): return (self.vnc.get_width(), self.vnc.get_height()) def show(self): self.tex.show() def reinit_texture(self, vnc, w, h): new = 0 if self.tex is None: self.tex = clutter.Texture() new = 1 self.tex.set_pixbuf(self.vnc.get_pixbuf()) self.tex.set_size(w, h) if new: self.group.add(self.tex) def refresh_texture(self, vnc, x,y,w,h): if self.tex is not None: self.tex.set_pixbuf(self.vnc.get_pixbuf()) class ClutterVNCSet: def __init__(self, layout): self.layout = layout self.embed = clutter.cluttergtk.Embed() self.embed.set_flags(gtk.CAN_FOCUS) self.embed.grab_focus() self.stage = self.embed.get_stage() self.stage.set_color(clutter.Color(0, 0, 0, 255)) self.stage.set_size(800,800) self.group = clutter.Group() self.group.set_position(0,0) self.group.set_size(800,800) self.stage.add(self.group) self.group.show() self.vncs = [] self.current = 0 self.offset = 0 self.animate = 0 self.timeline = clutter.Timeline(30, 30) self.timeline.connect("new-frame", self.do_frame) self.layout.add(self.embed) self.embed.show_all() self.group.connect("add", self.child_added) self.stage.connect("button-press-event", self.button_event) self.stage.connect("button-release-event", self.button_event) self.stage.connect("motion-event", self.button_event) self.stage.connect("key-press-event", self.key_event) self.stage.connect("key-release-event", self.key_event) def do_frame(self, timeline, frame): if self.animate == 1: self.offset = 1-(frame/30.0) else: self.offset = (frame/30.0)-1 self.reposition_all() def child_added(self, src, child): self.reposition_all() def reposition_one(self, tex, pos): (w,h) = self.stage.get_size() (cw,ch) = tex.get_size() nsides = len(self.vncs) rot = 360/nsides * (pos-self.current+self.offset) if nsides > 2: depth = (cw/2) / math.tan(math.radians((360/nsides)/2)) else: depth = 0 # XXX kill when depth sorting is done tex.set_opacity(127) tex.set_position((w-cw)/2, (h-cw)/2) tex.rotate_y(int(rot), (cw/2), int(-depth)) tex.show() #print "Rotate %d %f at %f depth %f" % (self.current, self.offset, rot, depth) def reposition_all(self): for i in range(len(self.vncs)): if self.vncs[i].tex is not None: self.reposition_one(self.vncs[i].tex, i) # XXX depth sort def button_state(self, ev): state = 0 if ev.get_state() & clutter.BUTTON1_MASK: state = state | (1 << 0) elif ev.get_state() & clutter.BUTTON2_MASK: state = state | (1 << 1) elif ev.get_state() & clutter.BUTTON3_MASK: state = state | (1 << 2) elif ev.get_state() & clutter.BUTTON4_MASK: state = state | (1 << 3) elif ev.get_state() & clutter.BUTTON5_MASK: state = state | (1 << 4) if ev.type == clutter.BUTTON_PRESS: state = state | (1 << (ev.button-1)) elif ev.type == clutter.BUTTON_RELEASE: state = state & ~(1 << (ev.button-1)) return state def button_event(self, src, ev): vnc = self.vncs[self.current] (w,h) = self.stage.get_size() (cw,ch) = vnc.tex.get_size() x = ev.x - ((w-cw)/2) y = ev.y - ((h-ch)/2) state = self.button_state(ev) #print "Button " + str(state) + " " + str(x) + " " + str(y) vnc.vnc.send_pointer(state, x, y) def key_event(self, src, ev): if ev.type == clutter.KEY_PRESS and \ (ev.keyval == clutter.keysyms.Page_Up or ev.keyval == clutter.keysyms.Page_Down) and \ (ev.get_state() & clutter.CONTROL_MASK) and \ (ev.get_state() & clutter.MOD1_MASK): if ev.keyval == clutter.keysyms.Page_Up: self.animate = -1 if self.current == 0: self.current = len(self.vncs)-1 else: self.current = self.current - 1 else: self.animate = 1 if self.current == (len(self.vncs)-1): self.current = 0 else: self.current = self.current + 1 self.timeline.start() return vnc = self.vncs[self.current] kv = gtk.gdk.keyval_name(ev.keyval) if ev.type == clutter.KEY_PRESS: vnc.vnc.send_keys([kv], 1) else: vnc.vnc.send_keys([kv], 2) def add_vnc(self, host, port, passwd): vnc = ClutterVNC(self.layout, self.group, host, port, passwd) self.vncs.append(vnc) def split_host_port(disp): offset = disp.find(":") if offset != -1: return (disp[:offset], str(5900 + int(disp[offset+1:]))) else: return (disp, "5900") window = gtk.Window() layout = gtk.VBox() window.add(layout) window.show_all() vncset = ClutterVNCSet(layout) for i in range(len(sys.argv)-1): (host,port) = split_host_port(sys.argv[i+1]) vncset.add_vnc(host, port, "123456") window.set_focus(vncset.embed) gtk.main()