#!/usr/bin/python
#
# Copyright (C) 2007 Daniel P. Berrange <dan@berrange.com>
#
# 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()
