diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
deleted file mode 100644
index 9f3768a..0000000
--- a/.github/workflows/test.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-name: Test
-on: [push, pull_request]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- os: [linux]
- tags: [[], ['null'], [portal], [uinput], [x11],
- ['null', portal, uinput, x11]]
- include:
- - os: windows
- tags: []
- - os: windows
- tags: ['null']
- env:
- GOOS: ${{ matrix.os }}
- GOFLAGS: -tags=${{ join(matrix.tags, ',') }}
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-go@v4
- with:
- go-version: stable
- check-latest: true
- - run: sudo apt install libxrandr-dev libxtst-dev libxt-dev
- if: ${{ contains(matrix.tags, 'x11') }}
-
- - name: Go Format
- run: gofmt -s -w . && git diff --exit-code
-
- - name: Go Mod Tidy
- run: go mod tidy && git diff --exit-code
-
- - name: Go Vet
- run: go vet ./...
-
- - name: Go Build
- run: go build ./...
-
- - name: Go Test (with data race detection)
- run: go test -race ./...
- if: ${{ matrix.os == 'linux' }}
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 07c477a..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-# Binaries
-remote-touchpad
-
-# Systemd service state
-.remote-touchpad.service.state
-
-# Logs and run files
-.rts-log
-
-# Environment files
-.env
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index e59454c..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,220 +0,0 @@
-# Changelog
-
-## 1.5.4 (2026-08-02)
-
-* Improve X11 keyboard input
-* Update dependencies
-
-## 1.5.3 (2025-12-24)
-
-* Update dependencies
-
-## 1.5.2 (2025-10-12)
-
-* Update dependencies
-
-## 1.5.0 (2025-03-18)
-
-* Add restore token support for the remote desktop portal
-* Update dependencies
-
-## 1.4.8 (2024-09-01)
-
-* Update dependencies
-
-## 1.4.7 (2024-08-30)
-
-* Update dependencies
-
-## 1.4.6 (2024-02-06)
-
-* Prevent incorrect MIME types
-
-## 1.4.5 (2024-01-31)
-
-* Keep text input open after sending
-* Workaround for virtual keyboard covering buttons (Chromium & iOS)
-* Enable hardware keyboard input in mouse mode
-
-## 1.4.4 (2024-01-23)
-
-* Fix Xwayland detection on newer servers
-* Fix support for the KDE remote desktop portal
-* Set correct minimum version for Go
-* Update dependencies
-
-## 1.4.3 (2023-10-03)
-
-* Update dependencies
-
-## 1.4.2 (2023-05-20)
-
-* Fix Windows support
-
-## 1.4.1 (2023-05-11)
-
-* uinput: Fix vertical scroll direction
-* Update dependencies
-
-## 1.4.0 (2023-04-25)
-
-* Add feedback to buttons
-* Add numeric keypad
-* Prevent touch triggering mouse mode
-
-## 1.3.0 (2023-03-03)
-
-* Initial support for uinput
-* Update dependencies
-
-## 1.2.3 (2022-12-29)
-
-* Improve XWayland detection
-* Update dependencies
-* Flatpak: Add Wayland socket to disable X11 socket
-
-## 1.2.2 (2022-09-23)
-
-* Update dependencies
-* Fix race in RemoteDesktop portal controller
-
-## 1.2.1 (2022-06-20)
-
-* Update dependencies
-
-## 1.2.0 (2022-02-26)
-
-* Add command-line options for move and scroll speed
-* Add hardware mouse support
-* Limit update rate
-* Windows: Reverse vertical scroll direction
-* Show error message if compiled without controller
-* Don't show disabled controllers in "unsupported platform" error message
-* Switch from go-bindata-assetfs to go:embed
-
-## 1.1.0 (2022-01-28)
-
-* Add more shortcut keys (Arrows, Delete, Enter, …)
-* Add basic support for physical keyboard
-* Allow fullscreen on keys page
-* Enable touchpad on keys page
-* Retain keyboard text when reloading website
-* Improve compatibility with old browsers
-* Write URL and QR code to stdout
-* Ignore unparseable IP
-* Remove trailing new line from QR code
-
-## 1.0.5 (2022-01-07)
-
-* Use relative URL for websocket
-* Update dependencies
-
-## 1.0.4 (2021-11-06)
-
-* Disable browser touch gestures
-* Update dependencies
-
-## 1.0.3 (2021-09-18)
-
-* Update dependencies
-
-## 1.0.2 (2021-06-05)
-
-* New logo
-* Update dependencies
-
-## 1.0.1 (2020-09-13)
-
-* Update dependencies
-
-## 1.0.0 (2020-08-26)
-
-* Add super and browser back and forward keys
-* Reduce minimum size of textarea
-* Small UI improvements
-* Remove obsolete command line arguments
-
-## 0.0.18 (2020-05-16)
-
-* Fix error messages for TLS certificate and private key files
-
-## 0.0.17 (2020-02-20)
-
-* Minor HTML improvements
-
-## 0.0.16 (2020-02-17)
-
-* Fix textarea on WebKit
-
-## 0.0.15 (2019-11-03)
-
-* Prefer IPv4 addresses
-
-## 0.0.14 (2019-10-28)
-
-* Handle quirks of browser touch events
-* Hide navigation bar in fullscreen mode
-* Set terminal title
-* Flatpak: Update dependencies
-
-## 0.0.13 (2019-05-04)
-
-* Flatpak: Update dependencies
-* AppStream: Fix release descriptions
-
-## 0.0.12 (2018-12-08)
-
-* fix the browser Forward button
-
-## 0.0.11 (2018-10-20)
-
-* rename plugin to backend
-* update flatpak
-
-## 0.0.10 (2018-10-01)
-
-* avoid race condition when typing on X11
-
-## 0.0.9 (2018-09-29)
-
-* add multimedia keys
-
-## 0.0.8 (2018-09-07)
-
-* enable pointer movement after timeout
-
-## 0.0.7 (2018-09-04)
-
-* use custom font for icons
-
-## 0.0.6 (2018-08-23)
-
-* use colors in terminal for QR qode
-* remove ``-invert`` command line argument
-* mitigate race condition when typing text on X11
-* improve KeySyms used for text on X11 and Flatpak's RemoteDesktop portal
-* normalize line endings
-
-## 0.0.5 (2018-07-31)
-
-* remove mouse polling interval
-* add support for "pixel-perfect" scrolling
-* add experimental support for Flatpak's RemoteDesktop portal
-* X11 plugin: check session type
-* improve error logging
-
-## 0.0.4 (2018-07-22)
-
-* add setting for mouse polling interval
-
-## 0.0.3 (2018-06-26)
-
-* fix websocket with https
-
-## 0.0.2 (2018-06-25)
-
-* add reload button to disconnected-page
-
-## 0.0.1 (2018-06-23)
-
-initial release
diff --git a/README.md b/README.md
index e039950..951e288 100644
--- a/README.md
+++ b/README.md
@@ -1,56 +1,9 @@
# Remote Touchpad
-Control mouse and keyboard from the webbrowser of a smartphone
-(or any other device with a touchscreen).
-To take control open the displayed URL or scan the QR code.
+Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen).
+To take control just open the displayed URL or scan the QR code.
-Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11.
-
-## Installation
-
-* [Flatpak](https://flathub.org/apps/details/com.github.unrud.RemoteTouchpad)
-* [Snap](https://snapcraft.io/remote-touchpad)
-* [Windows](https://github.com/Unrud/remote-touchpad/releases/latest)
-* Golang:
- * Portal & uinput & X11:
-
- ```sh
- go install -tags portal,uinput,x11 github.com/unrud/remote-touchpad@latest
- ```
- * Windows:
-
- ```sh
- go install github.com/unrud/remote-touchpad@latest
- ```
-
-## Linux User Service Installation
-
-To install remote-touchpad as a user-specific systemd service (no root access required):
-
-1. Clone the repository and navigate to the project directory:
-
- ```sh
- git clone https://github.com/Unrud/remote-touchpad.git
- cd remote-touchpad
- ```
-
-2. Install the service using the provided wrapper script (no sudo required):
-
- ```sh
- ./rts install
- ```
-
-3. Access the service:
-
- * View service status: `./rts status`
- * Get URL: `./rts url`
- * View logs: `./rts log`
- * Stop service: `./rts stop`
- * Start service: `./rts start`
- * Restart service: `./rts restart`
- * Uninstall: `./rts uninstall`
-
-The service will run in the background and maintain a persistent connection with URL and QR code accessible via the `url` command.
+Supports with Linux/X11 and Windows.
## Screenshots
@@ -59,5 +12,3 @@ The service will run in the background and maintain a persistent connection with


-
-
diff --git a/bindata.generated.go b/bindata.generated.go
new file mode 100644
index 0000000..ef99e13
--- /dev/null
+++ b/bindata.generated.go
@@ -0,0 +1,363 @@
+// Code generated by go-bindata.
+// sources:
+// webdata/fn.js
+// webdata/icon.png
+// webdata/index.html
+// webdata/main.css
+// webdata/sha256.js
+// webdata/tex.png
+// DO NOT EDIT!
+
+package main
+
+import (
+ "github.com/elazarl/go-bindata-assetfs"
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+func (fi bindataFileInfo) IsDir() bool {
+ return false
+}
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var _webdataFnJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3a\xdd\x6f\xdb\xb6\xb7\xef\xf9\x2b\x4e\xf5\x50\xc8\x8d\x23\xdb\x29\x7a\x37\xd4\x75\xee\x75\x53\x77\x09\x96\x34\x81\xed\x6c\x35\x82\xa0\xa0\xa5\x63\x5b\xab\x4c\x6a\x22\x15\x27\xbd\xcd\xff\x7e\x41\x7d\x52\x14\x25\x67\x1d\xfa\x72\xf1\x33\xb0\x4e\x21\xcf\x39\x3c\xdf\x1f\x94\x7a\xaf\x0e\xe0\x15\x00\xc0\x29\x0b\x1f\x23\x7f\xbd\x11\x60\xbb\x1d\x38\xee\x0f\x7e\x85\x1b\x1a\xc5\xde\xbb\x58\xfe\xfb\x3f\x2c\x16\x01\x63\x5f\x1d\x97\x6d\x4f\x0e\x20\x47\x9a\x6f\x7c\x0e\x2b\x3f\x40\xf0\x39\x84\x24\x12\xc0\x56\x30\xc5\x2d\x13\x78\x34\x67\xb1\xbb\x09\x89\xe7\x94\xe0\x1f\x19\x5b\x92\x48\xc2\xae\x22\x44\xe0\x6c\x25\x76\x24\xc2\xb7\xf0\xc8\x62\x70\x09\x85\x08\x3d\x9f\x8b\xc8\x5f\xc6\x02\xc1\x17\x40\xa8\xd7\x63\x11\x6c\x99\xe7\xaf\x1e\x33\x22\xbe\x80\x98\x7a\x18\x81\xd8\x20\x08\x8c\xb6\x5c\x1e\x2a\xff\xf8\xed\xd3\x0d\xfc\x86\x14\x23\x12\xc0\x75\xbc\x0c\x7c\x17\x2e\x7c\x17\x29\x47\x20\x1c\x42\xb9\xc2\x37\xe8\xc1\x32\x27\x25\x91\x3e\x4a\x4e\x66\x19\x27\xf0\x91\xc5\xd4\x23\xc2\x67\xb4\x0b\xe8\x8b\x0d\x46\x70\x8f\x11\xf7\x19\x85\xd7\xf9\x31\x19\xcd\x2e\xb0\x28\xa3\x63\x13\x21\x45\x88\x80\x85\x12\xb5\x03\x84\x3e\x42\x40\x44\x89\xad\x28\x41\x53\x8f\xd4\x46\x29\xb4\x07\x3e\x4d\x0e\xd9\xb0\x10\x41\x6c\x88\x90\xf2\xee\xfc\x20\x80\x25\x42\xcc\x71\x15\x07\xdd\x8c\xd0\x32\x16\xf0\xe7\xf9\xfc\xec\xea\x66\x0e\xe3\x4f\x0b\xf8\x73\x3c\x9d\x8e\x3f\xcd\x17\x43\xd8\xf9\x62\xc3\x62\x01\x78\x8f\x29\x35\x7f\x1b\x06\x3e\x7a\xb0\x23\x51\x44\xa8\x78\x04\xb6\xca\x88\x5c\x4e\xa6\xa7\x67\xe3\x4f\xf3\xf1\xfb\xf3\x8b\xf3\xf9\x02\x58\x04\x1f\xcf\xe7\x9f\x26\xb3\x19\x7c\xbc\x9a\xc2\x18\xae\xc7\xd3\xf9\xf9\xe9\xcd\xc5\x78\x0a\xd7\x37\xd3\xeb\xab\xd9\xc4\x01\x98\xa1\x64\x0e\x33\x1a\x2d\x7a\x5f\x25\xd6\x8b\x10\x3c\x14\xc4\x0f\x78\xa9\x87\x05\x8b\x81\x6f\x58\x1c\x78\xb0\x21\xf7\x08\x11\xba\xe8\xdf\xa3\x07\x04\x5c\x16\x3e\xee\xb7\x69\x4a\x86\x04\x8c\xae\x13\x81\x33\xef\x72\x00\xce\x57\x40\x99\xe8\x02\x47\x84\x77\x1b\x21\xc2\xb7\xbd\xde\x6e\xb7\x73\xd6\x34\x76\x58\xb4\xee\x05\x29\x01\xde\x3b\x91\xec\xf4\x0e\x0e\xa0\xd7\x83\xdb\x01\x24\x26\xe9\xc2\x71\xfa\x80\xbc\x0b\xaf\xf3\xc7\xbb\x03\x97\x51\x2e\x60\x7e\x75\x73\x7a\xf6\xe5\xf2\xea\x8f\xc9\x97\xf9\xd9\x74\x32\x3b\xbb\xba\xf8\x00\x23\xb8\x1d\xf4\xbb\x30\x78\x23\xff\xbb\x1b\x56\x40\xe7\xe7\x97\x13\x69\xa0\x11\x1c\xbf\xe9\xe7\x5b\x09\xfe\xe5\xcd\x85\x5c\x1e\xe4\x8b\xb3\xd3\xe9\xd5\xc5\x45\xbe\xdc\x77\xfa\x6f\x86\x07\x92\xaf\xdb\xf0\xa1\xc7\xbb\xb0\x8d\x03\x71\xd7\x05\xc7\x71\x72\x5e\xae\xaf\xce\x3f\xcd\x27\xd3\x2f\xe3\xd3\xd3\xc9\xc5\x64\x3a\x9e\x9f\x5f\x7d\x92\xbc\xdc\xf6\xbb\xd0\xbf\xeb\xc2\xed\xaf\xbf\x74\x61\x20\x1f\x06\xbf\xbc\xce\x9e\xde\xbc\x79\xdd\x85\xe3\xbb\x82\xc9\x9b\xeb\x0f\xe3\xf9\xe4\x4b\x42\xe8\x8f\xf1\x05\x8c\x40\xe1\x72\xfc\xf9\xcb\xf9\x87\x8b\xc9\x97\x14\x68\x26\x99\xed\x0f\x0f\x0e\xee\x49\x04\x3b\x3e\x4c\xfe\x1f\x12\xaf\x78\x08\xc8\x12\x83\x6c\x5f\x48\xb5\x5d\x32\x69\xcf\x11\xac\x48\xc0\x71\x58\xae\xcf\x84\xcc\x14\x23\xe8\x2b\x6b\x17\x84\x8b\x09\xf5\xb4\xd5\x29\x06\x48\x38\x7a\xa7\x2c\xa6\x0a\x06\xa3\x6b\xe6\xd3\x75\x66\x1b\x29\xf3\x5d\xba\xb1\x65\xf7\xf8\x79\x16\x6f\x4b\x50\xb9\xb2\xa8\xac\x70\x37\x62\x41\xf0\xd9\xb0\x56\x85\xf3\x22\xb2\x5e\xfb\x74\x5d\x15\x20\x5f\x9d\xfb\x5b\x94\x21\x36\x02\x1a\x07\x41\xba\x17\x87\x1e\x11\x28\x77\xa2\xca\xba\xef\x05\x78\x93\xec\xf1\x94\xfc\xc1\x2a\xa6\xae\xcc\x14\xb0\x8a\x83\x80\xbb\x11\x22\x9d\x50\xb2\x0c\xd0\xb3\x3b\xf0\xbf\x07\x32\xa8\x22\x14\x71\x44\xc1\xf6\x98\x1b\x6f\x91\x0a\xa7\x06\x0a\xdf\xbf\x27\x90\xf2\x57\x40\xed\x70\xf9\xd5\x17\x1f\x9f\x05\xbb\x65\xdf\x24\xe0\x6c\x3f\x20\x6f\x25\x98\xa8\xa7\x33\x3c\x78\x52\x04\x8b\xf0\xef\x18\xb9\xc2\x88\x8d\xb9\x64\xfe\x0a\x6c\x74\x6a\x00\xf9\xb6\xfc\x19\xb6\xed\xce\x30\xd9\x7e\x02\x0c\x38\x66\x44\x52\x69\xa7\xed\xa4\x1a\x80\x8c\x04\xb7\xec\x9b\x02\x38\x33\x50\x33\x41\x98\x49\xf1\x3d\x7c\x19\x00\x0a\x42\x15\x5d\xe2\x83\x5f\x81\x51\xf4\x58\x98\xa8\x0a\xa3\x1e\xd4\x00\x62\xe0\x59\x73\xa2\xc9\x7e\x92\x26\xc0\x36\xc2\x5b\xf6\xed\x94\x50\x17\x03\xb3\x76\xdb\xe0\x5a\xc9\xf2\x67\xf0\xaa\x03\x99\x75\xad\x44\x59\x80\x12\xef\x59\x01\x99\x82\x3e\x2f\x20\x5b\x60\xab\x01\xd9\x06\xc8\x5b\x09\xca\xd4\xa3\xc5\x23\xf1\xbc\x12\xc5\xdd\x10\xba\xc6\xc9\x3d\x52\x71\xe1\x73\x21\xcb\xab\x1d\x64\x0f\x46\xef\xaa\x8b\xfa\x62\x34\x4a\xda\xb0\x95\x4f\xd1\x33\xea\x9b\x78\x5e\xf5\x04\x6b\xa5\x31\x60\x75\xa1\x38\x75\x9f\x33\xd6\xe5\xfd\x11\x0e\x52\x5a\x3f\xc6\x87\xd1\x38\x3f\xc2\xc4\x96\x7d\xfb\x41\x0e\x0c\x56\xff\x11\x06\x2e\x67\x25\x9d\xd3\xa6\xf3\x2b\xde\x23\x3b\xb2\xa4\xda\xda\x22\xed\x91\x84\xbf\xc5\x99\x20\xdb\x50\x8b\x8e\xf2\x7c\xdf\x43\x2a\xfc\x95\x8f\xd1\xdb\xb4\x96\x3b\xe5\x4a\xb7\x80\x0a\xc9\x1a\x3f\xe7\x00\xc9\x1f\xda\x5e\xd2\x2b\xb4\x00\x2c\xd4\xbd\x85\xb6\x57\x43\x56\x00\x0a\x09\xde\x96\x8f\xa9\xe4\xd5\xc0\x51\x7b\x8d\x73\xea\xe1\xc3\xfb\xc7\x73\xcf\xf6\xbd\x39\xfb\xe8\xd3\x42\xe3\xb2\xcf\xb5\x93\x5a\x9f\x54\x78\xf0\xe1\x9d\xd6\xa5\x38\x01\xd2\xb5\xd8\x0c\xc1\x3f\x3c\x54\xed\x94\x36\x08\x30\xd2\xc0\x6f\xfd\x3b\x45\x61\xc3\x83\x52\xaf\x2b\xb0\x25\xfc\x08\x74\x1e\xf2\x5f\x66\x0a\x7f\x58\xac\x3e\x1d\x94\xff\x66\xbb\x47\x83\xaa\x98\x2e\x09\xdc\x58\x8e\x2c\xd7\xcc\xa7\x02\xa3\xb1\xeb\x62\x80\x51\x32\x0a\x5d\xc6\x81\xb0\x79\x88\xd8\x2a\xae\xa9\x1d\x6d\x12\x9a\x1f\xc3\xc8\x88\x70\xeb\xdf\xdd\xf6\xef\x4a\xce\x49\x1b\xe0\x40\x01\x94\x6a\xe1\xc7\xf0\x6e\x04\x15\x46\xf3\x9f\xcb\xa8\xf0\x69\x8c\xba\x4e\x0a\x8d\x4a\x85\xf6\x1b\x34\x49\x8e\x4d\x68\x7c\xd0\xc8\xda\xd1\x40\x93\xa2\x1d\x54\x95\x23\xaf\x32\xa9\xbe\x8f\xf8\xa0\x03\x3d\x29\x99\x7c\xea\xc0\x2b\xb0\xc9\xf1\x11\x19\x74\xe0\x10\xc8\x60\xa8\x98\x55\x0a\xd1\x62\x01\x38\xa9\x4a\x97\x1d\x63\xe4\xa9\x85\x8c\xca\x6d\xc5\x9d\x06\x7a\xd0\x7c\xa8\xf6\xc9\x45\x15\x6d\xea\x9f\xe5\xde\x8e\x3b\x1c\xa9\x67\x5b\x99\x0f\xbe\x8f\x85\x60\x14\x06\xd0\xb7\xb4\x6a\x96\x76\xda\x72\xbe\x18\x53\x6f\x96\x34\xef\x95\xb6\xa8\xd2\x89\xa7\x47\xa8\xd2\x33\x7a\x53\x00\x24\xcc\x55\xfd\x48\x1f\x8a\x4e\xa0\x0f\x2f\x5f\x56\xdb\x78\x83\xb7\x54\xdb\x7f\x8e\xe2\x5c\x0a\x71\x4f\x02\x5b\x3b\xaf\xab\x8f\x5d\x9d\x7a\xa8\x56\xb5\xa9\xb1\x9b\x4b\xaa\x30\x74\x98\x4c\x92\x79\x42\x49\xe6\x20\x18\xc1\x25\x11\x1b\x47\x44\x31\x75\xed\x7c\x34\xea\x54\xa1\x16\x75\xa8\x45\x09\x25\xb5\x91\xec\x92\x25\x4f\x29\x74\xe0\x64\x04\x03\xf8\xfe\x1d\x2a\xeb\x8b\x74\x5d\x55\x49\x31\x8b\x1d\x8d\xd2\xe7\x61\x65\x6b\xa1\x6c\x2d\x14\xfd\xeb\xb3\x52\xbe\xa1\x3b\x87\x34\x3e\x58\x70\x98\xc9\x7a\x08\x56\xf1\xd7\xa2\xa3\x7a\xa8\x32\xf3\x55\x65\x2d\x07\x41\x45\x27\xd9\x24\x68\x82\x6c\xd2\x4b\x46\xc7\xa0\x99\x0c\xaf\xae\x1b\x65\x06\x3d\x1a\xe5\x7f\x0d\xb5\xed\x45\x65\xfb\x47\x74\x94\x46\x46\xa2\x97\x5c\x03\xb9\x9e\x72\xd6\xf4\x1c\xa2\xd2\x3e\x19\xd5\xee\x00\x54\x21\xdc\x00\x49\x54\xf8\xb8\xe2\xfd\x8a\x3b\x9b\x46\xe2\xba\x83\x6f\x08\xf5\x02\x4c\xaa\xb5\x8d\xf7\x42\x8d\x64\x63\x11\xad\x85\x5f\xe5\x5a\x01\xef\x85\x53\x94\xf4\x61\x15\x46\xbb\x92\xa8\xec\x19\x2e\x1b\xf2\xfd\xfa\x5d\x40\xd5\xbf\x44\x71\x13\x21\x0f\x4f\x7b\x3a\x2f\xe3\x79\xd8\x58\x31\x45\x7b\x67\x20\xc5\x17\x65\x37\x20\x48\xb4\xc6\xb4\xe1\x0b\x89\x27\x13\x52\xe3\x66\x72\x13\xf3\x4f\x4a\xa0\xe4\x3a\x8c\x50\xb6\x89\x1f\x70\x45\x64\xbd\x57\x8c\xa8\xd9\x20\x8c\xf9\xc6\xd6\x3a\xc2\x84\x8b\x6e\x55\xf5\x9d\x8e\xa6\xe0\xca\x1d\x8f\x2a\xe5\x8b\x5c\xbf\x3a\xcf\xca\x7d\x4e\x73\x2a\xab\x65\x95\xb6\x8c\x56\x95\xbb\x72\x17\xd4\x96\x1a\xb4\xa8\x6c\xcf\x0e\xb9\x58\x7a\xad\x7b\x61\x28\x45\x45\x20\xe5\xc9\x5d\x43\xd2\x64\x6c\x2b\x9f\x06\x67\x15\x51\xdd\xde\xa6\xd0\x9b\x50\x4f\x0d\xbc\x9f\xe6\xd2\x69\xb3\xfb\xa0\x75\xbb\x65\x5b\x2d\x4c\xcd\xaf\xa6\x55\x89\xff\xae\x5e\x7d\xdb\xdc\x5b\x73\x60\x1e\x06\xbe\x8b\x92\x50\x17\x06\xba\x93\x56\xb2\xc0\xe1\x61\xa3\x0b\x1b\xb2\x4c\x99\x45\x0d\xf9\xe4\xc4\x78\x77\x9c\x29\xaa\x96\xcb\xf2\x3c\x55\x1a\xf0\xe9\x19\x29\xb1\x48\x0a\xda\xd1\xb2\x2e\xbd\x7c\x69\x74\x12\xf5\xe4\xf6\xfe\xeb\x27\x30\xf1\x42\x91\xf5\xe5\xcb\xaa\x4a\xe1\x48\xcd\xea\xef\xaa\xd7\xe9\xba\x4f\x2d\x53\x4e\x6b\x89\xc5\x94\xd7\xb5\x52\x0c\xe9\x9b\x94\x14\x7d\xa0\x78\x4f\x39\x80\x9b\xa9\x1c\x37\x52\x79\xad\x50\xd9\x43\xe4\x75\x23\x11\xe3\xcc\xd1\x60\x21\x59\xd0\x33\x44\x59\xdf\x07\x96\x16\x32\x39\x51\x83\xe8\xf5\x84\xc2\x51\xe4\xb9\xa8\xd6\xc6\x77\x35\x33\xd4\xd4\x55\x25\xfe\x3c\x76\xfb\xd6\x9e\xfe\x37\xcd\x51\xe9\x85\xe0\x7f\xd2\xd4\xbf\x4e\x53\xd0\x96\x64\xea\x7a\x97\x50\xba\xd6\x79\xbc\xfd\x5c\x46\x5b\xb6\xb2\xa8\xae\xfc\x7f\xb2\x4c\xf2\xe6\xc7\xe7\x22\x2f\xfc\xfc\xef\x48\xa4\x03\x40\xc8\x76\xea\xa1\xc9\x0d\x15\x1c\xd5\x2e\x73\xbc\x87\x6c\x2f\x49\x67\x5d\x99\x3c\x0e\xa1\x89\xc0\xa2\x85\xc0\xa2\x20\xa0\x49\x65\x4e\xc8\xad\x55\x47\xce\x2a\x89\x54\x66\xa8\x5b\x33\xc9\x23\x18\xdc\xe9\xba\x6b\x70\x27\x83\x12\xa5\xe9\xfe\x89\xbe\x86\x55\xf4\xc7\x3a\x7a\xab\xb6\xaa\xe8\x32\x10\x3e\x60\x20\x88\x1e\x18\x0d\x24\x0c\x81\x93\x78\xfe\xe1\x48\x0a\xf2\x6a\xff\xa5\x59\x31\x09\x7a\x0f\x1d\xe8\x29\xe7\xbf\x82\x41\xbf\xdf\xef\x54\xe8\x2e\x12\xba\x8f\xff\x8c\xee\xe3\x1e\xba\x8d\x8a\x35\xd8\x61\x3f\xd6\xc2\xa0\xfe\x76\xac\x52\xc5\xcf\x69\x99\xda\x5b\x81\x3c\xab\x9d\x8c\x9a\xbb\x81\xd6\xde\x24\x99\xce\xf7\xce\x1a\x87\xa3\xd4\xca\xaf\xca\xf7\xf4\x0d\x83\x46\x0a\xb9\x30\x43\x2a\x0d\x44\x23\x43\xb5\x1e\x42\xbb\x18\x48\xd9\x50\xbe\x0c\x18\x1a\xa0\x17\x25\xf4\xa2\x09\xfa\x49\x9b\xc8\xb5\xab\x33\xe3\x9d\xff\x86\x04\x92\x53\x9c\x22\x0f\x19\xe5\x68\x6f\x91\x73\xb2\xc6\x4a\x21\xd8\x90\xab\xe5\x5f\x72\x0c\xc1\x1d\xfc\xc5\x67\x67\x63\xdb\x9a\x9d\x8d\x8f\x8e\xdf\xfc\x97\xd5\x05\x6b\x3e\xf9\x3c\xcf\xab\x7b\x0a\xea\x70\x14\x67\x97\xe3\xd3\xdf\xf1\x31\xa7\x67\x86\x4b\xf9\xb4\x77\x3e\xf5\xd8\xce\x09\x98\x9b\x78\xbf\xb3\x21\x7c\xe3\xf0\x78\xc9\x45\x64\x0f\xf2\x1c\x98\x5d\x42\x2e\x05\x23\x76\x86\xbe\x4e\x8f\xb1\xad\xf7\x8b\xf9\x64\x66\x75\xd2\x0b\xc4\x8c\x5a\xfd\x95\x48\xc0\x88\x67\x75\x21\x17\xde\x56\x65\x24\xb1\xd8\xc8\x52\xe2\x12\xa1\xdf\x1d\x24\x1f\x23\x84\x48\xd3\x69\xab\x78\xe5\xb2\x46\x91\xbd\x9f\x49\x8a\x92\x95\x81\x58\xca\x3d\x93\x1b\x30\x9e\x50\x6b\x44\x4a\x21\x72\x1c\x39\xf2\xb7\x00\x87\x44\x85\x4c\xe6\xff\x3d\xe0\x09\x8c\xca\xd1\x57\x7c\x5c\x32\x12\xb5\x1e\x93\xc3\xa8\x78\xe5\xbb\xac\xa2\x77\x6d\xc4\xd7\x61\x55\x3a\x02\x1f\x44\x1b\xae\xdc\xcf\xe1\x53\xdd\x38\x5c\x3c\x06\xe8\x78\x3e\x0f\x03\x22\x2b\x83\x45\x19\x45\xab\x50\x43\xeb\x7e\x2e\x4a\x2b\x90\x3c\xd3\xb9\x27\x41\x8c\x72\xc3\xca\x5e\xc3\xec\x78\xe6\xf0\x7f\xe2\x72\xc6\xdc\xaf\x28\x6c\x6b\xc7\xdf\xf6\x7a\xb2\xb1\x2d\x3d\x95\x71\x41\xc9\x16\xe1\xb0\x12\xb3\xca\xcf\x2e\x60\x43\x16\x09\xf8\x6f\xb0\xde\x56\x28\x24\xab\x6f\xc1\xb2\x3a\xcd\x34\xac\xde\x8e\x4b\xad\x64\x8c\x39\x8c\x66\x51\x25\x1d\x35\xf7\xe6\xe4\x62\x47\xcf\x92\x15\xb7\xd6\xf3\xd0\x8e\x3b\x89\x8e\x6d\xed\xfe\x21\x8d\x34\x53\x5e\xd1\xa3\xa4\xda\x05\xe4\x93\x40\x3d\xa9\x24\xbc\x39\x1e\x11\x44\xed\x68\xb2\x88\x69\x35\x4e\x2e\xc8\xc6\xe7\x82\x45\x8f\x0e\x17\x44\xa0\xcc\xaa\x8a\x9b\x6a\x62\x35\x1b\x7d\x15\xe0\x83\x55\x95\x35\x31\xfe\x8a\xb9\x31\xb7\xf7\x0d\x3a\x46\x67\xd3\x48\x66\xaa\x7a\xca\xdf\xe5\x25\xc6\x4a\x94\xac\x9a\x4a\xb7\x92\xe1\x7b\x04\x5d\x28\xf3\x77\x1d\x55\xeb\x3c\x57\x9f\xfb\xa2\xa6\x5d\x89\x09\x60\x01\xd7\x14\xa4\xaa\x5e\x72\x6d\xec\xcd\x38\x79\xbe\x30\xe4\x6e\x37\xf0\xdd\xaf\x56\xb7\x29\x42\x9e\xfb\x2b\xa3\xe5\x27\xd9\xe0\xdf\xe9\x56\xf3\x26\xb3\x73\xe6\xa1\x10\xc6\x7c\x33\x93\xe1\xa0\xa4\xec\x2e\x58\xe9\xa7\xa7\xf0\xbb\x96\xc5\x9f\x94\xb7\x1a\x2f\x0c\x5f\xa4\xa9\xc2\xea\x19\xbc\x55\xa2\x54\xf6\x1a\x4a\xa3\x09\x7f\x9a\x0d\x4c\x31\x5b\xff\xc2\x2c\x24\x5e\xfd\x26\x22\x5f\x69\xf4\x50\x99\xd7\x7e\xb2\x77\x36\xab\x45\x29\x50\x2f\x46\x49\xa5\x68\xb8\x81\xc9\x6d\x3e\x97\x55\x56\x56\x99\x12\xb3\x63\x48\x7b\x6a\xcd\xab\x2a\x24\xa1\x9a\xb6\x51\xb9\xb7\x2d\x89\xfb\xd5\xd6\x7c\x29\x03\x61\x34\x64\x61\x96\x98\xf7\xd4\xa3\x26\xd7\xcf\x7d\x5f\x17\xed\x39\x39\x17\x9e\x5b\xe8\xa1\xf1\x0a\xab\x59\xd4\x52\x27\x4f\x65\xcb\x51\x77\x80\x64\x74\xe1\x72\x6a\xb7\xba\xea\x9b\xae\x6e\xf1\xdd\xe4\x1e\x5c\xa4\x5e\x81\x39\xa1\xde\xb3\xf1\xdc\xe4\xc2\xac\x40\x4d\xef\xcf\x9e\x8d\x2d\x87\x9c\x02\x57\x8e\x0b\x25\xe6\x53\xf9\xf8\x7f\x01\x00\x00\xff\xff\x87\xf5\x90\xa3\x3f\x30\x00\x00")
+
+func webdataFnJsBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataFnJs,
+ "webdata/fn.js",
+ )
+}
+
+func webdataFnJs() (*asset, error) {
+ bytes, err := webdataFnJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/fn.js", size: 12351, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _webdataIconPng = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x00\x2a\x06\xd5\xf9\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\x42\x28\x9b\x78\x00\x00\x05\xdc\x49\x44\x41\x54\x78\xda\xed\x59\xcf\x6b\x17\x39\x14\xff\x24\x93\xf9\xce\xd4\x56\xa4\x88\x15\x6f\x4a\xa1\xa5\xd4\x73\x0b\x05\x11\x6f\xeb\x71\xc1\xed\xa5\x57\xf7\xb8\xfb\x17\xb8\x7b\xd0\xf3\xee\x61\xb7\xff\x44\x57\x3c\x89\x22\x78\xf1\xd2\x53\x11\x85\x9e\x7a\xf0\x26\x75\x29\x54\x51\x4b\x67\xe6\x3b\xc9\x64\x0f\x9a\x21\x93\x6f\x92\xc9\x7c\xeb\xc2\xc2\xe6\x03\xe5\xdb\xc9\xe4\x25\x2f\xef\x57\xde\x7b\x03\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\xfc\xef\x40\x7c\x2f\xa5\x94\x6b\x00\x12\xed\x19\x52\x4a\x34\x4d\xd3\xf9\x35\xc7\x85\x10\x9d\xf9\xe6\xdc\xa6\x69\x26\x68\x6d\xf3\xf4\x31\x00\x1d\x3a\xd7\x3c\x93\x46\x08\xc1\x6f\xdc\xb8\xb1\x37\x48\x00\x65\x59\xfe\x74\x7a\x7a\xfa\xc7\x78\x3c\x86\x94\x52\x17\x88\xf7\x7f\xf5\x4b\x08\x69\x19\x30\x04\xda\x4b\x3b\xe4\xbd\x94\x12\x84\x90\xf6\xd0\xea\x7f\x73\x5e\x5d\xd7\x28\xcb\xf2\xe7\xb5\xb5\xb5\x3f\x7b\x05\x50\x14\xc5\x2f\x9f\x3f\x7f\xbe\xaf\xa4\x78\xf9\xf2\x65\x27\x33\x7d\xbf\xbe\xb1\x21\x34\xd3\x08\xac\x69\x1a\x10\x42\x40\x08\xc1\xbb\x77\xef\x90\x24\x09\x4e\x4e\x4e\x7e\x5d\x5f\x5f\x7f\xe0\x14\x80\x94\xf2\xca\xf1\xf1\xf1\xa1\x32\xa5\xf9\xf9\x79\x5c\xbf\x7e\x1d\x84\x90\x8e\xc4\x4d\xe9\x2b\xad\xb7\x8b\x1a\x9a\x50\xcf\x3a\xad\x49\x73\x26\x3f\x36\xf8\xd0\xf7\xfe\xf0\xe1\x03\xf6\xf7\xf7\x71\x74\x74\x04\x29\x25\xde\xbe\x7d\x7b\xe5\xf6\xed\xdb\x7f\xab\xf7\x4c\x5f\xe8\xe4\xe4\xe4\xbe\x3a\xfc\x57\xff\xc1\xc2\xc2\x42\xd0\xc6\xff\x99\xa0\x66\xf0\xc4\x18\x6b\xdd\x91\x10\x82\x4b\x97\x2e\x3d\x00\xf0\xa3\x7a\x4f\x75\x62\x21\xc4\xf7\x2e\xbf\x75\x04\x49\x2f\x23\x7d\x63\xff\x86\xf0\x7c\xb1\xe7\x2b\x0f\x9d\x33\x52\x63\xe2\x45\x9b\x8f\x85\x9a\xab\xf2\x39\xd7\xe1\x42\xd7\x1c\x2a\x18\xd3\xfd\x7c\x16\x41\x08\xb9\xe8\x13\x80\x73\x71\x97\x60\x4c\x7a\xdd\xd7\xa7\x05\xa5\xb4\xf7\x60\xa1\x42\x31\x03\xa4\xb9\x0e\xf5\x69\xc0\x76\x58\xc5\xdc\xb7\xd0\x8e\x2b\x20\xea\xfb\x87\x0a\x7e\xda\xd8\x44\xcf\x22\xd1\x10\x3f\xec\xbb\xc2\xfa\x2c\x2c\xd4\xf5\xa6\xc9\x37\x9c\x02\x38\x3c\x3c\x74\x26\x15\x43\xb5\x3e\xf4\xbd\x8a\x23\x3e\x6b\x09\x1d\x0f\x51\xda\x44\x0c\x60\x8c\xe1\xe9\xd3\xa7\x60\x8c\x39\x19\xd1\x99\x54\x7f\x7d\x4c\xd8\xc6\x86\x1e\x96\x52\xea\x9c\xaf\xde\xf5\xa4\xf6\x13\xeb\x32\x73\x02\x21\xa4\xe3\xe7\x7a\x46\xa5\x88\xd5\x66\x94\xd2\x4e\x9e\x6e\xfa\xb3\x2d\x45\xd6\xe7\x52\x4a\xdb\x7b\x5a\xed\x63\xbb\xca\xd4\xfe\x42\x88\x8e\xf0\xf4\xb5\x93\x24\x01\xe7\x7c\xb0\x15\x30\x9f\xa4\x5c\xc1\x45\x15\x3b\x59\x96\xa1\xae\x6b\x10\x42\x30\x1a\x8d\xda\xcd\xc6\xe3\x71\x3b\x56\x55\x15\xd2\x34\x85\x94\x12\x9c\x73\x30\xc6\x90\x24\x5f\xea\xab\xaa\xaa\x5a\x81\xa6\x69\x0a\x21\x04\xea\xba\x06\x63\xac\x73\x13\x8c\xc7\xe3\x76\x3f\xc5\x5f\x5d\xd7\xa0\x94\x22\xcb\xb2\x96\x1f\x57\x06\x3a\x38\x08\xea\x1a\xb7\x45\x67\x73\x2e\xa5\x14\x52\x4a\x54\x55\x05\x21\x44\xeb\x3e\x66\xb1\x42\x29\x45\x92\x24\x28\xcb\x12\x55\x55\x75\x34\x5e\x96\x25\x92\x24\x69\xe7\x37\x4d\x83\xba\xae\x21\x84\x40\x92\x24\x68\x9a\x06\x42\x88\xf6\x59\xad\x55\xd7\x75\x2b\x70\xdd\xd2\xcc\xfd\x95\x40\x7b\x83\x60\x48\x81\x13\x72\x53\xa8\xcd\x6d\x99\x99\xcf\x57\x15\x9d\xa2\x55\x73\xd3\x34\x05\x21\x64\xc2\xcc\x6d\x77\xbb\x79\x7d\x4a\x29\xf1\xe4\xc9\x13\x5c\xb8\x70\xa1\x3f\x08\xda\x18\xb1\x05\x1e\x7d\xae\x62\x94\x31\x06\xc6\x18\x84\x10\xed\x58\x9e\xe7\x6d\xcc\x50\x9a\xcd\xb2\x0c\x79\x9e\x5b\x4b\x58\x1d\x79\x9e\xb7\x87\x56\x9a\x57\xd6\x05\x00\x9c\x73\x8c\x46\x23\xa4\x69\xda\x2b\xd8\x2c\xcb\xac\xca\x60\x21\x89\x90\xeb\x6e\x56\x3e\xac\xfc\x5e\xd5\xde\xca\x2d\xca\xb2\x9c\x88\xd4\x9c\xf3\x8e\xa9\x2a\x3a\x42\x08\xca\xb2\xec\x58\x91\x72\x13\x25\xa4\xb2\x2c\x41\x29\xc5\xe9\xe9\x69\x6b\xd2\xe6\x1e\xae\xc4\x6b\x50\x10\xf4\x55\x59\x7d\xa5\xae\xbe\xa1\x8a\x0d\xb6\x2b\xcb\x46\x63\xde\x02\xe6\x7b\x15\x3c\xf5\x5b\xca\xb5\x86\xee\x06\x84\x90\x56\x31\x41\x89\x50\x5f\xe3\x22\x44\xb2\x21\x89\x8b\xbe\x96\xb9\x66\x5f\x23\xc5\x57\x84\xd9\x3a\x46\xb7\x6e\xdd\xc2\xa7\x4f\x9f\xfa\x6f\x01\x97\xb9\xfb\x0e\x1d\x52\xb4\xe8\xb7\x41\x68\xdd\x11\x9a\x0a\x9b\xeb\xea\x3c\xab\xf1\xd1\x68\x64\x8d\x01\xde\x20\x18\x0a\x1f\x9d\xab\x0b\x34\x4d\xc5\x18\x52\x62\x0f\xae\x3c\x5d\xc5\x4c\x68\x19\xec\x2b\x7f\x5d\x9a\x99\x96\xf1\x90\x26\x8b\xcd\xff\x83\x8b\x21\x95\xad\xdd\xbc\x79\xb3\x8d\xd6\x3e\x93\xee\xcb\xb6\x6c\x81\xf1\x2c\xbd\x82\x21\xb4\xba\xcb\xf9\x2c\x94\xda\xb4\xbf\xb8\xb8\x38\x11\x89\x6d\x4d\xcf\x50\x86\x5c\x01\x6e\x68\x05\xa9\x5b\x66\x08\xad\xab\x70\xf2\x16\x43\xa6\xb6\xd4\xdd\xac\x47\x54\xf5\xab\xe7\xff\x43\x7c\xd7\x56\x41\xf6\xb9\x84\x2f\x69\x6a\x9a\x66\x22\x1d\xd6\xbe\x71\x78\x0b\xa2\xde\x44\x48\x3f\xa4\xd9\x12\x2f\x8a\x02\x79\x9e\x4f\xd5\x21\x76\x69\x73\x1a\x14\x45\x81\xb9\xb9\x39\x6b\x94\xcf\xb2\xcc\xcb\x17\x0b\x35\xdf\x6b\xd7\xae\xe1\xdc\xb9\x73\x98\x9d\x9d\xc5\xee\xee\x6e\x5b\x99\xd9\x5a\x5b\x36\x6b\xb1\x7d\x5b\xf8\x56\xed\x76\x5d\x41\x7a\x41\xa6\x2b\xcf\x15\x04\x3b\x02\xe0\x9c\x97\x00\x72\xdb\xa7\xa5\x99\x99\x19\xbc\x7a\xf5\x0a\x57\xaf\x5e\xc5\xf9\xf3\xe7\x3b\x69\xf0\xd0\x62\xaa\xaf\x06\x70\xc5\x88\xbe\xf9\x4d\xd3\x60\x6b\x6b\x0b\xcb\xcb\xcb\x78\xfd\xfa\x35\x1e\x3f\x7e\x3c\xb1\xde\x78\x3c\x2e\x7c\xb7\xc0\x23\x5f\x82\xc2\x39\x3f\x53\xab\x6c\xda\xfb\x7b\x88\x45\x54\x55\x85\x8f\x1f\x3f\x4e\x58\xa8\xd6\x47\x78\x64\xb5\x80\xd5\xd5\x55\xec\xed\xed\xfd\xb6\xb1\xb1\xb1\xa5\x4a\x4e\x15\x79\x93\x24\xc1\xf1\xf1\x31\xd6\xd7\xd7\x41\x08\xc1\x9b\x37\x6f\xda\x46\x84\x2d\x6d\x75\x7d\x3e\x1b\x52\x6b\xf8\x3e\x77\x99\xef\x54\x47\xa9\x69\x1a\xec\xec\xec\xa0\xae\x6b\xa4\x69\x8a\x2c\xcb\x3a\xe5\x33\x21\x04\xcf\x9f\x3f\xff\x7d\x61\x61\x01\x47\x47\x47\x5f\xc6\x4c\x46\xf6\xf7\xf7\xff\x9a\x9f\x9f\xff\x41\xf5\x07\x77\x77\x77\x9d\xbe\xce\x39\xef\x94\xba\xbe\xac\xd0\x3c\x54\xe8\xbc\xbe\x4c\x53\x75\xa8\x54\xa1\x64\x5a\x2f\xe7\x1c\x1b\x1b\x1b\xe0\x9c\xe3\xe0\xe0\xe0\xe1\xe6\xe6\xe6\x66\x47\xf0\x36\x6d\xbc\x78\xf1\xe2\xe1\xca\xca\xca\x9d\xba\xae\x7b\x99\x1d\x32\x36\x0d\x8d\x4f\x20\x21\x63\x4a\x51\x2f\x5f\xbe\x7c\x74\xf7\xee\xdd\x3b\x13\x96\xe7\x32\xc9\xa5\xa5\xa5\xe5\x7b\xf7\xee\x7d\x37\x1a\x8d\x66\xcc\xc5\xf5\x06\xa8\xad\xfb\x62\xeb\x2d\xba\xde\xd9\x7a\x90\xb6\x67\xbd\x4c\x76\xed\x69\xa3\x2b\x8a\xa2\xd8\xde\xde\x7e\xf6\xfe\xfd\xfb\x03\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x00\xc0\x3f\x7d\x90\x35\x14\x7a\x41\x74\xf4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\x28\x1c\x87\x5b\x2a\x06\x00\x00")
+
+func webdataIconPngBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataIconPng,
+ "webdata/icon.png",
+ )
+}
+
+func webdataIconPng() (*asset, error) {
+ bytes, err := webdataIconPngBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/icon.png", size: 1578, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _webdataIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x94\xb1\x8e\xd4\x30\x10\x86\xfb\x7b\x0a\x33\x35\x49\x00\x09\x44\x11\x47\x42\x77\x57\x50\x81\xd0\x21\x41\xe9\xd8\xb3\xc9\x70\x8e\x6d\xec\xc9\xde\xed\x3b\x50\x53\x52\x40\xc3\x73\xf1\x24\x68\x63\xed\x25\x97\xdd\x45\x54\xc9\xfc\xf3\xcf\xe7\x19\xdb\x72\xfd\xe4\xea\xdd\xe5\xcd\xe7\xf7\xd7\xa2\xe7\xc1\x36\x17\xf5\xfe\x23\xac\x72\x9d\x04\x74\xd0\x5c\x08\x21\x44\xdd\xa3\x32\xf9\x77\x0a\x07\x64\x25\x74\xaf\x62\x42\x96\x30\xf2\xa6\x78\x0d\xa2\x5a\x1b\x9c\x1a\x50\xc2\x96\xf0\x2e\xf8\xc8\x20\xb4\x77\x8c\x8e\x25\xdc\x91\xe1\x5e\x1a\xdc\x92\xc6\x62\x0a\x04\x39\x62\x52\xb6\x48\x5a\x59\x94\xcf\x9f\x8a\x41\xdd\xd3\x30\x0e\xb3\x30\x26\x8c\x53\xa4\x5a\x8b\xf2\xd9\x89\xf5\x7a\xe6\x50\xe0\xd7\x91\xb6\x12\x3e\x15\x1f\xdf\x14\x97\x7e\x08\x8a\xa9\xb5\xb8\x58\xfc\xed\xb5\x44\xd3\xe1\xe3\xfa\xa4\x23\x05\x16\x29\x6a\x09\x1b\x57\x7e\x49\xd0\xd4\x55\x16\xcf\xb8\x52\xaf\x5e\xbc\x7c\x75\xce\xc9\xc4\x16\x9b\x0f\x38\x78\x46\x71\xe3\x47\xdd\x07\x65\xea\x2a\xcb\xb3\xcd\x92\xbb\x15\x7d\xc4\x8d\x84\x41\x91\x2b\x75\x4a\x20\x06\x34\xa4\x24\x24\x1d\x11\x1d\x88\x88\x56\x42\xe2\x9d\xc5\xd4\x23\xf2\xe3\xbe\x17\x00\xd2\xde\x95\xc1\x75\x20\x78\x17\x50\x02\x0d\xaa\xc3\x6a\x12\x32\xa2\xf7\x91\xf5\xc8\x62\x6f\x7c\xa0\xd4\xd5\x7c\xb4\x75\xeb\xcd\x6e\x01\x37\xb4\x15\x64\x24\xf8\x80\x8e\x5c\x07\x73\x6a\x4a\x87\xe6\xd2\x3b\x87\x9a\xc9\x75\x65\x59\xd6\x55\x58\xd4\x56\x86\xb6\x27\x50\xda\xfa\x84\xe6\x98\x74\x45\x49\x67\x18\x9a\xff\x02\x05\x75\x4c\x39\x24\xac\x6a\xd1\x42\x33\x6f\x7b\x58\x39\xf7\x10\x6d\x55\x4a\x12\xda\x91\xd9\xbb\xb4\x62\xe5\xdd\x98\x52\x13\x74\x33\x5a\x9b\xcf\x23\x8b\xd0\xfc\xf9\xfe\xb3\xae\x72\xf0\xef\xd2\x5b\xdc\xb5\x5e\x45\xf3\x50\xf8\xed\xf7\xa9\xc2\xf5\x9c\x67\xc6\x3e\xd0\xd6\xb3\x33\xde\xb3\x8a\xa8\x26\xd3\x3e\xd8\xdf\xca\x83\xb8\xf2\x2e\x9a\x4b\xe8\xe6\xc6\x7e\xfc\x3a\x6e\x6c\xd1\x47\x5d\xe5\xeb\x51\x57\xf9\xa1\xf8\x1b\x00\x00\xff\xff\xb9\x03\xa0\x33\x39\x04\x00\x00")
+
+func webdataIndexHtmlBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataIndexHtml,
+ "webdata/index.html",
+ )
+}
+
+func webdataIndexHtml() (*asset, error) {
+ bytes, err := webdataIndexHtmlBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/index.html", size: 1081, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _webdataMainCss = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x55\xed\xae\x9b\x30\x0c\xfd\xcf\x53\x78\xaa\x26\xdd\x2b\x35\x77\xbd\x1f\x93\xa6\xdc\x3f\x7b\x95\x40\x5c\x9a\x35\xc4\x28\x31\xa2\xdd\xc4\xbb\x4f\x21\x94\x42\x0b\xdd\x1a\xa9\x42\xc7\x71\x7c\x7c\x7c\x02\x07\xae\xec\x16\x72\xd2\xe7\x2d\x6c\x6a\xa5\xb7\xb0\x39\xe2\x39\x27\xe5\xe3\x23\xd5\xe8\x8c\x2b\xb7\xb0\x29\x2c\x05\xd4\xf0\x27\x03\x00\x68\x8d\xe6\x83\x84\xd7\xdd\xee\xeb\x67\x0f\x1c\xd0\x94\x07\x9e\x22\x95\xf2\xa5\x71\x12\x76\x9f\x59\x97\x65\xf1\xfc\x21\x77\x4f\x8e\xc5\x5e\x55\xc6\x9e\x25\x04\xe5\x42\xbf\x61\x28\x1d\xd0\xe9\xbc\x61\x26\xb7\x5e\x3c\x57\xc5\xb1\xf4\xd4\x38\x2d\x0a\xb2\xe4\x25\x6c\x3e\x76\x71\xc1\x17\x53\xd5\xe4\x59\x39\xfe\xbc\xdd\x69\x2a\x55\xa2\x84\xc6\xdb\x27\xc6\xd3\x4b\xed\xca\xe7\x54\x77\xa5\x88\x36\xa1\xb6\xea\x2c\x61\x6f\xf1\x94\x4e\x53\xd6\x94\x4e\x18\xc6\x2a\x48\x28\xd0\x31\xfa\x14\xf8\xd5\x04\x36\xfb\xb3\x28\xc8\x31\x3a\x9e\x07\xfb\x76\x83\xf9\x8d\x12\xde\xb0\x4a\xd8\x40\xbb\x3d\x18\xc6\x84\xb4\xe4\xb5\x68\xbd\xaa\x25\xe4\x1e\xd5\x51\x44\x60\x14\x66\x9d\x53\x7c\x12\xda\x78\x2c\xd8\x90\x93\xf1\xe4\xa6\x72\x63\xa2\x55\x39\xda\x21\x7b\xd4\xea\x2d\xae\x94\xce\x78\x62\x11\x0e\x4a\x53\x2b\x7b\x20\xfe\xc4\x6b\x7d\x4a\x7f\x3b\xd8\xbc\xff\x88\x6b\x3b\x06\x1f\xc5\x7a\xfc\x41\xde\x2c\x34\x51\xa7\x1d\xcc\x93\x93\xd5\x53\xf3\x08\xa6\x5a\x82\x6a\x98\x66\x68\x4e\xcc\x54\x4d\x03\x7d\x1b\xfd\x78\x56\xb5\xff\xb8\x68\xff\x40\xe9\x7d\x63\x6d\x28\x3c\xa2\x1b\x2d\x78\xb9\x0a\x23\x70\x35\xe8\xaa\x1b\xd9\x2b\x17\x6a\xe5\x71\xb4\x21\x79\x8d\x5e\xf6\xfd\x07\xb2\x46\x43\x6e\x55\x71\x9c\x06\xc7\xa6\x1c\x39\xbc\xb9\x52\x2f\xdf\x47\xee\xe9\xda\x8d\x3e\xfa\xb7\xb7\x96\xda\xba\x10\x4f\x85\x2d\xee\xf9\x52\x36\xee\x9e\x77\x3c\xdf\xeb\x13\xa3\x2b\xc7\x61\x22\xe9\x8c\x34\x8f\xe9\x19\xff\xef\x5b\x4f\x6d\x4a\x8d\xa3\xbc\xbc\x28\x2c\x9e\x24\xbc\xcd\x35\xbc\xd6\xf6\x98\x3a\xbf\x22\x4b\x72\xdc\x8f\x67\x72\xeb\x06\x64\x98\x46\x97\x65\x2f\xa9\xeb\xb0\x4c\x3c\xf2\xbb\x9f\xff\x8a\x34\x03\xde\x5b\x78\x61\xa8\xfd\x7b\xb2\xcb\xb2\x9f\x15\x6a\xa3\xe0\xa9\x52\x27\xa1\x42\x8d\x05\x0b\xaf\xd8\x90\x84\x8f\x6f\xef\xcf\x43\x8d\x5b\x41\x1f\xdd\xfc\x18\xeb\xb2\x94\x76\x47\x76\x65\xee\x37\xa1\x9e\xf3\xa2\x59\x57\x7c\xb9\xf8\x49\xe8\xb2\xee\x6f\x00\x00\x00\xff\xff\x19\xc9\xf5\x7f\x5c\x06\x00\x00")
+
+func webdataMainCssBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataMainCss,
+ "webdata/main.css",
+ )
+}
+
+func webdataMainCss() (*asset, error) {
+ bytes, err := webdataMainCssBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/main.css", size: 1628, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _webdataSha256Js = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x5b\x77\xdb\x38\x92\x7e\xd7\xaf\xa0\x75\xb6\xd5\x84\x55\x96\x01\x10\xe0\x45\x14\x94\x63\x3b\xce\xa5\x3b\x9d\xce\xe4\xb2\xdb\x33\x1a\xe5\x1c\x90\x04\x49\x8d\x28\x4a\xa6\x28\x3b\x8e\xe5\xfd\xed\x7b\x40\x52\x37\xdb\x99\xf4\x65\x1e\xf6\x85\x02\xc0\x62\xa1\x50\xf5\xe1\x43\x15\xa9\xd3\xe3\x96\x71\x66\xfc\x24\xaf\xe5\x87\xb0\x98\x2c\x4a\x63\x32\x5b\x64\x6a\xa6\xf2\x52\x96\x93\x79\x6e\xcc\x63\xa3\x4c\x95\xf1\xe1\xd5\x99\x11\xcb\xd9\x24\xbb\xd5\x23\xa9\x5c\xa6\x6a\x09\x86\x5c\xb6\x8c\x48\xc5\x93\x5c\x45\xc6\x24\x37\x5e\xbc\x7e\xf7\xc1\x78\xf7\xe9\xdc\x20\x2e\x3e\x61\x86\xcc\xa3\xdd\x10\xc5\x54\xcb\x1b\x37\x2a\xcb\xf4\xaf\x56\x1a\xce\x8b\x42\x2d\x17\xf3\x3c\x9a\xe4\x49\xcb\x78\xf5\xcb\xd9\xc5\xc3\xe9\xe5\xf2\xe9\x09\x3c\x57\xb6\x5a\xc6\xc5\x7c\x71\x5b\x4c\x92\xb4\x34\x28\xc6\xee\x09\xc5\xc4\x35\xce\x8b\x89\xcc\x8d\x8f\xab\x42\x4d\xc1\x20\x9e\xa7\x87\xb1\x67\xbc\x93\xab\xcc\xf8\x69\x9e\xe6\xcb\x72\x9e\x1b\x1d\xe3\x62\x9e\x97\xc5\x24\x58\x95\xf3\x62\xd9\x32\x9e\x4f\x96\x75\x4f\x45\xc6\x2a\x8f\x54\x51\xd9\x77\xfe\xe1\xb9\xf1\x66\x12\xaa\x7c\xa9\x5a\xc6\x07\xa5\x8c\xb4\x2c\x17\xfd\xd3\xd3\x50\x66\x93\x44\xdb\xd7\x4b\x26\x65\xba\x0a\x7a\xe1\x7c\x76\xfa\xaf\xe5\x87\x57\x67\xa7\x46\x3c\x2f\x8c\xd9\xbc\x50\xc6\x24\x8f\xe7\xc5\xac\x5a\x45\xeb\xf8\xb4\xf5\xe3\x6a\xa9\x0c\x3d\x49\x58\xfe\xe8\x9b\xf1\x2a\x0f\xf5\x1d\xf3\x35\xba\xdb\xb4\x8d\x1b\x33\x04\x09\x11\xba\xbb\x96\x85\x91\x09\x0c\x81\x18\x8d\x21\x11\x18\x62\xc8\x61\x0a\x0a\x52\xb8\x82\x5b\x58\xc0\x4c\x1c\x11\x28\xf5\xdd\x42\x5f\x56\xf0\x55\x1c\x11\x3f\x12\xd1\x7a\x7d\x77\xef\xc7\x22\xea\xa9\x3c\x9c\x6b\xaf\xae\xd7\xed\x4f\x1f\x5f\xb8\x6d\x7f\x25\xa2\x5e\xbe\x9a\xbd\x9f\xaf\xf2\x68\xb9\x5e\x13\x7f\x12\x9b\xab\x23\x21\x16\xb2\x58\xaa\xd7\x79\x69\xae\x80\x60\xb4\x5e\x93\xe1\x0a\x95\x69\x31\xbf\x31\x2e\x8b\x62\x5e\x98\xed\xed\x43\xc6\x6c\xb5\x2c\x0d\x69\x4c\xf2\x52\x25\xaa\x30\x86\xc2\x20\x6d\xa4\xf5\x60\x21\x44\xd8\xcb\xe4\xb2\x7c\x9d\x47\xea\xcb\xaf\xb1\xd9\xfe\xf0\xea\xec\xa4\x0d\x18\xa1\x49\x6c\x5e\x89\xed\x72\x03\x90\xe8\xae\x50\xe5\xaa\xc8\x8d\x33\xdd\x83\x10\xdd\xc3\xed\x81\x00\x64\x10\xd7\x3e\x48\x40\x69\xf5\x95\x32\x4a\x59\x5b\x4f\xb3\x5e\xd7\x5d\x6e\x57\x5d\x94\x08\x53\x76\x6d\x3e\x1c\x0e\xbd\xc1\x80\xa1\x2e\xe1\xa0\x04\xb1\x7d\x95\x2d\x95\x71\xb0\x90\x4f\xb9\xfa\xb2\x50\xa1\x0e\xb1\xd2\x23\x1a\x50\x95\xaa\x07\x98\x6b\x23\x3f\x9e\x17\xa6\x1f\xf4\x32\x95\x27\x65\x3a\x10\x89\x8f\x82\xde\x62\xb5\x4c\x4d\x8c\xfc\x60\x24\x87\xc3\x21\x1f\xaf\x05\xa1\xee\x60\x40\xd9\x89\xfc\xc1\xa2\xbe\x14\xb2\x9b\xf9\xc1\x28\x19\x0b\xd9\x61\xd4\x63\x9e\xed\x50\x8f\xfb\xad\x60\x94\x9c\x90\xb1\x90\xa7\xdb\x41\x7b\x8d\xfd\x4c\x6c\xd4\x57\x93\x49\x81\x7d\x39\xc8\x7c\xd9\x15\x0a\xc5\xe2\xcc\x0c\x7a\xcb\x6c\x12\x2a\x53\x82\xec\x2a\x04\x31\x84\xe8\x91\x2b\x50\x20\x46\xf1\x08\x8f\x21\x1e\x11\x7d\xa1\xfa\x62\xe9\x0b\xd3\x17\xae\x2f\xf6\x78\x5c\xbb\x62\xfb\xf0\xc6\x71\x81\x88\xff\x9a\x93\x9a\x30\x06\xf7\xb0\xd8\x8b\xdf\x36\xbc\x9b\x15\xa0\x7b\x38\xb4\x3a\x15\x9c\x50\x50\x82\x52\xf6\x0d\xd3\xb6\x12\xfc\xa9\x38\x5e\xa4\xf3\xa5\xaa\xac\x32\xae\xa5\xde\xec\xa5\x31\x59\x1a\xf9\xbc\x34\x96\xab\xc5\x62\x5e\x94\x2a\x6a\xa3\x3f\xfd\xdc\x54\x9c\x9b\x12\x62\xe4\xe7\xe2\x8b\x19\x22\xbf\x4c\x27\xcb\xde\x52\x95\x9a\x9c\x7e\x56\x0f\x90\x9a\xd4\x38\xad\x50\x7a\xa4\x77\xc1\xec\x70\xef\x54\x8c\x36\x55\xb7\x86\xcc\x0a\x25\xa3\x5b\x63\xa9\xca\x36\xf2\x5b\x1b\xf1\xaf\x87\xe2\x17\x32\xaf\xcc\x51\xa5\xb1\x7b\x32\x2e\x55\x61\x84\x32\xcb\x26\x79\x62\xac\x16\x91\x2c\x95\x46\xa8\x30\x13\xbd\xd9\xd1\xe3\x9d\x2e\x9b\x15\x98\x01\xf2\x03\x21\x7b\xc1\x24\x7f\xa3\x72\x0d\xd0\xde\xb5\xcc\x56\xca\x57\x22\x1d\x0e\x87\x96\x9f\x08\x75\xca\x4e\x2a\x26\x50\x83\xe0\xd4\x45\x77\x35\x14\x6f\x4d\x09\x01\x60\xd0\xeb\x47\xbe\xdc\xdf\x06\x72\xbb\x0d\xe4\x28\x19\x77\x44\x83\x69\xcc\xf0\xfd\x26\x94\x6a\xb8\x55\xf5\xbb\x9f\xd5\xc2\x81\xc0\x7e\xa0\x05\x83\xae\x20\xa8\x1c\x05\x63\x21\x47\xc1\xf8\xb3\x87\x3d\x4e\x29\x73\x6d\x28\xb6\x63\x84\x33\x8f\x73\xdb\xa5\xae\x9f\x8b\x2b\xb3\x84\x1c\xf9\x99\x48\xfd\x99\x38\xc2\xf7\x75\xc8\x6a\x4f\xed\xc2\x25\xeb\x50\x85\x10\x83\x82\x48\x60\x58\x54\x5e\xe0\x7e\x28\xa6\xd5\x7a\x13\xe4\x4b\x11\x6e\xbc\x15\x8b\xb0\xf1\x56\x28\xaa\xfd\x5e\x6d\x53\x25\xb0\xaf\x06\xa1\xaf\xba\x62\x81\xa2\x6e\x3a\x10\xb2\xd3\x31\xb5\x09\x71\x83\x76\x05\xaa\xbb\x40\x90\x23\x88\xba\x22\x45\x7e\xd6\x15\x91\x1f\x88\xcd\xed\x68\x38\x1c\xb6\x38\xf2\x13\x21\x7f\x48\xfd\xaf\x3b\x73\x13\x55\xbe\x92\xcb\x74\xcf\xde\x0d\x09\x46\x90\xc2\x14\xae\xbe\x05\xb1\x06\x33\x1a\x20\x46\xa3\xa4\xc1\xcc\x52\x95\xa5\xc6\xcc\x06\x4a\x15\xb8\x2f\xcc\x18\xf9\xcb\x9b\x49\x19\xa6\xda\x25\xa1\x5c\x2a\xa3\xfd\xea\xf2\xb7\x76\x3f\x3a\xf0\x55\xb3\x89\x9f\x9b\x12\x14\x4c\xd1\xbd\x1f\x14\x4a\x4e\xfd\x5a\xfe\xdc\x66\xdf\x90\xbf\x7c\x5a\xfe\xef\x1f\x2f\x3f\x7c\xe3\x89\x17\xfa\x89\x07\xf2\x67\xef\xdf\x9f\xfd\xfd\xfc\xd3\x8b\x17\x97\xef\xdb\xfd\xb2\xb8\xbd\x4b\x45\xae\x6e\x8c\xb3\xa2\x90\xb7\xe7\xab\x38\x56\x85\x89\xd1\x7d\x28\xf5\x22\xae\xd1\xdd\x81\x3f\xf6\x9e\x3d\xdc\xdb\x46\x70\x6b\x68\x57\x1b\x2a\xbf\x9e\x14\xf3\x5c\x53\x59\x1b\xf9\xf7\x4f\x5a\xf5\xf2\xc0\xaa\x48\xc5\x72\x95\x95\xfd\x83\x89\xea\xd3\xbd\x3e\x14\x03\x65\xbc\xba\xfc\x0d\x8c\x73\x9b\x81\x51\xad\x16\x8c\x79\x61\xec\xaf\x03\xf9\xad\xfb\x2b\x71\xbb\x25\x76\x04\x09\x64\xb0\x30\x73\x54\x1f\x37\xa9\x20\x7e\x3a\x58\xf9\xa9\x46\xbf\x16\xbc\x02\xb5\xdd\x83\x8d\x55\x91\x79\x85\xf6\xf0\xf2\xcb\xd9\xc5\x93\x78\x99\x42\x09\xab\x0a\x2f\xe4\xfb\x78\xd1\xe0\xb8\x99\x94\xe9\x7c\x55\x1a\xf1\xa4\x58\x96\x4f\xe1\xa6\xfc\x73\xb8\x29\xff\x20\x6e\x1e\xc9\xff\x55\xdc\x44\xff\x0f\x71\x33\x5f\x95\x8b\x55\xf9\xe2\x8f\xa3\x67\xfa\x0d\xf4\xac\xc4\x95\x59\x34\x40\x59\x89\x5b\xb3\x4e\x14\x57\x7b\xa8\x59\xa1\xfb\xfb\x6d\x96\x39\x33\xd1\xdd\xae\xf7\xfc\x30\xe7\x6c\xb7\x7d\x79\x2a\x5c\x5f\xf7\x02\x48\xfc\x1d\x31\xcb\x9a\x97\x13\x11\x8e\x82\xe1\x70\x48\xc7\xc3\xe1\xd0\x3d\x36\xad\x6e\xf0\x03\x3b\x3e\x21\x08\xb2\xae\x68\x63\x42\x2d\xc6\x6d\xc7\xf5\x64\x10\x46\x2a\x6e\xf7\xc2\x54\x16\x67\xa5\x99\x0c\x87\x43\xd6\x21\x1c\x75\xff\x8d\x8c\xbe\xbf\x35\xba\x57\xfb\xe9\xd3\x62\xa1\x8a\x67\x59\xaf\x9c\x57\xad\x0b\xb9\x54\x26\xea\x67\xbb\x05\x5c\x3e\x5c\x00\x04\x42\x9e\xba\x90\xe8\xa4\xb9\xb2\x3f\x11\xd8\x4f\x06\x81\x9f\x74\x85\x85\xf4\x40\x2c\x92\x2e\x19\x04\xcf\xc2\x51\xd2\x25\xd5\x5a\xfa\x18\x72\x91\x74\x69\x33\x48\x77\x83\x66\x38\x4a\x0e\x96\x9b\xd4\xcb\xed\x50\xce\xd1\x60\x40\xec\xb5\x19\x6f\x6e\x99\x49\x97\xa0\x83\xdb\xee\x3a\xdf\xbb\x49\xf7\x6e\x42\x2c\xb0\xcf\x86\xb1\x1f\x6b\xaf\xba\xc7\x49\xd7\x3e\x8e\x07\x42\x3e\xd3\x6e\x3c\x3b\xbf\x78\x7e\xf9\xe2\xe5\xab\xd7\x3f\xfd\xfc\xe6\x97\xb7\xbf\xbe\xfb\xdb\xfb\x0f\x1f\x3f\xfd\xf7\xff\xfc\xf6\xf7\x7f\xd4\x2e\x4b\xd2\xc9\xbf\xa6\xd9\x2c\x9f\x2f\xae\x8a\x65\xb9\xba\xbe\xf9\x72\xfb\x75\xe7\xd6\xee\xe9\xd6\xa5\x7a\xf6\x96\x7d\x6c\x5a\x27\x31\xea\xd8\x16\xea\xeb\xe3\xa8\x17\xd8\xec\x9d\x8c\x36\x9e\xde\xf3\xe5\x0b\xed\xcb\x86\x45\xb4\x27\xb3\xca\x93\x87\x38\xc8\xbe\x83\x83\x6a\x71\x51\x57\x7c\x28\x8b\x49\x9e\xf4\xe2\x62\x3e\xbb\x48\x65\x71\x31\x8f\x94\x99\xec\xc2\xbb\x9b\xf4\xe5\xfe\xa4\x7a\xbe\x0c\x82\x47\xdb\x36\x42\x90\xf8\x49\x35\xfc\x69\x92\x97\x6e\x75\x4f\x27\x38\xda\xb0\x4c\x60\x3f\x1b\x44\xfa\xac\x25\x28\x19\x65\x63\x11\x8e\xb2\x03\xdb\xb2\x9d\x6d\xbb\xb4\x75\x6b\xc1\x85\x19\xd6\xf3\x4b\x71\xb7\x87\xba\xfe\x11\x81\xda\x55\xfd\xb6\x68\xc3\x32\x95\x53\xf5\x46\xe5\xfd\x13\x72\xef\x87\xba\x10\xb9\xbb\xf7\xe5\x3e\x4c\x45\xb8\xdf\x5b\xaf\x8f\x88\x7f\x54\x97\x46\xa9\x5c\xfe\x7a\x93\xbf\x2b\xe6\x0b\x55\x94\xb7\x66\xbb\x56\xdb\x46\x9d\x8e\x29\x9b\x70\xe8\xd4\xa3\x6a\xd4\x79\x7e\x30\x9f\x67\x4a\xe6\xed\x23\x21\xca\xdb\x85\x9a\xc7\xc6\xc1\x54\x87\x9c\xfe\x3a\xbf\x96\xd9\x24\x32\xf6\x04\x8c\xfa\x78\xaa\x88\x7c\xbe\x68\x32\x76\x9d\x77\xb6\x97\x55\x60\x0e\x14\x37\x13\x3f\xa9\xb3\xbe\xf7\xa4\xba\xc6\x93\x72\xe7\xc9\xf3\xbd\x58\x3e\x3a\x2d\xaa\xec\xb4\xbf\x6d\x13\xfb\xfc\xf2\xa0\xfb\xe6\xb2\xdd\xff\x37\xbc\xb9\xc9\x73\xb7\x9c\xa9\xf5\x81\xd1\x68\xaa\x18\x73\xa3\x06\xf9\xf7\xcd\xe4\xe1\x37\x8f\x2a\x08\xa0\x89\x7a\x2c\x36\x69\x2a\x44\x9b\x2a\xbb\xaa\x6a\x8f\x84\x88\x7f\xa0\x87\x6e\xa9\x61\x6d\xcc\x63\x4d\xd9\x86\xf6\xe0\xd6\x9e\x49\x6e\x04\xb7\xa5\xfe\x0d\x8b\xaa\x50\x5a\xb6\x75\x02\x1e\xac\xd7\x23\x3c\xae\x11\x83\xfd\x2b\x11\x56\xb9\xb7\xc6\x6d\x24\xb0\x1f\x0d\x62\x3f\xea\x0a\x8a\xee\xa6\xbb\x52\x5c\xf6\x96\xab\x60\x59\x16\x66\x04\x14\x01\xb1\x2b\x48\x4c\x96\x6f\xe5\x5b\x73\x8a\xbe\x6b\x4f\x38\xcf\x4b\x39\xc9\x97\xc6\xa4\x09\xa1\x26\x03\x19\x96\xaa\xd0\x06\xb5\x52\x51\x65\xa1\x04\x75\xaf\x9a\x9c\x56\xe7\xc1\x74\xaf\xdc\x55\x87\xe5\xae\x1a\xaf\xc5\x74\x30\xa8\x36\x52\x5a\x6f\xa4\xfb\x3a\xf4\x77\x55\x86\xdc\x0f\xa0\x4e\x9a\xfb\xec\x38\xee\x86\xf7\x87\x67\xf2\xc7\xcb\xdf\x3e\x1e\x78\x3e\x84\x60\x43\xd7\xd5\x6b\x0d\x81\x9b\x17\x1b\x33\x58\x40\xb1\x73\x58\xf5\x36\x43\x3b\x2c\xaa\x1c\xa6\xa1\x5b\x21\x48\x08\x21\x2b\x1a\x2f\x84\x05\x4d\x42\xbe\x29\xa5\x95\x66\x80\x9a\xe2\xc3\x8a\x03\x35\xed\x9c\x95\xa6\x42\x90\x8b\xd1\x18\x08\x75\x87\xf1\xb3\xbc\x5e\x5c\x8c\xfa\x14\x33\x3d\x60\x36\x23\xc4\xa3\x6b\xcd\xe6\x36\x82\xcd\x08\x75\xd7\xb1\x66\x4e\xd4\xe7\x9c\x7a\xf6\x30\x5e\xaf\xb9\x63\x31\x36\x10\x5b\x3d\x94\xb2\xea\x29\x42\xa1\x12\xd7\x0a\x3a\xb6\x05\xdb\x67\xfb\xa6\xb6\x0b\x62\x61\x73\x6e\xd9\x5d\xd3\x8c\x3b\x04\x53\x4b\x1f\x1f\x78\xfd\xc0\xce\xfa\xce\x66\x7a\xca\x70\xad\xda\xdd\xaa\x26\x74\xab\xfb\xd1\x44\x08\x52\x81\xfd\x74\x90\x6f\xfc\x51\x25\x93\x77\x0b\x31\xed\xb6\xea\x60\xcf\xc4\xe2\x41\xb0\x67\x87\xc1\x9e\x8d\xd7\x22\x1f\xa5\xe3\x2a\xde\x45\x77\x51\xc7\xdb\x9f\x76\x05\xd9\x56\x78\xdb\xdd\xab\x43\x51\x97\x9d\xd5\xb6\xdb\x8b\x0c\x85\x5c\x1c\x8c\x77\x3a\x47\x78\x4f\xf4\xa8\x1e\x22\x4f\x06\xf0\xee\x51\xf4\x6a\x1e\xcd\x3b\x1d\x33\x15\x71\x73\x7c\xa6\xfa\x78\xad\xce\x5e\xe4\xeb\x15\xfe\x81\x05\xc6\x8f\x57\x47\xbf\x01\x69\xf7\x78\xda\x8d\xee\xbf\x93\xc6\xee\x73\x09\xde\x91\x88\x46\xb4\xc6\xed\x89\x4e\xc3\x65\x6f\xa9\x64\x11\xa6\xe6\xe9\xe7\x91\x3c\xf9\x7a\x76\xf2\x0f\x7c\xe2\x89\xee\x3f\x4f\xc7\xdd\xff\x3a\x45\x4f\x73\xef\x76\xe3\x56\xdc\x22\x97\xea\xc4\x66\x46\xc3\xdf\xba\x98\x93\xbd\xc9\xe6\x7d\x9b\x68\xa3\xaa\xc2\x2f\xd4\x22\x93\xa1\x32\x4f\xff\x29\x4e\x13\x68\xb5\xeb\x17\x74\x27\xe4\x48\x88\x69\xa7\x33\x1d\x6c\xa8\xee\xe9\x09\x7f\x14\x3f\x1a\xf1\x7c\x95\x47\x4f\x4e\xf8\x90\xcb\x66\x7b\x5c\x36\x15\xd8\xdf\x69\xd7\x1e\x65\xe8\xee\x4a\x6c\x99\x6c\x0a\x0c\x6d\xe8\xa6\x0a\xf9\xd5\x41\xc8\xa3\xff\x44\xd2\xb3\x71\xc6\xd5\x48\x8d\x11\xa4\x6b\x11\x0d\x06\xc4\x3d\xb1\x8f\xd5\x5e\xf1\xbe\x99\xf7\x84\x34\x60\x5b\x88\xb8\x3b\x6b\xf8\xf8\x21\x76\xa2\x43\xec\x44\xe3\xb5\x30\x35\x5b\x12\xfb\xc4\x3d\x56\x9b\x24\x4f\x13\xe3\x06\x4a\x3a\x9d\xbb\xff\x26\x96\x1e\xd3\xe3\x13\x25\xce\x0e\x4d\xd1\xe6\xb5\xef\x23\xd7\x4f\xf7\x5c\x9f\x0b\xec\xe7\x3b\xd7\xe7\x5d\xd1\xd2\x0e\x95\xfb\x5b\x28\xd7\xd4\x90\x77\xa7\xd0\xb0\x3d\xec\xb1\x7d\xa7\xb3\x5d\x23\xd4\x6c\x1f\x1d\xb2\xbd\xff\xad\xe5\x6c\xe6\x7c\xb4\xaa\x3f\x52\x88\x65\xff\xc9\x42\xec\xf7\xf8\x2e\x6f\x7c\x97\x3e\x4c\x27\x25\xda\x3b\x96\x65\x4f\x1f\xe6\x6f\x6a\x9f\x46\x1a\x29\x4a\x44\x5d\x7d\x5e\xa9\x07\x0e\x9c\x1e\x3a\x70\x3a\x5e\x8b\x74\x14\x8d\x1b\x1f\xaa\xef\xfa\x70\x37\xcf\x9e\x1f\x7f\xf7\x2b\x07\x7d\xbe\x7e\xb7\x74\x7c\x9c\x75\x17\x75\xa6\xd6\xdc\xd0\x0e\x91\xeb\x70\x30\xb0\xe8\xc9\x5e\x3a\xf7\xd3\xa6\xb6\xda\x88\x75\xe4\xe7\xff\x0d\x3b\x7b\x6a\x7e\x7e\x42\x22\xec\x44\x9f\xe5\xbe\xd0\x1b\x9d\x86\x35\x02\x7a\x5e\x8a\x3e\xeb\x1f\x62\xd5\xbf\x94\xa2\x9d\xec\x2f\x0f\x64\xed\x46\x96\x34\xb2\x7c\x4f\xf6\xed\x03\x59\xa7\x91\x75\xd1\xe7\x2a\xc2\x3b\xc9\x5f\x1f\x48\x92\x8d\xa8\x57\x8b\x12\xbc\x93\x7d\xb7\x5f\x8f\x98\x61\x47\x1f\xda\x1c\x75\x4d\xd9\xb4\x9a\x48\x9a\x66\xf5\xa0\xad\xef\x6c\x1a\x51\xdd\x68\x04\xab\xea\x30\xaa\x3b\x3b\xf5\x7f\xab\x3d\x06\x59\x3d\x45\xf0\xd4\x14\x5d\x33\xda\xb6\xb2\xdf\x3d\x6d\xd7\xcc\x36\x0d\x5d\x99\xb5\x1e\x58\x12\x3c\xb4\xe4\xfd\xc6\x12\x08\x9a\xcf\x2d\xbf\xd7\x96\xae\x19\xfc\x59\xab\xaa\x46\xf2\xd8\x4f\xc9\x43\xeb\xbe\xec\x8a\xb2\xd1\x18\xa2\xef\x7d\x66\xda\x14\x1b\x62\x64\x51\xcb\xb5\x1c\x82\x2d\x0a\x1e\x61\x84\x63\xdb\xb6\xc0\x25\xd4\xc1\xd4\xf3\x3c\x60\x84\x31\x8f\x50\xdb\x73\x80\x51\x0f\x3b\x0e\x77\xb9\x03\xc4\xe1\xd8\xc6\x16\xa6\x1c\x88\xed\x31\xec\xd8\xae\xe5\x81\x45\x31\xc3\x0e\x67\xd4\x1d\x43\x24\x46\xc4\x71\x3c\x6c\x59\x0e\xb6\xc0\x22\x8c\x11\x8b\x51\xc7\x01\x82\x89\xe5\x61\x46\x19\x05\xea\x38\x16\x73\xb1\x63\x53\x20\x16\xf7\x5c\xcf\x22\xc4\x03\x6a\x63\xec\x52\xea\x51\x06\x9c\xba\x8e\xc5\x6c\x8b\x03\xe1\x8c\x30\xee\x51\xca\xc7\xb0\x2d\x52\x36\x9f\x49\xfa\xfb\x5c\xba\xf9\x32\xd2\x97\x22\xf2\x1f\xde\xb0\x5c\xa6\x6f\x8c\x34\x93\xcd\xa0\xbe\xb6\xf6\x3b\xdf\xb8\x8e\x1f\x69\xe2\x84\x3e\xd2\xf4\x07\x14\x3d\xc9\x57\x9f\xf2\x69\x3e\xbf\x39\xf8\xe4\xa2\x09\xfb\xf1\xa7\x99\xb7\xf3\x7d\x99\xe5\xc1\x07\x99\xc7\x55\xe6\xd9\xc1\x2b\x1f\x08\xea\x77\x3d\x4d\xd6\x35\x83\x02\x16\x70\x03\x25\x7c\x81\x15\x7c\x85\x33\x38\x87\x0b\x78\x0e\x97\xf0\x02\xae\x35\x94\x5e\x3e\xfc\x8e\x16\x1d\x7e\x52\x8c\x50\x21\x6c\x06\x37\x82\xc0\x0b\xf1\x76\x35\x0b\x54\x01\xa5\x78\x07\x5f\xc4\xdf\x60\x25\xde\xc3\x57\xf1\x16\xce\xc4\xaf\x70\x2e\xde\xc0\x85\xf8\x05\x2e\xc5\xcf\xf0\x5c\xfc\x04\x2f\xc5\xab\xbf\xf6\x3d\x2d\x12\x52\x9f\x52\x99\x90\x23\x32\xf6\x03\x21\x47\x74\xec\x27\x42\x8e\xac\xb1\x1f\x0b\x39\x62\x63\x3f\x17\x72\xc4\xc7\x3a\xf3\x1b\xd9\x63\x5f\x09\x39\x72\xc6\xd5\x99\xb5\x10\x2d\xec\x2f\x06\x85\xbf\xd0\x27\x15\xb1\x87\x8b\x67\x3a\x15\x3e\xbe\x81\x54\x84\xbb\x54\xf8\x19\xee\x87\xa3\xd9\x18\x66\xfb\x83\x5d\x52\x0f\x77\xc9\x18\xae\x47\x8b\x71\x75\x2c\xbe\x30\x53\x98\x21\xd4\xaf\x06\xbe\x98\x67\xe6\xf5\x68\x71\x42\xc7\x48\x4b\x9c\x38\x63\xf8\x5a\x0d\x10\xde\x8c\x10\x5b\x67\x5c\x62\x65\x2a\xb8\x30\x63\x04\xcf\xcd\x2a\x22\x08\x5e\x8e\x16\xb5\x56\x04\x33\x51\x9a\xe7\x66\x84\xe0\xd2\xac\x49\x07\x81\x12\x53\x98\x8a\x1c\x72\x11\x43\x2c\x4a\x33\x81\x14\x41\x22\x02\x08\x44\x06\x99\x88\x20\x12\x65\x65\x88\xaf\x5d\x23\x4a\x33\x02\xdd\xd0\x5d\xa2\xbb\x19\xe8\x86\xee\x52\xdd\x0d\x40\x37\x74\xd7\x1a\x57\xda\x74\x43\x77\x99\xee\xc6\xa0\x1b\xba\xcb\x75\x37\x07\xdd\xd0\x5d\x5b\x77\xa7\xa0\x1b\xba\xeb\xe8\xae\x02\xdd\xd8\x43\x9f\xc6\xda\x2b\xff\x95\x18\x11\x42\x6c\x8b\x53\x86\x5d\x20\xae\xe7\x31\xe6\x30\x46\xc0\xc2\xcc\xb3\xa8\xc5\x1c\x02\x96\x47\x09\xc6\x1e\x77\x2c\xf0\x6c\xe2\xb9\x0e\xd1\x35\x1a\xc7\xae\xe7\x60\xcf\xb3\x80\x32\x6e\xd9\x16\x77\x98\x0b\xd4\x75\xb0\x63\x5b\x94\x12\xb0\x6c\xca\x2c\x97\x60\x17\x83\x45\x30\xf7\x5c\x86\x09\xd8\xd8\xa1\x94\x53\xc7\x05\xc2\xa8\xed\xba\x5a\x1b\x10\x8f\x72\xec\xb8\x96\xeb\x02\x25\x36\xc5\x8e\x4b\xb1\x0d\x2d\x6a\x13\xe6\xba\x2e\xd1\xec\x44\x99\x4b\x29\xe5\x5a\x97\x6b\x71\x4d\x4f\x98\x00\xc3\x94\x52\xca\x1c\x87\x01\xb5\x99\xc5\x1c\xec\xb8\x60\xe3\x9a\xb0\x5c\x70\x1c\x4c\x39\xf7\x5c\x5d\x4f\x32\x8f\x70\x4c\x28\x05\xc2\x39\xc7\x2e\xb1\x3d\x0a\xc4\xf3\x6c\x6c\x33\xcf\xb5\x81\x72\xce\x28\xc5\xae\x4b\x81\xba\x94\xb8\x16\xb3\x98\x07\xd4\xe3\xd4\xf3\x6c\x17\xbb\x60\x51\x82\x2d\x62\xd9\xda\x1b\x96\x65\x73\x87\xb8\x1e\x01\x8b\xbb\x4c\x33\x20\x21\x40\x88\xe5\x51\x5b\x7b\xc3\xb2\x5c\xca\x88\xeb\x71\xb0\x6d\xdb\xc2\x0e\xc5\x1c\x1c\xc7\xd2\xaa\xaa\x82\xda\x63\x0e\x77\x2c\x47\x33\xaa\x67\x13\xcd\xa3\x44\x53\x34\x27\xae\xe5\x60\x0c\xc4\x73\x6d\xdb\x26\x98\x13\xa0\x44\x2f\xc1\xb6\x38\xd6\x2e\xb6\x3d\x6e\x63\xcb\x01\xea\x58\x98\xb9\xdc\xa3\x44\xdb\xaa\x29\x9e\x11\x02\x16\xe5\x9e\x63\x61\x17\x63\xb0\x2c\xc6\x1d\x9b\x39\xda\x56\x4e\x6c\x6c\x73\x97\x38\x60\xd9\x18\x5b\x9c\xba\x98\x01\xc3\x1e\xe3\x0e\xf1\xb0\x07\x54\x1f\x06\x96\xc5\x18\x30\x0b\xd3\x8a\xee\x81\x63\xdb\x63\xae\x4d\x6c\xb0\xb9\x87\x6d\xcc\xb9\x0d\xae\x6b\x79\x9e\xe3\x3a\x0e\x78\xdc\x25\x96\xc7\x1d\x02\xc4\xa2\x54\x47\x45\xd7\xf2\x5c\xdb\x4e\xb1\xc6\x85\xc3\x1c\xd7\xb1\x1c\xc7\x03\xe2\x71\xce\x6d\x1d\x23\xa0\xda\x4a\xcc\x5c\xc2\x81\x56\xd3\x60\xc6\x29\x50\xcb\x26\x2e\xa7\x8c\x32\xa0\x8c\xba\xcc\xb2\x99\xc3\xa0\x45\x1d\x6e\x3b\x16\x23\xae\x53\x1f\x58\x16\x61\x8e\x07\x96\x45\x3d\x8b\x72\xea\xb9\x63\xbf\xbd\xa1\x4e\xcd\x6f\xcd\x5b\xbf\xfa\x5f\x35\x9d\x4e\xfd\xdb\x93\xb3\xe8\x59\xdd\xdc\xfd\x37\x65\x9b\x32\xdd\xdc\xa3\x7e\x7b\x95\x37\x7f\xc4\xd9\x7b\x75\xa8\xbe\x68\x9a\x5e\x3e\x33\x9f\xbc\x3b\x9b\x47\xab\x4c\x75\x3a\xf5\x6f\xaf\x11\xee\x74\xcc\xc3\x01\x71\x83\x60\xd7\xec\xbf\xee\x55\xff\xa4\x11\x37\xf7\xc8\xd4\xe9\x3e\xf2\x5b\xff\x17\x00\x00\xff\xff\x8a\x1d\x7d\x6b\xa4\x24\x00\x00")
+
+func webdataSha256JsBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataSha256Js,
+ "webdata/sha256.js",
+ )
+}
+
+func webdataSha256Js() (*asset, error) {
+ bytes, err := webdataSha256JsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/sha256.js", size: 9380, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _webdataTexPng = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x00\x26\x0f\xd9\xf0\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\x00\x00\x0e\xed\x49\x44\x41\x54\x78\xda\x75\x9b\xe7\x72\x1b\x4b\x0e\x85\x31\xdd\xd3\x13\x49\x51\xf2\x7d\xff\x47\x5c\x8b\x69\x72\xe8\xfb\x63\x01\xd7\x67\xac\xd6\x55\x2a\xd2\x13\x3a\xa0\x81\x83\x83\xc0\x42\x44\x3e\xe5\xbf\xff\xee\x22\xf2\x25\x22\x2f\x11\x29\x44\xe4\x43\x44\x46\x11\xa9\x45\x64\x12\x91\x56\x44\xa2\x88\x3c\x45\xa4\x14\x91\x55\x44\x92\xde\x2f\x74\x8c\xa7\x8e\x21\x22\xf2\x5b\x44\x6e\x22\xf2\xd6\xf7\x2e\x22\xb2\x88\xc8\xa6\x63\x05\x9d\xf3\x43\xaf\x1d\x22\xd2\x8b\xc8\xa0\x9f\x9b\xce\xc3\xb1\x0b\x11\x69\x74\x8d\xb5\x5e\x5f\x74\x9e\x5d\xc7\x4c\x3a\xd6\x53\x44\x3a\x11\xa9\x44\xe4\xd4\x35\x1c\xf8\xbc\x8b\xc8\x67\x21\x22\xff\xe8\x66\xa2\x2e\xf6\xa2\x0f\xd8\xdf\xa9\x93\xd5\xd8\x98\x88\xc8\x2f\xfd\x3c\xf4\xfd\x56\xbf\x8b\x2e\x30\xeb\xe6\x6c\x61\xab\x2e\x5e\x9c\x40\xef\xba\xe8\x02\xf3\x65\x9d\xaf\xd2\xe7\xa3\x7e\x9e\x2a\x98\xac\xe3\x66\xdd\xe4\x43\x85\x60\x07\x79\xc5\xbb\x6f\x9d\x4b\xdc\x7c\x8d\x88\xcc\x85\x4e\x7c\xd3\x81\x1b\x1d\xb8\xd0\xc1\x0b\x11\x99\xf5\x7a\xc4\x06\xc5\xfd\xdf\x16\xde\x38\x21\x5c\xf5\xfb\xa6\xf7\x3b\x3c\x2f\x38\x11\xd1\xb9\x46\x08\xe6\x70\xc2\x7a\xe9\x18\xbf\x70\xf2\xb5\x6a\x4c\x83\x75\xed\x2a\xec\x52\xd7\xde\xeb\xb8\x9d\x8e\x1b\x39\x77\xd0\x53\x8a\xfa\xe2\x1d\x2a\x57\xe8\xf5\x80\x17\x16\x6c\xee\x50\x21\xd9\xa4\xa6\x7a\x0f\x8c\x11\xf5\x1d\x33\x15\x53\xbd\x51\x37\x33\x61\xe1\xd9\x09\x68\xd0\xef\xad\x6e\xe4\xaa\xe6\xfa\x82\x60\xb8\xf9\xbb\xce\x57\xeb\x38\xa7\x0a\x21\xc2\xcc\x0f\x98\x5b\x14\x91\xdf\x51\x17\xbb\xaa\xa4\x0a\x48\x48\x74\xf3\x41\x17\x57\xea\x46\xde\xaa\x5e\xb6\xb9\x52\x27\xb5\x67\x4d\xf5\x6c\xc3\xb3\x7e\x8f\x3a\x8e\x61\x40\xd2\x3f\x5b\x7c\xa3\x8b\x36\xed\xab\x74\x83\x2d\x4e\xae\xd0\x8d\x47\x7d\xbe\xc4\x5a\x7b\x9d\x6f\xd2\xcf\x46\xff\x82\x0a\xcd\x84\x61\x9a\x91\x04\x17\x2e\x6a\xdb\x3d\x06\x7c\xe9\xc9\x6e\xba\x81\x13\xf6\x65\x42\xb2\x05\x3c\x74\xa1\xa3\x5e\xcb\xba\x88\x0a\x9b\x34\x01\x35\xba\x80\x08\xa1\x98\xe9\x94\x18\xdb\xd6\x76\xa8\xd0\x57\x08\xbb\xd0\xb1\x0c\x28\x03\x80\xb2\xd2\xbf\xd5\x1d\x5e\x80\x69\xd9\xbd\x22\x02\x3c\x3a\x7d\x38\xea\xe6\x3f\x75\xa0\x11\xf6\x66\x92\x0b\xb8\xb6\xeb\xe6\xef\x2a\xc0\x87\x6e\x28\xc1\x56\x03\xd4\x3d\xea\xb8\x19\xf3\x65\x00\xe1\x5d\xc7\x34\x01\x6c\x7a\x40\x83\x2e\xdc\xbc\x52\xa9\xd7\x78\x08\x3d\x4e\xb8\xc6\xf5\xc6\x61\x4f\x65\x42\x8c\x58\x68\xd6\x81\x33\x54\x67\x56\xc1\xd4\x58\xd0\xa4\x2f\xdb\x26\x16\x7d\xde\x54\xbf\xd1\x4d\xd4\x50\xbf\x46\x17\x6f\x9a\xf0\x8d\x93\x7c\xe8\x1c\x87\x9d\x8a\x6a\x9b\x01\x6b\x82\x49\x34\xba\xe9\x4e\x3f\x03\xdc\x9c\xcd\x61\x58\xf2\xad\x02\xa9\x60\xb6\x33\xc6\x7a\x89\xc8\x1e\xa0\x3e\x41\x25\x7f\xc0\xdd\x25\x98\xc4\x81\xd3\xb3\xeb\xab\x9e\xce\x7f\x30\xce\xa4\xae\xd5\x5c\xce\x0d\xa7\xdd\xe9\xc4\xbf\x1c\xfa\x8b\x6e\x68\xd7\xcf\x03\x18\x63\xdc\x21\x38\x20\xeb\x01\x9a\x01\x3c\xa2\x71\x60\x6e\xa6\xfa\x82\x8b\xb7\xef\x75\x04\x8a\x07\xd8\x7a\x05\x9b\x35\xa0\x1b\xb1\xf1\x84\x13\x78\x81\x4b\xd8\xf5\x0a\xbe\x3c\x42\xea\x01\x60\x97\x01\x66\xe2\xfe\x7f\x40\xd5\x2b\x77\x10\x27\xd6\x69\x26\x92\xc0\x63\x6c\xbd\x37\x60\x92\x3d\xb7\xa9\xd0\xcc\x63\x55\x11\xf6\x55\x80\x79\x0d\xba\x20\x5b\xb0\xbd\x6c\x98\x30\xeb\x7b\x11\x13\xbc\xf5\x5d\x53\xed\x05\x4c\x72\xc4\x89\x1b\x43\x9b\x70\xca\x04\x28\xdb\xcc\xa4\x9b\x28\x60\xd3\x77\xa0\x7d\x01\xe0\xe4\x49\x07\xc5\x24\x73\x91\x46\x9a\x2e\xd0\x70\xf3\x68\x6b\xd4\xc1\xff\xd1\x9b\xb6\xc1\x5e\xaf\x9b\xcf\x6c\x71\x8a\x09\xee\x2c\xb9\x01\x47\x80\x8f\x81\x57\x80\x86\x99\x80\x32\x3e\xa9\xc2\x11\xdf\x5b\xb0\xd1\x0a\xe6\xb6\x62\xa3\x01\xa0\x9d\x20\x98\x17\x04\xb9\xab\x20\x7e\xeb\xfb\x46\xb8\x0e\x11\x99\x23\x7c\x7a\x09\x9a\x59\x82\xca\xda\xa2\x9e\x40\x6a\xaa\x5e\xa5\x40\x36\x69\x1c\x30\xea\x84\x6f\x9c\x40\x09\xb7\x45\x5a\x3b\x80\x65\x8a\xbe\x6b\x9a\x76\x60\xb1\x76\x30\xb6\xf9\x13\x6e\x74\x83\xd0\x4b\x70\x90\xa8\xcf\x05\xfd\xbc\x80\x94\xd9\x1c\x7d\x00\xe5\x35\x00\xeb\x75\xb3\x27\x98\x9d\xa8\x5b\x0c\xba\x31\x9e\xf4\xa4\x83\x57\x88\x25\x16\xb8\x57\x5b\x28\x37\x62\xe0\x77\xd1\x77\x66\x9c\x76\xeb\xdc\x66\xd6\x67\xae\xc0\xa4\x84\x53\x4c\xba\xde\x08\x0f\x62\x26\xb7\x02\x9b\x04\x87\x1d\xcc\x6c\x2d\x18\x1a\xc1\x9e\xcc\x95\xb4\xb0\xd1\xda\xf9\xf4\xe0\x82\x1e\x0b\x5c\x9e\xba\xf1\x41\xb5\xe7\x70\xbc\x3e\xc3\x76\x2b\x75\x55\x37\x98\x49\x01\xef\x62\x66\x77\x42\x90\xdc\x68\xed\x22\x3c\x7b\x6f\x03\xd0\x06\x77\x7f\x03\x50\xf7\x46\x84\x6a\xd8\x70\x01\x04\x2e\x15\x74\x2e\x70\x4f\x54\xb7\x59\x37\x59\xc1\x9e\x7b\xbc\x7f\x00\x1f\x02\xe8\xee\x0b\x76\xd9\xc2\x23\x9c\xce\x25\x9b\xea\x2e\x18\xaf\x82\xb6\x74\x10\x6a\x00\x6f\x30\x97\x67\xd4\xbe\xc0\xe6\x03\x40\x32\x88\xc8\x14\x41\x64\x22\x1e\x28\xb1\x11\x23\x44\x2d\xc8\x8e\xc0\xed\xb4\xca\x03\x1a\x6c\xe0\xe1\xdc\xcd\x37\x26\x4d\xc0\x86\x8c\xf1\x36\xb8\xd8\x13\xff\x3f\x61\x7a\x15\x02\x2b\x33\x29\xf3\x50\x2b\x22\xc1\x1e\xe6\x69\xf4\xda\x4e\xfd\xd4\xe7\x5e\x22\x72\x58\xa4\xc4\x10\x78\xd0\x17\x8d\x1a\x5b\x58\x6b\x9c\xbc\x03\xb0\x95\x38\x79\xaa\xdb\x0a\xd5\x4d\xf0\xbf\x46\xab\x3b\xb8\x34\xd2\xe1\x12\x66\xb2\xea\x67\x0b\x97\x59\xaa\x19\xf4\xb0\x6f\x86\xb9\xa5\x0a\xd7\x08\x14\x4d\x38\xc1\x3b\x18\x16\x7d\x44\xd0\x5a\x01\x50\xec\x50\xeb\x13\x12\xbc\x42\x48\x25\xc0\xcc\x10\xdd\x62\x85\x15\x82\x12\xa8\x66\x0b\xac\xf8\x70\x82\x37\xe0\xdb\x41\x86\x12\x68\x6e\x02\xf5\xdd\xb0\xc1\xe8\x98\x2a\xd9\x6a\x09\x52\x26\x78\xdf\xf0\xee\x8f\xe4\x09\x28\x25\x5c\x8d\x00\x41\x1b\xa8\x62\x0f\x97\x46\x54\xed\xf4\xfe\x07\x36\xf5\x40\x14\xf7\x5b\xc7\xe9\x60\xe3\x01\x26\x95\x71\x3a\xd1\xd1\x57\x6a\x67\xab\x63\x12\xa0\x4d\xad\x23\x38\xcd\xe0\x58\xe4\x0a\x93\x9f\x44\xa4\x34\x57\x24\x18\x88\xe4\xe3\xff\x65\x80\xcc\x35\x7d\xba\x28\x4b\x5c\x2e\xa1\x70\xd7\x5e\x50\xd9\x03\x99\x1a\xa6\xbd\x98\xde\xb2\x77\x5a\xbd\xfe\x89\x50\x78\x81\x16\xda\x78\x3b\xbc\xc4\x8a\x54\x98\x99\xcd\x1b\xe1\xb2\x88\x48\x8c\x60\x59\x93\xfe\xd5\x88\xa3\x4d\xc2\x4f\x00\xda\x8c\x81\x0b\xe7\x8f\x0b\x4c\x72\xd7\xe7\xde\xce\xc6\x0b\x10\xa0\xb7\x4b\x9e\x10\x7b\x36\x3c\x9b\x10\x01\x8e\x88\x2c\x67\x08\x61\xd6\x79\xc9\x12\x07\xf0\x89\x5d\x35\x73\x82\x00\xc6\xa8\x76\x5d\x20\xa4\x9d\xa1\x22\x3d\x36\x12\x60\x57\xe1\x87\xcd\xbc\x90\x33\xb0\x88\x50\xc0\xe3\x09\x72\x01\x00\xd5\x23\x91\x31\x63\xde\x1d\x1b\x2a\xc1\xfa\x22\x48\x54\x82\x56\x9d\x08\xd5\x4b\x68\x60\x03\x93\x39\x61\xb2\xab\x88\xf4\x11\x2e\xa5\x41\x52\x81\x8b\xad\x61\xab\xa3\x8b\xed\x4d\x4b\x2a\x00\xce\x84\x34\x75\xc4\x73\xb3\x4b\x4e\xee\x48\xc1\x59\xb4\x79\x45\x7e\xb1\x82\x29\xbd\x21\x04\x03\xb8\x87\x6a\xcd\x0d\xb8\x50\x43\x0b\x99\x29\x9a\xa1\xd9\x34\x83\xb7\x05\x43\x5f\x48\x3e\x04\xd8\xdd\xdb\x05\x40\x09\x83\x32\x17\xb7\x21\x26\xb8\x81\xd7\x07\xdc\xcb\x2e\xc1\x61\xdf\x17\x2c\x28\x42\x5b\xc2\x0f\xae\x30\x80\xe4\x78\x52\xb4\xea\x7a\x6b\xcc\xc7\x58\x63\x80\x86\x1a\x6f\x90\x08\xc6\x74\x47\x14\xf8\x01\x5b\x1f\xe1\x0d\x76\xa8\xd0\x03\xd7\x83\x7b\x66\x05\x2b\xec\x90\x59\x12\x00\xac\x05\x58\x44\xfe\x1a\x3e\x9f\xd9\xa6\x03\x99\xe4\x4f\x68\xd5\x06\x8e\x62\x73\x46\xb0\x42\xcb\x78\x59\x2e\xf3\x89\x9a\x41\x25\x22\x39\x42\xba\x09\xd2\x7e\x02\xa4\x2e\x28\x9c\x04\x70\x80\xfa\x07\x6d\x60\xf8\x1c\x91\x27\x68\x70\xd2\x11\x29\xb1\x1a\xb1\xbb\xc0\x8e\x5b\x44\xa6\x15\xee\x5d\x91\x9e\x8f\x00\xe3\x0c\xe1\x0d\x3f\x30\xdb\x0c\x3c\x58\xa0\x65\x4b\x00\xc1\x99\x61\xb3\x01\x19\xa0\x19\x92\xa5\xdb\x9a\x20\x80\x37\x48\x54\xe9\xdc\x29\xdd\xac\x05\x2b\x9f\x18\x83\x2a\x5c\xe9\xf3\x86\x35\x3d\x38\xc0\x0a\x77\xbb\x81\x27\x30\x65\xd7\x20\x0f\xb0\xb8\x02\x8a\xcd\x37\x83\x7d\x1e\x41\x27\xb4\x45\xd9\x64\x17\x9d\x9c\x2a\xb8\x62\xf3\x0f\x80\xd2\x08\x36\x97\x9d\x40\x5e\x8e\x3b\x64\x10\x93\x11\x3e\x9d\x5e\xa3\x44\x91\x66\x45\x76\x89\x87\xf3\xa1\x5a\x7a\x60\xbe\x07\x34\xc5\x12\xb1\x0c\xdb\x23\x68\x78\xe1\x53\x62\x2c\x2b\x31\x32\xeb\x10\xc9\x31\x4a\xeb\x81\xe2\x8d\x63\x90\x25\xd4\x30\x80\x2d\x9e\x2e\x0b\x64\x41\xcd\x03\x14\x75\x46\x4c\x21\xa8\x21\x6c\x60\xab\x77\x1c\xcc\x05\xf7\x78\x40\xc9\x21\xfe\xa9\xdf\x6f\xa0\xfa\x87\x85\xc3\x33\x22\xa4\x06\x3e\xfe\x1b\x68\xda\xc0\xd7\x33\xb9\x90\x91\xc5\xcd\xf0\xcb\x57\x94\xaa\x4a\x64\x62\x1b\x14\x4d\x32\x4c\xc3\x5c\xb0\xd1\xd7\x1b\x5c\x65\xe7\xc2\x6a\x41\xe2\xd6\xb4\x31\x3b\xfe\x6f\x02\x3f\x11\x34\x7d\x41\x0b\x2d\x4c\x5e\xa2\xaa\x5e\x05\xd5\x0b\x88\xf0\xb2\xa3\x9a\x9b\x8b\x1b\x04\x88\xba\x20\x3b\x14\x11\x18\x95\x70\x85\x56\x07\x48\xd0\x86\xa7\x82\x99\xa9\xec\x27\xd8\xde\x82\xf7\x22\x00\xb6\x81\x20\x04\x07\xe8\xf3\x95\x86\x47\x57\x17\x1e\x9b\xa9\xd5\xd1\x51\xdf\x02\xf6\x41\x4e\x3f\x83\x84\x18\x08\xbd\x9d\x26\x54\x08\x79\x23\x88\x51\x44\xc1\x25\xa1\xa6\x77\xba\xe2\xe9\x82\x8d\xd6\x08\x6d\x77\x9c\x6a\x84\x5a\xef\x20\x5e\x87\x9e\x70\x80\x09\x17\x2e\xc8\xf2\x89\xd8\x87\x61\x40\x80\xbd\x58\x9c\x1e\x20\x98\x01\x8b\xb3\xb8\xe0\x82\x4c\xd0\x0e\xf5\x9c\x41\x70\xde\x18\x23\x02\x79\x05\x9b\x58\x5d\xed\xbe\x74\x69\x2e\x82\xe6\x13\x60\xbb\x20\xfb\xfb\x81\x77\x99\x56\x2b\x61\x3a\x19\x40\xfc\x84\x80\xd7\xa8\x03\x04\x54\x67\xbe\x90\x4b\x2f\x5c\x69\xea\x00\x16\x30\x0e\x67\x89\x2b\x63\xe2\xd1\x35\x50\xb0\x36\x38\x22\x91\x92\x5d\x99\xfc\x0d\x77\x76\x80\x9a\xef\x48\x6e\x94\xae\x77\x81\x01\x50\x05\x0f\x62\xda\xb0\x41\xbb\x2c\x31\xd3\x47\xa0\x26\xeb\x7d\xc1\x15\x40\x17\x00\xcf\x13\x19\xda\xf5\x07\xae\xf0\x86\x06\xd5\x30\x05\x6e\x50\x1c\xc3\x4b\xd0\x8c\x12\x38\xf3\xc6\xe6\x8d\x99\x16\xf0\x0c\x57\x6c\x7e\xc7\xdc\xac\x2e\x99\xe6\xec\x38\x08\x9b\x63\x0f\x2e\x81\x39\xb8\x64\xc4\x0e\x1c\x58\xd0\xb0\x10\xf1\xfd\x70\x99\x99\x8c\x7c\x9e\x20\xc5\x1e\x40\xab\x23\xea\x77\xc4\x9b\xe4\xc6\xba\x20\xc0\xb1\x6a\xd2\x0b\xc9\x54\x92\xa3\x02\x39\xbf\x15\x60\xf7\x81\x14\xdd\x87\xcb\x12\x2f\x11\xfc\x7e\xc0\x20\x0f\x98\x46\x09\x4e\xbd\x83\xba\x96\x08\x78\x12\xaa\xcb\x15\xde\xdb\xe0\xc6\x4e\x54\x6d\x3b\x17\x55\xd6\xa0\xc3\x11\x66\x50\x82\x50\x95\xc0\x9d\x08\x00\x1e\x10\x22\x5b\x06\x69\x82\xc9\x4d\x2e\x95\x57\xc3\x2c\xaa\x88\x4d\xb7\x8e\x46\xd2\xfd\x9c\x70\x71\xb6\xc8\x87\xcb\x07\x36\xe8\x05\x6a\xb1\xc0\x27\x18\x58\x86\xbb\x8b\x30\x15\xcb\x11\x32\x93\x64\xbc\xe4\x1b\x6e\xd3\x67\x7f\x4a\xb7\xce\xc2\xa5\xe3\x33\x34\x3c\x62\x0d\x96\xc7\x88\x85\xfa\xdd\x0d\x38\x30\xa2\xf2\x62\x78\x70\x75\x79\xb5\xca\x55\x94\x04\x4c\x6b\xc2\xc4\xe2\x52\x6a\xab\x6b\xc1\x79\x23\x77\x60\x05\xd7\x6f\x20\xfb\xe1\x3c\x47\xeb\xc6\x9c\x74\xcc\x1b\xa8\xb5\x99\xce\x0b\xcc\x50\xe0\xbd\x58\xdd\x6e\x22\x54\x6a\x06\x15\x35\x81\xdc\x90\x99\x2d\x5d\xc5\xa6\x70\x00\x16\x40\x6b\x1b\xe0\x48\x05\x96\x67\xe5\xeb\x03\x0b\x1d\xa0\x19\x02\xce\xc1\x2c\xd1\x0c\x8e\x91\x1c\x66\x34\x2e\xa9\xc2\x1c\x23\xb9\x00\x4b\x6d\x16\x5a\x2f\x6c\x36\x22\xd3\xaa\xa0\x3a\x3b\x22\x3f\xfa\xd4\x15\x9c\x3a\x41\x4d\xd9\xac\x58\x01\xa0\x8c\xe2\x76\x98\xef\xe1\xa8\x2e\xeb\x77\x0d\xd2\x5b\x8c\x0d\xc4\x95\xca\x58\x43\xb8\x20\x0f\x78\x41\x5d\x23\x21\xb2\x3c\x90\xb5\xbe\x46\x50\x48\xf6\xee\x50\xfd\x4e\xd4\xda\x0e\x04\x17\x27\x8a\x9b\xec\xc6\x4a\xe0\x14\x2c\x46\xd2\x3b\x04\x00\xe4\x0a\xf2\x24\x2e\x33\x35\x40\xb8\xc6\x27\x46\xac\x91\xcc\xd3\x12\x1f\xa5\x6b\xb6\x0a\x28\xea\x5c\x71\x20\x95\x55\x86\x7e\xe9\x8b\x33\xc0\xea\x80\xda\x24\xa8\xd3\x82\x0d\x97\x00\xb1\x02\x2e\xae\x43\x71\x95\x02\x99\x71\x2a\x09\x55\x9e\x0b\x28\x6a\xe1\xf2\x09\x7c\xb7\x43\xc2\xc5\xd7\x28\x18\x7d\xd6\xae\x41\x8b\xb1\x0a\xdb\x72\x32\xed\xa4\x07\xad\xdd\x5c\x3f\xd0\x08\x64\x66\x5d\x6f\x72\x6a\xfc\x72\xe9\xaa\x87\x23\x2a\x74\x97\x01\x44\x85\x79\x84\x19\xc5\xcd\x16\xd7\xd9\x5f\x70\x83\x90\x12\xf2\x92\x9e\x09\x0a\x4c\x72\x82\xe9\xb1\x09\x34\x33\x4e\xdf\xc1\xe6\x6a\xd7\x94\xc4\xf6\x94\xe0\xda\x6a\x3b\x3d\x49\x26\x54\xde\xd8\xfc\x81\x14\x9b\x09\xee\xed\x28\xec\x0b\xa6\x26\xe0\x12\x6c\x8e\xfa\x46\xf3\xf5\xe6\x36\x93\x21\xd8\xe4\xdc\xe5\x00\x2d\x9d\x10\x88\xcd\x22\x52\x17\xc8\xc4\x1c\xae\x1a\x9b\x5d\x17\x57\x74\xcf\x19\x08\x5e\x71\xed\x0e\x15\x3c\x51\x20\xf9\x44\x5e\x3f\xb9\xbe\x81\x19\x45\x8b\x13\xc2\x3d\xe0\x21\x76\x34\x3e\xb1\x5c\xee\xf3\x91\x33\xa8\xee\x0b\x60\x2e\xd8\xcb\x84\xac\xd7\x5f\x1d\x9f\xc1\xd5\xe9\x36\xd7\xb5\xc9\xfa\x5c\x0b\xb2\x52\xaa\xba\x6f\x8a\x27\x23\x62\x00\x36\x51\xcd\xa8\xfe\x18\x3e\x8c\xa8\xfa\x9c\xaa\x29\x02\x6f\xd3\xa2\x96\xb8\xbb\x76\x9d\xda\x01\x2c\x35\x77\x41\x7f\xe3\xee\x1a\x3a\xac\xa7\xe1\xb7\x31\xc1\xc3\x15\x0d\x08\x3a\xd9\xb5\xb0\x65\x78\x8a\x15\x51\x56\x46\x51\x23\x38\xb7\xca\x9e\x9f\x05\x1d\x24\x3d\x04\x65\xf7\x2e\x88\x39\x26\x74\x8f\xef\x30\x8d\x07\xf2\x07\x0d\x90\x3e\x83\x2c\x25\xb4\xc3\x1a\x4e\xd5\xd8\xa7\xad\x6b\x89\x40\xc8\x84\x9e\x7e\x06\x13\xa7\x2b\x81\x9d\x00\xc4\x41\x17\xcd\xae\x8c\x0d\x27\xf8\x44\x00\x62\x9d\x9e\x4f\xb6\xa9\xb9\x34\x56\x04\x4e\x04\x0d\xcd\x17\x78\x87\xec\x50\x7e\xc5\x3b\x2f\xf4\x3a\x2c\x20\x75\x09\x07\x90\x60\xa6\xb5\x65\x84\xae\xae\x53\x2c\xba\x82\xe3\xe8\x72\x7e\xec\xf3\x59\xa0\x5e\x1d\x16\x18\x00\x6a\x77\xd7\x11\x52\x38\x92\x53\x80\xd0\x14\x20\x41\x19\xd9\x9d\x13\x4c\x73\x81\x1b\x5e\x50\x43\xb8\x80\x7f\x6c\x88\x02\x2d\x0c\x9e\x51\x3e\x33\x8d\x0e\x11\xc8\xfe\x00\xdd\xb5\x74\xf7\x05\x51\x5c\x85\x8d\x07\xd7\x57\x9c\xa1\x3d\x6f\x47\x43\x7b\x57\xba\x9e\x5c\x35\x89\xe4\x2a\xc2\x64\x28\xc4\x17\xfa\x0f\x7a\xb8\xc9\x0d\x6a\x3e\x40\xb3\x6a\x68\x14\xab\xc1\x33\xb4\x40\x44\x24\x19\x70\x7c\x23\x1a\x13\xd7\x29\xba\x3a\x96\x66\x9b\x3f\x61\x0e\x1d\xea\xf5\xe9\x87\x5c\x1c\x91\xbb\x41\x32\x93\x6c\x8f\x7d\x7f\xc1\xf1\x8f\x1d\x49\xda\x8c\x1f\x51\x74\x18\xff\x84\xfa\x57\xae\x3f\x28\x39\xd0\x36\xc0\xff\xa3\x01\xcc\xb9\x77\xf8\xd5\xc5\x08\xda\x58\x20\x11\xc9\x9e\xe2\x06\x41\x88\xa9\xe8\x0a\x8a\xcd\x3e\x82\xd3\x2d\xa6\xc0\xe6\x03\xdc\xd9\x08\x95\x0e\x48\x7c\xb0\xc3\xfc\xe2\xbc\x0a\x93\xad\x25\x82\xa3\x1a\xa4\xa9\x72\x6c\x31\x47\xd7\xd4\x10\xf1\xd0\x02\x92\xc1\xf6\xf9\xc9\xd5\x06\x5f\xe8\xbe\xf8\xd2\xfb\xbf\x50\xab\xab\xe5\xef\x5f\x89\x08\x2a\x43\x15\x52\x5f\x6c\xd7\x59\xd1\x25\xde\x41\x6b\x4a\x57\x60\x89\x40\xf9\x16\xe6\xc0\xa6\x88\xd2\x71\x1a\x16\x57\xda\x08\x2e\x2e\xb0\xcf\x01\x54\x32\xa1\x43\x24\xc1\x36\x05\xa9\x6b\xf6\xed\x2d\xae\x5a\x94\xdd\x49\xac\xae\x4c\x76\x3a\xee\x51\x40\xcb\x58\x2a\x2f\xf1\xe3\x07\x3b\x8c\x15\x05\x56\x03\xe5\x01\x59\xeb\x04\xe1\x3c\x71\x68\x7f\xea\x0a\x11\x68\x3c\xa2\xbf\x2e\xa1\xb2\xeb\x55\x94\x1d\x60\x33\x3c\x43\x06\x25\xde\x5c\x34\xc8\xae\xed\x84\x45\x0c\xb0\xc9\x37\x4e\x91\xe4\x26\x40\x43\x4e\xb8\x34\x76\xa9\xcf\x08\x7a\xaa\x1f\x92\xaf\xec\x70\x63\xe9\x2d\xb2\x3f\xc0\xab\xe7\x82\x1f\x3b\xcc\x38\x55\x86\x99\x11\x69\xf0\x1d\xa7\x7c\xa2\xb8\x1a\x40\x4b\x83\x6b\x8d\x3b\x21\xfc\x0b\xdc\x22\x69\xee\x6f\x84\xb1\x3e\x1f\x11\x91\x11\xca\x20\x62\x77\xb4\xdf\xcc\x30\x63\xae\x67\x37\x13\x68\x5d\x8a\xea\xd0\x09\xe9\x3e\x76\xc7\x0f\x46\x70\xfa\x80\xd4\xb8\x65\x81\x16\xa4\xca\x99\xe4\xc8\x40\xf8\x16\xaa\x5d\xbb\x5e\xe2\xec\xba\xbd\x3c\xa5\xbd\x23\x5b\xd4\xb9\x0c\x51\x46\x69\x5d\x5c\x5b\xbd\xdd\xef\x08\x82\xbb\x6b\x42\x30\x35\x9d\xe0\x09\x56\xf9\xfb\x57\x5d\x03\x3c\xc7\x02\x21\x4c\x6a\x02\x35\x9a\x9f\x36\x2c\x76\x83\x1b\x23\x7d\xe5\x2f\x4a\x0a\xd7\xdb\x13\x5d\x5d\xb1\x00\x36\x25\xd7\xa3\x90\x1c\x9d\x2f\x5d\xc0\x74\x07\x86\xfc\x71\x83\x2d\xdc\x58\xf1\x43\xda\xc9\xba\xc7\x5a\x9c\x20\x0b\x1d\xac\xb7\xb5\x08\x75\x59\xa6\x0e\x10\x18\x8b\x2f\xd9\xb9\xcc\xdd\x69\xdc\x13\x59\x67\x33\xa9\x2b\xaa\x44\x34\xc9\x09\x58\xd3\x01\xbb\xc8\x26\xaf\xc0\xab\x49\x44\x5e\x11\x7e\x7f\x40\x80\x53\xe0\x94\x03\xea\xeb\xc9\xd9\xe0\x0c\xa2\xb4\x43\x65\x27\xd8\x68\x01\x04\x7e\xfc\xd0\xba\x52\xe1\xf9\x16\xa5\x36\x12\x23\x56\x83\x6b\x04\x37\x95\x63\x83\xb3\x3b\x04\xeb\x48\x99\xc0\x1b\x46\xe4\x22\xfa\xc8\x5a\xb9\xcb\x99\x95\x6e\xb1\x6f\x14\x45\x0e\x54\x6a\x76\x97\x13\xbc\xc8\xff\xfe\xf8\xaa\xc0\x82\x17\x07\xa8\xec\x4a\xb9\xbb\xdc\xc3\xdb\x9d\x78\x03\xd3\xdc\x61\x52\x07\xb0\x24\xff\xd0\xd5\x56\x03\x1c\x4b\x80\x7c\x64\x73\x31\xab\x36\x6c\x56\x64\x58\xb9\x22\x91\x69\xc1\xc8\x17\xcc\xa1\xc3\x09\xda\xa4\xdf\x00\xcc\x0d\x27\x11\xc1\x0e\x2f\xae\x11\xe3\x81\xee\x90\xc6\xc5\x16\x8c\x1c\x93\xeb\x71\x2a\x71\x78\xb3\xfc\xfd\x63\xac\x09\x1d\x22\xe6\x7d\xd2\xbf\x07\xe4\x68\x00\x10\x3b\x06\x69\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\x91\x78\x0d\x81\x26\x0f\x00\x00")
+
+func webdataTexPngBytes() ([]byte, error) {
+ return bindataRead(
+ _webdataTexPng,
+ "webdata/tex.png",
+ )
+}
+
+func webdataTexPng() (*asset, error) {
+ bytes, err := webdataTexPngBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "webdata/tex.png", size: 3878, mode: os.FileMode(436), modTime: time.Unix(1529766470, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if err != nil {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "webdata/fn.js": webdataFnJs,
+ "webdata/icon.png": webdataIconPng,
+ "webdata/index.html": webdataIndexHtml,
+ "webdata/main.css": webdataMainCss,
+ "webdata/sha256.js": webdataSha256Js,
+ "webdata/tex.png": webdataTexPng,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+var _bintree = &bintree{nil, map[string]*bintree{
+ "webdata": &bintree{nil, map[string]*bintree{
+ "fn.js": &bintree{webdataFnJs, map[string]*bintree{}},
+ "icon.png": &bintree{webdataIconPng, map[string]*bintree{}},
+ "index.html": &bintree{webdataIndexHtml, map[string]*bintree{}},
+ "main.css": &bintree{webdataMainCss, map[string]*bintree{}},
+ "sha256.js": &bintree{webdataSha256Js, map[string]*bintree{}},
+ "tex.png": &bintree{webdataTexPng, map[string]*bintree{}},
+ }},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
+
+
+func assetFS() *assetfs.AssetFS {
+ assetInfo := func(path string) (os.FileInfo, error) {
+ return os.Stat(path)
+ }
+ for k := range _bintree.Children {
+ return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k}
+ }
+ panic("unreachable")
+}
diff --git a/bindata.go b/bindata.go
new file mode 100644
index 0000000..5465368
--- /dev/null
+++ b/bindata.go
@@ -0,0 +1,3 @@
+package main
+
+//go:generate go-bindata-assetfs -o ./bindata.generated.go webdata/...
diff --git a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
deleted file mode 100644
index 113ef4d..0000000
--- a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
+++ /dev/null
@@ -1,389 +0,0 @@
-
-
- com.github.unrud.RemoteTouchpad
- CC0-1.0
- GPL-3.0-or-later
- Remote Touchpad
- Control mouse and keyboard from a smartphone
-
-
- Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen).
- To take control open the displayed URL or scan the QR code.
-
-
- Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11.
-
+ Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen).
+ To take control just open the displayed URL or scan the QR code.
+
+
+ Supports Linux/X11 and Windows.
+
+
+ https://github.com/Unrud/remote-touchpad/issues
+ https://github.com/Unrud/remote-touchpad
+ Unrud
+
+
+
+
+
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.1/screenshots/1.png
+
+
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.1/screenshots/2.png
+
+
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.1/screenshots/3.png
+
+
+
+
diff --git a/desktop/com.github.unrud.RemoteTouchpad.desktop b/desktop/remote-touchpad.desktop
similarity index 71%
rename from desktop/com.github.unrud.RemoteTouchpad.desktop
rename to desktop/remote-touchpad.desktop
index 774441d..8ba6773 100644
--- a/desktop/com.github.unrud.RemoteTouchpad.desktop
+++ b/desktop/remote-touchpad.desktop
@@ -4,6 +4,6 @@ Type=Application
Name=Remote Touchpad
Comment=Control mouse and keyboard from a smartphone
Terminal=true
-Exec=remote-touchpad-wait-on-error
-Icon=com.github.unrud.RemoteTouchpad
+Exec=remote-touchpad
+Icon=remote-touchpad
Categories=Network;RemoteAccess;ConsoleOnly;
diff --git a/desktop/remote-touchpad.ico b/desktop/remote-touchpad.ico
index ec844d0..6313008 100644
Binary files a/desktop/remote-touchpad.ico and b/desktop/remote-touchpad.ico differ
diff --git a/desktop/remote-touchpad_128.png b/desktop/remote-touchpad_128.png
new file mode 100644
index 0000000..88b612e
Binary files /dev/null and b/desktop/remote-touchpad_128.png differ
diff --git a/desktop/remote-touchpad_256.png b/desktop/remote-touchpad_256.png
new file mode 100644
index 0000000..70eb4d4
Binary files /dev/null and b/desktop/remote-touchpad_256.png differ
diff --git a/desktop/remote-touchpad_512.png b/desktop/remote-touchpad_512.png
new file mode 100644
index 0000000..03f2fed
Binary files /dev/null and b/desktop/remote-touchpad_512.png differ
diff --git a/desktop/remote-touchpad_64.png b/desktop/remote-touchpad_64.png
new file mode 100644
index 0000000..37844d5
Binary files /dev/null and b/desktop/remote-touchpad_64.png differ
diff --git a/flatpak/com.github.unrud.RemoteTouchpad.json b/flatpak/com.github.unrud.RemoteTouchpad.json
new file mode 100644
index 0000000..f2b220f
--- /dev/null
+++ b/flatpak/com.github.unrud.RemoteTouchpad.json
@@ -0,0 +1,113 @@
+{
+ "app-id" : "com.github.unrud.RemoteTouchpad",
+ "runtime" : "org.freedesktop.Platform",
+ "runtime-version" : "1.6",
+ "sdk" : "org.freedesktop.Sdk",
+ "command" : "remote-touchpad",
+ "rename-appdata-file" : "remote-touchpad.appdata.xml",
+ "rename-desktop-file" : "remote-touchpad.desktop",
+ "rename-icon" : "remote-touchpad",
+ "finish-args" : [
+ "--socket=x11",
+ "--share=network"
+ ],
+ "build-options" : {
+ "append-path" : "/app/share/go/bin",
+ "env" : {
+ }
+ },
+ "modules" : [
+ {
+ "name" : "go-compiler",
+ "sources" : [
+ {
+ "only-arches" : [
+ "i386"
+ ],
+ "type" : "archive",
+ "strip-components" : 0,
+ "url" : "https://dl.google.com/go/go1.10.3.linux-386.tar.gz",
+ "sha256" : "3d5fe1932c904a01acb13dae07a5835bffafef38bef9e5a05450c52948ebdeb4"
+ },
+ {
+ "only-arches" : [
+ "x86_64"
+ ],
+ "type" : "archive",
+ "strip-components" : 0,
+ "url" : "https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz",
+ "sha256" : "fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035"
+ },
+ {
+ "only-arches" : [
+ "arm"
+ ],
+ "type" : "archive",
+ "strip-components" : 0,
+ "url" : "https://dl.google.com/go/go1.10.3.linux-armv6l.tar.gz",
+ "sha256" : "d3df3fa3d153e81041af24f31a82f86a21cb7b92c1b5552fb621bad0320f06b6"
+ },
+ {
+ "only-arches" : [
+ "aarch64"
+ ],
+ "type" : "archive",
+ "strip-components" : 0,
+ "url" : "https://dl.google.com/go/go1.10.3.linux-arm64.tar.gz",
+ "sha256" : "355128a05b456c9e68792143801ad18e0431510a53857f640f7b30ba92624ed2"
+ }
+ ],
+ "buildsystem" : "simple",
+ "build-commands" : [
+ "mkdir -p /app/share",
+ "cp -r go /app/share"
+ ],
+ "build-options" : {
+ "no-debuginfo" : true
+ },
+ "cleanup" : [
+ "*"
+ ]
+ },
+ {
+ "name" : "remote-desktop",
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://github.com/golang/net",
+ "commit" : "afe8f62b1d6bbd81f31868121a50b06d8188e1f9",
+ "dest" : "src/golang.org/x/net"
+ },
+ {
+ "type" : "git",
+ "url" : "https://github.com/skip2/go-qrcode",
+ "commit" : "cf5f9fa2f0d847edb8e038db7ed975e239095e1a",
+ "dest" : "src/github.com/skip2/go-qrcode"
+ },
+ {
+ "type" : "git",
+ "url" : "https://github.com/elazarl/go-bindata-assetfs",
+ "commit" : "38087fe4dafb822e541b3f7955075cc1c30bd294",
+ "dest" : "src/github.com/elazarl/go-bindata-assetfs"
+ },
+ {
+ "type" : "git",
+ "path" : "..",
+ "branch" : "master",
+ "dest" : "src/github.com/unrud/remote-touchpad"
+ }
+ ],
+ "buildsystem" : "simple",
+ "build-commands" : [
+ "env GOPATH=\"$(pwd)\" go build github.com/unrud/remote-touchpad",
+ "install -Dm0755 -t /app/bin remote-touchpad",
+ "install -Dm0644 -t /app/share/appdata src/github.com/unrud/remote-touchpad/desktop/remote-touchpad.appdata.xml",
+ "install -Dm0644 -t /app/share/applications src/github.com/unrud/remote-touchpad/desktop/remote-touchpad.desktop",
+ "install -Dm0644 src/github.com/unrud/remote-touchpad/desktop/remote-touchpad_64.png /app/share/icons/hicolor/64x64/apps/remote-touchpad.png",
+ "install -Dm0644 src/github.com/unrud/remote-touchpad/desktop/remote-touchpad_128.png /app/share/icons/hicolor/128x128/apps/remote-touchpad.png",
+ "install -Dm0644 src/github.com/unrud/remote-touchpad/desktop/remote-touchpad_256.png /app/share/icons/hicolor/256x256/apps/remote-touchpad.png",
+ "install -Dm0644 src/github.com/unrud/remote-touchpad/desktop/remote-touchpad_512.png /app/share/icons/hicolor/512x512/apps/remote-touchpad.png"
+ ]
+ }
+ ]
+}
diff --git a/flatpak/com.github.unrud.RemoteTouchpad.yaml b/flatpak/com.github.unrud.RemoteTouchpad.yaml
deleted file mode 100644
index 239160e..0000000
--- a/flatpak/com.github.unrud.RemoteTouchpad.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-app-id: com.github.unrud.RemoteTouchpad
-runtime: org.freedesktop.Platform
-runtime-version: '25.08'
-sdk: org.freedesktop.Sdk
-command: remote-touchpad
-sdk-extensions:
- - org.freedesktop.Sdk.Extension.golang
-finish-args:
- - --socket=fallback-x11
- - --socket=wayland
- - --share=ipc # Required by Flathub linter
- - --share=network
-modules:
- - name: remote-touchpad
- sources:
- - type: git
- path: ..
- - type: git
- url: https://github.com/godbus/dbus
- tag: v5.2.2
- commit: a8ac15ba63645f02ffd57f4b443203279ab40b30
- dest: vendor/github.com/godbus/dbus/v5
- - type: git
- url: https://github.com/skip2/go-qrcode
- commit: da1b6568686e89143e94f980a98bc2dbd5537f13
- dest: vendor/github.com/skip2/go-qrcode
- - type: git
- url: https://github.com/golang/net
- tag: v0.57.0
- commit: b8f09f6f062ceb4531b7af4bd17a5c8fe9c4b2b5
- dest: vendor/golang.org/x/net
- - type: git
- url: https://github.com/golang/sys
- tag: v0.47.0
- commit: 9e7e939dcafac07e8ab4cffa6e5fc74908413f00
- dest: vendor/golang.org/x/sys
- - type: file
- path: modules.txt
- dest: vendor
- buildsystem: simple
- build-commands:
- - . /usr/lib/sdk/golang/enable.sh; go build -tags portal,x11
- - install -Dm0755 -t /app/bin remote-touchpad
- - install -Dm0755 -t /app/bin desktop/remote-touchpad-wait-on-error
- - install -Dm0644 -t /app/share/metainfo desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
- - install -Dm0644 -t /app/share/applications desktop/com.github.unrud.RemoteTouchpad.desktop
- - install -Dm0644 -t /app/share/icons/hicolor/scalable/apps desktop/com.github.unrud.RemoteTouchpad.svg
- - |
- for res in 16 32 48 64 128 256 512; do
- rsvg-convert --height "${res}" --keep-aspect-ratio --format png --output com.github.unrud.RemoteTouchpad.png desktop/com.github.unrud.RemoteTouchpad.svg || exit 1
- install -Dm0644 -t "/app/share/icons/hicolor/${res}x${res}/apps" com.github.unrud.RemoteTouchpad.png || exit 1
- done
diff --git a/flatpak/modules.txt b/flatpak/modules.txt
deleted file mode 100644
index 6092904..0000000
--- a/flatpak/modules.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-# github.com/bendahl/uinput v1.7.0
-## explicit; go 1.13
-github.com/bendahl/uinput
-# github.com/godbus/dbus/v5 v5.2.2
-## explicit; go 1.20
-github.com/godbus/dbus/v5
-# github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-## explicit; go 1.13
-github.com/skip2/go-qrcode
-github.com/skip2/go-qrcode/bitset
-github.com/skip2/go-qrcode/reedsolomon
-# golang.org/x/net v0.57.0
-## explicit; go 1.25.0
-golang.org/x/net/websocket
-# golang.org/x/sys v0.47.0
-## explicit; go 1.25.0
-golang.org/x/sys/unix
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 5afd19d..0000000
--- a/go.mod
+++ /dev/null
@@ -1,12 +0,0 @@
-module github.com/unrud/remote-touchpad
-
-go 1.25.5
-
-require (
- github.com/bendahl/uinput v1.7.0
- github.com/godbus/dbus/v5 v5.2.2
- github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
- golang.org/x/net v0.57.0
-)
-
-require golang.org/x/sys v0.47.0 // indirect
diff --git a/go.sum b/go.sum
deleted file mode 100644
index 783b4df..0000000
--- a/go.sum
+++ /dev/null
@@ -1,10 +0,0 @@
-github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg=
-github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
-github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
-github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
-github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
-github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
-golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
-golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
-golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
-golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
diff --git a/host.go b/host.go
index 622b3bf..beb5921 100644
--- a/host.go
+++ b/host.go
@@ -1,9 +1,9 @@
/*
- * Copyright (c) 2018-2019 Unrud
+ * Copyright (c) 2018 Unrud
*
* This file is part of Remote-Touchpad.
*
- * Remote-Touchpad is free software: you can redistribute it and/or modify
+ * Foobar 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 3 of the License, or
* (at your option) any later version.
@@ -13,8 +13,8 @@
* 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 Remote-Touchpad. If not, see .
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
*/
package main
@@ -22,25 +22,11 @@ package main
import (
"fmt"
"net"
- "sort"
"strings"
)
-const ipv4Rating int = +1
-
-func findDefaultHost() string {
- type hostsValue struct {
- prio int
- host string
- }
- hosts := make([]hostsValue, 0)
- hosts = append(hosts, hostsValue{0, "localhost"})
- addIP := func(prio int, ip net.IP) {
- if ip.To4() != nil {
- prio += ipv4Rating
- }
- hosts = append(hosts, hostsValue{prio, ip.String()})
- }
+func FindDefaultHost() string {
+ fallbackAddr := ""
for _, publicIP := range []string{"2001:4860:4860::8888", "8.8.8.8"} {
addr := fmt.Sprintf("[%s]:80", publicIP)
conn, err := net.Dial("udp", addr)
@@ -50,40 +36,43 @@ func findDefaultHost() string {
conn.Close()
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
- panic(err)
- }
- ip := net.ParseIP(host)
- if ip != nil {
- addIP(100, ip)
- }
- }
- interfaces, _ := net.Interfaces()
- for _, inter := range interfaces {
- if inter.Flags&net.FlagUp == 0 {
continue
}
- addrs, _ := inter.Addrs()
+ return host
+ }
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ goto fallback
+ }
+ for _, inter := range interfaces {
+ if inter.Flags&net.FlagUp == 0 || inter.Flags&net.FlagLoopback != 0 {
+ continue
+ }
+ addrs, err := inter.Addrs()
+ if err != nil {
+ continue
+ }
addrs:
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
- panic(err)
- }
- if inter.Flags&net.FlagLoopback != 0 {
- addIP(10, ip)
continue
}
- for _, linkLocalPrefix := range []string{
- "169.254.", "fe8", "fe9", "fea", "feb",
- } {
+ if fallbackAddr == "" {
+ fallbackAddr = ip.String()
+ }
+ for _, linkLocalPrefix := range []string{"169.254.", "fe8", "fe9", "fea", "feb"} {
if strings.HasPrefix(ip.String(), linkLocalPrefix) {
- addIP(20, ip)
continue addrs
}
}
- addIP(30, ip)
+ return ip.String()
}
+
}
- sort.Slice(hosts, func(i, j int) bool { return hosts[i].prio > hosts[j].prio })
- return hosts[0].host
+fallback:
+ if fallbackAddr != "" {
+ return fallbackAddr
+ }
+ return "localhost"
}
diff --git a/icon-font/U+1F507 Speaker with Cancellation Stroke.svg b/icon-font/U+1F507 Speaker with Cancellation Stroke.svg
deleted file mode 100644
index 782192a..0000000
--- a/icon-font/U+1F507 Speaker with Cancellation Stroke.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+1F509 Speaker with One Sound Wave.svg b/icon-font/U+1F509 Speaker with One Sound Wave.svg
deleted file mode 100644
index bfb498e..0000000
--- a/icon-font/U+1F509 Speaker with One Sound Wave.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+1F50A Speaker with Three Sound Waves.svg b/icon-font/U+1F50A Speaker with Three Sound Waves.svg
deleted file mode 100644
index 755e32f..0000000
--- a/icon-font/U+1F50A Speaker with Three Sound Waves.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+1FA9F Window.svg b/icon-font/U+1FA9F Window.svg
deleted file mode 100644
index e6c41e9..0000000
--- a/icon-font/U+1FA9F Window.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+2190 Leftwards Arrow.svg b/icon-font/U+2190 Leftwards Arrow.svg
deleted file mode 100644
index 6364869..0000000
--- a/icon-font/U+2190 Leftwards Arrow.svg
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
diff --git a/icon-font/U+2191 Upwards Arrow.svg b/icon-font/U+2191 Upwards Arrow.svg
deleted file mode 100644
index f6e7e6a..0000000
--- a/icon-font/U+2191 Upwards Arrow.svg
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
diff --git a/icon-font/U+2192 Rightwards Arrow.svg b/icon-font/U+2192 Rightwards Arrow.svg
deleted file mode 100644
index f13e555..0000000
--- a/icon-font/U+2192 Rightwards Arrow.svg
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
diff --git a/icon-font/U+2193 Downwards Arrow.svg b/icon-font/U+2193 Downwards Arrow.svg
deleted file mode 100644
index 001042c..0000000
--- a/icon-font/U+2193 Downwards Arrow.svg
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
diff --git a/icon-font/U+21BB Clockwise Open Circle Arrow.svg b/icon-font/U+21BB Clockwise Open Circle Arrow.svg
deleted file mode 100644
index 0d2833b..0000000
--- a/icon-font/U+21BB Clockwise Open Circle Arrow.svg
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
diff --git a/icon-font/U+2261 Identical To.svg b/icon-font/U+2261 Identical To.svg
deleted file mode 100644
index bdf2d84..0000000
--- a/icon-font/U+2261 Identical To.svg
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
diff --git a/icon-font/U+2326 Erase To the Right.svg b/icon-font/U+2326 Erase To the Right.svg
deleted file mode 100644
index 83f7216..0000000
--- a/icon-font/U+2326 Erase To the Right.svg
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
diff --git a/icon-font/U+2328 Keyboard.svg b/icon-font/U+2328 Keyboard.svg
deleted file mode 100644
index d473aa0..0000000
--- a/icon-font/U+2328 Keyboard.svg
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
diff --git a/icon-font/U+232B Erase To the Left.svg b/icon-font/U+232B Erase To the Left.svg
deleted file mode 100644
index 1543cdd..0000000
--- a/icon-font/U+232B Erase To the Left.svg
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
diff --git a/icon-font/U+23ED Black Right-Pointing Double Triangle with Vertical Bar.svg b/icon-font/U+23ED Black Right-Pointing Double Triangle with Vertical Bar.svg
deleted file mode 100644
index 673885a..0000000
--- a/icon-font/U+23ED Black Right-Pointing Double Triangle with Vertical Bar.svg
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
diff --git a/icon-font/U+23EE Black Left-Pointing Double Triangle with Vertical Bar.svg b/icon-font/U+23EE Black Left-Pointing Double Triangle with Vertical Bar.svg
deleted file mode 100644
index 52f55e1..0000000
--- a/icon-font/U+23EE Black Left-Pointing Double Triangle with Vertical Bar.svg
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
diff --git a/icon-font/U+23EF Black Right-Pointing Triangle with Double Vertical Bar.svg b/icon-font/U+23EF Black Right-Pointing Triangle with Double Vertical Bar.svg
deleted file mode 100644
index cca45ff..0000000
--- a/icon-font/U+23EF Black Right-Pointing Triangle with Double Vertical Bar.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+27A3 Three-D Bottom-Lighted Rightwards Arrowhead.svg b/icon-font/U+27A3 Three-D Bottom-Lighted Rightwards Arrowhead.svg
deleted file mode 100644
index a44611e..0000000
--- a/icon-font/U+27A3 Three-D Bottom-Lighted Rightwards Arrowhead.svg
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
diff --git a/icon-font/U+2922 North East and South West Arrow.svg b/icon-font/U+2922 North East and South West Arrow.svg
deleted file mode 100644
index 608af70..0000000
--- a/icon-font/U+2922 North East and South West Arrow.svg
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
diff --git a/icon-font/U+2B60 Leftwards Triangle-Headed Arrow.svg b/icon-font/U+2B60 Leftwards Triangle-Headed Arrow.svg
deleted file mode 100644
index 6364869..0000000
--- a/icon-font/U+2B60 Leftwards Triangle-Headed Arrow.svg
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
diff --git a/icon-font/U+2B62 Rightwards Triangle-Headed Arrow.svg b/icon-font/U+2B62 Rightwards Triangle-Headed Arrow.svg
deleted file mode 100644
index f13e555..0000000
--- a/icon-font/U+2B62 Rightwards Triangle-Headed Arrow.svg
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
diff --git a/icon-font/U+2B70 Leftwards Triangle-Headed Arrow To Bar.svg b/icon-font/U+2B70 Leftwards Triangle-Headed Arrow To Bar.svg
deleted file mode 100644
index a92cc25..0000000
--- a/icon-font/U+2B70 Leftwards Triangle-Headed Arrow To Bar.svg
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
diff --git a/icon-font/U+2B72 Rightwards Triangle-Headed Arrow To Bar.svg b/icon-font/U+2B72 Rightwards Triangle-Headed Arrow To Bar.svg
deleted file mode 100644
index 33c8d85..0000000
--- a/icon-font/U+2B72 Rightwards Triangle-Headed Arrow To Bar.svg
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
diff --git a/icon-font/U+2B82 Rightwards Triangle-Headed Arrow Over Leftwards Triangle-Headed Arrow.svg b/icon-font/U+2B82 Rightwards Triangle-Headed Arrow Over Leftwards Triangle-Headed Arrow.svg
deleted file mode 100644
index 3cdb17f..0000000
--- a/icon-font/U+2B82 Rightwards Triangle-Headed Arrow Over Leftwards Triangle-Headed Arrow.svg
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
diff --git a/icon-font/U+2BA0 Downwards Triangle-Headed Arrow with Long Tip Leftwards.svg b/icon-font/U+2BA0 Downwards Triangle-Headed Arrow with Long Tip Leftwards.svg
deleted file mode 100644
index 8075176..0000000
--- a/icon-font/U+2BA0 Downwards Triangle-Headed Arrow with Long Tip Leftwards.svg
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
diff --git a/icon-font/U+2BC7 Black Medium Left-Pointing Triangle Centred.svg b/icon-font/U+2BC7 Black Medium Left-Pointing Triangle Centred.svg
deleted file mode 100644
index 26afd5b..0000000
--- a/icon-font/U+2BC7 Black Medium Left-Pointing Triangle Centred.svg
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
diff --git a/icon-font/icon.sfd b/icon-font/icon.sfd
deleted file mode 100644
index 34d0889..0000000
--- a/icon-font/icon.sfd
+++ /dev/null
@@ -1,1091 +0,0 @@
-SplineFontDB: 3.2
-FontName: Icon
-FullName: Icon
-FamilyName: Icon
-Weight: Regular
-Copyright: Copyright (c) 2018 Unrud
-UComments: "2018-8-29: Created with FontForge (http://fontforge.org)"
-Version: 001.000
-ItalicAngle: 0
-UnderlinePosition: -102.4
-UnderlineWidth: 51.2
-Ascent: 819
-Descent: 205
-InvalidEm: 0
-LayerCount: 2
-Layer: 0 0 "Back" 1
-Layer: 1 0 "Zeichen" 0
-XUID: [1021 32 396054688 5708680]
-FSType: 0
-OS2Version: 0
-OS2_WeightWidthSlopeOnly: 0
-OS2_UseTypoMetrics: 1
-CreationTime: 1535548659
-ModificationTime: 1643323396
-PfmFamily: 17
-TTFWeight: 400
-TTFWidth: 5
-LineGap: 92
-VLineGap: 0
-OS2TypoAscent: 0
-OS2TypoAOffset: 1
-OS2TypoDescent: 0
-OS2TypoDOffset: 1
-OS2TypoLinegap: 92
-OS2WinAscent: 0
-OS2WinAOffset: 1
-OS2WinDescent: 0
-OS2WinDOffset: 1
-HheadAscent: 0
-HheadAOffset: 1
-HheadDescent: 0
-HheadDOffset: 1
-OS2Vendor: 'PfEd'
-MarkAttachClasses: 1
-DEI: 91125
-LangName: 1033
-Encoding: UnicodeFull
-Compacted: 1
-UnicodeInterp: none
-NameList: AGL For New Fonts
-DisplaySize: -48
-AntiAlias: 1
-FitToEm: 0
-WinInfo: 0 35 12
-BeginPrivate: 0
-EndPrivate
-TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
-BeginChars: 1114112 25
-
-StartChar: uni2328
-Encoding: 9000 9000 0
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: 10 82<256 768> 205 61<292 404 456 568 620 732> 379 51<128 241 292 404 456 568 620 732 783 896> 543 61<128 241 292 404 456 568 620 732 783 896>
-VStem: 20 108<266 379 430 543> 241 51<266 379 430 543> 404 52<266 379 430 543> 568 52<266 379 430 543> 732 51<266 379 430 543> 768 236<92 205> 896 108<266 379 430 543>
-LayerCount: 2
-Fore
-SplineSet
-72 604 m 2xff20
- 952 604 l 2
- 981 604 1004 582 1004 553 c 2
- 1004 61 l 2
- 1004 32 981 10 952 10 c 2
- 72 10 l 2
- 43 10 20 32 20 61 c 2
- 20 553 l 2
- 20 582 43 604 72 604 c 2xff20
-138 543 m 2
- 132 543 128 538 128 532 c 2
- 128 440 l 2
- 128 434 132 430 138 430 c 2
- 230 430 l 2
- 236 430 241 434 241 440 c 2
- 241 532 l 2
- 241 538 236 543 230 543 c 2
- 138 543 l 2
-302 543 m 2
- 296 543 292 538 292 532 c 2
- 292 440 l 2
- 292 434 296 430 302 430 c 2
- 394 430 l 2
- 400 430 404 434 404 440 c 2
- 404 532 l 2
- 404 538 400 543 394 543 c 2
- 302 543 l 2
-466 543 m 2
- 460 543 456 538 456 532 c 2
- 456 440 l 2
- 456 434 460 430 466 430 c 2
- 558 430 l 2
- 564 430 568 434 568 440 c 2
- 568 532 l 2
- 568 538 564 543 558 543 c 2
- 466 543 l 2
-630 543 m 2
- 624 543 620 538 620 532 c 2
- 620 440 l 2
- 620 434 624 430 630 430 c 2
- 722 430 l 2
- 728 430 732 434 732 440 c 2
- 732 532 l 2xff80
- 732 538 728 543 722 543 c 2
- 630 543 l 2
-794 543 m 2
- 788 543 783 538 783 532 c 2
- 783 440 l 2
- 783 434 788 430 794 430 c 2
- 886 430 l 2
- 892 430 896 434 896 440 c 2
- 896 532 l 2xffa0
- 896 538 892 543 886 543 c 2
- 794 543 l 2
-138 379 m 2
- 132 379 128 375 128 369 c 2
- 128 276 l 2
- 128 270 132 266 138 266 c 2
- 230 266 l 2
- 236 266 241 270 241 276 c 2
- 241 369 l 2
- 241 375 236 379 230 379 c 2
- 138 379 l 2
-302 379 m 2
- 296 379 292 375 292 369 c 2
- 292 276 l 2
- 292 270 296 266 302 266 c 2
- 394 266 l 2
- 400 266 404 270 404 276 c 2
- 404 369 l 2
- 404 375 400 379 394 379 c 2
- 302 379 l 2
-466 379 m 2
- 460 379 456 375 456 369 c 2
- 456 276 l 2
- 456 270 460 266 466 266 c 2
- 558 266 l 2
- 564 266 568 270 568 276 c 2
- 568 369 l 2
- 568 375 564 379 558 379 c 2
- 466 379 l 2
-630 379 m 2
- 624 379 620 375 620 369 c 2
- 620 276 l 2
- 620 270 624 266 630 266 c 2
- 722 266 l 2
- 728 266 732 270 732 276 c 2
- 732 369 l 2
- 732 375 728 379 722 379 c 2
- 630 379 l 2
-794 379 m 2
- 788 379 783 375 783 369 c 2
- 783 276 l 2
- 783 270 788 266 794 266 c 2
- 886 266 l 2
- 892 266 896 270 896 276 c 2
- 896 369 l 2
- 896 375 892 379 886 379 c 2
- 794 379 l 2
-266 205 m 2
- 260 205 256 201 256 195 c 2
- 256 102 l 2
- 256 96 260 92 266 92 c 2
- 758 92 l 2
- 764 92 768 96 768 102 c 2
- 768 195 l 2xff40
- 768 201 764 205 758 205 c 2
- 266 205 l 2
-EndSplineSet
-EndChar
-
-StartChar: uni2922
-Encoding: 10530 10530 1
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: -92 82<253 420> 625 82<604 771>
-VStem: 113 82<48 215> 829 82<399 566>
-LayerCount: 2
-Fore
-SplineSet
-614 707 m 2
- 901 707 l 2
- 906 707 911 701 911 696 c 2
- 911 410 l 2
- 911 404 907 399 901 399 c 2
- 840 399 l 2
- 834 399 829 404 829 410 c 2
- 829 566 l 1
- 650 388 l 2
- 646 384 640 384 636 388 c 2
- 593 431 l 2
- 589 435 589 441 593 445 c 2
- 771 625 l 1
- 614 625 l 2
- 609 625 604 630 604 635 c 2
- 604 696 l 2
- 604 702 608 707 614 707 c 2
-410 -92 m 2
- 123 -92 l 2
- 118 -92 113 -87 113 -82 c 2
- 113 205 l 2
- 113 211 117 215 123 215 c 2
- 184 215 l 2
- 190 215 195 211 195 205 c 2
- 195 48 l 1
- 374 226 l 2
- 378 230 384 230 388 226 c 2
- 431 183 l 2
- 435 179 435 173 431 169 c 2
- 253 -10 l 1
- 410 -10 l 2
- 415 -10 420 -15 420 -20 c 2
- 420 -82 l 2
- 420 -88 416 -92 410 -92 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni27A3
-Encoding: 10147 10147 2
-Width: 1024
-VWidth: 0
-Flags: W
-LayerCount: 2
-Fore
-SplineSet
-34 651 m 0
- 35 651 l 0
- 36 651 l 0
- 37 651 l 0
- 39 650 l 2
- 993 342 l 2
- 994 341 l 0
- 995 341 l 0
- 996 340 l 0
- 997 340 l 0
- 998 339 l 0
- 999 338 l 0
- 1000 337 l 0
- 1001 336 l 0
- 1001 334 l 0
- 1002 333 l 0
- 1002 332 l 0
- 1004 330 l 0
- 1004 329 l 0
- 1004 328 l 2
- 1004 327 l 0
- 1004 321 998 314 993 312 c 2
- 39 -36 l 2
- 38 -37 35 -37 34 -37 c 0
- 26 -37 18 -30 18 -22 c 0
- 18 -20 19 -16 20 -14 c 2
- 199 327 l 1
- 199 328 l 1
- 198 328 l 1
- 20 628 l 2
- 20 629 l 0
- 19 630 l 0
- 17 634 18 638 19 642 c 0
- 20 643 l 0
- 20 644 l 0
- 22 645 l 0
- 22 646 l 0
- 24 649 29 651 33 651 c 0
- 34 651 l 0
-231 328 m 2
- 231 327 l 0
- 231 325 231 321 230 319 c 2
- 66 6 l 1
- 941 327 l 1
- 937 328 l 1
- 231 328 l 2
-EndSplineSet
-EndChar
-
-StartChar: uni21BB
-Encoding: 8635 8635 3
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: -12 82<420.425 611.489> 545 82<415.37 602.162>
-VStem: 193 81<210.457 396.738>
-LayerCount: 2
-Fore
-SplineSet
-499 627 m 0
- 502 627 509 627 512 627 c 0
- 615 627 741 554 791 464 c 1
- 834 615 l 2
- 836 620 841 623 846 622 c 2
- 905 605 l 2
- 910 603 914 598 912 593 c 2
- 835 316 l 2
- 834 311 827 308 822 309 c 2
- 546 387 l 2
- 541 388 537 394 539 399 c 2
- 555 459 l 2
- 557 464 563 467 568 466 c 2
- 719 424 l 1
- 681 491 589 545 512 545 c 0
- 478 545 426 530 396 514 c 0
- 329 476 274 384 274 307 c 0
- 274 273 289 221 305 191 c 0
- 343 124 435 70 512 70 c 0
- 546 70 599 84 629 100 c 1
- 669 29 l 1
- 629 6 558 -12 512 -12 c 0
- 409 -12 283 61 233 151 c 0
- 210 191 193 261 193 307 c 0
- 193 410 265 536 355 586 c 0
- 392 606 457 625 499 627 c 0
-EndSplineSet
-EndChar
-
-StartChar: u1F50A
-Encoding: 128266 128266 4
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: 144 342<20 269>
-VStem: 20 492<144 486> 529 100<148.145 215.75 402.021 485.328> 552 107<170.891 440.716> 724 107<170.593 443.765> 896 108<172.375 441.418>
-LayerCount: 2
-Fore
-SplineSet
-497 650 m 0xcc
- 505 650 512 643 512 635 c 2
- 512 -20 l 2
- 512 -33 497 -40 487 -32 c 2
- 269 144 l 1
- 36 144 l 2
- 28 144 20 152 20 160 c 2
- 20 471 l 2
- 20 479 28 486 36 486 c 2
- 290 486 l 1
- 487 646 l 2
- 490 648 494 650 497 650 c 0xcc
-898 614 m 0
- 903 614 908 612 911 608 c 0
- 967 526 1001 427 1004 321 c 0
- 1004 294 l 2
- 1001 189 967 90 912 8 c 0
- 908 2 901 0 894 3 c 2
- 821 33 l 2
- 812 37 809 47 815 55 c 0
- 866 126 896 213 896 307 c 0
- 896 403 864 490 812 562 c 0
- 806 570 810 582 819 586 c 2
- 893 613 l 2
- 895 614 896 614 898 614 c 0
-758 562 m 0
- 764 562 768 559 771 554 c 0
- 809 480 831 396 831 307 c 0
- 831 221 810 140 774 67 c 0
- 770 60 762 56 755 59 c 2
- 684 89 l 2
- 676 92 672 102 676 110 c 0
- 707 169 724 235 724 307 c 0
- 724 382 705 452 672 512 c 0
- 668 520 671 531 680 534 c 2
- 753 561 l 2
- 755 562 756 562 758 562 c 0
-610 507 m 0
- 616 507 623 503 625 497 c 0
- 648 438 659 374 659 307 c 0xdc
- 659 245 648 185 629 129 c 0
- 626 121 616 117 608 120 c 2
- 538 148 l 2
- 531 151 526 160 529 168 c 0xec
- 544 212 552 258 552 307 c 0xdc
- 552 360 541 412 524 459 c 0
- 521 467 526 476 534 479 c 2
- 605 506 l 2
- 607 507 608 507 610 507 c 0
-EndSplineSet
-EndChar
-
-StartChar: u1F509
-Encoding: 128265 128265 5
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: 144 342<20 269>
-VStem: 20 492<144 486> 529 100<148.145 215.75 402.021 485.328> 552 107<170.891 440.716>
-LayerCount: 2
-Fore
-SplineSet
-497 650 m 0xc0
- 505 650 512 643 512 635 c 2
- 512 -20 l 2
- 512 -33 497 -40 487 -32 c 2
- 269 144 l 1
- 36 144 l 2
- 28 144 20 152 20 160 c 2
- 20 471 l 2
- 20 479 28 486 36 486 c 2
- 290 486 l 1
- 487 646 l 2
- 490 648 494 650 497 650 c 0xc0
-610 507 m 0
- 616 507 623 503 625 497 c 0
- 648 438 659 374 659 307 c 0xd0
- 659 245 648 185 629 129 c 0
- 626 121 616 117 608 120 c 2
- 538 148 l 2
- 531 151 526 160 529 168 c 0xe0
- 544 212 552 258 552 307 c 0xd0
- 552 360 541 412 524 459 c 0
- 521 467 526 476 534 479 c 2
- 605 506 l 2
- 607 507 608 507 610 507 c 0
-EndSplineSet
-EndChar
-
-StartChar: u1F507
-Encoding: 128263 128263 6
-Width: 1024
-VWidth: 0
-Flags: W
-HStem: 144 342<20 269>
-VStem: 20 492<144 486>
-LayerCount: 2
-Fore
-SplineSet
-497 650 m 0
- 505 650 512 643 512 635 c 2
- 512 -20 l 2
- 512 -33 497 -40 487 -32 c 2
- 269 144 l 1
- 36 144 l 2
- 28 144 20 152 20 160 c 2
- 20 471 l 2
- 20 479 28 486 36 486 c 2
- 290 486 l 1
- 487 646 l 2
- 490 648 494 650 497 650 c 0
-679 495 m 0
- 683 495 687 493 690 490 c 2
- 815 365 l 1
- 941 490 l 2
- 947 496 957 496 963 490 c 2
- 998 455 l 2
- 1004 449 1004 439 998 433 c 2
- 873 307 l 1
- 998 181 l 2
- 1004 175 1004 166 998 160 c 2
- 963 124 l 2
- 957 118 947 118 941 124 c 2
- 815 250 l 1
- 690 124 l 2
- 684 118 674 118 668 124 c 2
- 632 160 l 2
- 626 166 626 175 632 181 c 2
- 758 307 l 1
- 632 433 l 2
- 626 439 626 449 632 455 c 2
- 668 490 l 2
- 671 493 675 495 679 495 c 0
-EndSplineSet
-EndChar
-
-StartChar: uni23ED
-Encoding: 9197 9197 7
-Width: 1024
-VWidth: 0
-Flags: W
-VStem: 840 164<-36 288 327 650>
-LayerCount: 2
-Fore
-SplineSet
-988 650 m 2
- 996 650 1004 643 1004 635 c 2
- 1004 -20 l 2
- 1004 -28 996 -36 988 -36 c 2
- 855 -36 l 2
- 847 -36 840 -28 840 -20 c 2
- 840 288 l 1
- 455 -32 l 2
- 445 -40 430 -33 430 -20 c 2
- 430 288 l 1
- 46 -32 l 2
- 36 -40 20 -33 20 -20 c 2
- 20 635 l 2
- 20 643 29 650 37 650 c 0
- 40 650 43 648 46 646 c 2
- 430 327 l 1
- 430 635 l 2
- 430 643 437 650 445 650 c 0
- 448 650 452 648 455 646 c 2
- 840 327 l 1
- 840 635 l 2
- 840 643 847 650 855 650 c 2
- 988 650 l 2
-EndSplineSet
-EndChar
-
-StartChar: uni23EE
-Encoding: 9198 9198 8
-Width: 1024
-VWidth: 0
-Flags: W
-VStem: 20 164<-36 288 327 650>
-LayerCount: 2
-Fore
-SplineSet
-36 650 m 2
- 169 650 l 2
- 177 650 184 643 184 635 c 2
- 184 327 l 1
- 569 646 l 2
- 572 648 576 650 579 650 c 0
- 587 650 594 643 594 635 c 2
- 594 327 l 1
- 978 646 l 2
- 981 648 984 650 987 650 c 0
- 995 650 1004 643 1004 635 c 2
- 1004 -20 l 2
- 1004 -33 988 -40 978 -32 c 2
- 594 288 l 1
- 594 -20 l 2
- 594 -33 579 -40 569 -32 c 2
- 184 288 l 1
- 184 -20 l 2
- 184 -28 177 -36 169 -36 c 2
- 36 -36 l 2
- 28 -36 20 -28 20 -20 c 2
- 20 635 l 2
- 20 643 28 650 36 650 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni23EF
-Encoding: 9199 9199 9
-Width: 1024
-VWidth: 0
-Flags: W
-VStem: 561 164<-36 650> 840 164<-36 650>
-LayerCount: 2
-Fore
-SplineSet
-36 650 m 0
- 40 650 43 648 46 646 c 2
- 439 319 l 2
- 446 313 446 301 439 295 c 2
- 46 -32 l 2
- 36 -40 20 -33 20 -20 c 2
- 20 635 l 2
- 20 643 28 650 36 650 c 0
-577 650 m 2
- 710 650 l 2
- 718 650 725 643 725 635 c 2
- 725 -20 l 2
- 725 -28 718 -36 710 -36 c 2
- 577 -36 l 2
- 569 -36 561 -28 561 -20 c 2
- 561 635 l 2
- 561 643 569 650 577 650 c 2
-855 650 m 2
- 988 650 l 2
- 996 650 1004 643 1004 635 c 2
- 1004 -20 l 2
- 1004 -28 996 -36 988 -36 c 2
- 855 -36 l 2
- 847 -36 840 -28 840 -20 c 2
- 840 635 l 2
- 840 643 847 650 855 650 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni2B60
-Encoding: 11104 11104 10
-Width: 1024
-Flags: W
-HStem: 225 164<445 1004>
-LayerCount: 2
-Fore
-SplineSet
-430 650 m 0
- 438 650 445 643 445 635 c 2
- 445 404 l 2
- 445 396 453 389 461 389 c 2
- 988 389 l 2
- 996 389 1004 382 1004 374 c 2
- 1004 241 l 2
- 1004 233 996 225 988 225 c 2
- 461 225 l 2
- 453 225 445 218 445 210 c 2
- 445 -20 l 2
- 445 -33 430 -40 420 -32 c 2
- 26 295 l 2
- 19 301 19 313 26 319 c 2
- 420 646 l 2
- 423 648 426 650 430 650 c 0
-EndSplineSet
-EndChar
-
-StartChar: uni2B62
-Encoding: 11106 11106 11
-Width: 1024
-Flags: W
-HStem: 225 164<20 579>
-LayerCount: 2
-Fore
-SplineSet
-594 650 m 0
- 598 650 601 648 604 646 c 2
- 997 319 l 2
- 1004 313 1004 301 997 295 c 2
- 604 -32 l 2
- 594 -40 579 -33 579 -20 c 2
- 579 210 l 2
- 579 218 571 225 563 225 c 2
- 36 225 l 2
- 28 225 20 233 20 241 c 2
- 20 374 l 2
- 20 382 28 389 36 389 c 2
- 563 389 l 2
- 571 389 579 396 579 404 c 2
- 579 635 l 2
- 579 643 586 650 594 650 c 0
-EndSplineSet
-EndChar
-
-StartChar: u1FA9F
-Encoding: 129695 129695 12
-Width: 1024
-Flags: W
-HStem: -184 450<20 471 553 1004> 348 451<20 471 553 1004>
-VStem: 20 451<-184 266 348 799> 553 451<-184 266 348 799>
-LayerCount: 2
-Fore
-SplineSet
-560 266 m 2
- 996 266 l 2
- 1000 266 1004 263 1004 259 c 2
- 1004 -177 l 2
- 1004 -181 1000 -184 996 -184 c 2
- 560 -184 l 2
- 556 -184 553 -181 553 -177 c 2
- 553 259 l 2
- 553 263 556 266 560 266 c 2
-28 266 m 2
- 464 266 l 2
- 468 266 471 263 471 259 c 2
- 471 -177 l 2
- 471 -181 468 -184 464 -184 c 2
- 28 -184 l 2
- 24 -184 20 -181 20 -177 c 2
- 20 259 l 2
- 20 263 24 266 28 266 c 2
-560 799 m 2
- 996 799 l 2
- 1000 799 1004 796 1004 792 c 2
- 1004 355 l 2
- 1004 351 1000 348 996 348 c 2
- 560 348 l 2
- 556 348 553 351 553 355 c 2
- 553 792 l 2
- 553 796 556 799 560 799 c 2
-28 799 m 2
- 464 799 l 2
- 468 799 471 796 471 792 c 2
- 471 355 l 2
- 471 351 468 348 464 348 c 2
- 28 348 l 2
- 24 348 20 351 20 355 c 2
- 20 792 l 2
- 20 796 24 799 28 799 c 2
-EndSplineSet
-EndChar
-
-StartChar: equivalence
-Encoding: 8801 8801 13
-Width: 1024
-Flags: W
-HStem: 47 83<169 855> 266 82<169 855> 484 83<169 855>
-CounterMasks: 1 e0
-LayerCount: 2
-Fore
-SplineSet
-855 333 m 2
- 855 282 l 2
- 855 274 848 266 840 266 c 2
- 184 266 l 2
- 176 266 169 274 169 282 c 2
- 169 333 l 2
- 169 341 176 348 184 348 c 2
- 840 348 l 2
- 848 348 855 341 855 333 c 2
-855 115 m 2
- 855 62 l 2
- 855 54 848 47 840 47 c 2
- 184 47 l 2
- 176 47 169 54 169 62 c 2
- 169 115 l 2
- 169 123 176 130 184 130 c 2
- 840 130 l 2
- 848 130 855 123 855 115 c 2
-855 552 m 2
- 855 500 l 2
- 855 492 848 484 840 484 c 2
- 184 484 l 2
- 176 484 169 492 169 500 c 2
- 169 552 l 2
- 169 560 176 567 184 567 c 2
- 840 567 l 2
- 848 567 855 560 855 552 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni2B70
-Encoding: 11120 11120 14
-Width: 1024
-Flags: W
-HStem: 225 164<594 1004>
-VStem: 20 164<-36 288 327 650>
-LayerCount: 2
-Fore
-SplineSet
-36 650 m 2
- 169 650 l 2
- 177 650 184 643 184 635 c 2
- 184 327 l 1
- 569 646 l 2
- 572 648 576 650 579 650 c 0
- 587 650 594 643 594 635 c 2
- 594 404 l 2
- 594 396 601 389 609 389 c 2
- 988 389 l 2
- 996 389 1004 382 1004 374 c 2
- 1004 241 l 2
- 1004 233 996 225 988 225 c 2
- 609 225 l 2
- 601 225 594 218 594 210 c 2
- 594 -20 l 2
- 594 -33 579 -40 569 -32 c 2
- 184 288 l 1
- 184 -20 l 2
- 184 -28 177 -36 169 -36 c 2
- 36 -36 l 2
- 28 -36 20 -28 20 -20 c 2
- 20 635 l 2
- 20 643 28 650 36 650 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni2B72
-Encoding: 11122 11122 15
-Width: 1024
-Flags: W
-HStem: 225 164<20 430>
-VStem: 840 164<-36 288 327 650>
-LayerCount: 2
-Fore
-SplineSet
-988 650 m 2
- 996 650 1004 643 1004 635 c 2
- 1004 -20 l 2
- 1004 -28 996 -36 988 -36 c 2
- 855 -36 l 2
- 847 -36 840 -28 840 -20 c 2
- 840 288 l 1
- 455 -32 l 2
- 445 -40 430 -33 430 -20 c 2
- 430 210 l 2
- 430 218 423 225 415 225 c 2
- 36 225 l 2
- 28 225 20 233 20 241 c 2
- 20 374 l 2
- 20 382 28 389 36 389 c 2
- 415 389 l 2
- 423 389 430 396 430 404 c 2
- 430 635 l 2
- 430 643 437 650 445 650 c 0
- 448 650 452 648 455 646 c 2
- 840 327 l 1
- 840 635 l 2
- 840 643 847 650 855 650 c 2
- 988 650 l 2
-EndSplineSet
-EndChar
-
-StartChar: uni232B
-Encoding: 9003 9003 16
-Width: 1024
-Flags: W
-HStem: 495 155<546 601.75 822.25 877>
-LayerCount: 2
-Fore
-SplineSet
-430 650 m 2
- 988 650 l 2
- 996 650 1004 643 1004 635 c 2
- 1004 374 l 1
- 1004 241 l 1
- 1004 -20 l 2
- 1004 -28 996 -36 988 -36 c 2
- 430 -36 l 2
- 427 -36 423 -34 420 -32 c 2
- 26 295 l 2
- 19 301 19 313 26 319 c 2
- 420 646 l 2
- 423 648 426 650 430 650 c 2
-848 495 m 0
- 844 495 841 493 838 490 c 2
- 712 365 l 1
- 586 490 l 2
- 583 493 579 495 575 495 c 0
- 571 495 567 493 564 490 c 2
- 528 455 l 2
- 522 449 522 439 528 433 c 2
- 654 307 l 1
- 528 181 l 2
- 522 175 522 166 528 160 c 2
- 564 124 l 2
- 570 118 580 118 586 124 c 2
- 712 250 l 1
- 838 124 l 2
- 844 118 853 118 859 124 c 2
- 895 160 l 2
- 901 166 901 175 895 181 c 2
- 769 307 l 1
- 895 433 l 2
- 901 439 901 449 895 455 c 2
- 859 490 l 2
- 856 493 852 495 848 495 c 0
-EndSplineSet
-EndChar
-
-StartChar: uni2326
-Encoding: 8998 8998 17
-Width: 1024
-Flags: W
-HStem: 495 155<147 201.75 421.375 478>
-LayerCount: 2
-Fore
-SplineSet
-594 650 m 2
- 598 650 601 648 604 646 c 2
- 997 319 l 2
- 1004 313 1004 301 997 295 c 2
- 604 -32 l 2
- 601 -34 597 -36 594 -36 c 2
- 36 -36 l 2
- 28 -36 20 -28 20 -20 c 2
- 20 241 l 1
- 20 374 l 1
- 20 635 l 2
- 20 643 28 650 36 650 c 2
- 594 650 l 2
-176 495 m 0
- 172 495 168 493 165 490 c 2
- 129 455 l 2
- 123 449 123 439 129 433 c 2
- 255 307 l 1
- 129 181 l 2
- 123 175 123 166 129 160 c 2
- 165 124 l 2
- 171 118 180 118 186 124 c 2
- 312 250 l 1
- 437 124 l 2
- 443 118 454 118 460 124 c 2
- 496 160 l 2
- 502 166 502 175 496 181 c 2
- 370 307 l 1
- 496 433 l 2
- 502 439 502 449 496 455 c 2
- 460 490 l 2
- 457 493 453 495 449 495 c 0
- 445 495 440 493 437 490 c 2
- 312 365 l 1
- 186 490 l 2
- 183 493 180 495 176 495 c 0
-EndSplineSet
-EndChar
-
-StartChar: arrowleft
-Encoding: 8592 8592 18
-Width: 1024
-Flags: W
-HStem: 225 164<445 1004>
-LayerCount: 2
-Fore
-SplineSet
-430 650 m 0
- 438 650 445 643 445 635 c 2
- 445 404 l 2
- 445 396 453 389 461 389 c 2
- 988 389 l 2
- 996 389 1004 382 1004 374 c 2
- 1004 241 l 2
- 1004 233 996 225 988 225 c 2
- 461 225 l 2
- 453 225 445 218 445 210 c 2
- 445 -20 l 2
- 445 -33 430 -40 420 -32 c 2
- 26 295 l 2
- 19 301 19 313 26 319 c 2
- 420 646 l 2
- 423 648 426 650 430 650 c 0
-EndSplineSet
-EndChar
-
-StartChar: arrowup
-Encoding: 8593 8593 19
-Width: 1024
-Flags: W
-VStem: 430 164<-184 374>
-LayerCount: 2
-Fore
-SplineSet
-855 389 m 0
- 855 381 848 374 840 374 c 2
- 609 374 l 2
- 601 374 594 366 594 358 c 2
- 594 -169 l 2
- 594 -177 587 -184 579 -184 c 2
- 445 -184 l 2
- 437 -184 430 -177 430 -169 c 2
- 430 358 l 2
- 430 366 423 374 415 374 c 2
- 184 374 l 2
- 171 374 165 389 173 399 c 2
- 500 793 l 2
- 506 800 518 800 524 793 c 2
- 851 399 l 2
- 853 396 855 393 855 389 c 0
-EndSplineSet
-EndChar
-
-StartChar: arrowright
-Encoding: 8594 8594 20
-Width: 1024
-Flags: W
-HStem: 225 164<20 579>
-LayerCount: 2
-Fore
-SplineSet
-594 650 m 0
- 598 650 601 648 604 646 c 2
- 997 319 l 2
- 1004 313 1004 301 997 295 c 2
- 604 -32 l 2
- 594 -40 579 -33 579 -20 c 2
- 579 210 l 2
- 579 218 571 225 563 225 c 2
- 36 225 l 2
- 28 225 20 233 20 241 c 2
- 20 374 l 2
- 20 382 28 389 36 389 c 2
- 563 389 l 2
- 571 389 579 396 579 404 c 2
- 579 635 l 2
- 579 643 586 650 594 650 c 0
-EndSplineSet
-EndChar
-
-StartChar: arrowdown
-Encoding: 8595 8595 21
-Width: 1024
-Flags: W
-VStem: 430 164<241 799>
-LayerCount: 2
-Fore
-SplineSet
-169 225 m 0
- 169 233 176 241 184 241 c 2
- 415 241 l 2
- 423 241 430 248 430 256 c 2
- 430 783 l 2
- 430 791 437 799 445 799 c 2
- 579 799 l 2
- 587 799 594 791 594 783 c 2
- 594 256 l 2
- 594 248 601 241 609 241 c 2
- 840 241 l 2
- 853 241 859 225 851 215 c 2
- 524 -178 l 2
- 518 -185 506 -185 500 -178 c 2
- 172 215 l 2
- 170 218 169 221 169 225 c 0
-EndSplineSet
-EndChar
-
-StartChar: uni2B82
-Encoding: 11138 11138 22
-Width: 1024
-Flags: W
-HStem: 85 164<364 620 854 1004> 366 163<20 660>
-LayerCount: 2
-Fore
-SplineSet
-676 750 m 0
- 680 750 683 748 686 746 c 2
- 997 460 l 2
- 1004 454 1004 442 997 436 c 2
- 686 150 l 2
- 676 142 660 149 660 162 c 2
- 660 350 l 2
- 660 358 653 366 645 366 c 2
- 36 366 l 2
- 28 366 20 373 20 381 c 2
- 20 514 l 2
- 20 522 28 529 36 529 c 2
- 645 529 l 2
- 653 529 660 537 660 545 c 2
- 660 734 l 2
- 660 742 668 750 676 750 c 0
-185 325 m 1
- 244 325 305 325 364 325 c 1
- 364 264 l 2
- 364 256 371 249 379 249 c 2
- 620 249 l 1
- 620 217 620 184 621 152 c 0
- 625 124 652 103 680 105 c 0
- 693 106 706 113 716 122 c 0
- 736 140 756 159 776 177 c 0
- 802 201 828 225 854 249 c 1
- 988 249 l 2
- 996 249 1004 241 1004 233 c 2
- 1004 100 l 2
- 1004 92 996 85 988 85 c 2
- 379 85 l 2
- 371 85 364 78 364 70 c 2
- 364 -120 l 2
- 364 -133 348 -140 338 -132 c 2
- 26 155 l 2
- 19 161 19 172 26 178 c 2
- 185 325 l 1
-EndSplineSet
-EndChar
-
-StartChar: uni2BA0
-Encoding: 11168 11168 23
-Width: 1024
-HStem: 225 164<445 840>
-VStem: 840 164<389 799>
-LayerCount: 2
-Fore
-SplineSet
-855 799 m 2
- 988 799 l 2
- 996 799 1004 791 1004 783 c 2
- 1004 240 l 2
- 1004 232 996 225 988 225 c 2
- 461 225 l 2
- 453 225 445 218 445 210 c 2
- 445 -20 l 2
- 445 -33 430 -40 420 -32 c 2
- 26 295 l 2
- 18 301 18 313 26 319 c 2
- 420 646 l 2
- 423 648 426 650 430 650 c 0
- 438 650 445 642 445 634 c 2
- 445 404 l 2
- 445 396 453 389 461 389 c 2
- 824 389 l 2
- 832 389 840 396 840 404 c 2
- 840 783 l 2
- 840 791 847 799 855 799 c 2
-EndSplineSet
-EndChar
-
-StartChar: uni2BC7
-Encoding: 11207 11207 24
-Width: 1024
-LayerCount: 2
-Fore
-SplineSet
-793 -34 m 2
- 215 294 l 2
- 210 297 208 301 208 307 c 0
- 208 313 210 317 215 320 c 2
- 793 648 l 2
- 798 651 804 651 809 648 c 0
- 814 645 816 639 816 634 c 2
- 816 -20 l 2
- 816 -25 814 -31 809 -34 c 0
- 804 -37 798 -37 793 -34 c 2
-EndSplineSet
-EndChar
-EndChars
-EndSplineFont
diff --git a/icon.go b/icon.go
new file mode 100644
index 0000000..2706811
--- /dev/null
+++ b/icon.go
@@ -0,0 +1,3 @@
+package main
+
+//go:generate rsrc -ico desktop/remote-touchpad.ico -o icon.syso
diff --git a/icon_windows.go b/icon_windows.go
deleted file mode 100644
index e814d01..0000000
--- a/icon_windows.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package main
-
-//go:generate rsrc -ico desktop/remote-touchpad.ico -o icon_windows.syso
diff --git a/icon_windows.syso b/icon_windows.syso
index 070a200..ac271e9 100644
Binary files a/icon_windows.syso and b/icon_windows.syso differ
diff --git a/inputcontrol/controller.go b/inputcontrol/controller.go
deleted file mode 100644
index 111e937..0000000
--- a/inputcontrol/controller.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import "sort"
-
-type (
- PointerButton int
- Key int
-)
-
-const (
- PointerButtonLeft PointerButton = iota
- PointerButtonRight
- PointerButtonMiddle
- PointerButtonLimit
-)
-
-const (
- KeyVolumeMute Key = iota
- KeyVolumeDown
- KeyVolumeUp
- KeyMediaPlayPause
- KeyMediaPrevTrack
- KeyMediaNextTrack
- KeyBrowserBack
- KeyBrowserForward
- KeySuper
- KeyLeft
- KeyRight
- KeyUp
- KeyDown
- KeyHome
- KeyEnd
- KeyBackSpace
- KeyDelete
- KeyReturn
- KeyLimit
-)
-
-type ControllerInfo struct {
- Name string
- Init func() (Controller, error)
-
- priority int
-}
-
-var Controllers []ControllerInfo
-
-func RegisterController(name string, init func() (Controller, error), priority int) {
- Controllers = append(Controllers, ControllerInfo{name, init, priority})
- sort.SliceStable(Controllers, func(i, j int) bool {
- return Controllers[i].priority < Controllers[j].priority
- })
-}
-
-type UnsupportedPlatformError struct {
- Err error
-}
-
-func (e *UnsupportedPlatformError) Error() string {
- return e.Err.Error()
-}
-
-func (e *UnsupportedPlatformError) Unwrap() error {
- return e.Err
-}
-
-type Controller interface {
- Close() error
- KeyboardText(text string) error
- KeyboardKey(key Key) error
- PointerButton(button PointerButton, press bool) error
- PointerMove(deltaX, deltaY int) error
- PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error
-}
diff --git a/inputcontrol/controller_null.go b/inputcontrol/controller_null.go
deleted file mode 100644
index 6188cce..0000000
--- a/inputcontrol/controller_null.go
+++ /dev/null
@@ -1,66 +0,0 @@
-//go:build null
-
-/*
- * Copyright (c) 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import (
- "log"
-)
-
-type nullController struct{}
-
-func init() {
- RegisterController("null", InitNullController, 1000)
-}
-
-func InitNullController() (Controller, error) {
- return &nullController{}, nil
-}
-
-func (p *nullController) Close() error {
- return nil
-}
-
-func (p *nullController) KeyboardText(text string) error {
- log.Printf("KeyboardText(text: %#v)", text)
- return nil
-}
-
-func (p *nullController) KeyboardKey(key Key) error {
- log.Printf("KeyboardKey(key: %#v)", key)
- return nil
-}
-
-func (p *nullController) PointerButton(button PointerButton, press bool) error {
- log.Printf("PointerButton(button: %#v, press: %#v)", button, press)
- return nil
-}
-
-func (p *nullController) PointerMove(deltaX, deltaY int) error {
- log.Printf("PointerMove(deltaX: %#v, deltaY: %#v)", deltaX, deltaY)
- return nil
-}
-
-func (p *nullController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- log.Printf("PointerScroll(deltaHorizontal: %#v, deltaVertical: %#v, finish: %#v)",
- deltaHorizontal, deltaVertical, finish)
- return nil
-}
diff --git a/inputcontrol/controller_portal.go b/inputcontrol/controller_portal.go
deleted file mode 100644
index 99ecf8b..0000000
--- a/inputcontrol/controller_portal.go
+++ /dev/null
@@ -1,400 +0,0 @@
-//go:build portal
-
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/hkdf"
- "crypto/rand"
- "crypto/sha256"
- "errors"
- "fmt"
- "io"
- "log"
- "os"
- "path/filepath"
- "slices"
-
- "github.com/godbus/dbus/v5"
-)
-
-const (
- deviceKeyboard uint32 = 1
- devicePointer uint32 = 2
-
- btnReleased uint32 = 0
- btnPressed uint32 = 1
-
- untilRevoked uint32 = 2
-
- // linux/input-event-codes.h
- btnLeft int32 = 0x110
- btnRight int32 = 0x111
- btnMiddle int32 = 0x112
-)
-
-type portalController struct {
- bus *dbus.Conn
- portalDesktop dbus.BusObject
- sessionHandle dbus.ObjectPath
-}
-
-func init() {
- RegisterController("RemoteDesktop portal", InitPortalController, 1)
-}
-
-func InitPortalController() (Controller, error) {
- bus, err := dbus.SessionBusPrivate()
- if err != nil {
- return nil, &UnsupportedPlatformError{err}
- }
- cleanupBus := true
- defer func() {
- if cleanupBus {
- bus.Close()
- }
- }()
- err = bus.Auth(nil)
- if err != nil {
- return nil, &UnsupportedPlatformError{err}
- }
- err = bus.Hello()
- if err != nil {
- return nil, &UnsupportedPlatformError{err}
- }
- portalDesktop := bus.Object("org.freedesktop.portal.Desktop",
- "/org/freedesktop/portal/desktop")
- remoteDesktopVersionV, err := portalDesktop.GetProperty(
- "org.freedesktop.portal.RemoteDesktop.version")
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("getting 'version' failed: %w", err),
- }
- }
- remoteDesktopVersion, ok := remoteDesktopVersionV.Value().(uint32)
- if !ok {
- return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'version' type"),
- }
- }
- restoreTokenStore, err := func() (*secretStore, error) {
- if remoteDesktopVersion < 2 {
- return nil, nil
- }
- cacheDirectory, err := os.UserCacheDir()
- if err != nil {
- return nil, err
- }
- if err := os.MkdirAll(cacheDirectory, 0o700); err != nil {
- return nil, err
- }
- secret, err := retrieveSecret(bus)
- if err != nil {
- return nil, err
- }
- return newSecretStore(secret,
- filepath.Join(cacheDirectory, "remote-touchpad.portal-restore-token.bin"))
- }()
- if err != nil {
- log.Printf("Skipping restore token: %v", err)
- }
- availableDeviceTypesV, err := portalDesktop.GetProperty(
- "org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("getting 'AvailableDeviceTypes' failed: %w", err),
- }
- }
- availableDeviceTypes, ok := availableDeviceTypesV.Value().(uint32)
- if !ok {
- return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'AvailableDeviceTypes' return type"),
- }
- }
- if availableDeviceTypes&deviceKeyboard == 0 ||
- availableDeviceTypes&devicePointer == 0 {
- return nil, &UnsupportedPlatformError{
- errors.New("keyboard or pointer source type not supported"),
- }
- }
- createSessionResults, err := checkResponse(getResponse(bus, portalDesktop,
- "org.freedesktop.portal.RemoteDesktop.CreateSession", 0,
- map[string]dbus.Variant{"session_handle_token": dbus.MakeVariant("t")},
- ))
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'CreateSession' failed: %w", err),
- }
- }
- sessionHandleString, ok := createSessionResults["session_handle"].Value().(string)
- if !ok {
- return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'session_handle' type in 'CreateSession' return value"),
- }
- }
- sessionHandle := dbus.ObjectPath(sessionHandleString)
- selectDevicesOptions := map[string]dbus.Variant{
- "types": dbus.MakeVariant(deviceKeyboard | devicePointer),
- }
- if restoreTokenStore != nil {
- if restoreToken, err := restoreTokenStore.Load(); err != nil {
- if !errors.Is(err, os.ErrNotExist) {
- log.Printf("Failed to load restore token: %v", err)
- }
- } else if len(restoreToken) > 0 {
- selectDevicesOptions["restore_token"] = dbus.MakeVariant(string(restoreToken))
- }
- selectDevicesOptions["persist_mode"] = dbus.MakeVariant(untilRevoked)
- }
- _, err = checkResponse(getResponse(bus, portalDesktop,
- "org.freedesktop.portal.RemoteDesktop.SelectDevices", 0,
- sessionHandle, selectDevicesOptions,
- ))
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'SelectDevices' failed: %w", err),
- }
- }
- startResponseStatus, startResults, err := getResponse(bus, portalDesktop,
- "org.freedesktop.portal.RemoteDesktop.Start", 0,
- sessionHandle, "", map[string]dbus.Variant{},
- )
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'Start' failed: %w", err),
- }
- }
- if startResponseStatus != 0 {
- return nil, errors.New("keyboard or pointer access denied")
- }
- if restoreToken, _ := startResults["restore_token"].Value().(string); restoreTokenStore != nil {
- if err := restoreTokenStore.Store([]byte(restoreToken)); err != nil {
- log.Printf("Failed to store restore token: %v", err)
- }
- }
- devices, ok := startResults["devices"].Value().(uint32)
- if !ok {
- return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'devices' type in 'Start' return value"),
- }
- }
- if devices&deviceKeyboard == 0 || devices&devicePointer == 0 {
- return nil, errors.New("keyboard or pointer access denied")
- }
- cleanupBus = false
- return &portalController{
- bus: bus, portalDesktop: portalDesktop,
- sessionHandle: sessionHandle,
- }, nil
-}
-
-func retrieveSecret(bus *dbus.Conn) ([]byte, error) {
- portalDesktop := bus.Object("org.freedesktop.portal.Desktop",
- "/org/freedesktop/portal/desktop")
- secretReader, secretWriter, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- defer secretReader.Close()
- defer secretWriter.Close()
- if _, err := checkResponse(getResponse(bus, portalDesktop,
- "org.freedesktop.portal.Secret.RetrieveSecret", 0,
- dbus.UnixFD(secretWriter.Fd()), map[string]dbus.Variant{},
- )); err != nil {
- return nil, fmt.Errorf("calling 'RetrieveSecret' failed: %w", err)
- }
- if err := secretWriter.Close(); err != nil {
- return nil, err
- }
- secret, err := io.ReadAll(secretReader)
- if err != nil {
- return nil, err
- }
- if len(secret) < 16 {
- return nil, fmt.Errorf("'RetrieveSecret' returned too few bytes (%d)", len(secret))
- }
- return secret, err
-}
-
-type secretStore struct {
- aesgcm cipher.AEAD
- filename string
-}
-
-func newSecretStore(key []byte, filename string) (*secretStore, error) {
- derivedKey, err := hkdf.Key(sha256.New, key, nil, "", 32)
- if err != nil {
- return nil, err
- }
- block, err := aes.NewCipher(derivedKey)
- if err != nil {
- return nil, err
- }
- aesgcm, err := cipher.NewGCM(block)
- if err != nil {
- return nil, err
- }
- return &secretStore{
- aesgcm: aesgcm,
- filename: filename,
- }, nil
-}
-
-func (s *secretStore) Load() ([]byte, error) {
- data, err := os.ReadFile(s.filename)
- if err != nil {
- return nil, err
- }
- if len(data) < s.aesgcm.NonceSize() {
- return nil, errors.New("invalid ciphertext")
- }
- nonce := data[:s.aesgcm.NonceSize()]
- ciphertext := data[len(nonce):]
- return s.aesgcm.Open(nil, nonce, ciphertext, nil)
-}
-
-func (s *secretStore) Store(data []byte) error {
- nonce := make([]byte, s.aesgcm.NonceSize())
- if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
- return err
- }
- ciphertext := s.aesgcm.Seal(nil, nonce, data, nil)
- return os.WriteFile(s.filename, slices.Concat(nonce, ciphertext), 0o600)
-}
-
-func getResponse(bus *dbus.Conn, object dbus.BusObject, method string,
- flags dbus.Flags, args ...interface{},
-) (uint32, map[string]dbus.Variant, error) {
- ch := make(chan *dbus.Signal, 512)
- bus.Signal(ch)
- defer bus.RemoveSignal(ch)
- var requestPath dbus.ObjectPath
- if err := object.Call(method, flags, args...).Store(&requestPath); err != nil {
- return 0, nil, err
- }
- for {
- s := <-ch
- if s.Path == requestPath && s.Name == "org.freedesktop.portal.Request.Response" {
- if len(s.Body) != 2 {
- return 0, nil, fmt.Errorf("unexpected 'Response' return length (%d)", len(s.Body))
- }
- responseStatus, ok := s.Body[0].(uint32)
- if !ok {
- return 0, nil, errors.New("unexpected 'Response' return type")
- }
- results, ok := s.Body[1].(map[string]dbus.Variant)
- if !ok {
- return 0, nil, errors.New("unexpected 'Response' return type")
- }
- return responseStatus, results, nil
- }
- }
-}
-
-func checkResponse(responseStatus uint32, results map[string]dbus.Variant, err error) (map[string]dbus.Variant, error) {
- if err == nil && responseStatus != 0 {
- err = fmt.Errorf("unexpected 'Response' status (%d)", responseStatus)
- }
- return results, err
-}
-
-func (p *portalController) Close() error {
- return p.bus.Close()
-}
-
-func (p *portalController) keyboardKeys(keys []Keysym) error {
- for _, keysym := range keys {
- for _, state := range [...]uint32{btnPressed, btnReleased} {
- if err := p.portalDesktop.Call(
- "org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0,
- p.sessionHandle, map[string]dbus.Variant{}, keysym, state,
- ).Store(); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-func (p *portalController) KeyboardText(text string) error {
- keys := make([]Keysym, 0, len(text))
- for _, runeValue := range text {
- keysym, err := RuneToKeysym(runeValue)
- if err != nil {
- return err
- }
- keys = append(keys, keysym)
- }
- return p.keyboardKeys(keys)
-}
-
-func (p *portalController) KeyboardKey(key Key) error {
- keysym, err := KeyToKeysym(key)
- if err != nil {
- return err
- }
- keys := [...]Keysym{keysym}
- return p.keyboardKeys(keys[:])
-}
-
-func (p *portalController) PointerButton(button PointerButton, press bool) error {
- var btn int32
- switch button {
- case PointerButtonLeft:
- btn = btnLeft
- case PointerButtonMiddle:
- btn = btnMiddle
- case PointerButtonRight:
- btn = btnRight
- default:
- return fmt.Errorf("unsupported pointer button: %#v", button)
- }
- state := btnReleased
- if press {
- state = btnPressed
- }
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerButton", 0,
- p.sessionHandle, map[string]dbus.Variant{}, btn, state,
- ).Store(); err != nil {
- return err
- }
- return nil
-}
-
-func (p *portalController) PointerMove(deltaX, deltaY int) error {
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerMotion", 0,
- p.sessionHandle, map[string]dbus.Variant{}, float64(deltaX), float64(deltaY),
- ).Store(); err != nil {
- return err
- }
- return nil
-}
-
-func (p *portalController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerAxis", 0,
- p.sessionHandle, map[string]dbus.Variant{"finish": dbus.MakeVariant(finish)}, float64(deltaHorizontal), float64(deltaVertical),
- ).Store(); err != nil {
- return err
- }
- return nil
-}
diff --git a/inputcontrol/controller_uinput.go b/inputcontrol/controller_uinput.go
deleted file mode 100644
index 2dd9bab..0000000
--- a/inputcontrol/controller_uinput.go
+++ /dev/null
@@ -1,200 +0,0 @@
-//go:build uinput
-
-/*
- * Copyright (c) 2023 De_Coder github.com/ps100000
- * Copyright (c) 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import (
- "errors"
- "fmt"
- "log"
- "os"
- "time"
-
- "github.com/bendahl/uinput"
-)
-
-const shiftKeysDelay time.Duration = 50 * time.Millisecond
-
-type uinputController struct {
- keymap *Keymap
- keyboard uinput.Keyboard
- mouse uinput.Mouse
-}
-
-func init() {
- RegisterController("uinput", InitUinputController, 10)
-}
-
-func InitUinputController() (Controller, error) {
- keymapName, keymapSet := os.LookupEnv("REMOTE_TOUCHPAD_UINPUT_KEYMAP")
- if !keymapSet {
- keymapName = "defkeymap"
- }
- keymap, err := LoadKeymap(keymapName)
- if err != nil {
- return nil, err
- }
- keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("remote-touchpad-keyboard"))
- if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
- err = &UnsupportedPlatformError{err}
- }
- if err != nil {
- return nil, err
- }
- mouse, err := uinput.CreateMouse("/dev/uinput", []byte("remote-touchpad-mouse"))
- if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
- err = &UnsupportedPlatformError{err}
- }
- if err != nil {
- keyboard.Close()
- return nil, err
- }
- if !keymapSet {
- log.Print("Hint: Set the keyboard mapping with the REMOTE_TOUCHPAD_UINPUT_KEYMAP environment variable")
- }
- return &uinputController{keymap, keyboard, mouse}, nil
-}
-
-func (p *uinputController) Close() error {
- return errors.Join(p.keyboard.Close(), p.mouse.Close())
-}
-
-func (p *uinputController) KeyboardText(text string) error {
- var activeShiftKeys KeyCombo
- updateShiftKeys := func(keyCombo KeyCombo) error {
- if activeShiftKeys.ShiftKeys == keyCombo.ShiftKeys {
- activeShiftKeys.Key = keyCombo.Key
- return nil
- }
- if activeShiftKeys.Key != 0 {
- time.Sleep(shiftKeysDelay)
- activeShiftKeys.Key = 0
- }
- for i := range keyCombo.ShiftKeys {
- if activeShiftKeys.ShiftKeys[i] != keyCombo.ShiftKeys[i] {
- if activeShiftKeys.ShiftKeys[i] != 0 {
- if err := p.keyboard.KeyUp(activeShiftKeys.ShiftKeys[i]); err != nil {
- return err
- }
- activeShiftKeys.ShiftKeys[i] = 0
- }
- if keyCombo.ShiftKeys[i] != 0 {
- if err := p.keyboard.KeyDown(keyCombo.ShiftKeys[i]); err != nil {
- return err
- }
- activeShiftKeys.ShiftKeys[i] = keyCombo.ShiftKeys[i]
- }
- }
- }
- if keyCombo.Key != 0 {
- activeShiftKeys.Key = keyCombo.Key
- time.Sleep(shiftKeysDelay)
- }
- return nil
- }
- defer updateShiftKeys(KeyCombo{})
- for _, runeValue := range text {
- keyCombo, found := p.keymap.Get(runeValue)
- if !found {
- return fmt.Errorf("unsupported rune: %q", runeValue)
- }
- if err := updateShiftKeys(keyCombo); err != nil {
- return err
- }
- if err := p.keyboard.KeyPress(keyCombo.Key); err != nil {
- return err
- }
- }
- return updateShiftKeys(KeyCombo{Key: 1})
-}
-
-func (p *uinputController) KeyboardKey(key Key) error {
- var uinputKey int
- switch key {
- case KeyBackSpace:
- uinputKey = uinput.KeyBackspace
- case KeyReturn:
- uinputKey = uinput.KeyEnter
- case KeyDelete:
- uinputKey = uinput.KeyDelete
- case KeyHome:
- uinputKey = uinput.KeyHome
- case KeyLeft:
- uinputKey = uinput.KeyLeft
- case KeyUp:
- uinputKey = uinput.KeyUp
- case KeyRight:
- uinputKey = uinput.KeyRight
- case KeyDown:
- uinputKey = uinput.KeyDown
- case KeyEnd:
- uinputKey = uinput.KeyEnd
- case KeySuper:
- uinputKey = uinput.KeyLeftmeta
- case KeyVolumeMute:
- uinputKey = uinput.KeyMute
- case KeyVolumeDown:
- uinputKey = uinput.KeyVolumedown
- case KeyVolumeUp:
- uinputKey = uinput.KeyVolumeup
- case KeyMediaPlayPause:
- uinputKey = uinput.KeyPlaypause
- case KeyMediaPrevTrack:
- uinputKey = uinput.KeyPrevioussong
- case KeyMediaNextTrack:
- uinputKey = uinput.KeyNextsong
- case KeyBrowserBack:
- uinputKey = uinput.KeyBack
- case KeyBrowserForward:
- uinputKey = uinput.KeyForward
- default:
- return fmt.Errorf("unsupported key: %#v", key)
- }
- return p.keyboard.KeyPress(uinputKey)
-}
-
-func (p *uinputController) PointerButton(button PointerButton, press bool) error {
- switch {
- case button == PointerButtonLeft && press:
- return p.mouse.LeftPress()
- case button == PointerButtonLeft && !press:
- return p.mouse.LeftRelease()
- case button == PointerButtonRight && press:
- return p.mouse.RightPress()
- case button == PointerButtonRight && !press:
- return p.mouse.RightRelease()
- case button == PointerButtonMiddle && press:
- return p.mouse.MiddlePress()
- case button == PointerButtonMiddle && !press:
- return p.mouse.MiddleRelease()
- default:
- return fmt.Errorf("unsupported pointer button: %#v", button)
- }
-}
-
-func (p *uinputController) PointerMove(deltaX, deltaY int) error {
- return p.mouse.Move(int32(deltaX), int32(deltaY))
-}
-
-func (p *uinputController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- return errors.Join(p.mouse.Wheel(false, int32(-deltaVertical)), p.mouse.Wheel(true, int32(deltaHorizontal)))
-}
diff --git a/inputcontrol/controller_windows.go b/inputcontrol/controller_windows.go
deleted file mode 100644
index 58b5e9a..0000000
--- a/inputcontrol/controller_windows.go
+++ /dev/null
@@ -1,244 +0,0 @@
-//go:build windows
-
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import (
- "fmt"
- "syscall"
- "unsafe"
-)
-
-const (
- inputMouse uintptr = 0x0
- inputKeyboard uintptr = 0x1
-
- keyeventfKeyup uint32 = 0x2
- keyeventfUnicode uint32 = 0x4
-
- vkBack uint16 = 0x8
- vkReturn uint16 = 0xD
- vkEnd uint16 = 0x23
- vkHome uint16 = 0x24
- vkLeft uint16 = 0x25
- vkUp uint16 = 0x26
- vkRight uint16 = 0x27
- vkDown uint16 = 0x28
- vkDelete uint16 = 0x2E
- vkLwin uint16 = 0x5B
- vkBrowserBack uint16 = 0xA6
- vkBrowserForward uint16 = 0xA7
- vkVolumeMute uint16 = 0xAD
- vkVolumeDown uint16 = 0xAE
- vkVolumeUp uint16 = 0xAF
- vkMediaNextTrack uint16 = 0xB0
- vkMediaPrevTrack uint16 = 0xB1
- vkMediaPlayPause uint16 = 0xB3
-
- mouseeventfMove uint32 = 0x1
- mouseeventfLeftdown uint32 = 0x2
- mouseeventfLeftup uint32 = 0x4
- mouseeventfRightdown uint32 = 0x8
- mouseeventfRightup uint32 = 0x10
- mouseeventfMiddledown uint32 = 0x20
- mouseeventfMiddleup uint32 = 0x40
- mouseeventfWheel uint32 = 0x800
- mouseeventfHwheel uint32 = 0x1000
-
- scrollMult int = 6
-)
-
-var (
- user32DLL = syscall.NewLazyDLL("user32.dll")
- sendInputProc = user32DLL.NewProc("SendInput")
-)
-
-type mouseInput struct {
- typ uintptr // HACK: padded uint32
-
- dx, dy int32
- mouseData, dwFlags, time uint32
- dwExtraInfo uintptr
-}
-
-type keybdInput struct {
- typ uintptr // HACK: padded uint32
-
- wVk, wScan uint16
- dwFlags, time uint32
- dwExtraInfo uintptr
-
- padding [8]byte
-}
-
-type windowsController struct{}
-
-func init() {
- RegisterController("Windows", InitWindowsController, 0)
-}
-
-func InitWindowsController() (Controller, error) {
- p := &windowsController{}
- if err := sendInputProc.Find(); err != nil {
- return nil, &UnsupportedPlatformError{err}
- }
- return p, nil
-}
-
-func (p *windowsController) Close() error {
- return nil
-}
-
-func (p *windowsController) sendInput(inputs []keybdInput) error {
- if len(inputs) == 0 {
- return nil
- }
- if sent, _, err := sendInputProc.Call(uintptr(len(inputs)),
- uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0])); int(sent) != len(inputs) {
- return err
- }
- return nil
-}
-
-func (p *windowsController) KeyboardText(text string) error {
- inputs := make([]keybdInput, 0, len(text)*2)
- for _, runeValue := range text {
- in := keybdInput{typ: inputKeyboard, wScan: uint16(runeValue), dwFlags: keyeventfUnicode}
- inputs = append(inputs, in)
- in.dwFlags |= keyeventfKeyup
- inputs = append(inputs, in)
- }
- if len(inputs) == 0 {
- return nil
- }
- return p.sendInput(inputs)
-}
-
-func (p *windowsController) KeyboardKey(key Key) error {
- input := keybdInput{typ: inputKeyboard}
- switch key {
- case KeyBackSpace:
- input.wVk = vkBack
- case KeyReturn:
- input.wVk = vkReturn
- case KeyEnd:
- input.wVk = vkEnd
- case KeyHome:
- input.wVk = vkHome
- case KeyLeft:
- input.wVk = vkLeft
- case KeyUp:
- input.wVk = vkUp
- case KeyRight:
- input.wVk = vkRight
- case KeyDown:
- input.wVk = vkDown
- case KeyDelete:
- input.wVk = vkDelete
- case KeySuper:
- input.wVk = vkLwin
- case KeyBrowserBack:
- input.wVk = vkBrowserBack
- case KeyBrowserForward:
- input.wVk = vkBrowserForward
- case KeyVolumeMute:
- input.wVk = vkVolumeMute
- case KeyVolumeDown:
- input.wVk = vkVolumeDown
- case KeyVolumeUp:
- input.wVk = vkVolumeUp
- case KeyMediaNextTrack:
- input.wVk = vkMediaNextTrack
- case KeyMediaPrevTrack:
- input.wVk = vkMediaPrevTrack
- case KeyMediaPlayPause:
- input.wVk = vkMediaPlayPause
- default:
- return fmt.Errorf("key not mapped to virtual-key code: %#v", key)
- }
- inputs := [...]keybdInput{input, input}
- inputs[1].dwFlags |= keyeventfKeyup
- return p.sendInput(inputs[:])
-}
-
-func (p *windowsController) PointerButton(button PointerButton, press bool) error {
- input := mouseInput{typ: inputMouse}
- if button == PointerButtonLeft && press {
- input.dwFlags = mouseeventfLeftdown
- } else if button == PointerButtonLeft {
- input.dwFlags = mouseeventfLeftup
- } else if button == PointerButtonMiddle && press {
- input.dwFlags = mouseeventfMiddledown
- } else if button == PointerButtonMiddle {
- input.dwFlags = mouseeventfMiddleup
- } else if button == PointerButtonRight && press {
- input.dwFlags = mouseeventfRightdown
- } else if button == PointerButtonRight {
- input.dwFlags = mouseeventfRightup
- } else {
- return fmt.Errorf("unsupported pointer button: %#v", button)
- }
- if sent, _, err := sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)),
- unsafe.Sizeof(input)); int(sent) != 1 {
- return err
- }
- return nil
-}
-
-func (p *windowsController) PointerMove(deltaX, deltaY int) error {
- input := mouseInput{
- typ: inputMouse,
- dx: int32(deltaX),
- dy: int32(deltaY),
- dwFlags: mouseeventfMove,
- }
- if sent, _, err := sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)),
- unsafe.Sizeof(input)); int(sent) != 1 {
- return err
- }
- return nil
-}
-
-func (p *windowsController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- inputs := make([]mouseInput, 0, 2)
- if deltaHorizontal != 0 {
- inputs = append(inputs, mouseInput{
- typ: inputMouse,
- dwFlags: mouseeventfHwheel,
- mouseData: uint32(deltaHorizontal * scrollMult),
- })
- }
- if deltaVertical != 0 {
- inputs = append(inputs, mouseInput{
- typ: inputMouse,
- dwFlags: mouseeventfWheel,
- mouseData: uint32(-deltaVertical * scrollMult),
- })
- }
- if len(inputs) == 0 {
- return nil
- }
- if sent, _, err := sendInputProc.Call(uintptr(len(inputs)),
- uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0])); int(sent) != len(inputs) {
- return err
- }
- return nil
-}
diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go
deleted file mode 100644
index f45b75b..0000000
--- a/inputcontrol/controller_x11.go
+++ /dev/null
@@ -1,397 +0,0 @@
-//go:build x11
-
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-// #cgo LDFLAGS: -lX11 -lXrandr -lXtst
-// #include
-// #include
-// #include
-// #include
-// #include
-// #include
-// Window MacroDefaultRootWindow(Display *dpy) {
-// return DefaultRootWindow(dpy);
-// }
-import "C"
-
-import (
- "errors"
- "fmt"
- "maps"
- "slices"
- "strings"
- "sync"
- "time"
- "unsafe"
-)
-
-const (
- keyboardMappingDelay time.Duration = 200 * time.Millisecond
- scrollDiv int = 20
-)
-
-var modifierIndexes = [...]int{
- C.ShiftMapIndex,
- C.Mod1MapIndex,
- C.Mod2MapIndex,
- C.Mod3MapIndex,
- C.Mod4MapIndex,
- C.Mod5MapIndex,
-}
-
-type x11Controller struct {
- display *C.Display
- lock sync.Mutex
- scrollHorizontal, scrollVertical int
-}
-
-func init() {
- RegisterController("X11", InitX11Controller, 0)
-}
-
-func InitX11Controller() (Controller, error) {
- display := C.XOpenDisplay(nil)
- if display == nil {
- return nil, &UnsupportedPlatformError{
- errors.New("failed to connect to X server"),
- }
- }
- p := &x11Controller{display: display}
- if p.xIsXwayland() {
- p.Close()
- return nil, &UnsupportedPlatformError{
- errors.New("X server is Xwayland"),
- }
- }
- return p, nil
-}
-
-func (p *x11Controller) xIsXwayland() bool {
- // Detection method from https://gitlab.freedesktop.org/xorg/app/xisxwayland/-/blob/xisxwayland-2/xisxwayland.c
- var opcode, event, error, major, minor C.int
- xwaylandExtensionName := C.CString("XWAYLAND")
- defer C.free(unsafe.Pointer(xwaylandExtensionName))
- if C.XQueryExtension(p.display, xwaylandExtensionName, &opcode, &event, &error) != 0 {
- return true
- }
- if C.XRRQueryExtension(p.display, &event, &error) == 0 ||
- C.XRRQueryVersion(p.display, &major, &minor) == 0 {
- return false
- }
- resources := C.XRRGetScreenResourcesCurrent(p.display, C.MacroDefaultRootWindow(p.display))
- if resources == nil {
- return false
- }
- defer C.XRRFreeScreenResources(resources)
- if resources.noutput < 1 {
- return false
- }
- output := C.XRRGetOutputInfo(p.display, resources, *resources.outputs)
- if output == nil {
- return false
- }
- defer C.XRRFreeOutputInfo(output)
- return strings.HasPrefix(C.GoString(output.name), "XWAYLAND")
-}
-
-func (p *x11Controller) Close() error {
- p.lock.Lock()
- defer p.lock.Unlock()
- if p.display == nil {
- return errors.New("X server connection closed")
- }
- C.XCloseDisplay(p.display)
- p.display = nil
- return nil
-}
-
-func (p *x11Controller) findEmptyKeycodeLocked() (keycode C.KeyCode, keysymsPerKeycode int, err error) {
- var minKeycodes, maxKeycodes C.int
- C.XDisplayKeycodes(p.display, &minKeycodes, &maxKeycodes)
- var keysymsPerKeycodeC C.int
- keysyms := C.XGetKeyboardMapping(
- p.display,
- C.KeyCode(minKeycodes),
- maxKeycodes-minKeycodes+1,
- &keysymsPerKeycodeC,
- )
- if keysyms == nil {
- return 0, 0, errors.New("failed to get keyboard mapping")
- }
- defer C.XFree(unsafe.Pointer(keysyms))
- keysymsPerKeycode = int(keysymsPerKeycodeC)
-keycodes:
- for keycode := int(minKeycodes); keycode <= int(maxKeycodes); keycode++ {
- for i := range keysymsPerKeycode {
- keysymsIndex := (keycode-int(minKeycodes))*keysymsPerKeycode + i
- keysym := *(*C.KeySym)(unsafe.Pointer(
- uintptr(unsafe.Pointer(keysyms)) +
- uintptr(keysymsIndex)*unsafe.Sizeof(*keysyms),
- ))
- if keysym != 0 {
- continue keycodes
- }
- }
- return C.KeyCode(keycode), keysymsPerKeycode, nil
- }
- return 0, 0, errors.New("no empty keycode found")
-}
-
-func (p *x11Controller) changeKeyMappingLocked(
- keycode C.KeyCode, keysymsPerKeycode int, keysym Keysym,
-) {
- keycodeMapping := make([]C.KeySym, keysymsPerKeycode)
- for i := range keycodeMapping {
- keycodeMapping[i] = C.KeySym(keysym)
- }
- C.XChangeKeyboardMapping(
- p.display,
- C.int(keycode),
- C.int(keysymsPerKeycode),
- (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])),
- 1,
- )
- C.XSync(p.display, C.False)
-}
-
-func (p *x11Controller) getModKeycodesLocked() map[uint]C.KeyCode {
- modKeymap := C.XGetModifierMapping(p.display)
- defer C.XFreeModifiermap(modKeymap)
- modKeycodes := make(map[uint]C.KeyCode)
- for _, modIndex := range modifierIndexes {
- for i := range int(modKeymap.max_keypermod) {
- keycodeIndex := modIndex*int(modKeymap.max_keypermod) + i
- keycode := *(*C.KeyCode)(unsafe.Pointer(
- uintptr(unsafe.Pointer(modKeymap.modifiermap)) +
- uintptr(keycodeIndex)*unsafe.Sizeof(*modKeymap.modifiermap),
- ))
- if keycode != 0 {
- modKeycodes[1< 9 {
- return errors.New("unsupported pointer button")
- }
- var pressC C.int = C.False
- if press {
- pressC = C.True
- }
- C.XTestFakeButtonEvent(p.display, C.uint(button), pressC, 0)
- C.XSync(p.display, C.False)
- return nil
-}
-
-func (p *x11Controller) PointerButton(button PointerButton, press bool) error {
- switch button {
- case PointerButtonLeft:
- return p.sendButton(1, press)
- case PointerButtonRight:
- return p.sendButton(3, press)
- case PointerButtonMiddle:
- return p.sendButton(2, press)
- default:
- return fmt.Errorf("unsupported pointer button: %#v", button)
- }
-}
-
-func (p *x11Controller) PointerMove(deltaX, deltaY int) error {
- p.lock.Lock()
- defer p.lock.Unlock()
- if p.display == nil {
- return errors.New("X server connection closed")
- }
- C.XTestFakeRelativeMotionEvent(p.display, C.int(deltaX), C.int(deltaY), 0)
- C.XSync(p.display, C.False)
- return nil
-}
-
-func (p *x11Controller) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- p.lock.Lock()
- stepsHorizontal := (p.scrollHorizontal + deltaHorizontal) / scrollDiv
- stepsVertical := (p.scrollVertical + deltaVertical) / scrollDiv
- if finish {
- p.scrollHorizontal = 0
- p.scrollVertical = 0
- } else {
- p.scrollHorizontal = (p.scrollHorizontal + deltaHorizontal) % scrollDiv
- p.scrollVertical = (p.scrollVertical + deltaVertical) % scrollDiv
- }
- p.lock.Unlock()
- var buttonHorizontal uint = 7
- if stepsHorizontal < 0 {
- buttonHorizontal = 6
- stepsHorizontal = -stepsHorizontal
- }
- for range stepsHorizontal {
- if err := p.sendButton(buttonHorizontal, true); err != nil {
- return err
- }
- if err := p.sendButton(buttonHorizontal, false); err != nil {
- return err
- }
- }
- var buttonVertical uint = 5
- if stepsVertical < 0 {
- buttonVertical = 4
- stepsVertical = -stepsVertical
- }
- for range stepsVertical {
- if err := p.sendButton(buttonVertical, true); err != nil {
- return err
- }
- if err := p.sendButton(buttonVertical, false); err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/inputcontrol/keymap.go b/inputcontrol/keymap.go
deleted file mode 100644
index 353e338..0000000
--- a/inputcontrol/keymap.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build uinput
-
-/*
- * Copyright (c) 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-import (
- "fmt"
- "os/exec"
- "strings"
- "unsafe"
-)
-
-const bkeymapFileSignature = "bkeymap"
-
-// uapi/linux/keyboard.h
-const (
- nrKeys = 128
- maxNrKeymaps = 256
- nrShift = 9
-
- ktLatin = 0
- ktShift = 7
- ktLetter = 11
-)
-
-type KeyCombo struct {
- Key int
- ShiftKeys [nrShift]int
-}
-
-type Keymap struct {
- keyComboMap map[rune]KeyCombo
-}
-
-func (keymap Keymap) Get(character rune) (keyCombo KeyCombo, found bool) {
- keyCombo, found = keymap.keyComboMap[character]
- return
-}
-
-func LoadKeymap(name string) (*Keymap, error) {
- if len(name) == 0 || strings.HasPrefix(name, "-") {
- return nil, fmt.Errorf("invalid keymap name: %#v", name)
- }
- bkeymap, err := exec.Command("loadkeys", "--bkeymap", name).Output()
- if err != nil {
- return nil, &UnsupportedPlatformError{err}
- }
- if !strings.HasPrefix(string(bkeymap), bkeymapFileSignature) {
- return nil, fmt.Errorf("invalid bkeymap: signature not found")
- }
- bkeymapHeader := bkeymap[len(bkeymapFileSignature):]
- var keymaps [][nrKeys]int
- if len(bkeymapHeader) < maxNrKeymaps {
- return nil, fmt.Errorf("invalid bkeymap: EOF")
- }
- currentBkeymap := bkeymapHeader[maxNrKeymaps:]
- for keymapIndex := 0; keymapIndex < maxNrKeymaps; keymapIndex += 1 {
- if bkeymapHeader[keymapIndex] == 0 {
- continue
- }
- if len(currentBkeymap) < nrKeys*2 {
- return nil, fmt.Errorf("invalid bkeymap: EOF")
- }
- for len(keymaps) <= keymapIndex {
- keymaps = append(keymaps, [nrKeys]int{})
- }
- for key := 0; key < nrKeys; key += 1 {
- var keysym uint16
- copy(unsafe.Slice((*byte)(unsafe.Pointer(&keysym)), 2),
- currentBkeymap[key*2:])
- keymaps[keymapIndex][key] = int(keysym)
- }
- currentBkeymap = currentBkeymap[nrKeys*2:]
- }
- keymap := Keymap{keyComboMap: make(map[rune]KeyCombo)}
- if len(keymaps) == 0 {
- return &keymap, nil
- }
- var shiftKeys [nrShift]int
- for key, keysym := range keymaps[0] {
- keyType := keysym >> 8
- keyValue := keysym & 0xff
- if keyType == ktShift && keyValue < nrShift && shiftKeys[keyValue] == 0 {
- shiftKeys[keyValue] = key
- }
- }
-keymapLoop:
- for keymapIndex := range keymaps {
- var activeShiftKeys [nrShift]int
- for i, shiftKey := range shiftKeys {
- if keymapIndex&(1<> 8
- keyValue := keysym & 0xff
- if (keyType != ktLatin && keyType != ktLetter) || keyValue == 0 {
- continue
- }
- character := rune(keyValue)
- if _, ok := keymap.keyComboMap[character]; ok {
- continue
- }
- keymap.keyComboMap[character] = KeyCombo{key, activeShiftKeys}
- }
- }
- return &keymap, nil
-}
diff --git a/inputcontrol/keysyms.generated.go b/inputcontrol/keysyms.generated.go
deleted file mode 100644
index 8734718..0000000
--- a/inputcontrol/keysyms.generated.go
+++ /dev/null
@@ -1,207 +0,0 @@
-// Code generated by keysyms.generator.go. DO NOT EDIT.
-//go:build portal || x11
-
-package inputcontrol
-
-var keysymsMap = map[rune]Keysym{
- 0x0008: 0x0000ff08,
- 0x0009: 0x0000ff09,
- 0x000a: 0x0000ff0a,
- 0x000b: 0x0000ff0b,
- 0x000d: 0x0000ff0d,
- 0x0013: 0x0000ff13,
- 0x0014: 0x0000ff14,
- 0x0015: 0x0000ff15,
- 0x001b: 0x0000ff1b,
- 0x0020: 0x00000020,
- 0x0021: 0x00000021,
- 0x0022: 0x00000022,
- 0x0023: 0x00000023,
- 0x0024: 0x00000024,
- 0x0025: 0x00000025,
- 0x0026: 0x00000026,
- 0x0027: 0x00000027,
- 0x0028: 0x00000028,
- 0x0029: 0x00000029,
- 0x002a: 0x0000002a,
- 0x002b: 0x0000002b,
- 0x002c: 0x0000002c,
- 0x002d: 0x0000002d,
- 0x002e: 0x0000002e,
- 0x002f: 0x0000002f,
- 0x0030: 0x00000030,
- 0x0031: 0x00000031,
- 0x0032: 0x00000032,
- 0x0033: 0x00000033,
- 0x0034: 0x00000034,
- 0x0035: 0x00000035,
- 0x0036: 0x00000036,
- 0x0037: 0x00000037,
- 0x0038: 0x00000038,
- 0x0039: 0x00000039,
- 0x003a: 0x0000003a,
- 0x003b: 0x0000003b,
- 0x003c: 0x0000003c,
- 0x003d: 0x0000003d,
- 0x003e: 0x0000003e,
- 0x003f: 0x0000003f,
- 0x0040: 0x00000040,
- 0x0041: 0x00000041,
- 0x0042: 0x00000042,
- 0x0043: 0x00000043,
- 0x0044: 0x00000044,
- 0x0045: 0x00000045,
- 0x0046: 0x00000046,
- 0x0047: 0x00000047,
- 0x0048: 0x00000048,
- 0x0049: 0x00000049,
- 0x004a: 0x0000004a,
- 0x004b: 0x0000004b,
- 0x004c: 0x0000004c,
- 0x004d: 0x0000004d,
- 0x004e: 0x0000004e,
- 0x004f: 0x0000004f,
- 0x0050: 0x00000050,
- 0x0051: 0x00000051,
- 0x0052: 0x00000052,
- 0x0053: 0x00000053,
- 0x0054: 0x00000054,
- 0x0055: 0x00000055,
- 0x0056: 0x00000056,
- 0x0057: 0x00000057,
- 0x0058: 0x00000058,
- 0x0059: 0x00000059,
- 0x005a: 0x0000005a,
- 0x005b: 0x0000005b,
- 0x005c: 0x0000005c,
- 0x005d: 0x0000005d,
- 0x005e: 0x0000005e,
- 0x005f: 0x0000005f,
- 0x0060: 0x00000060,
- 0x0061: 0x00000061,
- 0x0062: 0x00000062,
- 0x0063: 0x00000063,
- 0x0064: 0x00000064,
- 0x0065: 0x00000065,
- 0x0066: 0x00000066,
- 0x0067: 0x00000067,
- 0x0068: 0x00000068,
- 0x0069: 0x00000069,
- 0x006a: 0x0000006a,
- 0x006b: 0x0000006b,
- 0x006c: 0x0000006c,
- 0x006d: 0x0000006d,
- 0x006e: 0x0000006e,
- 0x006f: 0x0000006f,
- 0x0070: 0x00000070,
- 0x0071: 0x00000071,
- 0x0072: 0x00000072,
- 0x0073: 0x00000073,
- 0x0074: 0x00000074,
- 0x0075: 0x00000075,
- 0x0076: 0x00000076,
- 0x0077: 0x00000077,
- 0x0078: 0x00000078,
- 0x0079: 0x00000079,
- 0x007a: 0x0000007a,
- 0x007b: 0x0000007b,
- 0x007c: 0x0000007c,
- 0x007d: 0x0000007d,
- 0x007e: 0x0000007e,
- 0x00a0: 0x000000a0,
- 0x00a1: 0x000000a1,
- 0x00a2: 0x000000a2,
- 0x00a3: 0x000000a3,
- 0x00a4: 0x000000a4,
- 0x00a5: 0x000000a5,
- 0x00a6: 0x000000a6,
- 0x00a7: 0x000000a7,
- 0x00a8: 0x000000a8,
- 0x00a9: 0x000000a9,
- 0x00aa: 0x000000aa,
- 0x00ab: 0x000000ab,
- 0x00ac: 0x000000ac,
- 0x00ad: 0x000000ad,
- 0x00ae: 0x000000ae,
- 0x00af: 0x000000af,
- 0x00b0: 0x000000b0,
- 0x00b1: 0x000000b1,
- 0x00b2: 0x000000b2,
- 0x00b3: 0x000000b3,
- 0x00b4: 0x000000b4,
- 0x00b5: 0x000000b5,
- 0x00b6: 0x000000b6,
- 0x00b7: 0x000000b7,
- 0x00b8: 0x000000b8,
- 0x00b9: 0x000000b9,
- 0x00ba: 0x000000ba,
- 0x00bb: 0x000000bb,
- 0x00bc: 0x000000bc,
- 0x00bd: 0x000000bd,
- 0x00be: 0x000000be,
- 0x00bf: 0x000000bf,
- 0x00c0: 0x000000c0,
- 0x00c1: 0x000000c1,
- 0x00c2: 0x000000c2,
- 0x00c3: 0x000000c3,
- 0x00c4: 0x000000c4,
- 0x00c5: 0x000000c5,
- 0x00c6: 0x000000c6,
- 0x00c7: 0x000000c7,
- 0x00c8: 0x000000c8,
- 0x00c9: 0x000000c9,
- 0x00ca: 0x000000ca,
- 0x00cb: 0x000000cb,
- 0x00cc: 0x000000cc,
- 0x00cd: 0x000000cd,
- 0x00ce: 0x000000ce,
- 0x00cf: 0x000000cf,
- 0x00d0: 0x000000d0,
- 0x00d1: 0x000000d1,
- 0x00d2: 0x000000d2,
- 0x00d3: 0x000000d3,
- 0x00d4: 0x000000d4,
- 0x00d5: 0x000000d5,
- 0x00d6: 0x000000d6,
- 0x00d7: 0x000000d7,
- 0x00d8: 0x000000d8,
- 0x00d9: 0x000000d9,
- 0x00da: 0x000000da,
- 0x00db: 0x000000db,
- 0x00dc: 0x000000dc,
- 0x00dd: 0x000000dd,
- 0x00de: 0x000000de,
- 0x00df: 0x000000df,
- 0x00e0: 0x000000e0,
- 0x00e1: 0x000000e1,
- 0x00e2: 0x000000e2,
- 0x00e3: 0x000000e3,
- 0x00e4: 0x000000e4,
- 0x00e5: 0x000000e5,
- 0x00e6: 0x000000e6,
- 0x00e7: 0x000000e7,
- 0x00e8: 0x000000e8,
- 0x00e9: 0x000000e9,
- 0x00ea: 0x000000ea,
- 0x00eb: 0x000000eb,
- 0x00ec: 0x000000ec,
- 0x00ed: 0x000000ed,
- 0x00ee: 0x000000ee,
- 0x00ef: 0x000000ef,
- 0x00f0: 0x000000f0,
- 0x00f1: 0x000000f1,
- 0x00f2: 0x000000f2,
- 0x00f3: 0x000000f3,
- 0x00f4: 0x000000f4,
- 0x00f5: 0x000000f5,
- 0x00f6: 0x000000f6,
- 0x00f7: 0x000000f7,
- 0x00f8: 0x000000f8,
- 0x00f9: 0x000000f9,
- 0x00fa: 0x000000fa,
- 0x00fb: 0x000000fb,
- 0x00fc: 0x000000fc,
- 0x00fd: 0x000000fd,
- 0x00fe: 0x000000fe,
- 0x00ff: 0x000000ff,
-}
diff --git a/inputcontrol/keysyms.generator.go b/inputcontrol/keysyms.generator.go
deleted file mode 100644
index cc18417..0000000
--- a/inputcontrol/keysyms.generator.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build ignore
-
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package main
-
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "regexp"
- "sort"
- "strconv"
-)
-
-type Keysym int32
-
-const (
- keysymdefHeader string = "/usr/include/X11/keysymdef.h"
- output string = "keysyms.generated.go"
- maxMappedUnicode rune = 0xff
-)
-
-var overrideKeysyms = map[string]rune{
- "XK_BackSpace": 0x08,
- "XK_Tab": 0x09,
- "XK_Linefeed": 0x0a,
- "XK_Clear": 0x0b,
- "XK_Return": 0x0d,
- "XK_Pause": 0x13,
- "XK_Scroll_Lock": 0x14,
- "XK_Sys_Req": 0x15,
- "XK_Escape": 0x1b,
-}
-
-func main() {
- f, err := os.Open(keysymdefHeader)
- if err != nil {
- panic(err)
- }
- defer f.Close()
- reader := bufio.NewReader(f)
- re := regexp.MustCompile("^\\#define " +
- "(XK_[a-zA-Z_0-9]+)\\s+" + // keysymName
- "0x([0-9a-f]+)\\s*" + // keysym
- "(?:/\\*\\s*(?:U\\+([0-9A-F]{4,6}))?.*\\*/\\s*)?" + // keysymUnicode (optional)
- "$")
- keysymsMap := make(map[rune]Keysym)
- for {
- l, err := reader.ReadString('\n')
- if err == io.EOF {
- break
- } else if err != nil {
- panic(err)
- }
- submatches := re.FindStringSubmatch(l)
- if len(submatches) == 0 {
- continue
- }
- keysymName := submatches[1]
- keysymTemp, err := strconv.ParseInt(submatches[2], 16, 32)
- keysym := Keysym(keysymTemp)
- if err != nil {
- panic(err)
- }
- unicode, found := overrideKeysyms[keysymName]
- if !found {
- if len(submatches[3]) == 0 {
- continue
- }
- unicodeTemp, err := strconv.ParseInt(submatches[3], 16, 32)
- unicode = rune(unicodeTemp)
- if err != nil {
- panic(err)
- }
-
- }
- if unicode > maxMappedUnicode {
- continue
- }
- if _, found := keysymsMap[unicode]; found {
- continue
- }
- keysymsMap[unicode] = keysym
- }
- content := "// Code generated by keysyms.generator.go. DO NOT EDIT.\n" +
- "//go:build portal || x11\n\n" +
- "package inputcontrol\n\n" +
- "var keysymsMap = map[rune]Keysym{\n"
- keys := make([]rune, 0)
- for key := range keysymsMap {
- keys = append(keys, key)
- }
- sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
- for _, unicode := range keys {
- keysym := keysymsMap[unicode]
- content += fmt.Sprintf("\t0x%04x: 0x%08x,\n", unicode, keysym)
- }
- content += "}\n"
- o, err := os.Create(output)
- if err != nil {
- panic(err)
- }
- defer o.Close()
- if _, err := o.WriteString(content); err != nil {
- panic(err)
- }
-}
diff --git a/inputcontrol/keysyms.go b/inputcontrol/keysyms.go
deleted file mode 100644
index de183a1..0000000
--- a/inputcontrol/keysyms.go
+++ /dev/null
@@ -1,109 +0,0 @@
-//go:build portal || x11
-
-/*
- * Copyright (c) 2018 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package inputcontrol
-
-//go:generate go run keysyms.generator.go
-
-import "fmt"
-
-type Keysym int32
-
-const (
- // X11/keysymdef.h
- xkBackSpace Keysym = 0xff08
- xkReturn Keysym = 0xff0D
- xkDelete Keysym = 0xffff
- xkHome Keysym = 0xff50
- xkLeft Keysym = 0xff51
- xkUp Keysym = 0xff52
- xkRight Keysym = 0xff53
- xkDown Keysym = 0xff54
- xkEnd Keysym = 0xff57
- xkSuperL Keysym = 0xffeb
- // X11/XF86keysym.h
- xf86xkAudioLowerVolume Keysym = 0x1008ff11
- xf86xkAudioMute Keysym = 0x1008ff12
- xf86xkAudioRaiseVolume Keysym = 0x1008ff13
- xf86xkAudioPlay Keysym = 0x1008ff14
- xf86xkAudioPrev Keysym = 0x1008ff16
- xf86xkAudioNext Keysym = 0x1008ff17
- xf86xkBack Keysym = 0x1008ff26
- xf86xkForward Keysym = 0x1008ff27
-)
-
-func RuneToKeysym(runeValue rune) (Keysym, error) {
- if runeValue == '\n' {
- runeValue = '\r'
- }
- keysym, found := keysymsMap[runeValue]
- if !found {
- if runeValue < 0x100 || runeValue > 0x10ffff {
- return 0, fmt.Errorf("rune not mappend to keysym and "+
- "out of range for direct unicode mapping: %q", runeValue)
- }
- keysym = Keysym(0x01000000 + runeValue)
- }
- return keysym, nil
-}
-
-func KeyToKeysym(key Key) (Keysym, error) {
- switch key {
- case KeyBackSpace:
- return xkBackSpace, nil
- case KeyReturn:
- return xkReturn, nil
- case KeyDelete:
- return xkDelete, nil
- case KeyHome:
- return xkHome, nil
- case KeyLeft:
- return xkLeft, nil
- case KeyUp:
- return xkUp, nil
- case KeyRight:
- return xkRight, nil
- case KeyDown:
- return xkDown, nil
- case KeyEnd:
- return xkEnd, nil
- case KeySuper:
- return xkSuperL, nil
- case KeyVolumeMute:
- return xf86xkAudioMute, nil
- case KeyVolumeDown:
- return xf86xkAudioLowerVolume, nil
- case KeyVolumeUp:
- return xf86xkAudioRaiseVolume, nil
- case KeyMediaPlayPause:
- return xf86xkAudioPlay, nil
- case KeyMediaPrevTrack:
- return xf86xkAudioPrev, nil
- case KeyMediaNextTrack:
- return xf86xkAudioNext, nil
- case KeyBrowserBack:
- return xf86xkBack, nil
- case KeyBrowserForward:
- return xf86xkForward, nil
- default:
- return 0, fmt.Errorf("key not mapped to keysym: %#v", key)
- }
-}
diff --git a/main.go b/main.go
index f8db693..394d5ea 100644
--- a/main.go
+++ b/main.go
@@ -1,9 +1,9 @@
/*
- * Copyright (c) 2018-2019 Unrud
+ * Copyright (c) 2018 Unrud
*
* This file is part of Remote-Touchpad.
*
- * Remote-Touchpad is free software: you can redistribute it and/or modify
+ * Foobar 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 3 of the License, or
* (at your option) any later version.
@@ -13,8 +13,8 @@
* 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 Remote-Touchpad. If not, see .
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
*/
package main
@@ -27,19 +27,14 @@ import (
"errors"
"flag"
"fmt"
+ "golang.org/x/net/websocket"
"log"
mathrand "math/rand"
"net"
"net/http"
- "os"
"strconv"
"strings"
"time"
- "unicode/utf8"
-
- "github.com/unrud/remote-touchpad/inputcontrol"
- "github.com/unrud/remote-touchpad/terminal"
- "golang.org/x/net/websocket"
)
const (
@@ -48,71 +43,43 @@ const (
authenticationRateBurst int = 10
challengeLength int = 8
defaultBind string = ":0"
- version string = "1.5.4"
- prettyAppName string = "Remote Touchpad"
+ version string = "0.0.1"
)
-type config struct {
- UpdateRate uint `json:"updateRate"`
- ScrollSpeed float64 `json:"scrollSpeed"`
- MoveSpeed float64 `json:"moveSpeed"`
- MouseScrollSpeed float64 `json:"mouseScrollSpeed"`
- MouseMoveSpeed float64 `json:"mouseMoveSpeed"`
-}
-
-func processCommand(controller inputcontrol.Controller, command string) error {
- if len(command) == 0 {
- return errors.New("empty command")
+func processCommand(plugin Plugin, command string) error {
+ commandParts := strings.Split(command, " ")
+ if commandParts[0] == "KeyboardText" {
+ text := strings.Join(commandParts[1:], " ")
+ return plugin.KeyboardText(text)
}
- if command == "S" {
- return controller.PointerScroll(0, 0, true)
- }
- if command[0] == 't' {
- text := command[1:]
- if !utf8.ValidString(text) {
- return errors.New("invalid utf-8")
- }
- return controller.KeyboardText(text)
- }
- arguments := strings.Split(command[1:], ";")
- if command[0] == 'k' && len(arguments) != 1 ||
- command[0] != 'k' && len(arguments) != 2 {
+ if len(commandParts) != 3 {
return errors.New("wrong number of arguments")
}
- x, err := strconv.ParseInt(arguments[0], 10, 32)
+ x, err := strconv.ParseInt(commandParts[1], 10, 32)
if err != nil {
return err
}
- if command[0] == 'k' {
- if x < 0 || x >= int64(inputcontrol.KeyLimit) {
- return errors.New("unsupported key")
- }
- return controller.KeyboardKey(inputcontrol.Key(x))
- }
- y, err := strconv.ParseInt(arguments[1], 10, 32)
+ y, err := strconv.ParseInt(commandParts[2], 10, 32)
if err != nil {
return err
}
- if command[0] == 'm' {
- return controller.PointerMove(int(x), int(y))
+ if commandParts[0] == "PointerMove" {
+ return plugin.PointerMove(int(x), int(y))
}
- if command[0] == 's' {
- return controller.PointerScroll(int(x), int(y), false)
+ if commandParts[0] == "PointerScroll" {
+ return plugin.PointerScroll(int(x), int(y))
}
- if command[0] == 'S' {
- return controller.PointerScroll(int(x), int(y), true)
- }
- if command[0] == 'b' {
- if x < 0 || x >= int64(inputcontrol.PointerButtonLimit) {
- return errors.New("unsupported pointer button")
+ if commandParts[0] == "PointerButton" {
+ if x <= 0 || x > 3 {
+ return errors.New("unknown pointer button")
}
b := true
if y == 0 {
b = false
}
- return controller.PointerButton(inputcontrol.PointerButton(x), b)
+ return plugin.PointerButton(uint(x), b)
}
- return errors.New("unsupported command")
+ return errors.New("unknown command")
}
type challenge struct {
@@ -151,61 +118,46 @@ func secureRandBase64(length int) string {
}
func main() {
- terminal.SetTitle(prettyAppName)
var bind, certFile, keyFile, secret string
- var showVersion bool
- var config config
+ var showVersion, invert bool
flag.BoolVar(&showVersion, "version", false, "show program's version number and exit")
- flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]:PORT")
+ flag.StringVar(&bind, "bind", defaultBind, "bind server to [IP]:PORT")
flag.StringVar(&secret, "secret", "", "shared secret for client authentication")
flag.StringVar(&certFile, "cert", "", "file containing TLS certificate")
flag.StringVar(&keyFile, "key", "", "file containing TLS private key")
- flag.UintVar(&config.UpdateRate, "update-rate", 30, "number of updates per second")
- flag.Float64Var(&config.MoveSpeed, "move-speed", 1, "move speed multiplier")
- flag.Float64Var(&config.ScrollSpeed, "scroll-speed", 1, "scroll speed multiplier")
- flag.Float64Var(&config.MouseMoveSpeed, "mouse-move-speed", 1, "mouse move speed multiplier")
- flag.Float64Var(&config.MouseScrollSpeed, "mouse-scroll-speed", 1, "mouse scroll speed multiplier")
+ flag.BoolVar(&invert, "invert", false, "use inverse colors for QR code")
flag.Parse()
if showVersion {
fmt.Println(version)
return
}
if certFile != "" && keyFile == "" {
- log.Fatal("TLS private key file missing")
+ log.Fatal("TLS certificate missing")
}
if certFile == "" && keyFile != "" {
- log.Fatal("TLS certificate file missing")
+ log.Fatal("TLS private key missing")
}
tls := certFile != "" && keyFile != ""
if secret == "" {
secret = secureRandBase64(defaultSecretLength)
}
- if len(inputcontrol.Controllers) == 0 {
- log.Fatal("compiled without controller")
- }
- var controller inputcontrol.Controller
- var controllerName string
- var platformErrs []error
- for _, controllerInfo := range inputcontrol.Controllers {
- controllerName = controllerInfo.Name
+ var plugin Plugin
+ platformErrors := ""
+ for _, f := range Plugins {
var err error
- controller, err = controllerInfo.Init()
+ plugin, err = f()
if err == nil {
break
+ } else if _, ok := err.(UnsupportedPlatformError); ok {
+ platformErrors += err.Error() + "\n"
} else {
- var unsupportedErr *inputcontrol.UnsupportedPlatformError
- wrappedErr := fmt.Errorf("%v controller: %w", controllerName, err)
- if errors.As(err, &unsupportedErr) {
- platformErrs = append(platformErrs, wrappedErr)
- } else {
- log.Fatal(wrappedErr)
- }
+ log.Fatal(err)
}
}
- if controller == nil {
- log.Fatal(fmt.Errorf("unsupported platform:\n%w", errors.Join(platformErrs...)))
+ if plugin == nil {
+ log.Fatal("unsupported platform:\n" + platformErrors)
}
- defer controller.Close()
+ defer plugin.Close()
authenticationChallenges := make(chan challenge, authenticationRateBurst)
go authenticationChallengeGenerator(secret, authenticationChallenges)
listener, err := net.Listen("tcp", bind)
@@ -225,11 +177,11 @@ func main() {
}
}
if host == "" {
- host = findDefaultHost()
+ host = FindDefaultHost()
}
port := addr.Port
mux := http.NewServeMux()
- mux.Handle("/", http.FileServer(http.FS(webdataFS)))
+ mux.Handle("/", http.FileServer(assetFS()))
mux.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
var message string
challenge := <-authenticationChallenges
@@ -240,13 +192,11 @@ func main() {
if !challenge.verify(message) {
return
}
- websocket.JSON.Send(ws, config)
for {
if err := websocket.Message.Receive(ws, &message); err != nil {
return
}
- if err := processCommand(controller, message); err != nil {
- log.Print(fmt.Errorf("%s controller: %w", controllerName, err))
+ if err := processCommand(plugin, message); err != nil {
return
}
}
@@ -259,17 +209,16 @@ func main() {
if tls {
scheme = "https"
}
- url := fmt.Sprintf("%s://%s/#%s", scheme, domain, secret)
- fmt.Println(url)
- if qrCode, err := terminal.GenerateQRCode(url, terminal.SupportsColor(os.Stdout.Fd())); err == nil {
- fmt.Print(qrCode)
- } else {
- log.Printf("QR code error: %v", err)
- }
+ url := fmt.Sprintf("%s://%s/#%s\n", scheme, domain, secret)
+ readyMsg := "ready: " + url
+ qrCode, _ := GenerateQRCode(url, invert)
+ readyMsg += qrCode
if !tls {
- fmt.Println("▌ WARNING: TLS is not enabled ▐")
- fmt.Println("▌Don't use in an untrusted network!▐")
+ readyMsg += ("" +
+ "▌ WARNING: TLS is not enabled ▐\n" +
+ "▌Don't use in an untrusted network!▐\n")
}
+ log.Print(readyMsg)
if tls {
err = http.ServeTLS(listener, mux, certFile, keyFile)
} else {
diff --git a/plugin.go b/plugin.go
new file mode 100644
index 0000000..014f6ef
--- /dev/null
+++ b/plugin.go
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad 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 Foobar. If not, see .
+ */
+
+package main
+
+import "fmt"
+
+var Plugins [](func() (Plugin, error)) = [](func() (Plugin, error)){
+ InitX11Plugin,
+ InitWindowsPlugin,
+}
+
+type UnsupportedPlatformError struct {
+ name string
+ message string
+}
+
+func (e UnsupportedPlatformError) Error() string {
+ return fmt.Sprintf("%s plugin: %s", e.name, e.message)
+}
+
+type Plugin interface {
+ Close() error
+ KeyboardText(text string) error
+ PointerButton(button uint, press bool) error
+ PointerMove(deltaX, deltaY int) error
+ PointerScroll(stepsHorizontal, stepsVertical int) error
+}
diff --git a/plugin_windows.go b/plugin_windows.go
new file mode 100644
index 0000000..34c472f
--- /dev/null
+++ b/plugin_windows.go
@@ -0,0 +1,169 @@
+// +build windows
+
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad 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 Foobar. If not, see .
+ */
+
+package main
+
+import (
+ "errors"
+ "syscall"
+ "unsafe"
+)
+
+const (
+ inputMouse uintptr = 0x0
+ inputKeyboard uintptr = 0x1
+
+ keyeventfKeyup uint32 = 0x2
+ keyeventfUnicode uint32 = 0x4
+
+ mouseeventfMove uint32 = 0x1
+ mouseeventfLeftdown uint32 = 0x2
+ mouseeventfLeftup uint32 = 0x4
+ mouseeventfRightdown uint32 = 0x8
+ mouseeventfRightup uint32 = 0x10
+ mouseeventfMiddledown uint32 = 0x20
+ mouseeventfMiddleup uint32 = 0x40
+ mouseeventfWheel uint32 = 0x800
+ mouseeventfHwheel uint32 = 0x1000
+
+ wheelDelta int = 120
+)
+
+type mouseInput struct {
+ typ uintptr // HACK: padded uint32
+
+ dx, dy int32
+ mouseData, dwFlags, time uint32
+ dwExtraInfo uintptr
+}
+
+type keybdInput struct {
+ typ uintptr // HACK: padded uint32
+
+ wVk, wScan uint16
+ dwFlags, time uint32
+ dwExtraInfo uintptr
+
+ padding [8]byte
+}
+
+type windowsPlugin struct {
+ userDLL *syscall.LazyDLL
+ sendInputProc *syscall.LazyProc
+}
+
+func InitWindowsPlugin() (Plugin, error) {
+ p := &windowsPlugin{}
+ p.userDLL = syscall.NewLazyDLL("user32.dll")
+ p.sendInputProc = p.userDLL.NewProc("SendInput")
+ if err := p.sendInputProc.Find(); err != nil {
+ return nil, UnsupportedPlatformError{"Windows", err.Error()}
+ }
+ return p, nil
+}
+
+func (p *windowsPlugin) Close() error {
+ return nil
+}
+
+func (p *windowsPlugin) KeyboardText(text string) error {
+ runes := []rune(text)
+ if len(runes) == 0 {
+ return nil
+ }
+ inputs := make([]keybdInput, len(runes)*2)
+ for i := range inputs {
+ inputs[i].typ = inputKeyboard
+ inputs[i].wScan = uint16(runes[i/2])
+ inputs[i].dwFlags = keyeventfUnicode
+ if i%2 == 1 {
+ inputs[i].dwFlags |= keyeventfKeyup
+ }
+ }
+ if r, _, err := p.sendInputProc.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0])); int(r) != len(inputs) {
+ return err
+ }
+ return nil
+}
+
+func (p *windowsPlugin) PointerButton(button uint, press bool) error {
+ if button == 0 || button > 3 {
+ return errors.New("unsupported pointer button")
+ }
+ input := mouseInput{
+ typ: inputMouse,
+ }
+ if button == 1 && press {
+ input.dwFlags = mouseeventfLeftdown
+ } else if button == 1 {
+ input.dwFlags = mouseeventfLeftup
+ } else if button == 2 && press {
+ input.dwFlags = mouseeventfMiddledown
+ } else if button == 2 {
+ input.dwFlags = mouseeventfMiddleup
+ } else if button == 3 && press {
+ input.dwFlags = mouseeventfRightdown
+ } else if button == 3 {
+ input.dwFlags = mouseeventfRightup
+ }
+ if r, _, err := p.sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)), unsafe.Sizeof(input)); int(r) != 1 {
+ return err
+ }
+ return nil
+}
+
+func (p *windowsPlugin) PointerMove(deltaX, deltaY int) error {
+ input := mouseInput{
+ typ: inputMouse,
+ dx: int32(deltaX),
+ dy: int32(deltaY),
+ dwFlags: mouseeventfMove,
+ }
+ if r, _, err := p.sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)), unsafe.Sizeof(input)); int(r) != 1 {
+ return err
+ }
+ return nil
+}
+
+func (p *windowsPlugin) PointerScroll(stepsHorizontal, stepsVertical int) error {
+ inputs := make([]mouseInput, 0, 2)
+ if stepsHorizontal != 0 {
+ inputs = append(inputs, mouseInput{
+ typ: inputMouse,
+ dwFlags: mouseeventfHwheel,
+ mouseData: uint32(stepsHorizontal * wheelDelta),
+ })
+ }
+ if stepsVertical != 0 {
+ inputs = append(inputs, mouseInput{
+ typ: inputMouse,
+ dwFlags: mouseeventfWheel,
+ mouseData: uint32(stepsVertical * wheelDelta),
+ })
+ }
+ if len(inputs) == 0 {
+ return nil
+ }
+ if r, _, err := p.sendInputProc.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0])); int(r) != len(inputs) {
+ return err
+ }
+ return nil
+}
diff --git a/terminal/terminal.go b/plugin_windows_dummy.go
similarity index 53%
rename from terminal/terminal.go
rename to plugin_windows_dummy.go
index 8050fc6..8db1b06 100644
--- a/terminal/terminal.go
+++ b/plugin_windows_dummy.go
@@ -1,9 +1,11 @@
+// +build !windows
+
/*
- * Copyright (c) 2018 Unrud
+ * Copyright (c) 2018 Unrud
*
* This file is part of Remote-Touchpad.
*
- * Remote-Touchpad is free software: you can redistribute it and/or modify
+ * Foobar 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 3 of the License, or
* (at your option) any later version.
@@ -13,15 +15,12 @@
* 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 Remote-Touchpad. If not, see .
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
*/
-package terminal
+package main
-const (
- ForegroundBlack string = "\033[30m"
- ForegroundReset string = "\033[39m"
- BackgroundWhite string = "\033[47m"
- BackgroundReset string = "\033[49m"
-)
+func InitWindowsPlugin() (Plugin, error) {
+ return nil, UnsupportedPlatformError{"Windows", "disabled"}
+}
diff --git a/plugin_x11.go b/plugin_x11.go
new file mode 100644
index 0000000..bf520f4
--- /dev/null
+++ b/plugin_x11.go
@@ -0,0 +1,173 @@
+// +build linux
+
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad 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 Foobar. If not, see .
+ */
+
+package main
+
+// #cgo LDFLAGS: -lX11 -lXtst
+// #include
+// #include
+// #include
+import "C"
+import (
+ "errors"
+ "sync"
+ "time"
+ "unsafe"
+)
+
+const typingDelay time.Duration = 100 * time.Millisecond
+
+type x11Plugin struct {
+ display *C.Display
+ lock sync.Mutex
+}
+
+func InitX11Plugin() (Plugin, error) {
+ display := C.XOpenDisplay(nil)
+ if display == nil {
+ return nil, UnsupportedPlatformError{"X11", "failed to connect to X server"}
+ }
+ return &x11Plugin{display: display}, nil
+}
+
+func (p *x11Plugin) Close() error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ if p.display == nil {
+ return errors.New("X server connection closed")
+ }
+ C.XCloseDisplay(p.display)
+ p.display = nil
+ return nil
+}
+
+func (p *x11Plugin) KeyboardText(text string) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ if p.display == nil {
+ return errors.New("X server connection closed")
+ }
+ if text == "" {
+ return nil
+ }
+ var minKeycodes, maxKeycodes C.int
+ C.XDisplayKeycodes(p.display, &minKeycodes, &maxKeycodes)
+ var keysymsPerKeycode C.int
+ keysyms := C.XGetKeyboardMapping(p.display, C.KeyCode(minKeycodes), maxKeycodes-minKeycodes+1, &keysymsPerKeycode)
+ if keysyms == nil {
+ return errors.New("failed to get keyboard mapping")
+ }
+ emptyKeycode := C.KeyCode(0)
+keycodes:
+ for keycode := C.KeyCode(minKeycodes); keycode <= C.KeyCode(maxKeycodes); keycode++ {
+ for i := 0; i < int(keysymsPerKeycode); i++ {
+ keysymsIndex := int(keycode-C.KeyCode(minKeycodes))*int(keysymsPerKeycode) + i
+ keysym := *(*C.KeySym)(unsafe.Pointer(uintptr(unsafe.Pointer(keysyms)) + uintptr(keysymsIndex)*unsafe.Sizeof(*keysyms)))
+ if keysym != 0 {
+ continue keycodes
+ }
+ }
+ emptyKeycode = keycode
+ break
+ }
+ C.XFree(unsafe.Pointer(keysyms))
+ if emptyKeycode == 0 {
+ return errors.New("no empty keycode found")
+ }
+ keycodeMapping := make([]C.KeySym, keysymsPerKeycode)
+ for _, runeValue := range text {
+ keysym := C.KeySym(0x01000000 + runeValue)
+ for i := range keycodeMapping {
+ keycodeMapping[i] = keysym
+ }
+ C.XChangeKeyboardMapping(p.display, C.int(emptyKeycode), keysymsPerKeycode, (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1)
+ C.XTestFakeKeyEvent(p.display, C.uint(emptyKeycode), C.True, 0)
+ C.XTestFakeKeyEvent(p.display, C.uint(emptyKeycode), C.False, 0)
+ // race condition!
+ C.XFlush(p.display)
+ time.Sleep(typingDelay)
+ }
+ for i := range keycodeMapping {
+ keycodeMapping[i] = 0
+ }
+ C.XChangeKeyboardMapping(p.display, C.int(emptyKeycode), keysymsPerKeycode, (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1)
+ C.XFlush(p.display)
+ return nil
+}
+
+func (p *x11Plugin) PointerButton(button uint, press bool) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ if p.display == nil {
+ return errors.New("X server connection closed")
+ }
+ if button == 0 || button > 9 {
+ return errors.New("unsupported pointer button")
+ }
+ var pressC C.int = C.False
+ if press {
+ pressC = C.True
+ }
+ C.XTestFakeButtonEvent(p.display, C.uint(button), pressC, 0)
+ C.XFlush(p.display)
+ return nil
+}
+
+func (p *x11Plugin) PointerMove(deltaX, deltaY int) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ if p.display == nil {
+ return errors.New("X server connection closed")
+ }
+ C.XTestFakeRelativeMotionEvent(p.display, C.int(deltaX), C.int(deltaY), 0)
+ C.XFlush(p.display)
+ return nil
+}
+
+func (p *x11Plugin) PointerScroll(stepsHorizontal, stepsVertical int) error {
+ var buttonHorizontal uint = 7
+ if stepsHorizontal < 0 {
+ buttonHorizontal = 6
+ stepsHorizontal = -stepsHorizontal
+ }
+ for i := 0; i < stepsHorizontal; i++ {
+ if err := p.PointerButton(buttonHorizontal, true); err != nil {
+ return err
+ }
+ if err := p.PointerButton(buttonHorizontal, false); err != nil {
+ return err
+ }
+ }
+ var buttonVertical uint = 5
+ if stepsVertical < 0 {
+ buttonVertical = 4
+ stepsVertical = -stepsVertical
+ }
+ for i := 0; i < stepsVertical; i++ {
+ if err := p.PointerButton(buttonVertical, true); err != nil {
+ return err
+ }
+ if err := p.PointerButton(buttonVertical, false); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/plugin_x11_dummy.go b/plugin_x11_dummy.go
new file mode 100644
index 0000000..029f5aa
--- /dev/null
+++ b/plugin_x11_dummy.go
@@ -0,0 +1,26 @@
+// +build !linux
+
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad 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 Foobar. If not, see .
+ */
+
+package main
+
+func InitX11Plugin() (Plugin, error) {
+ return nil, UnsupportedPlatformError{"X11", "disabled"}
+}
diff --git a/terminal/qr.go b/qr.go
similarity index 56%
rename from terminal/qr.go
rename to qr.go
index 105aee7..6e9e0f3 100644
--- a/terminal/qr.go
+++ b/qr.go
@@ -1,9 +1,9 @@
/*
- * Copyright (c) 2018 Unrud
+ * Copyright (c) 2018 Unrud
*
* This file is part of Remote-Touchpad.
*
- * Remote-Touchpad is free software: you can redistribute it and/or modify
+ * Foobar 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 3 of the License, or
* (at your option) any later version.
@@ -13,50 +13,44 @@
* 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 Remote-Touchpad. If not, see .
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
*/
-package terminal
+package main
import qrcode "github.com/skip2/go-qrcode"
-func GenerateQRCode(message string, colorize bool) (string, error) {
+func GenerateQRCode(message string, invert bool) (string, error) {
q, err := qrcode.New(message, qrcode.Medium)
if err != nil {
return "", err
}
- return qrCodeToString(q.Bitmap(), colorize), nil
+ return qrCodeToString(q.Bitmap(), invert), nil
}
-func qrCodeToString(bits [][]bool, colorize bool) string {
+func qrCodeToString(bits [][]bool, invert bool) string {
s := ""
for y := -1; y < len(bits); y += 2 {
- if colorize {
- s += ForegroundBlack + BackgroundWhite
- }
for x := range bits[0] {
- upper := false
+ upper := !invert
if 0 <= y && y < len(bits) {
upper = bits[y][x]
}
- lower := false
+ lower := !invert
if 0 <= y+1 && y+1 < len(bits) {
lower = bits[y+1][x]
}
- if upper && lower {
- s += "█"
- } else if upper && !lower {
+ if upper != invert && lower != invert {
+ s += " "
+ } else if upper == invert && lower != invert {
s += "▀"
- } else if !upper && lower {
+ } else if upper != invert && lower == invert {
s += "▄"
} else {
- s += " "
+ s += "█"
}
}
- if colorize {
- s += ForegroundReset + BackgroundReset
- }
s += "\n"
}
return s
diff --git a/remote-touchpad.service b/remote-touchpad.service
deleted file mode 100644
index 76f5a74..0000000
--- a/remote-touchpad.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=Remote Touchpad Service
-After=network.target
-
-[Service]
-Type=simple
-ExecStart=%h/.local/bin/remote-touchpad
-Restart=always
-RestartSec=5s
-
-[Install]
-WantedBy=default.target
\ No newline at end of file
diff --git a/rts b/rts
deleted file mode 100755
index 84fbe46..0000000
--- a/rts
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/bin/bash
-
-# Remote Touchpad CLI Wrapper
-# Provides easy access to remote-touchpad service
-
-SERVICE_NAME="remote-touchpad"
-SERVICE_USER="$USER"
-
-install() {
- echo "Installing remote-touchpad as a user systemd service..."
-
- # Check if Go is installed
- if ! command -v go &> /dev/null; then
- echo "Error: Go is not installed. Please install Go first."
- return 1
- fi
-
- echo "Building remote-touchpad binary..."
- go build -tags portal,uinput,x11 -o remote-touchpad
-
- if [ ! -f "remote-touchpad" ]; then
- echo "Error: Failed to build remote-touchpad binary"
- return 1
- fi
-
- # Setup directories
- mkdir -p ~/.local/bin ~/.config/systemd/user
-
- echo "Installing service files..."
- cp remote-touchpad ~/.local/bin/ || echo "Error: cp to ~/.local/bin failed"
- cp remote-touchpad.service ~/.config/systemd/user/ || echo "Error: cp to ~/.config/systemd/user failed"
-
- echo "Enabling and starting service..."
- systemctl --user daemon-reload
- systemctl --user enable --now ${SERVICE_NAME}.service
-
- echo "✓ remote-touchpad user service installed and started"
- echo ""
- echo "Use './rts status' to check the service"
- echo "Use './rts url' to get the connection URL"
-}
-
-start() {
- systemctl --user start ${SERVICE_NAME}.service
- echo "Started remote-touchpad service"
-}
-
-stop() {
- systemctl --user stop ${SERVICE_NAME}.service
- echo "Stopped remote-touchpad service"
-}
-
-status() {
- systemctl --user status ${SERVICE_NAME}.service --no-pager
-}
-
-restart() {
- systemctl --user restart ${SERVICE_NAME}.service
- echo "Restarted remote-touchpad service"
-}
-
-url() {
- # Extract URL from service logs
- echo "Fetching remote-touchpad URL..."
- local url=$(journalctl --user -u ${SERVICE_NAME}.service -n 100 --no-pager 2>/dev/null | grep -oE "http://[^ ]+|https://[^ ]+" | tail -1)
- if [ -n "$url" ]; then
- echo "Remote Touchpad URL: $url"
- # Generate QR code if qrencode is available
- if command -v qrencode &> /dev/null; then
- echo "Generating QR code..."
- echo "$url" | qrencode -t ansiutf8
- else
- echo "QR code generation requires qrencode. Install it with:"
- echo " sudo apt-get install qrencode # Debian/Ubuntu"
- echo " sudo dnf install qrencode # Fedora"
- echo " brew install qrencode # macOS"
- fi
- else
- echo "Could not find URL in service logs. Is the service running?"
- fi
-}
-
-log() {
- echo "Viewing remote-touchpad service logs..."
- journalctl --user -u ${SERVICE_NAME}.service --no-pager
-}
-
-uninstall() {
- echo "Uninstalling remote-touchpad..."
- systemctl --user stop ${SERVICE_NAME}.service
- systemctl --user disable ${SERVICE_NAME}.service
- rm -f ~/.config/systemd/user/${SERVICE_NAME}.service
- rm -f ~/.local/bin/remote-touchpad
- systemctl --user daemon-reload
- echo "✓ remote-touchpad uninstalled"
-}
-
-help() {
- echo "Remote Touchpad CLI Wrapper"
- echo ""
- echo "Usage: rts [command]"
- echo ""
- echo "Commands:"
- echo " install - Install remote-touchpad as a systemd user service"
- echo " start - Start the remote-touchpad service"
- echo " stop - Stop the remote-touchpad service"
- echo " status - Check service status"
- echo " restart - Restart the service"
- echo " url - Show the remote-touchpad URL"
- echo " log - View service logs"
- echo " uninstall - Uninstall the service"
- echo " help - Show this help message"
-}
-
-# Main command handler
-case "$1" in
- install)
- install
- ;;
- start)
- start
- ;;
- stop)
- stop
- ;;
- status)
- status
- ;;
- restart)
- restart
- ;;
- url)
- url
- ;;
- log)
- log
- ;;
- uninstall)
- uninstall
- ;;
- help|--help|-h|"")
- help
- ;;
- *)
- echo "Unknown command: $1"
- help
- exit 1
- ;;
-esac
diff --git a/screenshots/1.png b/screenshots/1.png
index a85086a..50333e3 100644
Binary files a/screenshots/1.png and b/screenshots/1.png differ
diff --git a/screenshots/2.png b/screenshots/2.png
index 58922a3..578ca6f 100644
Binary files a/screenshots/2.png and b/screenshots/2.png differ
diff --git a/screenshots/3.png b/screenshots/3.png
index 43bff6b..26f4945 100644
Binary files a/screenshots/3.png and b/screenshots/3.png differ
diff --git a/screenshots/4.png b/screenshots/4.png
deleted file mode 100644
index 6fceade..0000000
Binary files a/screenshots/4.png and /dev/null differ
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
deleted file mode 100644
index 6d23326..0000000
--- a/snap/snapcraft.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-# Build by running `snapcraft`.
-
-# WARNING:
-# Snapcraft uses caching for already build steps but it's buggy and can cause strange problems.
-# Clean the cache by running `snapcraft clean`.
-
-name: remote-touchpad
-license: GPL-3.0+
-grade: stable
-adopt-info: remote-touchpad
-
-base: core24
-confinement: strict
-
-platforms:
- amd64:
- arm64:
-
-apps:
- remote-touchpad:
- command: bin/remote-touchpad
- plugs:
- - network-bind
- - desktop
- - x11
- remote-touchpad-wait-on-error:
- command: bin/remote-touchpad-wait-on-error
- plugs:
- - network-bind
- - desktop
- - x11
- common-id: com.github.unrud.RemoteTouchpad
- desktop: usr/share/applications/com.github.unrud.RemoteTouchpad.desktop
-
-parts:
- remote-touchpad:
- plugin: go
- go-buildtags:
- - portal
- - x11
- source: .
- source-type: git
- build-snaps:
- - go
- build-packages:
- - gcc
- - libc6-dev
- - libxt-dev
- - libxrandr-dev
- - libxtst-dev
- stage-packages:
- - libxt6
- - libxrandr2
- - libxtst6
- override-build: |
- craftctl default
- install -Dm0755 -t "${CRAFT_PART_INSTALL}/bin" desktop/remote-touchpad-wait-on-error
- install -Dm0644 -t "${CRAFT_PART_INSTALL}/usr/share/metainfo" desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
- install -Dm0644 -t "${CRAFT_PART_INSTALL}/usr/share/applications" desktop/com.github.unrud.RemoteTouchpad.desktop
- install -Dm0644 -t "${CRAFT_PART_INSTALL}/usr/share/icons/hicolor/scalable/apps" desktop/com.github.unrud.RemoteTouchpad.svg
- parse-info: [usr/share/metainfo/com.github.unrud.RemoteTouchpad.metainfo.xml]
diff --git a/terminal/terminal_posix.go b/terminal/terminal_posix.go
deleted file mode 100644
index 9d80e70..0000000
--- a/terminal/terminal_posix.go
+++ /dev/null
@@ -1,39 +0,0 @@
-//go:build !windows
-
-/*
- * Copyright (c) 2018-2019 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package terminal
-
-// #include
-import "C"
-import "os"
-
-func SupportsColor(fd uintptr) bool {
- return C.isatty(C.int(fd)) != 0
-}
-
-func SetTitle(title string) bool {
- if C.isatty(C.int(os.Stdout.Fd())) != 0 {
- os.Stdout.Write([]byte("\x1b]2;" + title + "\x07"))
- os.Stdout.Sync()
- return true
- }
- return false
-}
diff --git a/terminal/terminal_windows.go b/terminal/terminal_windows.go
deleted file mode 100644
index b28c084..0000000
--- a/terminal/terminal_windows.go
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2018-2019 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package terminal
-
-import (
- "syscall"
- "unsafe"
-)
-
-const (
- enableProcessedOuput uint32 = 0x1
- enableWrapAtEolOutput uint32 = 0x2
- enableVirtualTerminalProcessing uint32 = 0x4
-)
-
-var (
- kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
- setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
- setConsoleTitleProc = kernel32DLL.NewProc("SetConsoleTitleW")
-)
-
-func SupportsColor(fd uintptr) bool {
- r, _, _ := setConsoleModeProc.Call(fd, uintptr(enableProcessedOuput|
- enableWrapAtEolOutput|enableVirtualTerminalProcessing))
- return r != 0
-}
-
-func SetTitle(title string) bool {
- r, _, _ := setConsoleTitleProc.Call(
- uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))))
- return r != 0
-}
diff --git a/webdata.go b/webdata.go
deleted file mode 100644
index 1b816b6..0000000
--- a/webdata.go
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2024 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-package main
-
-import (
- "embed"
- "io/fs"
- "log"
- "mime"
-)
-
-//go:embed webdata/*
-var webdataFSWithPrefix embed.FS
-var webdataFS fs.FS
-
-var webdataTypes = map[string]string{
- ".css": "text/css; charset=utf-8",
- ".html": "text/html; charset=utf-8",
- ".mjs": "text/javascript; charset=utf-8",
- ".png": "image/png",
- ".woff": "font/woff",
-}
-
-func init() {
- var err error
- webdataFS, err = fs.Sub(webdataFSWithPrefix, "webdata")
- if err != nil {
- log.Fatal(err)
- }
- for ext, typ := range webdataTypes {
- if err := mime.AddExtensionType(ext, typ); err != nil {
- log.Fatal(err)
- }
- }
-}
diff --git a/webdata/app/compat.mjs b/webdata/app/compat.mjs
deleted file mode 100644
index 8a26833..0000000
--- a/webdata/app/compat.mjs
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-export const fullscreenEnabled = () => {
- return (document.fullscreenEnabled ||
- document.webkitFullscreenEnabled ||
- false);
-};
-
-export const requestFullscreen = (element, options) => {
- if (element.requestFullscreen) {
- element.requestFullscreen(options);
- } else if (element.webkitRequestFullscreen) {
- element.webkitRequestFullscreen(options);
- }
-};
-
-export const exitFullscreen = () => {
- if (document.exitFullscreen) {
- document.exitFullscreen();
- } else if (document.webkitExitFullscreen) {
- document.webkitExitFullscreen();
- }
-};
-
-export const fullscreenElement = () => {
- return (document.fullscreenElement ||
- document.webkitFullscreenElement ||
- null);
-};
-
-export const addFullscreenchangeEventListener = (listener) => {
- if ("onfullscreenchange" in document) {
- document.addEventListener("fullscreenchange", listener);
- } else if ("onwebkitfullscreenchange" in document) {
- document.addEventListener("webkitfullscreenchange", listener);
- }
-};
-
-export const requestPointerLock = (element) => {
- if (element.requestPointerLock) {
- element.requestPointerLock();
- }
-};
-
-export const exitPointerLock = () => {
- if (document.exitPointerLock) {
- document.exitPointerLock();
- }
-};
-
-export const pointerLockElement = () => {
- return document.pointerLockElement || null;
-};
-
-export const addPointerlockchangeEventListener = (listener) => {
- if ("onpointerlockchange" in document) {
- document.addEventListener("pointerlockchange", listener);
- }
-};
-
-export const vibrate = (pattern) => {
- if (navigator.vibrate) {
- navigator.vibrate(pattern);
- }
-};
diff --git a/webdata/app/inputcontroller.mjs b/webdata/app/inputcontroller.mjs
deleted file mode 100644
index a2984fb..0000000
--- a/webdata/app/inputcontroller.mjs
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-export const POINTER_BUTTON_LEFT = 0;
-export const POINTER_BUTTON_RIGHT = 1;
-export const POINTER_BUTTON_MIDDLE = 2;
-
-export const KEY_VOLUME_MUTE = 0;
-export const KEY_VOLUME_DOWN = 1;
-export const KEY_VOLUME_UP = 2;
-export const KEY_MEDIA_PLAY_PAUSE = 3;
-export const KEY_MEDIA_PREV_TRACK = 4;
-export const KEY_MEDIA_NEXT_TRACK = 5;
-export const KEY_BROWSER_BACK = 6;
-export const KEY_BROWSER_FORWARD = 7;
-export const KEY_SUPER = 8;
-export const KEY_LEFT = 9;
-export const KEY_RIGHT = 10;
-export const KEY_UP = 11;
-export const KEY_DOWN = 12;
-export const KEY_HOME = 13;
-export const KEY_END = 14;
-export const KEY_BACK_SPACE = 15;
-export const KEY_DELETE = 16;
-export const KEY_RETURN = 17;
-
-export default class InputController {
- #updateRate = 0;
-
- #moveXSum = 0;
- #moveYSum = 0;
- #scrollHSum = 0;
- #scrollVSum = 0;
- #scrolling = false;
- #scrollFinish = false;
- #updateTimeoutActive = false;
- #socket;
-
- constructor(socket) {
- this.#socket = socket;
- }
-
- configure(config) {
- this.#updateRate = config.updateRate;
- }
-
- #startUpdate(fromTimeout) {
- if (this.#updateTimeoutActive && !fromTimeout) {
- return;
- }
- this.#updateTimeoutActive = false;
- let finished = true;
- const xInt = Math.trunc(this.#moveXSum);
- const yInt = Math.trunc(this.#moveYSum);
- if (xInt != 0 || yInt != 0) {
- this.#socket.send("m" + xInt + ";" + yInt);
- this.#moveXSum -= xInt;
- this.#moveYSum -= yInt;
- finished = false;
- }
- const hInt = Math.trunc(this.#scrollHSum);
- const vInt = Math.trunc(this.#scrollVSum);
- if (hInt != 0 || vInt != 0) {
- this.#socket.send((this.#scrollFinish ? "S" : "s") + hInt + ";" + vInt);
- this.#scrollHSum -= hInt;
- this.#scrollVSum -= vInt;
- this.#scrolling = !this.#scrollFinish;
- this.#scrollFinish = false;
- finished = false;
- } else if (this.#scrollFinish && this.#scrolling) {
- this.#socket.send("S");
- this.#scrolling = false;
- this.#scrollFinish = false;
- }
- this.#updateTimeoutActive = !finished && this.#updateRate > 0;
- if (this.#updateTimeoutActive) {
- setTimeout(this.#startUpdate.bind(this), 1000 / this.#updateRate, true);
- }
- }
-
- pointerMove(deltaX, deltaY) {
- this.#moveXSum += deltaX;
- this.#moveYSum += deltaY;
- this.#startUpdate();
- }
-
- pointerScroll(deltaHorizontal, deltaVertical, finish) {
- this.#scrollHSum += deltaHorizontal;
- this.#scrollVSum += deltaVertical;
- this.#scrollFinish |= finish;
- this.#startUpdate();
- };
-
- pointerButton(button, press) {
- this.#socket.send("b" + button + ";" + (press ? 1 : 0));
- }
-
- keyboardKey(key) {
- this.#socket.send("k" + key);
- }
-
- keyboardText(text) {
- this.#socket.send("t" + text);
- }
-}
diff --git a/webdata/app/keyboard.mjs b/webdata/app/keyboard.mjs
deleted file mode 100644
index 84195d9..0000000
--- a/webdata/app/keyboard.mjs
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-import {
- KEY_SUPER, KEY_BACK_SPACE, KEY_RETURN, KEY_DELETE, KEY_HOME, KEY_END,
- KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
-} from "./inputcontroller.mjs";
-
-export default class Keyboard {
- #inputController;
- #checkAllowedCallback;
-
- constructor(inputController, checkAllowedCallback) {
- this.#inputController = inputController;
- this.#checkAllowedCallback = checkAllowedCallback;
- document.addEventListener("keydown", this.#handleKeydown.bind(this));
- }
-
- configure() {}
-
- #handleKeydown(event) {
- if (!this.#checkAllowedCallback() ||
- event.ctrlKey || event.altKey || event.isComposing) {
- return;
- }
- let key = null;
- if (event.key == "OS" || event.key == "Super" || event.key == "Meta") {
- key = KEY_SUPER;
- } else if (event.key == "Backspace") {
- key = KEY_BACK_SPACE;
- } else if (event.key == "Enter") {
- key = KEY_RETURN;
- } else if (event.key == "Delete") {
- key = KEY_DELETE;
- } else if (event.key == "Home") {
- key = KEY_HOME;
- } else if (event.key == "End") {
- key = KEY_END;
- } else if (event.key == "Left" || event.key == "ArrowLeft") {
- key = KEY_LEFT;
- } else if (event.key == "Right" || event.key == "ArrowRight") {
- key = KEY_RIGHT;
- } else if (event.key == "Up" || event.key == "ArrowUp") {
- key = KEY_UP;
- } else if (event.key == "Down" || event.key == "ArrowDown") {
- key = KEY_DOWN;
- }
- if (key != null) {
- if (!event.shiftKey) {
- event.preventDefault();
- this.#inputController.keyboardKey(key);
- }
- } else if (event.key.length == 1) {
- event.preventDefault();
- this.#inputController.keyboardText(event.key);
- }
- }
-}
diff --git a/webdata/app/main.mjs b/webdata/app/main.mjs
deleted file mode 100644
index 5477b89..0000000
--- a/webdata/app/main.mjs
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-import InputController, * as inputcontrollerModule from "./inputcontroller.mjs";
-import Socket from "./socket.mjs";
-import UI from "./ui.mjs";
-
-const url = new URL("ws", location.href);
-url.protocol = url.protocol == "http:" ? "ws:" : "wss:";
-
-const socket = new Socket(url, window.location.hash.substr(1));
-const inputController = new InputController(socket);
-const ui = new UI(inputController);
-
-socket.addEventListener("config", (event) => {
- const config = event.detail;
- inputController.configure(config);
- ui.configure(config);
-});
-
-socket.addEventListener("close", () => {
- ui.close();
-});
-
-window.app = {
- key: inputController.keyboardKey.bind(inputController),
- text: inputController.keyboardText.bind(inputController),
- toggleFullscreen: ui.toggleFullscreen.bind(ui),
- showTextInput: ui.showTextInput.bind(ui),
- showKeys: ui.showKeys.bind(ui),
- setKeysPage: ui.setKeysPage.bind(ui),
-};
-for (const name in inputcontrollerModule) {
- if (name.startsWith("KEY_")) {
- window.app[name] = inputcontrollerModule[name];
- }
-}
diff --git a/webdata/app/mouse.mjs b/webdata/app/mouse.mjs
deleted file mode 100644
index 8158eb3..0000000
--- a/webdata/app/mouse.mjs
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-export default class Mouse {
- #moveSpeed = 1;
- #scrollSpeed = 1;
-
- #buttons = 0;
- #inputController;
-
- constructor (inputController, element) {
- this.#inputController = inputController;
- for (const type of ["touchstart", "touchend", "touchcancel", "touchmove"]) {
- element.addEventListener(type, (event) => {
- event.preventDefault();
- });
- }
- element.addEventListener("mousedown", this.#handleMouseDownAndUp.bind(this));
- element.addEventListener("mouseup", this.#handleMouseDownAndUp.bind(this));
- element.addEventListener("mousemove", this.#handleMousemove.bind(this));
- element.addEventListener("wheel", this.#handleWheel.bind(this));
- }
-
- configure(config) {
- this.#moveSpeed = config.mouseMoveSpeed;
- this.#scrollSpeed = config.mouseScrollSpeed;
- }
-
- #updateButtons(newButtons) {
- for (let button = 0; button < 3; button += 1) {
- const flag = 1 << button;
- if ((newButtons&flag) != (this.#buttons & flag)) {
- this.#inputController.pointerButton(button, newButtons & flag);
- }
- }
- this.#buttons = newButtons;
- }
-
- #handleMouseDownAndUp(event) {
- this.#updateButtons(event.buttons);
- }
-
- #handleMousemove(event) {
- this.#inputController.pointerMove(
- event.movementX * this.#moveSpeed, event.movementY * this.#moveSpeed);
- }
-
- #handleWheel(event) {
- if (event.deltaMode == WheelEvent.DOM_DELTA_PIXEL) {
- this.#inputController.pointerScroll(
- event.deltaX * this.#scrollSpeed, event.deltaY * this.#scrollSpeed, true);
- } else if (event.deltaMode == WheelEvent.DOM_DELTA_LINE) {
- this.#inputController.pointerScroll(
- event.deltaX * 20 * this.#scrollSpeed, event.deltaY * 20 * this.#scrollSpeed,
- true);
- }
- }
-}
diff --git a/webdata/app/sha256.mjs b/webdata/app/sha256.mjs
deleted file mode 100644
index 3f7cd3f..0000000
--- a/webdata/app/sha256.mjs
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * A JavaScript implementation of the SHA family of hashes - defined in FIPS PUB 180-4, FIPS PUB 202,
- * and SP 800-185 - as well as the corresponding HMAC implementation as defined in FIPS PUB 198-1.
- *
- * Copyright 2008-2022 Brian Turek, 1998-2009 Paul Johnston & Contributors
- * Distributed under the BSD License
- * See http://caligatio.github.com/jsSHA/ for more information
- */
-const t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function r(t,r,n,i){let e,s,o;const h=r||[0],u=(n=n||0)>>>3,f=-1===i?3:0;for(e=0;e>>2,h.length<=s&&h.push(0),h[s]|=t[e]<<8*(f+i*(o%4));return{value:h,binLen:8*t.length+n}}function n(n,i,e){switch(i){case"UTF8":case"UTF16BE":case"UTF16LE":break;default:throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE")}switch(n){case"HEX":return function(t,r,n){return function(t,r,n,i){let e,s,o,h;if(0!=t.length%2)throw new Error("String of HEX type must be in byte increments");const u=r||[0],f=(n=n||0)>>>3,c=-1===i?3:0;for(e=0;e>>1)+f,o=h>>>2;u.length<=o;)u.push(0);u[o]|=s<<8*(c+i*(h%4))}return{value:u,binLen:4*t.length+n}}(t,r,n,e)};case"TEXT":return function(t,r,n){return function(t,r,n,i,e){let s,o,h,u,f,c,a,w,A=0;const E=n||[0],l=(i=i||0)>>>3;if("UTF8"===r)for(a=-1===e?3:0,h=0;hs?o.push(s):2048>s?(o.push(192|s>>>6),o.push(128|63&s)):55296>s||57344<=s?o.push(224|s>>>12,128|s>>>6&63,128|63&s):(h+=1,s=65536+((1023&s)<<10|1023&t.charCodeAt(h)),o.push(240|s>>>18,128|s>>>12&63,128|s>>>6&63,128|63&s)),u=0;u>>2;E.length<=f;)E.push(0);E[f]|=o[u]<<8*(a+e*(c%4)),A+=1}else for(a=-1===e?2:0,w="UTF16LE"===r&&1!==e||"UTF16LE"!==r&&1===e,h=0;h>>8),c=A+l,f=c>>>2;E.length<=f;)E.push(0);E[f]|=s<<8*(a+e*(c%4)),A+=2}return{value:E,binLen:8*A+i}}(t,i,r,n,e)};case"B64":return function(r,n,i){return function(r,n,i,e){let s,o,h,u,f,c,a,w=0;const A=n||[0],E=(i=i||0)>>>3,l=-1===e?3:0,p=r.indexOf("=");if(-1===r.search(/^[a-zA-Z0-9=+/]+$/))throw new Error("Invalid character in base-64 string");if(r=r.replace(/=/g,""),-1!==p&&p>>2;A.length<=c;)A.push(0);A[c]|=(u>>>16-8*h&255)<<8*(l+e*(a%4)),w+=1}}return{value:A,binLen:8*w+i}}(r,n,i,e)};case"BYTES":return function(t,r,n){return function(t,r,n,i){let e,s,o,h;const u=r||[0],f=(n=n||0)>>>3,c=-1===i?3:0;for(s=0;s>>2,u.length<=o&&u.push(0),u[o]|=e<<8*(c+i*(h%4));return{value:u,binLen:8*t.length+n}}(t,r,n,e)};case"ARRAYBUFFER":try{new ArrayBuffer(0)}catch(t){throw new Error("ARRAYBUFFER not supported by this environment")}return function(t,n,i){return function(t,n,i,e){return r(new Uint8Array(t),n,i,e)}(t,n,i,e)};case"UINT8ARRAY":try{new Uint8Array(0)}catch(t){throw new Error("UINT8ARRAY not supported by this environment")}return function(t,n,i){return r(t,n,i,e)};default:throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY")}}function i(r,n,i,e){switch(r){case"HEX":return function(t){return function(t,r,n,i){const e="0123456789abcdef";let s,o,h="";const u=r/8,f=-1===n?3:0;for(s=0;s>>2]>>>8*(f+n*(s%4)),h+=e.charAt(o>>>4&15)+e.charAt(15&o);return i.outputUpper?h.toUpperCase():h}(t,n,i,e)};case"B64":return function(r){return function(r,n,i,e){let s,o,h,u,f,c="";const a=n/8,w=-1===i?3:0;for(s=0;s>>2]:0,f=s+2>>2]:0,h=(r[s>>>2]>>>8*(w+i*(s%4))&255)<<16|(u>>>8*(w+i*((s+1)%4))&255)<<8|f>>>8*(w+i*((s+2)%4))&255,o=0;o<4;o+=1)c+=8*s+6*o<=n?t.charAt(h>>>6*(3-o)&63):e.b64Pad;return c}(r,n,i,e)};case"BYTES":return function(t){return function(t,r,n){let i,e,s="";const o=r/8,h=-1===n?3:0;for(i=0;i>>2]>>>8*(h+n*(i%4))&255,s+=String.fromCharCode(e);return s}(t,n,i)};case"ARRAYBUFFER":try{new ArrayBuffer(0)}catch(t){throw new Error("ARRAYBUFFER not supported by this environment")}return function(t){return function(t,r,n){let i;const e=r/8,s=new ArrayBuffer(e),o=new Uint8Array(s),h=-1===n?3:0;for(i=0;i>>2]>>>8*(h+n*(i%4))&255;return s}(t,n,i)};case"UINT8ARRAY":try{new Uint8Array(0)}catch(t){throw new Error("UINT8ARRAY not supported by this environment")}return function(t){return function(t,r,n){let i;const e=r/8,s=-1===n?3:0,o=new Uint8Array(e);for(i=0;i>>2]>>>8*(s+n*(i%4))&255;return o}(t,n,i)};default:throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY")}}const e=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],s=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],o=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225];function h(t){const r={outputUpper:!1,b64Pad:"=",outputLen:-1},n=t||{},i="Output length must be a multiple of 8";if(r.outputUpper=n.outputUpper||!1,n.b64Pad&&(r.b64Pad=n.b64Pad),n.outputLen){if(n.outputLen%8!=0)throw new Error(i);r.outputLen=n.outputLen}else if(n.shakeLen){if(n.shakeLen%8!=0)throw new Error(i);r.outputLen=n.shakeLen}if("boolean"!=typeof r.outputUpper)throw new Error("Invalid outputUpper formatting option");if("string"!=typeof r.b64Pad)throw new Error("Invalid b64Pad formatting option");return r}function u(t,r){return t>>>r|t<<32-r}function f(t,r){return t>>>r}function c(t,r,n){return t&r^~t&n}function a(t,r,n){return t&r^t&n^r&n}function w(t){return u(t,2)^u(t,13)^u(t,22)}function A(t,r){const n=(65535&t)+(65535&r);return(65535&(t>>>16)+(r>>>16)+(n>>>16))<<16|65535&n}function E(t,r,n,i){const e=(65535&t)+(65535&r)+(65535&n)+(65535&i);return(65535&(t>>>16)+(r>>>16)+(n>>>16)+(i>>>16)+(e>>>16))<<16|65535&e}function l(t,r,n,i,e){const s=(65535&t)+(65535&r)+(65535&n)+(65535&i)+(65535&e);return(65535&(t>>>16)+(r>>>16)+(n>>>16)+(i>>>16)+(e>>>16)+(s>>>16))<<16|65535&s}function p(t){return u(t,7)^u(t,18)^f(t,3)}function R(t){return u(t,6)^u(t,11)^u(t,25)}function U(t){let r;return r="SHA-224"==t?s.slice():o.slice(),r}function d(t,r){let n,i,s,o,h,U,d,y,T,F,b;const m=[];for(n=r[0],i=r[1],s=r[2],o=r[3],h=r[4],U=r[5],d=r[6],y=r[7],b=0;b<64;b+=1)m[b]=b<16?t[b]:E(u(g=m[b-2],17)^u(g,19)^f(g,10),m[b-7],p(m[b-15]),m[b-16]),T=l(y,R(h),c(h,U,d),e[b],m[b]),F=A(w(n),a(n,i,s)),y=d,d=U,U=h,h=A(o,T),o=s,s=i,i=n,n=A(T,F);var g;return r[0]=A(n,r[0]),r[1]=A(i,r[1]),r[2]=A(s,r[2]),r[3]=A(o,r[3]),r[4]=A(h,r[4]),r[5]=A(U,r[5]),r[6]=A(d,r[6]),r[7]=A(y,r[7]),r}class y extends class{constructor(t,r,n){const i=n||{};if(this.t=r,this.i=i.encoding||"UTF8",this.numRounds=i.numRounds||1,isNaN(this.numRounds)||this.numRounds!==parseInt(this.numRounds,10)||1>this.numRounds)throw new Error("numRounds must a integer >= 1");this.o=t,this.h=[],this.u=0,this.A=!1,this.l=0,this.p=!1,this.R=[],this.U=[]}update(t){let r,n=0;const i=this.T>>>5,e=this.F(t,this.h,this.u),s=e.binLen,o=e.value,h=s>>>5;for(r=0;r>>5),this.u=s%this.T,this.A=!0,this}getHash(t,r){let n,e,s=this.B;const o=h(r);if(this.H){if(-1===o.outputLen)throw new Error("Output length must be specified in options");s=o.outputLen}const u=i(t,s,this.v,o);if(this.p&&this.Y)return u(this.Y(o));for(e=this.C(this.h.slice(),this.u,this.l,this.S(this.m),s),n=1;n>>24-s%32),e=this.C(e,s,0,this.I(this.o),s);return u(e)}setHMACKey(t,r,i){if(!this.L)throw new Error("Variant does not support HMAC");if(this.A)throw new Error("Cannot set MAC key after calling update");const e=n(r,(i||{}).encoding||"UTF8",this.v);this.M(e(t))}M(t){const r=this.T>>>3,n=r/4-1;let i;if(1!==this.numRounds)throw new Error("Cannot set numRounds with MAC");if(this.p)throw new Error("MAC key already set");for(r>>9<<4),u=r+n;for(;t.length<=h;)t.push(0);for(t[r>>>5]|=128<<24-r%32,t[h]=4294967295&u,t[h-1]=u/4294967296|0,s=0;s
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-import jsSHA from "./sha256.mjs"
-
-const challengeResponse = (message, secret) => {
- const shaObj = new jsSHA("SHA-256", "TEXT");
- shaObj.setHMACKey(message, "TEXT");
- shaObj.update(secret);
- return btoa(shaObj.getHMAC("BYTES"));
-};
-
-export default class Socket extends EventTarget {
- #secret;
- #authenticated;
- #ws;
-
- constructor(url, secret) {
- super();
- this.#secret = secret;
- this.#authenticated = false;
- this.#ws = new WebSocket(url);
- this.#ws.addEventListener("message", this.#handle_ws_message.bind(this));
- this.#ws.addEventListener("close", this.#handle_ws_close.bind(this));
- }
-
- #handle_ws_message(event) {
- if (!this.#authenticated) {
- this.#ws.send(challengeResponse(event.data, this.#secret));
- this.#authenticated = true;
- return;
- }
- let config;
- try {
- config = JSON.parse(event.data);
- } catch (e) {
- this.#ws.close();
- throw (e);
- }
- this.dispatchEvent(new CustomEvent("config", {detail: config}));
- }
-
- #handle_ws_close() {
- this.dispatchEvent(new CustomEvent("close"));
- }
-
- send(message) {
- this.#ws.send(message);
- }
-}
diff --git a/webdata/app/touchpad.mjs b/webdata/app/touchpad.mjs
deleted file mode 100644
index 68fd7a3..0000000
--- a/webdata/app/touchpad.mjs
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-import {POINTER_BUTTON_LEFT, POINTER_BUTTON_MIDDLE, POINTER_BUTTON_RIGHT} from "./inputcontroller.mjs";
-
-// [1 Touch, 2 Touches, 3 Touches] (as pixel)
-const TOUCH_MOVE_THRESHOLD = [10, 15, 15];
-// Max time between consecutive touches for clicking or dragging (as milliseconds)
-const TOUCH_TIMEOUT = 250;
-// [[pixel/second, multiplicator], ...]
-const POINTER_ACCELERATION = [
- [0, 0],
- [87, 1],
- [173, 1],
- [553, 2],
-];
-
-const copyTouch = (touch, timeStamp) => ({
- identifier: touch.identifier,
- pageX: touch.pageX,
- pageXStart: touch.pageX,
- pageY: touch.pageY,
- pageYStart: touch.pageY,
- timeStamp: timeStamp,
-});
-
-const calculateAccelerationMult = (speed) => {
- for (let i = 0; i < POINTER_ACCELERATION.length; i += 1) {
- const s2 = POINTER_ACCELERATION[i][0];
- const a2 = POINTER_ACCELERATION[i][1];
- if (s2 <= speed) {
- continue;
- }
- if (i == 0) {
- return a2;
- }
- const s1 = POINTER_ACCELERATION[i - 1][0];
- const a1 = POINTER_ACCELERATION[i - 1][1];
- return ((speed - s1) / (s2 - s1)) * (a2 - a1) + a1;
- }
- if (POINTER_ACCELERATION.length > 0) {
- return POINTER_ACCELERATION[POINTER_ACCELERATION.length - 1][1];
- }
- return 1;
-};
-
-export default class Touchpad {
- #moveSpeed = 1;
- #scrollSpeed = 1;
-
- #moved = false;
- #startTimeStamp = 0;
- #lastEndTimeStamp = 0;
- #releasedCount = 0;
- #ongoingTouches = [];
- #dragging = false;
- #draggingTimeout = null;
- #inputController;
- #checkAllowedCallback;
-
- constructor(inputController, checkAllowedCallback) {
- this.#inputController = inputController;
- this.#checkAllowedCallback = checkAllowedCallback;
- document.addEventListener("touchstart", this.#handleTouchstart.bind(this));
- document.addEventListener("touchend", this.#handleTouchend.bind(this));
- document.addEventListener("touchcancel", this.#handleTouchend.bind(this));
- document.addEventListener("touchmove", this.#handleTouchmove.bind(this));
- }
-
- configure(config) {
- this.#moveSpeed = config.moveSpeed;
- this.#scrollSpeed = config.scrollSpeed;
- }
-
- #ongoingTouchIndexById(idToFind) {
- for (let i = 0; i < this.#ongoingTouches.length; i += 1) {
- if (this.#ongoingTouches[i].identifier == idToFind) {
- return i;
- }
- }
- return -1;
- }
-
- #handleDraggingTimeout() {
- this.#draggingTimeout = null;
- this.#inputController.pointerButton(POINTER_BUTTON_LEFT, false);
- }
-
- #handleTouchstart(event) {
- // Might get called multiple times for the same touches
- if (this.#ongoingTouches.length == 0) {
- this.#startTimeStamp = event.timeStamp;
- this.#moved = false;
- }
- const touches = event.changedTouches;
- let foundTouch = false;
- for (let i = 0; i < touches.length; i += 1) {
- if (this.#ongoingTouches.length == 0 &&
- !this.#checkAllowedCallback(touches[i].target)) {
- continue;
- }
- foundTouch = true;
- const touch = copyTouch(touches[i], event.timeStamp);
- const idx = this.#ongoingTouchIndexById(touch.identifier);
- if (idx < 0) {
- this.#ongoingTouches.push(touch);
- } else {
- this.#ongoingTouches[idx] = touch;
- }
- }
- if (!foundTouch) {
- return;
- }
- event.preventDefault();
- this.#lastEndTimeStamp = 0;
- if (this.#draggingTimeout != null) {
- clearTimeout(this.#draggingTimeout);
- this.#draggingTimeout = null;
- this.#dragging = true;
- }
- this.#inputController.pointerScroll(0, 0, true);
- }
-
- #handleTouchend(event) {
- const touches = event.changedTouches;
- let foundTouch = false;
- for (let i = 0; i < touches.length; i += 1) {
- const idx = this.#ongoingTouchIndexById(touches[i].identifier);
- if (idx < 0) {
- continue;
- }
- foundTouch = true;
- this.#ongoingTouches.splice(idx, 1);
- this.#releasedCount += 1;
- }
- if (!foundTouch) {
- return;
- }
- event.preventDefault();
- this.#lastEndTimeStamp = event.timeStamp;
- this.#inputController.pointerScroll(0, 0, true);
- if (this.#releasedCount > TOUCH_MOVE_THRESHOLD.length) {
- this.#moved = true;
- }
- if (this.#ongoingTouches.length == 0 && this.#releasedCount >= 1) {
- if (this.#dragging) {
- this.#dragging = false;
- this.#inputController.pointerButton(POINTER_BUTTON_LEFT, false);
- }
- if (!this.#moved && event.timeStamp - this.#startTimeStamp < TOUCH_TIMEOUT) {
- let button = 0;
- if (this.#releasedCount == 1) {
- button = POINTER_BUTTON_LEFT;
- } else if (this.#releasedCount == 2) {
- button = POINTER_BUTTON_RIGHT;
- } else if (this.#releasedCount == 3) {
- button = POINTER_BUTTON_MIDDLE;
- }
- this.#inputController.pointerButton(button, true);
- if (button == POINTER_BUTTON_LEFT) {
- this.#draggingTimeout = setTimeout(
- this.#handleDraggingTimeout.bind(this), TOUCH_TIMEOUT);
- } else {
- this.#inputController.pointerButton(button, false);
- }
- }
- this.#releasedCount = 0;
- }
- }
-
- #handleTouchmove(event) {
- let sumX = 0;
- let sumY = 0;
- const touches = event.changedTouches;
- let foundTouch = false;
- for (let i = 0; i < touches.length; i += 1) {
- const idx = this.#ongoingTouchIndexById(touches[i].identifier);
- if (idx < 0) {
- continue;
- }
- foundTouch = true;
- if (!this.#moved) {
- const dist = Math.sqrt(
- Math.pow(touches[i].pageX - this.#ongoingTouches[idx].pageXStart, 2) +
- Math.pow(touches[i].pageY - this.#ongoingTouches[idx].pageYStart, 2)
- );
- if (this.#ongoingTouches.length > TOUCH_MOVE_THRESHOLD.length ||
- dist > TOUCH_MOVE_THRESHOLD[this.#ongoingTouches.length - 1] ||
- event.timeStamp - this.#startTimeStamp >= TOUCH_TIMEOUT) {
- this.#moved = true;
- }
- }
- const dx = touches[i].pageX - this.#ongoingTouches[idx].pageX;
- const dy = touches[i].pageY - this.#ongoingTouches[idx].pageY;
- const timeDelta = event.timeStamp - this.#ongoingTouches[idx].timeStamp;
- sumX += dx * calculateAccelerationMult(Math.abs(dx) / timeDelta * 1000);
- sumY += dy * calculateAccelerationMult(Math.abs(dy) / timeDelta * 1000);
- this.#ongoingTouches[idx].pageX = touches[i].pageX;
- this.#ongoingTouches[idx].pageY = touches[i].pageY;
- this.#ongoingTouches[idx].timeStamp = event.timeStamp;
- }
- if (!foundTouch) {
- return;
- }
- event.preventDefault();
- if (this.#moved && event.timeStamp - this.#lastEndTimeStamp >= TOUCH_TIMEOUT) {
- if (this.#ongoingTouches.length == 1 || this.#dragging) {
- this.#inputController.pointerMove(
- sumX * this.#moveSpeed, sumY * this.#moveSpeed);
- } else if (this.#ongoingTouches.length == 2) {
- this.#inputController.pointerScroll(
- -sumX * this.#scrollSpeed, -sumY * this.#scrollSpeed, false);
- }
- }
- }
-}
diff --git a/webdata/app/ui.mjs b/webdata/app/ui.mjs
deleted file mode 100644
index a21ddf1..0000000
--- a/webdata/app/ui.mjs
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (c) 2018-2019, 2023 Unrud
- *
- * This file is part of Remote-Touchpad.
- *
- * Remote-Touchpad 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 3 of the License, or
- * (at your option) any later version.
- *
- * Remote-Touchpad 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 Remote-Touchpad. If not, see .
- */
-
-import Keyboard from "./keyboard.mjs";
-import Mouse from "./mouse.mjs";
-import Touchpad from "./touchpad.mjs";
-import * as compat from "./compat.mjs";
-
-const IGNORE_CLICK_AFTER_TOUCH_DURATION = 1000; // milliseconds
-const CLICK_VIBRATION_PATTERN = [10];
-
-const buttons = document.querySelectorAll("button");
-const scenes = document.querySelectorAll("body > .scene");
-const openingScene = document.getElementById("opening");
-const closedScene = document.getElementById("closed");
-const padScene = document.getElementById("pad");
-const keysScene = document.getElementById("keys");
-const keysPages = keysScene.querySelectorAll(":scope > .page");
-const textInputScene = document.getElementById("text-input");
-const textInput = textInputScene.querySelector("textarea");
-const mouseScene = document.getElementById("mouse");
-const sendText = document.getElementById("send-text");
-
-export default class UI {
- #activeScene = null;
- #keysActiveName = "";
- #ready = false;
- #closed = false;
- #ignoreClickUntilTimeStamp = Number.MIN_VALUE;
- #inputController;
- #mouse;
- #keyboard;
- #touchpad;
-
- constructor(inputController) {
- this.#inputController = inputController;
- this.#mouse = new Mouse(inputController, mouseScene);
- this.#keyboard = new Keyboard(inputController,
- () => this.#activeScene?.classList.contains("keyboard-input"));
- this.#touchpad = new Touchpad(inputController,
- (target) => target.classList.contains("touch-input"));
- document.addEventListener("mousedown", this.#handleMousedown.bind(this));
- document.addEventListener("touchend", this.#handleTouchend.bind(this));
- textInput.addEventListener("input", () => { this.#updateTextInput(); });
- sendText.addEventListener("click", this.#handleSendText.bind(this));
- window.addEventListener("popstate", () => { this.#update(); });
- compat.addFullscreenchangeEventListener(() => { this.#update(); });
- compat.addPointerlockchangeEventListener(() => { this.#update(); });
- for (const button of buttons) {
- button.setAttribute("tabindex", "-1");
- button.addEventListener("click", this.#handleButtonClick.bind(this));
- }
- this.#update();
- }
-
- configure(config) {
- this.#mouse.configure(config);
- this.#keyboard.configure(config);
- this.#touchpad.configure(config);
- if (!this.#closed) {
- this.#ready = true;
- }
- this.#update();
- }
-
- close() {
- this.#ready = false;
- this.#closed = true;
- this.#update();
- }
-
- #handleTouchend(event) {
- // HACK: event.preventDefault doesn't reliably stop click events in Firefox (112)
- this.#ignoreClickUntilTimeStamp = event.timeStamp + IGNORE_CLICK_AFTER_TOUCH_DURATION;
- }
-
- #handleMousedown(event) {
- if (this.#activeScene != mouseScene && event.buttons == 1 &&
- this.#ignoreClickUntilTimeStamp <= event.timeStamp &&
- event.target.classList.contains("mouse-input")) {
- compat.requestPointerLock(mouseScene);
- }
- }
-
- #updateTextInput(newValue = null) {
- if (newValue != null) {
- textInput.value = newValue;
- }
- sessionStorage.setItem("text-input", textInput.value);
- sendText.textContent = sendText.getAttribute(`data-${textInput.value ? "send" : "back"}-content`);
- textInput.focus();
- }
-
- #handleButtonClick(event) {
- event.target.classList.add("click");
- setTimeout(() => event.target.classList.remove("click"), 0);
- compat.vibrate(CLICK_VIBRATION_PATTERN);
- }
-
- #handleSendText() {
- if (textInput.value) {
- // normalize line endings
- const text = textInput.value.replace(/\r\n?/g, "\n");
- this.#inputController.keyboardText(text);
- this.#updateTextInput("");
- } else {
- history.back();
- }
- }
-
- #showScene(scene) {
- this.#activeScene = scene;
- if (compat.fullscreenElement() && !scene.classList.contains("allow-fullscreen")) {
- compat.exitFullscreen();
- }
- if (compat.pointerLockElement() && this.#activeScene != mouseScene) {
- compat.exitPointerLock();
- }
- textInput.value = "";
- for (const otherScene of scenes) {
- otherScene.classList.toggle("hidden", otherScene != scene);
- }
- }
-
- setKeysPage(index, relative = false) {
- if (relative) {
- for (let i = 0; i < keysPages.length && keysPages[i].classList.contains("hidden");
- i += 1, index += 1);
- }
- index = ((index % keysPages.length) + keysPages.length) % keysPages.length;
- sessionStorage.setItem(this.#keysActiveName, index);
- for (let i = 0; i < keysPages.length; i += 1) {
- keysPages[i].classList.toggle("hidden", i != index);
- }
- }
-
- showKeys(name = "", defaultPageIndex = 0) {
- this.#showScene(keysScene);
- this.#keysActiveName = "keys" + (name ? ":" + name : "");
- let pageIndex = parseInt(sessionStorage.getItem(this.#keysActiveName));
- if (isNaN(pageIndex)) {
- pageIndex = defaultPageIndex;
- }
- this.setKeysPage(pageIndex);
- if (history.state != this.#keysActiveName) {
- history.pushState(this.#keysActiveName, "");
- }
- }
-
- showTextInput() {
- this.#showScene(textInputScene);
- this.#updateTextInput(sessionStorage.getItem("text-input") || "");
- if (history.state != "text-input") {
- history.pushState("text-input", "");
- }
- }
-
- toggleFullscreen() {
- if (compat.fullscreenElement()) {
- compat.exitFullscreen();
- } else {
- compat.requestFullscreen(document.documentElement, {navigationUI: "hide"});
- }
- }
-
- #update() {
- const fullscreenEnabled = compat.fullscreenEnabled();
- for (const element of document.querySelectorAll(".visble-if-fullscreen-enabled")) {
- element.classList.toggle("hidden", !fullscreenEnabled);
- }
- if (!this.#ready) {
- this.#showScene(this.#closed ? closedScene : openingScene);
- } else if (compat.pointerLockElement()) {
- this.#showScene(mouseScene);
- } else if ((history.state || "").split(":")[0] == "keys") {
- this.showKeys(history.state.substr("keys:".length));
- } else if (history.state == "text-input") {
- this.showTextInput();
- } else {
- this.#showScene(padScene);
- }
- }
-}
diff --git a/webdata/fn.js b/webdata/fn.js
new file mode 100644
index 0000000..78287c5
--- /dev/null
+++ b/webdata/fn.js
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad 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 Foobar. If not, see .
+ */
+
+ // [1 Touch, 2 Touches, 3 Touches]
+const TOUCH_MOVE_THRESHOLD = [10, 15, 15];
+const TOUCH_TIMEOUT = 250;
+const MOVE_MULT = 1;
+const SCROLL_MULT = 0.05;
+// [[px/s, mult], ...]
+const POINTER_ACCELERATION = [[0, 0], [87, 1], [173, 1], [553, 2]];
+const UPDATE_INTERVAL = 50;
+const MAX_IDLE_UPDATES = 10;
+
+var ws;
+var pad;
+var padlabel;
+
+var touchMoved = false;
+var touchStart = 0;
+var touchLastEnd = 0;
+var touchReleasedCount = 0;
+var ongoingTouches = [];
+var moveXSum = 0;
+var moveYSum = 0;
+var scrollXSum = 0;
+var scrollYSum = 0;
+var dragging = false;
+var draggingTimeout = null;
+var updateTimer = null;
+var idleUpdates = 0;
+
+function fullscreenEnabled() {
+ return (document.fullscreenEnabled ||
+ document.webkitFullscreenEnabled ||
+ document.mozFullScreenEnabled ||
+ document.msFullscreenEnabled ||
+ false);
+}
+
+function requestFullscreen(e) {
+ if (e.requestFullscreen) {
+ e.requestFullscreen();
+ } else if (e.webkitRequestFullscreen) {
+ e.webkitRequestFullscreen();
+ } else if (e.mozRequestFullScreen) {
+ e.mozRequestFullScreen();
+ } else if (e.msRequestFullscreen) {
+ e.msRequestFullscreen();
+ }
+}
+
+function exitFullscreen() {
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ } else if (document.webkitExitFullscreen) {
+ document.webkitExitFullscreen();
+ } else if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen();
+ }
+}
+
+function fullscreenElement() {
+ return (document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.mozFullScreenElement ||
+ document.msFullscreenElement ||
+ null);
+}
+
+function addFullscreenchangeEventListener(listener) {
+ if (document.fullscreenElement !== undefined) {
+ document.addEventListener("fullscreenchange", listener);
+ } else if (document.webkitFullscreenElement !== undefined) {
+ document.addEventListener("webkitfullscreenchange", listener);
+ } else if (document.mozFullScreenElement !== undefined) {
+ document.addEventListener("mozfullscreenchange", listener);
+ } else if (document.msFullscreenElement !== undefined) {
+ document.addEventListener("MSFullscreenChange", listener);
+ }
+}
+
+function copyTouch(touch, timeStamp) {
+ return {
+ identifier: touch.identifier,
+ pageX: touch.pageX,
+ pageXStart: touch.pageX,
+ pageY: touch.pageY,
+ pageYStart: touch.pageY,
+ timeStamp: timeStamp
+ };
+}
+
+function ongoingTouchIndexById(idToFind) {
+ for (var i = 0; i < ongoingTouches.length; i++) {
+ var id = ongoingTouches[i].identifier;
+
+ if (id == idToFind) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+function calculatePointerAccelerationMult(speed) {
+ for (var i = 0; i < POINTER_ACCELERATION.length; i++) {
+ s2 = POINTER_ACCELERATION[i][0];
+ a2 = POINTER_ACCELERATION[i][1];
+ if (s2 <= speed) {
+ continue;
+ }
+ if (i == 0) {
+ return a2;
+ }
+ s1 = POINTER_ACCELERATION[i-1][0];
+ a1 = POINTER_ACCELERATION[i-1][1];
+ return ((speed-s1) / (s2-s1)) * (a2-a1) + a1;
+ }
+ if (POINTER_ACCELERATION.length > 0) {
+ return POINTER_ACCELERATION[POINTER_ACCELERATION.length-1][1];
+ }
+ return 1;
+}
+
+function onDraggingTimeout() {
+ draggingTimeout = null;
+ ws.send("PointerButton 1 0");
+}
+
+function updateMoveAndScroll() {
+ if (updateTimer == null) {
+ onUpdateTimeout();
+ if (UPDATE_INTERVAL > 0 && idleUpdates == 0) {
+ updateTimer = setInterval(onUpdateTimeout, UPDATE_INTERVAL);
+ }
+ }
+}
+
+function onUpdateTimeout() {
+ idleUpdates += 1;
+ var moveX = Math.trunc(moveXSum);
+ var moveY = Math.trunc(moveYSum);
+ if (Math.abs(moveX) >= 1 || Math.abs(moveY) >= 1) {
+ moveXSum -= moveX;
+ moveYSum -= moveY;
+ idleUpdates = 0;
+ ws.send("PointerMove " + moveX + " " + moveY);
+ }
+ var scrollX = Math.trunc(scrollXSum);
+ var scrollY = Math.trunc(scrollYSum);
+ if (Math.abs(scrollX) >= 1 || Math.abs(scrollY) >= 1) {
+ scrollXSum -= scrollX;
+ scrollYSum -= scrollY;
+ idleUpdates = 0;
+ ws.send("PointerScroll " + scrollX + " " + scrollY);
+ }
+ if (idleUpdates >= MAX_IDLE_UPDATES) {
+ clearInterval(updateTimer);
+ updateTimer = null;
+ }
+}
+
+function handleStart(evt) {
+ if (ongoingTouches.length == 0) {
+ touchStart = evt.timeStamp;
+ touchMoved = false;
+ touchReleasedCount = 0;
+ dragging = false;
+ }
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ if (touches[i].target !== pad && touches[i].target !== padlabel) {
+ continue;
+ }
+ evt.preventDefault();
+ ongoingTouches.push(copyTouch(touches[i], evt.timeStamp));
+ touchLastEnd = 0;
+ if (!dragging) {
+ moveXSum = Math.trunc(moveXSum);
+ moveYSum = Math.trunc(moveYSum);
+ }
+ scrollXSum = Math.trunc(scrollXSum);
+ scrollYSum = Math.trunc(scrollYSum);
+ if (draggingTimeout !== null) {
+ clearTimeout(draggingTimeout);
+ draggingTimeout = null;
+ dragging = true;
+ }
+ }
+}
+
+function handleEnd(evt) {
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var idx = ongoingTouchIndexById(touches[i].identifier);
+ if (idx < 0) {
+ continue;
+ }
+ ongoingTouches.splice(idx, 1);
+ touchReleasedCount++;
+ touchLastEnd = evt.timeStamp;
+ }
+ if (touchReleasedCount > TOUCH_MOVE_THRESHOLD.length) {
+ touchMoved = true;
+ }
+ if (ongoingTouches.length == 0 && touchReleasedCount >= 1 &&
+ dragging) {
+ ws.send("PointerButton 1 0");
+ }
+ if (ongoingTouches.length == 0 && touchReleasedCount >= 1 &&
+ !touchMoved && evt.timeStamp - touchStart < TOUCH_TIMEOUT) {
+ var button = 0;
+ if (touchReleasedCount == 1) {
+ button = 1;
+ } else if (touchReleasedCount == 2) {
+ button = 3;
+ }else if (touchReleasedCount == 3) {
+ button = 2;
+ }
+ ws.send("PointerButton " + button + " 1");
+ if (button == 1) {
+ draggingTimeout = setTimeout(onDraggingTimeout, TOUCH_TIMEOUT);
+ } else {
+ ws.send("PointerButton " + button + " 0");
+ }
+ }
+}
+
+function handleCancel(evt) {
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var idx = ongoingTouchIndexById(touches[i].identifier);
+ if (idx < 0) {
+ continue;
+ }
+ ongoingTouches.splice(idx, 1);
+ touchReleasedCount++;
+ touchLastEnd = evt.timeStamp;
+ touchMoved = true;
+ }
+}
+
+function handleMove(evt) {
+ var sumX = 0;
+ var sumY = 0;
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var idx = ongoingTouchIndexById(touches[i].identifier);
+ if (idx < 0) {
+ continue;
+ }
+ var dist = Math.sqrt(Math.pow(touches[i].pageX - ongoingTouches[idx].pageXStart, 2) + Math.pow(touches[i].pageY - ongoingTouches[idx].pageYStart, 2));
+ if (ongoingTouches.length > TOUCH_MOVE_THRESHOLD.length || dist > TOUCH_MOVE_THRESHOLD[ongoingTouches.length - 1]) {
+ touchMoved = true;
+ }
+ var dx = touches[i].pageX - ongoingTouches[idx].pageX;
+ var dy = touches[i].pageY - ongoingTouches[idx].pageY;
+ var timeDelta = evt.timeStamp - ongoingTouches[idx].timeStamp;
+ sumX += dx * calculatePointerAccelerationMult(Math.abs(dx) / timeDelta * 1000);
+ sumY += dy * calculatePointerAccelerationMult(Math.abs(dy) / timeDelta * 1000);
+ ongoingTouches[idx].pageX = touches[i].pageX;
+ ongoingTouches[idx].pageY = touches[i].pageY;
+ ongoingTouches[idx].timeStamp = evt.timeStamp;
+ }
+ if (touchMoved && evt.timeStamp - touchLastEnd >= TOUCH_TIMEOUT) {
+ if (ongoingTouches.length == 1 || dragging) {
+ moveXSum += sumX * MOVE_MULT;
+ moveYSum += sumY * MOVE_MULT;
+ } else if (ongoingTouches.length == 2) {
+ scrollXSum -= sumX * SCROLL_MULT;
+ scrollYSum -= sumY * SCROLL_MULT;
+ }
+ updateMoveAndScroll();
+ }
+}
+
+function challengeResponse(message) {
+ var shaObj = new jsSHA("SHA-256", "TEXT");
+ shaObj.setHMACKey(message, "TEXT");
+ shaObj.update(window.location.hash.substr(1));
+ return btoa(shaObj.getHMAC("BYTES"));
+}
+
+window.addEventListener("load", function() {
+ var authenticated = false;
+ var opening = document.getElementById("opening");
+ var closed = document.getElementById("closed");
+ pad = document.getElementById("pad");
+ padlabel = document.getElementById("padlabel");
+ var keyboard = document.getElementById("keyboard");
+ var fullscreenbutton = document.getElementById("fullscreenbutton");
+ var text = document.getElementById("text");
+ closed.style.display = "none";
+ pad.style.display = "none";
+ keyboard.style.display = "none";
+ text.value = "";
+
+ ws = new WebSocket("ws://" + location.hostname +
+ (location.port ? ":" + location.port : "") +
+ "/ws");
+
+ ws.onmessage = function(event) {
+ if (authenticated) {
+ ws.close();
+ return;
+ }
+ authenticated = true;
+ ws.send(challengeResponse(event.data));
+ opening.style.display = "none";
+ if (history.state == "keyboard") {
+ keyboard.style.display = "flex";
+ text.focus();
+ } else {
+ pad.style.display = "flex";
+ }
+ };
+
+ ws.onclose = function() {
+ if (fullscreenElement()) {
+ exitFullscreen();
+ }
+ opening.style.display = "none";
+ pad.style.display = "none";
+ keyboard.style.display = "none"
+ closed.style.display = "flex";
+ };
+
+ document.getElementById("keyboardbutton").addEventListener("click",
+ function(e) {
+ if (fullscreenElement()) {
+ exitFullscreen();
+ }
+ pad.style.display = "none";
+ keyboard.style.display = "flex";
+ text.focus();
+ history.pushState("keyboard", "Remote Keyboard");
+ });
+ if (!fullscreenEnabled()) {
+ fullscreenbutton.style.display = "none";
+ }
+ fullscreenbutton.addEventListener("click", function(e) {
+ if (fullscreenElement()) {
+ exitFullscreen();
+ } else {
+ requestFullscreen(pad);
+ }
+ });
+ document.getElementById("sendbutton").addEventListener("click",
+ function(e) {
+ if (text.value != "") {
+ ws.send("KeyboardText " + text.value);
+ text.value = "";
+ }
+ window.history.back();
+ });
+ window.onpopstate = function(event) {
+ if (keyboard.style.display == "flex") {
+ pad.style.display = "flex";
+ keyboard.style.display = "none";
+ } else {
+ window.history.back();
+ }
+ };
+ pad.addEventListener("touchstart", handleStart, false);
+ pad.addEventListener("touchend", handleEnd, false);
+ pad.addEventListener("touchcancel", handleCancel, false);
+ pad.addEventListener("touchmove", handleMove, false);
+}, false);
diff --git a/webdata/icon.png b/webdata/icon.png
index dbd97b4..37844d5 100644
Binary files a/webdata/icon.png and b/webdata/icon.png differ
diff --git a/webdata/icon.woff b/webdata/icon.woff
deleted file mode 100644
index 84d6ccb..0000000
Binary files a/webdata/icon.woff and /dev/null differ
diff --git a/webdata/index.html b/webdata/index.html
index 52fb46f..c93bbe3 100644
--- a/webdata/index.html
+++ b/webdata/index.html
@@ -1,100 +1,32 @@
-
-
-
-Remote Touchpad
-
-
-
-