root/src/apps/remotedesktop/RemoteDesktop.cpp
/*
 * Copyright 2009-2017, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 */

#include <Application.h>
#include <FindDirectory.h>
#include <Path.h>
#include <Screen.h>
#include <Window.h>

#include "RemoteView.h"

#include <new>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>


void
print_usage(const char *app)
{
        printf("usage:\t%s <host> [-p <port>] [-w <width>] [-h <height>]\n", app);
        printf("usage:\t%s <user@host> -s [<sshPort>] [-p <port>] [-w <width>]"
                " [-h <height>] [-c <command>]\n", app);
        printf("\t%s --help\n\n", app);

        printf("Connect to & run applications from a different computer\n\n");
        printf("Arguments available for use:\n\n");
        printf("\t-p\t\tspecify the port to communicate on (default 10900)\n");
        printf("\t-c\t\tsend a command to the other computer (default Terminal)\n");
        printf("\t-s\t\tuse SSH, optionally specify the SSH port to use (22)\n");
        printf("\t-w\t\tmake the virtual desktop use the specified width\n");
        printf("\t-h\t\tmake the virtual desktop use the specified height\n");
        printf("\nIf no width and height are specified, the window is opened with"
                " the size of the the local screen.\n");
}


int
main(int argc, char *argv[])
{
        if (argc < 2 || strcmp(argv[1], "--help") == 0) {
                print_usage(argv[0]);
                return 1;
        }

        uint16 port = 10900;
        uint16 sshPort = 22;
        int32 width = -1;
        int32 height = -1;
        bool useSSH = false;
        const char *command = NULL;
        const char *host = argv[1];

        for (int32 i = 2; i < argc; i++) {
                if (strcmp(argv[i], "-p") == 0) {
                        if (argc <= i + 1 || sscanf(argv[i + 1], "%" B_SCNu16, &port) != 1) {
                                print_usage(argv[0]);
                                return 2;
                        }

                        i++;
                        continue;
                }

                if (strcmp(argv[i], "-w") == 0) {
                        if (argc <= i + 1 || sscanf(argv[i + 1], "%" B_SCNd32, &width) != 1) {
                                print_usage(argv[0]);
                                return 2;
                        }

                        i++;
                        continue;
                }

                if (strcmp(argv[i], "-h") == 0) {
                        if (argc <= i + 1 || sscanf(argv[i + 1], "%" B_SCNd32, &height) != 1) {
                                print_usage(argv[0]);
                                return 2;
                        }

                        i++;
                        continue;
                }

                if (strcmp(argv[i], "-s") == 0) {
                        if((i + 1 < argc) && sscanf(argv[i + 1], "%" B_SCNu16, &sshPort) == 1) {
                                i++;
                        }

                        useSSH = true;
                        continue;
                }

                if (strcmp(argv[i], "-c") == 0) {
                        if (argc <= i + 1) {
                                print_usage(argv[0]);
                                return 2;
                        }

                        i++;
                        command = argv[i];
                        continue;
                }

                print_usage(argv[0]);
                return 2;
        }

        if (command != NULL && !useSSH) {
                print_usage(argv[0]);
                return 2;
        }

        pid_t sshPID = -1;
        if (useSSH) {
                BPath terminalPath;
                if (command == NULL) {
                        if (find_directory(B_SYSTEM_APPS_DIRECTORY, &terminalPath)
                                        != B_OK) {
                                printf("failed to determine system-apps directory\n");
                                return 3;
                        }
                        if (terminalPath.Append("Terminal") != B_OK) {
                                printf("failed to append to system-apps path\n");
                                return 3;
                        }
                        command = terminalPath.Path();
                }

                char shellCommand[4096];
                snprintf(shellCommand, sizeof(shellCommand),
                        "echo connected; export TARGET_SCREEN=%" B_PRIu16 "; %s\n", port,
                        command);

                int pipes[4];
                if (pipe(&pipes[0]) != 0 || pipe(&pipes[2]) != 0) {
                        printf("failed to create redirection pipes\n");
                        return 3;
                }

                sshPID = fork();
                if (sshPID < 0) {
                        printf("failed to fork ssh process\n");
                        return 3;
                }

                if (sshPID == 0) {
                        // child code, redirect std* and execute ssh
                        close(STDOUT_FILENO);
                        close(STDIN_FILENO);
                        dup2(pipes[1], STDOUT_FILENO);
                        dup2(pipes[1], STDERR_FILENO);
                        dup2(pipes[2], STDIN_FILENO);
                        for (int32 i = 0; i < 4; i++)
                                close(pipes[i]);

                        char localRedirect[50];
                        sprintf(localRedirect, "localhost:%" B_PRIu16 ":localhost:%"
                                B_PRIu16, port, port);

                        char portNumber[10];
                        sprintf(portNumber, "%" B_PRIu16, sshPort);

                        int result = execl("ssh", "-C", "-L", localRedirect,
                                "-p", portNumber, "-o", "ExitOnForwardFailure=yes", host,
                                shellCommand, NULL);

                        // we don't get here unless there was an error in executing
                        printf("failed to execute ssh process in child\n");
                        return result;
                } else {
                        close(pipes[1]);
                        close(pipes[2]);

                        char buffer[10];
                        read(pipes[0], buffer, sizeof(buffer));
                                // block until connected/error message from ssh

                        host = "localhost";
                }
        }

        BApplication app("application/x-vnd.Haiku-RemoteDesktop");
        BRect windowFrame = BRect(0, 0, width - 1, height - 1);
        if (!windowFrame.IsValid()) {
                BScreen screen;
                windowFrame = screen.Frame();
        }

        BWindow *window = new(std::nothrow) BWindow(windowFrame, "RemoteDesktop",
                B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE);

        if (window == NULL) {
                printf("no memory to allocate window\n");
                return 4;
        }

        RemoteView *view = new(std::nothrow) RemoteView(window->Bounds(), host,
                port);
        if (view == NULL) {
                printf("no memory to allocate remote view\n");
                return 4;
        }

        status_t init = view->InitCheck();
        if (init != B_OK) {
                printf("initialization of remote view failed: %s\n", strerror(init));
                delete view;
                return 5;
        }

        window->AddChild(view);
        view->MakeFocus();
        window->Show();
        app.Run();

        if (sshPID >= 0)
                kill(sshPID, SIGHUP);

        return 0;
}