/*
 * Copyright © 2011-2014 Intel Corporation
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of the
 * copyright holders not be used in advertising or publicity
 * pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#include <xwayland-config.h>

#if !defined(SYSV) && !defined(WIN32)
#include <sys/resource.h>
#endif

#include <stdio.h>
#include <errno.h>

#include <X11/Xatom.h>
#include <selection.h>
#include <micmap.h>
#include <misyncshm.h>
#include <compositeext.h>
#include <compint.h>
#include <glx_extinit.h>
#include <opaque.h>
#include <os.h>
#include <xserver_poll.h>
#include <propertyst.h>
#include <version-config.h>

#include "xwayland-screen.h"
#include "xwayland-vidmode.h"

#ifdef XF86VIDMODE
#include <X11/extensions/xf86vmproto.h>
extern _X_EXPORT Bool noXFree86VidModeExtension;
#endif

void
ddxGiveUp(enum ExitCode error)
{
}

void
OsVendorInit(void)
{
    if (serverGeneration == 1)
        ForceClockId(CLOCK_MONOTONIC);
}

void
OsVendorFatalError(const char *f, va_list args)
{
}

#if defined(DDXBEFORERESET)
void
ddxBeforeReset(void)
{
    return;
}
#endif

#if INPUTTHREAD
/** This function is called in Xserver/os/inputthread.c when starting
    the input thread. */
void
ddxInputThreadInit(void)
{
}
#endif

void
ddxUseMsg(void)
{
    ErrorF("-rootless              run rootless, requires wm support\n");
    ErrorF("-fullscreen            run fullscreen when rootful\n");
    ErrorF("-geometry WxH          set Xwayland window size when rootful\n");
    ErrorF("-host-grab             disable host keyboard shortcuts when rootful\n");
    ErrorF("-wm fd                 create X client for wm on given fd\n");
    ErrorF("-initfd fd             add given fd as a listen socket for initialization clients\n");
    ErrorF("-listenfd fd           add given fd as a listen socket\n");
    ErrorF("-listen fd             deprecated, use \"-listenfd\" instead\n");
#ifdef XWL_HAS_EGLSTREAM
    ErrorF("-eglstream             use eglstream backend for nvidia GPUs\n");
#endif
    ErrorF("-shm                   use shared memory for passing buffers\n");
    ErrorF("-verbose [n]           verbose startup messages\n");
    ErrorF("-version               show the server version and exit\n");
    ErrorF("-noTouchPointerEmulation  disable touch pointer emulation\n");
    ErrorF("-force-xrandr-emulation   force non-native modes to be exposed when viewporter is not exposed by the compositor\n");
#ifdef XWL_HAS_LIBDECOR
    ErrorF("-decorate              add decorations to Xwayland when rootful\n");
#endif
#ifdef XWL_HAS_EI_PORTAL
    ErrorF("-enable-ei-portal      use the XDG portal for input emulation\n");
#endif
}

static int init_fd = -1;
static int wm_fd = -1;
static int listen_fds[5] = { -1, -1, -1, -1, -1 };
static int listen_fd_count = 0;
static int verbosity = 0;

static void
xwl_show_version(void)
{
    ErrorF("%s Xwayland %s (%d)\n", VENDOR_NAME, VENDOR_MAN_VERSION, VENDOR_RELEASE);
    ErrorF("X Protocol Version %d, Revision %d\n", X_PROTOCOL, X_PROTOCOL_REVISION);
#if defined(BUILDERSTRING)
    if (strlen(BUILDERSTRING))
        ErrorF("%s\n", BUILDERSTRING);
#endif
}

static void
try_raising_nofile_limit(void)
{
#ifdef RLIMIT_NOFILE
    struct rlimit rlim;

    /* Only fiddle with the limit if not set explicitly from the command line */
    if (limitNoFile >= 0)
        return;

    if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
        ErrorF("Failed to get the current nofile limit: %s\n", strerror(errno));
        return;
    }

    rlim.rlim_cur = rlim.rlim_max;

    if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
        ErrorF("Failed to set the current nofile limit: %s\n", strerror(errno));
        return;
    }

    LogMessageVerb(X_INFO, 3, "Raising the file descriptors limit to %li\n",
                   rlim.rlim_max);
#endif
}

static void
xwl_add_listen_fd(int argc, char *argv[], int i)
{
    NoListenAll = TRUE;
    if (listen_fd_count == ARRAY_SIZE(listen_fds))
        FatalError("Too many -listen arguments given, max is %zu\n",
                   ARRAY_SIZE(listen_fds));

    listen_fds[listen_fd_count++] = atoi(argv[i + 1]);
}

int
ddxProcessArgument(int argc, char *argv[], int i)
{
    if (strcmp(argv[i], "-rootless") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-listen") == 0) {
        CHECK_FOR_REQUIRED_ARGUMENTS(1);

        /* Not an FD */
        if (!isdigit(*argv[i + 1]))
            return 0;

        LogMessageVerb(X_WARNING, 0, "Option \"-listen\" for file descriptors is deprecated\n"
                                     "Please use \"-listenfd\" instead.\n");

        xwl_add_listen_fd (argc, argv, i);
        return 2;
    }
    else if (strcmp(argv[i], "-listenfd") == 0) {
        CHECK_FOR_REQUIRED_ARGUMENTS(1);

        xwl_add_listen_fd (argc, argv, i);
        return 2;
    }
    else if (strcmp(argv[i], "-wm") == 0) {
        CHECK_FOR_REQUIRED_ARGUMENTS(1);
        wm_fd = atoi(argv[i + 1]);
        return 2;
    }
    else if (strcmp(argv[i], "-initfd") == 0) {
        CHECK_FOR_REQUIRED_ARGUMENTS(1);
        init_fd = atoi(argv[i + 1]);
        return 2;
    }
    else if (strcmp(argv[i], "-shm") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-verbose") == 0) {
        if (++i < argc && argv[i]) {
            char *end;
            long val;

            val = strtol(argv[i], &end, 0);
            if (*end == '\0') {
                verbosity = val;
                LogSetParameter(XLOG_VERBOSITY, verbosity);
                return 2;
            }
        }
        LogSetParameter(XLOG_VERBOSITY, ++verbosity);
        return 1;
    }
    else if (strcmp(argv[i], "-eglstream") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-version") == 0) {
        xwl_show_version();
        exit(0);
    }
    else if (strcmp(argv[i], "-noTouchPointerEmulation") == 0) {
        touchEmulatePointer = FALSE;
        return 1;
    }
    else if (strcmp(argv[i], "-force-xrandr-emulation") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-geometry") == 0) {
        CHECK_FOR_REQUIRED_ARGUMENTS(1);
        return 2;
    }
    else if (strcmp(argv[i], "-fullscreen") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-host-grab") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-decorate") == 0) {
        return 1;
    }
    else if (strcmp(argv[i], "-enable-ei-portal") == 0) {
        return 1;
    }

    return 0;
}

static CARD32
add_client_fd(OsTimerPtr timer, CARD32 time, void *arg)
{
    if (!AddClientOnOpenFD(wm_fd))
        FatalError("Failed to add wm client\n");

    TimerFree(timer);

    return 0;
}

static void
listen_on_fds(void)
{
    int i;

    for (i = 0; i < listen_fd_count; i++)
        ListenOnOpenFD(listen_fds[i], FALSE);
}

static void
wm_selection_callback(CallbackListPtr *p, void *data, void *arg)
{
    SelectionInfoRec *info = arg;
    struct xwl_screen *xwl_screen = data;
    static const char atom_name[] = "WM_S0";
    static Atom atom_wm_s0;

    if (atom_wm_s0 == None)
        atom_wm_s0 = MakeAtom(atom_name, strlen(atom_name), TRUE);
    if (info->selection->selection != atom_wm_s0 ||
        info->kind != SelectionSetOwner)
        return;

    listen_on_fds();

    DeleteCallback(&SelectionCallback, wm_selection_callback, xwl_screen);
}

static void _X_ATTRIBUTE_PRINTF(1, 0)
xwl_log_handler(const char *format, va_list args)
{
    char msg[256];

    vsnprintf(msg, sizeof msg, format, args);
    ErrorF("XWAYLAND: %s", msg);
}

#ifdef XWL_HAS_XWAYLAND_EXTENSION
#include <X11/extensions/xwaylandproto.h>

Bool noXwaylandExtension = FALSE;

static int
ProcXwlQueryVersion(ClientPtr client)
{
    xXwlQueryVersionReply reply;
    int major, minor;

    REQUEST(xXwlQueryVersionReq);
    REQUEST_SIZE_MATCH(xXwlQueryVersionReq);

    if (version_compare(stuff->majorVersion, stuff->minorVersion,
                        XWAYLAND_EXTENSION_MAJOR,
                        XWAYLAND_EXTENSION_MINOR) < 0) {
        major = stuff->majorVersion;
        minor = stuff->minorVersion;
    } else {
        major = XWAYLAND_EXTENSION_MAJOR;
        minor = XWAYLAND_EXTENSION_MINOR;
    }

    reply = (xXwlQueryVersionReply) {
        .type = X_Reply,
        .sequenceNumber = client->sequence,
        .length = 0,
        .majorVersion = major,
        .minorVersion = minor,
    };

    if (client->swapped) {
        swaps(&reply.sequenceNumber);
        swapl(&reply.length);
        swaps(&reply.majorVersion);
        swaps(&reply.minorVersion);
    }

    WriteReplyToClient(client, sizeof(reply), &reply);
    return Success;
}

static int _X_COLD
SProcXwlQueryVersion(ClientPtr client)
{
    REQUEST(xXwlQueryVersionReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xXwlQueryVersionReq);
    swaps(&stuff->majorVersion);
    swaps(&stuff->minorVersion);

    return ProcXwlQueryVersion(client);
}

static int
ProcXwaylandDispatch(ClientPtr client)
{
    REQUEST(xReq);

    switch (stuff->data) {
    case X_XwlQueryVersion:
        return ProcXwlQueryVersion(client);
    }
    return BadRequest;
}

static int
SProcXwaylandDispatch(ClientPtr client)
{
    REQUEST(xReq);

    switch (stuff->data) {
    case X_XwlQueryVersion:
        return SProcXwlQueryVersion(client);
    }
    return BadRequest;
}

static void
xwlExtensionInit(void)
{
    AddExtension(XWAYLAND_EXTENSION_NAME,
                 XwlNumberEvents, XwlNumberErrors,
                 ProcXwaylandDispatch, SProcXwaylandDispatch,
                 NULL, StandardMinorOpcode);
}

#endif

static const ExtensionModule xwayland_extensions[] = {
#ifdef XF86VIDMODE
    { xwlVidModeExtensionInit, XF86VIDMODENAME, &noXFree86VidModeExtension },
#endif
#ifdef XWL_HAS_XWAYLAND_EXTENSION
    { xwlExtensionInit, XWAYLAND_EXTENSION_NAME, &noXwaylandExtension },
#endif
};

void
InitOutput(ScreenInfo * screen_info, int argc, char **argv)
{
    int depths[] = { 1, 4, 8, 15, 16, 24, 32 };
    int bpp[] =    { 1, 8, 8, 16, 16, 32, 32 };
    int i;

    for (i = 0; i < ARRAY_SIZE(depths); i++) {
        screen_info->formats[i].depth = depths[i];
        screen_info->formats[i].bitsPerPixel = bpp[i];
        screen_info->formats[i].scanlinePad = BITMAP_SCANLINE_PAD;
    }

    screen_info->imageByteOrder = IMAGE_BYTE_ORDER;
    screen_info->bitmapScanlineUnit = BITMAP_SCANLINE_UNIT;
    screen_info->bitmapScanlinePad = BITMAP_SCANLINE_PAD;
    screen_info->bitmapBitOrder = BITMAP_BIT_ORDER;
    screen_info->numPixmapFormats = ARRAY_SIZE(depths);

    if (serverGeneration == 1) {
        try_raising_nofile_limit();
        LoadExtensionList(xwayland_extensions,
                          ARRAY_SIZE(xwayland_extensions), FALSE);
    }

    wl_log_set_handler_client(xwl_log_handler);

    if (AddScreen(xwl_screen_init, argc, argv) == -1) {
        FatalError("Couldn't add screen\n");
    }

    xorgGlxCreateVendor();

    LocalAccessScopeUser();

    if (wm_fd >= 0 || init_fd >= 0) {
        if (wm_fd >= 0)
            TimerSet(NULL, 0, 1, add_client_fd, NULL);
        if (init_fd >= 0)
            ListenOnOpenFD(init_fd, FALSE);
        AddCallback(&SelectionCallback, wm_selection_callback, NULL);
    }
    else if (listen_fd_count > 0) {
        listen_on_fds();
    }
}
