Compare commits
315 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ac00c3b48 | |||
| ed7e8bae54 | |||
|
|
a77f3aa8ec | ||
|
|
9764d1975d | ||
|
|
5e68074781 | ||
|
|
f80b1d6ef7 | ||
|
|
346d9cf375 | ||
|
|
6ef2be8776 | ||
|
|
85b6d6ea9d | ||
|
|
1aa8f92cc8 | ||
|
|
996edb0501 | ||
|
|
a30bed4624 | ||
|
|
a2dfa7ac47 | ||
|
|
eebde58826 | ||
|
|
800171efdd | ||
|
|
2b3d5c5bf3 | ||
|
|
1add8e4cb8 | ||
|
|
06674b224f | ||
|
|
d48d9aed3d | ||
|
|
5771f44b0e | ||
|
|
16dc47b570 | ||
|
|
abd9fd4773 | ||
|
|
7c1652e9dd | ||
|
|
0fe41d0b15 | ||
|
|
21461d9a76 | ||
|
|
c6c54c77a7 | ||
|
|
7a806f7b00 | ||
|
|
4fb452c542 | ||
|
|
c71e5c4b8b | ||
|
|
30cfff86b0 | ||
|
|
288345b5d3 | ||
|
|
b4ecfad316 | ||
|
|
dd510bc4a0 | ||
|
|
4916fc59db | ||
|
|
064c3cd242 | ||
|
|
6996fbf958 | ||
|
|
016351e9d2 | ||
|
|
6325782058 | ||
|
|
0184dcb22f | ||
|
|
652ee5aa6c | ||
|
|
88fd6e7314 | ||
|
|
6a812607fe | ||
|
|
f11cefc74c | ||
|
|
7cff8286c8 | ||
|
|
280a654f59 | ||
|
|
d502ca651c | ||
|
|
1985842ff0 | ||
|
|
fc363fe404 | ||
|
|
cc143b59e6 | ||
|
|
02d49bf680 | ||
|
|
601d8c8358 | ||
|
|
0ca1431594 | ||
|
|
48ff3d67a0 | ||
|
|
e94fb986c0 | ||
|
|
7f7604a462 | ||
|
|
e1dbdc9b68 | ||
|
|
6c746b23f1 | ||
|
|
c8e6664a8d | ||
|
|
1803577892 | ||
|
|
4358c0b0dc | ||
|
|
737c9671cf | ||
|
|
925cb745eb | ||
|
|
722678ca4f | ||
|
|
c6fe56479a | ||
|
|
1794150285 | ||
|
|
6a1a7420dc | ||
|
|
13f5df3bf8 | ||
|
|
bb25f2dd37 | ||
|
|
af116f4cfb | ||
|
|
3c2bec8916 | ||
|
|
45801d7cd5 | ||
|
|
4285f6aa1b | ||
|
|
20cea84056 | ||
|
|
b239ca7160 | ||
|
|
e5369af219 | ||
|
|
51108765e3 | ||
|
|
37dff3cb6b | ||
|
|
891fde7051 | ||
|
|
91a37de0fb | ||
|
|
d81b57b1f9 | ||
|
|
0bd7260a0c | ||
|
|
e69bd15733 | ||
|
|
bbb6cc4dc1 | ||
|
|
d254ae5b0d | ||
|
|
809d3a0064 | ||
|
|
feaf02217c | ||
|
|
f5d19a0354 | ||
|
|
342334ac18 | ||
|
|
124152f7ec | ||
|
|
6f03281fc7 | ||
|
|
79a88dc676 | ||
|
|
9c5dcbee10 | ||
|
|
f30b4cf48e | ||
|
|
681d9c4f1d | ||
|
|
2fc5d0825b | ||
|
|
c1491f4a84 | ||
|
|
b96944d4d6 | ||
|
|
61f910adf0 | ||
|
|
518c6ef952 | ||
|
|
1b3d26c465 | ||
|
|
1675d5d2f8 | ||
|
|
8a2a29c9d6 | ||
|
|
e29982dc00 | ||
|
|
7522d131ec | ||
|
|
6134fa564b | ||
|
|
523d2039b6 | ||
|
|
771eea98b5 | ||
|
|
d6b75771c3 | ||
|
|
90922d85c6 | ||
|
|
0c401e0247 | ||
|
|
b748aceaf9 | ||
|
|
a9117b82ad | ||
|
|
9543c514d0 | ||
|
|
238ef14843 | ||
|
|
190b2763bd | ||
|
|
9432869bce | ||
|
|
61fa13974d | ||
|
|
e027c71341 | ||
|
|
a279e195be | ||
|
|
25d5cfa505 | ||
|
|
2211b8a662 | ||
|
|
89131db79b | ||
|
|
42dcafda3d | ||
|
|
9082ca434a | ||
|
|
98f4d1c692 | ||
|
|
fecec13869 | ||
|
|
1797d7585b | ||
|
|
db9ca255fa | ||
|
|
b3db523db1 | ||
|
|
51dbecaa63 | ||
|
|
ab09e7c674 | ||
|
|
56cff8deb2 | ||
|
|
ad3847f5aa | ||
|
|
1f3aa58568 | ||
|
|
41c3e0b10d | ||
|
|
7fc46bf38c | ||
|
|
b3a4ca7449 | ||
|
|
abc8ef5818 | ||
|
|
c0e6ef52ec | ||
|
|
15b05b5fa6 | ||
|
|
a4cac5fb54 | ||
|
|
7392eec8f1 | ||
|
|
3feddd5f68 | ||
|
|
42528c0cf7 | ||
|
|
c85275f263 | ||
|
|
facaacb3fb | ||
|
|
077dc6def0 | ||
|
|
e702b239eb | ||
|
|
cf8753698f | ||
|
|
632c944561 | ||
|
|
c749865ef1 | ||
|
|
24f3eb63ec | ||
|
|
a113b3adb7 | ||
|
|
a23a1260e9 | ||
|
|
5da7b376c9 | ||
|
|
957e116305 | ||
|
|
86cbc977b0 | ||
|
|
f741196640 | ||
|
|
9806aa26e1 | ||
|
|
d22dc0f4dd | ||
|
|
92a46e6637 | ||
|
|
cfad7e9d68 | ||
|
|
348ec1f6ab | ||
|
|
64327f326c | ||
|
|
dd2d13b4e4 | ||
|
|
02fa08e266 | ||
|
|
70e63a57af | ||
|
|
130c430d3d | ||
|
|
47e9bb2047 | ||
|
|
55b0ba894e | ||
|
|
e2ee520380 | ||
|
|
c53252c197 | ||
|
|
1ec541d78c | ||
|
|
1f22f5668a | ||
|
|
72d4d60419 | ||
|
|
c1a55e57ae | ||
|
|
aa903a664a | ||
|
|
82f62b7edb | ||
|
|
0b6dac0ae6 | ||
|
|
592f40006f | ||
|
|
5be09594df | ||
|
|
b95ffd4c1d | ||
|
|
d00c3dab85 | ||
|
|
8139cf0734 | ||
|
|
50af32094d | ||
|
|
84944c578d | ||
|
|
e24b06b0f6 | ||
|
|
50ec8ff4db | ||
|
|
6d68809ccf | ||
|
|
fd854f8b16 | ||
|
|
41ef45d345 | ||
|
|
69ca24da48 | ||
|
|
125e9a4d94 | ||
|
|
ae7a84ec64 | ||
|
|
a3e4d1c90a | ||
|
|
18752a362d | ||
|
|
c93c6c7de5 | ||
|
|
38e6049304 | ||
|
|
ba9ed841d8 | ||
|
|
e501c8cd9a | ||
|
|
585a3c0dcc | ||
|
|
1f410b5a18 | ||
|
|
9ad8590517 | ||
|
|
bd376578d4 | ||
|
|
7c0d475394 | ||
|
|
b432b95413 | ||
|
|
fad0e2d7c0 | ||
|
|
08b852f836 | ||
|
|
049d79448c | ||
|
|
afcfa8a67a | ||
|
|
071062fe77 | ||
|
|
330e3c1619 | ||
|
|
8e94866d92 | ||
|
|
e59145d8e0 | ||
|
|
a7963f2f70 | ||
|
|
86c570edc4 | ||
|
|
bac6c4ac0c | ||
|
|
7934a06207 | ||
|
|
f93806b469 | ||
|
|
dfba2b6f62 | ||
|
|
b5af659f79 | ||
|
|
dfa70a56b5 | ||
|
|
8e3e62a9de | ||
|
|
6092502dd9 | ||
|
|
de46eb2bd1 | ||
|
|
e2a4d17f8a | ||
|
|
390827829a | ||
|
|
1e46a174aa | ||
|
|
18b8d15af0 | ||
|
|
628912633b | ||
|
|
5bcda69519 | ||
|
|
ebf02bdbea | ||
|
|
f4a4cbee8b | ||
|
|
e4c0c92d03 | ||
|
|
da32043032 | ||
|
|
95e14dd5bc | ||
|
|
0ba1464157 | ||
|
|
875b7a1717 | ||
|
|
afd64d9733 | ||
|
|
21ad831022 | ||
|
|
09a57c70ed | ||
|
|
945169fb84 | ||
|
|
84b1329710 | ||
|
|
756ab3a03f | ||
|
|
0aa1ecfe33 | ||
|
|
a06707c5e9 | ||
|
|
147a4b2874 | ||
|
|
18e4a4b51d | ||
|
|
f0af4aaad4 | ||
|
|
2f76cbd05c | ||
|
|
235f6efbac | ||
|
|
56add5f796 | ||
|
|
2acdaf572d | ||
|
|
8e7c945f0f | ||
|
|
7edb092f93 | ||
|
|
361dc89614 | ||
|
|
8722dac897 | ||
|
|
d95acc275c | ||
|
|
9901e9e9ec | ||
|
|
491aa2fd3c | ||
|
|
92c3e0a75a | ||
|
|
cddda0d32d | ||
|
|
e0df49caad | ||
|
|
f6db7c0c08 | ||
|
|
0a647816cd | ||
|
|
88339bf3f2 | ||
|
|
1ddfffa843 | ||
|
|
34f4dbf77a | ||
|
|
b3b068fe59 | ||
|
|
07678e56ab | ||
|
|
4ca4889955 | ||
|
|
820149a140 | ||
|
|
374d665938 | ||
|
|
573c93582d | ||
|
|
c335af3d90 | ||
|
|
4b0329b97a | ||
|
|
cc231211b5 | ||
|
|
b96247c181 | ||
|
|
08a7df30e5 | ||
|
|
bc6d25458f | ||
|
|
057342bbad | ||
|
|
e341b559c7 | ||
|
|
4f4b697730 | ||
|
|
8a558c0437 | ||
|
|
33c9b815b0 | ||
|
|
ed74ab60e4 | ||
|
|
5a5ad3e6c6 | ||
|
|
b1f8f7ea1c | ||
|
|
f7fff5468c | ||
|
|
f2a7c1dc1d | ||
|
|
549de83fe3 | ||
|
|
1787e573cb | ||
|
|
67e1f31362 | ||
|
|
1fe8ae8b75 | ||
|
|
71d7cd42b9 | ||
|
|
c1509ac8fa | ||
|
|
209eaf960d | ||
|
|
e877947824 | ||
|
|
2eb0baaca1 | ||
|
|
fe568cd37c | ||
|
|
e73a18710b | ||
|
|
5fe430b3f2 | ||
|
|
f9ab379abc | ||
|
|
f2e67640fe | ||
|
|
57d3747968 | ||
|
|
a9230ceea4 | ||
|
|
26337f3a0e | ||
|
|
a296bd1188 | ||
|
|
3a8ecb0b88 | ||
|
|
5ef0e86ab8 | ||
|
|
931dff5f04 | ||
|
|
ad17454c53 | ||
|
|
82470705fd | ||
|
|
2fe8fb47b3 | ||
|
|
b5ed8ee23b |
43
.github/workflows/test.yaml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
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' }}
|
||||
11
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Binaries
|
||||
remote-touchpad
|
||||
|
||||
# Systemd service state
|
||||
.remote-touchpad.service.state
|
||||
|
||||
# Logs and run files
|
||||
.rts-log
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
220
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
# 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
|
||||
53
README.md
|
|
@ -1,9 +1,56 @@
|
|||
# Remote Touchpad
|
||||
|
||||
Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen).
|
||||
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 Linux/X11 and Windows.
|
||||
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.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
@ -12,3 +59,5 @@ Supports Linux/X11 and Windows.
|
|||

|
||||
|
||||

|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
package main
|
||||
|
||||
//go:generate go-bindata-assetfs -o ./bindata.generated.go webdata/...
|
||||
|
|
@ -4,6 +4,6 @@ Type=Application
|
|||
Name=Remote Touchpad
|
||||
Comment=Control mouse and keyboard from a smartphone
|
||||
Terminal=true
|
||||
Exec=remote-touchpad
|
||||
Icon=remote-touchpad
|
||||
Exec=remote-touchpad-wait-on-error
|
||||
Icon=com.github.unrud.RemoteTouchpad
|
||||
Categories=Network;RemoteAccess;ConsoleOnly;
|
||||
389
desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.github.unrud.RemoteTouchpad</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<name>Remote Touchpad</name>
|
||||
<summary>Control mouse and keyboard from a smartphone</summary>
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11.
|
||||
</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">com.github.unrud.RemoteTouchpad.desktop</launchable>
|
||||
<url type="bugtracker">https://github.com/Unrud/remote-touchpad/issues</url>
|
||||
<url type="homepage">https://github.com/Unrud/remote-touchpad</url>
|
||||
<url type="vcs-browser">https://github.com/Unrud/remote-touchpad</url>
|
||||
<developer_name translate="no">Unrud</developer_name>
|
||||
<developer id="io.github.unrud">
|
||||
<name translate="no">Unrud</name>
|
||||
</developer>
|
||||
<releases>
|
||||
<release version="1.5.4" date="2026-08-02">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Improve X11 keyboard input</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.5.3" date="2025-12-24">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.5.2" date="2025-10-12">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.5.0" date="2025-03-18">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add restore token support for the remote desktop portal</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.8" date="2024-09-01">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.7" date="2024-08-30">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.6" date="2024-02-06">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Prevent incorrect MIME types</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.5" date="2024-01-31">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Keep text input open after sending</li>
|
||||
<li>Workaround for virtual keyboard covering buttons (Chromium & iOS)</li>
|
||||
<li>Enable hardware keyboard input in mouse mode</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.4" date="2024-01-23">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fix Xwayland detection on newer servers</li>
|
||||
<li>Fix support for the KDE remote desktop portal</li>
|
||||
<li>Set correct minimum version for Go</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.3" date="2023-10-03">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.2" date="2023-05-20">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fix Windows support</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.1" date="2023-05-11">
|
||||
<description>
|
||||
<ul>
|
||||
<li>uinput: Fix vertical scroll direction</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.4.0" date="2023-04-25">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add feedback to buttons</li>
|
||||
<li>Add numeric keypad</li>
|
||||
<li>Prevent touch triggering mouse mode</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.3.0" date="2023-03-03">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Initial support for uinput</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.2.3" date="2022-12-29">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Improve XWayland detection</li>
|
||||
<li>Update dependencies</li>
|
||||
<li>Flatpak: Add Wayland socket to disable X11 socket</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.2.2" date="2022-09-23">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
<li>Fix race in RemoteDesktop portal controller</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.2.1" date="2022-06-20">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.2.0" date="2022-02-26">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add command-line options for move and scroll speed</li>
|
||||
<li>Add hardware mouse support</li>
|
||||
<li>Limit update rate</li>
|
||||
<li>Windows: Reverse vertical scroll direction</li>
|
||||
<li>Show error message if compiled without controller</li>
|
||||
<li>Don't show disabled controllers in "unsupported platform" error message</li>
|
||||
<li>Switch from go-bindata-assetfs to go:embed</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.1.0" date="2022-01-28">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add more shortcut keys (Arrows, Delete, Enter, …)</li>
|
||||
<li>Add basic support for physical keyboard</li>
|
||||
<li>Allow fullscreen on keys page</li>
|
||||
<li>Enable touchpad on keys page</li>
|
||||
<li>Retain keyboard text when reloading website</li>
|
||||
<li>Improve compatibility with old browsers</li>
|
||||
<li>Write URL and QR code to stdout</li>
|
||||
<li>Ignore unparseable IP</li>
|
||||
<li>Remove trailing new line from QR code</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.5" date="2022-01-07">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Use relative URL for websocket</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.4" date="2021-11-06">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Disable browser touch gestures</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.3" date="2021-09-18">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.2" date="2021-06-05">
|
||||
<description>
|
||||
<ul>
|
||||
<li>New logo</li>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.1" date="2020-09-13">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.0" date="2020-08-26">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add super and browser back and forward keys</li>
|
||||
<li>Reduce minimum size of textarea</li>
|
||||
<li>Small UI improvements</li>
|
||||
<li>Remove obsolete command line arguments</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.18" date="2020-05-16">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fix error messages for TLS certificate and private key files</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.17" date="2020-02-20">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Minor HTML improvements</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.16" date="2020-02-17">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fix textarea on WebKit</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.15" date="2019-11-03">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Prefer IPv4 addresses</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.14" date="2019-10-28">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Handle quirks of browser touch events</li>
|
||||
<li>Hide navigation bar in fullscreen mode</li>
|
||||
<li>Set terminal title</li>
|
||||
<li>Flatpak: Update dependencies</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.13" date="2019-05-04">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Flatpak: Update dependencies</li>
|
||||
<li>AppStream: Fix release descriptions</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.12" date="2018-12-08">
|
||||
<description>
|
||||
<ul>
|
||||
<li>fix the browser Forward button</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.11" date="2018-10-20">
|
||||
<description>
|
||||
<ul>
|
||||
<li>rename plugin to backend</li>
|
||||
<li>update flatpak</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.10" date="2018-10-01">
|
||||
<description>
|
||||
<ul>
|
||||
<li>avoid race condition when typing on X11</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.9" date="2018-09-29">
|
||||
<description>
|
||||
<ul>
|
||||
<li>add multimedia keys</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.8" date="2018-09-07">
|
||||
<description>
|
||||
<ul>
|
||||
<li>enable pointer movement after timeout</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.7" date="2018-09-04">
|
||||
<description>
|
||||
<ul>
|
||||
<li>use custom font for icons</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.6" date="2018-08-23">
|
||||
<description>
|
||||
<ul>
|
||||
<li>use colors in terminal for QR qode</li>
|
||||
<li>remove -invert command line argument</li>
|
||||
<li>mitigate race condition when typing text on X11</li>
|
||||
<li>improve KeySyms used for text on X11 and Flatpak's RemoteDesktop portal</li>
|
||||
<li>normalize line endings</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.5" date="2018-07-31">
|
||||
<description>
|
||||
<ul>
|
||||
<li>remove mouse polling interval</li>
|
||||
<li>add support for "pixel-perfect" scrolling</li>
|
||||
<li>add experimental support for Flatpak's RemoteDesktop portal</li>
|
||||
<li>X11 plugin: check session type</li>
|
||||
<li>improve error logging</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.4" date="2018-07-22">
|
||||
<description>
|
||||
<ul>
|
||||
<li>add setting for mouse polling interval</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.3" date="2018-06-26">
|
||||
<description>
|
||||
<ul>
|
||||
<li>fix websocket with https</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.2" date="2018-06-25">
|
||||
<description>
|
||||
<ul>
|
||||
<li>add reload button to disconnected-page</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.1" date="2018-06-23">
|
||||
<description>
|
||||
<p>initial release</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/1.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/3.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/4.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
</component>
|
||||
11
desktop/com.github.unrud.RemoteTouchpad.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="128" height="128" version="1.1" viewBox="0 0 33.867 33.867" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m31.75 22.754v1.0583c0 1.1726-0.94403 2.1167-2.1167 2.1167h-25.4c-1.1726 0-2.1167-0.94403-2.1167-2.1167v-1.0583z" fill="#cacccc"/>
|
||||
<rect x="2.1167" y="8.9958" width="29.633" height="15.875" rx="2.1167" ry="2.1167" fill="#eaeced"/>
|
||||
<rect x="29.898" y="15.346" width=".52917" height="3.175" rx=".26458" ry=".26458" fill="#cacccc"/>
|
||||
<rect x="5.2917" y="10.054" width="23.283" height="13.758" fill="#5e5c64"/>
|
||||
<rect x="3.175" y="13.758" width="1.0583" height="6.35" rx=".52917" ry=".5919" fill="#cacccc"/>
|
||||
<path d="m9.0777 25.71c-0.00214 0.23918 0.060837 0.44999 0.19063 0.57978l4.5382 4.5377 0.32814 0.32815c0.38896 0.3767 0.90809 0.58989 1.4506 0.59169h7.1107c1.1579-2.3e-5 2.0965-0.93865 2.0965-2.0965v-1.059h-10l-3.9818-3.9402h-1.7328z" fill="#dea560"/>
|
||||
<circle cx="15.316" cy="16.593" r="3.175" fill="#62a0ea" fill-opacity=".5"/>
|
||||
<path d="m15.316 15.469c-0.62555 5e-6 -1.1328 0.52585-1.1327 0.97927v9.1845c-6.2e-5 0.14239-0.11548 0.2578-0.25786 0.25786-0.0683-2.46e-4 -0.13371-0.02756-0.1819-0.07596l-5.3e-4 5.29e-4 -2.529-2.529c-0.32061-0.32062-1.1278-0.25715-1.665 0.28009-0.53724 0.53725-0.60122 1.3449-0.2806 1.6655l4.5382 4.5377 0.32814 0.32815c0.38896 0.3767 0.90809 0.58989 1.4506 0.59169h7.1107c1.1579-2.4e-5 2.0965-0.93865 2.0965-2.0965v-5.9392c4e-6 -0.45341-0.5072-0.97926-1.1327-0.97927-0.62555 1.3e-5 -1.1328 0.52585-1.1327 0.97927v0.50385c-5.8e-5 0.14239-0.11547 0.25781-0.25786 0.25787-0.14239-5.9e-5 -0.25781-0.11548-0.25786-0.25787v-1.603c2e-6 -0.45341-0.50721-0.97926-1.1327-0.97927-0.62554 1e-5 -1.1328 0.52586-1.1327 0.97927v0.78651c-5.9e-5 0.14239-0.11548 0.25781-0.25787 0.25786-0.14239-5.8e-5 -0.25781-0.11548-0.25786-0.25786v-1.4159c4e-6 -0.45341-0.50721-0.97926-1.1327-0.97927-0.62554 6e-6 -1.1328 0.52586-1.1327 0.97927v1.4159c-5.9e-5 0.14239-0.11547 0.25781-0.25786 0.25786-0.14239-5.8e-5 -0.25781-0.11548-0.25786-0.25786v-5.8927c5e-6 -0.45341-0.5072-0.97926-1.1327-0.97927z" fill="#ffbe6f"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2 KiB |
8
desktop/remote-touchpad-wait-on-error
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
remote-touchpad "$@"
|
||||
if [ $? -eq 1 ]; then
|
||||
echo
|
||||
read -n 1 -s -r -p "Press any key to close"
|
||||
echo
|
||||
fi
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>remote-touchpad.desktop</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>AGPL-3.0-or-later</project_license>
|
||||
<name>Remote Touchpad</name>
|
||||
<summary>Control mouse and keyboard from a smartphone</summary>
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Supports Linux/X11 and Windows.
|
||||
</p>
|
||||
</description>
|
||||
<url type="bugtracker">https://github.com/Unrud/remote-touchpad/issues</url>
|
||||
<url type="homepage">https://github.com/Unrud/remote-touchpad</url>
|
||||
<developer_name>Unrud</developer_name>
|
||||
<releases>
|
||||
<release version="0.0.2" date="2018-06-25" />
|
||||
<release version="0.0.1" date="2018-06-23" />
|
||||
</releases>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.2/screenshots/1.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.2/screenshots/2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.2/screenshots/3.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
</component>
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"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",
|
||||
"sdk-extensions" : [
|
||||
"org.freedesktop.Sdk.Extension.golang"
|
||||
],
|
||||
"finish-args" : [
|
||||
"--socket=x11",
|
||||
"--share=network"
|
||||
],
|
||||
"modules" : [
|
||||
{
|
||||
"name" : "remote-touchpad",
|
||||
"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" : [
|
||||
". /usr/lib/sdk/golang/enable.sh; 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
52
flatpak/com.github.unrud.RemoteTouchpad.yaml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
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
|
||||
17
flatpak/modules.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# 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
|
||||
12
go.mod
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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
|
||||
10
go.sum
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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=
|
||||
67
host.go
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
* Copyright (c) 2018-2019 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Foobar is free software: you can redistribute it and/or modify
|
||||
* 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.
|
||||
|
|
@ -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 Foobar. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
|
@ -22,11 +22,25 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FindDefaultHost() string {
|
||||
fallbackAddr := ""
|
||||
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()})
|
||||
}
|
||||
for _, publicIP := range []string{"2001:4860:4860::8888", "8.8.8.8"} {
|
||||
addr := fmt.Sprintf("[%s]:80", publicIP)
|
||||
conn, err := net.Dial("udp", addr)
|
||||
|
|
@ -36,43 +50,40 @@ func FindDefaultHost() string {
|
|||
conn.Close()
|
||||
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
continue
|
||||
panic(err)
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
addIP(100, ip)
|
||||
}
|
||||
return host
|
||||
}
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
goto fallback
|
||||
}
|
||||
interfaces, _ := net.Interfaces()
|
||||
for _, inter := range interfaces {
|
||||
if inter.Flags&net.FlagUp == 0 || inter.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := inter.Addrs()
|
||||
if err != nil {
|
||||
if inter.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
addrs, _ := inter.Addrs()
|
||||
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
|
||||
}
|
||||
if fallbackAddr == "" {
|
||||
fallbackAddr = ip.String()
|
||||
}
|
||||
for _, linkLocalPrefix := range []string{"169.254.", "fe8", "fe9", "fea", "feb"} {
|
||||
for _, linkLocalPrefix := range []string{
|
||||
"169.254.", "fe8", "fe9", "fea", "feb",
|
||||
} {
|
||||
if strings.HasPrefix(ip.String(), linkLocalPrefix) {
|
||||
addIP(20, ip)
|
||||
continue addrs
|
||||
}
|
||||
}
|
||||
return ip.String()
|
||||
addIP(30, ip)
|
||||
}
|
||||
|
||||
}
|
||||
fallback:
|
||||
if fallbackAddr != "" {
|
||||
return fallbackAddr
|
||||
}
|
||||
return "localhost"
|
||||
sort.Slice(hosts, func(i, j int) bool { return hosts[i].prio > hosts[j].prio })
|
||||
return hosts[0].host
|
||||
}
|
||||
|
|
|
|||
121
icon-font/U+1F507 Speaker with Cancellation Stroke.svg
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+1F507 Speaker with Cancellation Stroke.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.51998147"
|
||||
inkscape:cx="173.85039"
|
||||
inkscape:cy="614.2482"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 484.74219 165.21094 C 481.46507 165.30684 478.30896 166.474 475.75781 168.5332 L 282.64453 324.57617 L 34.949219 324.57617 C 26.665288 324.577 19.95005 331.2922 19.949219 339.57617 L 19.949219 644.40039 C 19.95005 652.68432 26.665288 659.39956 34.949219 659.40039 L 262.81641 659.40039 L 475.75781 831.46484 C 485.56622 839.38586 500.17721 832.40823 500.18359 819.80078 L 500.18359 180.20508 C 500.18259 171.74797 493.19563 164.96209 484.74219 165.21094 z M 662.77734 316.65039 C 658.87578 316.70784 655.14976 318.28384 652.39062 321.04297 L 617.19531 356.24023 C 611.3367 362.09869 611.3367 371.59662 617.19531 377.45508 L 739.73438 500.00195 L 617.19531 622.54883 C 611.33954 628.40437 611.33588 637.89811 617.1875 643.75781 L 652.39062 678.96094 C 658.247 684.82162 667.74685 684.82528 673.60742 678.96875 L 796.1543 556.41602 L 918.69922 678.96875 C 924.55979 684.82528 934.05964 684.82162 939.91602 678.96094 L 975.11914 643.75781 C 980.97076 637.89811 980.96905 628.40437 975.11328 622.54883 L 852.57227 500.00195 L 975.11328 377.45508 C 980.97189 371.59662 980.97189 362.09869 975.11328 356.24023 L 939.91602 321.04297 C 934.05756 315.18436 924.55768 315.18436 918.69922 321.04297 L 796.1543 443.58984 L 673.60742 321.04297 C 670.73974 318.17529 666.8324 316.59049 662.77734 316.65039 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
id="path828" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
121
icon-font/U+1F509 Speaker with One Sound Wave.svg
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+1F509 Speaker with One Sound Wave.svg.2018_09_27_20_14_27.0.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.73536486"
|
||||
inkscape:cx="732.0533"
|
||||
inkscape:cy="512.86977"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 484.74219 165.21094 C 481.46507 165.30679 478.30896 166.474 475.75781 168.5332 L 282.64453 324.57617 L 34.949219 324.57617 C 26.665288 324.577 19.95005 331.2922 19.949219 339.57617 L 19.949219 644.40039 C 19.95005 652.68432 26.665288 659.39956 34.949219 659.40039 L 262.81641 659.40039 L 475.75781 831.46484 C 485.56622 839.38586 500.17721 832.40823 500.18359 819.80078 L 500.18359 180.20508 C 500.18223 171.74797 493.19563 164.96209 484.74219 165.21094 z M 596.02148 305.23047 C 594.17662 305.21535 592.34405 305.54205 590.61719 306.19141 L 520.87305 332.35156 C 513.10865 335.26429 509.18062 343.92497 512.10352 351.68555 C 529.44066 397.74521 538.92969 447.67401 538.92969 499.94336 C 538.92969 547.59292 531.03301 593.28945 516.50391 635.89648 C 513.94392 643.40353 517.66997 651.60435 525.00781 654.61523 L 594.06445 682.95508 C 602.00112 686.21367 611.05039 682.16384 613.9082 674.07422 C 633.15681 619.5579 643.63477 560.92547 643.63477 499.94336 C 643.63477 434.88046 631.69314 372.49004 609.91406 314.92383 C 607.72481 309.13737 602.20797 305.28746 596.02148 305.23047 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
id="path828" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
121
icon-font/U+1F50A Speaker with Three Sound Waves.svg
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+1F50A Speaker with Three Sound Waves.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="1.039963"
|
||||
inkscape:cx="755.38558"
|
||||
inkscape:cy="561.27698"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 484.74219 165.21094 C 481.46507 165.30679 478.30896 166.474 475.75781 168.5332 L 282.64453 324.57617 L 34.949219 324.57617 C 26.665288 324.577 19.95005 331.2922 19.949219 339.57617 L 19.949219 644.40039 C 19.95005 652.68432 26.665288 659.39956 34.949219 659.40039 L 262.81641 659.40039 L 475.75781 831.46484 C 485.56622 839.38586 500.17721 832.40823 500.18359 819.80078 L 500.18359 180.20508 C 500.18223 171.74797 493.19563 164.96209 484.74219 165.21094 z M 876.86523 199.69336 C 875.19506 199.73399 873.54347 200.05184 871.97852 200.63672 L 800.04883 227.61914 C 790.72609 231.12057 787.34036 242.56637 793.25781 250.57617 C 844.71918 320.24234 875.10742 406.37235 875.10742 499.94336 C 875.10742 591.93283 845.74202 676.72496 795.85547 745.75977 C 790.17219 753.62504 793.34048 764.73472 802.31641 768.42188 L 872.73047 797.32227 C 879.31705 800.0224 886.89572 797.72283 890.86914 791.81641 C 944.66917 711.81469 977.04812 616.16489 979.64258 513.34961 C 979.64417 513.22413 979.64417 513.09814 979.64258 512.97266 L 979.64258 486.90625 C 979.64417 486.78077 979.64417 486.65673 979.64258 486.53125 C 977.02866 382.94396 944.18613 286.62909 889.6582 206.26172 C 886.78996 202.0373 881.96971 199.56343 876.86523 199.69336 z M 739.73242 251.22461 C 737.97188 251.23973 736.22799 251.5658 734.58008 252.18555 L 664.35547 278.52344 C 655.82938 281.72349 652.09733 291.72067 656.44141 299.72461 C 688.6996 359.18599 707.01563 427.31195 707.01562 499.94336 C 707.01562 569.50662 690.20981 634.92705 660.45117 692.57422 C 656.41554 700.393 659.94447 709.99017 668.08398 713.33203 L 737.14062 741.67773 C 744.39754 744.6574 752.72396 741.54392 756.24414 734.5332 C 791.72147 663.91099 811.72067 584.17527 811.7207 499.94336 C 811.7207 413.30635 790.56195 331.42543 753.16602 259.32422 C 750.56816 254.31194 745.37775 251.18088 739.73242 251.22461 z M 596.02148 305.23047 C 594.17662 305.21535 592.34405 305.54205 590.61719 306.19141 L 520.87305 332.35156 C 513.10865 335.26429 509.18062 343.92497 512.10352 351.68555 C 529.44066 397.74521 538.92969 447.67401 538.92969 499.94336 C 538.92969 547.59292 531.03301 593.28945 516.50391 635.89648 C 513.94392 643.40353 517.66997 651.60435 525.00781 654.61523 L 594.06445 682.95508 C 602.00112 686.21367 611.05039 682.16384 613.9082 674.07422 C 633.15681 619.5579 643.63477 560.92547 643.63477 499.94336 C 643.63477 434.88046 631.69314 372.49004 609.91406 314.92383 C 607.72481 309.13737 602.20797 305.28746 596.02148 305.23047 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
id="path828" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
121
icon-font/U+1FA9F Window.svg
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="U+1FA9F Window Emoji.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="600.55213"
|
||||
inkscape:cx="333.69173"
|
||||
inkscape:zoom="1"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
d="m 144.70288,175.30013 h 112.77019 c 1.00766,0 1.81888,0.81121 1.81888,1.81887 v 112.77098 c 0,1.00766 -0.81122,1.81887 -1.81888,1.81887 H 144.70288 c -1.00766,0 -1.81888,-0.81121 -1.81888,-1.81887 V 177.119 c 0,-1.00766 0.81122,-1.81887 1.81888,-1.81887 z m -137.5920073,0 H 119.88093 c 1.00766,0 1.81887,0.81121 1.81887,1.81887 v 112.77098 c 0,1.00766 -0.81121,1.81887 -1.81887,1.81887 H 7.1108727 c -1.0076563,0 -1.8188743,-0.81121 -1.8188743,-1.81887 V 177.119 c 0,-1.00766 0.811218,-1.81887 1.8188743,-1.81887 z M 144.70288,37.707294 h 112.77019 c 1.00766,0 1.81888,0.811219 1.81888,1.818875 V 152.29702 c 0,1.00766 -0.81122,1.81887 -1.81888,1.81887 H 144.70288 c -1.00766,0 -1.81888,-0.81121 -1.81888,-1.81887 V 39.526169 c 0,-1.007656 0.81122,-1.818875 1.81888,-1.818875 z m -137.5920073,0 H 119.88093 c 1.00766,0 1.81887,0.811219 1.81887,1.818875 V 152.29702 c 0,1.00766 -0.81121,1.81887 -1.81887,1.81887 H 7.1108727 c -1.0076563,0 -1.8188743,-0.81121 -1.8188743,-1.81887 V 39.526169 c 0,-1.007656 0.811218,-1.818875 1.8188743,-1.818875 z"
|
||||
style="fill:#000000;stroke-width:0.0328268;stop-color:#000000"
|
||||
id="rect857" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
122
icon-font/U+2190 Leftwards Arrow.svg
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="U+2B60 Leftwards Triangle-Headed Arrow .svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="398.16955"
|
||||
inkscape:cx="369.80754"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
d="m 111.13772,76.12716 c 2.15968,0.04486 3.88735,1.808597 3.8871,3.96875 v 59.46045 c 2.2e-4,2.19179 1.77696,3.96853 3.96875,3.96875 h 136.32811 c 2.19179,2.2e-4 3.96853,1.77696 3.96875,3.96875 v 34.43046 c -2.2e-4,2.19179 -1.77696,3.96854 -3.96875,3.96875 H 118.99357 c -2.19179,2.2e-4 -3.96853,1.77696 -3.96875,3.96875 v 59.46045 c -0.002,3.33572 -3.86802,5.18189 -6.46317,3.08612 L 6.7461621,167.79495 c -1.9655102,-1.58863 -1.9655102,-4.58516 0,-6.17379 L 108.56165,77.007725 c 0.72802,-0.588163 1.64034,-0.899858 2.57607,-0.880565 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
123
icon-font/U+2191 Upwards Arrow.svg
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2191 Upwards Arrow.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="397.51809"
|
||||
inkscape:cx="371.25037"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
d="m 220.86574,143.56816 c -0.0449,2.15968 -1.8086,3.88735 -3.96875,3.8871 h -59.46045 c -2.19179,2.2e-4 -3.96853,1.77696 -3.96875,3.96875 v 136.32811 c -2.2e-4,2.19179 -1.77696,3.96853 -3.96875,3.96875 h -34.43046 c -2.19179,-2.2e-4 -3.96854,-1.77696 -3.96875,-3.96875 V 151.42401 c -2.2e-4,-2.19179 -1.77696,-3.96853 -3.96875,-3.96875 H 47.670631 c -3.33572,-0.002 -5.18189,-3.86802 -3.08612,-6.46317 L 129.19795,39.176603 c 1.58863,-1.96551 4.58516,-1.96551 6.17379,0 l 84.61344,101.815487 c 0.58816,0.72802 0.89985,1.64034 0.88056,2.57607 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
123
icon-font/U+2192 Rightwards Arrow.svg
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="U+2B95 Rightwards Black Arrow .svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="398.16955"
|
||||
inkscape:cx="369.80754"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
d="m 579.87305,165.20508 c -8.16257,0.16955 -14.69235,6.83564 -14.69141,15 V 404.9375 c -8.3e-4,8.28393 -6.71607,14.99917 -15,15 H 34.925781 c -8.283925,8.3e-4 -14.999164,6.71607 -15,15 v 130.13086 c 8.36e-4,8.28393 6.716075,14.99921 15,15 H 550.18164 c 8.28393,8.3e-4 14.99917,6.71607 15,15 v 224.73242 c 0.008,12.60745 14.61929,19.58508 24.42774,11.66406 L 974.42383,511.66602 c 7.4287,-6.00428 7.4287,-17.32972 0,-23.33399 L 589.60938,168.5332 c -2.75158,-2.22298 -6.19972,-3.40104 -9.73633,-3.32812 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
123
icon-font/U+2193 Downwards Arrow.svg
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2193 Downwards Arrow.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="397.51809"
|
||||
inkscape:cx="371.25037"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
d="m 43.696719,185.85518 c 0.04486,-2.15968 1.808597,-3.88735 3.96875,-3.8871 h 59.460451 c 2.19179,-2.2e-4 3.96853,-1.77696 3.96875,-3.96875 V 41.671221 c 2.2e-4,-2.19179 1.77696,-3.96853 3.96875,-3.96875 h 34.43046 c 2.19179,2.2e-4 3.96854,1.77696 3.96875,3.96875 V 177.99933 c 2.2e-4,2.19179 1.77696,3.96853 3.96875,3.96875 h 59.46045 c 3.33572,0.002 5.18189,3.86802 3.08612,6.46317 l -84.61344,101.81549 c -1.58863,1.96551 -4.58516,1.96551 -6.17379,0 L 44.577284,188.43125 c -0.588163,-0.72802 -0.899858,-1.64034 -0.880565,-2.57607 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
120
icon-font/U+21BB Clockwise Open Circle Arrow.svg
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+21BB Clockwise Open Circle Arrow.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.80283551"
|
||||
inkscape:cx="-178.37846"
|
||||
inkscape:cy="629.41539"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg964">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:21.16666603;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="M 128.96474,82.25221 A 82.523797,82.523797 0 0 0 91.926171,92.736843 82.523797,82.523797 0 0 0 60.331201,205.0845 82.523797,82.523797 0 0 0 172.67886,236.67999 l -10.35647,-18.4614 a 61.356078,61.356078 0 0 1 -83.5303,-23.49056 61.356078,61.356078 0 0 1 23.49107,-83.5303 61.356078,61.356078 0 0 1 83.51066,23.45955 l -39.06066,-10.98486 c -1.41105,-0.39682 -2.86654,0.41933 -3.26337,1.83038 l -4.30723,15.31793 c -0.39682,1.41105 0.41933,2.86602 1.83038,3.26285 l 71.42303,20.08611 c 1.38301,0.33929 2.78648,-0.46935 3.175,-1.85105 l 20.06286,-71.339832 c 0.39683,-1.411052 -0.41983,-2.866535 -1.8309,-3.263366 l -15.32258,-4.308781 c -1.37713,-0.387292 -2.79339,0.382341 -3.22977,1.731161 l -11.0138,39.162988 -0.002,0.001 a 82.523797,82.523797 0 0 0 -75.29004,-42.0496 z"
|
||||
id="path894"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
136
icon-font/U+2261 Identical To.svg
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+2261 Identical To .svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.56769045"
|
||||
inkscape:cx="861.38493"
|
||||
inkscape:cy="500.27264"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg964"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:measure-start="406.912,419.243"
|
||||
inkscape:measure-end="406.912,580.422"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
id="path827"
|
||||
d="m 220.87358,125.65977 c -2.2e-4,-2.19179 -1.77696,-3.96852 -3.96875,-3.96875 H 47.678502 c -2.191787,2.3e-4 -3.968527,1.77696 -3.968747,3.96875 v 13.26379 c 2.2e-4,2.19179 1.77696,3.96854 3.968747,3.96875 H 216.90483 c 2.19179,-2.1e-4 3.96853,-1.77696 3.96875,-3.96875 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 220.87358,182.16411 c -2.2e-4,-2.19179 -1.77696,-3.96852 -3.96875,-3.96875 H 47.678502 c -2.191787,2.3e-4 -3.968527,1.77696 -3.968747,3.96875 v 13.26379 c 2.2e-4,2.19179 1.77696,3.96854 3.968747,3.96875 H 216.90483 c 2.19179,-2.1e-4 3.96853,-1.77696 3.96875,-3.96875 z"
|
||||
id="path200"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 220.87358,69.155427 c -2.2e-4,-2.19179 -1.77696,-3.96852 -3.96875,-3.96875 H 47.678502 c -2.191787,2.3e-4 -3.968527,1.77696 -3.968747,3.96875 v 13.263781 c 2.2e-4,2.19179 1.77696,3.968537 3.968747,3.968747 H 216.90483 c 2.19179,-2.1e-4 3.96853,-1.776957 3.96875,-3.968747 z"
|
||||
id="path202"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
123
icon-font/U+2326 Erase To the Right.svg
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2326 Erase To the Right.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="432.54171"
|
||||
inkscape:cx="961.39837"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
id="path841"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 153.42494,76.12716 c 0.93573,-0.01929 1.84805,0.292402 2.57607,0.880565 L 257.8165,161.62116 c 1.96551,1.58863 1.96551,4.58516 0,6.17379 l -101.81549,84.61344 c -0.81646,0.65934 -1.75837,0.92725 -2.66651,0.88263 H 9.2409757 c -2.1917898,-2.2e-4 -3.9685409,-1.77696 -3.96875,-3.96875 V 181.92432 147.49386 80.09591 c 2.091e-4,-2.19179 1.7769602,-3.96853 3.96875,-3.96875 z M 45.447551,116.19655 c -1.015892,0 -2.03204,0.38767 -2.807065,1.16272 l -9.312609,9.31261 c -1.55009,1.55005 -1.55009,4.06304 0,5.61309 l 32.422309,32.42386 -32.422309,32.42386 c -1.549791,1.54928 -1.549791,4.06118 -0.0016,5.61155 l 9.314159,9.31416 c 1.5495,1.55057 4.062999,1.5516 5.613611,0.002 l 32.423343,-32.42541 32.42386,32.42541 c 1.55061,1.5496 4.06411,1.54857 5.61361,-0.002 l 9.31416,-9.31416 c 1.54831,-1.55038 1.54727,-4.06227 -0.002,-5.61155 L 95.605211,164.70883 128.027,132.28497 c 1.55009,-1.55005 1.55009,-4.06304 0,-5.61309 l -9.31209,-9.31261 c -0.73003,-0.73002 -1.71586,-1.14701 -2.74815,-1.16221 -1.0729,-0.0158 -2.10672,0.40347 -2.86546,1.16221 L 80.67744,149.78313 48.254097,117.35927 c -0.775025,-0.77505 -1.790657,-1.16272 -2.806546,-1.16272 z" />
|
||||
<g
|
||||
id="g22" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
94
icon-font/U+2328 Keyboard.svg
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+2328 Keyboard.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview7"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.6675088"
|
||||
inkscape:cx="36.943583"
|
||||
inkscape:cy="434.3724"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide816"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide814"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-35.062481)"
|
||||
id="layer1"
|
||||
style="fill:#000000"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
id="rect828"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
d="m 70,220 c -27.700001,0 -50,22.3 -50,50 v 480 c 0,27.7 22.299999,50 50,50 h 860 c 27.7,0 50,-22.3 50,-50 V 270 c 0,-27.7 -22.3,-50 -50,-50 z m 65,60 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z M 135,440 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z m 160,0 h 90 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 h -90 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z M 260,610 h 480 c 5.54,0 10,4.46 10,10 v 90 c 0,5.54 -4.46,10 -10,10 H 260 c -5.54,0 -10,-4.46 -10,-10 v -90 c 0,-5.54 4.46,-10 10,-10 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.98675704"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
125
icon-font/U+232B Erase To the Left.svg
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+232B Erase To the Left.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="432.54171"
|
||||
inkscape:cx="961.39837"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
id="path841"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 420.04883 165.20508 C 416.51221 165.13217 413.06407 166.31022 410.3125 168.5332 L 25.498047 488.33203 C 18.069348 494.3363 18.069348 505.66174 25.498047 511.66602 L 410.3125 831.46484 C 413.39833 833.95684 416.95828 834.96942 420.39062 834.80078 L 964.99609 834.80078 C 973.28002 834.79995 979.9953 828.08471 979.99609 819.80078 L 979.99609 565.06836 L 979.99609 434.9375 L 979.99609 180.20508 C 979.9953 171.92115 973.28002 165.20591 964.99609 165.20508 L 420.04883 165.20508 z M 828.15234 316.64844 C 831.99193 316.64844 835.83249 318.11366 838.76172 321.04297 L 873.95898 356.24023 C 879.81759 362.09869 879.81759 371.59662 873.95898 377.45508 L 751.41797 500.00195 L 873.95898 622.54883 C 879.81646 628.40437 879.81646 637.89815 873.96484 643.75781 L 838.76172 678.96094 C 832.90534 684.82136 823.4055 684.82528 817.54492 678.96875 L 695 556.41602 L 572.45312 678.96875 C 566.59255 684.82551 557.09271 684.82159 551.23633 678.96094 L 516.0332 643.75781 C 510.18132 637.89811 510.18524 628.40437 516.04102 622.54883 L 638.58008 500.00195 L 516.04102 377.45508 C 510.18241 371.59662 510.18241 362.09869 516.04102 356.24023 L 551.23633 321.04297 C 553.9955 318.28384 557.72148 316.70784 561.62305 316.65039 C 565.6781 316.59052 569.58545 318.17529 572.45312 321.04297 L 695 443.58984 L 817.54492 321.04297 C 820.47415 318.11366 824.31276 316.64844 828.15234 316.64844 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)" />
|
||||
<g
|
||||
id="g22" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+23ED Black Right-Pointing Double Triangle with Vertical Bar.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.55461861"
|
||||
inkscape:cx="826.41907"
|
||||
inkscape:cy="532.06492"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 255.29862,76.12716 c 2.19179,2.2e-4 3.96854,1.77696 3.96875,3.96875 v 169.22636 c -2.1e-4,2.19179 -1.77696,3.96853 -3.96875,3.96875 h -34.43053 c -2.19179,-2.2e-4 -3.96853,-1.77696 -3.96875,-3.96875 v -79.51401 l -99.32387,82.60013 c -2.59514,2.09577 -6.46148,0.2496 -6.46317,-3.08612 v -79.51814 l -99.329557,82.60426 c -2.5951393,2.09577 -6.4609663,0.2496 -6.4626543,-3.08612 V 80.09591 c 3.598e-4,-2.23761 1.8488977,-4.033041 4.085537,-3.967199 0.8670713,0.02536 1.7021253,0.334182 2.3771173,0.879014 L 111.1123,159.61198 V 80.09591 c 3.6e-4,-2.23761 1.84941,-4.033041 4.08605,-3.967199 0.86707,0.02536 1.70213,0.334182 2.37712,0.879014 l 99.32387,82.600125 V 80.09591 c 2.2e-4,-2.19179 1.77696,-3.96853 3.96875,-3.96875 z"
|
||||
id="path832"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+23EE Black Left-Pointing Double Triangle with Vertical Bar.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.55461861"
|
||||
inkscape:cx="406.82297"
|
||||
inkscape:cy="697.5945"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 9.2888388,76.127161 c -2.19179,2.2e-4 -3.96854,1.77696 -3.96875,3.96875 V 249.32227 c 2.1e-4,2.19179 1.77696,3.96853 3.96875,3.96875 H 43.719369 c 2.19179,-2.2e-4 3.96853,-1.77696 3.96875,-3.96875 v -79.51401 l 99.323871,82.60013 c 2.59514,2.09577 6.46148,0.2496 6.46317,-3.08612 v -79.51814 l 99.32956,82.60426 c 2.59514,2.09577 6.46096,0.2496 6.46265,-3.08612 V 80.095911 c -3.6e-4,-2.23761 -1.8489,-4.033041 -4.08554,-3.967199 -0.86707,0.02536 -1.70212,0.334182 -2.37711,0.879014 L 153.47516,159.61198 V 80.095911 c -2.6e-4,-2.23761 -1.84941,-4.033041 -4.08605,-3.967199 -0.86707,0.02536 -1.70213,0.334182 -2.37712,0.879014 L 47.688119,159.60785 V 80.095911 c -2.2e-4,-2.19179 -1.77696,-3.96853 -3.96875,-3.96875 z"
|
||||
id="path832-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+23EF Black Right-Pointing Triangle with Double Vertical Bar.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.39217458"
|
||||
inkscape:cx="1029.7358"
|
||||
inkscape:cy="424.58204"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="false">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 34.798828 165.20508 C 26.636227 165.37463 20.106477 172.04072 20.107422 180.20508 L 20.107422 819.80078 C 20.113822 832.40823 34.726741 839.38586 44.535156 831.46484 L 429.34961 511.66602 C 436.77831 505.66174 436.77831 494.3363 429.34961 488.33203 L 44.535156 168.5332 C 41.783607 166.31022 38.335407 165.13216 34.798828 165.20508 z M 562.77734 165.20508 C 554.49341 165.20591 547.77818 171.92115 547.77734 180.20508 L 547.77734 819.80078 C 547.77818 828.08471 554.49341 834.79995 562.77734 834.80078 L 692.9082 834.80078 C 701.19213 834.79995 707.90741 828.08471 707.9082 819.80078 L 707.9082 180.20508 C 707.90741 171.92115 701.19213 165.20591 692.9082 165.20508 L 562.77734 165.20508 z M 834.77734 165.20508 C 826.49341 165.20591 819.77818 171.92115 819.77734 180.20508 L 819.77734 819.80078 C 819.77818 828.08471 826.49341 834.79995 834.77734 834.80078 L 964.9082 834.80078 C 973.19213 834.79995 979.90741 828.08471 979.9082 819.80078 L 979.9082 180.20508 C 979.90741 171.92115 973.19213 165.20591 964.9082 165.20508 L 834.77734 165.20508 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
id="path832" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
121
icon-font/U+27A3 Three-D Bottom-Lighted Rightwards Arrowhead.svg
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+27A3 Three-D Bottom-Lighted Rightwards Arrowhead.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.40141775"
|
||||
inkscape:cx="-696.51465"
|
||||
inkscape:cy="600.56652"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg964"
|
||||
inkscape:snap-global="true">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 8.7736245,75.791674 c -0.123916,-0.0016 -0.2452709,0.0044 -0.3653523,0.01344 a 3.9691468,3.9691468 0 0 0 -0.067179,0.0062 C 7.2317321,75.913716 6.2976727,76.45878 5.6601195,77.238097 a 3.9691468,3.9691468 0 0 0 -0.028422,0.03514 c -0.075952,0.095 -0.1484805,0.192712 -0.2154907,0.294039 a 3.9691468,3.9691468 0 0 0 -0.019637,0.03101 c -0.057995,0.08945 -0.1120172,0.181192 -0.1627807,0.274919 a 3.9691468,3.9691468 0 0 0 -0.03824,0.07183 c -0.054268,0.105191 -0.1039474,0.212806 -0.1488281,0.322461 a 3.9691468,3.9691468 0 0 0 -0.00982,0.02481 c -0.3795162,0.945056 -0.4031882,2.039264 0.054261,3.065962 a 3.9691468,3.9691468 0 0 0 0.010335,0.02222 c 0.045024,0.09943 0.094615,0.198189 0.1488282,0.296106 a 3.9691468,3.9691468 0 0 0 0.056327,0.103353 L 51.196358,159.41706 h 5.16e-4 l 0.154512,0.26097 -46.1444686,88.1388 a 3.9691468,3.9691468 0 0 0 4.8772216,5.56814 L 256.69337,163.28763 a 3.9691468,3.9691468 0 0 0 2.59829,-3.87057 h 0.006 c -0.002,-0.046 -0.007,-0.0913 -0.0103,-0.13694 a 3.9691468,3.9691468 0 0 0 -0.005,-0.0667 c -0.0102,-0.11599 -0.0244,-0.23085 -0.0444,-0.34416 a 3.9691468,3.9691468 0 0 0 -0.004,-0.0253 c -0.0235,-0.12819 -0.0528,-0.25426 -0.0884,-0.37827 -2e-4,-6.9e-4 -3.2e-4,-0.001 -5.2e-4,-0.002 a 3.9691468,3.9691468 0 0 0 -0.003,-0.007 c -0.0328,-0.11324 -0.0712,-0.22416 -0.11369,-0.33331 a 3.9691468,3.9691468 0 0 0 -0.0295,-0.0739 c -0.0397,-0.0964 -0.0832,-0.19104 -0.13022,-0.2837 a 3.9691468,3.9691468 0 0 0 -0.0264,-0.0532 c -0.0583,-0.1107 -0.12084,-0.2185 -0.18913,-0.32298 a 3.9691468,3.9691468 0 0 0 -0.015,-0.0217 c -0.0618,-0.0932 -0.12802,-0.18318 -0.1974,-0.27079 a 3.9691468,3.9691468 0 0 0 -0.0568,-0.0692 c -0.0635,-0.0766 -0.12978,-0.15103 -0.19895,-0.22273 a 3.9691468,3.9691468 0 0 0 -0.0481,-0.0501 c -0.0872,-0.0873 -0.17798,-0.171 -0.27337,-0.25012 a 3.9691468,3.9691468 0 0 0 -0.0248,-0.0196 c -0.0848,-0.0692 -0.17282,-0.13464 -0.26355,-0.19689 a 3.9691468,3.9691468 0 0 0 -0.0811,-0.0532 c -0.079,-0.0514 -0.15982,-0.1003 -0.24288,-0.14624 a 3.9691468,3.9691468 0 0 0 -0.0765,-0.0419 c -0.10802,-0.0567 -0.21815,-0.11014 -0.33228,-0.1571 a 3.9691468,3.9691468 0 0 0 -0.28991,-0.10645 L 9.9451294,75.984944 c -0.1129358,-0.03663 -0.2276441,-0.06619 -0.3431315,-0.0925 A 3.9691468,3.9691468 0 0 0 9.4924437,75.869706 C 9.410698,75.853492 9.3285502,75.839917 9.245947,75.828881 a 3.9691468,3.9691468 0 0 0 -0.119889,-0.01499 c -0.1153425,-0.01185 -0.2310612,-0.02 -0.3472657,-0.0217 -0.00173,-2.6e-5 -0.00344,-4.94e-4 -0.00517,-5.17e-4 z M 59.854745,159.41706 H 241.99763 l 1.14515,0.37052 -226.146918,82.62286 42.412398,-81.00952 a 3.9691468,3.9691468 0 0 0 0.446485,-1.98386 z"
|
||||
id="path943"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
125
icon-font/U+2922 North East and South West Arrow.svg
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+2922 North East and South West Arrow.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.6675088"
|
||||
inkscape:cx="-229.33596"
|
||||
inkscape:cy="533.99774"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg964">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-8.6025155)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
id="path1517"
|
||||
d="m 158.73364,37.705046 c -1.46579,0 -2.64583,1.180041 -2.64583,2.645833 v 15.916858 c 0,1.430541 1.12482,2.585774 2.54145,2.640667 h 40.68227 l -46.19098,46.189936 -5.2e-4,5.2e-4 c -1.03596,1.03651 -1.03579,2.70456 5.2e-4,3.74086 l 11.22722,11.22722 c 1.03629,1.0363 2.70435,1.03647 3.74086,5.1e-4 l 46.19046,-46.191485 v 40.577875 c 0,1.46579 1.18004,2.64584 2.64583,2.64584 h 15.91169 c 1.46579,0 2.64584,-1.18005 2.64584,-2.64584 V 40.260446 c -0.0478,-1.423224 -1.20594,-2.5554 -2.64119,-2.5554 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:21.16666603;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:21.16666603;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 105.83739,244.08332 c 1.46579,0 2.64583,-1.18004 2.64583,-2.64583 v -15.91686 c 0,-1.43054 -1.12482,-2.58577 -2.54144,-2.64066 H 65.259526 l 46.190964,-46.18995 5.1e-4,-5.1e-4 c 1.03596,-1.03651 1.03579,-2.70457 -5.1e-4,-3.74086 l -11.22723,-11.22722 c -1.036275,-1.03631 -2.704335,-1.03648 -3.740835,-5.2e-4 l -46.190459,46.1915 v -40.57789 c 0,-1.46579 -1.18005,-2.64583 -2.64584,-2.64583 h -15.91169 c -1.46579,0 -2.64583,1.18004 -2.64583,2.64583 v 74.1934 c 0.0478,1.42323 1.20593,2.5554 2.64118,2.5554 z"
|
||||
id="path1642"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
122
icon-font/U+2B60 Leftwards Triangle-Headed Arrow.svg
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="U+2B60 Leftwards Triangle-Headed Arrow .svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="398.16955"
|
||||
inkscape:cx="369.80754"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
d="m 111.13772,76.12716 c 2.15968,0.04486 3.88735,1.808597 3.8871,3.96875 v 59.46045 c 2.2e-4,2.19179 1.77696,3.96853 3.96875,3.96875 h 136.32811 c 2.19179,2.2e-4 3.96853,1.77696 3.96875,3.96875 v 34.43046 c -2.2e-4,2.19179 -1.77696,3.96854 -3.96875,3.96875 H 118.99357 c -2.19179,2.2e-4 -3.96853,1.77696 -3.96875,3.96875 v 59.46045 c -0.002,3.33572 -3.86802,5.18189 -6.46317,3.08612 L 6.7461621,167.79495 c -1.9655102,-1.58863 -1.9655102,-4.58516 0,-6.17379 L 108.56165,77.007725 c 0.72802,-0.588163 1.64034,-0.899858 2.57607,-0.880565 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
123
icon-font/U+2B62 Rightwards Triangle-Headed Arrow.svg
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="U+2B95 Rightwards Black Arrow .svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="398.16955"
|
||||
inkscape:cx="369.80754"
|
||||
inkscape:zoom="0.5710432"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
d="m 579.87305,165.20508 c -8.16257,0.16955 -14.69235,6.83564 -14.69141,15 V 404.9375 c -8.3e-4,8.28393 -6.71607,14.99917 -15,15 H 34.925781 c -8.283925,8.3e-4 -14.999164,6.71607 -15,15 v 130.13086 c 8.36e-4,8.28393 6.716075,14.99921 15,15 H 550.18164 c 8.28393,8.3e-4 14.99917,6.71607 15,15 v 224.73242 c 0.008,12.60745 14.61929,19.58508 24.42774,11.66406 L 974.42383,511.66602 c 7.4287,-6.00428 7.4287,-17.32972 0,-23.33399 L 589.60938,168.5332 c -2.75158,-2.22298 -6.19972,-3.40104 -9.73633,-3.32812 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
122
icon-font/U+2B70 Leftwards Triangle-Headed Arrow To Bar.svg
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+2B70 Leftwards Triangle-Headed Arrow To Bar.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.78434916"
|
||||
inkscape:cx="345.50939"
|
||||
inkscape:cy="525.27627"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
id="path832-3"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 35.107422 165.20508 C 26.823491 165.20591 20.108216 171.92115 20.107422 180.20508 L 20.107422 819.80078 C 20.108216 828.08471 26.823491 834.79995 35.107422 834.80078 L 165.23828 834.80078 C 173.52221 834.79995 180.23745 828.08471 180.23828 819.80078 L 180.23828 519.27539 L 555.63672 831.46484 C 565.44512 839.38586 580.05807 832.40823 580.06445 819.80078 L 580.06445 595.06836 C 580.06528 586.78443 586.78052 580.06919 595.06445 580.06836 L 964.99609 580.06836 C 973.28002 580.06757 979.99526 573.35229 979.99609 565.06836 L 979.99609 434.9375 C 979.99526 426.65357 973.28002 419.93833 964.99609 419.9375 L 595.06445 419.9375 C 586.78052 419.93667 580.06528 413.22143 580.06445 404.9375 L 580.06445 180.20508 C 580.06347 171.74797 573.07454 164.96209 564.62109 165.21094 C 561.34398 165.30679 558.18786 166.474 555.63672 168.5332 L 180.23828 480.72266 L 180.23828 180.20508 C 180.23745 171.92115 173.52221 165.20591 165.23828 165.20508 L 35.107422 165.20508 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
120
icon-font/U+2B72 Rightwards Triangle-Headed Arrow To Bar.svg
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg964"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000"
|
||||
sodipodi:docname="U+2B72 Rightwards Triangle-Headed Arrow To Bar.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:zoom="0.78434916"
|
||||
inkscape:cx="345.50939"
|
||||
inkscape:cy="326.38525"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<sodipodi:guide
|
||||
position="0,52.916667"
|
||||
orientation="0,1"
|
||||
id="guide815"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="5.2916667,0"
|
||||
orientation="1,0"
|
||||
id="guide817"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="0,259.29167"
|
||||
orientation="0,1"
|
||||
id="guide819"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="259.29167,0"
|
||||
orientation="1,0"
|
||||
id="guide821"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="29.104167,0"
|
||||
orientation="1,0"
|
||||
id="guide823"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="235.47917,0"
|
||||
orientation="1,0"
|
||||
id="guide825"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="0,1"
|
||||
id="guide827"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="132.29167,161.39583"
|
||||
orientation="1,0"
|
||||
id="guide829"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
id="path832-3"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 255.32188,76.12716 c 2.19179,2.2e-4 3.96854,1.77696 3.96875,3.96875 v 169.22636 c -2.1e-4,2.19179 -1.77696,3.96853 -3.96875,3.96875 h -34.43046 c -2.19179,-2.2e-4 -3.96853,-1.77696 -3.96875,-3.96875 v -79.51401 l -99.32417,82.60013 c -2.59514,2.09577 -6.46148,0.2496 -6.46317,-3.08612 v -59.46045 c -2.2e-4,-2.19179 -1.77696,-3.96853 -3.96875,-3.96875 H 9.2888386 c -2.1917898,-2.1e-4 -3.9685303,-1.77696 -3.9687499,-3.96875 v -34.43046 c 2.196e-4,-2.19179 1.7769601,-3.96853 3.9687499,-3.96875 H 107.16658 c 2.19179,-2.2e-4 3.96853,-1.77696 3.96875,-3.96875 V 80.09591 c 2.6e-4,-2.23761 1.84942,-4.033041 4.08606,-3.967199 0.86707,0.02536 1.70212,0.334184 2.37711,0.879014 l 99.32417,82.600125 V 80.09591 c 2.2e-4,-2.19179 1.77696,-3.96853 3.96875,-3.96875 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2B82 Rightwards Triangle-Headed Arrow Over Leftwards Triangle-Headed Arrow.svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="440.81976"
|
||||
inkscape:cx="627.83421"
|
||||
inkscape:zoom="0.59888422"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:snap-page="false"
|
||||
inkscape:snap-to-guides="true"
|
||||
inkscape:snap-grids="false"
|
||||
inkscape:snap-others="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-bbox="true">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccc"
|
||||
d="m 174.5914,50.361898 c -2.15968,0.04486 -3.88735,1.808597 -3.8871,3.96875 v 48.877122 c -2.2e-4,2.19179 -1.77696,3.96853 -3.96875,3.96875 H 9.2407794 c -2.1917884,2.2e-4 -3.9685287,1.77696 -3.9687499,3.96875 v 34.43045 c 2.212e-4,2.19179 1.7769615,3.96854 3.9687499,3.96875 H 166.73555 c 2.19179,2.2e-4 3.96853,1.77696 3.96875,3.96875 v 48.87712 c 0.002,3.33572 3.86802,5.18189 6.46317,3.08612 l 80.64883,-74.03011 c 1.96551,-1.58863 1.96551,-4.58515 0,-6.17378 L 177.16747,51.242464 c -0.72802,-0.588164 -1.64034,-0.899859 -2.57607,-0.880566 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.9375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path841" />
|
||||
<path
|
||||
id="path16"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 181.25391 482.71289 L 25.498047 625.6875 C 18.069348 631.69177 18.069348 643.01721 25.498047 649.02148 L 330.3125 928.82031 C 340.12095 936.74133 354.73268 929.7637 354.74023 917.15625 L 354.74023 732.42383 C 354.74107 724.1399 361.4563 717.42466 369.74023 717.42383 L 964.99609 717.42383 C 973.28002 717.42303 979.99526 710.70776 979.99609 702.42383 L 979.99609 572.29297 C 979.99526 564.00904 973.28002 557.2938 964.99609 557.29297 L 834.09375 557.29297 C 808.85644 580.47789 783.62321 603.66677 758.38086 626.84766 C 738.58721 644.94751 718.93574 663.20661 699.09375 681.25 C 689.39662 689.8341 677.42827 696.51296 664.28516 697.35156 C 637.29619 699.42218 609.8368 678.92 606.08203 651.73828 C 605.3507 620.26206 605.20414 588.77752 605.19727 557.29297 L 369.74023 557.29297 C 361.4563 557.29214 354.74107 550.5769 354.74023 542.29297 L 354.74023 482.73242 C 296.9114 482.72628 239.08275 482.71891 181.25391 482.71289 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
|
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2190 Leftwards Arrow (Kopie).svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="443.69979"
|
||||
inkscape:cx="850.06562"
|
||||
inkscape:zoom="0.69641682"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="false"
|
||||
inkscape:bbox-nodes="true">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958" />
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
id="path841"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 834.86914,20 c -8.28393,8.31e-4 -14.99921,6.716069 -15,15 v 369.9375 c -8.7e-4,8.28393 -6.71607,14.99917 -15,15 H 449.74023 c -8.28393,-8.3e-4 -14.99916,-6.71607 -15,-15 V 180.20508 c 9.5e-4,-8.16436 -6.52883,-14.83045 -14.6914,-15 -3.53662,-0.0729 -6.98476,1.10514 -9.73633,3.32812 L 25.498047,488.33203 c -7.4287,6.00427 -7.4287,17.32971 0,23.33399 L 410.3125,831.46484 c 9.80844,7.92102 24.42018,0.94339 24.42773,-11.66406 V 595.06836 c 8.4e-4,-8.28393 6.71607,-14.99917 15,-15 h 515.25586 c 8.28393,-7.9e-4 14.99917,-6.71607 15,-15 L 980,35 c -8.3e-4,-8.283931 -6.71607,-14.999169 -15,-15 z"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,32.41665)"
|
||||
sodipodi:nodetypes="cccccccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
159
icon-font/U+2BC7 Black Medium Left-Pointing Triangle Centred.svg
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="U+2B60 Leftwards Triangle-Headed Arrow (Kopie).svg"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
version="1.1"
|
||||
id="svg964"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="437.77147"
|
||||
inkscape:cx="266.98483"
|
||||
inkscape:zoom="0.71726922"
|
||||
inkscape:guide-bbox="true"
|
||||
showguides="true"
|
||||
showgrid="false"
|
||||
id="namedview8"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:snap-bbox="false">
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide815"
|
||||
orientation="0,1"
|
||||
position="0,52.916667" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide817"
|
||||
orientation="1,0"
|
||||
position="5.2916667,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide819"
|
||||
orientation="0,1"
|
||||
position="0,259.29167" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide821"
|
||||
orientation="1,0"
|
||||
position="259.29167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide823"
|
||||
orientation="1,0"
|
||||
position="29.104167,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide825"
|
||||
orientation="1,0"
|
||||
position="235.47917,0" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide827"
|
||||
orientation="0,1"
|
||||
position="132.29167,161.39583" />
|
||||
<sodipodi:guide
|
||||
inkscape:color="rgb(0,0,255)"
|
||||
inkscape:label=""
|
||||
inkscape:locked="false"
|
||||
id="guide829"
|
||||
orientation="1,0"
|
||||
position="132.29167,161.39583" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs958">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect6088"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
satellites_param="F,0,0,1,0,38,0,4 @ F,0,0,1,0,38,0,4 @ F,0,0,1,0,38,0,4"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
radius="38"
|
||||
chamfer_steps="4"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect5304"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
satellites_param="F,0,0,1,0,7,0,1 @ F,0,0,1,0,7,0,1 @ F,0,0,1,0,7,0,1"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
radius="7"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata961">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-32.41665)">
|
||||
<path
|
||||
id="path2476"
|
||||
inkscape:transform-center-x="26.879406"
|
||||
d="M 204.85638,252.74656 55.648672,168.16048 a 3.9683103,3.9683103 90.000003 0 1 0,-6.90434 L 204.85639,76.670075 a 4.0782719,4.0782719 30.225521 0 1 6.08954,3.54783 l -1e-5,168.980825 a 4.078272,4.078272 149.77449 0 1 -6.08954,3.54783 z"
|
||||
style="fill:#000000;stroke-width:0.273459" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
1091
icon-font/icon.sfd
Normal file
93
inputcontrol/controller.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
66
inputcontrol/controller_null.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
//go:build null
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
400
inputcontrol/controller_portal.go
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
//go:build portal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
200
inputcontrol/controller_uinput.go
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
//go:build uinput
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 De_Coder github.com/ps100000
|
||||
* Copyright (c) 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)))
|
||||
}
|
||||
244
inputcontrol/controller_windows.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
397
inputcontrol/controller_x11.go
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
//go:build x11
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
// #cgo LDFLAGS: -lX11 -lXrandr -lXtst
|
||||
// #include <stdlib.h>
|
||||
// #include <X11/Xlib.h>
|
||||
// #include <X11/Intrinsic.h>
|
||||
// #include <X11/extensions/Xrandr.h>
|
||||
// #include <X11/extensions/XTest.h>
|
||||
// #include <X11/XKBlib.h>
|
||||
// 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<<modIndex] = keycode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return modKeycodes
|
||||
}
|
||||
|
||||
func (p *x11Controller) findKeycodeLocked(
|
||||
keyboard C.XkbDescPtr, modKeycodes map[uint]C.KeyCode, activeMods uint, keysym Keysym,
|
||||
) (keycode C.KeyCode, mods uint) {
|
||||
keycode = C.XKeysymToKeycode(p.display, C.KeySym(keysym))
|
||||
if keycode == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
for activeModBitmap := range uint(1) << len(modKeycodes) {
|
||||
mods := activeMods
|
||||
for i, mod := range slices.Sorted(maps.Keys(modKeycodes)) {
|
||||
if activeModBitmap&(1<<i) != 0 {
|
||||
mods ^= mod
|
||||
}
|
||||
}
|
||||
var retMods C.uint
|
||||
var retKeysym C.KeySym
|
||||
C.XkbTranslateKeyCode(keyboard, keycode, C.uint(mods), &retMods, &retKeysym)
|
||||
if retKeysym == C.KeySym(keysym) {
|
||||
return keycode, mods
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (p *x11Controller) sendModsLocked(
|
||||
modKeycodes map[uint]C.KeyCode, mods uint, press bool,
|
||||
) {
|
||||
var pressC C.int = C.False
|
||||
if press {
|
||||
pressC = C.True
|
||||
}
|
||||
for mod, keycode := range modKeycodes {
|
||||
if mods&mod != 0 {
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), pressC, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *x11Controller) keyboardKeys(keys []Keysym) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.display == nil {
|
||||
return errors.New("X server connection closed")
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
rootWindow := C.XDefaultRootWindow(p.display)
|
||||
modKeycodes := p.getModKeycodesLocked()
|
||||
keyboard := C.XkbGetKeyboard(p.display,
|
||||
C.XkbCompatMapMask|C.XkbGeometryMask, C.XkbUseCoreKbd)
|
||||
defer C.XkbFreeKeyboard(keyboard, C.XkbAllComponentsMask, C.True)
|
||||
var emptyKeycode C.KeyCode
|
||||
var keysymsPerKeycode int
|
||||
for _, keysym := range keys {
|
||||
var root, child C.Window
|
||||
var rootX, rootY, x, y C.int
|
||||
var activeModsC C.uint
|
||||
C.XQueryPointer(
|
||||
p.display,
|
||||
rootWindow,
|
||||
&root, &child,
|
||||
&rootX, &rootY,
|
||||
&x, &y,
|
||||
&activeModsC,
|
||||
)
|
||||
activeMods := uint(activeModsC)
|
||||
keycode, mods := p.findKeycodeLocked(keyboard, modKeycodes, activeMods, keysym)
|
||||
var pressMods, releaseMods uint
|
||||
if keycode == 0 {
|
||||
if emptyKeycode == 0 {
|
||||
var err error
|
||||
emptyKeycode, keysymsPerKeycode, err = p.findEmptyKeycodeLocked()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.changeKeyMappingLocked(emptyKeycode, keysymsPerKeycode, 0)
|
||||
}
|
||||
keycode = emptyKeycode
|
||||
p.changeKeyMappingLocked(keycode, keysymsPerKeycode, keysym)
|
||||
} else {
|
||||
pressMods = mods & ^activeMods
|
||||
releaseMods = activeMods & ^mods
|
||||
}
|
||||
p.sendModsLocked(modKeycodes, releaseMods, false)
|
||||
p.sendModsLocked(modKeycodes, pressMods, true)
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), C.True, 0)
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), C.False, 0)
|
||||
p.sendModsLocked(modKeycodes, pressMods, false)
|
||||
p.sendModsLocked(modKeycodes, releaseMods, true)
|
||||
C.XSync(p.display, C.False)
|
||||
if keycode == emptyKeycode {
|
||||
// race condition!
|
||||
time.Sleep(keyboardMappingDelay)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) 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 *x11Controller) KeyboardKey(key Key) error {
|
||||
keysym, err := KeyToKeysym(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := [...]Keysym{keysym}
|
||||
return p.keyboardKeys(keys[:])
|
||||
}
|
||||
|
||||
func (p *x11Controller) sendButton(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.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
|
||||
}
|
||||
130
inputcontrol/keymap.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
//go:build uinput
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<<i) != 0 {
|
||||
if shiftKey == 0 {
|
||||
continue keymapLoop
|
||||
}
|
||||
activeShiftKeys[i] = shiftKey
|
||||
}
|
||||
}
|
||||
for key, keysym := range keymaps[keymapIndex] {
|
||||
keyType := keysym >> 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
|
||||
}
|
||||
207
inputcontrol/keysyms.generated.go
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// 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,
|
||||
}
|
||||
126
inputcontrol/keysyms.generator.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
//go:build ignore
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
109
inputcontrol/keysyms.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//go:build portal || x11
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
151
main.go
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
* Copyright (c) 2018-2019 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Foobar is free software: you can redistribute it and/or modify
|
||||
* 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.
|
||||
|
|
@ -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 Foobar. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
|
@ -27,14 +27,19 @@ 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 (
|
||||
|
|
@ -43,43 +48,71 @@ const (
|
|||
authenticationRateBurst int = 10
|
||||
challengeLength int = 8
|
||||
defaultBind string = ":0"
|
||||
version string = "0.0.2"
|
||||
version string = "1.5.4"
|
||||
prettyAppName string = "Remote Touchpad"
|
||||
)
|
||||
|
||||
func processCommand(plugin Plugin, command string) error {
|
||||
commandParts := strings.Split(command, " ")
|
||||
if commandParts[0] == "KeyboardText" {
|
||||
text := strings.Join(commandParts[1:], " ")
|
||||
return plugin.KeyboardText(text)
|
||||
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")
|
||||
}
|
||||
if len(commandParts) != 3 {
|
||||
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 {
|
||||
return errors.New("wrong number of arguments")
|
||||
}
|
||||
x, err := strconv.ParseInt(commandParts[1], 10, 32)
|
||||
x, err := strconv.ParseInt(arguments[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y, err := strconv.ParseInt(commandParts[2], 10, 32)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if commandParts[0] == "PointerMove" {
|
||||
return plugin.PointerMove(int(x), int(y))
|
||||
if command[0] == 'm' {
|
||||
return controller.PointerMove(int(x), int(y))
|
||||
}
|
||||
if commandParts[0] == "PointerScroll" {
|
||||
return plugin.PointerScroll(int(x), int(y))
|
||||
if command[0] == 's' {
|
||||
return controller.PointerScroll(int(x), int(y), false)
|
||||
}
|
||||
if commandParts[0] == "PointerButton" {
|
||||
if x <= 0 || x > 3 {
|
||||
return errors.New("unknown pointer button")
|
||||
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")
|
||||
}
|
||||
b := true
|
||||
if y == 0 {
|
||||
b = false
|
||||
}
|
||||
return plugin.PointerButton(uint(x), b)
|
||||
return controller.PointerButton(inputcontrol.PointerButton(x), b)
|
||||
}
|
||||
return errors.New("unknown command")
|
||||
return errors.New("unsupported command")
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
|
|
@ -118,46 +151,61 @@ func secureRandBase64(length int) string {
|
|||
}
|
||||
|
||||
func main() {
|
||||
terminal.SetTitle(prettyAppName)
|
||||
var bind, certFile, keyFile, secret string
|
||||
var showVersion, invert bool
|
||||
var showVersion bool
|
||||
var config config
|
||||
flag.BoolVar(&showVersion, "version", false, "show program's version number and exit")
|
||||
flag.StringVar(&bind, "bind", defaultBind, "bind server to [IP]:PORT")
|
||||
flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]: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.BoolVar(&invert, "invert", false, "use inverse colors for QR code")
|
||||
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.Parse()
|
||||
if showVersion {
|
||||
fmt.Println(version)
|
||||
return
|
||||
}
|
||||
if certFile != "" && keyFile == "" {
|
||||
log.Fatal("TLS certificate missing")
|
||||
log.Fatal("TLS private key file missing")
|
||||
}
|
||||
if certFile == "" && keyFile != "" {
|
||||
log.Fatal("TLS private key missing")
|
||||
log.Fatal("TLS certificate file missing")
|
||||
}
|
||||
tls := certFile != "" && keyFile != ""
|
||||
if secret == "" {
|
||||
secret = secureRandBase64(defaultSecretLength)
|
||||
}
|
||||
var plugin Plugin
|
||||
platformErrors := ""
|
||||
for _, f := range Plugins {
|
||||
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 err error
|
||||
plugin, err = f()
|
||||
controller, err = controllerInfo.Init()
|
||||
if err == nil {
|
||||
break
|
||||
} else if _, ok := err.(UnsupportedPlatformError); ok {
|
||||
platformErrors += err.Error() + "\n"
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
if plugin == nil {
|
||||
log.Fatal("unsupported platform:\n" + platformErrors)
|
||||
if controller == nil {
|
||||
log.Fatal(fmt.Errorf("unsupported platform:\n%w", errors.Join(platformErrs...)))
|
||||
}
|
||||
defer plugin.Close()
|
||||
defer controller.Close()
|
||||
authenticationChallenges := make(chan challenge, authenticationRateBurst)
|
||||
go authenticationChallengeGenerator(secret, authenticationChallenges)
|
||||
listener, err := net.Listen("tcp", bind)
|
||||
|
|
@ -177,11 +225,11 @@ func main() {
|
|||
}
|
||||
}
|
||||
if host == "" {
|
||||
host = FindDefaultHost()
|
||||
host = findDefaultHost()
|
||||
}
|
||||
port := addr.Port
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(assetFS()))
|
||||
mux.Handle("/", http.FileServer(http.FS(webdataFS)))
|
||||
mux.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
|
||||
var message string
|
||||
challenge := <-authenticationChallenges
|
||||
|
|
@ -192,11 +240,13 @@ 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(plugin, message); err != nil {
|
||||
if err := processCommand(controller, message); err != nil {
|
||||
log.Print(fmt.Errorf("%s controller: %w", controllerName, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -209,16 +259,17 @@ func main() {
|
|||
if tls {
|
||||
scheme = "https"
|
||||
}
|
||||
url := fmt.Sprintf("%s://%s/#%s\n", scheme, domain, secret)
|
||||
readyMsg := "ready: " + url
|
||||
qrCode, _ := GenerateQRCode(url, invert)
|
||||
readyMsg += qrCode
|
||||
if !tls {
|
||||
readyMsg += ("" +
|
||||
"▌ WARNING: TLS is not enabled ▐\n" +
|
||||
"▌Don't use in an untrusted network!▐\n")
|
||||
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)
|
||||
}
|
||||
if !tls {
|
||||
fmt.Println("▌ WARNING: TLS is not enabled ▐")
|
||||
fmt.Println("▌Don't use in an untrusted network!▐")
|
||||
}
|
||||
log.Print(readyMsg)
|
||||
if tls {
|
||||
err = http.ServeTLS(listener, mux, certFile, keyFile)
|
||||
} else {
|
||||
|
|
|
|||
44
plugin.go
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
173
plugin_x11.go
|
|
@ -1,173 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
// #cgo LDFLAGS: -lX11 -lXtst
|
||||
// #include <X11/Xlib.h>
|
||||
// #include <X11/Intrinsic.h>
|
||||
// #include <X11/extensions/XTest.h>
|
||||
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
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
func InitX11Plugin() (Plugin, error) {
|
||||
return nil, UnsupportedPlatformError{"X11", "disabled"}
|
||||
}
|
||||
12
remote-touchpad.service
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[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
|
||||
149
rts
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
#!/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
|
||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 133 KiB |
BIN
screenshots/4.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
61
snap/snapcraft.yaml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# 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]
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Foobar is free software: you can redistribute it and/or modify
|
||||
* 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.
|
||||
|
|
@ -13,44 +13,50 @@
|
|||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
package terminal
|
||||
|
||||
import qrcode "github.com/skip2/go-qrcode"
|
||||
|
||||
func GenerateQRCode(message string, invert bool) (string, error) {
|
||||
func GenerateQRCode(message string, colorize bool) (string, error) {
|
||||
q, err := qrcode.New(message, qrcode.Medium)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return qrCodeToString(q.Bitmap(), invert), nil
|
||||
return qrCodeToString(q.Bitmap(), colorize), nil
|
||||
}
|
||||
|
||||
func qrCodeToString(bits [][]bool, invert bool) string {
|
||||
func qrCodeToString(bits [][]bool, colorize bool) string {
|
||||
s := ""
|
||||
for y := -1; y < len(bits); y += 2 {
|
||||
if colorize {
|
||||
s += ForegroundBlack + BackgroundWhite
|
||||
}
|
||||
for x := range bits[0] {
|
||||
upper := !invert
|
||||
upper := false
|
||||
if 0 <= y && y < len(bits) {
|
||||
upper = bits[y][x]
|
||||
}
|
||||
lower := !invert
|
||||
lower := false
|
||||
if 0 <= y+1 && y+1 < len(bits) {
|
||||
lower = bits[y+1][x]
|
||||
}
|
||||
if upper != invert && lower != invert {
|
||||
s += " "
|
||||
} else if upper == invert && lower != invert {
|
||||
if upper && lower {
|
||||
s += "█"
|
||||
} else if upper && !lower {
|
||||
s += "▀"
|
||||
} else if upper != invert && lower == invert {
|
||||
} else if !upper && lower {
|
||||
s += "▄"
|
||||
} else {
|
||||
s += "█"
|
||||
s += " "
|
||||
}
|
||||
}
|
||||
if colorize {
|
||||
s += ForegroundReset + BackgroundReset
|
||||
}
|
||||
s += "\n"
|
||||
}
|
||||
return s
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
// +build !windows
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Foobar is free software: you can redistribute it and/or modify
|
||||
* 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.
|
||||
|
|
@ -15,12 +13,15 @@
|
|||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
package terminal
|
||||
|
||||
func InitWindowsPlugin() (Plugin, error) {
|
||||
return nil, UnsupportedPlatformError{"Windows", "disabled"}
|
||||
}
|
||||
const (
|
||||
ForegroundBlack string = "\033[30m"
|
||||
ForegroundReset string = "\033[39m"
|
||||
BackgroundWhite string = "\033[47m"
|
||||
BackgroundReset string = "\033[49m"
|
||||
)
|
||||
39
terminal/terminal_posix.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//go:build !windows
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package terminal
|
||||
|
||||
// #include <unistd.h>
|
||||
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
|
||||
}
|
||||
49
terminal/terminal_windows.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
52
webdata.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
82
webdata/app/compat.mjs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
121
webdata/app/inputcontroller.mjs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
74
webdata/app/keyboard.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
webdata/app/main.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
74
webdata/app/mouse.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
webdata/app/sha256.mjs
Normal file
66
webdata/app/socket.mjs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
232
webdata/app/touchpad.mjs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
webdata/app/ui.mjs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
389
webdata/fn.js
|
|
@ -1,389 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud<unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// [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();
|
||||
}
|
||||
};
|
||||
document.getElementById("reloadbutton").addEventListener("click",
|
||||
function(e) {
|
||||
location.reload();
|
||||
});
|
||||
pad.addEventListener("touchstart", handleStart, false);
|
||||
pad.addEventListener("touchend", handleEnd, false);
|
||||
pad.addEventListener("touchcancel", handleCancel, false);
|
||||
pad.addEventListener("touchmove", handleMove, false);
|
||||
}, false);
|
||||
BIN
webdata/icon.png
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
webdata/icon.woff
Normal file
|
|
@ -1,33 +1,100 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<script src="fn.js"></script>
|
||||
<script src="sha256.js"></script>
|
||||
<title>Remote Touchpad</title>
|
||||
<link href="main.css" media="screen" rel="stylesheet" />
|
||||
<link href="icon.png" type="image/png" rel="shortcut icon" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="opening">
|
||||
<p>Connecting...</p>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, interactive-widget=resizes-content">
|
||||
<script src="app/main.mjs" type="module"></script>
|
||||
<title>Remote Touchpad</title>
|
||||
<link href="main.css" media="screen" rel="stylesheet">
|
||||
<link href="icon.png" type="image/png" rel="icon">
|
||||
|
||||
<div id="opening" class="scene">
|
||||
<noscript><p>JavaScript is required!</p></noscript>
|
||||
<p>Connecting…</p>
|
||||
</div>
|
||||
|
||||
<div id="closed" class="scene">
|
||||
<p>Disconnected</p>
|
||||
<button class="large" onclick="location.reload()">↻</button>
|
||||
</div>
|
||||
|
||||
<div id="pad" class="scene touch-input mouse-input keyboard-input allow-fullscreen">
|
||||
<p class="background">Touchpad</p>
|
||||
<button class="top left" onclick="app.showKeys()">≡</button>
|
||||
<button class="bottom left visible-if-fullscreen-enabled" onclick="app.toggleFullscreen()">⤢</button>
|
||||
<button class="bottom right" onclick="app.showTextInput()">⌨</button>
|
||||
</div>
|
||||
|
||||
<div id="text-input" class="scene">
|
||||
<textarea class="grow" cols=1 rows=1></textarea>
|
||||
<div class="buttons">
|
||||
<button onclick="app.showKeys('text-input', 1)">≡</button>
|
||||
<button class="grow" id="send-text" data-send-content="➣" data-back-content="⯇"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="keys" class="scene touch-input mouse-input keyboard-input allow-fullscreen">
|
||||
<div class="page">
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_BROWSER_BACK)">⭠</button>
|
||||
<button onclick="app.key(app.KEY_SUPER)">🪟</button>
|
||||
<button onclick="app.key(app.KEY_BROWSER_FORWARD)">⭢</button>
|
||||
</div>
|
||||
<div id="closed">
|
||||
<p>Disconnected</p>
|
||||
<button id="reloadbutton">↻</button>
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_MEDIA_PREV_TRACK)">⏮</button>
|
||||
<button onclick="app.key(app.KEY_MEDIA_PLAY_PAUSE)">⏯</button>
|
||||
<button onclick="app.key(app.KEY_MEDIA_NEXT_TRACK)">⏭</button>
|
||||
</div>
|
||||
<div id="pad">
|
||||
<p id="padlabel">Touchpad</p>
|
||||
<div class="buttons">
|
||||
<button id="fullscreenbutton">▢</button>
|
||||
<button id="keyboardbutton">⌨</button>
|
||||
</div>
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_VOLUME_DOWN)">🔉</button>
|
||||
<button onclick="app.key(app.KEY_VOLUME_MUTE)">🔇</button>
|
||||
<button onclick="app.key(app.KEY_VOLUME_UP)">🔊</button>
|
||||
</div>
|
||||
<div id="keyboard">
|
||||
<textarea id="text"></textarea>
|
||||
<button id="sendbutton">➣</button>
|
||||
</div>
|
||||
<div class="page">
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_BACK_SPACE)">⌫</button>
|
||||
<button onclick="app.key(app.KEY_RETURN)">⮠</button>
|
||||
<button onclick="app.key(app.KEY_DELETE)">⌦</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_HOME)">⭰</button>
|
||||
<button onclick="app.key(app.KEY_UP)">↑</button>
|
||||
<button onclick="app.key(app.KEY_END)">⭲</button>
|
||||
</div>
|
||||
<div class="buttons" style="grid-column: 1 / 4">
|
||||
<button onclick="app.key(app.KEY_LEFT)">←</button>
|
||||
<button onclick="app.key(app.KEY_DOWN)">↓</button>
|
||||
<button onclick="app.key(app.KEY_RIGHT)">→</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page">
|
||||
<button onclick="app.key(app.KEY_BACK_SPACE)">⌫</button>
|
||||
<button onclick="app.text(this.textContent)">/</button>
|
||||
<button onclick="app.text(this.textContent)">*</button>
|
||||
<button onclick="app.text(this.textContent)">-</button>
|
||||
|
||||
<button style="grid-row: 2" onclick="app.text(this.textContent)">7</button>
|
||||
<button onclick="app.text(this.textContent)">8</button>
|
||||
<button onclick="app.text(this.textContent)">9</button>
|
||||
<button onclick="app.text(this.textContent)">+</button>
|
||||
|
||||
<button style="grid-row: 3" onclick="app.text(this.textContent)">4</button>
|
||||
<button onclick="app.text(this.textContent)">5</button>
|
||||
<button onclick="app.text(this.textContent)">6</button>
|
||||
<button onclick="app.text(this.textContent)">=</button>
|
||||
|
||||
<button style="grid-row: 4" onclick="app.text(this.textContent)">1</button>
|
||||
<button onclick="app.text(this.textContent)">2</button>
|
||||
<button onclick="app.text(this.textContent)">3</button>
|
||||
<button style="grid-row: 4 / 6; grid-column: 4" onclick="app.key(app.KEY_RETURN)">⮠</button>
|
||||
|
||||
<button style="grid-row: 5; grid-column: 1 / 3" onclick="app.text(this.textContent)">0</button>
|
||||
<button onclick="app.text(this.textContent)"><script>document.write(Intl.NumberFormat().formatToParts(1.1).find(x => x.type === "decimal").value)</script></button>
|
||||
</div>
|
||||
<button class="top right" onclick="app.setKeysPage(1, true)">⮂</button>
|
||||
<button class="top left" onclick="history.back()">⯇</button>
|
||||
</div>
|
||||
|
||||
<div id="mouse" class="scene keyboard-input allow-fullscreen">
|
||||
<p class="background">Mouse</p>
|
||||
</div>
|
||||
|
|
|
|||
253
webdata/main.css
|
|
@ -1,103 +1,228 @@
|
|||
html, body, #pad, #keyboard, #opening, #closed {
|
||||
@font-face {
|
||||
font-family: "icon";
|
||||
src: url("icon.woff") format("woff");
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: #404040;
|
||||
color: white;
|
||||
background-image: url(tex.png);
|
||||
touch-action: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.scene {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans;
|
||||
}
|
||||
|
||||
#pad, #sendbutton, #opening, #closed {
|
||||
background-color: #404040 !important;
|
||||
background-image: url(tex.png);
|
||||
}
|
||||
|
||||
#opening, #closed {
|
||||
.scene {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: 2em;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
word-wrap: break-word;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#pad {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#padlabel {
|
||||
color: #424242;
|
||||
text-shadow:
|
||||
-1px -1px 0 #383838,
|
||||
1px -1px 0 #383838,
|
||||
-1px 1px 0 #383838,
|
||||
1px 1px 0 #383838;
|
||||
font-weight: bold;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
text-align: center;
|
||||
font-size: 4em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#fullscreenbutton, #keyboardbutton, #sendbutton, #reloadbutton {
|
||||
p.background {
|
||||
color: gray;
|
||||
text-shadow:
|
||||
-1px -1px 0 black,
|
||||
1px -1px 0 black,
|
||||
-1px 1px 0 black,
|
||||
1px 1px 0 black;
|
||||
opacity: 0.08;
|
||||
font-weight: bold;
|
||||
font-size: 4rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (max-width: 32rem) {
|
||||
p.background {
|
||||
font-size: 13vw;
|
||||
}
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 1px solid black;
|
||||
height: 1.5em;
|
||||
width: 2em;
|
||||
font-size: 2em;
|
||||
min-width: 1.2em;
|
||||
min-height: 1.2em;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 2px;
|
||||
font-size: 2rem;
|
||||
color: white;
|
||||
font-family: "icon";
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s ease-out;
|
||||
}
|
||||
|
||||
#fullscreenbutton, #keyboardbutton, #sendbutton {
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button.click {
|
||||
box-shadow: 0 0 1rem white inset;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
button.top,
|
||||
button.bottom,
|
||||
button.left,
|
||||
button.right {
|
||||
position: absolute;
|
||||
max-width: 50%;
|
||||
max-height: 50%;
|
||||
}
|
||||
|
||||
button.top {
|
||||
top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
button.bottom {
|
||||
bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#fullscreenbutton {
|
||||
button.left {
|
||||
left: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#keyboardbutton {
|
||||
button.right {
|
||||
right: 0;
|
||||
border-right: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#keyboard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#text {
|
||||
flex: 2;
|
||||
border: none;
|
||||
resize: none;
|
||||
font-size: 2em;
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#sendbutton {
|
||||
.buttons > button:not(:last-child) {
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.scene > .grow,
|
||||
.buttons > .grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: none;
|
||||
resize: none;
|
||||
font-size: 2rem;
|
||||
background-color: white;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#keys {
|
||||
padding: 0;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
#keys .page {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
grid-auto-rows: minmax(min-content, 6rem);
|
||||
grid-auto-columns: minmax(min-content, 8rem);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
#keys .page * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 4/3) {
|
||||
#keyboard {
|
||||
#text-input {
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#text-input .buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#text-input .buttons > button {
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#text-input .buttons > button:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Only Safari for iOS */
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
/* Virtual keyboard can overlay content */
|
||||
#text-input .buttons > button:last-child {
|
||||
display: flex;
|
||||
padding-top: 0.2em;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Portrait orientation (not for Safari on iOS) */
|
||||
@supports not (-webkit-touch-callout: none) { @media (max-aspect-ratio: 4/3) {
|
||||
#text-input {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#sendbutton {
|
||||
border-left: none;
|
||||
border-top: 1px solid black;
|
||||
height: 1.5em;
|
||||
width: 100%;
|
||||
#text-input .buttons {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
#text-input .buttons > button {
|
||||
border-top: 1px solid black;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#text-input .buttons > button:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
A JavaScript implementation of the SHA family of hashes, as
|
||||
defined in FIPS PUB 180-4 and FIPS PUB 202, as well as the corresponding
|
||||
HMAC implementation as defined in FIPS PUB 198a
|
||||
|
||||
Copyright 2008-2018 Brian Turek, 1998-2009 Paul Johnston & Contributors
|
||||
Distributed under the BSD License
|
||||
See http://caligatio.github.com/jsSHA/ for more information
|
||||
*/
|
||||
'use strict';(function(I){function w(c,a,d){var l=0,b=[],g=0,f,n,k,e,h,q,y,p,m=!1,t=[],r=[],u,z=!1;d=d||{};f=d.encoding||"UTF8";u=d.numRounds||1;if(u!==parseInt(u,10)||1>u)throw Error("numRounds must a integer >= 1");if(0===c.lastIndexOf("SHA-",0))if(q=function(b,a){return A(b,a,c)},y=function(b,a,l,f){var g,e;if("SHA-224"===c||"SHA-256"===c)g=(a+65>>>9<<4)+15,e=16;else throw Error("Unexpected error in SHA-2 implementation");for(;b.length<=g;)b.push(0);b[a>>>5]|=128<<24-a%32;a=a+l;b[g]=a&4294967295;
|
||||
b[g-1]=a/4294967296|0;l=b.length;for(a=0;a<l;a+=e)f=A(b.slice(a,a+e),f,c);if("SHA-224"===c)b=[f[0],f[1],f[2],f[3],f[4],f[5],f[6]];else if("SHA-256"===c)b=f;else throw Error("Unexpected error in SHA-2 implementation");return b},p=function(b){return b.slice()},"SHA-224"===c)h=512,e=224;else if("SHA-256"===c)h=512,e=256;else throw Error("Chosen SHA variant is not supported");else throw Error("Chosen SHA variant is not supported");k=B(a,f);n=x(c);this.setHMACKey=function(b,a,g){var e;if(!0===m)throw Error("HMAC key already set");
|
||||
if(!0===z)throw Error("Cannot set HMAC key after calling update");f=(g||{}).encoding||"UTF8";a=B(a,f)(b);b=a.binLen;a=a.value;e=h>>>3;g=e/4-1;if(e<b/8){for(a=y(a,b,0,x(c));a.length<=g;)a.push(0);a[g]&=4294967040}else if(e>b/8){for(;a.length<=g;)a.push(0);a[g]&=4294967040}for(b=0;b<=g;b+=1)t[b]=a[b]^909522486,r[b]=a[b]^1549556828;n=q(t,n);l=h;m=!0};this.update=function(a){var c,f,e,d=0,p=h>>>5;c=k(a,b,g);a=c.binLen;f=c.value;c=a>>>5;for(e=0;e<c;e+=p)d+h<=a&&(n=q(f.slice(e,e+p),n),d+=h);l+=d;b=f.slice(d>>>
|
||||
5);g=a%h;z=!0};this.getHash=function(a,f){var d,h,k,q;if(!0===m)throw Error("Cannot call getHash after setting HMAC key");k=C(f);switch(a){case "HEX":d=function(a){return D(a,e,k)};break;case "B64":d=function(a){return E(a,e,k)};break;case "BYTES":d=function(a){return F(a,e)};break;case "ARRAYBUFFER":try{h=new ArrayBuffer(0)}catch(v){throw Error("ARRAYBUFFER not supported by this environment");}d=function(a){return G(a,e)};break;default:throw Error("format must be HEX, B64, BYTES, or ARRAYBUFFER");
|
||||
}q=y(b.slice(),g,l,p(n));for(h=1;h<u;h+=1)q=y(q,e,0,x(c));return d(q)};this.getHMAC=function(a,f){var d,k,t,u;if(!1===m)throw Error("Cannot call getHMAC without first setting HMAC key");t=C(f);switch(a){case "HEX":d=function(a){return D(a,e,t)};break;case "B64":d=function(a){return E(a,e,t)};break;case "BYTES":d=function(a){return F(a,e)};break;case "ARRAYBUFFER":try{d=new ArrayBuffer(0)}catch(v){throw Error("ARRAYBUFFER not supported by this environment");}d=function(a){return G(a,e)};break;default:throw Error("outputFormat must be HEX, B64, BYTES, or ARRAYBUFFER");
|
||||
}k=y(b.slice(),g,l,p(n));u=q(r,x(c));u=y(k,e,h,u);return d(u)}}function m(){}function D(c,a,d){var l="";a/=8;var b,g;for(b=0;b<a;b+=1)g=c[b>>>2]>>>8*(3+b%4*-1),l+="0123456789abcdef".charAt(g>>>4&15)+"0123456789abcdef".charAt(g&15);return d.outputUpper?l.toUpperCase():l}function E(c,a,d){var l="",b=a/8,g,f,n;for(g=0;g<b;g+=3)for(f=g+1<b?c[g+1>>>2]:0,n=g+2<b?c[g+2>>>2]:0,n=(c[g>>>2]>>>8*(3+g%4*-1)&255)<<16|(f>>>8*(3+(g+1)%4*-1)&255)<<8|n>>>8*(3+(g+2)%4*-1)&255,f=0;4>f;f+=1)8*g+6*f<=a?l+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(n>>>
|
||||
6*(3-f)&63):l+=d.b64Pad;return l}function F(c,a){var d="",l=a/8,b,g;for(b=0;b<l;b+=1)g=c[b>>>2]>>>8*(3+b%4*-1)&255,d+=String.fromCharCode(g);return d}function G(c,a){var d=a/8,l,b=new ArrayBuffer(d),g;g=new Uint8Array(b);for(l=0;l<d;l+=1)g[l]=c[l>>>2]>>>8*(3+l%4*-1)&255;return b}function C(c){var a={outputUpper:!1,b64Pad:"=",shakeLen:-1};c=c||{};a.outputUpper=c.outputUpper||!1;!0===c.hasOwnProperty("b64Pad")&&(a.b64Pad=c.b64Pad);if("boolean"!==typeof a.outputUpper)throw Error("Invalid outputUpper formatting option");
|
||||
if("string"!==typeof a.b64Pad)throw Error("Invalid b64Pad formatting option");return a}function B(c,a){var d;switch(a){case "UTF8":case "UTF16BE":case "UTF16LE":break;default:throw Error("encoding must be UTF8, UTF16BE, or UTF16LE");}switch(c){case "HEX":d=function(a,b,c){var f=a.length,d,k,e,h,q;if(0!==f%2)throw Error("String of HEX type must be in byte increments");b=b||[0];c=c||0;q=c>>>3;for(d=0;d<f;d+=2){k=parseInt(a.substr(d,2),16);if(isNaN(k))throw Error("String of HEX type contains invalid characters");
|
||||
h=(d>>>1)+q;for(e=h>>>2;b.length<=e;)b.push(0);b[e]|=k<<8*(3+h%4*-1)}return{value:b,binLen:4*f+c}};break;case "TEXT":d=function(c,b,d){var f,n,k=0,e,h,q,m,p,r;b=b||[0];d=d||0;q=d>>>3;if("UTF8"===a)for(r=3,e=0;e<c.length;e+=1)for(f=c.charCodeAt(e),n=[],128>f?n.push(f):2048>f?(n.push(192|f>>>6),n.push(128|f&63)):55296>f||57344<=f?n.push(224|f>>>12,128|f>>>6&63,128|f&63):(e+=1,f=65536+((f&1023)<<10|c.charCodeAt(e)&1023),n.push(240|f>>>18,128|f>>>12&63,128|f>>>6&63,128|f&63)),h=0;h<n.length;h+=1){p=k+
|
||||
q;for(m=p>>>2;b.length<=m;)b.push(0);b[m]|=n[h]<<8*(r+p%4*-1);k+=1}else if("UTF16BE"===a||"UTF16LE"===a)for(r=2,n="UTF16LE"===a&&!0||"UTF16LE"!==a&&!1,e=0;e<c.length;e+=1){f=c.charCodeAt(e);!0===n&&(h=f&255,f=h<<8|f>>>8);p=k+q;for(m=p>>>2;b.length<=m;)b.push(0);b[m]|=f<<8*(r+p%4*-1);k+=2}return{value:b,binLen:8*k+d}};break;case "B64":d=function(a,b,c){var f=0,d,k,e,h,q,m,p;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw Error("Invalid character in base-64 string");k=a.indexOf("=");a=a.replace(/\=/g,
|
||||
"");if(-1!==k&&k<a.length)throw Error("Invalid '=' found in base-64 string");b=b||[0];c=c||0;m=c>>>3;for(k=0;k<a.length;k+=4){q=a.substr(k,4);for(e=h=0;e<q.length;e+=1)d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(q[e]),h|=d<<18-6*e;for(e=0;e<q.length-1;e+=1){p=f+m;for(d=p>>>2;b.length<=d;)b.push(0);b[d]|=(h>>>16-8*e&255)<<8*(3+p%4*-1);f+=1}}return{value:b,binLen:8*f+c}};break;case "BYTES":d=function(a,b,c){var d,n,k,e,h;b=b||[0];c=c||0;k=c>>>3;for(n=0;n<a.length;n+=
|
||||
1)d=a.charCodeAt(n),h=n+k,e=h>>>2,b.length<=e&&b.push(0),b[e]|=d<<8*(3+h%4*-1);return{value:b,binLen:8*a.length+c}};break;case "ARRAYBUFFER":try{d=new ArrayBuffer(0)}catch(l){throw Error("ARRAYBUFFER not supported by this environment");}d=function(a,b,c){var d,n,k,e,h;b=b||[0];c=c||0;n=c>>>3;h=new Uint8Array(a);for(d=0;d<a.byteLength;d+=1)e=d+n,k=e>>>2,b.length<=k&&b.push(0),b[k]|=h[d]<<8*(3+e%4*-1);return{value:b,binLen:8*a.byteLength+c}};break;default:throw Error("format must be HEX, TEXT, B64, BYTES, or ARRAYBUFFER");
|
||||
}return d}function r(c,a){return c>>>a|c<<32-a}function J(c,a,d){return c&a^~c&d}function K(c,a,d){return c&a^c&d^a&d}function L(c){return r(c,2)^r(c,13)^r(c,22)}function M(c){return r(c,6)^r(c,11)^r(c,25)}function N(c){return r(c,7)^r(c,18)^c>>>3}function O(c){return r(c,17)^r(c,19)^c>>>10}function P(c,a){var d=(c&65535)+(a&65535);return((c>>>16)+(a>>>16)+(d>>>16)&65535)<<16|d&65535}function Q(c,a,d,l){var b=(c&65535)+(a&65535)+(d&65535)+(l&65535);return((c>>>16)+(a>>>16)+(d>>>16)+(l>>>16)+(b>>>
|
||||
16)&65535)<<16|b&65535}function R(c,a,d,l,b){var g=(c&65535)+(a&65535)+(d&65535)+(l&65535)+(b&65535);return((c>>>16)+(a>>>16)+(d>>>16)+(l>>>16)+(b>>>16)+(g>>>16)&65535)<<16|g&65535}function x(c){var a=[],d;if(0===c.lastIndexOf("SHA-",0))switch(a=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],d=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],c){case "SHA-224":break;case "SHA-256":a=d;break;case "SHA-384":a=[new m,new m,
|
||||
new m,new m,new m,new m,new m,new m];break;case "SHA-512":a=[new m,new m,new m,new m,new m,new m,new m,new m];break;default:throw Error("Unknown SHA variant");}else throw Error("No SHA variants supported");return a}function A(c,a,d){var l,b,g,f,n,k,e,h,m,r,p,w,t,x,u,z,A,B,C,D,E,F,v=[],G;if("SHA-224"===d||"SHA-256"===d)r=64,w=1,F=Number,t=P,x=Q,u=R,z=N,A=O,B=L,C=M,E=K,D=J,G=H;else throw Error("Unexpected error in SHA-2 implementation");d=a[0];l=a[1];b=a[2];g=a[3];f=a[4];n=a[5];k=a[6];e=a[7];for(p=
|
||||
0;p<r;p+=1)16>p?(m=p*w,h=c.length<=m?0:c[m],m=c.length<=m+1?0:c[m+1],v[p]=new F(h,m)):v[p]=x(A(v[p-2]),v[p-7],z(v[p-15]),v[p-16]),h=u(e,C(f),D(f,n,k),G[p],v[p]),m=t(B(d),E(d,l,b)),e=k,k=n,n=f,f=t(g,h),g=b,b=l,l=d,d=t(h,m);a[0]=t(d,a[0]);a[1]=t(l,a[1]);a[2]=t(b,a[2]);a[3]=t(g,a[3]);a[4]=t(f,a[4]);a[5]=t(n,a[5]);a[6]=t(k,a[6]);a[7]=t(e,a[7]);return a}var H;H=[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];"function"===typeof define&&define.amd?define(function(){return w}):"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(module.exports=w),exports=w):I.jsSHA=w})(this);
|
||||
42
webdata_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWebdataTypesCompleteness(t *testing.T) {
|
||||
if err := fs.WalkDir(webdataFS, ".", func(_ string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
ext := filepath.Ext(d.Name())
|
||||
if _, haveType := webdataTypes[ext]; !haveType {
|
||||
return fmt.Errorf("missing mime type for extension %#v", ext)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||