Remove hterm

This commit is contained in:
Søren L. Hansen 2022-03-28 15:48:20 -07:00
parent 26fc4127a9
commit 163fd0537c
12 changed files with 34 additions and 308 deletions

View File

@ -73,7 +73,6 @@ By default, GoTTY starts a web server at port 8080. Open the URL on your web bro
--width value Static width of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_WIDTH] --width value Static width of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_WIDTH]
--height value Static height of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_HEIGHT] --height value Static height of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_HEIGHT]
--ws-origin value A regular expression that matches origin URLs to be accepted by WebSocket. No cross origin requests are acceptable by default [$GOTTY_WS_ORIGIN] --ws-origin value A regular expression that matches origin URLs to be accepted by WebSocket. No cross origin requests are acceptable by default [$GOTTY_WS_ORIGIN]
--term value Terminal name to use on the browser, one of xterm or hterm. (default: "xterm") [$GOTTY_TERM]
--enable-webgl Enable WebGL renderer (default: true) [$GOTTY_ENABLE_WEBGL] --enable-webgl Enable WebGL renderer (default: true) [$GOTTY_ENABLE_WEBGL]
--close-signal value Signal sent to the command process when gotty close it (default: SIGHUP) (default: 1) [$GOTTY_CLOSE_SIGNAL] --close-signal value Signal sent to the command process when gotty close it (default: SIGHUP) (default: 1) [$GOTTY_CLOSE_SIGNAL]
--close-timeout value Time in seconds to force kill process after client is disconnected (default: -1) (default: -1) [$GOTTY_CLOSE_TIMEOUT] --close-timeout value Time in seconds to force kill process after client is disconnected (default: -1) (default: -1) [$GOTTY_CLOSE_TIMEOUT]
@ -82,7 +81,7 @@ By default, GoTTY starts a web server at port 8080. Open the URL on your web bro
--version, -v print the version (default: false) --version, -v print the version (default: false)
``` ```
### Config File ### Config File
You can customize default options and your terminal (hterm) by providing a config file to the `gotty` command. GoTTY loads a profile file at `~/.gotty` by default when it exists. You can customize default options and your terminal by providing a config file to the `gotty` command. GoTTY loads a profile file at `~/.gotty` by default when it exists.
``` ```
// Listen at port 9000 by default // Listen at port 9000 by default
@ -91,12 +90,6 @@ port = "9000"
// Enable TSL/SSL by default // Enable TSL/SSL by default
enable_tls = true enable_tls = true
// hterm preferences
// Smaller font and a little bit bluer background color
preferences {
font_size = 5
background_color = "rgb(16, 16, 32)"
}
``` ```
See the [`.gotty`](https://github.com/sorenisanerd/gotty/blob/master/.gotty) file in this repository for the list of configuration options. See the [`.gotty`](https://github.com/sorenisanerd/gotty/blob/master/.gotty) file in this repository for the list of configuration options.
@ -164,7 +157,7 @@ To build the frontend part (JS files and other static files), you need `npm`.
## Architecture ## Architecture
GoTTY uses [xterm.js](https://xtermjs.org/) and [hterm](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm) to run a JavaScript based terminal on web browsers. GoTTY itself provides a websocket server that simply relays output from the TTY to clients and receives input from clients and forwards it to the TTY. This hterm + websocket idea is inspired by [Wetty](https://github.com/krishnasrinivas/wetty). GoTTY uses [xterm.js](https://xtermjs.org/) to run a JavaScript based terminal on web browsers. GoTTY itself provides a websocket server that simply relays output from the TTY to clients and receives input from clients and forwards it to the TTY. This xterm + websocket idea is inspired by [Wetty](https://github.com/krishnasrinivas/wetty).
## Alternatives ## Alternatives

File diff suppressed because one or more lines are too long

View File

@ -1,37 +1,3 @@
/*!
* libapps (https://npmjs.com/package/libapps)
* @license BSD-3-Clause
* @version 1.70.0
* ==libapps/LICENSE==
* // Copyright (c) 2006-2009 The Chromium OS Authors. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without
* // modification, are permitted provided that the following conditions are
* // met:
* //
* // * Redistributions of source code must retain the above copyright
* // notice, this list of conditions and the following disclaimer.
* // * Redistributions in binary form must reproduce the above
* // copyright notice, this list of conditions and the following disclaimer
* // in the documentation and/or other materials provided with the
* // distribution.
* // * Neither the name of Google Inc. nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*! /*!
* xterm (https://npmjs.com/package/xterm) * xterm (https://npmjs.com/package/xterm)
* @license MIT * @license MIT

File diff suppressed because one or more lines are too long

12
js/package-lock.json generated
View File

@ -9,7 +9,6 @@
"version": "2.0.0", "version": "2.0.0",
"dependencies": { "dependencies": {
"css-loader": "^5.2.6", "css-loader": "^5.2.6",
"libapps": "github:yudai/libapps#release-hterm-1.70",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"xterm": "^4.12.0", "xterm": "^4.12.0",
"xterm-addon-fit": "^0.5.0", "xterm-addon-fit": "^0.5.0",
@ -869,12 +868,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/libapps": {
"version": "1.70.0",
"resolved": "git+ssh://git@github.com/yudai/libapps.git#424e3e95e5346ef0c0c281aaf2ef73463a55b39e",
"integrity": "sha512-BpAlnvEvCmcXAPGBdUfrjo1al9V4vIktyKLBxOdYdY/XEAb6rx3Lz/XeZJrns5Tn3IFF0DfBQ9jEF4YZgDMsOg==",
"license": "BSD-3-Clause"
},
"node_modules/license-loader": { "node_modules/license-loader": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/license-loader/-/license-loader-0.5.0.tgz", "resolved": "https://registry.npmjs.org/license-loader/-/license-loader-0.5.0.tgz",
@ -2491,11 +2484,6 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true "dev": true
}, },
"libapps": {
"version": "git+ssh://git@github.com/yudai/libapps.git#424e3e95e5346ef0c0c281aaf2ef73463a55b39e",
"integrity": "sha512-BpAlnvEvCmcXAPGBdUfrjo1al9V4vIktyKLBxOdYdY/XEAb6rx3Lz/XeZJrns5Tn3IFF0DfBQ9jEF4YZgDMsOg==",
"from": "libapps@github:yudai/libapps#release-hterm-1.70"
},
"license-loader": { "license-loader": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/license-loader/-/license-loader-0.5.0.tgz", "resolved": "https://registry.npmjs.org/license-loader/-/license-loader-0.5.0.tgz",

View File

@ -11,7 +11,6 @@
}, },
"dependencies": { "dependencies": {
"css-loader": "^5.2.6", "css-loader": "^5.2.6",
"libapps": "github:yudai/libapps#release-hterm-1.70",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"xterm": "^4.12.0", "xterm": "^4.12.0",
"xterm-addon-fit": "^0.5.0", "xterm-addon-fit": "^0.5.0",

View File

@ -1,94 +0,0 @@
import * as bare from "libapps";
export class Hterm {
elem: HTMLElement;
term: bare.hterm.Terminal;
io: bare.hterm.IO;
columns: number;
rows: number;
// to "show" the current message when removeMessage() is called
message: string;
constructor(elem: HTMLElement) {
this.elem = elem;
bare.hterm.defaultStorage = new bare.lib.Storage.Memory();
this.term = new bare.hterm.Terminal();
this.term.getPrefs().set("send-encoding", "raw");
this.term.decorate(this.elem);
this.io = this.term.io.push();
this.term.installKeyboard();
};
info(): { columns: number, rows: number } {
return { columns: this.columns, rows: this.rows };
};
output(data: string) {
if (this.term.io != null) {
this.term.io.writeUTF8(data);
}
};
showMessage(message: string, timeout: number) {
this.message = message;
if (timeout > 0) {
this.term.io.showOverlay(message, timeout);
} else {
this.term.io.showOverlay(message, null);
}
};
removeMessage(): void {
// there is no hideOverlay(), so show the same message with 0 sec
this.term.io.showOverlay(this.message, 0);
}
setWindowTitle(title: string) {
this.term.setWindowTitle(title);
};
setPreferences(value: object) {
Object.keys(value).forEach((key) => {
if (key != "EnableWebGL") {
this.term.getPrefs().set(key, value[key]);
}
});
};
onInput(callback: (input: string) => void) {
this.io.onVTKeystroke = (data) => {
callback(data);
};
this.io.sendString = (data) => {
callback(data);
};
};
onResize(callback: (colmuns: number, rows: number) => void) {
this.io.onTerminalResize = (columns: number, rows: number) => {
this.columns = columns;
this.rows = rows;
callback(columns, rows);
};
};
deactivate(): void {
this.io.onVTKeystroke = function () { };
this.io.sendString = function () { };
this.io.onTerminalResize = function () { };
this.term.uninstallKeyboard();
}
reset(): void {
this.removeMessage();
this.term.installKeyboard();
}
close(): void {
this.term.uninstallKeyboard();
}
}

View File

@ -1,4 +1,3 @@
import { Hterm } from "./hterm";
import { Xterm } from "./xterm"; import { Xterm } from "./xterm";
import { Terminal, WebTTY, protocols } from "./webtty"; import { Terminal, WebTTY, protocols } from "./webtty";
import { ConnectionFactory } from "./websocket"; import { ConnectionFactory } from "./websocket";
@ -11,11 +10,8 @@ const elem = document.getElementById("terminal")
if (elem !== null) { if (elem !== null) {
var term: Terminal; var term: Terminal;
if (gotty_term == "hterm") {
term = new Hterm(elem);
} else {
term = new Xterm(elem); term = new Xterm(elem);
}
const httpsEnabled = window.location.protocol == "https:"; const httpsEnabled = window.location.protocol == "https:";
const url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws'; const url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws';
const args = window.location.search; const args = window.location.search;

View File

@ -2,13 +2,11 @@ import { Terminal, IDisposable } from "xterm";
import { FitAddon } from 'xterm-addon-fit'; import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links'; import { WebLinksAddon } from 'xterm-addon-web-links';
import { WebglAddon } from 'xterm-addon-webgl'; import { WebglAddon } from 'xterm-addon-webgl';
import { lib } from "libapps"
export class Xterm { export class Xterm {
elem: HTMLElement; elem: HTMLElement;
term: Terminal; term: Terminal;
resizeListener: () => void; resizeListener: () => void;
decoder: lib.UTF8Decoder;
message: HTMLElement; message: HTMLElement;
messageTimeout: number; messageTimeout: number;
@ -38,8 +36,6 @@ export class Xterm {
this.term.focus(); this.term.focus();
this.resizeListener(); this.resizeListener();
window.addEventListener("resize", () => { this.resizeListener(); }); window.addEventListener("resize", () => { this.resizeListener(); });
this.decoder = new lib.UTF8Decoder()
}; };
info(): { columns: number, rows: number } { info(): { columns: number, rows: number } {
@ -47,7 +43,7 @@ export class Xterm {
}; };
output(data: string) { output(data: string) {
this.term.write(this.decoder.decode(data)); this.term.write(Uint8Array.from(data, c => c.charCodeAt(0)));
}; };
showMessage(message: string, timeout: number) { showMessage(message: string, timeout: number) {

View File

@ -1,51 +0,0 @@
export declare namespace hterm {
export class Terminal {
io: IO;
onTerminalReady: () => void;
constructor();
getPrefs(): Prefs;
decorate(HTMLElement);
installKeyboard(): void;
uninstallKeyboard(): void;
setWindowTitle(title: string): void;
reset(): void;
softReset(): void;
}
export class IO {
writeUTF8: ((data: string) => void);
writeUTF16: ((data: string) => void);
onVTKeystroke: ((data: string) => void) | null;
sendString: ((data: string) => void) | null;
onTerminalResize: ((columns: number, rows: number) => void) | null;
push(): IO;
writeUTF(data: string);
showOverlay(message: string, timeout: number | null);
}
export class Prefs {
set(key: string, value: string): void;
}
export var defaultStorage: lib.Storage;
}
export declare namespace lib {
export interface Storage {
}
export interface Memory {
new (): Storage;
Memory(): Storage
}
export var Storage: {
Memory: Memory
}
export class UTF8Decoder {
decode(str: string)
}
}

View File

@ -154,13 +154,6 @@ func (server *Server) processWSConn(ctx context.Context, conn *websocket.Conn) e
if server.options.Height > 0 { if server.options.Height > 0 {
opts = append(opts, webtty.WithFixedRows(server.options.Height)) opts = append(opts, webtty.WithFixedRows(server.options.Height))
} }
if server.options.Preferences == nil {
server.options.Preferences = &HtermPrefernces{}
}
// Awkward hack until HtermPreferences can be phased out
server.options.Preferences.EnableWebGL = server.options.EnableWebGL
opts = append(opts, webtty.WithMasterPreferences(server.options.Preferences))
tty, err := webtty.New(&wsWrapper{conn}, slave, opts...) tty, err := webtty.New(&wsWrapper{conn}, slave, opts...)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to create webtty") return errors.Wrapf(err, "failed to create webtty")
@ -236,7 +229,7 @@ func (server *Server) handleAuthToken(w http.ResponseWriter, r *http.Request) {
func (server *Server) handleConfig(w http.ResponseWriter, r *http.Request) { func (server *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript") w.Header().Set("Content-Type", "application/javascript")
w.Write([]byte("var gotty_term = '" + server.options.Term + "';")) w.Write([]byte("var gotty_term = 'xterm';"))
} }
// titleVariables merges maps in a specified order. // titleVariables merges maps in a specified order.

View File

@ -26,11 +26,9 @@ type Options struct {
Once bool `hcl:"once" flagName:"once" flagDescribe:"Accept only one client and exit on disconnection" default:"false"` Once bool `hcl:"once" flagName:"once" flagDescribe:"Accept only one client and exit on disconnection" default:"false"`
Timeout int `hcl:"timeout" flagName:"timeout" flagDescribe:"Timeout seconds for waiting a client(0 to disable)" default:"0"` Timeout int `hcl:"timeout" flagName:"timeout" flagDescribe:"Timeout seconds for waiting a client(0 to disable)" default:"0"`
PermitArguments bool `hcl:"permit_arguments" flagName:"permit-arguments" flagDescribe:"Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB)" default:"false"` PermitArguments bool `hcl:"permit_arguments" flagName:"permit-arguments" flagDescribe:"Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB)" default:"false"`
Preferences *HtermPrefernces `hcl:"preferences"`
Width int `hcl:"width" flagName:"width" flagDescribe:"Static width of the screen, 0(default) means dynamically resize" default:"0"` Width int `hcl:"width" flagName:"width" flagDescribe:"Static width of the screen, 0(default) means dynamically resize" default:"0"`
Height int `hcl:"height" flagName:"height" flagDescribe:"Static height of the screen, 0(default) means dynamically resize" default:"0"` Height int `hcl:"height" flagName:"height" flagDescribe:"Static height of the screen, 0(default) means dynamically resize" default:"0"`
WSOrigin string `hcl:"ws_origin" flagName:"ws-origin" flagDescribe:"A regular expression that matches origin URLs to be accepted by WebSocket. No cross origin requests are acceptable by default" default:""` WSOrigin string `hcl:"ws_origin" flagName:"ws-origin" flagDescribe:"A regular expression that matches origin URLs to be accepted by WebSocket. No cross origin requests are acceptable by default" default:""`
Term string `hcl:"term" flagName:"term" flagDescribe:"Terminal name to use on the browser, one of xterm or hterm." default:"xterm"`
EnableWebGL bool `hcl:"enable_webgl" flagName:"enable-webgl" flagDescribe:"Enable WebGL renderer" default:"true"` EnableWebGL bool `hcl:"enable_webgl" flagName:"enable-webgl" flagDescribe:"Enable WebGL renderer" default:"true"`
Quiet bool `hcl:"quiet" flagName:"quiet" flagDescribe:"Don't log" default:"false"` Quiet bool `hcl:"quiet" flagName:"quiet" flagDescribe:"Don't log" default:"false"`
@ -43,61 +41,3 @@ func (options *Options) Validate() error {
} }
return nil return nil
} }
type HtermPrefernces struct {
AltGrMode *string `hcl:"alt_gr_mode" json:"alt-gr-mode,omitempty"`
AltBackspaceIsMetaBackspace bool `hcl:"alt_backspace_is_meta_backspace" json:"alt-backspace-is-meta-backspace,omitempty"`
AltIsMeta bool `hcl:"alt_is_meta" json:"alt-is-meta,omitempty"`
AltSendsWhat string `hcl:"alt_sends_what" json:"alt-sends-what,omitempty"`
AudibleBellSound string `hcl:"audible_bell_sound" json:"audible-bell-sound,omitempty"`
DesktopNotificationBell bool `hcl:"desktop_notification_bell" json:"desktop-notification-bell,omitempty"`
BackgroundColor string `hcl:"background_color" json:"background-color,omitempty"`
BackgroundImage string `hcl:"background_image" json:"background-image,omitempty"`
BackgroundSize string `hcl:"background_size" json:"background-size,omitempty"`
BackgroundPosition string `hcl:"background_position" json:"background-position,omitempty"`
BackspaceSendsBackspace bool `hcl:"backspace_sends_backspace" json:"backspace-sends-backspace,omitempty"`
CharacterMapOverrides map[string]map[string]string `hcl:"character_map_overrides" json:"character-map-overrides,omitempty"`
CloseOnExit bool `hcl:"close_on_exit" json:"close-on-exit,omitempty"`
CursorBlink bool `hcl:"cursor_blink" json:"cursor-blink,omitempty"`
CursorBlinkCycle [2]int `hcl:"cursor_blink_cycle" json:"cursor-blink-cycle,omitempty"`
CursorColor string `hcl:"cursor_color" json:"cursor-color,omitempty"`
ColorPaletteOverrides []*string `hcl:"color_palette_overrides" json:"color-palette-overrides,omitempty"`
CopyOnSelect bool `hcl:"copy_on_select" json:"copy-on-select,omitempty"`
UseDefaultWindowCopy bool `hcl:"use_default_window_copy" json:"use-default-window-copy,omitempty"`
ClearSelectionAfterCopy bool `hcl:"clear_selection_after_copy" json:"clear-selection-after-copy,omitempty"`
CtrlPlusMinusZeroZoom bool `hcl:"ctrl_plus_minus_zero_zoom" json:"ctrl-plus-minus-zero-zoom,omitempty"`
CtrlCCopy bool `hcl:"ctrl_c_copy" json:"ctrl-c-copy,omitempty"`
CtrlVPaste bool `hcl:"ctrl_v_paste" json:"ctrl-v-paste,omitempty"`
EastAsianAmbiguousAsTwoColumn bool `hcl:"east_asian_ambiguous_as_two_column" json:"east-asian-ambiguous-as-two-column,omitempty"`
Enable8BitControl *bool `hcl:"enable_8_bit_control" json:"enable-8-bit-control,omitempty"`
EnableBold *bool `hcl:"enable_bold" json:"enable-bold,omitempty"`
EnableBoldAsBright bool `hcl:"enable_bold_as_bright" json:"enable-bold-as-bright,omitempty"`
EnableClipboardNotice bool `hcl:"enable_clipboard_notice" json:"enable-clipboard-notice,omitempty"`
EnableClipboardWrite bool `hcl:"enable_clipboard_write" json:"enable-clipboard-write,omitempty"`
EnableDec12 bool `hcl:"enable_dec12" json:"enable-dec12,omitempty"`
EnableWebGL bool `json:"EnableWebGL,omitempty"`
Environment map[string]string `hcl:"environment" json:"environment,omitempty"`
FontFamily string `hcl:"font_family" json:"font-family,omitempty"`
FontSize int `hcl:"font_size" json:"font-size,omitempty"`
FontSmoothing string `hcl:"font_smoothing" json:"font-smoothing,omitempty"`
ForegroundColor string `hcl:"foreground_color" json:"foreground-color,omitempty"`
HomeKeysScroll bool `hcl:"home_keys_scroll" json:"home-keys-scroll,omitempty"`
Keybindings map[string]string `hcl:"keybindings" json:"keybindings,omitempty"`
MaxStringSequence int `hcl:"max_string_sequence" json:"max-string-sequence,omitempty"`
MediaKeysAreFkeys bool `hcl:"media_keys_are_fkeys" json:"media-keys-are-fkeys,omitempty"`
MetaSendsEscape bool `hcl:"meta_sends_escape" json:"meta-sends-escape,omitempty"`
MousePasteButton *int `hcl:"mouse_paste_button" json:"mouse-paste-button,omitempty"`
PageKeysScroll bool `hcl:"page_keys_scroll" json:"page-keys-scroll,omitempty"`
PassAltNumber *bool `hcl:"pass_alt_number" json:"pass-alt-number,omitempty"`
PassCtrlNumber *bool `hcl:"pass_ctrl_number" json:"pass-ctrl-number,omitempty"`
PassMetaNumber *bool `hcl:"pass_meta_number" json:"pass-meta-number,omitempty"`
PassMetaV bool `hcl:"pass_meta_v" json:"pass-meta-v,omitempty"`
ReceiveEncoding string `hcl:"receive_encoding" json:"receive-encoding,omitempty"`
ScrollOnKeystroke bool `hcl:"scroll_on_keystroke" json:"scroll-on-keystroke,omitempty"`
ScrollOnOutput bool `hcl:"scroll_on_output" json:"scroll-on-output,omitempty"`
ScrollbarVisible bool `hcl:"scrollbar_visible" json:"scrollbar-visible,omitempty"`
ScrollWheelMoveMultiplier int `hcl:"scroll_wheel_move_multiplier" json:"scroll-wheel-move-multiplier,omitempty"`
SendEncoding string `hcl:"send_encoding" json:"send-encoding,omitempty"`
ShiftInsertPaste bool `hcl:"shift_insert_paste" json:"shift-insert-paste,omitempty"`
UserCss string `hcl:"user_css" json:"user-css,omitempty"`
}