"""
ajaxWM - the free web based window manager for remote terminals
Copyright (C) 2007 Dennis Felsing, Andreas Waidler

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 3 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, see <http://www.gnu.org/licenses/>.

$Id: Window.py 76 2007-12-15 11:26:24Z r0q $
"""

#{{{ Imports
import threading
import os
import pty
import fcntl
import time

from Terminal import Terminal
from Ssh import Ssh
import ajaxwm
#}}}
class Window(threading.Thread):#{{{
    """
For every window a new thread is spawned. Objects of the window class 
control one Terminal each. So they also have to open and close the pty for
the Terminal. 

A programm, which is specified by cmd, is assigned to the Terminal object 
and set to ssh localhost by default. By this the client can for example 
login on the server. But of course this is not the only way of using an
ajaxWM-Window. Another useful thing is assigning a screen session
read-only, so one interact with that screen session, while others can watch
him from their PC.

Windows can only be spawned by and accessed via an object of the Session 
class.
    """
    def __init__(self, username, host, port, password, width, height):#{{{
        threading.Thread.__init__(self)
        
        self.username = username
        self.host = host
        self.port = port
        self.password = password
        self.width = width
        self.height = height
    #}}}                        
    def run(self):#{{{                              
        """
Forks a virtual terminal and there runs the specified command
gets called when the thread is started.
        """
        pid, fd = pty.fork()
        if pid == 0:
            # The process of the virtual terminal
            try:
                # A list of the existing file descriptors.
                fdl = map(int, os.listdir('/proc/self/fd'))
            except OSError:
                fdl = range(256)
            for i in filter(lambda i: i > 2, fdl):
                # We only need stdin(i == 0), stdout(1) and stderr(2).
                try:
                    os.close(i)
                except OSError:
                    pass
            # SSH instead of /bin/login even for login on localhost because 
            # /bin/login requires root access and I myself wouldn't try to use
            # this program as root. (paranoia is healthy!) If you intended to 
            # do so, better use another user with as little rights as 
            # possible. The only disadvantage is the longer time to login 
            # (or to realize that you can't) when using SSH.
            ssh = Ssh()
            cmd = ssh.login(self.username, self.host, self.port)
            env = {'COLUMNS': str(self.width), 
                   'LINES': str(self.height), 
                   'TERM': 'linux',
                   'PATH': os.environ['PATH']}
            # See the POSIX execvpe (which gets called) for information.
            os.execvpe(cmd[0], cmd, env)
        else:
            # The main/parent process
            self.pid = pid
            self.fd = fd
            
            # sets the file status flags for the open file descriptor fd to 
            # non blocking mode (just continue if nothing can be read)
            fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
            self.terminal = Terminal(self.width, self.height)
            self.resize(self.width, self.height)
            self.writePassword()
    #}}}
    def __del__(self):#{{{
        # Kill all base classes.
        for base in self.__class__.__bases__:
            # Avoid problems with diamond inheritance.
            basekey = 'del_' + str(base)
            if not hasattr(self, basekey):
                setattr(self, basekey, 1)
            else:
                continue
            # Call this base class' destructor if it has one.
            if hasattr(base, '__del__'):
                base.__del__(self)
        try:
            # Closing the pty also kills all processes on it.
            os.close(self.fd)
        except (IOError, OSError):
            pass
    #}}}
    def resize(self, width=80, height=25):#{{{
        """
Client has resized his window, so now the virtual terminal and the
object of the Terminal class have to be informed and actualised.
        """
        if not self.width is width and not self.height is height:
            self.width, self.height = width, height
            self.terminal.resize(width, height)
            fcntl.ioctl(fd, termios.TIOCSWINSZ, 
                        struct.pack('HHHH', height, width, 0, 0))
    #}}}
    def read(self):#{{{
        """Makes the Terminal object read the content of the pty."""
        try:
            self.terminal.write(ajaxwm.read(self.fd))
        except (KeyError, IOError, OSError):
            del self
    #}}}
    def write(self, s): #{{{
        """Writes new keypresses to the pty."""
        try:
            os.write(self.fd, s.encode(ajaxwm.ENCODING))
        except UnicodeEncodeError:
            os.write(self.fd, s)
        except (IOError, OSError):
            del self
    #}}}
    def html(self, color=1):#{{{
        """Returns the html version of the content of the window."""
        self.read()
        self.join()
        try:
            return self.terminal.dumphtml(color)
        except KeyError:
            return False
    #}}}
    def writePassword(self):#{{{
        while 1:
            try:
                if 'Password:' in ajaxwm.read(self.fd):
                    self.write(self.password + u'\n')
                    return 
            except OSError:
                pass
            time.sleep(0.2)
    #}}}
#}}}

#------------------------------------------------------------------------------
# vim: fdm=marker ts=4 sw=4 expandtab 
