Compare commits

...

293 commits

Author SHA1 Message Date
2ac00c3b48 Updated gitignore
Some checks failed
Test / test (linux, []) (push) Has been cancelled
Test / test (windows, [null]) (push) Has been cancelled
Test / test (linux, [null portal uinput x11]) (push) Has been cancelled
Test / test (linux, [null]) (push) Has been cancelled
Test / test (linux, [portal]) (push) Has been cancelled
Test / test (linux, [uinput]) (push) Has been cancelled
Test / test (linux, [x11]) (push) Has been cancelled
Test / test (windows, []) (push) Has been cancelled
2026-08-14 22:01:02 +02:00
ed7e8bae54 Add systemd user service and CLI wrapper for remote-touchpad
Some checks are pending
Test / test (linux, []) (push) Waiting to run
Test / test (linux, [null portal uinput x11]) (push) Waiting to run
Test / test (linux, [null]) (push) Waiting to run
Test / test (linux, [portal]) (push) Waiting to run
Test / test (linux, [uinput]) (push) Waiting to run
Test / test (linux, [x11]) (push) Waiting to run
Test / test (windows, []) (push) Waiting to run
Test / test (windows, [null]) (push) Waiting to run
This commit introduces a complete systemd user service setup for
remote-touchpad, eliminating the need for users to manually manage
the binary process. Key additions:

- New rts CLI wrapper script providing a user-friendly interface
  for service management without requiring root access
- Systemd service file for proper background operation with
  restart policies and dependency management
- Service management commands: install, start, stop, status,
  restart, and uninstall
- URL extraction and QR code generation via the url command
- Service log viewing via the log command

The installation process now:
1. Verifies Go is installed
2. Builds the remote-touchpad binary with appropriate tags
3. Installs service files to ~/.config/systemd/user/
4. Installs the binary to ~/.local/bin/
5. Enables and starts the service automatically

The README has been updated with comprehensive installation
instructions for the new user service approach.
2026-08-14 21:59:58 +02:00
Unrud
a77f3aa8ec Bump version to 1.5.4 2026-08-03 00:34:58 +02:00
Unrud
9764d1975d Update dependencies 2026-08-03 00:34:58 +02:00
Unrud
5e68074781 X11: Reduce delay when mapping new keys
Reliability is improved by XSync
2026-08-03 00:32:34 +02:00
Unrud
f80b1d6ef7 X11: Minimize mod changes 2026-08-03 00:32:34 +02:00
Unrud
346d9cf375 X11: Cleanup code 2026-08-03 00:32:33 +02:00
Unrud
6ef2be8776 Format code 2026-08-02 22:12:27 +02:00
Unrud
85b6d6ea9d X11: Use XSync to improve reliability 2026-08-02 13:10:04 +02:00
Unrud
1aa8f92cc8 Use range over integers 2026-08-02 13:10:04 +02:00
Unrud
996edb0501 Snap: Specifiy supported platforms 2026-07-11 20:59:03 +02:00
Unrud
a30bed4624 Snap: Update to core24 2026-07-11 20:20:03 +02:00
Unrud
a2dfa7ac47 Bump version to 1.5.3 2025-12-24 22:59:32 +01:00
Unrud
eebde58826 Update dependencies 2025-12-24 22:57:01 +01:00
Unrud
800171efdd Bump version to 1.5.2 2025-10-12 06:45:35 +02:00
Unrud
2b3d5c5bf3 Flatpak: Update modules.txt 2025-10-12 06:42:46 +02:00
Unrud
1add8e4cb8 Bump version to 1.5.1 2025-10-12 06:26:29 +02:00
Unrud
06674b224f Update dependencies 2025-10-12 06:26:11 +02:00
Unrud
d48d9aed3d Bump version to 1.5.0 2025-03-18 22:02:15 +01:00
Unrud
5771f44b0e Cleanup portal controller 2025-03-18 09:18:16 +01:00
Unrud
16dc47b570 Encrypt restore token 2025-03-18 09:18:15 +01:00
Unrud
abd9fd4773 Update dependencies 2025-03-17 20:48:45 +01:00
Unrud
7c1652e9dd CI: Install missing package 2025-03-17 20:24:25 +01:00
godalming
0fe41d0b15 Fix not being able to create restore token on snap because the cache directory does not exist 2025-03-17 20:21:35 +01:00
godalming
21461d9a76 Fix file permisions. Detect if restore tokens are not supported (version <= 1). Always log and continue if it is not possible to use a restore token rather than exit the program or log using fmt. 2025-03-17 00:19:48 +01:00
godalming
c6c54c77a7 Add support for session restore on the linux portals backend, fixing #77 2025-03-17 00:19:48 +01:00
Unrud
7a806f7b00 Flatpak: Update runtime 2024-11-17 21:33:51 +01:00
Unrud
4fb452c542 Bump version to 1.4.8 2024-09-01 17:15:32 +02:00
Unrud
c71e5c4b8b Flatpak: Add IPC to fix linting error 2024-09-01 17:12:02 +02:00
Unrud
30cfff86b0 Update go.sum 2024-09-01 17:09:23 +02:00
Unrud
288345b5d3 Bump version to 1.4.7 2024-08-30 23:45:18 +02:00
Unrud
b4ecfad316 Update dependencies 2024-08-30 23:41:45 +02:00
Unrud
dd510bc4a0 Update appdata 2024-04-13 19:03:36 +02:00
Unrud
4916fc59db Update dependencies 2024-04-06 17:22:46 +02:00
Unrud
064c3cd242 appdata: Add vcs-browser URL 2024-04-05 22:38:15 +02:00
Unrud
6996fbf958 appdata: Add developer ID 2024-04-05 22:37:48 +02:00
Unrud
016351e9d2 Bump version to 1.4.6 2024-02-06 11:33:41 +01:00
Unrud
6325782058 Fix CI test 2024-02-06 11:33:40 +01:00
Unrud
0184dcb22f Set MIME types explicitly
Fixes #61
Fixes #70
2024-02-06 11:26:34 +01:00
Unrud
652ee5aa6c Bump version to 1.4.5 2024-01-31 14:59:38 +01:00
Unrud
88fd6e7314 Workaround for virtual keyboard covering buttons for Safari on iOS 2024-01-31 09:33:11 +01:00
Unrud
6a812607fe Fix virtual keyboard covering buttons in Chromium 2024-01-31 09:33:11 +01:00
Unrud
f11cefc74c Keep text input open after sending 2024-01-31 09:33:10 +01:00
Unrud
7cff8286c8 Hide focus outline for textarea 2024-01-31 09:33:10 +01:00
Unrud
280a654f59 Enable hardware keyboard input in mouse mode 2024-01-31 09:33:09 +01:00
Unrud
d502ca651c Disable keyboard navigation for all buttons 2024-01-31 09:33:09 +01:00
Unrud
1985842ff0 Refactor CSS 2024-01-31 09:33:09 +01:00
Unrud
fc363fe404 Snap: Update to core22 2024-01-27 07:40:22 +01:00
Unrud
cc143b59e6 Bump version to 1.4.4 2024-01-23 20:51:57 +01:00
Unrud
02d49bf680 Flatpak: Update dependencies 2024-01-23 20:51:53 +01:00
Unrud
601d8c8358 Update dependencies 2024-01-23 19:42:59 +01:00
Unrud
0ca1431594 Fix Xwayland detection with newer servers
Fixes #63
2024-01-23 19:33:15 +01:00
Unrud
48ff3d67a0 Fix minimum go version
Fixes #62
2024-01-23 14:14:29 +01:00
Unrud
e94fb986c0 Fix typo that causes error with xdg-desktop-portal-kde
Fixes #67
2024-01-23 14:11:16 +01:00
Unrud
7f7604a462 Bump version to 1.4.3 2023-10-04 01:44:59 +02:00
Unrud
e1dbdc9b68 Update dependencies 2023-10-04 01:41:25 +02:00
Unrud
6c746b23f1 Bump version to 1.4.2 2023-05-21 01:01:22 +02:00
Unrud
c8e6664a8d Fix Windows controller
Partially revert 1794150285
Fixes #58
2023-05-21 00:53:33 +02:00
Unrud
1803577892 Bump version to 1.4.1 2023-05-11 23:03:46 +02:00
Unrud
4358c0b0dc Update dependencies 2023-05-11 22:55:04 +02:00
Unrud
737c9671cf Flatpak: Update dependencies 2023-05-09 00:10:03 +02:00
Unrud
925cb745eb Run tests with github actions 2023-05-08 23:58:02 +02:00
Unrud
722678ca4f Update dependencies 2023-05-08 22:49:45 +02:00
Unrud
c6fe56479a uinput: Fix vertical scroll direction 2023-05-08 22:31:34 +02:00
Unrud
1794150285 Simplify error handling 2023-05-08 22:31:02 +02:00
Unrud
6a1a7420dc Update dependencies 2023-05-08 17:48:24 +02:00
Unrud
13f5df3bf8 Bump verstion to 1.4.0 2023-04-25 16:40:10 +02:00
Unrud
bb25f2dd37 Flatpak: Update vendor packages 2023-04-25 16:32:01 +02:00
Unrud
af116f4cfb Explain prevention of mouse mode by touch 2023-04-25 16:31:41 +02:00
Unrud
3c2bec8916 Update dependencies 2023-04-25 01:41:43 +02:00
Unrud
45801d7cd5 Add numeric keypad
Fixes #54
2023-04-25 01:38:52 +02:00
Unrud
4285f6aa1b Add visual and vibration feedback to buttons 2023-04-25 01:38:51 +02:00
Unrud
20cea84056 Prevent touch triggering mouse mode 2023-04-25 01:38:50 +02:00
Unrud
b239ca7160 Use ECMAScript modules 2023-04-25 01:38:50 +02:00
Unrud
e5369af219 Refactor web 2023-04-25 01:38:49 +02:00
Unrud
51108765e3 Add null controller 2023-04-25 01:38:49 +02:00
Unrud
37dff3cb6b uinput: Detect unsupported platform 2023-04-25 01:38:48 +02:00
Unrud
891fde7051 Refactor errors 2023-04-25 01:38:48 +02:00
Unrud
91a37de0fb Bump version to 1.3.0 2023-03-03 23:14:34 +01:00
Unrud
d81b57b1f9 Add uinput build example 2023-03-03 23:06:34 +01:00
Unrud
0bd7260a0c uinput-controller: Show hint for setting keymap 2023-02-27 23:43:10 +01:00
Unrud
e69bd15733 uinput-controller: Lower priority 2023-02-27 23:42:16 +01:00
Unrud
bbb6cc4dc1 uinput-controller: Initial support for keymaps
Keymap can be set with environment variable REMOTE_TOUCHPAD_UINPUT_KEYMAP
2023-02-26 16:45:37 +01:00
Unrud
d254ae5b0d uinput-controller: Map runes/keys directly and handle errors 2023-02-23 02:07:14 +01:00
Unrud
809d3a0064 uinput-controller: Format code 2023-02-23 01:09:29 +01:00
De_Coder
feaf02217c improve uinput controller 2023-02-23 01:09:11 +01:00
De_Coder
f5d19a0354 Add uinput-based controller 2023-02-23 01:09:11 +01:00
Unrud
342334ac18 Update dependencies 2023-02-23 01:09:05 +01:00
Unrud
124152f7ec Include missing rune, key or button in error 2023-02-23 01:09:04 +01:00
Unrud
6f03281fc7 Refactor: Use switch statement 2023-02-23 01:09:03 +01:00
Unrud
79a88dc676 Bump version to 1.2.3 2022-12-29 22:32:47 +01:00
Unrud
9c5dcbee10 Update dependencies 2022-12-29 22:32:47 +01:00
Unrud
f30b4cf48e Flatpak: Add Wayland socket to disable X11 socket 2022-12-29 22:32:38 +01:00
Unrud
681d9c4f1d Improve XWayland detection 2022-12-29 22:19:38 +01:00
Unrud
2fc5d0825b Flatpak: Remove x11 from finish-args 2022-11-03 01:56:15 +01:00
Unrud
c1491f4a84 Snap: Update to core20 2022-09-28 00:48:41 +02:00
Unrud
b96944d4d6 Bump version to 1.2.2 2022-09-24 02:15:04 +02:00
Unrud
61f910adf0 Update dependencies 2022-09-24 02:09:15 +02:00
Unrud
518c6ef952 Fix race in RemoteDesktop portal controller 2022-09-24 02:09:05 +02:00
Unrud
1b3d26c465 Flatpak: Update runtime 2022-09-04 22:13:57 +02:00
Unrud
1675d5d2f8 Bump version to 1.2.1 2022-06-20 23:11:22 +02:00
Unrud
8a2a29c9d6 Reorganize 2022-06-20 03:48:43 +02:00
Unrud
e29982dc00 Update dependencies 2022-06-20 01:19:01 +02:00
Unrud
7522d131ec Update to go 1.17 2022-06-20 01:19:01 +02:00
Unrud
6134fa564b Flatpak: Generate vendor/modules.txt 2022-06-20 01:19:00 +02:00
Unrud
523d2039b6 Bump version to 1.2.0 2022-02-26 13:53:17 +01:00
Unrud
771eea98b5 Increase update rate 2022-02-26 13:53:17 +01:00
Unrud
d6b75771c3 Throw exception again 2022-02-23 21:11:24 +01:00
Unrud
90922d85c6 Return early if no relevant touches 2022-02-23 21:11:23 +01:00
Unrud
0c401e0247 Add missing semicolons and initialize variables 2022-02-22 23:36:56 +01:00
Unrud
b748aceaf9 Prevent soft lockup if an error occurs 2022-02-22 23:36:56 +01:00
Unrud
a9117b82ad Update changelog 2022-02-22 22:40:30 +01:00
Unrud
9543c514d0 Show error message if compiled without controller 2022-02-22 22:40:07 +01:00
Unrud
238ef14843 Rename variable 2022-02-22 22:19:37 +01:00
Unrud
190b2763bd Organize code in namespaces 2022-02-22 22:05:48 +01:00
Unrud
9432869bce Limit update rate 2022-02-22 20:27:39 +01:00
Unrud
61fa13974d README: Switch from "go get" to "go install" 2022-02-22 16:23:00 +01:00
Unrud
e027c71341 Normalize line endings client side 2022-02-22 16:17:55 +01:00
Unrud
a279e195be Windows: Reverse vertical scroll direction 2022-02-21 22:30:29 +01:00
Unrud
25d5cfa505 Rename backend to controller 2022-02-21 22:30:28 +01:00
Unrud
2211b8a662 Remove disabled backends 2022-02-21 22:30:28 +01:00
Unrud
89131db79b Centralized UI updates 2022-02-21 22:30:27 +01:00
Unrud
42dcafda3d Improve variable names 2022-02-21 22:30:26 +01:00
Unrud
9082ca434a Add fullscreenchange event listener 2022-02-21 22:30:26 +01:00
Unrud
98f4d1c692 Add options for configuring pointer and scroll speed 2022-02-21 22:30:25 +01:00
Unrud
fecec13869 Add hardware mouse support 2022-02-21 22:30:20 +01:00
Unrud
1797d7585b Prevent clicks from touches on touchpad 2022-02-21 22:30:20 +01:00
Unrud
db9ca255fa Add Touch prefix to handlers 2022-02-21 22:30:19 +01:00
Unrud
b3db523db1 Keep move and scroll remainder 2022-02-21 22:30:19 +01:00
Unrud
51dbecaa63 Split function updateMoveAndScroll 2022-02-21 22:30:19 +01:00
Unrud
ab09e7c674 Refactor key detection 2022-02-21 22:30:18 +01:00
Unrud
56cff8deb2 Listen for keydown event on document 2022-02-21 22:30:18 +01:00
Unrud
ad3847f5aa Combine scroll and scroll finish commands 2022-02-21 22:30:18 +01:00
Unrud
1f3aa58568 Remove padlabel id 2022-02-21 22:30:17 +01:00
Unrud
41c3e0b10d Switch to go:embed 2022-02-21 22:30:16 +01:00
Unrud
7fc46bf38c Rename class touchpad to touch 2022-02-03 14:51:54 +01:00
Unrud
b3a4ca7449 Use go-bindata instead of go-bindata-assetfs 2022-02-03 14:45:35 +01:00
Unrud
abc8ef5818 Workaround for assetFS FileInfo
See https://github.com/elazarl/go-bindata-assetfs/pull/55
2022-02-01 13:59:32 +01:00
Unrud
c0e6ef52ec Use camel case for variable name 2022-02-01 13:59:32 +01:00
Unrud
15b05b5fa6 Bump version to 1.1.0 2022-01-29 11:04:23 +01:00
Unrud
a4cac5fb54 Update screenshots 2022-01-29 11:04:22 +01:00
Unrud
7392eec8f1 Update dependencies 2022-01-29 11:04:22 +01:00
Unrud
3feddd5f68 Ignore unparseable IP 2022-01-29 11:04:21 +01:00
Unrud
42528c0cf7 Use class for scenes and fix resizing keys 2022-01-29 11:04:21 +01:00
Unrud
c85275f263 Add back button to keys scene and allow fullscreen
Closes #29
2022-01-28 00:14:20 +01:00
Unrud
facaacb3fb Use history instead of window.history 2022-01-28 00:13:19 +01:00
Unrud
077dc6def0 Check modifier keys for physical keyboard 2022-01-28 00:12:55 +01:00
Unrud
e702b239eb Enable touchpad and keyboard on keys page
Fixes #25
2022-01-27 17:00:50 +01:00
Unrud
cf8753698f Save keys page in session 2022-01-27 16:55:50 +01:00
Unrud
632c944561 Use classes for fullscreen and pad capability 2022-01-27 16:55:50 +01:00
Unrud
c749865ef1 Save keyboard text in session 2022-01-27 16:55:50 +01:00
Unrud
24f3eb63ec Cosmetics
Rename variable keys_pages to keysPages.
Rename variable page_index to pageIndex.
Rename textarea text to keyboardtext.
Rename button textkeysbutton to keyboardkeysbutton.
Omit last , in array.
2022-01-27 16:51:33 +01:00
Unrud
a113b3adb7 Add support for physical keyboard 2022-01-27 07:26:31 +01:00
Unrud
a23a1260e9 Cosmetics 2022-01-27 07:17:35 +01:00
Unrud
5da7b376c9 Add return key 2022-01-27 07:17:23 +01:00
Unrud
957e116305 Invert QR code 2022-01-24 09:00:16 +01:00
Unrud
86cbc977b0 Print URL and QR code to stdout
Closes #30
2022-01-24 08:59:37 +01:00
Unrud
f741196640 Remove newline from QR code 2022-01-24 08:58:41 +01:00
Unrud
9806aa26e1 Cosmetics 2022-01-24 08:58:40 +01:00
Unrud
d22dc0f4dd Add more keys
Fixes #36
Fixes #27
Fixes #22
2022-01-24 08:08:21 +01:00
Unrud
92a46e6637 Improve icon font
Round values to integer
Change EM square to 1024
Move glyph uni2630 to uni2261
2022-01-24 07:52:34 +01:00
Unrud
cfad7e9d68 Use pointer as cursor for buttons 2022-01-24 07:52:27 +01:00
Unrud
348ec1f6ab Remove unused function 2022-01-24 07:52:19 +01:00
Unrud
64327f326c Cosmetics 2022-01-24 07:52:09 +01:00
Unrud
dd2d13b4e4 Compatibility with ECMA Version 5
Fixes #40
2022-01-17 15:18:56 +01:00
Unrud
02fa08e266 Bump version to 1.0.5 2022-01-08 00:37:46 +01:00
Unrud
70e63a57af Update dependencies 2022-01-08 00:25:02 +01:00
Unrud
130c430d3d Use relative URL for websocket
Partially merged from branch 'schnusch-http-prefix'
2022-01-08 00:23:59 +01:00
schnusch
47e9bb2047 use relative URL for websocket 2022-01-04 14:51:18 +01:00
Unrud
55b0ba894e Bump version to 1.0.4 2021-11-06 21:45:02 +01:00
Unrud
e2ee520380 Disable touch-action (closes #37) 2021-11-06 21:45:01 +01:00
Unrud
c53252c197 Update dependencies 2021-11-06 21:45:01 +01:00
Unrud
1ec541d78c Bump version to 1.0.3 2021-09-18 21:17:54 +02:00
Unrud
1f22f5668a Rename appdata to metainfo 2021-09-18 21:17:53 +02:00
Unrud
72d4d60419 Update dependencies 2021-09-18 21:17:53 +02:00
Unrud
c1a55e57ae Improve generators 2021-09-18 21:17:52 +02:00
Unrud
aa903a664a Flatpak: Update runtime 2021-09-08 18:53:48 +02:00
Unrud
82f62b7edb Bump version to 1.0.2 2021-06-06 01:04:02 +02:00
Unrud
0b6dac0ae6 Fix markdown lint 2021-06-06 00:54:31 +02:00
Unrud
592f40006f New logo 2021-06-06 00:46:14 +02:00
Unrud
5be09594df Remove IE quirks 2021-06-06 00:46:03 +02:00
Unrud
b95ffd4c1d Flatpak: Vendored dependencies
Fixes build with newer Go versions
2021-06-06 00:45:57 +02:00
Unrud
d00c3dab85 Update Go dependencies 2021-06-06 00:45:39 +02:00
Unrud
8139cf0734 Bump version to 1.0.1 2020-09-13 10:54:38 +02:00
Unrud
50af32094d Flatpak: Update runtime and dependencies 2020-09-13 10:25:29 +02:00
Unrud
84944c578d Snap: Remove workaround 2020-09-13 10:25:29 +02:00
Unrud
e24b06b0f6 Separate tags with comma 2020-09-13 10:25:29 +02:00
Unrud
50ec8ff4db Update go modules 2020-09-13 10:25:28 +02:00
Unrud
6d68809ccf Bump version to 1.0.0 2020-08-26 03:57:22 +02:00
Unrud
fd854f8b16 Flatpak: Update dependencies 2020-08-26 03:57:22 +02:00
Unrud
41ef45d345 Add golang installation instructions
Fixes #15
2020-08-26 03:57:22 +02:00
Unrud
69ca24da48 Update screenshots 2020-08-26 03:57:22 +02:00
Unrud
125e9a4d94 Update bindata 2020-08-26 03:31:59 +02:00
Unrud
ae7a84ec64 Remove experimental mark from DesktopPortal 2020-08-26 03:31:59 +02:00
Unrud
a3e4d1c90a Remove obsolete command line arguments 2020-08-26 03:31:59 +02:00
Unrud
18752a362d use strict 2020-08-26 03:31:59 +02:00
Unrud
c93c6c7de5 Add go.mod 2020-08-26 03:31:59 +02:00
Unrud
38e6049304 Use tags for go generate 2020-08-26 03:31:59 +02:00
Unrud
ba9ed841d8 Add super and browser back and forward keys
Fixes #13
2020-08-26 03:31:27 +02:00
Unrud
e501c8cd9a Rework CSS 2020-08-26 03:30:22 +02:00
Unrud
585a3c0dcc Reduce minimum size of textarea 2020-08-26 03:30:21 +02:00
Unrud
1f410b5a18 Only exit fullscreen when in fullscreen mode 2020-08-26 03:30:21 +02:00
Unrud
9ad8590517 Cosmetics 2020-08-26 03:30:20 +02:00
Unrud
bd376578d4 Snap: Update 2020-08-16 10:06:57 +02:00
Unrud
7c0d475394 Bump version to 0.0.18 2020-05-17 03:12:19 +02:00
Unrud
b432b95413 Update dependencies 2020-05-17 03:11:49 +02:00
Stefano Mersi
fad0e2d7c0 Update main.go 2020-05-02 19:15:33 +02:00
Unrud
08b852f836 Update dependencies 2020-04-30 13:29:01 +02:00
林博仁(Buo-ren Lin)
049d79448c Snap: Fix license top-level property
Snapcraft uses SPDX 2.1 syntax[1], which allows specifying "or later" license
style by appending a `+` operator after the license expression.  Refer
Appendix IV:  SPDX License Expressions[2] for more info regarding this syntax.

[1] https://spdx.org/spdx-specification-21-web-version
[2] https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60

Refer-to: license - Snapcraft top-level metadata - doc - snapcraft.io <https://forum.snapcraft.io/t/snapcraft-top-level-metadata/8334#heading--license>
Signed-off-by: 林博仁(Buo-ren Lin) <Buo.Ren.Lin@gmail.com>
2020-03-01 18:25:14 +01:00
Unrud
afcfa8a67a Bump version to 0.0.17 2020-02-20 18:57:09 +01:00
Unrud
071062fe77 Minor HTML improvements 2020-02-20 18:57:09 +01:00
Unrud
330e3c1619 Correct year of release date 2020-02-19 00:33:42 +01:00
Unrud
8e94866d92 Bump version to 0.0.16 2020-02-17 18:02:01 +01:00
Unrud
e59145d8e0 Update screenshots 2020-02-17 18:02:01 +01:00
Unrud
a7963f2f70 Flatpak: Update dependencies 2020-02-17 18:02:00 +01:00
Unrud
86c570edc4 Fix textarea on WebKit 2020-02-17 18:02:00 +01:00
Unrud
bac6c4ac0c Cosmetic changes 2020-02-17 18:01:59 +01:00
Unrud
7934a06207 Rework Flatpak and Snap 2020-01-04 05:00:26 +01:00
Unrud
f93806b469 Flatpak: Update dependencies 2020-01-04 03:12:49 +01:00
Unrud
dfba2b6f62 Bump version to 0.0.15 2019-11-03 14:11:55 +01:00
Unrud
b5af659f79 Use CSS class to hide elements 2019-11-03 14:11:55 +01:00
Unrud
dfa70a56b5 Prefer Ipv4 addresses 2019-11-03 14:11:55 +01:00
Unrud
8e3e62a9de Bump version to 0.0.14 2019-10-28 01:50:12 +01:00
Unrud
6092502dd9 Snap: Fix build 2019-10-28 01:50:12 +01:00
Unrud
de46eb2bd1 Flatpak: Update dependencies 2019-10-28 01:50:11 +01:00
Unrud
e2a4d17f8a Remove gitignore 2019-10-28 01:50:11 +01:00
Unrud
390827829a Set terminal title 2019-10-28 01:50:10 +01:00
Unrud
1e46a174aa Handle quirks of browser touch events 2019-10-28 01:50:10 +01:00
Unrud
18b8d15af0 Hide navigation bar in fullscreen mode 2019-10-28 01:50:09 +01:00
Unrud
628912633b Cosmetics 2019-10-28 01:50:09 +01:00
Unrud
5bcda69519 Add space between name and email in copyright 2019-10-28 01:50:08 +01:00
Unrud
ebf02bdbea Flatpak: Move AppData to metainfo 2019-10-11 23:06:55 +02:00
Unrud
f4a4cbee8b Flatpak: Update dependencies 2019-07-30 07:48:21 +02:00
Unrud
e4c0c92d03 bump version to 0.0.13 2019-05-04 22:26:51 +02:00
Unrud
da32043032 update webdata 2019-05-04 22:26:50 +02:00
Unrud
95e14dd5bc Flatpak: YAML style 2019-05-04 22:26:50 +02:00
Unrud
0ba1464157 Flatpak: Update dependencies 2019-05-04 22:26:49 +02:00
Unrud
875b7a1717 AppStream: Fix release descriptions 2019-05-04 22:26:48 +02:00
Unrud
afd64d9733 Flatpak: update dependencies 2019-02-23 23:16:05 +01:00
Unrud
21ad831022 Snap: Add link 2019-02-23 23:12:11 +01:00
Unrud
09a57c70ed Snap: Add snapcraft 2019-02-23 22:42:57 +01:00
Unrud
945169fb84 Flatpak: update dependencies 2019-01-11 19:59:52 +01:00
Unrud
84b1329710 Flatpak: convert to yaml 2019-01-11 19:59:51 +01:00
Unrud
756ab3a03f remove flatpak Makefile 2019-01-05 18:48:56 +01:00
Unrud
0aa1ecfe33 improve flatpak Makefile 2018-12-31 15:45:06 +01:00
Unrud
a06707c5e9 fix license header (2) 2018-12-31 15:43:05 +01:00
Unrud
147a4b2874 fix license header 2018-12-31 14:48:29 +01:00
Unrud
18e4a4b51d bump version to 0.0.12 2018-12-08 11:54:42 +01:00
Unrud
f0af4aaad4 Flatpak: update dependencies 2018-12-08 11:54:25 +01:00
Unrud
2f76cbd05c fix the browser Forward button 2018-12-08 11:53:56 +01:00
Unrud
235f6efbac bump verstion to 0.0.11 2018-10-20 04:25:55 +02:00
Unrud
56add5f796 Flatpak: update dependencies 2018-10-20 04:25:55 +02:00
Unrud
2acdaf572d Flatpak: Use correct names for icon, desktop and appdata without renaming 2018-10-20 04:25:54 +02:00
Unrud
8e7c945f0f rename plugin to backend 2018-10-19 00:25:28 +02:00
Unrud
7edb092f93 Flatpak: Update runtime 2018-10-12 12:04:54 +02:00
Unrud
361dc89614 bump version to 0.0.10 2018-10-01 07:35:55 +02:00
Unrud
8722dac897 X11: wait 500ms before and after remapping keycode 2018-10-01 07:35:55 +02:00
Unrud
d95acc275c X11: use existing keycodes when available 2018-10-01 07:35:54 +02:00
Unrud
9901e9e9ec exit fullscreen for multimedia keys 2018-10-01 07:35:54 +02:00
Unrud
491aa2fd3c add gitignore 2018-09-30 11:14:04 +02:00
Unrud
92c3e0a75a Add Makefile for building flatpak 2018-09-30 11:10:25 +02:00
Unrud
cddda0d32d reduce keyboard mapping delay 2018-09-30 11:10:21 +02:00
Unrud
e0df49caad disable user-select everywhere 2018-09-30 11:10:20 +02:00
Unrud
f6db7c0c08 bump version to 0.0.9 2018-09-30 00:26:34 +02:00
Unrud
0a647816cd update screenshots 2018-09-30 00:26:33 +02:00
Unrud
88339bf3f2 cosmetics 2018-09-30 00:26:33 +02:00
Unrud
1ddfffa843 simplify css 2018-09-30 00:26:32 +02:00
Unrud
34f4dbf77a add multimedia keys 2018-09-30 00:26:31 +02:00
Unrud
b3b068fe59 Use internal constants for pointer buttons, instead of x11 mapping 2018-09-30 00:26:31 +02:00
Unrud
07678e56ab cosmetics 2018-09-30 00:26:30 +02:00
Unrud
4ca4889955 scale padlabel to screen size 2018-09-30 00:26:29 +02:00
Unrud
820149a140 bump version to 0.0.8 2018-09-07 09:29:19 +02:00
Unrud
374d665938 flatpak: update dependencies 2018-09-07 09:29:17 +02:00
Unrud
573c93582d update screenshots 2018-09-07 09:29:16 +02:00
Unrud
c335af3d90 include font and image sources 2018-09-07 09:29:15 +02:00
Unrud
4b0329b97a update icon 2018-09-07 09:29:10 +02:00
Unrud
cc231211b5 format source 2018-09-07 09:29:09 +02:00
Unrud
b96247c181 enable pointer movement after timeout 2018-09-07 09:29:08 +02:00
Unrud
08a7df30e5 disable selection 2018-09-07 09:29:07 +02:00
Unrud
bc6d25458f transparent text 2018-09-07 09:29:06 +02:00
Unrud
057342bbad bump version to 0.0.7 2018-09-04 03:39:12 +02:00
Unrud
e341b559c7 update screenshots 2018-08-29 16:15:24 +02:00
Unrud
4f4b697730 use custom font for icons
font only includes glyphs for the required icons
2018-08-29 16:10:15 +02:00
Unrud
8a558c0437 bump version to 0.0.6 2018-08-23 17:26:08 +02:00
Unrud
33c9b815b0 use correct KeySyms for runes < 0x100 2018-08-23 17:26:08 +02:00
Unrud
ed74ab60e4 normalize line endings 2018-08-23 17:26:08 +02:00
Unrud
5a5ad3e6c6 hide obsolete command line arguments 2018-08-23 17:12:35 +02:00
Unrud
b1f8f7ea1c rename _dummy to _disabled 2018-08-23 17:12:35 +02:00
Unrud
f7fff5468c mitigate race when sending text on X11 2018-08-23 17:12:34 +02:00
Unrud
f2a7c1dc1d remove invert argument and use colors in terminal instead 2018-08-23 17:12:34 +02:00
Unrud
549de83fe3 sort imports 2018-08-23 17:12:33 +02:00
Unrud
1787e573cb minor refactoring 2018-08-23 17:12:33 +02:00
Unrud
67e1f31362 update url for golang-dbus 2018-08-01 13:01:14 +02:00
94 changed files with 8701 additions and 1865 deletions

43
.github/workflows/test.yaml vendored Normal file
View 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
View file

@ -0,0 +1,11 @@
# Binaries
remote-touchpad
# Systemd service state
.remote-touchpad.service.state
# Logs and run files
.rts-log
# Environment files
.env

View file

@ -1,25 +1,220 @@
# Changelog # 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) ## 0.0.5 (2018-07-31)
* remove mouse polling interval * remove mouse polling interval
* add support for "pixel-perfect" scrolling * add support for "pixel-perfect" scrolling
* add experimental support for Flatpak's RemoteDesktop portal * add experimental support for Flatpak's RemoteDesktop portal
* X11 plugin: check session type * X11 plugin: check session type
* improve error logging * improve error logging
## 0.0.4 (2018-07-22) ## 0.0.4 (2018-07-22)
* add setting for mouse polling interval * add setting for mouse polling interval
## 0.0.3 (2018-06-26) ## 0.0.3 (2018-06-26)
* fix websocket with https * fix websocket with https
## 0.0.2 (2018-06-25) ## 0.0.2 (2018-06-25)
* add reload button to disconnected-page * add reload button to disconnected-page
## 0.0.1 (2018-06-23) ## 0.0.1 (2018-06-23)
initial release initial release

View file

@ -1,14 +1,56 @@
# Remote Touchpad # 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. To take control open the displayed URL or scan the QR code.
Supports Flatpak's RemoteDesktop portal (experimental), Windows and X11. Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11.
## Installation ## Installation
* [Flatpak](https://flathub.org/apps/details/com.github.unrud.RemoteTouchpad) * [Flatpak](https://flathub.org/apps/details/com.github.unrud.RemoteTouchpad)
* [Windows](https://github.com/Unrud/remote-touchpad/releases/latest) * [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 ## Screenshots
@ -17,3 +59,5 @@ Supports Flatpak's RemoteDesktop portal (experimental), Windows and X11.
![screenshot 2](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/2.png) ![screenshot 2](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/2.png)
![screenshot 3](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/3.png) ![screenshot 3](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/3.png)
![screenshot 4](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/4.png)

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
package main
//go:generate go-bindata-assetfs -mode 0664 -modtime 1 -o ./bindata.generated.go webdata/...

View file

@ -5,5 +5,5 @@ Name=Remote Touchpad
Comment=Control mouse and keyboard from a smartphone Comment=Control mouse and keyboard from a smartphone
Terminal=true Terminal=true
Exec=remote-touchpad-wait-on-error Exec=remote-touchpad-wait-on-error
Icon=remote-touchpad Icon=com.github.unrud.RemoteTouchpad
Categories=Network;RemoteAccess;ConsoleOnly; Categories=Network;RemoteAccess;ConsoleOnly;

View 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&apos;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 &amp; 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&apos;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 &quot;pixel-perfect&quot; scrolling</li>
<li>add experimental support for Flatpak&apos;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>

View 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

View file

@ -1,61 +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>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&apos;s RemoteDesktop portal (experimental), Windows and X11.
</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.5" date="2018-07-31">
<ul>
<li>remove mouse polling interval</li>
<li>add support for &quot;pixel-perfect&quot; scrolling</li>
<li>add experimental support for Flatpak&apos;s RemoteDesktop portal</li>
<li>X11 plugin: check session type</li>
<li>improve error logging</li>
</ul>
</release>
<release version="0.0.4" date="2018-07-22">
<ul>
<li>add setting for mouse polling interval</li>
</ul>
</release>
<release version="0.0.3" date="2018-06-26">
<ul>
<li>fix websocket with https</li>
</ul>
</release>
<release version="0.0.2" date="2018-06-25">
<ul>
<li>add reload button to disconnected-page</li>
</ul>
</release>
<release version="0.0.1" date="2018-06-23">
<p>initial release</p>
</release>
</releases>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.5/screenshots/1.png</image>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.5/screenshots/2.png</image>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/Unrud/remote-touchpad/0.0.5/screenshots/3.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
</component>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,66 +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",
"url" : "https://anonscm.debian.org/git/pkg-go/packages/golang-dbus.git",
"commit" : "79d96b151df08809558c825f8a75188826e8e228",
"dest" : "src/anonscm.debian.org/git/pkg-go/packages/golang-dbus.git"
},
{
"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 -tags 'portal x11' github.com/unrud/remote-touchpad",
"install -Dm0755 -t /app/bin remote-touchpad",
"install -Dm0755 -t /app/bin src/github.com/unrud/remote-touchpad/desktop/remote-touchpad-wait-on-error",
"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"
]
}
]
}

View 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
View 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
View 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
View 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=

62
host.go
View file

@ -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. * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
@ -13,8 +13,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Foobar. If not, see <http://www.gnu.org/licenses/>. * along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
*/ */
package main package main
@ -22,11 +22,25 @@ package main
import ( import (
"fmt" "fmt"
"net" "net"
"sort"
"strings" "strings"
) )
func FindDefaultHost() (host string) { const ipv4Rating int = +1
host = "localhost"
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"} { for _, publicIP := range []string{"2001:4860:4860::8888", "8.8.8.8"} {
addr := fmt.Sprintf("[%s]:80", publicIP) addr := fmt.Sprintf("[%s]:80", publicIP)
conn, err := net.Dial("udp", addr) conn, err := net.Dial("udp", addr)
@ -34,42 +48,42 @@ func FindDefaultHost() (host string) {
continue continue
} }
conn.Close() conn.Close()
host, _, err = net.SplitHostPort(conn.LocalAddr().String()) host, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil { if err != nil {
panic(err) panic(err)
} }
return ip := net.ParseIP(host)
} if ip != nil {
interfaces, err := net.Interfaces() addIP(100, ip)
if err != nil { }
return
} }
interfaces, _ := net.Interfaces()
for _, inter := range interfaces { for _, inter := range interfaces {
if inter.Flags&net.FlagUp == 0 || inter.Flags&net.FlagLoopback != 0 { if inter.Flags&net.FlagUp == 0 {
continue
}
addrs, err := inter.Addrs()
if err != nil {
continue continue
} }
addrs, _ := inter.Addrs()
addrs: addrs:
for _, addr := range addrs { for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String()) ip, _, err := net.ParseCIDR(addr.String())
if err != nil { if err != nil {
panic(err)
}
if inter.Flags&net.FlagLoopback != 0 {
addIP(10, ip)
continue continue
} }
if host == "localhost" {
host = ip.String()
}
for _, linkLocalPrefix := range []string{ for _, linkLocalPrefix := range []string{
"169.254.", "fe8", "fe9", "fea", "feb"} { "169.254.", "fe8", "fe9", "fea", "feb",
} {
if strings.HasPrefix(ip.String(), linkLocalPrefix) { if strings.HasPrefix(ip.String(), linkLocalPrefix) {
addIP(20, ip)
continue addrs continue addrs
} }
} }
return ip.String() addIP(30, ip)
} }
} }
return sort.Slice(hosts, func(i, j int) bool { return hosts[i].prio > hosts[j].prio })
return hosts[0].host
} }

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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"
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

View 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"
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

View 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+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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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

View file

@ -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

View 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

File diff suppressed because it is too large Load diff

Binary file not shown.

View 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
}

View 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
}

View 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
}

View 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)))
}

View 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
}

View 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
View 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
}

View 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,
}

View 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
View 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)
}
}

127
main.go
View file

@ -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. * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
@ -13,8 +13,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Foobar. If not, see <http://www.gnu.org/licenses/>. * along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
*/ */
package main package main
@ -27,15 +27,19 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/net/websocket"
"log" "log"
mathrand "math/rand" mathrand "math/rand"
"net" "net"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/unrud/remote-touchpad/inputcontrol"
"github.com/unrud/remote-touchpad/terminal"
"golang.org/x/net/websocket"
) )
const ( const (
@ -44,50 +48,69 @@ const (
authenticationRateBurst int = 10 authenticationRateBurst int = 10
challengeLength int = 8 challengeLength int = 8
defaultBind string = ":0" defaultBind string = ":0"
version string = "0.0.5" version string = "1.5.4"
prettyAppName string = "Remote Touchpad"
) )
func processCommand(plugin Plugin, command string) error { 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 { if len(command) == 0 {
return errors.New("empty command") return errors.New("empty command")
} }
if command == "sf" { if command == "S" {
return plugin.PointerScrollFinish() return controller.PointerScroll(0, 0, true)
} }
if command[0] == 't' { if command[0] == 't' {
text := command[1:] text := command[1:]
if !utf8.ValidString(text) { if !utf8.ValidString(text) {
return errors.New("invalid utf-8") return errors.New("invalid utf-8")
} }
return plugin.KeyboardText(text) return controller.KeyboardText(text)
} }
arguments := strings.Split(command[1:], ";") arguments := strings.Split(command[1:], ";")
if len(arguments) != 2 { if command[0] == 'k' && len(arguments) != 1 ||
command[0] != 'k' && len(arguments) != 2 {
return errors.New("wrong number of arguments") return errors.New("wrong number of arguments")
} }
x, err := strconv.ParseInt(arguments[0], 10, 32) x, err := strconv.ParseInt(arguments[0], 10, 32)
if err != nil { if err != nil {
return err return err
} }
if command[0] == 'k' {
if x < 0 || x >= int64(inputcontrol.KeyLimit) {
return errors.New("unsupported key")
}
return controller.KeyboardKey(inputcontrol.Key(x))
}
y, err := strconv.ParseInt(arguments[1], 10, 32) y, err := strconv.ParseInt(arguments[1], 10, 32)
if err != nil { if err != nil {
return err return err
} }
if command[0] == 'm' { if command[0] == 'm' {
return plugin.PointerMove(int(x), int(y)) return controller.PointerMove(int(x), int(y))
} }
if command[0] == 's' { if command[0] == 's' {
return plugin.PointerScroll(int(x), int(y)) return controller.PointerScroll(int(x), int(y), false)
}
if command[0] == 'S' {
return controller.PointerScroll(int(x), int(y), true)
} }
if command[0] == 'b' { if command[0] == 'b' {
if x <= 0 || x > 3 { if x < 0 || x >= int64(inputcontrol.PointerButtonLimit) {
return errors.New("unsupported pointer button") return errors.New("unsupported pointer button")
} }
b := true b := true
if y == 0 { if y == 0 {
b = false b = false
} }
return plugin.PointerButton(uint(x), b) return controller.PointerButton(inputcontrol.PointerButton(x), b)
} }
return errors.New("unsupported command") return errors.New("unsupported command")
} }
@ -128,49 +151,61 @@ func secureRandBase64(length int) string {
} }
func main() { func main() {
terminal.SetTitle(prettyAppName)
var bind, certFile, keyFile, secret string 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.BoolVar(&showVersion, "version", false, "show program's version number and exit")
flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]:PORT") flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]:PORT")
flag.StringVar(&secret, "secret", "", "shared secret for client authentication") flag.StringVar(&secret, "secret", "", "shared secret for client authentication")
flag.StringVar(&certFile, "cert", "", "file containing TLS certificate") flag.StringVar(&certFile, "cert", "", "file containing TLS certificate")
flag.StringVar(&keyFile, "key", "", "file containing TLS private key") 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.Int("interval", 0, "obsolete, not used anymore") 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() flag.Parse()
if showVersion { if showVersion {
fmt.Println(version) fmt.Println(version)
return return
} }
if certFile != "" && keyFile == "" { if certFile != "" && keyFile == "" {
log.Fatal("TLS certificate missing") log.Fatal("TLS private key file missing")
} }
if certFile == "" && keyFile != "" { if certFile == "" && keyFile != "" {
log.Fatal("TLS private key missing") log.Fatal("TLS certificate file missing")
} }
tls := certFile != "" && keyFile != "" tls := certFile != "" && keyFile != ""
if secret == "" { if secret == "" {
secret = secureRandBase64(defaultSecretLength) secret = secureRandBase64(defaultSecretLength)
} }
var plugin Plugin if len(inputcontrol.Controllers) == 0 {
var pluginName string log.Fatal("compiled without controller")
platformErrors := "" }
for _, pluginInfo := range Plugins { var controller inputcontrol.Controller
pluginName = pluginInfo.Name var controllerName string
var platformErrs []error
for _, controllerInfo := range inputcontrol.Controllers {
controllerName = controllerInfo.Name
var err error var err error
plugin, err = pluginInfo.Init() controller, err = controllerInfo.Init()
if err == nil { if err == nil {
break break
} else if _, ok := err.(UnsupportedPlatformError); ok {
platformErrors += fmt.Sprintf("%s plugin: %v\n", pluginName, err)
} else { } else {
log.Fatal(fmt.Sprintf("%s plugin: %v", pluginName, 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 { if controller == nil {
log.Fatal("unsupported platform:\n" + platformErrors) log.Fatal(fmt.Errorf("unsupported platform:\n%w", errors.Join(platformErrs...)))
} }
defer plugin.Close() defer controller.Close()
authenticationChallenges := make(chan challenge, authenticationRateBurst) authenticationChallenges := make(chan challenge, authenticationRateBurst)
go authenticationChallengeGenerator(secret, authenticationChallenges) go authenticationChallengeGenerator(secret, authenticationChallenges)
listener, err := net.Listen("tcp", bind) listener, err := net.Listen("tcp", bind)
@ -190,11 +225,11 @@ func main() {
} }
} }
if host == "" { if host == "" {
host = FindDefaultHost() host = findDefaultHost()
} }
port := addr.Port port := addr.Port
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/", http.FileServer(assetFS())) mux.Handle("/", http.FileServer(http.FS(webdataFS)))
mux.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) { mux.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
var message string var message string
challenge := <-authenticationChallenges challenge := <-authenticationChallenges
@ -205,12 +240,13 @@ func main() {
if !challenge.verify(message) { if !challenge.verify(message) {
return return
} }
websocket.JSON.Send(ws, config)
for { for {
if err := websocket.Message.Receive(ws, &message); err != nil { if err := websocket.Message.Receive(ws, &message); err != nil {
return return
} }
if err := processCommand(plugin, message); err != nil { if err := processCommand(controller, message); err != nil {
log.Print(fmt.Sprintf("%s plugin: %v", pluginName, err)) log.Print(fmt.Errorf("%s controller: %w", controllerName, err))
return return
} }
} }
@ -223,16 +259,17 @@ func main() {
if tls { if tls {
scheme = "https" scheme = "https"
} }
url := fmt.Sprintf("%s://%s/#%s\n", scheme, domain, secret) url := fmt.Sprintf("%s://%s/#%s", scheme, domain, secret)
readyMsg := "ready: " + url fmt.Println(url)
qrCode, _ := GenerateQRCode(url, invert) if qrCode, err := terminal.GenerateQRCode(url, terminal.SupportsColor(os.Stdout.Fd())); err == nil {
readyMsg += qrCode fmt.Print(qrCode)
if !tls { } else {
readyMsg += ("" + log.Printf("QR code error: %v", err)
"▌ WARNING: TLS is not enabled ▐\n" + }
"▌Don't use in an untrusted network!▐\n") if !tls {
fmt.Println("▌ WARNING: TLS is not enabled ▐")
fmt.Println("▌Don't use in an untrusted network!▐")
} }
log.Print(readyMsg)
if tls { if tls {
err = http.ServeTLS(listener, mux, certFile, keyFile) err = http.ServeTLS(listener, mux, certFile, keyFile)
} else { } else {

View file

@ -1,48 +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
type PluginInfo struct {
Name string
Init func() (Plugin, error)
}
var Plugins []PluginInfo = []PluginInfo{
{"X11", InitX11Plugin},
{"RemoteDesktop portal", InitPortalPlugin},
{"Windows", InitWindowsPlugin},
}
type UnsupportedPlatformError struct {
err error
}
func (e UnsupportedPlatformError) Error() string {
return e.err.Error()
}
type Plugin interface {
Close() error
KeyboardText(text string) error
PointerButton(button uint, press bool) error
PointerMove(deltaX, deltaY int) error
PointerScroll(deltaHorizontal, deltaVertical int) error
PointerScrollFinish() error
}

View file

@ -1,271 +0,0 @@
// +build portal
/*
* 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 (
dbus "anonscm.debian.org/git/pkg-go/packages/golang-dbus.git"
"errors"
"fmt"
"sync"
)
const (
deviceKeyboard uint32 = 1
devicePointer uint32 = 2
btnReleased uint32 = 0
btnPressed uint32 = 1
btnLeft int32 = 0x110
btnRight int32 = 0x111
btnMiddle int32 = 0x112
)
type portalPlugin struct {
bus *dbus.Conn
remoteDesktop dbus.BusObject
sessionHandle dbus.ObjectPath
lock sync.RWMutex
}
func InitPortalPlugin() (Plugin, 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}
}
remoteDesktop := bus.Object("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop")
availableDeviceTypesV, err := remoteDesktop.GetProperty(
"org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
if err != nil {
return nil, UnsupportedPlatformError{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")}
}
inVardict := make(map[string]dbus.Variant)
inVardict["session_handle_token"] = dbus.MakeVariant("t")
result, outVardict, err := getResponse(bus, remoteDesktop,
"org.freedesktop.portal.RemoteDesktop.CreateSession", 0, inVardict)
if err != nil {
return nil, UnsupportedPlatformError{err}
}
if result != 0 {
return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
"Calling 'CreateSession' failed (%v)", result))}
}
sessionHandleV, ok := outVardict["session_handle"]
if !ok {
return nil, UnsupportedPlatformError{errors.New(
"'session_handle' missing from 'CreateSession' return value")}
}
sessionHandleS, ok := sessionHandleV.Value().(string)
if !ok {
return nil, UnsupportedPlatformError{errors.New(
"unexpected 'session_handle' type in 'CreateSession' return value")}
}
sessionHandle := dbus.ObjectPath(sessionHandleS)
inVardict = make(map[string]dbus.Variant)
inVardict["type"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
result, outVardict, err = getResponse(bus, remoteDesktop,
"org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
if err != nil {
return nil, UnsupportedPlatformError{err}
}
if result != 0 {
return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
"Calling 'SelectDevices' failed (%v)", result))}
}
inVardict = make(map[string]dbus.Variant)
result, outVardict, err = getResponse(bus, remoteDesktop,
"org.freedesktop.portal.RemoteDesktop.Start", 0, sessionHandle, "", inVardict)
if err != nil {
return nil, UnsupportedPlatformError{err}
}
if result != 0 {
return nil, errors.New("keyboard or pointer access denied")
}
devicesV, ok := outVardict["devices"]
if !ok {
return nil, UnsupportedPlatformError{errors.New(
"'devices' missing from 'Start' return value")}
}
devices, ok := devicesV.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 &portalPlugin{bus: bus, remoteDesktop: remoteDesktop,
sessionHandle: sessionHandle}, nil
}
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)
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, errors.New("unexpected 'Response' return length")
}
result, ok := s.Body[0].(uint32)
if !ok {
return 0, nil, errors.New("unexpected 'Response' return type")
}
outVardict, ok := s.Body[1].(map[string]dbus.Variant)
if !ok {
return 0, nil, errors.New("unexpected 'Response' return type")
}
return result, outVardict, nil
}
}
}
func (p *portalPlugin) Close() error {
p.lock.Lock()
defer p.lock.Unlock()
if p.bus == nil {
return errors.New("dbus connection closed")
}
if err := p.bus.Close(); err != nil {
return err
}
p.bus = nil
p.remoteDesktop = nil
return nil
}
func (p *portalPlugin) KeyboardText(text string) error {
p.lock.RLock()
defer p.lock.RUnlock()
if p.bus == nil {
return errors.New("dbus connection closed")
}
inVardict := make(map[string]dbus.Variant)
for _, runeValue := range text {
keysym := int32(0x01000000 + runeValue)
for _, state := range [...]uint32{btnPressed, btnReleased} {
if err := p.remoteDesktop.Call(
"org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym",
0, p.sessionHandle, inVardict, keysym, state).Store(); err != nil {
return err
}
}
}
return nil
}
func (p *portalPlugin) PointerButton(button uint, press bool) error {
p.lock.RLock()
defer p.lock.RUnlock()
if p.bus == nil {
return errors.New("dbus connection closed")
}
var btn int32
if button == 1 {
btn = btnLeft
} else if button == 2 {
btn = btnMiddle
} else if button == 3 {
btn = btnRight
} else {
return errors.New("unsupported pointer button")
}
state := btnReleased
if press {
state = btnPressed
}
inVardict := make(map[string]dbus.Variant)
if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerButton",
0, p.sessionHandle, inVardict, btn, state).Store(); err != nil {
return err
}
return nil
}
func (p *portalPlugin) PointerMove(deltaX, deltaY int) error {
p.lock.RLock()
defer p.lock.RUnlock()
if p.bus == nil {
return errors.New("dbus connection closed")
}
inVardict := make(map[string]dbus.Variant)
if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerMotion",
0, p.sessionHandle, inVardict, float64(deltaX), float64(deltaY)).Store(); err != nil {
return err
}
return nil
}
func (p *portalPlugin) pointerScrollFull(deltaHorizontal, deltaVertical int, finish bool) error {
p.lock.RLock()
defer p.lock.RUnlock()
if p.bus == nil {
return errors.New("dbus connection closed")
}
inVardict := make(map[string]dbus.Variant)
inVardict["finish"] = dbus.MakeVariant(finish)
if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerAxis",
0, p.sessionHandle, inVardict, float64(deltaHorizontal), float64(deltaVertical)).Store(); err != nil {
return err
}
return nil
}
func (p *portalPlugin) PointerScroll(deltaHorizontal, deltaVertical int) error {
return p.pointerScrollFull(deltaHorizontal, deltaVertical, false)
}
func (p *portalPlugin) PointerScrollFinish() error {
return p.pointerScrollFull(0, 0, true)
}

View file

@ -1,178 +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
scrollMult int = 6
)
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{err}
}
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 {
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
} else {
return errors.New("unsupported pointer button")
}
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(deltaHorizontal, deltaVertical int) 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 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) PointerScrollFinish() error {
return nil
}

View file

@ -1,28 +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"
func InitWindowsPlugin() (Plugin, error) {
return nil, UnsupportedPlatformError{errors.New("disabled")}
}

View file

@ -1,206 +0,0 @@
// +build x11
/*
* 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"
"fmt"
"os"
"sync"
"time"
"unsafe"
)
const (
typingDelay time.Duration = 100 * time.Millisecond
scrollDiv int = 20
)
type x11Plugin struct {
display *C.Display
lock sync.Mutex
scrollHorizontal, scrollVertical int
}
func InitX11Plugin() (Plugin, error) {
sessionType := os.Getenv("XDG_SESSION_TYPE")
if sessionType != "" && sessionType != "x11" {
return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
"unsupported session type '%v'", sessionType))}
}
display := C.XOpenDisplay(nil)
if display == nil {
return nil, UnsupportedPlatformError{
errors.New("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")
}
defer C.XFree(unsafe.Pointer(keysyms))
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
}
if emptyKeycode == 0 {
return errors.New("no empty keycode found")
}
keycodeMapping := make([]C.KeySym, keysymsPerKeycode)
defer func() {
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)
}()
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)
}
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(deltaHorizontal, deltaVertical int) error {
p.lock.Lock()
stepsHorizontal := (p.scrollHorizontal + deltaHorizontal) / scrollDiv
stepsVertical := (p.scrollVertical + deltaVertical) / scrollDiv
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 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
}
func (p *x11Plugin) PointerScrollFinish() error {
p.lock.Lock()
defer p.lock.Unlock()
p.scrollHorizontal = 0
p.scrollVertical = 0
return nil
}

View file

@ -1,28 +0,0 @@
// +build !x11
/*
* 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"
func InitX11Plugin() (Plugin, error) {
return nil, UnsupportedPlatformError{errors.New("disabled")}
}

12
remote-touchpad.service Normal file
View 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
View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Before After
Before After

BIN
screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

61
snap/snapcraft.yaml Normal file
View 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]

View file

@ -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. * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
@ -13,44 +13,50 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Foobar. If not, see <http://www.gnu.org/licenses/>. * along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
*/ */
package main package terminal
import qrcode "github.com/skip2/go-qrcode" 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) q, err := qrcode.New(message, qrcode.Medium)
if err != nil { if err != nil {
return "", err 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 := "" s := ""
for y := -1; y < len(bits); y += 2 { for y := -1; y < len(bits); y += 2 {
if colorize {
s += ForegroundBlack + BackgroundWhite
}
for x := range bits[0] { for x := range bits[0] {
upper := !invert upper := false
if 0 <= y && y < len(bits) { if 0 <= y && y < len(bits) {
upper = bits[y][x] upper = bits[y][x]
} }
lower := !invert lower := false
if 0 <= y+1 && y+1 < len(bits) { if 0 <= y+1 && y+1 < len(bits) {
lower = bits[y+1][x] lower = bits[y+1][x]
} }
if upper != invert && lower != invert { if upper && lower {
s += " " s += ""
} else if upper == invert && lower != invert { } else if upper && !lower {
s += "▀" s += "▀"
} else if upper != invert && lower == invert { } else if !upper && lower {
s += "▄" s += "▄"
} else { } else {
s += "" s += " "
} }
} }
if colorize {
s += ForegroundReset + BackgroundReset
}
s += "\n" s += "\n"
} }
return s return s

View file

@ -1,11 +1,9 @@
// +build !portal
/* /*
* Copyright (c) 2018 Unrud<unrud@outlook.com> * Copyright (c) 2018 Unrud <unrud@outlook.com>
* *
* This file is part of Remote-Touchpad. * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
@ -15,14 +13,15 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Foobar. If not, see <http://www.gnu.org/licenses/>. * along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
*/ */
package main package terminal
import "errors" const (
ForegroundBlack string = "\033[30m"
func InitPortalPlugin() (Plugin, error) { ForegroundReset string = "\033[39m"
return nil, UnsupportedPlatformError{errors.New("disabled")} BackgroundWhite string = "\033[47m"
} BackgroundReset string = "\033[49m"
)

View 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
}

View 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
View 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
View 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);
}
};

View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

66
webdata/app/socket.mjs Normal file
View 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
View 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
View 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);
}
}
}

View file

@ -1,385 +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;
// [[px/s, mult], ...]
const POINTER_ACCELERATION = [[0, 0], [87, 1], [173, 1], [553, 2]];
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 scrolling = false;
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("b1;0");
}
function updateMoveAndScroll() {
var moveX = Math.trunc(moveXSum);
var moveY = Math.trunc(moveYSum);
if (Math.abs(moveX) >= 1 || Math.abs(moveY) >= 1) {
moveXSum -= moveX;
moveYSum -= moveY;
ws.send("m" + 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;
scrolling = true;
ws.send("s" + scrollX + ";" + scrollY);
}
}
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;
}
if (scrolling) {
ws.send("sf");
scrolling = false;
}
}
}
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 (scrolling) {
ws.send("sf");
scrolling = false;
}
}
if (touchReleasedCount > TOUCH_MOVE_THRESHOLD.length) {
touchMoved = true;
}
if (ongoingTouches.length == 0 && touchReleasedCount >= 1 &&
dragging) {
ws.send("b1;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("b" + button + ";1");
if (button == 1) {
draggingTimeout = setTimeout(onDraggingTimeout, TOUCH_TIMEOUT);
} else {
ws.send("b" + 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;
if (scrolling) {
ws.send("sf");
scrolling = false;
}
}
}
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;
moveYSum += sumY;
} else if (ongoingTouches.length == 2) {
scrollXSum -= sumX;
scrollYSum -= sumY;
}
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 = "";
var wsProtocol = "wss:";
if (location.protocol == "http:") {
wsProtocol = "ws:";
}
ws = new WebSocket(wsProtocol + "//" + 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("t" + 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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

BIN
webdata/icon.woff Normal file

Binary file not shown.

View file

@ -1,33 +1,100 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, interactive-widget=resizes-content">
<meta name="viewport" content="width=device-width initial-scale=1, maximum-scale=1, user-scalable=0" /> <script src="app/main.mjs" type="module"></script>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Remote Touchpad</title>
<script src="fn.js"></script> <link href="main.css" media="screen" rel="stylesheet">
<script src="sha256.js"></script> <link href="icon.png" type="image/png" rel="icon">
<title>Remote Touchpad</title>
<link href="main.css" media="screen" rel="stylesheet" /> <div id="opening" class="scene">
<link href="icon.png" type="image/png" rel="shortcut icon" /> <noscript><p>JavaScript is required!</p></noscript>
</head> <p>Connecting…</p>
<body> </div>
<div id="opening">
<p>Connecting...</p> <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>
<div id="closed"> <div class="buttons" style="grid-column: 1 / 4">
<p>Disconnected</p> <button onclick="app.key(app.KEY_MEDIA_PREV_TRACK)"></button>
<button id="reloadbutton"></button> <button onclick="app.key(app.KEY_MEDIA_PLAY_PAUSE)"></button>
<button onclick="app.key(app.KEY_MEDIA_NEXT_TRACK)"></button>
</div> </div>
<div id="pad"> <div class="buttons" style="grid-column: 1 / 4">
<p id="padlabel">Touchpad</p> <button onclick="app.key(app.KEY_VOLUME_DOWN)">🔉</button>
<div class="buttons"> <button onclick="app.key(app.KEY_VOLUME_MUTE)">🔇</button>
<button id="fullscreenbutton"></button> <button onclick="app.key(app.KEY_VOLUME_UP)">🔊</button>
<button id="keyboardbutton"></button>
</div>
</div> </div>
<div id="keyboard"> </div>
<textarea id="text"></textarea> <div class="page">
<button id="sendbutton"></button> <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> </div>
</body> <div class="buttons" style="grid-column: 1 / 4">
</html> <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>

View file

@ -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%; width: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
} }
body { .scene {
font-family: sans; position: relative;
}
#pad, #sendbutton, #opening, #closed {
background-color: #404040 !important;
background-image: url(tex.png);
}
#opening, #closed {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; justify-content: center;
font-size: 2em; box-sizing: border-box;
color: white; align-items: center;
gap: 2rem;
padding: 0.5rem;
}
p {
margin: 0;
font-size: 2rem;
word-wrap: break-word; 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; 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; background-color: transparent;
border: 1px solid black; border: 1px solid black;
height: 1.5em; height: 1.5em;
width: 2em; 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; 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; border-bottom: none;
} }
#fullscreenbutton { button.left {
left: 0;
border-left: none; border-left: none;
} }
#keyboardbutton { button.right {
right: 0;
border-right: none; 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 { .buttons {
display: flex; display: flex;
flex-direction: row;
min-height: 0;
min-width: 0;
} }
#sendbutton { .buttons > button:not(:last-child) {
border-right: none; 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%; height: 100%;
} }
@media (max-aspect-ratio: 4/3) { #text-input {
#keyboard { 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; flex-direction: column;
} }
#sendbutton { #text-input .buttons {
border-left: none; flex-direction: row;
border-top: 1px solid black;
height: 1.5em;
width: 100%;
} }
}
#text-input .buttons > button {
border-top: 1px solid black;
border-bottom: none;
}
#text-input .buttons > button:first-child {
border-left: none;
}
} }

View file

@ -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
View 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)
}
}