###########################################################################
# Implements the client side of the epoptes communications protocol.
# The daemon reads this file when it starts, and sends it to clients when they
# connect. The clients actually source it and then wait for further commands.
#
# Copyright (C) 2010, 2012 Alkis Georgopoulos <alkisg@gmail.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 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/>.
#
# On Debian GNU/Linux systems, the complete text of the GNU General
# Public License can be found in `/usr/share/common-licenses/GPL'.
###########################################################################

# Output a message and exit with an error.
# Parameters:
# $1..$N = The message.
die() {
    echo "epoptes-client ERROR: $@" >&2
    exit 1
}

# Calculate, export and return a collection of useful variables.
info() {
    local server_ip def_iface

    if [ -z "$cached_info" ]; then
        VERSION=${VERSION:-0.4.3} # Just in case the client wasn't updated
        test -n "$USER" || USER=$(whoami)
        NAME=$(getent passwd "$UID" | cut -d':' -f5 | cut -d',' -f1)
        test -n "$HOME" || HOME=$(getent passwd "$UID" | cut -d: -f6)
        if [ -n "$LTSP_CLIENT_HOSTNAME" ]; then
            HOSTNAME="$LTSP_CLIENT_HOSTNAME"
        else
            HOSTNAME=$(hostname)
            test -n "$HOSTNAME" || die "Empty hostname"
        fi
        if [ -n "$LTSP_CLIENT" ] && [ -n "$LTSP_CLIENT_MAC" ]; then
            # LTSP exports those vars, use them if available.
            MAC="$LTSP_CLIENT_MAC"
            IP="$LTSP_CLIENT"
        else
            server_ip=$(getent hosts "$SERVER" | cut -d' ' -f1)
            def_iface=$(ip -oneline -family inet route get "$server_ip" \
                | sed -n '/.* dev \([^ ]*\).*/s//\1/p')
            test "${def_iface:-lo}" = "lo" && def_iface=$(ip -oneline -family \
                inet route show | sed -n '/^default .* dev \([^ ]*\).*/s//\1/p')
            def_iface=${def_iface:-eth0}
            MAC=$(ip -oneline -family inet link show dev "$def_iface" \
                | sed "s/.*ether \([^ ]*\).*/\\1/")
            MAC=$(echo "$MAC" | sed 'y/abcdef-/ABCDEF:/;s/[^A-F0-9:]//g')
            test -n "$MAC" || die "Empty MAC"
            IP=$(ip -oneline -family inet addr show dev "$def_iface" \
                | sed "s/.* \([0-9.]*\)\/.*/\\1/")
            test -n "$IP" || die "Empty IP"
        fi
        CPU=$(cat /proc/cpuinfo | grep "^model name" | head -1 | sed "s/.*: //")
        RAM=$(free -m | grep "^Mem" | awk '{print $2}')
        VGA=$(lspci -nn -m | sed -n -e '/"VGA/s/[^"]* "[^"]*" "[^"]*" "\([^"]*\)" .*/\1/p')
        OS=$(uname -o)

        export VERSION USER NAME HOME HOSTNAME MAC IP CPU RAM VGA OS
        cached_info=true
    fi
    cat <<EOF
uid=$UID
type=$TYPE
version=$VERSION
user=$USER
name=$NAME
home=$HOME
hostname=$HOSTNAME
mac=$MAC
ip=$IP
cpu=$CPU
ram=$RAM
vga=$VGA
os=$OS
EOF
}

# Execute a command in the background and optionally print its pid.
# For internal use. Parameters:
# [-p]   = print the pid.
# $1..$N = the command and its parameters.
background() {
    local print_pid

    if [ "$1" = "-p" ]; then
        print_pid=true
        shift
    fi

    # On root clients, try to get the active DISPLAY, the command may need it.
    test "$UID" -eq 0 && export $(./get-display)

    # The command is ran on a subshell with stdin and stdout redirected to
    # /dev/null, so that it doesn't interfere with the output of other commands.
    # stderr isn't changed, i.e. ~/.xsession-errors will be used.
    ( "$@" 0</dev/null >/dev/null ) &

    test -n "$print_pid" && echo $!
}

# Execute a command in the background.
# Parameters:
# $1 = the command.
execute() {
    local launcher

    # Do some logging, either in ~/.xsession-errors or on the console.
    echo "$(LANG=C date '+%c'), epoptes-client executing: $1" >&2

    case "$1" in
        '')
            echo "Can't execute an empty command." >&2
            ;;
        www.*)
            set "http://$1"
            launcher="xdg-open"
            ;;
        http:*|https:*|ftp:*|file:*|mailto:*)
            launcher="xdg-open"
            ;;
        *)
            if [ -e "$1" ] && ( [ ! -x "$1" ] || [ -d "$1" ] ); then
                launcher="xdg-open"
            elif which -- "$1" >/dev/null; then
                # Don't waste RAM for sh if it's just an executable.
                launcher=""
            fi
    esac
    # In all unhandled cases, run the command with sh -c.
    launcher=${launcher-sh -c}
    background $launcher "$1"
}


# Log out the connected user.
logout() {
    ./endsession --logout
}

# Reboot the client.
reboot() {
    ./endsession --reboot
}

# Shut down the client.
shutdown() {
    ./endsession --shutdown
}

# Create a thumbnail of the user screen.
# Parameters:
# $1 = thumbnail width.
# $2 = thumbnail height.
screenshot() {
    if ./screenshot "$1" "$2"; then
        BAD_SCREENSHOTS=0
    elif [ "$BAD_SCREENSHOTS" -eq 3 ]; then
        die "3 failed screenshots, exiting..."
    else
        BAD_SCREENSHOTS=$(($BAD_SCREENSHOTS+1))
    fi
}

# Lock the screen.
# Parameters:
# $1 = seconds to keep screen locked, 0 means forever - currently ignored.
# $2 = message to display to the user.
lock_screen() {
    test -n "$EPOPTES_LOCK_SCREEN_PID" && kill "$EPOPTES_LOCK_SCREEN_PID"
    EPOPTES_LOCK_SCREEN_PID=$(background -p ./lock-screen "$2")
}

# Unlock a locked screen.
unlock_screen() {
    if [ -n "$EPOPTES_LOCK_SCREEN_PID" ]; then
        kill "$EPOPTES_LOCK_SCREEN_PID"
        unset EPOPTES_LOCK_SCREEN_PID
    fi
}

# Mute the system sound.
# Parameters:
# $1 = seconds to keep sound muted, 0 means forever - currently ignored.
mute_sound() {
    background amixer -c 0 -q sset Master mute
}

# Unute the system sound.
unmute_sound() {
    background amixer -c 0 -q sset Master unmute
}

# Display some text to the user.
# Parameters:
# $1 = text.
# $2 = title.
# $3 = True/False, whether the text contains pango markup.
message() {
    background ./message "$1" "$2" "$3"
}

# Connect to the server to be monitored.
# Parameters:
# $1 = port.
get_monitored() {
    background x11vnc -noshm -24to32 -viewonly -connect_or_exit "$SERVER:$1"
}

# Connect to the server to get assistance.
# Parameters:
# $1 = port.
get_assisted() {
    background x11vnc -noshm -24to32 -connect_or_exit "$SERVER:$1"
}

# Deactivate the screensaver, in order for the users to watch a broadcast.
stop_screensaver() {
    if [ -x /usr/bin/gnome-screensaver-command ]; then
        gnome-screensaver-command -d
    fi
}

# Receive a broadcasted screen from the server.
# Parameters:
# $1 = port.
# $2 = password (encrypted as in ~/.vnc/passwd and octal-escaped).
# $3 = fullscreen.
receive_broadcast() {
    stop_receptions
    test "$UID" -eq 0 && export $(./get-display)
    xset dpms force on
    if [ -z "$VNCVIEWER" ]; then
        # ssvncviewer is one of the few that can cope with 24bpp (LP: #953512)
        if grep -q "Depth 24 pixmap format is 24 bpp" /var/log/Xorg.?.log &&
            [ -x /usr/bin/ssvncviewer ]
        then
            VNCVIEWER=ssvncviewer
        else
            VNCVIEWER=xvnc4viewer
        fi
    fi
    printf "$2" | {
        sleep 0.$(($(hexdump -e \"%d\" -n 2 /dev/urandom) % 50 + 50))
        if [ "$VNCVIEWER" = "ssvncviewer" ]; then
            exec ssvncviewer -shared -viewonly -passwd /dev/stdin \
                ${3:+fullscreen} "$SERVER:$1"
        elif [ "$VNCVIEWER" = "xvnc4viewer" ]; then
            exec xvnc4viewer -Shared -ViewOnly -passwd /dev/stdin \
                ${3:+FullScreen -UseLocalCursor=0 -MenuKey F13} "$SERVER:$1"
        else
            exec vncviewer -shared -viewonly -passwd /dev/stdin \
                ${3:+fullscreen} "$SERVER:$1"
        fi
    } >/dev/null 2>&1 &
    EPOPTES_VNCVIEWER_PID="$!"
}

# The vnc clients should automatically exit when the server is killed.
# Unfortunately, that isn't always true, so try to kill them anyway.
stop_receptions() {
    if [ -n "$EPOPTES_VNCVIEWER_PID" ]; then
        kill "$EPOPTES_VNCVIEWER_PID"
        unset EPOPTES_VNCVIEWER_PID
    fi
}

# Open a root terminal inside the X session.
root_term() {
    background xterm -e bash -l
}

# Send a screen session to the server using socat.
# Parameters:
# $1 = port.
remote_term() {
    local screen_params

    if [ "$UID" -eq 0 ]; then
        screen_params="bash -l"
    else
        screen_params="-l"
    fi
    background sh -c "
cd
sleep 1
TERM=xterm exec socat EXEC:'screen $screen_params',pty,stderr tcp:$SERVER:$1"
}

# Ping is called every few seconds just to make sure the connection is alive.
# But currently we use it as a workaround to kill stale clients too:
# Epoptes-client isn't registered as an X session client, and it doesn't
# exit automatically, so tell it to exit as soon as X is unavailable.
ping() {
    if [ "$UID" -gt 0 ]; then
        xprop -root -f EPOPTES_CLIENT 32c -set EPOPTES_CLIENT $$ || exit
    fi
}

# Display a message.
# Parameters:
# $1..$N = The message.
# echo()
# No need to implement it in the shell, it's embedded.

# Main

if [ -z "$UID" ] || [ -z "$TYPE" ] || [ -z "$SERVER" ]; then
    die "Required environment variables are missing."
fi

# Source the lsb init functions, for log_end_msg.
# Unfortunately it seems that Centos and Fedora don't have that file.
if [ -f /lib/lsb/init-functions ]; then
    ( # Use a subshell, we only need init-functions once
    . /lib/lsb/init-functions
    log_end_msg 0 >&5
    )
else
    echo "[ OK ]" >&5
fi

info
