/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "config.h" #include "common/option.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include // forwards static bool optRendererParse (struct Option * opt, const char * str); static StringList optRendererValues (struct Option * opt); static char * optRendererToString(struct Option * opt); static bool optPosParse (struct Option * opt, const char * str); static StringList optPosValues (struct Option * opt); static char * optPosToString (struct Option * opt); static bool optSizeParse (struct Option * opt, const char * str); static StringList optSizeValues (struct Option * opt); static char * optSizeToString (struct Option * opt); static char * optScancodeToString(struct Option * opt); static void doLicense(); static struct Option options[] = { // app options { .module = "app", .name = "configFile", .description = "A file to read additional configuration from", .shortopt = 'C', .type = OPTION_TYPE_STRING, .value.x_string = NULL, }, { .module = "app", .name = "renderer", .description = "Specify the renderer to use", .shortopt = 'g', .type = OPTION_TYPE_CUSTOM, .parser = optRendererParse, .getValues = optRendererValues, .toString = optRendererToString }, { .module = "app", .name = "license", .description = "Show the license for this application and then terminate", .shortopt = 'l', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "app", .name = "cursorPollInterval", .description = "How often to check for a cursor update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "framePollInterval", .description = "How often to check for a frame update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "allowDMA", .description = "Allow direct DMA transfers if possible (VM-VM only for now)", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, // window options { .module = "win", .name = "title", .description = "The window title", .type = OPTION_TYPE_STRING, .value.x_string = "Looking Glass (client)" }, { .module = "win", .name = "position", .description = "Initial window position at startup", .type = OPTION_TYPE_CUSTOM, .parser = optPosParse, .getValues = optPosValues, .toString = optPosToString }, { .module = "win", .name = "size", .description = "Initial window size at startup", .type = OPTION_TYPE_CUSTOM, .parser = optSizeParse, .getValues = optSizeValues, .toString = optSizeToString }, { .module = "win", .name = "autoResize", .description = "Auto resize the window to the guest", .shortopt = 'a', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "allowResize", .description = "Allow the window to be manually resized", .shortopt = 'n', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "keepAspect", .description = "Maintain the correct aspect ratio", .shortopt = 'r', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "forceAspect", .description = "Force the window to maintain the aspect ratio", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "dontUpscale", .description = "Never try to upscale the window", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "borderless", .description = "Borderless mode", .shortopt = 'd', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "fullScreen", .description = "Launch in fullscreen borderless mode", .shortopt = 'F', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "maximize", .description = "Launch window maximized", .shortopt = 'T', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "minimizeOnFocusLoss", .description = "Minimize window on focus loss", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "fpsMin", .description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)", .shortopt = 'K', .type = OPTION_TYPE_INT, .value.x_int = -1, }, { .module = "win", .name = "showFPS", .description = "Enable the FPS & UPS display", .shortopt = 'k', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "ignoreQuit", .description = "Ignore requests to quit (ie: Alt+F4)", .shortopt = 'Q', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "noScreensaver", .description = "Prevent the screensaver from starting", .shortopt = 'S', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "alerts", .description = "Show on screen alert messages", .shortopt = 'q', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "quickSplash", .description = "Skip fading out the splash screen when a connection is established", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, // input options { .module = "input", .name = "grabKeyboard", .description = "Grab the keyboard in capture mode", .shortopt = 'G', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "grabKeyboardOnFocus", .description = "Grab the keyboard when focused", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "escapeKey", .description = "Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values", .shortopt = 'm', .type = OPTION_TYPE_INT, .value.x_int = SDL_SCANCODE_SCROLLLOCK, .toString = optScancodeToString }, { .module = "input", .name = "ignoreWindowsKeys", .description = "Do not pass events for the windows keys to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "hideCursor", .description = "Hide the local mouse cursor", .shortopt = 'M', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "mouseSens", .description = "Initial mouse sensitivity when in capture mode (-9 to 9)", .type = OPTION_TYPE_INT, .value.x_int = 0, }, { .module = "input", .name = "rawMouse", .description = "Use RAW mouse input when in capture mode (good for gaming)", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "input", .name = "mouseRedraw", .description = "Mouse movements trigger redraws (ignores FPS minimum)", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "autoCapture", .description = "Try to keep the mouse captured when needed", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, // spice options { .module = "spice", .name = "enable", .description = "Enable the built in SPICE client for input and/or clipboard support", .shortopt = 's', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "host", .description = "The SPICE server host or UNIX socket", .shortopt = 'c', .type = OPTION_TYPE_STRING, .value.x_string = "127.0.0.1" }, { .module = "spice", .name = "port", .description = "The SPICE server port (0 = unix socket)", .shortopt = 'p', .type = OPTION_TYPE_INT, .value.x_int = 5900 }, { .module = "spice", .name = "input", .description = "Use SPICE to send keyboard and mouse input events to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboard", .description = "Use SPICE to syncronize the clipboard contents with the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToVM", .description = "Allow the clipboard to be syncronized TO the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToLocal", .description = "Allow the clipboard to be syncronized FROM the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "scaleCursor", .description = "Scale cursor input position to screen size when up/down scaled", .shortopt = 'j', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "captureOnStart", .description = "Capture mouse and keyboard on start", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "spice", .name = "alwaysShowCursor", .description = "Always show host cursor", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, {0} }; void config_init() { params.center = true; params.w = 1024; params.h = 768; option_register(options); } bool config_load(int argc, char * argv[]) { // load any global options first struct stat st; if (stat("/etc/looking-glass-client.ini", &st) >= 0) { DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini"); if (!option_load("/etc/looking-glass-client.ini")) return false; } // load user's local options struct passwd * pw = getpwuid(getuid()); char * localFile; alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir); if (stat(localFile, &st) >= 0) { DEBUG_INFO("Loading config from: %s", localFile); if (!option_load(localFile)) { free(localFile); return false; } } free(localFile); // parse the command line arguments if (!option_parse(argc, argv)) return false; // if a file was specified to also load, do it const char * configFile = option_get_string("app", "configFile"); if (configFile) { DEBUG_INFO("Loading config from: %s", configFile); if (!option_load(configFile)) return false; } // validate the values are sane if (!option_validate()) return false; if (option_get_bool("app", "license")) { doLicense(); return false; } // setup the application params for the basic types params.cursorPollInterval = option_get_int ("app", "cursorPollInterval"); params.framePollInterval = option_get_int ("app", "framePollInterval" ); params.allowDMA = option_get_bool ("app", "allowDMA" ); params.windowTitle = option_get_string("win", "title" ); params.autoResize = option_get_bool ("win", "autoResize" ); params.allowResize = option_get_bool ("win", "allowResize" ); params.keepAspect = option_get_bool ("win", "keepAspect" ); params.forceAspect = option_get_bool ("win", "forceAspect" ); params.dontUpscale = option_get_bool ("win", "dontUpscale" ); params.borderless = option_get_bool ("win", "borderless" ); params.fullscreen = option_get_bool ("win", "fullScreen" ); params.maximize = option_get_bool ("win", "maximize" ); params.fpsMin = option_get_int ("win", "fpsMin" ); params.showFPS = option_get_bool ("win", "showFPS" ); params.ignoreQuit = option_get_bool ("win", "ignoreQuit" ); params.noScreensaver = option_get_bool ("win", "noScreensaver"); params.showAlerts = option_get_bool ("win", "alerts" ); params.quickSplash = option_get_bool ("win", "quickSplash" ); params.grabKeyboard = option_get_bool("input", "grabKeyboard" ); params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus"); params.escapeKey = option_get_int ("input", "escapeKey" ); params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" ); params.hideMouse = option_get_bool("input", "hideCursor" ); params.mouseSens = option_get_int ("input", "mouseSens" ); params.rawMouse = option_get_bool("input", "rawMouse" ); params.mouseRedraw = option_get_bool("input", "mouseRedraw" ); params.autoCapture = option_get_bool("input", "autoCapture" ); params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); if (option_get_bool("spice", "enable")) { params.spiceHost = option_get_string("spice", "host"); params.spicePort = option_get_int ("spice", "port"); params.useSpiceInput = option_get_bool("spice", "input" ); params.useSpiceClipboard = option_get_bool("spice", "clipboard"); if (params.useSpiceClipboard) { params.clipboardToVM = option_get_bool("spice", "clipboardToVM" ); params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal"); if (!params.clipboardToVM && !params.clipboardToLocal) params.useSpiceClipboard = false; } params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); params.captureOnStart = option_get_bool("spice", "captureOnStart"); params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor"); } return true; } void config_free() { option_free(); } static void doLicense() { fprintf(stderr, "\n" "Looking Glass - KVM FrameRelay (KVMFR) Client\n" "Copyright(C) 2017-2019 Geoffrey McRae \n" "https://looking-glass.hostfission.com\n" "\n" "This program is free software; you can redistribute it and / or modify it under\n" "the terms of the GNU General Public License as published by the Free Software\n" "Foundation; either version 2 of the License, or (at your option) any later\n" "version.\n" "\n" "This program is distributed in the hope that it will be useful, but WITHOUT ANY\n" "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n" "PARTICULAR PURPOSE.See the GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License along with\n" "this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n" "Place, Suite 330, Boston, MA 02111 - 1307 USA\n" "\n" ); } static bool optRendererParse(struct Option * opt, const char * str) { if (!str) return false; if (strcasecmp(str, "auto") == 0) { params.forceRenderer = false; return true; } for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0) { params.forceRenderer = true; params.forceRendererIndex = i; return true; } return false; } static StringList optRendererValues(struct Option * opt) { StringList sl = stringlist_new(false); // this typecast is safe as the stringlist doesn't own the values for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) stringlist_push(sl, (char *)LG_Renderers[i]->get_name()); return sl; } static char * optRendererToString(struct Option * opt) { if (!params.forceRenderer) return strdup("auto"); if (params.forceRendererIndex >= LG_RENDERER_COUNT) return NULL; return strdup(LG_Renderers[params.forceRendererIndex]->get_name()); } static bool optPosParse(struct Option * opt, const char * str) { if (!str) return false; if (strcmp(str, "center") == 0) { params.center = true; return true; } if (sscanf(str, "%dx%d", ¶ms.x, ¶ms.y) == 2) { params.center = false; return true; } return false; } static StringList optPosValues(struct Option * opt) { StringList sl = stringlist_new(false); stringlist_push(sl, "center"); stringlist_push(sl, "x, ie: 100x100"); return sl; } static char * optPosToString(struct Option * opt) { if (params.center) return strdup("center"); int len = snprintf(NULL, 0, "%dx%d", params.x, params.y); char * str = malloc(len + 1); sprintf(str, "%dx%d", params.x, params.y); return str; } static bool optSizeParse(struct Option * opt, const char * str) { if (!str) return false; if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2) { if (params.w < 1 || params.h < 1) return false; return true; } return false; } static StringList optSizeValues(struct Option * opt) { StringList sl = stringlist_new(false); stringlist_push(sl, "x, ie: 100x100"); return sl; } static char * optSizeToString(struct Option * opt) { int len = snprintf(NULL, 0, "%dx%d", params.w, params.h); char * str = malloc(len + 1); sprintf(str, "%dx%d", params.w, params.h); return str; } static char * optScancodeToString(struct Option * opt) { char * str; alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int)); return str; }