2021-06-06 01:26:18 +00:00
|
|
|
/**
|
|
|
|
* Looking Glass
|
2022-01-05 08:42:46 +00:00
|
|
|
* Copyright © 2017-2022 The Looking Glass Authors
|
2021-06-06 01:26:18 +00:00
|
|
|
* https://looking-glass.io
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
2019-03-30 04:52:00 +00:00
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
#include "config.h"
|
2021-01-26 10:46:30 +00:00
|
|
|
#include "kb.h"
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
#include "common/option.h"
|
2019-04-11 01:12:59 +00:00
|
|
|
#include "common/debug.h"
|
2021-07-31 11:05:48 +00:00
|
|
|
#include "common/paths.h"
|
2019-05-21 05:03:59 +00:00
|
|
|
#include "common/stringutils.h"
|
2019-03-30 04:52:00 +00:00
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <unistd.h>
|
2021-01-26 10:46:30 +00:00
|
|
|
#include <string.h>
|
2021-01-18 16:08:56 +00:00
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
// forwards
|
2020-01-18 19:49:56 +00:00
|
|
|
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);
|
2021-01-26 10:46:30 +00:00
|
|
|
static bool optScancodeValidate(struct Option * opt, const char ** error);
|
2020-01-18 19:49:56 +00:00
|
|
|
static char * optScancodeToString(struct Option * opt);
|
2021-01-18 03:42:57 +00:00
|
|
|
static bool optRotateValidate (struct Option * opt, const char ** error);
|
2019-05-21 05:03:59 +00:00
|
|
|
|
2019-03-30 04:52:00 +00:00
|
|
|
static void doLicense();
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
static struct Option options[] =
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
// 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",
|
2019-05-21 05:51:14 +00:00
|
|
|
.description = "Show the license for this application and then terminate",
|
2019-05-21 05:03:59 +00:00
|
|
|
.shortopt = 'l',
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2019-05-22 02:00:06 +00:00
|
|
|
{
|
|
|
|
.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
|
|
|
|
},
|
2020-10-29 15:32:25 +00:00
|
|
|
{
|
|
|
|
.module = "app",
|
|
|
|
.name = "allowDMA",
|
2021-04-29 02:56:35 +00:00
|
|
|
.description = "Allow direct DMA transfers if supported (see `README.md` in the `module` dir)",
|
2020-10-29 15:32:25 +00:00
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
// 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",
|
2019-05-27 08:38:01 +00:00
|
|
|
.description = "Allow the window to be manually resized",
|
2019-05-21 05:03:59 +00:00
|
|
|
.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,
|
|
|
|
},
|
2020-01-03 04:53:44 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "forceAspect",
|
|
|
|
.description = "Force the window to maintain the aspect ratio",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2020-10-12 12:27:33 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "dontUpscale",
|
|
|
|
.description = "Never try to upscale the window",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2021-03-28 19:28:56 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "shrinkOnUpscale",
|
|
|
|
.description = "Limit the window dimensions when dontUpscale is enabled",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.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,
|
|
|
|
},
|
2019-05-26 06:30:24 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "maximize",
|
|
|
|
.description = "Launch window maximized",
|
|
|
|
.shortopt = 'T',
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2019-07-01 07:53:12 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "minimizeOnFocusLoss",
|
|
|
|
.description = "Minimize window on focus loss",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
2021-07-07 22:49:00 +00:00
|
|
|
.value.x_bool = false,
|
2019-07-01 07:53:12 +00:00
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
2020-05-22 08:28:16 +00:00
|
|
|
.name = "fpsMin",
|
2020-05-22 08:15:17 +00:00
|
|
|
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
|
2019-05-21 05:03:59 +00:00
|
|
|
.shortopt = 'K',
|
|
|
|
.type = OPTION_TYPE_INT,
|
2019-07-09 01:13:24 +00:00
|
|
|
.value.x_int = -1,
|
2019-05-21 05:03:59 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "ignoreQuit",
|
2021-09-01 23:50:51 +00:00
|
|
|
.description = "Ignore requests to quit (i.e. Alt+F4)",
|
2019-05-21 05:03:59 +00:00
|
|
|
.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,
|
|
|
|
},
|
2021-02-03 23:01:53 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "autoScreensaver",
|
|
|
|
.description = "Prevent the screensaver from starting when guest requests it",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "alerts",
|
|
|
|
.description = "Show on screen alert messages",
|
|
|
|
.shortopt = 'q',
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2020-07-19 04:29:29 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "quickSplash",
|
|
|
|
.description = "Skip fading out the splash screen when a connection is established",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2021-01-15 01:40:59 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "rotate",
|
2021-01-20 04:35:13 +00:00
|
|
|
.description = "Rotate the displayed image (0, 90, 180, 270)",
|
2021-01-18 03:42:57 +00:00
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.validator = optRotateValidate,
|
|
|
|
.value.x_int = 0,
|
2021-01-15 01:40:59 +00:00
|
|
|
},
|
2021-07-23 04:22:04 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "uiFont",
|
|
|
|
.description = "The font to use when rendering on-screen UI",
|
|
|
|
.type = OPTION_TYPE_STRING,
|
|
|
|
.value.x_string = "DejaVu Sans Mono",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "uiSize",
|
|
|
|
.description = "The font size to use when rendering on-screen UI",
|
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 14
|
|
|
|
},
|
2021-08-01 00:07:05 +00:00
|
|
|
{
|
|
|
|
.module = "win",
|
|
|
|
.name = "jitRender",
|
|
|
|
.description = "Enable just-in-time rendering",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
[client] config: disable JIT rendering by default
Without configuring Wayland compositors to send frame callbacks as late as
possible, JIT rendering can increase latency by more than one frame.
For example, by default, sway asks applications to render right after a
vblank, and does its own composition right after a vblank, resulting in
~2 frame's worth of latency. If max_render_time is set on the output,
it composes that many milliseconds before the vblank, losing ~1 frame's
worth of latency. If max_render_time is set on the window also, the frame
callback is sent that many milliseconds before composition, and we achieve
perfectly low latency.
Therefore, out of the box, JIT rendering should not be enabled, as manual
compositor configuration is required for optimal results.
For reference, the following sway settings results in the best latency:
output <insert output name> max_render_time 1
for_window [app_id="looking-glass-client"] max_render_time 1
This reverts commit 3baed057281b9953bd642dfb9faaad443bdf1a3f.
2021-08-01 04:56:57 +00:00
|
|
|
.value.x_bool = false,
|
2021-08-01 00:07:05 +00:00
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
// input options
|
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "grabKeyboard",
|
|
|
|
.description = "Grab the keyboard in capture mode",
|
|
|
|
.shortopt = 'G',
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2020-11-28 20:12:19 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "grabKeyboardOnFocus",
|
|
|
|
.description = "Grab the keyboard when focused",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
2021-10-14 14:29:25 +00:00
|
|
|
.value.x_bool = false,
|
2020-11-28 20:12:19 +00:00
|
|
|
},
|
2021-02-13 07:19:01 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "releaseKeysOnFocusLoss",
|
|
|
|
.description = "On focus loss, send key up events to guest for all held keys",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "escapeKey",
|
2021-01-26 10:46:30 +00:00
|
|
|
.description = "Specify the escape key, see <linux/input-event-codes.h> for valid values",
|
2019-05-21 05:03:59 +00:00
|
|
|
.shortopt = 'm',
|
|
|
|
.type = OPTION_TYPE_INT,
|
2021-01-26 10:46:30 +00:00
|
|
|
.value.x_int = KEY_SCROLLLOCK,
|
|
|
|
.validator = optScancodeValidate,
|
|
|
|
.toString = optScancodeToString,
|
2019-05-21 05:03:59 +00:00
|
|
|
},
|
2021-01-04 07:22:39 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "ignoreWindowsKeys",
|
|
|
|
.description = "Do not pass events for the windows keys to the guest",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "hideCursor",
|
|
|
|
.description = "Hide the local mouse cursor",
|
|
|
|
.shortopt = 'M',
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2019-05-23 19:29:38 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "mouseSens",
|
2019-05-23 19:31:16 +00:00
|
|
|
.description = "Initial mouse sensitivity when in capture mode (-9 to 9)",
|
2019-05-23 19:29:38 +00:00
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 0,
|
|
|
|
},
|
2021-01-09 03:39:48 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "mouseSmoothing",
|
2021-01-09 03:55:20 +00:00
|
|
|
.description = "Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing)",
|
2021-01-09 03:39:48 +00:00
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2021-01-07 16:12:42 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "rawMouse",
|
|
|
|
.description = "Use RAW mouse input when in capture mode (good for gaming)",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false,
|
|
|
|
},
|
2020-08-20 05:50:33 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "mouseRedraw",
|
|
|
|
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true,
|
|
|
|
},
|
2021-01-08 08:41:10 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "autoCapture",
|
|
|
|
.description = "Try to keep the mouse captured when needed",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false
|
|
|
|
},
|
2021-01-09 07:01:08 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "captureOnly",
|
|
|
|
.description = "Only enable input via SPICE if in capture mode",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false
|
|
|
|
},
|
2021-05-01 01:44:13 +00:00
|
|
|
{
|
|
|
|
.module = "input",
|
|
|
|
.name = "helpMenuDelay",
|
|
|
|
.description = "Show help menu after holding down the escape key for this many milliseconds",
|
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 200
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
// 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",
|
2021-09-01 23:26:48 +00:00
|
|
|
.description = "Use SPICE to synchronize the clipboard contents with the guest",
|
2019-05-21 05:03:59 +00:00
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "spice",
|
|
|
|
.name = "clipboardToVM",
|
2021-09-01 23:26:48 +00:00
|
|
|
.description = "Allow the clipboard to be synchronized TO the VM",
|
2019-05-21 05:03:59 +00:00
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "spice",
|
|
|
|
.name = "clipboardToLocal",
|
2021-09-01 23:26:48 +00:00
|
|
|
.description = "Allow the clipboard to be synchronized FROM the VM",
|
2019-05-21 05:03:59 +00:00
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2021-12-24 07:43:20 +00:00
|
|
|
{
|
|
|
|
.module = "spice",
|
|
|
|
.name = "audio",
|
|
|
|
.description = "Enable SPICE audio support",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
.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
|
|
|
|
},
|
2020-05-21 01:13:42 +00:00
|
|
|
{
|
|
|
|
.module = "spice",
|
|
|
|
.name = "captureOnStart",
|
|
|
|
.description = "Capture mouse and keyboard on start",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false
|
|
|
|
},
|
2020-11-15 13:41:22 +00:00
|
|
|
{
|
|
|
|
.module = "spice",
|
|
|
|
.name = "alwaysShowCursor",
|
|
|
|
.description = "Always show host cursor",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = false
|
|
|
|
},
|
2021-04-29 02:50:43 +00:00
|
|
|
{
|
2021-04-29 02:54:13 +00:00
|
|
|
.module = "spice",
|
2021-04-29 02:50:43 +00:00
|
|
|
.name = "showCursorDot",
|
|
|
|
.description = "Use a \"dot\" cursor when the window does not have focus",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{0}
|
|
|
|
};
|
|
|
|
|
2021-01-14 06:05:26 +00:00
|
|
|
void config_init(void)
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.center = true;
|
|
|
|
g_params.w = 1024;
|
|
|
|
g_params.h = 768;
|
2019-03-30 04:52:00 +00:00
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
option_register(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool config_load(int argc, char * argv[])
|
|
|
|
{
|
|
|
|
// load any global options first
|
2019-03-30 04:52:00 +00:00
|
|
|
struct stat st;
|
2021-07-30 11:27:09 +00:00
|
|
|
if (stat("/etc/looking-glass-client.ini", &st) >= 0 && S_ISREG(st.st_mode))
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini");
|
|
|
|
if (!option_load("/etc/looking-glass-client.ini"))
|
2019-03-30 04:52:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-30 21:38:19 +00:00
|
|
|
// load config from user's home directory
|
2019-03-30 04:52:00 +00:00
|
|
|
struct passwd * pw = getpwuid(getuid());
|
2019-05-21 05:03:59 +00:00
|
|
|
char * localFile;
|
|
|
|
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
|
2021-07-30 11:27:09 +00:00
|
|
|
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
DEBUG_INFO("Loading config from: %s", localFile);
|
|
|
|
if (!option_load(localFile))
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
free(localFile);
|
|
|
|
return false;
|
2019-03-30 04:52:00 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-21 05:03:59 +00:00
|
|
|
free(localFile);
|
2019-03-30 04:52:00 +00:00
|
|
|
|
2021-07-30 21:38:19 +00:00
|
|
|
// load config from XDG_CONFIG_HOME
|
|
|
|
char * xdgFile;
|
2021-07-31 11:05:48 +00:00
|
|
|
alloc_sprintf(&xdgFile, "%s/client.ini", lgConfigDir());
|
2021-07-30 21:38:19 +00:00
|
|
|
|
|
|
|
if (xdgFile && stat(xdgFile, &st) >= 0 && S_ISREG(st.st_mode))
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Loading config from: %s", xdgFile);
|
|
|
|
if (!option_load(xdgFile))
|
|
|
|
{
|
|
|
|
free(xdgFile);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(xdgFile);
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
// parse the command line arguments
|
|
|
|
if (!option_parse(argc, argv))
|
2019-03-30 04:52:00 +00:00
|
|
|
return false;
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
// if a file was specified to also load, do it
|
|
|
|
const char * configFile = option_get_string("app", "configFile");
|
|
|
|
if (configFile)
|
2019-03-30 05:00:47 +00:00
|
|
|
{
|
2021-07-30 11:24:55 +00:00
|
|
|
if (stat(configFile, &st) < 0 || !S_ISREG(st.st_mode))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("app:configFile set to invalid file: %s", configFile);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
DEBUG_INFO("Loading config from: %s", configFile);
|
|
|
|
if (!option_load(configFile))
|
|
|
|
return false;
|
2019-03-30 05:00:47 +00:00
|
|
|
}
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
// validate the values are sane
|
|
|
|
if (!option_validate())
|
2019-03-30 04:52:00 +00:00
|
|
|
return false;
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
if (option_get_bool("app", "license"))
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
doLicense();
|
|
|
|
return false;
|
2019-03-30 04:52:00 +00:00
|
|
|
}
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
// setup the application params for the basic types
|
2021-04-29 02:50:43 +00:00
|
|
|
g_params.cursorPollInterval = option_get_int ("app" , "cursorPollInterval");
|
|
|
|
g_params.framePollInterval = option_get_int ("app" , "framePollInterval" );
|
|
|
|
g_params.allowDMA = option_get_bool ("app" , "allowDMA" );
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-02-03 23:01:53 +00:00
|
|
|
g_params.windowTitle = option_get_string("win", "title" );
|
|
|
|
g_params.autoResize = option_get_bool ("win", "autoResize" );
|
|
|
|
g_params.allowResize = option_get_bool ("win", "allowResize" );
|
|
|
|
g_params.keepAspect = option_get_bool ("win", "keepAspect" );
|
|
|
|
g_params.forceAspect = option_get_bool ("win", "forceAspect" );
|
|
|
|
g_params.dontUpscale = option_get_bool ("win", "dontUpscale" );
|
2021-03-28 19:28:56 +00:00
|
|
|
g_params.shrinkOnUpscale = option_get_bool ("win", "shrinkOnUpscale");
|
2021-02-03 23:01:53 +00:00
|
|
|
g_params.borderless = option_get_bool ("win", "borderless" );
|
|
|
|
g_params.fullscreen = option_get_bool ("win", "fullScreen" );
|
|
|
|
g_params.maximize = option_get_bool ("win", "maximize" );
|
|
|
|
g_params.fpsMin = option_get_int ("win", "fpsMin" );
|
|
|
|
g_params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
|
|
|
g_params.noScreensaver = option_get_bool ("win", "noScreensaver" );
|
|
|
|
g_params.autoScreensaver = option_get_bool ("win", "autoScreensaver");
|
|
|
|
g_params.showAlerts = option_get_bool ("win", "alerts" );
|
|
|
|
g_params.quickSplash = option_get_bool ("win", "quickSplash" );
|
2021-08-01 00:07:05 +00:00
|
|
|
g_params.uiFont = option_get_string("win" , "uiFont" );
|
|
|
|
g_params.uiSize = option_get_int ("win" , "uiSize" );
|
|
|
|
g_params.jitRender = option_get_bool ("win" , "jitRender" );
|
2021-02-03 23:01:53 +00:00
|
|
|
|
|
|
|
if (g_params.noScreensaver && g_params.autoScreensaver)
|
|
|
|
{
|
2021-08-03 19:55:34 +00:00
|
|
|
DEBUG_WARN("win:noScreensaver (-S) and win:autoScreensaver "
|
|
|
|
"can't be used simultaneously");
|
2021-02-03 23:01:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
2019-05-21 05:03:59 +00:00
|
|
|
|
2021-01-18 03:42:57 +00:00
|
|
|
switch(option_get_int("win", "rotate"))
|
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
case 0 : g_params.winRotate = LG_ROTATE_0 ; break;
|
|
|
|
case 90 : g_params.winRotate = LG_ROTATE_90 ; break;
|
|
|
|
case 180: g_params.winRotate = LG_ROTATE_180; break;
|
|
|
|
case 270: g_params.winRotate = LG_ROTATE_270; break;
|
2021-01-18 03:42:57 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 07:19:01 +00:00
|
|
|
g_params.grabKeyboard = option_get_bool("input", "grabKeyboard" );
|
|
|
|
g_params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus" );
|
|
|
|
g_params.releaseKeysOnFocusLoss = option_get_bool("input", "releaseKeysOnFocusLoss");
|
|
|
|
g_params.escapeKey = option_get_int ("input", "escapeKey" );
|
|
|
|
g_params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" );
|
|
|
|
g_params.hideMouse = option_get_bool("input", "hideCursor" );
|
|
|
|
g_params.mouseSens = option_get_int ("input", "mouseSens" );
|
|
|
|
g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" );
|
|
|
|
g_params.rawMouse = option_get_bool("input", "rawMouse" );
|
|
|
|
g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" );
|
|
|
|
g_params.autoCapture = option_get_bool("input", "autoCapture" );
|
|
|
|
g_params.captureInputOnly = option_get_bool("input", "captureOnly" );
|
2019-05-21 05:03:59 +00:00
|
|
|
|
2021-08-03 19:54:59 +00:00
|
|
|
if (g_params.jitRender && !g_params.mouseRedraw)
|
|
|
|
{
|
|
|
|
DEBUG_WARN("win:jitRender is enabled, forcing input:mouseRedraw");
|
|
|
|
g_params.mouseRedraw = true;
|
|
|
|
}
|
|
|
|
|
2021-05-01 01:44:13 +00:00
|
|
|
g_params.helpMenuDelayUs = option_get_int("input", "helpMenuDelay") * (uint64_t) 1000;
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
2019-07-01 07:53:12 +00:00
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
if (option_get_bool("spice", "enable"))
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.spiceHost = option_get_string("spice", "host");
|
|
|
|
g_params.spicePort = option_get_int ("spice", "port");
|
2019-03-30 04:52:00 +00:00
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
|
|
|
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
2021-12-24 07:43:20 +00:00
|
|
|
g_params.useSpiceAudio = option_get_bool("spice", "audio" );
|
2019-03-30 04:52:00 +00:00
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
if (g_params.useSpiceClipboard)
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2021-06-02 12:09:09 +00:00
|
|
|
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
|
|
|
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
|
|
|
g_params.useSpiceClipboard = g_params.clipboardToVM || g_params.clipboardToLocal;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_params.clipboardToVM = false;
|
|
|
|
g_params.clipboardToLocal = false;
|
2019-03-30 04:52:00 +00:00
|
|
|
}
|
|
|
|
|
2021-04-29 02:50:43 +00:00
|
|
|
g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
|
|
|
g_params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
|
|
|
g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
|
|
|
|
g_params.showCursorDot = option_get_bool("spice", "showCursorDot");
|
2019-03-30 04:52:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-14 06:05:26 +00:00
|
|
|
void config_free(void)
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
2019-05-21 05:51:45 +00:00
|
|
|
option_free();
|
2019-03-30 04:52:00 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 06:05:26 +00:00
|
|
|
static void doLicense(void)
|
2019-03-30 04:52:00 +00:00
|
|
|
{
|
|
|
|
fprintf(stderr,
|
2021-07-30 10:47:39 +00:00
|
|
|
// BEGIN LICENSE BLOCK
|
2019-03-30 04:52:00 +00:00
|
|
|
"\n"
|
2021-07-30 10:47:39 +00:00
|
|
|
"Looking Glass\n"
|
2022-01-05 08:42:46 +00:00
|
|
|
"Copyright © 2017-2022 The Looking Glass Authors\n"
|
2021-07-30 10:47:39 +00:00
|
|
|
"https://looking-glass.io\n"
|
2019-03-30 04:52:00 +00:00
|
|
|
"\n"
|
2021-07-30 10:47:39 +00:00
|
|
|
"This program is free software; you can redistribute it and/or modify it under\n"
|
2019-03-30 04:52:00 +00:00
|
|
|
"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"
|
2021-07-30 10:47:39 +00:00
|
|
|
"PARTICULAR PURPOSE. See the GNU General Public License for more details.\n"
|
2019-03-30 04:52:00 +00:00
|
|
|
"\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"
|
2021-07-30 10:47:39 +00:00
|
|
|
"Place, Suite 330, Boston, MA 02111-1307 USA\n"
|
2019-03-30 04:52:00 +00:00
|
|
|
"\n"
|
2021-07-30 10:47:39 +00:00
|
|
|
// END LICENSE BLOCK
|
2019-03-30 04:52:00 +00:00
|
|
|
);
|
2019-05-21 05:03:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool optRendererParse(struct Option * opt, const char * str)
|
|
|
|
{
|
2020-01-18 19:48:20 +00:00
|
|
|
if (!str)
|
|
|
|
return false;
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
if (strcasecmp(str, "auto") == 0)
|
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.forceRenderer = false;
|
2019-05-21 05:03:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
2021-08-08 05:43:42 +00:00
|
|
|
if (strcasecmp(str, LG_Renderers[i]->getName()) == 0)
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.forceRenderer = true;
|
|
|
|
g_params.forceRendererIndex = i;
|
2019-05-21 05:03:59 +00:00
|
|
|
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)
|
2021-08-08 05:43:42 +00:00
|
|
|
stringlist_push(sl, (char *)LG_Renderers[i]->getName());
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
return sl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char * optRendererToString(struct Option * opt)
|
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
if (!g_params.forceRenderer)
|
2019-05-21 05:03:59 +00:00
|
|
|
return strdup("auto");
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
if (g_params.forceRendererIndex >= LG_RENDERER_COUNT)
|
2019-05-21 05:03:59 +00:00
|
|
|
return NULL;
|
|
|
|
|
2021-08-08 05:43:42 +00:00
|
|
|
return strdup(LG_Renderers[g_params.forceRendererIndex]->getName());
|
2019-05-21 05:03:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool optPosParse(struct Option * opt, const char * str)
|
|
|
|
{
|
2020-01-18 19:48:20 +00:00
|
|
|
if (!str)
|
|
|
|
return false;
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
if (strcmp(str, "center") == 0)
|
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.center = true;
|
2019-05-21 05:03:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2)
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
g_params.center = false;
|
2019-05-21 05:03:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static StringList optPosValues(struct Option * opt)
|
|
|
|
{
|
|
|
|
StringList sl = stringlist_new(false);
|
|
|
|
stringlist_push(sl, "center");
|
2021-09-01 23:50:51 +00:00
|
|
|
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
|
2019-05-21 05:03:59 +00:00
|
|
|
return sl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char * optPosToString(struct Option * opt)
|
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
if (g_params.center)
|
2019-05-21 05:03:59 +00:00
|
|
|
return strdup("center");
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
|
2019-05-21 05:03:59 +00:00
|
|
|
char * str = malloc(len + 1);
|
2021-01-25 08:58:36 +00:00
|
|
|
sprintf(str, "%dx%d", g_params.x, g_params.y);
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool optSizeParse(struct Option * opt, const char * str)
|
|
|
|
{
|
2020-01-18 19:48:20 +00:00
|
|
|
if (!str)
|
|
|
|
return false;
|
|
|
|
|
2021-09-11 00:30:31 +00:00
|
|
|
if (sscanf(str, "%ux%u", &g_params.w, &g_params.h) == 2)
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
2021-01-25 08:58:36 +00:00
|
|
|
if (g_params.w < 1 || g_params.h < 1)
|
2019-05-21 05:03:59 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static StringList optSizeValues(struct Option * opt)
|
|
|
|
{
|
|
|
|
StringList sl = stringlist_new(false);
|
2021-09-01 23:50:51 +00:00
|
|
|
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
|
2019-05-21 05:03:59 +00:00
|
|
|
return sl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char * optSizeToString(struct Option * opt)
|
|
|
|
{
|
2021-09-11 00:30:31 +00:00
|
|
|
int len = snprintf(NULL, 0, "%ux%u", g_params.w, g_params.h);
|
2019-05-21 05:03:59 +00:00
|
|
|
char * str = malloc(len + 1);
|
2021-09-11 00:30:31 +00:00
|
|
|
sprintf(str, "%ux%u", g_params.w, g_params.h);
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:46:30 +00:00
|
|
|
static bool optScancodeValidate(struct Option * opt, const char ** error)
|
|
|
|
{
|
|
|
|
if (opt->value.x_int >= 0 && opt->value.x_int < KEY_MAX)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
*error = "Out of range";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
static char * optScancodeToString(struct Option * opt)
|
|
|
|
{
|
|
|
|
char * str;
|
2021-01-18 16:08:56 +00:00
|
|
|
alloc_sprintf(&str, "%d = %s", opt->value.x_int,
|
2021-08-17 08:41:21 +00:00
|
|
|
linux_to_str[opt->value.x_int]);
|
2019-05-21 05:03:59 +00:00
|
|
|
return str;
|
2020-01-18 19:48:20 +00:00
|
|
|
}
|
2021-01-15 01:40:59 +00:00
|
|
|
|
2021-01-18 03:42:57 +00:00
|
|
|
static bool optRotateValidate(struct Option * opt, const char ** error)
|
2021-01-15 01:40:59 +00:00
|
|
|
{
|
2021-01-18 03:42:57 +00:00
|
|
|
switch(opt->value.x_int)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
case 90:
|
|
|
|
case 180:
|
|
|
|
case 270:
|
|
|
|
return true;
|
2021-01-15 01:40:59 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 03:42:57 +00:00
|
|
|
*error = "Rotation angle must be one of 0, 90, 180 or 270";
|
|
|
|
return false;
|
2021-01-15 01:40:59 +00:00
|
|
|
}
|