pax_global_header00006660000000000000000000000064145666213350014525gustar00rootroot0000000000000052 comment=15d09b0f9f971792c1a09a5e53640951b8b74aac wayvnc-0.8.0/000077500000000000000000000000001456662133500130415ustar00rootroot00000000000000wayvnc-0.8.0/.builds/000077500000000000000000000000001456662133500144015ustar00rootroot00000000000000wayvnc-0.8.0/.builds/archlinux.yml000066400000000000000000000015061456662133500171230ustar00rootroot00000000000000image: archlinux packages: - base-devel - libglvnd - libxkbcommon - pixman - gnutls - jansson - wayland - wayland-protocols - meson # runtime deps for integration testing: - sway - jq - lsof - python-pycryptodomex # needed by vncdotool - vncdotool sources: - http://github.com/any1/wayvnc - http://github.com/any1/neatvnc - http://github.com/any1/aml tasks: - aml: | cd aml meson --prefix=/usr build ninja -C build sudo ninja -C build install - neatvnc: | cd neatvnc meson --prefix=/usr build ninja -C build sudo ninja -C build install - build: | cd wayvnc meson --prefix=/usr build ninja -C build - test: | cd wayvnc ninja -C build test - integration: | cd wayvnc ./test/integration/integration.sh wayvnc-0.8.0/.builds/freebsd.yml000066400000000000000000000017661456662133500165500ustar00rootroot00000000000000image: freebsd/latest packages: - devel/meson - devel/pkgconf - devel/jansson - devel/evdev-proto - graphics/wayland - graphics/libdrm - graphics/libjpeg-turbo - graphics/mesa-libs - x11/pixman - x11/libxkbcommon - multimedia/ffmpeg - security/gnutls # runtime deps for integration testing: - x11-wm/sway - textproc/jq - sysutils/lsof - shells/bash - devel/py-pip sources: - http://github.com/any1/wayvnc - http://github.com/any1/neatvnc - http://github.com/any1/aml tasks: - pip-vncdotool: | sudo pip install vncdotool - aml: | cd aml meson --prefix=/usr build ninja -C build sudo ninja -C build install - neatvnc: | cd neatvnc meson --prefix=/usr build ninja -C build sudo ninja -C build install - build: | cd wayvnc meson --prefix=/usr build ninja -C build - test: | cd wayvnc ninja -C build test - integration: | cd wayvnc ./test/integration/integration.sh wayvnc-0.8.0/.github/000077500000000000000000000000001456662133500144015ustar00rootroot00000000000000wayvnc-0.8.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001456662133500165645ustar00rootroot00000000000000wayvnc-0.8.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015401456662133500212560ustar00rootroot00000000000000--- name: Bugs about: Crashes and other bugs labels: 'bug' --- ### Useful information: Please, try to gather as much of useful information as possible and follow these instructions: - **Version:** - Run this command: `wayvnc -V` - Try to reproduce while capturing a **trace log:** - `wayvnc -Ltrace | tee wayvnc-crash.log` - Get the **stack trace**: - If have `coredumpctl`, you can gather the stack trace after a crash using `coredumpctl gdb wayvnc` and then run `bt full` to obtain the stack trace. - Otherwise, you can either locate the core file and load it into gdb or run wayvnc in gdb like so: - `gdb --args wayvnc -Ltrace` - If the lines mentioning wayvnc, neatvnc or aml have `??`, please compile wayvnc and those other projects from source with debug symbols and try again. - Describe how to **reproduce** the problem wayvnc-0.8.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003121456662133500205500ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions url: "https://github.com/any1/wayvnc/discussions" about: "Please ask questions on IRC in #wayvnc on Libera Chat or in Discussions" wayvnc-0.8.0/.github/ISSUE_TEMPLATE/enhancement.md000066400000000000000000000001131456662133500213660ustar00rootroot00000000000000--- name: Enhancements about: New functionality labels: 'enhancement' --- wayvnc-0.8.0/.github/pull_request_template.md000066400000000000000000000000721456662133500213410ustar00rootroot00000000000000Please read CONTRIBUTING.md before making a pull request. wayvnc-0.8.0/.github/workflows/000077500000000000000000000000001456662133500164365ustar00rootroot00000000000000wayvnc-0.8.0/.github/workflows/build.yml000066400000000000000000000021461456662133500202630ustar00rootroot00000000000000name: Build and Unit Test on: push: branches: [ "master", "ci-test" ] pull_request: branches: [ "master" ] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: checkout aml uses: actions/checkout@v3 with: repository: any1/aml path: subprojects/aml - name: checkout neatvnc uses: actions/checkout@v3 with: repository: any1/neatvnc path: subprojects/neatvnc - name: prepare environment run: | sudo apt-get update sudo apt-get install -y meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev libavutil-dev libturbojpeg0-dev scdoc # runtime deps for integration testing: sudo apt-get install -y sway jq lsof pip install vncdotool - name: configure run: meson build -D tests=true - name: compile run: meson compile -C build - name: unit tests run: meson test --verbose -C build - name: integration tests run: ./test/integration/integration.sh wayvnc-0.8.0/.gitignore000066400000000000000000000001261456662133500150300ustar00rootroot00000000000000build subprojects *.swp .clang_complete .ycm_extra_conf.py perf.* *.pem .vimrc .cache wayvnc-0.8.0/CONTRIBUTING.md000066400000000000000000000110461456662133500152740ustar00rootroot00000000000000# Contributing to wayvnc ## Commit Messages Please, try to write good commit messages. Do your best to follow these 7 rules, borrowed from [Chris Beams](https://chris.beams.io/posts/git-commit/), plus 1 extra rule: 1. Separate subject from body with a blank line 2. Limit the subject line to 50 characters 3. Capitalize the subject line 4. Do not end the subject line with a period 5. Use the imperative mood in the subject line 6. Wrap the body at 72 characters 7. Use the body to explain what and why vs. how 8. (Extra) Prefix the subject line with the component that's modified If you wish to know why we follow these rules, please read Chris Beams' blog entry, linked above. Rule number 8 allows us to quickly gauge if a given commit is relevant to what we're looking for when skimming the log. It adds consistency and simplifies the message. For example ``` ctl-client: Print trailing newline for events ``` is better than ``` Print trailing newline for events in ctl-client ``` **Example:** ``` ctl-client: Print trailing newline for events If someone wants to parse this instead of using jq, a trailing newline delimits the end of the event. ``` ## Style This project follows the the [Linux kernel's style guide](https://www.kernel.org/doc/html/latest/process/coding-style.html#codingstyle) as far as coding style is concerned, with the following exceptions: * When declaring pointer variables, the asterisk (`*`) is placed on the left with the type rather than the variable name. Declaring multiple variables in the same line is not allowed. * Wrapped argument lists should not be aligned. Use two tabs instead. There is a lot of code that uses aligned argument lists in the project, but I have come to the conclusion that these alignments are not very nice to maintain. ### Summary & Examples: In case you aren't familiar with Linux's coding style, here is a short summary and some examples of acceptable formatting: * Use tabs for indenting. * We do not align code (mostly), but when we do, we use spaces rather than tabs. This rule is not according to the Linux style guide. * The preferred limit on the length of a single line is 80 columns. * User-visible string such as log messages must not be split up. * Use space after the following keywords: `if`, `switch`, `case`, `for`, `do`, `while`. * Do **not** use space after others such as: `sizeof`, `typeof`, `alignof` or `__attribute__`. * Do **not** use typedefs. * It is allowed to use typedefs for function pointers. This rule is not according to the Linux style guide. #### Functions ``` static int do_something(int number, const char* text) { body of function } ``` #### `if` ``` // Single statement if (condition) do_this(); // Multiple statements if (condition) { do_this(2, "41"); do_that(); } // Single statement if/else if (condition) do_this(); else do_that(); // Multi-statement if/else if (condition) { do_this(); do_that(); } else { otherwise(); } ``` #### `switch` ``` switch (value) { case 3: printf("three!\n"); break; case 5: printf("five!\n"); break; case 42: printf("the answer to life, the universe and everything: "); // fallthrough default: printf("%d\n", value); break; } ``` #### Arithmetic ``` int a = b * c + 5; ``` #### Pointers ``` char* some_text = "some text"; char* just_text = text + 5; char t = *just_text; char e = just_text[1]; ``` ## Testing ### Unit Tests wayvnc has a small but growing set of unit tests, which are run on every GitHub PR. To run them locally, do the following: ```bash meson test -C build ``` ### Integration Tests There are also a handful of integration tests which also run on every PR. Read the [integration tests documentation](test/integration/README.md) for more details, but to run them locally: ``` ./test/integration/integration.sh ``` ### Valgrind There is a helper script in [util/valgrind.sh](util/valgrind.sh) to aid in memory profiling of wayvnc and wayvncctl. This can help find and eliminate memory leaks. ### Automated Tests We run a set of tests on every PR, in three different environments. Each run ensures that the proposed code change: 1. Builds successfully 2. Passes all unit tests 3. Passes all integration tests And does so in 3 different environments: - Ubuntu as a [github action](.github/workflows/build.yml) - Arch Linux as a [sourcehut build](.builds/archlinux.yml) - FreeBSD as a [sourcehut build](.builds/freebsd.yaml) ## No Brown M&Ms All pull requests must contain the following sentence in the description: I have read and understood CONTRIBUTING.md. wayvnc-0.8.0/COPYING000066400000000000000000000013431456662133500140750ustar00rootroot00000000000000Copyright (c) 2019 - 2020 Andri Yngvason Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. wayvnc-0.8.0/FAQ.md000066400000000000000000000017231456662133500137750ustar00rootroot00000000000000# FAQ **Q: How can I run wayvnc in headless mode/over an SSH session?** A: Set the environment variables `WLR_BACKENDS=headless` and `WLR_LIBINPUT_NO_DEVICES=1` before starting sway, then set `WAYLAND_DISPLAY=wayland-1` and run wayvnc. For older versions of sway, `WAYLAND_DISPLAY` is `wayland-0`. Try that if `wayland-1` doesn't work. **Q: How can I pass my mod-key from Sway to the remote desktop session?** A: Create an almost empty mode in your sway config. Example: ``` mode passthrough { bindsym $mod+Pause mode default } bindsym $mod+Pause mode passthrough ``` This makes it so that when you press $mod+Pause, all keybindings, except the one to switch back, are disabled. **Q: Not all symbols show up when I'm typing. What can I do to fix this?** A: Try setting the keyboard layout in wayvnc to the one that most closely matches the keyboard layout that you're using on the client side. An exact layout isn't needed, just one that has all the symbols that you use. wayvnc-0.8.0/FUNDING.yml000066400000000000000000000000271456662133500146550ustar00rootroot00000000000000patreon: andriyngvason wayvnc-0.8.0/README.md000066400000000000000000000133011456662133500143160ustar00rootroot00000000000000# wayvnc [![Build and Unit Test](https://github.com/any1/wayvnc/actions/workflows/build.yml/badge.svg)](https://github.com/any1/wayvnc/actions/workflows/build.yml) [![builds.sr.ht status](https://builds.sr.ht/~andri/wayvnc/commits/master.svg)](https://builds.sr.ht/~andri/wayvnc/commits/master?) [![Packaging status](https://repology.org/badge/tiny-repos/wayvnc.svg)](https://repology.org/project/wayvnc/versions) ## Introduction This is a VNC server for wlroots-based Wayland compositors (:no_entry: Gnome, KDE and Weston are **not** supported). It attaches to a running Wayland session, creates virtual input devices, and exposes a single display via the RFB protocol. The Wayland session may be a headless one, so it is also possible to run wayvnc without a physical display attached. Please check the [FAQ](FAQ.md) for answers to common questions. For further support, join the #wayvnc IRC channel on libera.chat, or ask your questions on the GitHub [discussion forum](https://github.com/any1/wayvnc/discussions) for the project. ## Building ### Runtime Dependencies * aml * drm * gbm (optional) * libxkbcommon * neatvnc * pam (optional) * pixman * jansson ### Build Dependencies * GCC * meson * ninja * pkg-config #### For Arch Linux ``` pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson ``` #### For Fedora 37 ``` dnf install -y meson gcc ninja-build pkg-config egl-wayland egl-wayland-devel \ mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \ libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \ libxkbcommon-devel libxkbcommon libwayland-client \ pam-devel pixman-devel libgbm-devel libdrm-devel scdoc \ libavcodec-free-devel libavfilter-free-devel libavutil-free-devel \ turbojpeg-devel wayland-devel gnutls-devel jansson-devel ``` #### For Debian (unstable / testing) ``` apt build-dep wayvnc ``` #### For Ubuntu ``` apt install meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev \ libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev \ libavutil-dev libturbojpeg0-dev scdoc ``` #### Additional build-time dependencies The easiest way to satisfy the neatvnc and aml dependencies is to link to them in the subprojects directory: ``` git clone https://github.com/any1/wayvnc.git git clone https://github.com/any1/neatvnc.git git clone https://github.com/any1/aml.git mkdir wayvnc/subprojects cd wayvnc/subprojects ln -s ../../neatvnc . ln -s ../../aml . cd - mkdir neatvnc/subprojects cd neatvnc/subprojects ln -s ../../aml . cd - ``` ### Configure and Build ``` meson build ninja -C build ``` To run the unit tests: ``` meson test -C build ``` To run the [integration tests](test/integration/README.md): ``` ./test/integration/integration.sh ``` ## Running Wayvnc can be run from the build directory like so: ``` ./build/wayvnc ``` :radioactive: The server only accepts connections from localhost by default. To accept connections via any interface, set the address to `0.0.0.0` like this: ``` ./build/wayvnc 0.0.0.0 ``` :warning: Do not do this on a public network or the internet without user authentication enabled. The best way to protect your VNC connection is to use SSH tunneling while listening on localhost, but users can also be authenticated when connecting to wayvnc. ### Encryption & Authentication #### VeNCrypt (TLS) For TLS, you'll need a private X509 key and a certificate. A self-signed key with a certificate can be generated like so: ``` cd ~/.config/wayvnc openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ -keyout tls_key.pem -out tls_cert.pem -subj /CN=localhost \ -addext subjectAltName=DNS:localhost,DNS:localhost,IP:127.0.0.1 cd - ``` Replace `localhost` and `127.0.0.1` in the command above with your public facing host name and IP address, respectively, or just keep them as is if you're testing locally. Create a config with the authentication info and load it using the `--config` command line option or place it at the default location `$HOME/.config/wayvnc/config`. ``` use_relative_paths=true address=0.0.0.0 enable_auth=true username=luser password=p455w0rd private_key_file=tls_key.pem certificate_file=tls_cert.pem ``` #### RSA-AES The RSA-AES security type combines RSA with AES in EAX mode to provide secure authentication and encryption that's resilient to eavesdropping and MITM. Its main weakness is that the user has to verify the server's credentials on first use. Thereafter, the client software should warn the user if the server's credentials change. It's a Trust on First Use (TOFU) scheme as employed by SSH. For the RSA-AES to be enabled, you need to generate an RSA key. This can be achieved like so: ``` ssh-keygen -m pem -f ~/.config/wayvnc/rsa_key.pem -t rsa -N "" ``` You also need to tell wayvnc where this file is located, by setting setting the `rsa_private_key_file` configuration parameter: ``` use_relative_paths=true address=0.0.0.0 enable_auth=true username=luser password=p455w0rd rsa_private_key_file=rsa_key.pem ``` You may also add credentials for TLS in combination with RSA. The client will choose. ### wayvncctl control socket To facilitate runtime interaction and control, wayvnc opens a unix domain socket at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A client can connect and exchange json-formatted IPC messages to query and control the running wayvnc instance. Use the `wayvncctl` utility to interact with this control socket from the command line. See the `wayvnc(1)` manpage for an in-depth description of the IPC protocol and the available commands, and `wayvncctl(1)` for more on the command line interface. There is also a handy event-loop mode that can be used to run commands when various events occur in wayvnc. See [examples/event-watcher](examples/event-watcher) for more details. wayvnc-0.8.0/examples/000077500000000000000000000000001456662133500146575ustar00rootroot00000000000000wayvnc-0.8.0/examples/README.md000066400000000000000000000021101456662133500161300ustar00rootroot00000000000000# Example Scripts The scripts here are examples of how you can automate interesting things with the wayvncctl IPC events. ## event-watcher This is a pretty simple example that just demonstrates how to tie the `wayvncctl event-receive` event loop into a bash script. It logs when clients connect and disconnect. ## single-output-sway This is more purposeful, and implements an idea for multi-output wayland servers, collapsing all outputs down to one when the first client connects, and restoring the configuration when the last client exits. The mechanism used to collapse the outputs depends on the version of sway installed: - For sway-1.7 and earlier, the script just temporarily disables all outputs except the one being captured. This moves all workspaces to the single remaining output. - For sway-1.8 and later, the script creates a temporary virtual output called `HEADLESS-[0-9]+' and then disables all physical outputs, which moves all workspaces to the virtual output. On disconnect, all original physical outputs are re-enabled, and the virtual output is destroyed. wayvnc-0.8.0/examples/auto-attach.py000077500000000000000000000040361456662133500174510ustar00rootroot00000000000000#!/usr/bin/env python import asyncio import json import re import os import glob class Program: command_seq = 0 reader = None writer = None read_buffer = "" message_queue = asyncio.Queue() reply_queue = asyncio.Queue() decoder = json.JSONDecoder() tasks = [] async def read_message(self): while True: try: result, index = self.decoder.raw_decode(self.read_buffer) self.read_buffer = self.read_buffer[index:].lstrip() return result except json.JSONDecodeError: data = await self.reader.read(4096) self.read_buffer += data.decode('utf-8') async def send_command(self, method, params = None): cmd = { "method": method, "id": self.command_seq, } if not params is None: cmd['params'] = params self.command_seq += 1 self.writer.write(json.dumps(cmd).encode()) await self.writer.drain() reply = await self.reply_queue.get() self.reply_queue.task_done() return reply['code'] == 0 async def attach(self, display): return await self.send_command('attach', {'display': display}) async def attach_any(self): for path in glob.iglob('/run/user/*/wayland-*'): if path.endswith('.lock'): continue if await self.attach(path): return True return False async def handle_detached(self): while not await self.attach_any(): await asyncio.sleep(1.0) async def process_message(self, message): method = message['method'] if (method == 'detached'): await self.handle_detached() async def message_processor(self): while True: message = await self.read_message() if 'method' in message: await self.message_queue.put(message) elif 'code' in message: await self.reply_queue.put(message) async def main(self): self.reader, self.writer = await asyncio.open_unix_connection("/tmp/wayvncctl-0") self.tasks.append(asyncio.create_task(self.message_processor())) await self.attach_any() await self.send_command("event-receive") while True: message = await self.message_queue.get() await self.process_message(message) prog = Program() asyncio.run(prog.main()) wayvnc-0.8.0/examples/event-watcher000077500000000000000000000035121456662133500173620ustar00rootroot00000000000000#!/bin/bash # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to WAYVNCCTL=${WAYVNCCTL:-wayvncctl} connection_count_now() { echo "Total clients: $1" } while IFS= read -r EVT; do case "$(jq -r '.method' <<<"$EVT")" in client-*onnected) count=$(jq -r '.params.connection_count' <<<"$EVT") connection_count_now "$count" ;; wayvnc-shutdown) echo "wayvncctl is no longer running" connection_count_now 0 ;; wayvnc-startup) echo "Ready to receive wayvnc events" ;; esac done < <("$WAYVNCCTL" --wait --reconnect --json event-receive) wayvnc-0.8.0/examples/single-output-sway000077500000000000000000000103561456662133500204120ustar00rootroot00000000000000#!/bin/bash # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to WAYVNCCTL=${WAYVNCCTL:-wayvncctl} SWAYMSG=${SWAYMSG:-swaymsg} SWAY_HAS_UNPLUG=false IFS=" .-" read -r _ _ SWAYMAJOR SWAYMINOR _ < <($SWAYMSG -v) if [[ $SWAYMAJOR -ge 1 && $SWAYMINOR -ge 8 ]]; then echo "Detected sway version 1.8 or later: Enabling virtual output device mode" SWAY_HAS_UNPLUG=true else echo "Detected sway version 1.7 or earlier: Not enabling virtual output device mode" fi find_output_matching() { local pattern=$1 $WAYVNCCTL -j output-list | jq -r ".[].name | match(\"$pattern\").string" } wait_for_output_matching() { local pattern=$1 local output output=$(find_output_matching "$pattern") while [[ -z $output ]]; do sleep 0.5 output=$(find_output_matching "$pattern") done echo "$output" } OUTPUTS_TO_RECONNECT=() HEADLESS= restore_outputs() { [[ ${#OUTPUTS_TO_RECONNECT[@]} -ge 1 ]] || return echo "Restoring original output state" for output in "${OUTPUTS_TO_RECONNECT[@]}"; do echo "Re-enabling output $output" $SWAYMSG output "$output" enable done if [[ $SWAY_HAS_UNPLUG == true && $HEADLESS ]]; then local firstOutput=${OUTPUTS_TO_RECONNECT[0]} echo "Switching wayvnc back to physical output $firstOutput" wait_for_output_matching "$firstOutput" >/dev/null $WAYVNCCTL output-set "$firstOutput" echo "Removing virtual output $HEADLESS" $SWAYMSG output "$HEADLESS" unplug fi OUTPUTS_TO_RECONNECT=() HEADLESS= } trap restore_outputs EXIT collapse_outputs() { if [[ $SWAY_HAS_UNPLUG == true ]]; then local preexisting="$(find_output_matching 'HEADLESS-\\d+')" if [[ $preexisting ]]; then echo "Switching to preexisting virtual output $preexisting" $WAYVNCCTL output-set "$preexisting" else echo "Creating a virtual display" $SWAYMSG create_output echo "Waiting for virtusl output to be created..." HEADLESS=$(wait_for_output_matching 'HEADLESS-\\d+') echo "Switching to virtual output $HEADLESS" $WAYVNCCTL output-set "$HEADLESS" fi fi for output in $($WAYVNCCTL -j output-list | jq -r '.[] | select(.captured==false).name'); do echo "Disabling extra output $output" $SWAYMSG output "$output" disable OUTPUTS_TO_RECONNECT+=("$output") done } connection_count_now() { local count=$1 if [[ $count == 1 ]]; then collapse_outputs elif [[ $count == 0 ]]; then restore_outputs fi } while IFS= read -r EVT; do case "$(jq -r '.method' <<<"$EVT")" in client-*onnected) count=$(jq -r '.params.connection_count' <<<"$EVT") connection_count_now "$count" ;; wayvnc-shutdown) echo "wayvncctl is no longer running" connection_count_now 0 ;; wayvnc-startup) echo "Ready to receive wayvnc events" ;; esac done < <("$WAYVNCCTL" --wait --reconnect --json event-receive) wayvnc-0.8.0/include/000077500000000000000000000000001456662133500144645ustar00rootroot00000000000000wayvnc-0.8.0/include/buffer.h000066400000000000000000000046711456662133500161160ustar00rootroot00000000000000/* * Copyright (c) 2020 - 2021 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "sys/queue.h" #include "config.h" #include #include #include #include struct wl_buffer; struct gbm_bo; struct nvnc_fb; enum wv_buffer_type { WV_BUFFER_UNSPEC = 0, WV_BUFFER_SHM, #ifdef ENABLE_SCREENCOPY_DMABUF WV_BUFFER_DMABUF, #endif }; struct wv_buffer { enum wv_buffer_type type; TAILQ_ENTRY(wv_buffer) link; struct nvnc_fb* nvnc_fb; struct wl_buffer* wl_buffer; void* pixels; size_t size; int width, height, stride; uint32_t format; bool y_inverted; struct pixman_region16 damage; /* The following is only applicable to DMABUF */ struct gbm_bo* bo; }; TAILQ_HEAD(wv_buffer_queue, wv_buffer); struct wv_buffer_pool { struct wv_buffer_queue queue; enum wv_buffer_type type; int width, height, stride; uint32_t format; }; enum wv_buffer_type wv_buffer_get_available_types(void); struct wv_buffer* wv_buffer_create(enum wv_buffer_type, int width, int height, int stride, uint32_t fourcc); void wv_buffer_destroy(struct wv_buffer* self); void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width, int height); void wv_buffer_damage_whole(struct wv_buffer* self); void wv_buffer_damage_clear(struct wv_buffer* self); struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type, int width, int height, int stride, uint32_t format); void wv_buffer_pool_destroy(struct wv_buffer_pool* pool); void wv_buffer_pool_resize(struct wv_buffer_pool* pool, enum wv_buffer_type, int width, int height, int stride, uint32_t format); struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool); void wv_buffer_pool_release(struct wv_buffer_pool* pool, struct wv_buffer* buffer); wayvnc-0.8.0/include/cfg.h000066400000000000000000000027601456662133500154010ustar00rootroot00000000000000/* * Copyright (c) 2020 - 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #define X_CFG_LIST \ X(bool, enable_auth) \ X(bool, relax_encryption) \ X(string, private_key_file) \ X(string, certificate_file) \ X(string, rsa_private_key_file) \ X(string, username) \ X(string, password) \ X(string, address) \ X(uint, port) \ X(bool, enable_pam) \ X(string, xkb_rules) \ X(string, xkb_model) \ X(string, xkb_layout) \ X(string, xkb_variant) \ X(string, xkb_options) \ X(bool, use_relative_paths) \ struct cfg { char* directory; #define string char* #define uint uint32_t #define X(type, name) type name; X_CFG_LIST #undef X #undef uint #undef string }; int cfg_load(struct cfg* self, const char* path); void cfg_destroy(struct cfg* self); wayvnc-0.8.0/include/ctl-client.h000066400000000000000000000026011456662133500166720ustar00rootroot00000000000000/* * Copyright (c) 2022 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include struct ctl_client; struct option_parser; void ctl_client_debug_log(bool enable); struct ctl_client* ctl_client_new(const char* socket_path, void* userdata); void ctl_client_destroy(struct ctl_client*); void* ctl_client_userdata(struct ctl_client*); #define CTL_CLIENT_PRINT_JSON (1 << 0) #define CTL_CLIENT_SOCKET_WAIT (1 << 1) #define CTL_CLIENT_RECONNECT (1 << 2) int ctl_client_run_command(struct ctl_client* self, struct option_parser* parent_options, unsigned flags); void ctl_client_print_command_list(FILE* stream); void ctl_client_print_event_list(FILE* stream); wayvnc-0.8.0/include/ctl-commands.h000066400000000000000000000035271456662133500172250ustar00rootroot00000000000000/* * Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include enum cmd_type { CMD_ATTACH, CMD_DETACH, CMD_HELP, CMD_EVENT_RECEIVE, CMD_CLIENT_LIST, CMD_CLIENT_DISCONNECT, CMD_OUTPUT_LIST, CMD_OUTPUT_CYCLE, CMD_OUTPUT_SET, CMD_VERSION, CMD_WAYVNC_EXIT, CMD_UNKNOWN, }; #define CMD_LIST_LEN CMD_UNKNOWN enum event_type { EVT_CAPTURE_CHANGED, EVT_CLIENT_CONNECTED, EVT_CLIENT_DISCONNECTED, EVT_DETACHED, EVT_UNKNOWN, }; #define EVT_LIST_LEN EVT_UNKNOWN struct cmd_param_info { char* name; char* description; char* schema; bool positional; }; struct cmd_info { char* name; char* description; struct cmd_param_info params[5]; }; enum cmd_type ctl_command_parse_name(const char* name); struct cmd_info* ctl_command_by_type(enum cmd_type type); struct cmd_info* ctl_command_by_name(const char* name); enum event_type ctl_event_parse_name(const char* name); struct cmd_info* ctl_event_by_type(enum event_type type); struct cmd_info* ctl_event_by_name(const char* name); extern struct cmd_info ctl_command_list[]; extern struct cmd_info ctl_event_list[]; wayvnc-0.8.0/include/ctl-server.h000066400000000000000000000053061456662133500167270ustar00rootroot00000000000000/* * Copyright (c) 2022 Jim Ramsay * Copyright (c) 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "output.h" #include struct ctl; struct cmd_response; struct ctl_server_client; struct ctl_server_client_info { int id; union { struct sockaddr_storage address_storage; struct sockaddr address; }; const char* username; const char* seat; }; struct ctl_server_output { char name[65]; char description[128]; unsigned height; unsigned width; bool captured; char power[8]; }; struct ctl_server_actions { void* userdata; struct cmd_response* (*on_attach)(struct ctl*, const char* display); struct cmd_response* (*on_detach)(struct ctl*); struct cmd_response* (*on_output_cycle)(struct ctl*, enum output_cycle_direction direction); struct cmd_response* (*on_output_switch)(struct ctl*, const char* output_name); struct cmd_response* (*on_disconnect_client)(struct ctl*, const char* id); struct cmd_response* (*on_wayvnc_exit)(struct ctl*); struct ctl_server_client *(*client_next)(struct ctl*, struct ctl_server_client* prev); void (*client_info)(const struct ctl_server_client*, struct ctl_server_client_info* info); // Return number of elements created // Allocate 'outputs' array or set to NULL if none // Receiver will free(outputs) when done. int (*get_output_list)(struct ctl*, struct ctl_server_output** outputs); }; struct ctl* ctl_server_new(const char* socket_path, const struct ctl_server_actions* actions); void ctl_server_destroy(struct ctl*); void* ctl_server_userdata(struct ctl*); struct cmd_response* cmd_ok(void); struct cmd_response* cmd_failed(const char* fmt, ...); void ctl_server_event_connected(struct ctl*, const struct ctl_server_client_info *info, int new_connection_count); void ctl_server_event_disconnected(struct ctl*, const struct ctl_server_client_info *info, int new_connection_count); void ctl_server_event_capture_changed(struct ctl*, const char* captured_output); void ctl_server_event_detached(struct ctl*); wayvnc-0.8.0/include/data-control.h000066400000000000000000000027311456662133500172270ustar00rootroot00000000000000/* * Copyright (c) 2020 Scott Moreau * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include "wlr-data-control-unstable-v1.h" struct data_control { struct wl_display* wl_display; struct nvnc* server; struct zwlr_data_control_manager_v1* manager; struct zwlr_data_control_device_v1* device; struct zwlr_data_control_source_v1* selection; struct zwlr_data_control_source_v1* primary_selection; struct zwlr_data_control_offer_v1* offer; const char* mime_type; char* cb_data; size_t cb_len; }; void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat); void data_control_destroy(struct data_control* self); void data_control_to_clipboard(struct data_control* self, const char* text, size_t len); wayvnc-0.8.0/include/intset.h000066400000000000000000000022351456662133500161450ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include struct intset { size_t cap; size_t len; int32_t* storage; }; int intset_init(struct intset* self, size_t cap); void intset_destroy(struct intset* self); int intset_set(struct intset* self, int32_t value); void intset_clear(struct intset* self, int32_t value); bool intset_is_set(const struct intset* self, int32_t value); wayvnc-0.8.0/include/json-ipc.h000066400000000000000000000044751456662133500163710ustar00rootroot00000000000000/* * Copyright (c) 2022 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include struct jsonipc_request { const char* method; json_t* params; json_t* id; json_t* json; }; #define IPC_CODE_SUCCESS 0 struct jsonipc_error { int code; json_t* data; }; #define JSONIPC_ERR_INIT {0,NULL} struct jsonipc_response { int code; json_t* data; json_t* id; json_t* json; }; void jsonipc_error_set_new(struct jsonipc_error*, int code, json_t* data); void jsonipc_error_printf(struct jsonipc_error*, int code, const char* fmt, ...); void jsonipc_error_set_from_errno(struct jsonipc_error*, const char* context); void jsonipc_error_cleanup(struct jsonipc_error*); struct jsonipc_request* jsonipc_request_parse_new(json_t* root, struct jsonipc_error* err); struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params); struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params); struct jsonipc_request* jsonipc_event_parse_new(json_t* root, struct jsonipc_error* err); json_t* jsonipc_request_pack(struct jsonipc_request*, json_error_t* err); void jsonipc_request_destroy(struct jsonipc_request*); struct jsonipc_response* jsonipc_response_parse_new(json_t* root, struct jsonipc_error* err); struct jsonipc_response* jsonipc_response_new(int code, json_t* data, json_t* id); struct jsonipc_response* jsonipc_error_response_new(struct jsonipc_error* err, json_t* id); void jsonipc_response_destroy(struct jsonipc_response*); json_t* jsonipc_response_pack(struct jsonipc_response*, json_error_t* err); json_t* jprintf(const char* fmt, ...); json_t* jvprintf(const char* fmt, va_list ap); wayvnc-0.8.0/include/keyboard.h000066400000000000000000000027521456662133500164430ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include "intset.h" struct zwp_virtual_keyboard_v1; struct table_entry; struct keyboard { struct zwp_virtual_keyboard_v1* virtual_keyboard; struct xkb_context* context; struct xkb_keymap* keymap; struct xkb_state* state; size_t lookup_table_size; size_t lookup_table_length; struct table_entry* lookup_table; struct intset key_state; }; int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names); void keyboard_destroy(struct keyboard* self); void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed); void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code, bool is_pressed); wayvnc-0.8.0/include/option-parser.h000066400000000000000000000036771456662133500174540ustar00rootroot00000000000000/* * Copyright (c) 2022 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include struct wv_option { char short_opt; const char* long_opt; const char* schema; const char* help; const char* default_; const char* positional; bool is_subcommand; }; struct wv_option_value { const struct wv_option* option; char value[256]; }; struct option_parser { const char* name; const struct wv_option* options; int n_opts; struct wv_option_value values[128]; int n_values; int position; size_t remaining_argc; const char* const* remaining_argv; }; void option_parser_init(struct option_parser* self, const struct wv_option* options); void option_parser_print_usage(struct option_parser* self, FILE* stream); int option_parser_print_arguments(struct option_parser* self, FILE* stream); void option_parser_print_options(struct option_parser* self, FILE* stream); int option_parser_parse(struct option_parser* self, int argc, const char* const* argv); const char* option_parser_get_value(const struct option_parser* self, const char* name); const char* option_parser_get_value_no_default(const struct option_parser* self, const char* name); void option_parser_print_cmd_summary(const char* summary, FILE* stream); wayvnc-0.8.0/include/output-management.h000066400000000000000000000021111456662133500203020ustar00rootroot00000000000000/* * Copyright (c) 2023 The wayvnc authors * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include struct output; struct zwlr_output_manager_v1; void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager); bool wlr_output_manager_resize_output(struct output* output, uint16_t width, uint16_t height); void wlr_output_manager_destroy(void); wayvnc-0.8.0/include/output.h000066400000000000000000000060271456662133500162020ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include struct zxdg_output_manager_v1; struct zxdg_output_v1; struct zwlr_output_power_manager_v1; struct zwlr_output_power_v1; enum output_power_state { OUTPUT_POWER_UNKNOWN = 0, OUTPUT_POWER_OFF, OUTPUT_POWER_ON, }; const char* output_power_state_name(enum output_power_state state); struct output { struct wl_output* wl_output; struct zxdg_output_v1* xdg_output; struct zwlr_output_power_v1* wlr_output_power; struct wl_list link; uint32_t id; uint32_t width; uint32_t height; uint32_t x; uint32_t y; enum wl_output_transform transform; char make[256]; char model[256]; char name[256]; char description[256]; enum output_power_state power; bool is_dimension_changed; bool is_transform_changed; bool is_headless; void (*on_dimension_change)(struct output*); void (*on_transform_change)(struct output*); void (*on_power_change)(struct output*); void* userdata; }; struct output* output_new(struct wl_output* wl_output, uint32_t id); void output_destroy(struct output* output); void output_setup_wl_managers(struct wl_list* list); int output_set_power_state(struct output* output, enum output_power_state state); void output_list_destroy(struct wl_list* list); struct output* output_find_by_id(struct wl_list* list, uint32_t id); struct output* output_find_by_name(struct wl_list* list, const char* name); struct output* output_first(struct wl_list* list); enum output_cycle_direction { OUTPUT_CYCLE_FORWARD, OUTPUT_CYCLE_REVERSE, }; struct output* output_cycle(const struct wl_list* list, const struct output* current, enum output_cycle_direction); uint32_t output_get_transformed_width(const struct output* self); uint32_t output_get_transformed_height(const struct output* self); void output_transform_coord(const struct output* self, uint32_t src_x, uint32_t src_y, uint32_t* dst_x, uint32_t* dst_y); void output_transform_box_coord(const struct output* self, uint32_t src_x0, uint32_t src_y0, uint32_t src_x1, uint32_t src_y1, uint32_t* dst_x0, uint32_t* dst_y0, uint32_t* dst_x1, uint32_t* dst_y1); wayvnc-0.8.0/include/pam_auth.h000066400000000000000000000015471456662133500164420ustar00rootroot00000000000000/* * Copyright (c) 2020 Nicholas Sica * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include bool pam_auth(const char* username, const char* password); wayvnc-0.8.0/include/pixels.h000066400000000000000000000017731456662133500161510ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include enum wl_shm_format fourcc_to_wl_shm(uint32_t in); uint32_t fourcc_from_wl_shm(enum wl_shm_format in); int pixel_size_from_fourcc(uint32_t fourcc); wayvnc-0.8.0/include/pointer.h000066400000000000000000000024001456662133500163110ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include "wlr-virtual-pointer-unstable-v1.h" #include "output.h" struct pointer { struct nvnc* vnc; struct zwlr_virtual_pointer_v1* pointer; enum nvnc_button_mask current_mask; uint32_t current_x; uint32_t current_y; const struct output* output; }; int pointer_init(struct pointer* self); void pointer_destroy(struct pointer* self); void pointer_set(struct pointer* self, uint32_t x, uint32_t y, enum nvnc_button_mask button_mask); wayvnc-0.8.0/include/screencopy.h000066400000000000000000000041321456662133500170070ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include "wlr-screencopy-unstable-v1.h" #include "smooth.h" #include "buffer.h" struct zwlr_screencopy_manager_v1; struct zwlr_screencopy_frame_v1; struct wl_output; struct wl_buffer; struct wl_shm; struct aml_timer; struct renderer; enum screencopy_status { SCREENCOPY_STOPPED = 0, SCREENCOPY_IN_PROGRESS, SCREENCOPY_FAILED, SCREENCOPY_FATAL, SCREENCOPY_DONE, }; struct screencopy { enum screencopy_status status; struct wv_buffer_pool* pool; struct wv_buffer* front; struct wv_buffer* back; struct zwlr_screencopy_manager_v1* manager; struct zwlr_screencopy_frame_v1* frame; void* userdata; void (*on_done)(struct screencopy*); uint64_t last_time; uint64_t start_time; struct aml_timer* timer; struct smooth delay_smoother; double delay; bool is_immediate_copy; bool overlay_cursor; struct wl_output* wl_output; uint32_t wl_shm_width, wl_shm_height, wl_shm_stride; enum wl_shm_format wl_shm_format; bool have_linux_dmabuf; bool enable_linux_dmabuf; uint32_t dmabuf_width, dmabuf_height; uint32_t fourcc; double rate_limit; }; void screencopy_init(struct screencopy* self); void screencopy_destroy(struct screencopy* self); int screencopy_start(struct screencopy* self); int screencopy_start_immediate(struct screencopy* self); void screencopy_stop(struct screencopy* self); wayvnc-0.8.0/include/seat.h000066400000000000000000000025331456662133500155740ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include struct seat { struct wl_seat* wl_seat; struct wl_list link; uint32_t id; uint32_t capabilities; char name[256]; uint32_t occupancy; }; struct seat* seat_new(struct wl_seat* wl_seat, uint32_t id); void seat_destroy(struct seat* self); void seat_list_destroy(struct wl_list* list); struct seat* seat_find_by_name(struct wl_list* list, const char* name); struct seat* seat_find_by_id(struct wl_list* list, uint32_t id); struct seat* seat_find_unoccupied(struct wl_list* list); struct seat* seat_first(struct wl_list* list); wayvnc-0.8.0/include/shm.h000066400000000000000000000015221456662133500154240ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include int shm_alloc_fd(size_t size); wayvnc-0.8.0/include/smooth.h000066400000000000000000000022721456662133500161510ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include /* * This is an exponential smoothing filter with a time constant. * * The time constant must be set prior to applying the filter. It is, rougly * speaking, the response time of the filter. * * See: https://en.wikipedia.org/wiki/Exponential_smoothing */ struct smooth { double time_constant; uint64_t last_time; double last_result; }; double smooth(struct smooth* self, double input); wayvnc-0.8.0/include/strlcpy.h000066400000000000000000000015541456662133500163420ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include size_t strlcpy(char *dst, const char *src, size_t siz); wayvnc-0.8.0/include/table-printer.h000066400000000000000000000031261456662133500174070ustar00rootroot00000000000000/* * Copyright (c) 2023 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include struct table_printer{ FILE* stream; int max_width; int left_indent; int left_width; int column_offset; }; // Sets default values for every subsequent table_printer_new (Optional: defaults to 80/4/8) void table_printer_set_defaults(int max_width, int left_indent, int column_offset); void table_printer_init(struct table_printer* self, FILE* stream, int left_width); void table_printer_print_line(struct table_printer* self, const char* left_text, const char* right_text); void table_printer_print_fmtline(struct table_printer* self, const char* right_text, const char* left_format, ...); int table_printer_reflow_text(char* dst, int dst_size, const char* src, int width); void table_printer_indent_and_reflow_text(FILE* stream, const char* src, int width, int first_line_indent, int subsequent_indent); wayvnc-0.8.0/include/time-util.h000066400000000000000000000026601456662133500165520ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include static inline uint64_t timespec_to_us(const struct timespec* ts) { return (uint64_t)ts->tv_sec * UINT64_C(1000000) + (uint64_t)ts->tv_nsec / UINT64_C(1000); } static inline uint64_t timespec_to_ms(const struct timespec* ts) { return (uint64_t)ts->tv_sec * UINT64_C(1000) + (uint64_t)ts->tv_nsec / UINT64_C(1000000); } static inline uint64_t gettime_us(void) { struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); return timespec_to_us(&ts); } static inline uint64_t gettime_ms(void) { struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); return timespec_to_ms(&ts); } wayvnc-0.8.0/include/transform-util.h000066400000000000000000000024511456662133500176250ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include void wv_region_transform(struct pixman_region16 *dst, struct pixman_region16 *src, enum wl_output_transform transform, int width, int height); void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst, enum wl_output_transform src, int width, int height); enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr); enum wl_output_transform wv_output_transform_compose( enum wl_output_transform tr_a, enum wl_output_transform tr_b); wayvnc-0.8.0/include/tst.h000066400000000000000000000127251456662133500154560ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include #define XSTR(s) STR(s) #define STR(s) #s #define ASSERT_TRUE(expr) do { \ if (!(expr)) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be true\n"); \ return 1; \ } \ } while(0) #define ASSERT_FALSE(expr) do { \ if (expr) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be false\n"); \ return 1; \ } \ } while(0) #define TST_ASSERT_EQ_(value, expr, type, fmt) do { \ type expr_ = (expr); \ if (expr_ != (value)) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be equal to " XSTR(value) "; was " fmt "\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_INT_EQ(value, expr) TST_ASSERT_EQ_(value, expr, int, "%d") #define ASSERT_UINT_EQ(value, expr) TST_ASSERT_EQ_(value, expr, unsigned int, "%u") #define ASSERT_INT32_EQ(value, expr) TST_ASSERT_EQ_(value, expr, int32_t, "%" PRIi32) #define ASSERT_UINT32_EQ(value, expr) TST_ASSERT_EQ_(value, expr, uint32_t, "%" PRIu32) #define ASSERT_DOUBLE_EQ(value, expr) TST_ASSERT_EQ_(value, expr, double, "%f") #define ASSERT_PTR_EQ(value, expr) TST_ASSERT_EQ_(value, expr, void*, "%p") #define TST_ASSERT_GT_(value, expr, type, fmt) do { \ type expr_ = (expr); \ if (!(expr_ > (value))) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be greater than " XSTR(value) "; was " fmt "\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_INT_GT(value, expr) TST_ASSERT_GT_(value, expr, int, "%d") #define ASSERT_UINT_GT(value, expr) TST_ASSERT_GT_(value, expr, unsigned int, "%u") #define ASSERT_INT32_GT(value, expr) TST_ASSERT_GT_(value, expr, int32_t, "%" PRIi32) #define ASSERT_UINT32_GT(value, expr) TST_ASSERT_GT_(value, expr, uint32_t, "%" PRIu32) #define ASSERT_DOUBLE_GT(value, expr) TST_ASSERT_GT_(value, expr, double, "%f") #define TST_ASSERT_GE_(value, expr, type, fmt) do { \ type expr_ = (expr); \ if (!(expr_ >= (value))) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be greater than or equal to " XSTR(value) "; was " fmt "\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_INT_GE(value, expr) TST_ASSERT_GE_(value, expr, int, "%d") #define ASSERT_UINT_GE(value, expr) TST_ASSERT_GE_(value, expr, unsigned int, "%u") #define ASSERT_INT32_GE(value, expr) TST_ASSERT_GE_(value, expr, int32_t, "%" PRIi32) #define ASSERT_UINT32_GE(value, expr) TST_ASSERT_GE_(value, expr, uint32_t, "%" PRIu32) #define ASSERT_DOUBLE_GE(value, expr) TST_ASSERT_GE_(value, expr, double, "%f") #define TST_ASSERT_LT_(value, expr, type, fmt) do { \ type expr_ = (expr); \ if (!(expr_ < (value))) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be less than " XSTR(value) "; was " fmt "\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_INT_LT(value, expr) TST_ASSERT_LT_(value, expr, int, "%d") #define ASSERT_UINT_LT(value, expr) TST_ASSERT_LT_(value, expr, unsigned int, "%u") #define ASSERT_INT32_LT(value, expr) TST_ASSERT_LT_(value, expr, int32_t, "%" PRIi32) #define ASSERT_UINT32_LT(value, expr) TST_ASSERT_LT_(value, expr, uint32_t, "%" PRIu32) #define ASSERT_DOUBLE_LT(value, expr) TST_ASSERT_LT_(value, expr, double, "%f") #define TST_ASSERT_LE_(value, expr, type, fmt) do { \ type expr_ = (expr); \ if (!(expr_ <= (value))) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be less than or equal to " XSTR(value) "; was " fmt "\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_INT_LE(value, expr) TST_ASSERT_LE_(value, expr, int, "%d") #define ASSERT_UINT_LE(value, expr) TST_ASSERT_LE_(value, expr, unsigned int, "%u") #define ASSERT_INT32_LE(value, expr) TST_ASSERT_LE_(value, expr, int32_t, "%" PRIi32) #define ASSERT_UINT32_LE(value, expr) TST_ASSERT_LE_(value, expr, uint32_t, "%" PRIu32) #define ASSERT_DOUBLE_LE(value, expr) TST_ASSERT_LE_(value, expr, double, "%f") #define ASSERT_STR_EQ(value, expr) do { \ const char* expr_ = (expr); \ if (strcmp(expr_, (value)) != 0) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be " XSTR(value) "; was \"%s\"\n", \ expr_); \ return 1; \ } \ } while(0) #define ASSERT_NEQ(value, expr) do { \ if ((expr) != (value)) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to NOT be " XSTR(value) "\n"); \ return 1; \ } \ } while(0) #define ASSERT_STR_NEQ(value, expr) do { \ if (strcmp((expr), (value)) == 0) { \ fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to NOT be " XSTR(value) "\n"); \ return 1; \ } \ } while(0) #define RUN_TEST(test) do { \ if(!(test())) \ fprintf(stderr, XSTR(test) " passed\n"); \ else \ r = 1; \ } while(0); wayvnc-0.8.0/include/usdt.h000066400000000000000000000020331456662133500156120ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "config.h" #ifdef HAVE_USDT #include #else #define DTRACE_PROBE(...) #define DTRACE_PROBE1(...) #define DTRACE_PROBE2(...) #define DTRACE_PROBE3(...) #define DTRACE_PROBE4(...) #define DTRACE_PROBE5(...) #define DTRACE_PROBE6(...) #endif wayvnc-0.8.0/include/util.h000066400000000000000000000017761456662133500156250ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #define UDIV_UP(a, b) (((a) + (b) - 1) / (b)) extern const char* wayvnc_version; const char* default_ctl_socket_path(); void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by); wayvnc-0.8.0/meson.build000066400000000000000000000105031456662133500152020ustar00rootroot00000000000000project( 'wayvnc', 'c', version: '0.8.0', license: 'ISC', default_options: [ 'c_std=gnu11', 'warning_level=2', ], ) buildtype = get_option('buildtype') host_system = host_machine.system() prefix = get_option('prefix') c_args = [ '-D_GNU_SOURCE', '-DAML_UNSTABLE_API=1', '-Wno-unused-parameter', '-Wno-missing-field-initializers', ] version = '"@0@"'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) if git_commit.returncode() == 0 and git_branch.returncode() == 0 version = '"v@0@-@1@ (@2@)"'.format( meson.project_version(), git_commit.stdout().strip(), git_branch.stdout().strip(), ) endif endif add_project_arguments('-DPROJECT_VERSION=@0@'.format(version), language: 'c') if buildtype != 'debug' and buildtype != 'debugoptimized' c_args += '-DNDEBUG' endif add_project_arguments(c_args, language: 'c') cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) librt = cc.find_library('rt', required: false) libpam = cc.find_library('pam', required: get_option('pam')) pixman = dependency('pixman-1') gbm = dependency('gbm', required: get_option('screencopy-dmabuf')) drm = dependency('libdrm') xkbcommon = dependency('xkbcommon', version: '>=1.0.0') wayland_client = dependency('wayland-client') jansson = dependency('jansson') aml_version = ['>=0.3.0', '<0.4.0'] neatvnc_version = ['>=0.8', '<0.9.0'] neatvnc_project = subproject( 'neatvnc', required: false, version: neatvnc_version, ) aml_project = subproject('aml', required: false, version: aml_version) if aml_project.found() aml = aml_project.get_variable('aml_dep') else aml = dependency('aml', version: aml_version) endif if neatvnc_project.found() neatvnc = neatvnc_project.get_variable('neatvnc_dep') else neatvnc = dependency('neatvnc', version: neatvnc_version) endif inc = include_directories('include') subdir('protocols') sources = [ 'src/main.c', 'src/strlcpy.c', 'src/shm.c', 'src/screencopy.c', 'src/data-control.c', 'src/output.c', 'src/output-management.c', 'src/pointer.c', 'src/keyboard.c', 'src/seat.c', 'src/smooth.c', 'src/cfg.c', 'src/intset.c', 'src/buffer.c', 'src/pixels.c', 'src/transform-util.c', 'src/util.c', 'src/json-ipc.c', 'src/ctl-server.c', 'src/ctl-commands.c', 'src/option-parser.c', 'src/table-printer.c', ] dependencies = [ libm, librt, pixman, aml, gbm, drm, wayland_client, neatvnc, xkbcommon, client_protos, jansson, ] ctlsources = [ 'src/wayvncctl.c', 'src/util.c', 'src/json-ipc.c', 'src/ctl-client.c', 'src/ctl-commands.c', 'src/strlcpy.c', 'src/option-parser.c', 'src/table-printer.c', ] ctldependencies = [ jansson, ] config = configuration_data() config.set('PREFIX', '"' + prefix + '"') if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h') config.set('HAVE_USDT', true) endif if cc.has_function('memfd_create') config.set('HAVE_MEMFD', true) config.set('HAVE_MEMFD_CREATE', true) elif cc.has_function('SYS_memfd_create', prefix : '#include ') config.set('HAVE_MEMFD', true) endif if gbm.found() and not get_option('screencopy-dmabuf').disabled() config.set('ENABLE_SCREENCOPY_DMABUF', true) endif if libpam.found() dependencies += libpam sources += 'src/pam_auth.c' config.set('ENABLE_PAM', true) endif configure_file( output: 'config.h', configuration: config, ) executable( 'wayvnc', sources, dependencies: dependencies, include_directories: inc, install: true, ) executable( 'wayvncctl', ctlsources, dependencies: ctldependencies, include_directories: inc, install: true, ) scdoc = dependency('scdoc', native: true, required: get_option('man-pages')) if scdoc.found() scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) sh = find_program('sh', native: true) mandir = get_option('mandir') manpages = { 'wayvnc.scd': 'wayvnc.1', 'wayvncctl.scd': 'wayvncctl.1', } foreach input, output : manpages custom_target( output, input: input, output: output, command: [ sh, '-c', '@0@ <@INPUT@ >@1@'.format(scdoc_prog.path(), output) ], install: true, install_dir: '@0@/man1'.format(mandir) ) endforeach endif if get_option('tests') subdir('test') endif wayvnc-0.8.0/meson_options.txt000066400000000000000000000007221456662133500164770ustar00rootroot00000000000000option('screencopy-dmabuf', type: 'feature', value: 'auto', description: 'Enable GPU-side screencopy') option('pam', type: 'feature', value: 'auto', description: 'Enable PAM authentication') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('systemtap', type: 'boolean', value: false, description: 'Enable tracing using sdt') option('tests', type: 'boolean', value: true, description: 'Build unit tests') wayvnc-0.8.0/protocols/000077500000000000000000000000001456662133500150655ustar00rootroot00000000000000wayvnc-0.8.0/protocols/ext-transient-seat-v1.xml000066400000000000000000000111001456662133500216630ustar00rootroot00000000000000 Copyright © 2020 - 2023 Andri Yngvason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The transient seat protocol can be used by privileged clients to create independent seats that will be removed from the compositor when the client destroys its transient seat. This protocol is intended for use with virtual input protocols such as "virtual_keyboard_unstable_v1" or "wlr_virtual_pointer_unstable_v1", both of which allow the user to select a seat. The "wl_seat" global created by this protocol does not generate input events on its own, or have any capabilities except those assigned to it by other protocol extensions, such as the ones mentioned above. For example, a remote desktop server can create a seat with virtual inputs for each remote user by following these steps for each new connection: * Create a transient seat * Wait for the transient seat to be created * Locate a "wl_seat" global with a matching name * Create virtual inputs using the resulting "wl_seat" global The transient seat manager creates short-lived seats. Create a new seat that is removed when the client side transient seat object is destroyed. The actual seat may be removed sooner, in which case the transient seat object shall become inert. Destroy the manager. All objects created by the manager will remain valid until they are destroyed themselves. When the transient seat handle is destroyed, the seat itself will also be destroyed. This event advertises the global name for the wl_seat to be used with wl_registry_bind. It is sent exactly once, immediately after the transient seat is created and the new "wl_seat" global is advertised, if and only if the creation of the transient seat was allowed. The event informs the client that the compositor denied its request to create a transient seat. It is sent exactly once, immediately after the transient seat object is created, if and only if the creation of the transient seat was denied. After receiving this event, the client should destroy the object. When the transient seat object is destroyed by the client, the associated seat created by the compositor is also destroyed. wayvnc-0.8.0/protocols/linux-dmabuf-unstable-v1.xml000066400000000000000000000427261456662133500223540ustar00rootroot00000000000000 Copyright © 2014, 2015 Collabora, Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Following the interfaces from: https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt and the Linux DRM sub-system's AddFb2 ioctl. This interface offers ways to create generic dmabuf-based wl_buffers. Immediately after a client binds to this interface, the set of supported formats and format modifiers is sent with 'format' and 'modifier' events. The following are required from clients: - Clients must ensure that either all data in the dma-buf is coherent for all subsequent read access or that coherency is correctly handled by the underlying kernel-side dma-buf implementation. - Don't make any more attachments after sending the buffer to the compositor. Making more attachments later increases the risk of the compositor not being able to use (re-import) an existing dmabuf-based wl_buffer. The underlying graphics stack must ensure the following: - The dmabuf file descriptors relayed to the server will stay valid for the whole lifetime of the wl_buffer. This means the server may at any time use those fds to import the dmabuf into any kernel sub-system that might accept it. To create a wl_buffer from one or more dmabufs, a client creates a zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params request. All planes required by the intended format are added with the 'add' request. Finally, a 'create' or 'create_immed' request is issued, which has the following outcome depending on the import success. The 'create' request, - on success, triggers a 'created' event which provides the final wl_buffer to the client. - on failure, triggers a 'failed' event to convey that the server cannot use the dmabufs received from the client. For the 'create_immed' request, - on success, the server immediately imports the added dmabufs to create a wl_buffer. No event is sent from the server in this case. - on failure, the server can choose to either: - terminate the client by raising a fatal error. - mark the wl_buffer as failed, and send a 'failed' event to the client. If the client uses a failed wl_buffer as an argument to any request, the behaviour is compositor implementation-defined. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. Objects created through this interface, especially wl_buffers, will remain valid. This temporary object is used to collect multiple dmabuf handles into a single batch to create a wl_buffer. It can only be used once and should be destroyed after a 'created' or 'failed' event has been received. This event advertises one buffer format that the server supports. All the supported formats are advertised once when the client binds to this interface. A roundtrip after binding guarantees that the client has received all supported formats. For the definition of the format codes, see the zwp_linux_buffer_params_v1::create request. Warning: the 'format' event is likely to be deprecated and replaced with the 'modifier' event introduced in zwp_linux_dmabuf_v1 version 3, described below. Please refrain from using the information received from this event. This event advertises the formats that the server supports, along with the modifiers supported for each format. All the supported modifiers for all the supported formats are advertised once when the client binds to this interface. A roundtrip after binding guarantees that the client has received all supported format-modifier pairs. For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi == 0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event. It indicates that the server can support the format with an implicit modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it is as if no explicit modifier is specified. The effective modifier will be derived from the dmabuf. For the definition of the format and modifier codes, see the zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add requests. This temporary object is a collection of dmabufs and other parameters that together form a single logical buffer. The temporary object may eventually create one wl_buffer unless cancelled by destroying it before requesting 'create'. Single-planar formats only require one dmabuf, however multi-planar formats may require more than one dmabuf. For all formats, an 'add' request must be called once per plane (even if the underlying dmabuf fd is identical). You must use consecutive plane indices ('plane_idx' argument for 'add') from zero to the number of planes used by the drm_fourcc format code. All planes required by the format must be given exactly once, but can be given in any order. Each plane index can be set only once. Cleans up the temporary data sent to the server for dmabuf-based wl_buffer creation. This request adds one dmabuf to the set in this zwp_linux_buffer_params_v1. The 64-bit unsigned value combined from modifier_hi and modifier_lo is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the fb modifier, which is defined in drm_mode.h of Linux UAPI. This is an opaque token. Drivers use this token to express tiling, compression, etc. driver-specific modifications to the base format defined by the DRM fourcc code. Warning: It should be an error if the format/modifier pair was not advertised with the modifier event. This is not enforced yet because some implementations always accept DRM_FORMAT_MOD_INVALID. Also version 2 of this protocol does not have the modifier event. This request raises the PLANE_IDX error if plane_idx is too large. The error PLANE_SET is raised if attempting to set a plane that was already set. This asks for creation of a wl_buffer from the added dmabuf buffers. The wl_buffer is not created immediately but returned via the 'created' event if the dmabuf sharing succeeds. The sharing may fail at runtime for reasons a client cannot predict, in which case the 'failed' event is triggered. The 'format' argument is a DRM_FORMAT code, as defined by the libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the authoritative source on how the format codes should work. The 'flags' is a bitfield of the flags defined in enum "flags". 'y_invert' means the that the image needs to be y-flipped. Flag 'interlaced' means that the frame in the buffer is not progressive as usual, but interlaced. An interlaced buffer as supported here must always contain both top and bottom fields. The top field always begins on the first pixel row. The temporal ordering between the two fields is top field first, unless 'bottom_first' is specified. It is undefined whether 'bottom_first' is ignored if 'interlaced' is not set. This protocol does not convey any information about field rate, duration, or timing, other than the relative ordering between the two fields in one buffer. A compositor may have to estimate the intended field rate from the incoming buffer rate. It is undefined whether the time of receiving wl_surface.commit with a new buffer attached, applying the wl_surface state, wl_surface.frame callback trigger, presentation, or any other point in the compositor cycle is used to measure the frame or field times. There is no support for detecting missed or late frames/fields/buffers either, and there is no support whatsoever for cooperating with interlaced compositor output. The composited image quality resulting from the use of interlaced buffers is explicitly undefined. A compositor may use elaborate hardware features or software to deinterlace and create progressive output frames from a sequence of interlaced input buffers, or it may produce substandard image quality. However, compositors that cannot guarantee reasonable image quality in all cases are recommended to just reject all interlaced buffers. Any argument errors, including non-positive width or height, mismatch between the number of planes and the format, bad format, bad offset or stride, may be indicated by fatal protocol errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS. Dmabuf import errors in the server that are not obvious client bugs are returned via the 'failed' event as non-fatal. This allows attempting dmabuf sharing and falling back in the client if it fails. This request can be sent only once in the object's lifetime, after which the only legal request is destroy. This object should be destroyed after issuing a 'create' request. Attempting to use this object after issuing 'create' raises ALREADY_USED protocol error. It is not mandatory to issue 'create'. If a client wants to cancel the buffer creation, it can just destroy this object. This event indicates that the attempted buffer creation was successful. It provides the new wl_buffer referencing the dmabuf(s). Upon receiving this event, the client should destroy the zlinux_dmabuf_params object. This event indicates that the attempted buffer creation has failed. It usually means that one of the dmabuf constraints has not been fulfilled. Upon receiving this event, the client should destroy the zlinux_buffer_params object. This asks for immediate creation of a wl_buffer by importing the added dmabufs. In case of import success, no event is sent from the server, and the wl_buffer is ready to be used by the client. Upon import failure, either of the following may happen, as seen fit by the implementation: - the client is terminated with one of the following fatal protocol errors: - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, in case of argument errors such as mismatch between the number of planes and the format, bad format, non-positive width or height, or bad offset or stride. - INVALID_WL_BUFFER, in case the cause for failure is unknown or plaform specific. - the server creates an invalid wl_buffer, marks it as failed and sends a 'failed' event to the client. The result of using this invalid wl_buffer as an argument in any request by the client is defined by the compositor implementation. This takes the same arguments as a 'create' request, and obeys the same restrictions. wayvnc-0.8.0/protocols/meson.build000066400000000000000000000022741456662133500172340ustar00rootroot00000000000000wayland_scanner = find_program('wayland-scanner') wayland_client = dependency('wayland-client') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', 'wlr-virtual-pointer-unstable-v1.xml', 'virtual-keyboard-unstable-v1.xml', 'xdg-output-unstable-v1.xml', 'linux-dmabuf-unstable-v1.xml', 'wlr-data-control-unstable-v1.xml', 'wlr-output-management-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml', 'ext-transient-seat-v1.xml', ] client_protos_src = [] client_protos_headers = [] foreach xml: client_protocols client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [ wayland_client ] ) client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) wayvnc-0.8.0/protocols/virtual-keyboard-unstable-v1.xml000066400000000000000000000114261456662133500232360ustar00rootroot00000000000000 Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2013 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Copyright © 2018 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The virtual keyboard provides an application with requests which emulate the behaviour of a physical keyboard. This interface can be used by clients on its own to provide raw input events, or it can accompany the input method protocol. Provide a file descriptor to the compositor which can be memory-mapped to provide a keyboard mapping description. Format carries a value from the keymap_format enumeration. A key was pressed or released. The time argument is a timestamp with millisecond granularity, with an undefined base. All requests regarding a single object must share the same clock. Keymap must be set before issuing this request. State carries a value from the key_state enumeration. Notifies the compositor that the modifier and/or group state has changed, and it should update state. The client should use wl_keyboard.modifiers event to synchronize its internal state with seat state. Keymap must be set before issuing this request. A virtual keyboard manager allows an application to provide keyboard input events as if they came from a physical keyboard. Creates a new virtual keyboard associated to a seat. If the compositor enables a keyboard to perform arbitrary actions, it should present an error when an untrusted client requests a new keyboard. wayvnc-0.8.0/protocols/wlr-data-control-unstable-v1.xml000066400000000000000000000274161456662133500231510ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Copyright © 2019 Ivan Molodetskikh Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. This protocol allows a privileged client to control data devices. In particular, the client will be able to manage the current selection and take the role of a clipboard manager. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-seat data device controls. Create a new data source. Create a data device that can be used to manage a seat's selection. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This interface allows a client to manage a seat's selection. When the seat is destroyed, this object becomes inert. This request asks the compositor to set the selection to the data from the source on behalf of the client. The given source may not be used in any further set_selection or set_primary_selection requests. Attempting to use a previously used source is a protocol error. To unset the selection, set the source to NULL. Destroys the data device object. The data_offer event introduces a new wlr_data_control_offer object, which will subsequently be used in either the wlr_data_control_device.selection event (for the regular clipboard selections) or the wlr_data_control_device.primary_selection event (for the primary clipboard selections). Immediately following the wlr_data_control_device.data_offer event, the new data_offer object will send out wlr_data_control_offer.offer events to describe the MIME types it offers. The selection event is sent out to notify the client of a new wlr_data_control_offer for the selection for this device. The wlr_data_control_device.data_offer and the wlr_data_control_offer.offer events are sent out immediately before this event to introduce the data offer object. The selection event is sent to a client when a new selection is set. The wlr_data_control_offer is valid until a new wlr_data_control_offer or NULL is received. The client must destroy the previous selection wlr_data_control_offer, if any, upon receiving this event. The first selection event is sent upon binding the wlr_data_control_device object. This data control object is no longer valid and should be destroyed by the client. The primary_selection event is sent out to notify the client of a new wlr_data_control_offer for the primary selection for this device. The wlr_data_control_device.data_offer and the wlr_data_control_offer.offer events are sent out immediately before this event to introduce the data offer object. The primary_selection event is sent to a client when a new primary selection is set. The wlr_data_control_offer is valid until a new wlr_data_control_offer or NULL is received. The client must destroy the previous primary selection wlr_data_control_offer, if any, upon receiving this event. If the compositor supports primary selection, the first primary_selection event is sent upon binding the wlr_data_control_device object. This request asks the compositor to set the primary selection to the data from the source on behalf of the client. The given source may not be used in any further set_selection or set_primary_selection requests. Attempting to use a previously used source is a protocol error. To unset the primary selection, set the source to NULL. The compositor will ignore this request if it does not support primary selection. The wlr_data_control_source object is the source side of a wlr_data_control_offer. It is created by the source client in a data transfer and provides a way to describe the offered data and a way to respond to requests to transfer the data. This request adds a MIME type to the set of MIME types advertised to targets. Can be called several times to offer multiple types. Calling this after wlr_data_control_device.set_selection is a protocol error. Destroys the data source object. Request for data from the client. Send the data as the specified MIME type over the passed file descriptor, then close it. This data source is no longer valid. The data source has been replaced by another data source. The client should clean up and destroy this data source. A wlr_data_control_offer represents a piece of data offered for transfer by another client (the source client). The offer describes the different MIME types that the data can be converted to and provides the mechanism for transferring the data directly from the source client. To transfer the offered data, the client issues this request and indicates the MIME type it wants to receive. The transfer happens through the passed file descriptor (typically created with the pipe system call). The source client writes the data in the MIME type representation requested and then closes the file descriptor. The receiving client reads from the read end of the pipe until EOF and then closes its end, at which point the transfer is complete. This request may happen multiple times for different MIME types. Destroys the data offer object. Sent immediately after creating the wlr_data_control_offer object. One event per offered MIME type. wayvnc-0.8.0/protocols/wlr-export-dmabuf-unstable-v1.xml000066400000000000000000000217351456662133500233350ustar00rootroot00000000000000 Copyright © 2018 Rostislav Pehlivanov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. An interface to capture surfaces in an efficient way by exporting DMA-BUFs. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager with which to start capturing from sources. Capture the next frame of a an entire output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single DMA-BUF frame. If the capture is successful, the compositor will first send a "frame" event, followed by one or several "object". When the frame is available for readout, the "ready" event is sent. If the capture failed, the "cancel" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "cancel" event is received, the client should destroy the frame. Once an "object" event is received, the client is responsible for closing the associated file descriptor. All frames are read-only and may not be written into or altered. Special flags that should be respected by the client. Main event supplying the client with information about the frame. If the capture didn't fail, this event is always emitted first before any other events. This event is followed by a number of "object" as specified by the "num_objects" argument. Event which serves to supply the client with the file descriptors containing the data for each object. After receiving this event, the client must always close the file descriptor as soon as they're done with it and even if the frame fails. This event is sent as soon as the frame is presented, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy this object. Indicates reason for cancelling the frame. If the capture failed or if the frame is no longer valid after the "frame" event has been emitted, this event will be used to inform the client to scrap the frame. If the failure is temporary, the client may capture again the same source. If the failure is permanent, any further attempts to capture the same source will fail again. After receiving this event, the client should destroy this object. Unreferences the frame. This request must be called as soon as its no longer used. It can be called at any time by the client. The client will still have to close any FDs it has been given. wayvnc-0.8.0/protocols/wlr-output-management-unstable-v1.xml000066400000000000000000000622661456662133500242360ustar00rootroot00000000000000 Copyright © 2019 Purism SPC Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. This protocol exposes interfaces to obtain and modify output device configuration. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows reading and writing the current output device configuration. Output devices that display pixels (e.g. a physical monitor or a virtual output in a window) are represented as heads. Heads cannot be created nor destroyed by the client, but they can be enabled or disabled and their properties can be changed. Each head may have one or more available modes. Whenever a head appears (e.g. a monitor is plugged in), it will be advertised via the head event. Immediately after the output manager is bound, all current heads are advertised. Whenever a head's properties change, the relevant wlr_output_head events will be sent. Not all head properties will be sent: only properties that have changed need to. Whenever a head disappears (e.g. a monitor is unplugged), a wlr_output_head.finished event will be sent. After one or more heads appear, change or disappear, the done event will be sent. It carries a serial which can be used in a create_configuration request to update heads properties. The information obtained from this protocol should only be used for output configuration purposes. This protocol is not designed to be a generic output property advertisement protocol for regular clients. Instead, protocols such as xdg-output should be used. This event introduces a new head. This happens whenever a new head appears (e.g. a monitor is plugged in) or after the output manager is bound. This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child head and mode objects as well. In other words, this event is sent whenever a head or mode is created or destroyed and whenever one of their properties has been changed. Not all state is re-sent each time the current configuration changes: only the actual changes are sent. This allows changes to the output configuration to be seen as atomic, even if they happen via multiple events. A serial is sent to be used in a future create_configuration request. Create a new output configuration object. This allows to update head properties. Indicates the client no longer wishes to receive events for output configuration changes. However the compositor may emit further events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending manager events. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. A head is an output device. The difference between a wl_output object and a head is that heads are advertised even if they are turned off. A head object only advertises properties and cannot be used directly to change them. A head has some read-only properties: modes, name, description and physical_size. These cannot be changed by clients. Other properties can be updated via a wlr_output_configuration object. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the head name. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wlr_output_head objects, but if a wlr_output_head object is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. If the compositor implements the xdg-output protocol and this head is enabled, the xdg_output.name event must report the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over the lifetime of the wlr_output_head object. This event describes a human-readable description of the head. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. However, do not assume that the name is a reflection of the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. If the compositor implements xdg-output and this head is enabled, the xdg_output.description must report the same description. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not change over the lifetime of the wlr_output_head object. This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). This event introduces a mode for this head. It is sent once per supported mode. This event describes whether the head is enabled. A disabled head is not mapped to a region of the global compositor space. When a head is disabled, some properties (current_mode, position, transform and scale) are irrelevant. This event describes the mode currently in use for this head. It is only sent if the output is enabled. This events describes the position of the head in the global compositor space. It is only sent if the output is enabled. This event describes the transformation currently applied to the head. It is only sent if the output is enabled. This events describes the scale of the head in the global compositor space. It is only sent if the output is enabled. This event indicates that the head is no longer available. The head object becomes inert. Clients should send a destroy request and release any resources associated with it. This event describes the manufacturer of the head. This must report the same make as the wl_output interface does in its geometry event. Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the make of the head or the definition of a make is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. This event describes the model of the head. This must report the same model as the wl_output interface does in its geometry event. Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the model of the head or the definition of a model is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. This event describes the serial number of the head. Together with the make and model events the purpose is to allow clients to recognize heads from previous sessions and for example load head- specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the serial number of the head or the definition of a serial number is not sensible in the current setup. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. This request indicates that the client will no longer use this head object. This event describes whether adaptive sync is currently enabled for the head or not. Adaptive sync is also known as Variable Refresh Rate or VRR. This object describes an output mode. Some heads don't support output modes, in which case modes won't be advertised. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the mode size. The size is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled or transformed. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. This event indicates that the mode is no longer available. The mode object becomes inert. Clients should send a destroy request and release any resources associated with it. This request indicates that the client will no longer use this mode object. This object is used by the client to describe a full output configuration. First, the client needs to setup the output configuration. Each head can be either enabled (and configured) or disabled. It is a protocol error to send two enable_head or disable_head requests with the same head. It is a protocol error to omit a head in a configuration. Then, the client can apply or test the configuration. The compositor will then reply with a succeeded, failed or cancelled event. Finally the client should destroy the configuration object. Enable a head. This request creates a head configuration object that can be used to change the head's properties. Disable a head. Apply the new output configuration. In case the configuration is successfully applied, there is no guarantee that the new output state matches completely the requested configuration. For instance, a compositor might round the scale if it doesn't support fractional scaling. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Test the new output configuration. The configuration won't be applied, but will only be validated. Even if the compositor succeeds to test a configuration, applying it may fail. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Sent after the compositor has successfully applied the changes or tested them. Upon receiving this event, the client should destroy this object. If the current configuration has changed, events to describe the changes will be sent followed by a wlr_output_manager.done event. Sent if the compositor rejects the changes or failed to apply them. The compositor should revert any changes made by the apply request that triggered this event. Upon receiving this event, the client should destroy this object. Sent if the compositor cancels the configuration because the state of an output changed and the client has outdated information (e.g. after an output has been hotplugged). The client can create a new configuration with a newer serial and try again. Upon receiving this event, the client should destroy this object. Using this request a client can tell the compositor that it is not going to use the configuration object anymore. Any changes to the outputs that have not been applied will be discarded. This request also destroys wlr_output_configuration_head objects created via this object. This object is used by the client to update a single head's configuration. It is a protocol error to set the same property twice. This request sets the head's mode. This request assigns a custom mode to the head. The size is given in physical hardware units of the output device. If set to zero, the refresh rate is unspecified. It is a protocol error to set both a mode and a custom mode. This request sets the head's position in the global compositor space. This request sets the head's transform. This request sets the head's scale. This request enables/disables adaptive sync. Adaptive sync is also known as Variable Refresh Rate or VRR. wayvnc-0.8.0/protocols/wlr-output-power-management-unstable-v1.xml000066400000000000000000000127331456662133500253620ustar00rootroot00000000000000 Copyright © 2019 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-output power management mode controls. Create a output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. wayvnc-0.8.0/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000236661456662133500227370ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. wayvnc-0.8.0/protocols/wlr-virtual-pointer-unstable-v1.xml000066400000000000000000000153571456662133500237270ustar00rootroot00000000000000 Copyright © 2019 Josef Gajdusek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to emulate a physical pointer device. The requests are mostly mirror opposites of those specified in wl_pointer. The pointer has moved by a relative amount to the previous request. Values are in the global compositor space. The pointer has moved in an absolute coordinate frame. Value of x can range from 0 to x_extent, value of y can range from 0 to y_extent. A button was pressed or released. Scroll and other axis requests. Indicates the set of events that logically belong together. Source information for scroll and other axis. Stop notification for scroll and other axes. Discrete step information for scroll and other axes. This event allows the client to extend data normally sent using the axis event with discrete value. This object allows clients to create individual virtual pointer objects. Creates a new virtual pointer. The optional seat is a suggestion to the compositor. Creates a new virtual pointer. The seat and the output arguments are optional. If the seat argument is set, the compositor should assign the input device to the requested seat. If the output argument is set, the compositor should map the input device to the requested output. wayvnc-0.8.0/protocols/xdg-output-unstable-v1.xml000066400000000000000000000225121456662133500220700ustar00rootroot00000000000000 Copyright © 2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol aims at describing outputs in a way which is more in line with the concept of an output on desktop oriented systems. Some information are more specific to the concept of an output for a desktop oriented system and may not make sense in other applications, such as IVI systems for example. Typically, the global compositor space on a desktop system is made of a contiguous or overlapping set of rectangular regions. Some of the information provided in this protocol might be identical to their counterparts already available from wl_output, in which case the information provided by this protocol should be preferred to their equivalent in wl_output. The goal is to move the desktop specific concepts (such as output location within the global compositor space, the connector name and types, etc.) out of the core wl_output protocol. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. A global factory interface for xdg_output objects. Using this request a client can tell the server that it is not going to use the xdg_output_manager object anymore. Any objects already created through this instance are not affected. This creates a new xdg_output object for the given wl_output. An xdg_output describes part of the compositor geometry. This typically corresponds to a monitor that displays part of the compositor space. For objects version 3 onwards, after all xdg_output properties have been sent (when the object is created and when properties are updated), a wl_output.done event is sent. This allows changes to the output properties to be seen as atomic, even if they happen via multiple events. Using this request a client can tell the server that it is not going to use the xdg_output object anymore. The position event describes the location of the wl_output within the global compositor space. The logical_position event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the location of the output changes within the global compositor space. The logical_size event describes the size of the output in the global compositor space. For example, a surface without any buffer scale, transformation nor rotation set, with the size matching the logical_size will have the same size as the corresponding output when displayed. Most regular Wayland clients should not pay attention to the logical size and would rather rely on xdg_shell interfaces. Some clients such as Xwayland, however, need this to configure their surfaces in the global compositor space as the compositor may apply a different scale from what is advertised by the output scaling property (to achieve fractional scaling, for example). For example, for a wl_output mode 3840×2160 and a scale factor 2: - A compositor not scaling the surface buffers will advertise a logical size of 3840×2160, - A compositor automatically scaling the surface buffers will advertise a logical size of 1920×1080, - A compositor using a fractional scale of 1.5 will advertise a logical size to 2560×1620. For example, for a wl_output mode 1920×1080 and a 90 degree rotation, the compositor will advertise a logical size of 1080x1920. The logical_size event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the logical size of the output changes, either as a result of a change in the applied scale or because of a change in the corresponding output mode(see wl_output.mode) or transform (see wl_output.transform). This event is sent after all other properties of an xdg_output have been sent. This allows changes to the xdg_output properties to be seen as atomic, even if they happen via multiple events. For objects version 3 onwards, this event is deprecated. Compositors are not required to send it anymore and must send wl_output.done instead. Many compositors will assign names to their outputs, show them to the user, allow them to be configured by name, etc. The client may wish to know this name as well to offer the user similar behaviors. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wl_output globals, but if a wl_output global is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. The name event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output). This event is only sent once per xdg_output, and the name does not change over the lifetime of the wl_output global. Many compositors can produce human-readable descriptions of their outputs. The client may wish to know this description as well, to communicate the user for various purposes. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. The description event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the description changes. The description is optional, and may not be sent at all. For objects of version 2 and lower, this event is only sent once per xdg_output, and the description does not change over the lifetime of the wl_output global. wayvnc-0.8.0/src/000077500000000000000000000000001456662133500136305ustar00rootroot00000000000000wayvnc-0.8.0/src/buffer.c000066400000000000000000000205451456662133500152530ustar00rootroot00000000000000/* * Copyright (c) 2020 - 2021 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "linux-dmabuf-unstable-v1.h" #include "shm.h" #include "sys/queue.h" #include "buffer.h" #include "pixels.h" #include "config.h" #ifdef ENABLE_SCREENCOPY_DMABUF #include #endif extern struct wl_shm* wl_shm; extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf; extern struct gbm_device* gbm_device; enum wv_buffer_type wv_buffer_get_available_types(void) { enum wv_buffer_type type = 0; if (wl_shm) type |= WV_BUFFER_SHM; #ifdef ENABLE_SCREENCOPY_DMABUF if (zwp_linux_dmabuf && gbm_device) type |= WV_BUFFER_DMABUF; #endif return type; } struct wv_buffer* wv_buffer_create_shm(int width, int height, int stride, uint32_t fourcc) { assert(wl_shm); enum wl_shm_format wl_fmt = fourcc_to_wl_shm(fourcc); struct wv_buffer* self = calloc(1, sizeof(*self)); if (!self) return NULL; self->type = WV_BUFFER_SHM; self->width = width; self->height = height; self->stride = stride; self->format = fourcc; self->size = height * stride; int fd = shm_alloc_fd(self->size); if (fd < 0) goto failure; self->pixels = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (!self->pixels) goto mmap_failure; struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, fd, self->size); if (!pool) goto pool_failure; self->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt); wl_shm_pool_destroy(pool); if (!self->wl_buffer) goto shm_failure; int bpp = pixel_size_from_fourcc(fourcc); assert(bpp > 0); self->nvnc_fb = nvnc_fb_from_buffer(self->pixels, width, height, fourcc, stride / bpp); if (!self->nvnc_fb) { goto nvnc_fb_failure; } nvnc_set_userdata(self->nvnc_fb, self, NULL); pixman_region_init(&self->damage); close(fd); return self; nvnc_fb_failure: wl_buffer_destroy(self->wl_buffer); shm_failure: pool_failure: mmap_failure: close(fd); failure: free(self); return NULL; } #ifdef ENABLE_SCREENCOPY_DMABUF static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height, uint32_t fourcc) { assert(zwp_linux_dmabuf); assert(gbm_device); struct wv_buffer* self = calloc(1, sizeof(*self)); if (!self) return NULL; self->type = WV_BUFFER_DMABUF; self->width = width; self->height = height; self->format = fourcc; self->bo = gbm_bo_create(gbm_device, width, height, fourcc, GBM_BO_USE_RENDERING); if (!self->bo) goto bo_failure; struct zwp_linux_buffer_params_v1* params; params = zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf); if (!params) goto params_failure; uint32_t offset = gbm_bo_get_offset(self->bo, 0); uint32_t stride = gbm_bo_get_stride(self->bo); uint64_t mod = gbm_bo_get_modifier(self->bo); int fd = gbm_bo_get_fd(self->bo); if (fd < 0) goto fd_failure; zwp_linux_buffer_params_v1_add(params, fd, 0, offset, stride, mod >> 32, mod & 0xffffffff); self->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params, width, height, fourcc, /* flags */ 0); zwp_linux_buffer_params_v1_destroy(params); close(fd); if (!self->wl_buffer) goto buffer_failure; self->nvnc_fb = nvnc_fb_from_gbm_bo(self->bo); if (!self->nvnc_fb) { goto nvnc_fb_failure; } nvnc_set_userdata(self->nvnc_fb, self, NULL); return self; nvnc_fb_failure: wl_buffer_destroy(self->wl_buffer); buffer_failure: fd_failure: zwp_linux_buffer_params_v1_destroy(params); params_failure: gbm_bo_destroy(self->bo); bo_failure: free(self); return NULL; } #endif struct wv_buffer* wv_buffer_create(enum wv_buffer_type type, int width, int height, int stride, uint32_t fourcc) { switch (type) { case WV_BUFFER_SHM: return wv_buffer_create_shm(width, height, stride, fourcc); #ifdef ENABLE_SCREENCOPY_DMABUF case WV_BUFFER_DMABUF: return wv_buffer_create_dmabuf(width, height, fourcc); #endif case WV_BUFFER_UNSPEC:; } abort(); return NULL; } static void wv_buffer_destroy_shm(struct wv_buffer* self) { nvnc_fb_unref(self->nvnc_fb); wl_buffer_destroy(self->wl_buffer); munmap(self->pixels, self->size); free(self); } #ifdef ENABLE_SCREENCOPY_DMABUF static void wv_buffer_destroy_dmabuf(struct wv_buffer* self) { nvnc_fb_unref(self->nvnc_fb); wl_buffer_destroy(self->wl_buffer); gbm_bo_destroy(self->bo); free(self); } #endif void wv_buffer_destroy(struct wv_buffer* self) { pixman_region_fini(&self->damage); switch (self->type) { case WV_BUFFER_SHM: wv_buffer_destroy_shm(self); return; #ifdef ENABLE_SCREENCOPY_DMABUF case WV_BUFFER_DMABUF: wv_buffer_destroy_dmabuf(self); return; #endif case WV_BUFFER_UNSPEC:; } abort(); } void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width, int height) { pixman_region_union_rect(&self->damage, &self->damage, x, y, width, height); } void wv_buffer_damage_whole(struct wv_buffer* self) { wv_buffer_damage_rect(self, 0, 0, self->width, self->height); } void wv_buffer_damage_clear(struct wv_buffer* self) { pixman_region_clear(&self->damage); } struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type type, int width, int height, int stride, uint32_t format) { struct wv_buffer_pool* self = calloc(1, sizeof(*self)); if (!self) return NULL; TAILQ_INIT(&self->queue); self->type = type; self->width = width; self->height = height; self->stride = stride; self->format = format; return self; } static void wv_buffer_pool_clear(struct wv_buffer_pool* pool) { while (!TAILQ_EMPTY(&pool->queue)) { struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue); TAILQ_REMOVE(&pool->queue, buffer, link); wv_buffer_destroy(buffer); } } void wv_buffer_pool_destroy(struct wv_buffer_pool* pool) { wv_buffer_pool_clear(pool); free(pool); } void wv_buffer_pool_resize(struct wv_buffer_pool* pool, enum wv_buffer_type type, int width, int height, int stride, uint32_t format) { if (pool->type != type || pool->width != width || pool->height != height || pool->stride != stride || pool->format != format) { wv_buffer_pool_clear(pool); } pool->type = type; pool->width = width; pool->height = height; pool->stride = stride; pool->format = format; } static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool, struct wv_buffer* buffer) { if (pool->type != buffer->type) return false; switch (pool->type) { case WV_BUFFER_SHM: if (pool->stride != buffer->stride) { return false; } #ifdef ENABLE_SCREENCOPY_DMABUF /* fall-through */ case WV_BUFFER_DMABUF: #endif if (pool->width != buffer->width || pool->height != buffer->height || pool->format != buffer->format) return false; return true; case WV_BUFFER_UNSPEC: abort(); } return false; } void wv_buffer_pool__on_release(struct nvnc_fb* fb, void* context) { struct wv_buffer* buffer = nvnc_get_userdata(fb); struct wv_buffer_pool* pool = context; wv_buffer_pool_release(pool, buffer); } struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool) { struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue); if (buffer) { assert(wv_buffer_pool_match_buffer(pool, buffer)); TAILQ_REMOVE(&pool->queue, buffer, link); return buffer; } buffer = wv_buffer_create(pool->type, pool->width, pool->height, pool->stride, pool->format); if (buffer) nvnc_fb_set_release_fn(buffer->nvnc_fb, wv_buffer_pool__on_release, pool); return buffer; } void wv_buffer_pool_release(struct wv_buffer_pool* pool, struct wv_buffer* buffer) { wv_buffer_damage_clear(buffer); if (wv_buffer_pool_match_buffer(pool, buffer)) { TAILQ_INSERT_TAIL(&pool->queue, buffer, link); } else { wv_buffer_destroy(buffer); } } wayvnc-0.8.0/src/cfg.c000066400000000000000000000067661456662133500145520ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "cfg.h" #define XSTR(s) STR(s) #define STR(s) #s static char* cfg__get_default_path(void) { static char result[256]; char* xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home) { snprintf(result, sizeof(result), "%s/wayvnc/config", xdg_config_home); result[sizeof(result) - 1] = '\0'; return result; } char* home = getenv("HOME"); if (!home) return NULL; snprintf(result, sizeof(result), "%s/.config/wayvnc/config", home); result[sizeof(result) - 1] = '\0'; return result; } static char* cfg__trim_left(char* str) { while (isspace(*str)) ++str; return str; } static char* cfg__trim_right(char* str) { char* end = str + strlen(str) - 1; while (str < end && isspace(*end)) *end-- = '\0'; return str; } static inline char* cfg__trim(char* str) { return cfg__trim_right(cfg__trim_left(str)); } static int cfg__load_key_value(struct cfg* self, const char* key, const char* value) { #define LOAD_bool(v) (strcmp(v, "false") != 0) #define LOAD_string(v) strdup(v) #define LOAD_uint(v) strtoul(v, NULL, 0) #define X(type, name) \ if (strcmp(XSTR(name), key) == 0) { \ self->name = LOAD_ ## type(value); \ return 0; \ } X_CFG_LIST #undef X #undef LOAD_uint #undef LOAD_string #undef LOAD_bool return -1; } static int cfg__load_line(struct cfg* self, char* line) { line = cfg__trim(line); if (line[0] == '\0' || line[0] == '#') return 0; char* delim = strchr(line, '='); if (!delim) return -1; *delim = '\0'; char* key = cfg__trim_right(line); char* value = cfg__trim_left(delim + 1); return cfg__load_key_value(self, key, value); } static char* cfg__dirname(const char* path) { char buffer[PATH_MAX]; return strdup(dirname(realpath(path, buffer))); } int cfg_load(struct cfg* self, const char* requested_path) { const char* path = requested_path ? requested_path : cfg__get_default_path(); if (!path) return -1; FILE* stream = fopen(path, "r"); if (!stream) return -1; self->directory = cfg__dirname(path); char* line = NULL; size_t len = 0; int lineno = 0; while (getline(&line, &len, stream) > 0) { ++lineno; if (cfg__load_line(self, line) < 0) goto failure; } free(line); fclose(stream); return 0; failure: cfg_destroy(self); free(line); free(self->directory); fclose(stream); return lineno; } void cfg_destroy(struct cfg* self) { #define DESTROY_bool(...) #define DESTROY_uint(...) #define DESTROY_string(p) free(p) #define X(type, name) DESTROY_ ## type(self->name); X_CFG_LIST #undef X #undef DESTROY_string #undef DESTROY_uint #undef DESTROY_bool free(self->directory); } wayvnc-0.8.0/src/ctl-client.c000066400000000000000000000534031456662133500160370ustar00rootroot00000000000000/* * Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "json-ipc.h" #include "ctl-client.h" #include "ctl-commands.h" #include "strlcpy.h" #include "util.h" #include "option-parser.h" #include "table-printer.h" #define LOG(level, fmt, ...) \ fprintf(stderr, level ": %s: %d: " fmt "\n", __FILE__, __LINE__, \ ##__VA_ARGS__) #define ERROR(fmt, ...) \ LOG("ERROR", fmt, ##__VA_ARGS__) static bool do_debug = false; #define DEBUG(fmt, ...) \ if (do_debug) \ LOG("DEBUG", fmt, ##__VA_ARGS__) static struct cmd_info internal_events[] = { { .name = "wayvnc-startup", .description = "Sent by wayvncctl when a successful wayvnc control connection is established and event registration has succeeded, both upon initial startup and on subsequent registrations with --reconnect.", .params = {{}}, }, { .name = "wayvnc-shutdown", .description = "Sent by wayvncctl when the wayvnc control connection is dropped, usually due to wayvnc exiting.", .params = {{}}, }, }; #define EVT_LOCAL_STARTUP internal_events[0].name #define EVT_LOCAL_SHUTDOWN internal_events[1].name #define INTERNAL_EVT_LEN 2 struct ctl_client { void* userdata; struct sockaddr_un addr; unsigned flags; char read_buffer[1024]; size_t read_len; bool wait_for_events; int fd; }; void ctl_client_debug_log(bool enable) { do_debug = enable; } struct ctl_client* ctl_client_new(const char* socket_path, void* userdata) { if (!socket_path) socket_path = default_ctl_socket_path(); struct ctl_client* new = calloc(1, sizeof(*new)); new->userdata = userdata; new->fd = -1; if (strlen(socket_path) >= sizeof(new->addr.sun_path)) { errno = ENAMETOOLONG; ERROR("Failed to create unix socket: %m"); goto socket_failure; } strcpy(new->addr.sun_path, socket_path); new->addr.sun_family = AF_UNIX; return new; socket_failure: free(new); return NULL; } static int wait_for_socket(const char* socket_path, int timeout) { bool needs_log = true; struct stat sb; while (stat(socket_path, &sb) != 0) { if (timeout == 0) { ERROR("Failed to find socket path \"%s\": %m", socket_path); return 1; } if (needs_log) { needs_log = false; DEBUG("Waiting for socket path \"%s\" to appear", socket_path); } if (usleep(50000) == -1) { ERROR("Failed to wait for socket path: %m"); return -1; } } if (S_ISSOCK(sb.st_mode)) { DEBUG("Found socket \"%s\"", socket_path); } else { ERROR("Path \"%s\" exists but is not a socket (0x%x)", socket_path, sb.st_mode); return -1; } return 0; } static int try_connect(struct ctl_client* self, int timeout) { if (self->fd != -1) close(self->fd); self->fd = socket(AF_UNIX, SOCK_STREAM, 0); if (self->fd < 0) { ERROR("Failed to create unix socket: %m"); return 1; } while (connect(self->fd, (struct sockaddr*)&self->addr, sizeof(self->addr)) != 0) { if (timeout == 0 || errno != ENOENT) { ERROR("Failed to connect to unix socket \"%s\": %m", self->addr.sun_path); return 1; } if (usleep(50000) == -1) { ERROR("Failed to wait for connect to succeed: %m"); return 1; } } return 0; } static int ctl_client_connect(struct ctl_client* self, int timeout) { // TODO: Support arbitrary timeouts? assert(timeout == 0 || timeout == -1); if (wait_for_socket(self->addr.sun_path, timeout) != 0) return 1; if (try_connect(self, timeout) != 0) return 1; return 0; } void ctl_client_destroy(struct ctl_client* self) { close(self->fd); free(self); } void* ctl_client_userdata(struct ctl_client* self) { return self->userdata; } static struct jsonipc_request* ctl_client_parse_args(struct ctl_client* self, enum cmd_type* cmd, struct option_parser* options) { struct jsonipc_request* request = NULL; json_t* params = json_object(); struct cmd_info* info = ctl_command_by_type(*cmd); if (option_parser_get_value(options, "help")) { json_object_set_new(params, "command", json_string(info->name)); *cmd = CMD_HELP; info = ctl_command_by_type(*cmd); goto out; } for (int i = 0; info->params[i].name != NULL; ++i) { const char* key = info->params[i].name; const char* value = option_parser_get_value(options, key); if (!value) continue; json_object_set_new(params, key, json_string(value)); } out: request = jsonipc_request_new(info->name, params); json_decref(params); return request; } static json_t* json_from_buffer(struct ctl_client* self) { if (self->read_len == 0) { DEBUG("Read buffer is empty"); errno = EAGAIN; return NULL; } json_error_t err; json_t* root = json_loadb(self->read_buffer, self->read_len, JSON_DISABLE_EOF_CHECK, &err); if (root) { advance_read_buffer(&self->read_buffer, &self->read_len, err.position); } else if (json_error_code(&err) == json_error_premature_end_of_input) { if (self->read_len == sizeof(self->read_buffer)) { ERROR("Response message is too long"); errno = EMSGSIZE; } else { DEBUG("Awaiting more data"); errno = EAGAIN; } } else { ERROR("Json parsing failed: %s", err.text); errno = EINVAL; } return root; } static json_t* read_one_object(struct ctl_client* self, int timeout_ms) { json_t* root = json_from_buffer(self); if (root) return root; if (errno != EAGAIN) return NULL; struct pollfd pfd = { .fd = self->fd, .events = POLLIN, .revents = 0 }; while (!root) { int n = poll(&pfd, 1, timeout_ms); if (n == -1) { if (errno == EINTR && self->wait_for_events) continue; ERROR("Error waiting for a response: %m"); break; } else if (n == 0) { ERROR("Timeout waiting for a response"); break; } char* readptr = self->read_buffer + self->read_len; size_t remainder = sizeof(self->read_buffer) - self->read_len; n = recv(self->fd, readptr, remainder, 0); if (n == -1) { ERROR("Read failed: %m"); break; } else if (n == 0) { ERROR("Disconnected"); errno = ECONNRESET; break; } DEBUG("Read %d bytes", n); DEBUG("<< %.*s", n, readptr); self->read_len += n; root = json_from_buffer(self); if (!root && errno != EAGAIN) break; } return root; } static struct jsonipc_response* ctl_client_wait_for_response(struct ctl_client* self) { DEBUG("Waiting for a response"); json_t* root = read_one_object(self, 1000); if (!root) return NULL; struct jsonipc_error jipc_err = JSONIPC_ERR_INIT; struct jsonipc_response* response = jsonipc_response_parse_new(root, &jipc_err); if (!response) { char* msg = json_dumps(jipc_err.data, JSON_EMBED); ERROR("Could not parse json: %s", msg); free(msg); } json_decref(root); jsonipc_error_cleanup(&jipc_err); return response; } static void print_error(struct jsonipc_response* response, const char* method) { printf("ERROR: Failed to execute command: %s", method); if (!response->data) goto out; json_t* data = response->data; if (json_is_string(data)) printf(": %s", json_string_value(data)); else if (json_is_object(data) && json_is_string(json_object_get(data, "error"))) printf(": %s", json_string_value(json_object_get(data, "error"))); else json_dumpf(response->data, stdout, JSON_INDENT(2)); out: printf("\n"); } static void pretty_version(json_t* data) { printf("wayvnc is running:\n"); const char* key; json_t* value; json_object_foreach(data, key, value) printf(" %s: %s\n", key, json_string_value(value)); } static void pretty_client_list(json_t* data) { size_t i; json_t* value; json_array_foreach(data, i, value) { char* id = NULL; char* address = NULL; char* username = NULL; json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "address", &address, "username", &username); printf(" %s: ", id); if (username) printf("%s@", username); printf("%s\n", address ? address : ""); } } static void pretty_output_list(json_t* data) { size_t i; json_t* value; json_array_foreach(data, i, value) { char* name = NULL; char* description = NULL; int height = -1; int width = -1; int captured = false; json_unpack(value, "{s:s, s:s, s:i, s:i, s:b}", "name", &name, "description", &description, "height", &height, "width", &width, "captured", &captured); printf("%s %s: \"%s\" (%dx%d)\n", captured ? "*" : " ", name, description, width, height); } } static void pretty_print(json_t* data, struct jsonipc_request* request) { enum cmd_type cmd = ctl_command_parse_name(request->method); switch (cmd) { case CMD_VERSION: pretty_version(data); break; case CMD_CLIENT_LIST: pretty_client_list(data); break; case CMD_OUTPUT_LIST: pretty_output_list(data); break; case CMD_ATTACH: case CMD_DETACH: case CMD_CLIENT_DISCONNECT: case CMD_OUTPUT_SET: case CMD_OUTPUT_CYCLE: case CMD_WAYVNC_EXIT: printf("Ok\n"); break; case CMD_EVENT_RECEIVE: case CMD_HELP: abort(); // Handled directly by ctl_client_run_command case CMD_UNKNOWN: json_dumpf(data, stdout, JSON_INDENT(2)); } } static void print_compact_json(json_t* data) { json_dumpf(data, stdout, JSON_COMPACT); printf("\n"); } static int ctl_client_print_response(struct ctl_client* self, struct jsonipc_request* request, struct jsonipc_response* response) { DEBUG("Response code: %d", response->code); if (response->data) { if (self->flags & CTL_CLIENT_PRINT_JSON) print_compact_json(response->data); else if (response->code == 0) pretty_print(response->data, request); else print_error(response, request->method); } return response->code; } static struct ctl_client* sig_target = NULL; static void stop_loop(int signal) { sig_target->wait_for_events = false; } static void setup_signals(struct ctl_client* self) { sig_target = self; struct sigaction sa = { 0 }; sa.sa_handler = stop_loop; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); } static void print_indent(int level) { for (int i = 0; i < level; ++i) printf(" "); } static bool json_has_content(json_t* root) { if (!root) return false; size_t i; const char* key; json_t* value; switch (json_typeof(root)) { case JSON_NULL: return false; case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: return true; case JSON_STRING: return json_string_value(root)[0] != '\0'; case JSON_OBJECT: json_object_foreach(root, key, value) if (json_has_content(value)) return true; return false; case JSON_ARRAY: json_array_foreach(root, i, value) if (json_has_content(value)) return true; return false; } return false; } static void print_for_human(json_t* data, int level) { size_t i; const char* key; json_t* value; switch(json_typeof(data)) { case JSON_NULL: printf("\n"); break; case JSON_OBJECT: json_object_foreach(data, key, value) { if (!json_has_content(value)) continue; print_indent(level); printf("%s: ", key); print_for_human(value, level + 1); } break; case JSON_ARRAY: json_array_foreach(data, i, value) { if (!json_has_content(value)) continue; print_indent(level); printf("- "); print_for_human(value, level + 1); } break; case JSON_STRING: printf("%s\n", json_string_value(data)); break; case JSON_INTEGER: printf("%" JSON_INTEGER_FORMAT "\n", json_integer_value(data)); break; case JSON_REAL: printf("%f\n", json_real_value(data)); break; case JSON_TRUE: printf("true\n"); break; case JSON_FALSE: printf("false\n"); break; } } static void print_event(struct jsonipc_request* event, unsigned flags) { if (flags & CTL_CLIENT_PRINT_JSON) { print_compact_json(event->json); } else { printf("%s:\n", event->method); if (event->params) print_for_human(event->params, 1); printf("\n"); } fflush(stdout); } static void send_local_event(struct ctl_client* self, const char* name) { struct jsonipc_request* event = jsonipc_event_new(name, NULL); event->json = jsonipc_request_pack(event, NULL); print_event(event, self->flags); jsonipc_request_destroy(event); } static void send_startup_event(struct ctl_client* self) { send_local_event(self, EVT_LOCAL_STARTUP); } static void send_shutdown_event(struct ctl_client* self) { send_local_event(self, EVT_LOCAL_SHUTDOWN); } static ssize_t ctl_client_send_request(struct ctl_client* self, struct jsonipc_request* request) { json_error_t err; json_t* packed = jsonipc_request_pack(request, &err); if (!packed) { ERROR("Could not encode json: %s", err.text); return -1; } char buffer[512]; int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT); json_decref(packed); DEBUG(">> %.*s", len, buffer); return send(self->fd, buffer, len, MSG_NOSIGNAL); } static struct jsonipc_response* ctl_client_run_single_command(struct ctl_client* self, struct jsonipc_request* request) { if (ctl_client_send_request(self, request) < 0) return NULL; return ctl_client_wait_for_response(self); } static int ctl_client_register_for_events(struct ctl_client* self, struct jsonipc_request* request) { struct jsonipc_response* response = ctl_client_run_single_command(self, request); if (!response) return -1; int result = response->code; jsonipc_response_destroy(response); if (result == 0) send_startup_event(self); return result; } static int ctl_client_reconnect_event_loop(struct ctl_client* self, struct jsonipc_request* request) { if (ctl_client_connect(self, -1) != 0) return -1; return ctl_client_register_for_events(self, request); } static int block_until_reconnect(struct ctl_client* self, struct jsonipc_request* request) { while (ctl_client_reconnect_event_loop(self, request) != 0) if (usleep(50000) == -1) { DEBUG("Interrupted waiting for the IPC socket"); return -1; } return 0; } static int ctl_client_event_loop(struct ctl_client* self, struct jsonipc_request* request) { int result = ctl_client_register_for_events(self, request); if (result != 0) return result; self->wait_for_events = true; setup_signals(self); while (self->wait_for_events) { DEBUG("Waiting for an event"); json_t* root = read_one_object(self, -1); if (!root) { if (errno == ECONNRESET) { send_shutdown_event(self); if (self->flags & CTL_CLIENT_RECONNECT && block_until_reconnect( self, request) == 0) continue; } break; } struct jsonipc_error err = JSONIPC_ERR_INIT; struct jsonipc_request* event = jsonipc_event_parse_new(root, &err); json_decref(root); print_event(event, self->flags); jsonipc_request_destroy(event); } return 0; } static int ctl_client_print_single_command(struct ctl_client* self, enum cmd_type cmd, struct jsonipc_request* request) { struct jsonipc_response* response = ctl_client_run_single_command(self, request); if (!response) { if (errno == ECONNRESET && cmd == CMD_WAYVNC_EXIT) return 0; return 1; } int result = ctl_client_print_response(self, request, response); jsonipc_response_destroy(response); return result; } void ctl_client_print_command_list(FILE* stream) { fprintf(stream, "Commands:\n"); size_t max_namelen = 0; for (size_t i = 0; i < CMD_LIST_LEN; ++i) { if (i == CMD_HELP) // hidden continue; max_namelen = MAX(max_namelen, strlen(ctl_command_list[i].name)); } struct table_printer printer; table_printer_init(&printer, stdout, max_namelen); for (size_t i = 0; i < CMD_LIST_LEN; ++i) { if (i == CMD_HELP) // hidden continue; table_printer_print_line(&printer, ctl_command_list[i].name, ctl_command_list[i].description); } fprintf(stream, "\nRun 'wayvncctl command-name --help' for command-specific details.\n"); } static size_t param_render_length(const struct cmd_param_info* param) { return strlen(param->name) + strlen(param->schema) + 1; } static void print_event_info(const struct cmd_info* info) { printf("%s\n", info->name); option_parser_print_cmd_summary(info->description, stdout); if (info->params[0].name != NULL) { printf("Data fields:\n"); size_t max_namelen = 0; for (int i = 0; info->params[i].name != NULL; ++i) max_namelen = MAX(max_namelen, param_render_length(&info->params[i])); struct table_printer printer; table_printer_init(&printer, stdout, max_namelen); for (int i = 0; info->params[i].name != NULL; ++i) table_printer_print_fmtline(&printer, info->params[i].description, "%s: %s", info->params[i].name, info->params[i].schema); printf("\n"); } } static int print_event_details(const char* evt_name) { struct cmd_info* info = ctl_event_by_name(evt_name); if (info) { print_event_info(info); return 0; } for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i) { if (strcmp(evt_name, internal_events[i].name) == 0) { print_event_info(&internal_events[i]); return 0; } } ERROR("No such event \"%s\"\n", evt_name); return 1; } void ctl_client_print_event_list(FILE* stream) { printf("Events:\n"); size_t max_namelen = 0; for (size_t i = 0; i < EVT_LIST_LEN; ++i) max_namelen = MAX(max_namelen, strlen(ctl_event_list[i].name)); for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i) max_namelen = MAX(max_namelen, strlen(internal_events[i].name)); struct table_printer printer; table_printer_init(&printer, stdout, max_namelen); for (size_t i = 0; i < EVT_LIST_LEN; ++i) table_printer_print_line(&printer, ctl_event_list[i].name, ctl_event_list[i].description); for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i) table_printer_print_line(&printer, internal_events[i].name, internal_events[i].description); } static int print_command_usage(struct ctl_client* self, enum cmd_type cmd, struct option_parser* cmd_options, struct option_parser* parent_options) { if (self->flags & CTL_CLIENT_PRINT_JSON) { ERROR("JSON output is not supported for \"help\" output"); return 1; } struct cmd_info* info = ctl_command_by_type(cmd); if (!info) { ERROR("No such command"); return 1; } printf("Usage: wayvncctl [options] %s", info->name); option_parser_print_usage(cmd_options, stdout); printf("\n"); option_parser_print_cmd_summary(info->description, stdout); if (option_parser_print_arguments(cmd_options, stdout)) printf("\n"); option_parser_print_options(cmd_options, stdout); printf("\n"); option_parser_print_options(parent_options, stdout); printf("\n"); if (cmd == CMD_EVENT_RECEIVE) { ctl_client_print_event_list(stdout); printf("\n"); } return 0; } int ctl_client_init_cmd_parser(struct option_parser* parser, enum cmd_type cmd) { struct cmd_info* info = ctl_command_by_type(cmd); if (!info) { printf("Invalid command"); return -1; } size_t param_count = 0; while (info->params[param_count].name != NULL) param_count++; // Add 2: one for --help and one to null-terminate the list size_t alloc_count = param_count + 2; if (cmd == CMD_EVENT_RECEIVE) alloc_count++; struct wv_option* options = calloc(alloc_count, sizeof(*options)); size_t i = 0; for (; i < param_count; ++i) { struct wv_option* option = &options[i]; struct cmd_param_info* param = &info->params[i]; option->help = param->description; if (param->positional) { option->positional = param->name; option->help = param->description; } else { option->long_opt = param->name; option->schema = param->schema; } } if (cmd == CMD_EVENT_RECEIVE) { options[i].long_opt = "show"; options[i].schema = ""; options[i].help = "Display details about the given event"; i++; } options[i].long_opt = "help"; options[i].short_opt = 'h'; options[i].help = "Display this help text"; option_parser_init(parser, options); parser->name = "Parameters"; return 0; } static void ctl_client_destroy_cmd_parser(struct option_parser* parser) { // const in the struct, but we allocated it above free((void*)parser->options); } int ctl_client_run_command(struct ctl_client* self, struct option_parser* parent_options, unsigned flags) { self->flags = flags; int result = 1; const char* method = option_parser_get_value(parent_options, "command"); enum cmd_type cmd = ctl_command_parse_name(method); if (cmd == CMD_UNKNOWN || cmd == CMD_HELP) { ERROR("No such command \"%s\"\n", method); return 1; } struct option_parser cmd_options = { }; if (ctl_client_init_cmd_parser(&cmd_options, cmd) != 0) return 1; if (option_parser_parse(&cmd_options, parent_options->remaining_argc, parent_options->remaining_argv) != 0) goto parse_failure; if (option_parser_get_value(&cmd_options, "help")) { result = print_command_usage(self, cmd, &cmd_options, parent_options); goto help_printed; } if (cmd == CMD_EVENT_RECEIVE && option_parser_get_value(&cmd_options, "show")) { result = print_event_details(option_parser_get_value(&cmd_options, "show")); goto help_printed; } struct jsonipc_request* request = ctl_client_parse_args(self, &cmd, &cmd_options); if (!request) goto parse_failure; int timeout = (flags & CTL_CLIENT_SOCKET_WAIT) ? -1 : 0; result = ctl_client_connect(self, timeout); if (result != 0) goto connect_failure; switch (cmd) { case CMD_EVENT_RECEIVE: result = ctl_client_event_loop(self, request); break; default: result = ctl_client_print_single_command(self, cmd, request); break; } connect_failure: jsonipc_request_destroy(request); help_printed: parse_failure: ctl_client_destroy_cmd_parser(&cmd_options); return result; } wayvnc-0.8.0/src/ctl-commands.c000066400000000000000000000106651456662133500163650ustar00rootroot00000000000000/* * Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "ctl-commands.h" #include #include struct cmd_info ctl_command_list[] = { [CMD_ATTACH] = { "attach", "Attach to a running wayland compositor", { { "display", "Display name", "", .positional = true }, {}, } }, [CMD_DETACH] = { "detach", "Detach from the wayland compositor", {{}}, }, [CMD_HELP] = { "help", "List all commands and events, or show usage of a specific command or event", { { "command", "The command to show (optional)", "" }, { "event", "The event to show (optional)", "" }, {}, } }, [CMD_VERSION] = { "version", "Query the version of the wayvnc process", {{}} }, [CMD_EVENT_RECEIVE] = { "event-receive", "Register to begin receiving asynchronous events from wayvnc", // TODO: Event type filtering? {{}} }, [CMD_CLIENT_LIST] = { "client-list", "Return a list of all currently connected VNC sessions", {{}} }, [CMD_CLIENT_DISCONNECT] = { "client-disconnect", "Disconnect a VNC session", { { "id", "The ID of the client to disconnect", "", true }, {}, } }, [CMD_OUTPUT_LIST] = { "output-list", "Return a list of all currently detected Wayland outputs", {{}} }, [CMD_OUTPUT_CYCLE] = { "output-cycle", "Cycle the actively captured output to the next available output, wrapping through all outputs.", {{}} }, [CMD_OUTPUT_SET] = { "output-set", "Switch the actively captured output", { { "output-name", "The specific output name to capture", "", true }, {}, } }, [CMD_WAYVNC_EXIT] = { "wayvnc-exit", "Disconnect all clients and shut down wayvnc", {{}}, }, }; #define CLIENT_EVENT_PARAMS(including) \ { "id", \ "A unique identifier for this client", \ "" }, \ { "connection_count", \ "The total number of connected VNC clients " including " this one.", \ "" }, \ { "address", \ "The IP address of this client (may be null)", \ "" }, \ { "username", \ "The username used to authentice this client (may be null).", \ "" }, \ {}, struct cmd_info ctl_event_list[] = { [EVT_CAPTURE_CHANGED] = {"capture-changed", "Sent by wayvnc when the captured output is changed", { { "output-name", "The name of the output now being captured", "" }, {}, }, }, [EVT_CLIENT_CONNECTED] = {"client-connected", "Sent by wayvnc when a new VNC client connects", { CLIENT_EVENT_PARAMS("including") } }, [EVT_CLIENT_DISCONNECTED] = {"client-disconnected", "Sent by waynvc when a VNC client disconnects", { CLIENT_EVENT_PARAMS("not including") } }, [EVT_DETACHED] = {"detached", "Sent after detaching from compositor", {} }, }; enum cmd_type ctl_command_parse_name(const char* name) { if (!name || name[0] == '\0') return CMD_UNKNOWN; for (size_t i = 0; i < CMD_LIST_LEN; ++i) { if (strcmp(name, ctl_command_list[i].name) == 0) { return i; } } return CMD_UNKNOWN; } enum event_type ctl_event_parse_name(const char* name) { if (!name || name[0] == '\0') return EVT_UNKNOWN; for (size_t i = 0; i < EVT_LIST_LEN; ++i) { if (strcmp(name, ctl_event_list[i].name) == 0) { return i; } } return EVT_UNKNOWN; } struct cmd_info* ctl_command_by_type(enum cmd_type cmd) { if (cmd == CMD_UNKNOWN) return NULL; return &ctl_command_list[cmd]; } struct cmd_info* ctl_command_by_name(const char* name) { return ctl_command_by_type(ctl_command_parse_name(name)); } struct cmd_info* ctl_event_by_type(enum event_type evt) { if (evt == EVT_UNKNOWN) return NULL; return &ctl_event_list[evt]; } struct cmd_info* ctl_event_by_name(const char* name) { return ctl_event_by_type(ctl_event_parse_name(name)); } wayvnc-0.8.0/src/ctl-server.c000066400000000000000000000641341456662133500160720ustar00rootroot00000000000000/* * Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "output.h" #include "ctl-commands.h" #include "ctl-server.h" #include "json-ipc.h" #include "util.h" #include "strlcpy.h" #define FAILED_TO(action) \ nvnc_log(NVNC_LOG_ERROR, "Failed to " action ": %m"); enum send_priority { SEND_FIFO, SEND_IMMEDIATE, }; struct cmd { enum cmd_type type; }; struct cmd_attach { struct cmd cmd; char display[128]; }; struct cmd_help { struct cmd cmd; char id[64]; bool id_is_command; }; struct cmd_set_output { struct cmd cmd; char target[64]; enum output_cycle_direction cycle; }; struct cmd_disconnect_client { struct cmd cmd; char id[64]; }; struct cmd_response { int code; json_t* data; }; struct ctl_client { int fd; struct wl_list link; struct ctl* server; struct aml_handler* handler; char read_buffer[512]; size_t read_len; json_t* response_queue; char* write_buffer; char* write_ptr; size_t write_len; bool drop_after_next_send; bool accept_events; }; struct ctl { char socket_path[255]; struct ctl_server_actions actions; int fd; struct aml_handler* handler; struct wl_list clients; }; static struct cmd_response* cmd_response_new(int code, json_t* data) { struct cmd_response* new = calloc(1, sizeof(struct cmd_response)); new->code = code; new->data = data; return new; } static void cmd_response_destroy(struct cmd_response* self) { json_decref(self->data); free(self); } static struct cmd_attach* cmd_attach_new(json_t* args, struct jsonipc_error* err) { const char* display = NULL; if (json_unpack(args, "{s:s}", "display", &display) == -1) { jsonipc_error_printf(err, EINVAL, "Missing display name"); return NULL; } struct cmd_attach* cmd = calloc(1, sizeof(*cmd)); strlcpy(cmd->display, display, sizeof(cmd->display)); return cmd; } static struct cmd_help* cmd_help_new(json_t* args, struct jsonipc_error* err) { const char* command = NULL; const char* event = NULL; if (args && json_unpack(args, "{s?s, s?s}", "command", &command, "event", &event) == -1) { jsonipc_error_printf(err, EINVAL, "expecting \"command\" or \"event\" (optional)"); return NULL; } if (command && event) { jsonipc_error_printf(err, EINVAL, "expecting exacly one of \"command\" or \"event\""); return NULL; } struct cmd_help* cmd = calloc(1, sizeof(*cmd)); if (command) { strlcpy(cmd->id, command, sizeof(cmd->id)); cmd->id_is_command = true; } else if (event) { strlcpy(cmd->id, event, sizeof(cmd->id)); cmd->id_is_command = false; } return cmd; } static struct cmd_set_output* cmd_set_output_new(json_t* args, struct jsonipc_error* err) { const char* target = NULL; if (json_unpack(args, "{s:s}", "output-name", &target) == -1) { jsonipc_error_printf(err, EINVAL, "Missing output name"); return NULL; } struct cmd_set_output* cmd = calloc(1, sizeof(*cmd)); strlcpy(cmd->target, target, sizeof(cmd->target)); return cmd; } static struct cmd_disconnect_client* cmd_disconnect_client_new(json_t* args, struct jsonipc_error* err) { const char* id = NULL; if (json_unpack(args, "{s:s}", "id", &id) == -1) { jsonipc_error_printf(err, EINVAL, "Missing client id"); return NULL; } struct cmd_disconnect_client* cmd = calloc(1, sizeof(*cmd)); strlcpy(cmd->id, id, sizeof(cmd->id)); return cmd; } static json_t* list_allowed(struct cmd_info (*list)[], size_t len) { json_t* allowed = json_array(); for (size_t i = 0; i < len; ++i) { json_array_append_new(allowed, json_string((*list)[i].name)); } return allowed; } static json_t* list_allowed_commands() { return list_allowed(&ctl_command_list, CMD_LIST_LEN); } static json_t* list_allowed_events() { return list_allowed(&ctl_event_list, EVT_LIST_LEN); } static struct cmd* parse_command(struct jsonipc_request* ipc, struct jsonipc_error* err) { nvnc_trace("Parsing command %s", ipc->method); enum cmd_type cmd_type = ctl_command_parse_name(ipc->method); struct cmd* cmd = NULL; switch (cmd_type) { case CMD_ATTACH: cmd = (struct cmd*)cmd_attach_new(ipc->params, err); break; case CMD_HELP: cmd = (struct cmd*)cmd_help_new(ipc->params, err); break; case CMD_OUTPUT_SET: cmd = (struct cmd*)cmd_set_output_new(ipc->params, err); break; case CMD_CLIENT_DISCONNECT: cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err); break; case CMD_DETACH: case CMD_VERSION: case CMD_EVENT_RECEIVE: case CMD_CLIENT_LIST: case CMD_OUTPUT_LIST: case CMD_OUTPUT_CYCLE: case CMD_WAYVNC_EXIT: cmd = calloc(1, sizeof(*cmd)); break; case CMD_UNKNOWN: jsonipc_error_set_new(err, ENOENT, json_pack("{s:o, s:o}", "error", jprintf("Unknown command \"%s\"", ipc->method), "commands", list_allowed_commands())); break; } if (cmd) cmd->type = cmd_type; return cmd; } static void client_destroy(struct ctl_client* self) { nvnc_trace("Destroying client %p", self); aml_stop(aml_get_default(), self->handler); aml_unref(self->handler); close(self->fd); json_array_clear(self->response_queue); json_decref(self->response_queue); wl_list_remove(&self->link); free(self); } static void set_internal_error(struct cmd_response** err, int code, const char* fmt, ...) { char msg[256]; va_list ap; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); nvnc_log(NVNC_LOG_WARNING, msg); *err = cmd_response_new(code, json_pack("{s:s}", "error", msg)); } // Return values: // >0: Number of bytes read // 0: No bytes read (EAGAIN) // -1: Fatal error. Check 'err' for details, or if 'err' is null, terminate the connection. static ssize_t client_read(struct ctl_client* self, struct cmd_response** err) { size_t bufferspace = sizeof(self->read_buffer) - self->read_len; if (bufferspace == 0) { set_internal_error(err, EIO, "Buffer overflow"); return -1; } ssize_t n = recv(self->fd, self->read_buffer + self->read_len, bufferspace, MSG_DONTWAIT); if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { nvnc_trace("recv: EAGAIN"); return 0; } set_internal_error(err, EIO, "Read failed: %m"); return -1; } else if (n == 0) { nvnc_log(NVNC_LOG_INFO, "Control socket client disconnected: %p", self); errno = ENOTCONN; return -1; } self->read_len += n; nvnc_trace("Read %d bytes, total is now %d", n, self->read_len); return n; } static json_t* client_next_object(struct ctl_client* self, struct cmd_response** ierr) { if (self->read_len == 0) return NULL; json_error_t err; json_t* root = json_loadb(self->read_buffer, self->read_len, JSON_DISABLE_EOF_CHECK, &err); if (root) { nvnc_log(NVNC_LOG_DEBUG, "<< %.*s", err.position, self->read_buffer); advance_read_buffer(&self->read_buffer, &self->read_len, err.position); } else if (json_error_code(&err) == json_error_premature_end_of_input) { nvnc_trace("Awaiting more data"); } else { set_internal_error(ierr, EINVAL, err.text); } return root; } static struct cmd_response* generate_help_object(const char* id, bool id_is_command) { struct cmd_info* info = id_is_command ? ctl_command_by_name(id) : ctl_event_by_name(id); json_t* data; if (!info) { data = json_pack("{s:o, s:o}", "commands", list_allowed_commands(), "events", list_allowed_events()); } else { json_t* param_list = NULL; if (info->params[0].name) { param_list = json_object(); for (struct cmd_param_info* param = info->params; param->name; ++param) json_object_set_new(param_list, param->name, json_string(param->description)); } data = json_pack("{s:{s:s, s:o*}}", info->name, "description", info->description, "params", param_list); } struct cmd_response* response = cmd_ok(); response->data = data; return response; } static struct cmd_response* generate_version_object() { struct cmd_response* response = cmd_ok(); response->data = json_pack("{s:s, s:s, s:s}", "wayvnc", wayvnc_version, "neatvnc", nvnc_version, "aml", aml_version); return response; } static struct ctl_server_client* ctl_server_client_first(struct ctl* self) { return self->actions.client_next(self, NULL); } static struct ctl_server_client* ctl_server_client_next(struct ctl* self, struct ctl_server_client* prev) { return self->actions.client_next(self, prev); } static int sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr) { struct sockaddr_in* sa_in = (struct sockaddr_in*)addr; struct sockaddr_in6* sa_in6 = (struct sockaddr_in6*)addr; switch (addr->sa_family) { case AF_INET: inet_ntop(addr->sa_family, &sa_in->sin_addr, dst, sz); return 0; case AF_INET6: inet_ntop(addr->sa_family, &sa_in6->sin6_addr, dst, sz); return 0; } nvnc_log(NVNC_LOG_DEBUG, "Don't know how to convert sa_family %d to string", addr->sa_family); return -1; } static void ctl_server_client_get_info(struct ctl* self, const struct ctl_server_client* client, struct ctl_server_client_info* info) { return self->actions.client_info(client, info); } static struct cmd_response* generate_vnc_client_list(struct ctl* self) { struct cmd_response* response = cmd_ok(); response->data = json_array(); struct ctl_server_client* client; for (client = ctl_server_client_first(self); client; client = ctl_server_client_next(self, client)) { struct ctl_server_client_info info = {}; ctl_server_client_get_info(self, client, &info); char id_str[64]; snprintf(id_str, sizeof(id_str), "%d", info.id); json_t* packed = json_pack("{s:s}", "id", id_str); char address_string[256]; if (sockaddr_to_string(address_string, sizeof(address_string), &info.address) == 0) { json_object_set_new(packed, "address", json_string(address_string)); } if (info.username) json_object_set_new(packed, "username", json_string(info.username)); if (info.seat) json_object_set_new(packed, "seat", json_string(info.seat)); json_array_append_new(response->data, packed); } return response; } static struct cmd_response* generate_output_list(struct ctl* self) { struct ctl_server_output* outputs; size_t num_outputs = self->actions.get_output_list(self, &outputs); struct cmd_response* response = cmd_ok(); response->data = json_array(); for (size_t i = 0; i < num_outputs; ++i) json_array_append_new(response->data, json_pack( "{s:s, s:s, s:i, s:i, s:b, s:s}", "name", outputs[i].name, "description", outputs[i].description, "height", outputs[i].height, "width", outputs[i].width, "captured", outputs[i].captured, "power", outputs[i].power)); free(outputs); return response; } static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self, struct ctl_client* client, struct cmd* cmd) { const struct cmd_info* info = ctl_command_by_type(cmd->type); assert(info); nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name); struct cmd_response* response = NULL; switch (cmd->type) { case CMD_ATTACH:{ struct cmd_attach* c = (struct cmd_attach*)cmd; response = self->actions.on_attach(self, c->display); break; } case CMD_HELP:{ struct cmd_help* c = (struct cmd_help*)cmd; response = generate_help_object(c->id, c->id_is_command); break; } case CMD_OUTPUT_SET: { struct cmd_set_output* c = (struct cmd_set_output*)cmd; response = self->actions.on_output_switch(self, c->target); break; } case CMD_CLIENT_DISCONNECT: { struct cmd_disconnect_client* c = (struct cmd_disconnect_client*)cmd; response = self->actions.on_disconnect_client(self, c->id); break; } case CMD_DETACH: response = self->actions.on_detach(self); break; case CMD_WAYVNC_EXIT: response = self->actions.on_wayvnc_exit(self); break; case CMD_VERSION: response = generate_version_object(); break; case CMD_EVENT_RECEIVE: client->accept_events = true; response = cmd_ok(); break; case CMD_CLIENT_LIST: response = generate_vnc_client_list(self); break; case CMD_OUTPUT_LIST: response = generate_output_list(self); break; case CMD_OUTPUT_CYCLE: response = self->actions.on_output_cycle(self, OUTPUT_CYCLE_FORWARD); break; case CMD_UNKNOWN: break; } return response; } static void client_set_aml_event_mask(struct ctl_client* self) { int mask = AML_EVENT_READ; if (json_array_size(self->response_queue) > 0 || self->write_len) mask |= AML_EVENT_WRITE; aml_set_event_mask(self->handler, mask); } static int client_enqueue(struct ctl_client* self, json_t* message, enum send_priority priority) { int result; switch(priority) { case SEND_IMMEDIATE: result = json_array_insert(self->response_queue, 0, message); break; case SEND_FIFO: result = json_array_append(self->response_queue, message); break; } client_set_aml_event_mask(self); return result; } static int client_enqueue_jsonipc(struct ctl_client* self, struct jsonipc_response* resp, enum send_priority priority) { int result = 0; json_error_t err; json_t* packed_response = jsonipc_response_pack(resp, &err); if (!packed_response) { nvnc_log(NVNC_LOG_WARNING, "Pack failed: %s", err.text); result = -1; goto failure; } result = client_enqueue(self, packed_response, priority); json_decref(packed_response); if (result != 0) nvnc_log(NVNC_LOG_WARNING, "Append failed"); failure: jsonipc_response_destroy(resp); return result; } static int client_enqueue_error(struct ctl_client* self, struct jsonipc_error* err, json_t* id) { struct jsonipc_response* resp = jsonipc_error_response_new(err, id); return client_enqueue_jsonipc(self, resp, SEND_FIFO); } static int client_enqueue__response(struct ctl_client* self, struct cmd_response* response, json_t* id, enum send_priority priority) { nvnc_log(NVNC_LOG_INFO, "Enqueueing response: %s (%d)", response->code == 0 ? "OK" : "FAILED", response->code); char* str = NULL; if (response->data) str = json_dumps(response->data, 0); nvnc_log(NVNC_LOG_DEBUG, "Response data: %s", str); if(str) free(str); struct jsonipc_response* resp = jsonipc_response_new(response->code, response->data, id); cmd_response_destroy(response); return client_enqueue_jsonipc(self, resp, priority); } static int client_enqueue_response(struct ctl_client* self, struct cmd_response* response, json_t* id) { return client_enqueue__response(self, response, id, SEND_FIFO); } static int client_enqueue_internal_error(struct ctl_client* self, struct cmd_response* err) { int result = client_enqueue__response(self, err, NULL, SEND_IMMEDIATE); if (result != 0) client_destroy(self); self->drop_after_next_send = true; return result; } static void send_ready(struct ctl_client* client) { if (client->write_buffer) { nvnc_trace("Continuing partial write (%d left)", client->write_len); } else if (json_array_size(client->response_queue) > 0){ nvnc_trace("Sending new queued message"); json_t* item = json_array_get(client->response_queue, 0); client->write_len = json_dumpb(item, NULL, 0, JSON_COMPACT); client->write_buffer = calloc(1, client->write_len); client->write_ptr = client->write_buffer; json_dumpb(item, client->write_buffer, client->write_len, JSON_COMPACT); nvnc_log(NVNC_LOG_DEBUG, ">> %.*s", client->write_len, client->write_buffer); json_array_remove(client->response_queue, 0); } else { nvnc_trace("Nothing to send"); } if (!client->write_ptr) goto no_data; ssize_t n = send(client->fd, client->write_ptr, client->write_len, MSG_NOSIGNAL|MSG_DONTWAIT); if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { nvnc_trace("send: EAGAIN"); goto send_eagain; } nvnc_log(NVNC_LOG_ERROR, "Could not send response: %m"); client_destroy(client); return; } nvnc_trace("sent %d/%d bytes", n, client->write_len); client->write_ptr += n; client->write_len -= n; send_eagain: if (client->write_len == 0) { nvnc_trace("Write buffer empty!"); free(client->write_buffer); client->write_buffer = NULL; client->write_ptr = NULL; if (client->drop_after_next_send) { nvnc_log(NVNC_LOG_WARNING, "Intentional disconnect"); client_destroy(client); return; } } else { nvnc_trace("Write buffer has %d remaining", client->write_len); } no_data: client_set_aml_event_mask(client); } static void recv_ready(struct ctl_client* client) { struct ctl* server = client->server; struct cmd_response* details = NULL; switch (client_read(client, &details)) { case 0: // Needs more data return; case -1: // Fatal error if (details) client_enqueue_internal_error(client, details); else client_destroy(client); return; default: // Read some data; check it break; } json_t* root; while (true) { root = client_next_object(client, &details); if (root == NULL) break; struct jsonipc_error jipc_err = JSONIPC_ERR_INIT; struct jsonipc_request* request = jsonipc_request_parse_new(root, &jipc_err); if (!request) { client_enqueue_error(client, &jipc_err, NULL); goto request_parse_failed; } struct cmd* cmd = parse_command(request, &jipc_err); if (!cmd) { client_enqueue_error(client, &jipc_err, request->id); goto cmdparse_failed; } // TODO: Enqueue the command (and request ID) to be // handled by the main loop instead of doing the // dispatch here struct cmd_response* response = ctl_server_dispatch_cmd(server, client, cmd); if (!response) goto no_response; client_enqueue_response(client, response, request->id); no_response: free(cmd); cmdparse_failed: jsonipc_request_destroy(request); request_parse_failed: jsonipc_error_cleanup(&jipc_err); json_decref(root); } if (details) client_enqueue_internal_error(client, details); } static void on_ready(void* obj) { struct ctl_client* client = aml_get_userdata(obj); uint32_t events = aml_get_revents(obj); nvnc_trace("Client %p ready: 0x%x", client, events); if (events & AML_EVENT_WRITE) send_ready(client); else if (events & AML_EVENT_READ) recv_ready(client); } static void on_connection(void* obj) { nvnc_log(NVNC_LOG_DEBUG, "New connection"); struct ctl* server = aml_get_userdata(obj); struct ctl_client* client = calloc(1, sizeof(*client)); if (!client) { FAILED_TO("allocate a client object"); return; } client->server = server; client->response_queue = json_array(); client->fd = accept(server->fd, NULL, 0); if (client->fd < 0) { FAILED_TO("accept a connection"); goto accept_failure; } client->handler = aml_handler_new(client->fd, on_ready, client, NULL); if (!client->handler) { FAILED_TO("create a loop handler"); goto handle_failure; } if (aml_start(aml_get_default(), client->handler) < 0) { FAILED_TO("register for client events"); goto poll_start_failure; } wl_list_insert(&server->clients, &client->link); nvnc_log(NVNC_LOG_INFO, "New control socket client connected: %p", client); return; poll_start_failure: aml_unref(client->handler); handle_failure: close(client->fd); accept_failure: json_decref(client->response_queue); free(client); } static int cleanup_old_socket(struct ctl* self, struct sockaddr* addr, size_t addr_size) { struct stat sb; if (stat(self->socket_path, &sb) == -1) // Doesn't exist: safe to proceed. return 0; if (!S_ISSOCK(sb.st_mode)) { nvnc_log(NVNC_LOG_ERROR, "Socket path '%s’ exists already and is not a socket."); goto manual_intervention; } int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { FAILED_TO("open a temporary socket"); goto manual_intervention; } nvnc_log(NVNC_LOG_DEBUG, "Connecting to existing socket in case it's stale"); if (connect(fd, addr, addr_size) == 0) { close(fd); nvnc_log(NVNC_LOG_ERROR, "Another wayvnc process is already running."); nvnc_log(NVNC_LOG_ERROR, "Use the '-S' option to choose an alternate control socket location"); return -1; } nvnc_log(NVNC_LOG_DEBUG, "Connect failed: %m"); close(fd); nvnc_log(NVNC_LOG_WARNING, "Deleting stale control socket path \"%s\"", self->socket_path); if (unlink(self->socket_path) == -1) { FAILED_TO("remove stale unix socket"); goto manual_intervention; } return 0; manual_intervention: nvnc_log(NVNC_LOG_ERROR, "Manually remove \"%s\" or use the '-S' option to choose an alternate socket location", self->socket_path); return -1; } int ctl_server_init(struct ctl* self, const char* socket_path) { if (!socket_path) { socket_path = default_ctl_socket_path(); if (!getenv("XDG_RUNTIME_DIR")) nvnc_log(NVNC_LOG_WARNING, "$XDG_RUNTIME_DIR is not set. Falling back to control socket \"%s\"", socket_path); } strlcpy(self->socket_path, socket_path, sizeof(self->socket_path)); nvnc_log(NVNC_LOG_DEBUG, "Initializing wayvncctl socket: %s", self->socket_path); wl_list_init(&self->clients); struct sockaddr_un addr = { .sun_family = AF_UNIX, }; if (strlen(self->socket_path) >= sizeof(addr.sun_path)) { errno = ENAMETOOLONG; FAILED_TO("create unix socket"); goto socket_failure; } strcpy(addr.sun_path, self->socket_path); self->fd = socket(AF_UNIX, SOCK_STREAM, 0); if (self->fd < 0) { FAILED_TO("create unix socket"); goto socket_failure; } if (cleanup_old_socket(self, (struct sockaddr*)&addr, sizeof(addr)) != 0) goto bind_failure; if (bind(self->fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { FAILED_TO("bind unix socket"); goto bind_failure; } if (listen(self->fd, 16) < 0) { FAILED_TO("listen to unix socket"); goto listen_failure; } self->handler = aml_handler_new(self->fd, on_connection, self, NULL); if (!self->handler) { FAILED_TO("create a main loop handler"); goto handle_failure; } if (aml_start(aml_get_default(), self->handler) < 0) { FAILED_TO("Register for server events"); goto poll_start_failure; } return 0; poll_start_failure: aml_unref(self->handler); handle_failure: listen_failure: unlink(self->socket_path); bind_failure: close(self->fd); socket_failure: return -1; } static void ctl_server_stop(struct ctl* self) { aml_stop(aml_get_default(), self->handler); aml_unref(self->handler); struct ctl_client* client; struct ctl_client* tmp; wl_list_for_each_safe(client, tmp, &self->clients, link) client_destroy(client); close(self->fd); unlink(self->socket_path); } struct ctl* ctl_server_new(const char* socket_path, const struct ctl_server_actions* actions) { struct ctl* ctl = calloc(1, sizeof(*ctl)); memcpy(&ctl->actions, actions, sizeof(*actions)); if (ctl_server_init(ctl, socket_path) != 0) { free(ctl); return NULL; } return ctl; } void ctl_server_destroy(struct ctl* self) { ctl_server_stop(self); free(self); } void* ctl_server_userdata(struct ctl* self) { return self->actions.userdata; } struct cmd_response* cmd_ok() { return cmd_response_new(0, NULL); } struct cmd_response* cmd_failed(const char* fmt, ...) { va_list ap; va_start(ap, fmt); struct cmd_response* resp = cmd_response_new(1, json_pack("{s:o}", "error", jvprintf(fmt, ap))); va_end(ap); return resp; } json_t* pack_connection_event_params( const struct ctl_server_client_info *info, int new_connection_count) { // TODO: Why is the id a string? char id_str[64]; snprintf(id_str, sizeof(id_str), "%d", info->id); char address_string[256]; bool have_addr = sockaddr_to_string(address_string, sizeof(address_string), &info->address) == 0; return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}", "id", id_str, "address", have_addr ? address_string : NULL, "username", info->username, "seat", info->seat, "connection_count", new_connection_count); } int ctl_server_enqueue_event(struct ctl* self, enum event_type evt_type, json_t* params) { const char* event_name = ctl_event_list[evt_type].name; char* param_str = json_dumps(params, JSON_COMPACT); nvnc_log(NVNC_LOG_DEBUG, "Enqueueing %s event: %s", event_name, param_str); free(param_str); struct jsonipc_request* event = jsonipc_event_new(event_name, params); json_decref(params); json_error_t err; json_t* packed_event = jsonipc_request_pack(event, &err); jsonipc_request_destroy(event); if (!packed_event) { nvnc_log(NVNC_LOG_WARNING, "Could not pack %s event json: %s", event_name, err.text); return -1; } int enqueued = 0; struct ctl_client* client; wl_list_for_each(client, &self->clients, link) { if (!client->accept_events) { nvnc_trace("Skipping event send to control client %p", client); continue; } if (client_enqueue(client, packed_event, false) == 0) { nvnc_trace("Enqueued event for control client %p", client); enqueued++; } else { nvnc_trace("Failed to enqueue event for control client %p", client); } } json_decref(packed_event); nvnc_log(NVNC_LOG_DEBUG, "Enqueued %s event for %d clients", event_name, enqueued); return enqueued; } static void ctl_server_event_connect(struct ctl* self, enum event_type evt_type, const struct ctl_server_client_info *info, int new_connection_count) { json_t* params = pack_connection_event_params(info, new_connection_count); ctl_server_enqueue_event(self, evt_type, params); } void ctl_server_event_connected(struct ctl* self, const struct ctl_server_client_info *info, int new_connection_count) { ctl_server_event_connect(self, EVT_CLIENT_CONNECTED, info, new_connection_count); } void ctl_server_event_disconnected(struct ctl* self, const struct ctl_server_client_info *info, int new_connection_count) { ctl_server_event_connect(self, EVT_CLIENT_DISCONNECTED, info, new_connection_count); } void ctl_server_event_capture_changed(struct ctl* self, const char* captured_output) { ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED, json_pack("{s:s}", "output", captured_output)); } void ctl_server_event_detached(struct ctl* self) { ctl_server_enqueue_event(self, EVT_DETACHED, json_object()); } wayvnc-0.8.0/src/data-control.c000066400000000000000000000171051456662133500163670ustar00rootroot00000000000000/* * Copyright (c) 2020 Scott Moreau * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "data-control.h" struct receive_context { struct data_control* data_control; struct zwlr_data_control_offer_v1* offer; int fd; FILE* mem_fp; size_t mem_size; char* mem_data; }; static void destroy_receive_context(void* raw_ctx) { struct receive_context* ctx = raw_ctx; int fd = ctx->fd; if (ctx->mem_fp) fclose(ctx->mem_fp); free(ctx->mem_data); zwlr_data_control_offer_v1_destroy(ctx->offer); close(fd); free(ctx); } static void on_receive(void* handler) { struct receive_context* ctx = aml_get_userdata(handler); int fd = aml_get_fd(handler); assert(ctx->fd == fd); char buf[4096]; ssize_t ret = read(fd, &buf, sizeof(buf)); if (ret > 0) { fwrite(&buf, 1, ret, ctx->mem_fp); return; } fclose(ctx->mem_fp); ctx->mem_fp = NULL; if (ctx->mem_size) nvnc_send_cut_text(ctx->data_control->server, ctx->mem_data, ctx->mem_size); aml_stop(aml_get_default(), handler); } static void receive_data(void* data, struct zwlr_data_control_offer_v1* offer) { struct data_control* self = data; int pipe_fd[2]; if (pipe(pipe_fd) == -1) { nvnc_log(NVNC_LOG_ERROR, "pipe() failed: %m"); return; } struct receive_context* ctx = calloc(1, sizeof(*ctx)); if (!ctx) { nvnc_log(NVNC_LOG_ERROR, "OOM"); close(pipe_fd[0]); close(pipe_fd[1]); return; } zwlr_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]); wl_display_flush(self->wl_display); close(pipe_fd[1]); ctx->fd = pipe_fd[0]; ctx->data_control = self; ctx->offer = offer; ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size); if (!ctx->mem_fp) { close(ctx->fd); free(ctx); nvnc_log(NVNC_LOG_ERROR, "open_memstream() failed: %m"); return; } struct aml_handler* handler = aml_handler_new(ctx->fd, on_receive, ctx, destroy_receive_context); if (!handler) { close(ctx->fd); free(ctx); return; } aml_start(aml_get_default(), handler); aml_unref(handler); } static void data_control_offer(void* data, struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1, const char* mime_type) { struct data_control* self = data; if (self->offer) return; if (strcmp(mime_type, self->mime_type) != 0) { return; } self->offer = zwlr_data_control_offer_v1; } struct zwlr_data_control_offer_v1_listener data_control_offer_listener = { data_control_offer }; static void data_control_device_offer(void* data, struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, struct zwlr_data_control_offer_v1* id) { if (!id) return; zwlr_data_control_offer_v1_add_listener(id, &data_control_offer_listener, data); } static void data_control_device_selection(void* data, struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, struct zwlr_data_control_offer_v1* id) { struct data_control* self = data; if (id && self->offer == id) { receive_data(data, id); self->offer = NULL; } } static void data_control_device_finished(void* data, struct zwlr_data_control_device_v1* zwlr_data_control_device_v1) { zwlr_data_control_device_v1_destroy(zwlr_data_control_device_v1); } static void data_control_device_primary_selection(void* data, struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, struct zwlr_data_control_offer_v1* id) { struct data_control* self = data; if (id && self->offer == id) { receive_data(data, id); self->offer = NULL; return; } } static struct zwlr_data_control_device_v1_listener data_control_device_listener = { .data_offer = data_control_device_offer, .selection = data_control_device_selection, .finished = data_control_device_finished, .primary_selection = data_control_device_primary_selection }; static void data_control_source_send(void* data, struct zwlr_data_control_source_v1* zwlr_data_control_source_v1, const char* mime_type, int32_t fd) { struct data_control* self = data; char* d = self->cb_data; size_t len = self->cb_len; int ret; assert(d); ret = write(fd, d, len); if (ret < (int)len) nvnc_log(NVNC_LOG_ERROR, "write from clipboard incomplete"); close(fd); } static void data_control_source_cancelled(void* data, struct zwlr_data_control_source_v1* zwlr_data_control_source_v1) { struct data_control* self = data; if (self->selection == zwlr_data_control_source_v1) { self->selection = NULL; } if (self->primary_selection == zwlr_data_control_source_v1) { self->primary_selection = NULL; } zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1); } struct zwlr_data_control_source_v1_listener data_control_source_listener = { .send = data_control_source_send, .cancelled = data_control_source_cancelled }; static struct zwlr_data_control_source_v1* set_selection(struct data_control* self, bool primary) { struct zwlr_data_control_source_v1* selection; selection = zwlr_data_control_manager_v1_create_data_source(self->manager); if (selection == NULL) { nvnc_log(NVNC_LOG_ERROR, "zwlr_data_control_manager_v1_create_data_source() failed"); free(self->cb_data); self->cb_data = NULL; return NULL; } zwlr_data_control_source_v1_add_listener(selection, &data_control_source_listener, self); zwlr_data_control_source_v1_offer(selection, self->mime_type); if (primary) zwlr_data_control_device_v1_set_primary_selection(self->device, selection); else zwlr_data_control_device_v1_set_selection(self->device, selection); return selection; } void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat) { self->wl_display = wl_display; self->server = server; self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat); zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self); self->selection = NULL; self->primary_selection = NULL; self->cb_data = NULL; self->cb_len = 0; self->mime_type = "text/plain;charset=utf-8"; } void data_control_destroy(struct data_control* self) { if (self->selection) { zwlr_data_control_source_v1_destroy(self->selection); self->selection = NULL; } if (self->primary_selection) { zwlr_data_control_source_v1_destroy(self->primary_selection); self->primary_selection = NULL; } zwlr_data_control_device_v1_destroy(self->device); free(self->cb_data); } void data_control_to_clipboard(struct data_control* self, const char* text, size_t len) { if (!len) { nvnc_log(NVNC_LOG_ERROR, "%s called with 0 length", __func__); return; } free(self->cb_data); self->cb_data = malloc(len); if (!self->cb_data) { nvnc_log(NVNC_LOG_ERROR, "OOM: %m"); return; } memcpy(self->cb_data, text, len); self->cb_len = len; // Set copy/paste buffer self->selection = set_selection(self, false); // Set highlight/middle_click buffer self->primary_selection = set_selection(self, true); } wayvnc-0.8.0/src/intset.c000066400000000000000000000042021456662133500153000ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "intset.h" #include #include #include #define DEFAULT_CAPACITY 256 int intset_init(struct intset* self, size_t cap) { if (cap == 0) cap = DEFAULT_CAPACITY; memset(self, 0, sizeof(*self)); self->storage = malloc(cap * sizeof(*self->storage)); if (!self->storage) return -1; self->cap = cap; return 0; } void intset_destroy(struct intset* self) { free(self->storage); memset(self, 0, sizeof(*self)); } static int intset__grow(struct intset* self) { size_t new_cap = self->cap * 2; int32_t* new_storage = realloc(self->storage, new_cap); if (!new_storage) return -1; self->storage = new_storage; self->cap = new_cap; return 0; } int intset_set(struct intset* self, int32_t value) { if (intset_is_set(self, value)) return 0; if (self->len >= self->cap && intset__grow(self) < 0) return -1; self->storage[self->len++] = value; return 0; } static ssize_t intset__find_index(const struct intset* self, int32_t value) { for (size_t i = 0; i < self->len; ++i) if (self->storage[i] == value) return i; return -1; } void intset_clear(struct intset* self, int32_t value) { ssize_t index = intset__find_index(self, value); if (index < 0) return; self->storage[index] = self->storage[--self->len]; } bool intset_is_set(const struct intset* self, int32_t value) { return intset__find_index(self, value) >= 0; } wayvnc-0.8.0/src/json-ipc.c000066400000000000000000000126001456662133500155150ustar00rootroot00000000000000/* * Copyright (c) 2022 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "json-ipc.h" static const char* jsonipc_id_key = "id"; static const char* jsonipc_method_key = "method"; static const char* jsonipc_params_key = "params"; static const char* jsonipc_code_key = "code"; static const char* jsonipc_data_key = "data"; void jsonipc_error_set_new(struct jsonipc_error* err, int code, json_t* data) { if (!err) return; err->code = code; err->data = data; } void jsonipc_error_printf(struct jsonipc_error* err, int code, const char* fmt, ...) { va_list ap; va_start(ap, fmt); jsonipc_error_set_new(err, code, json_pack("{s:o}", "error", jvprintf(fmt, ap))); va_end(ap); } void jsonipc_error_set_from_errno(struct jsonipc_error* err, const char* context) { jsonipc_error_printf(err, errno, "%s: %m", context); } void jsonipc_error_cleanup(struct jsonipc_error* err) { if (!err) return; json_decref(err->data); } inline static bool is_valid_id(json_t* id) { return id == NULL || json_is_string(id) || json_is_number(id); } struct jsonipc_request* jsonipc_request_parse_new(json_t* root, struct jsonipc_error* err) { struct jsonipc_request* ipc = calloc(1, sizeof(*ipc)); ipc->json = root; json_incref(ipc->json); json_error_t unpack_error; if (json_unpack_ex(root, &unpack_error, 0, "{s:s, s?O, s?O}", jsonipc_method_key, &ipc->method, jsonipc_params_key, &ipc->params, jsonipc_id_key, &ipc->id) == -1) { jsonipc_error_printf(err, EINVAL, unpack_error.text); goto failure; } if (!is_valid_id(ipc->id)) { char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY); jsonipc_error_printf(err, EINVAL, "Invalid ID \"%s\"", id); free(id); goto failure; } return ipc; failure: jsonipc_request_destroy(ipc); return NULL; } struct jsonipc_request* jsonipc_request__new(const char* method, json_t* params, json_t* id) { struct jsonipc_request* ipc = calloc(1, sizeof(*ipc)); ipc->method = method; ipc->params = params; json_incref(ipc->params); ipc->id = id; return ipc; } static int request_id = 1; struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params) { return jsonipc_request__new(method, params, json_integer(request_id++)); } struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params) { return jsonipc_request__new(method, params, NULL); } struct jsonipc_request* jsonipc_event_parse_new(json_t* root, struct jsonipc_error* err) { return jsonipc_request_parse_new(root, err); } json_t* jsonipc_request_pack(struct jsonipc_request* self, json_error_t* err) { return json_pack_ex(err, 0, "{s:s, s:O*, s:O*}", jsonipc_method_key, self->method, jsonipc_params_key, self->params, jsonipc_id_key, self->id); } void jsonipc_request_destroy(struct jsonipc_request* self) { json_decref(self->params); json_decref(self->id); json_decref(self->json); free(self); } struct jsonipc_response* jsonipc_response_parse_new(json_t* root, struct jsonipc_error* err) { struct jsonipc_response* ipc = calloc(1, sizeof(*ipc)); ipc->json = root; json_incref(ipc->json); json_error_t unpack_error; if (json_unpack_ex(root, &unpack_error, 0, "{s:i, s?O, s?O}", jsonipc_code_key, &ipc->code, jsonipc_data_key, &ipc->data, jsonipc_id_key, &ipc->id) == -1) { jsonipc_error_printf(err, EINVAL, unpack_error.text); goto failure; } if (!is_valid_id(ipc->id)) { char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY); jsonipc_error_printf(err, EINVAL, "Invalid ID \"%s\"", id); free(id); goto failure; } return ipc; failure: jsonipc_response_destroy(ipc); return NULL; } struct jsonipc_response* jsonipc_response_new(int code, json_t* data, json_t* id) { struct jsonipc_response* rsp = calloc(1, sizeof(*rsp)); rsp->code = code; json_incref(id); rsp->id = id; json_incref(data); rsp->data = data; return rsp; } struct jsonipc_response* jsonipc_error_response_new( struct jsonipc_error* err, json_t* id) { return jsonipc_response_new(err->code, err->data, id); } void jsonipc_response_destroy(struct jsonipc_response* self) { json_decref(self->data); json_decref(self->json); json_decref(self->id); free(self); } json_t* jsonipc_response_pack(struct jsonipc_response* self, json_error_t* err) { return json_pack_ex(err, 0, "{s:i, s:O*, s:O*}", jsonipc_code_key, self->code, jsonipc_id_key, self->id, jsonipc_data_key, self->data); } json_t* jprintf(const char* fmt, ...) { va_list args; va_start(args, fmt); json_t* result = jvprintf(fmt, args); va_end(args); return result; } json_t* jvprintf(const char* fmt, va_list ap) { char buffer[128]; int len = vsnprintf(buffer, sizeof(buffer), fmt, ap); return json_stringn(buffer, len); } wayvnc-0.8.0/src/keyboard.c000066400000000000000000000270341456662133500156020ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * Acknowledgements: Reading Josef Gajdusek's wvnc code helped me understand * how to use the xkbcommon API to interface with the wayland virtual keyboard * interface. */ #include #include #include #include #include #include #include #include #include #include #include "virtual-keyboard-unstable-v1.h" #include "keyboard.h" #include "shm.h" #include "intset.h" #define MAYBE_UNUSED __attribute__((unused)) struct table_entry { xkb_keysym_t symbol; xkb_keycode_t code; int level; }; struct kb_mods { xkb_mod_mask_t depressed, latched, locked; }; static void append_entry(struct keyboard* self, xkb_keysym_t symbol, xkb_keycode_t code, int level) { if (self->lookup_table_size <= self->lookup_table_length) { size_t new_size = self->lookup_table_size * 2; struct table_entry* table = realloc(self->lookup_table, new_size * sizeof(*table)); if (!table) return; // TODO: Report this self->lookup_table_size = new_size; self->lookup_table = table; } struct table_entry* entry = &self->lookup_table[self->lookup_table_length++]; entry->symbol = symbol; entry->code = code; entry->level = level; } static void key_iter(struct xkb_keymap* map, xkb_keycode_t code, void* userdata) { struct keyboard* self = userdata; size_t n_levels = xkb_keymap_num_levels_for_key(map, code, 0); for (size_t level = 0; level < n_levels; level++) { const xkb_keysym_t* symbols; size_t n_syms = xkb_keymap_key_get_syms_by_level(map, code, 0, level, &symbols); for (size_t sym_idx = 0; sym_idx < n_syms; sym_idx++) append_entry(self, symbols[sym_idx], code, level); } } static int compare_symbols(const void* a, const void* b) { const struct table_entry* x = a; const struct table_entry* y = b; if (x->symbol == y->symbol) return x->code < y->code ? -1 : x->code > y->code; return x->symbol < y->symbol ? -1 : x->symbol > y->symbol; } static int compare_symbols2(const void* a, const void* b) { const struct table_entry* x = a; const struct table_entry* y = b; return x->symbol < y->symbol ? -1 : x->symbol > y->symbol; } static int create_lookup_table(struct keyboard* self) { self->lookup_table_length = 0; self->lookup_table_size = 128; self->lookup_table = malloc(self->lookup_table_size * sizeof(*self->lookup_table)); if (!self->lookup_table) return -1; xkb_keymap_key_for_each(self->keymap, key_iter, self); qsort(self->lookup_table, self->lookup_table_length, sizeof(*self->lookup_table), compare_symbols); return 0; } static char* get_symbol_name(xkb_keysym_t sym, char* dst, size_t size) { if (xkb_keysym_get_name(sym, dst, size) >= 0) return dst; snprintf(dst, size, "UNKNOWN (%x)", sym); return dst; } static void keyboard__dump_entry(const struct keyboard* self, const struct table_entry* entry) { char sym_name[256]; get_symbol_name(entry->symbol, sym_name, sizeof(sym_name)); const char* code_name MAYBE_UNUSED = xkb_keymap_key_get_name(self->keymap, entry->code); bool is_pressed MAYBE_UNUSED = intset_is_set(&self->key_state, entry->code); nvnc_log(NVNC_LOG_DEBUG, "symbol=%s level=%d code=%s %s", sym_name, entry->level, code_name, is_pressed ? "pressed" : "released"); } void keyboard_dump_lookup_table(const struct keyboard* self) { for (size_t i = 0; i < self->lookup_table_length; i++) keyboard__dump_entry(self, &self->lookup_table[i]); } int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names) { self->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!self->context) return -1; if (intset_init(&self->key_state, 0) < 0) goto key_state_failure; self->keymap = xkb_keymap_new_from_names(self->context, rule_names, 0); if (!self->keymap) goto keymap_failure; if (xkb_keymap_num_layouts(self->keymap) > 1) nvnc_log(NVNC_LOG_WARNING, "Multiple keyboard layouts have been specified, but only one is supported."); self->state = xkb_state_new(self->keymap); if (!self->state) goto state_failure; if (create_lookup_table(self) < 0) goto table_failure; // keyboard_dump_lookup_table(self); char* keymap_string = xkb_keymap_get_as_string(self->keymap, XKB_KEYMAP_FORMAT_TEXT_V1); if (!keymap_string) goto keymap_string_failure; size_t keymap_size = strlen(keymap_string) + 1; int keymap_fd = shm_alloc_fd(keymap_size); if (keymap_fd < 0) goto fd_failure; size_t written = 0; while (written < keymap_size) { ssize_t ret = write(keymap_fd, keymap_string + written, keymap_size - written); if (ret == -1 && errno == EINTR) continue; if (ret == -1) goto write_failure; written += ret; } free(keymap_string); zwp_virtual_keyboard_v1_keymap(self->virtual_keyboard, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, keymap_size); close(keymap_fd); return 0; write_failure: close(keymap_fd); fd_failure: free(keymap_string); keymap_string_failure: free(self->lookup_table); table_failure: xkb_state_unref(self->state); state_failure: xkb_keymap_unref(self->keymap); keymap_failure: intset_destroy(&self->key_state); key_state_failure: xkb_context_unref(self->context); return -1; } void keyboard_destroy(struct keyboard* self) { free(self->lookup_table); xkb_state_unref(self->state); xkb_keymap_unref(self->keymap); intset_destroy(&self->key_state); xkb_context_unref(self->context); } struct table_entry* keyboard_find_symbol(const struct keyboard* self, xkb_keysym_t symbol) { struct table_entry cmp = { .symbol = symbol }; struct table_entry* entry = bsearch(&cmp, self->lookup_table, self->lookup_table_length, sizeof(*self->lookup_table), compare_symbols2); if (!entry) return NULL; while (entry != self->lookup_table && (entry - 1)->symbol == symbol) --entry; return entry; } static void keyboard_send_mods(struct keyboard* self) { xkb_mod_mask_t depressed, latched, locked, group; depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED); latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED); locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED); group = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_EFFECTIVE); zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed, latched, locked, group); } static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code, bool is_pressed) { enum xkb_state_component comp, compmask; comp = xkb_state_update_key(self->state, code, is_pressed ? XKB_KEY_DOWN : XKB_KEY_UP); compmask = XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE; if (!(comp & compmask)) return; keyboard_send_mods(self); } static struct table_entry* match_level(struct keyboard* self, struct table_entry* entry) { xkb_keysym_t symbol = entry->symbol; while (true) { int level; level = xkb_state_key_get_level(self->state, entry->code, 0); if (entry->level == level) return entry; if (++entry >= &self->lookup_table[self->lookup_table_length] || entry->symbol != symbol) break; } return NULL; } static bool keyboard_symbol_is_mod(xkb_keysym_t symbol) { switch (symbol) { case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Control_L: case XKB_KEY_Caps_Lock: case XKB_KEY_Shift_Lock: case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: case XKB_KEY_ISO_Level5_Shift: case XKB_KEY_ISO_Level5_Lock: return true; } return false; } static void send_key(struct keyboard* self, xkb_keycode_t code, bool is_pressed) { zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, code - 8, is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED); } static void save_mods(struct keyboard* self, struct kb_mods* mods) { mods->depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED); mods->latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED); mods->locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED); } static void restore_mods(struct keyboard* self, struct kb_mods* mods) { xkb_state_update_mask(self->state, mods->depressed, mods->latched, mods->locked, XKB_STATE_MODS_DEPRESSED, XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED); } static void send_key_with_level(struct keyboard* self, xkb_keycode_t code, bool is_pressed, int level) { struct kb_mods save; save_mods(self, &save); xkb_mod_mask_t mods = 0; xkb_keymap_key_get_mods_for_level(self->keymap, code, 0, level, &mods, 1); xkb_state_update_mask(self->state, mods, 0, 0, XKB_STATE_MODS_DEPRESSED, XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED); keyboard_send_mods(self); nvnc_log(NVNC_LOG_DEBUG, "send key with level: old mods: %x, new mods: %x", save.latched | save.locked | save.depressed, mods); send_key(self, code, is_pressed); restore_mods(self, &save); keyboard_send_mods(self); } static bool update_key_state(struct keyboard* self, xkb_keycode_t code, bool is_pressed) { bool was_pressed = intset_is_set(&self->key_state, code); if (was_pressed == is_pressed) return false; if (is_pressed) intset_set(&self->key_state, code); else intset_clear(&self->key_state, code); return true; } void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed) { struct table_entry* entry = keyboard_find_symbol(self, symbol); if (!entry) { char name[256]; nvnc_log(NVNC_LOG_ERROR, "Failed to look up keyboard symbol: %s", get_symbol_name(symbol, name, sizeof(name))); return; } bool level_is_match = true; if (!keyboard_symbol_is_mod(symbol)) { struct table_entry* level_entry = match_level(self, entry); if (level_entry) entry = level_entry; else level_is_match = false; } #ifndef NDEBUG keyboard__dump_entry(self, entry); #endif if (!update_key_state(self, entry->code, is_pressed)) return; keyboard_apply_mods(self, entry->code, is_pressed); if (level_is_match) send_key(self, entry->code, is_pressed); else send_key_with_level(self, entry->code, is_pressed, entry->level); } void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code, bool is_pressed) { if (update_key_state(self, code, is_pressed)) { keyboard_apply_mods(self, code, is_pressed); send_key(self, code, is_pressed); } } wayvnc-0.8.0/src/main.c000066400000000000000000001502401456662133500147220ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wlr-screencopy-unstable-v1.h" #include "wlr-virtual-pointer-unstable-v1.h" #include "virtual-keyboard-unstable-v1.h" #include "xdg-output-unstable-v1.h" #include "wlr-output-power-management-unstable-v1.h" #include "wlr-output-management-unstable-v1.h" #include "linux-dmabuf-unstable-v1.h" #include "ext-transient-seat-v1.h" #include "screencopy.h" #include "data-control.h" #include "strlcpy.h" #include "output.h" #include "output-management.h" #include "pointer.h" #include "keyboard.h" #include "seat.h" #include "cfg.h" #include "transform-util.h" #include "usdt.h" #include "ctl-server.h" #include "util.h" #include "option-parser.h" #ifdef ENABLE_PAM #include "pam_auth.h" #endif #ifdef ENABLE_SCREENCOPY_DMABUF #include #include #endif #define DEFAULT_ADDRESS "127.0.0.1" #define DEFAULT_PORT 5900 #define XSTR(x) STR(x) #define STR(x) #x #define MAYBE_UNUSED __attribute__((unused)) struct wayvnc_client; enum socket_type { SOCKET_TYPE_TCP = 0, SOCKET_TYPE_UNIX, SOCKET_TYPE_WEBSOCKET, }; struct wayvnc { bool do_exit; struct wl_display* display; struct wl_registry* registry; struct aml_handler* wl_handler; struct wl_list outputs; struct wl_list seats; struct cfg cfg; struct zwp_virtual_keyboard_manager_v1* keyboard_manager; struct zwlr_virtual_pointer_manager_v1* pointer_manager; struct zwlr_data_control_manager_v1* data_control_manager; struct ext_transient_seat_manager_v1* transient_seat_manager; struct output* selected_output; struct seat* selected_seat; struct screencopy screencopy; struct aml_handler* wayland_handler; struct aml_signal* signal_handler; struct nvnc* nvnc; struct nvnc_display* nvnc_display; const char* kb_layout; const char* kb_variant; uint32_t damage_area_sum; uint32_t n_frames_captured; bool disable_input; bool use_transient_seat; int nr_clients; struct aml_ticker* performance_ticker; struct aml_timer* capture_retry_timer; struct ctl* ctl; bool is_initializing; bool start_detached; struct wayvnc_client* master_layout_client; }; struct wayvnc_client { struct wayvnc* server; struct nvnc_client* nvnc_client; struct seat* seat; struct ext_transient_seat_v1* transient_seat; unsigned id; struct pointer pointer; struct keyboard keyboard; struct data_control data_control; }; void wayvnc_exit(struct wayvnc* self); void on_capture_done(struct screencopy* sc); static void on_nvnc_client_new(struct nvnc_client* client); void switch_to_output(struct wayvnc*, struct output*); void switch_to_next_output(struct wayvnc*); void switch_to_prev_output(struct wayvnc*); static void client_init_seat(struct wayvnc_client* self); static void client_init_pointer(struct wayvnc_client* self); static void client_init_keyboard(struct wayvnc_client* self); static void client_init_data_control(struct wayvnc_client* self); static void client_detach_wayland(struct wayvnc_client* self); static int blank_screen(struct wayvnc* self); static bool wayland_attach(struct wayvnc* self, const char* display, const char* output); static void wayland_detach(struct wayvnc* self); struct wl_shm* wl_shm = NULL; struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf = NULL; struct gbm_device* gbm_device = NULL; struct zxdg_output_manager_v1* xdg_output_manager = NULL; struct zwlr_output_power_manager_v1* wlr_output_power_manager = NULL; static bool registry_add_input(void* data, struct wl_registry* registry, uint32_t id, const char* interface, uint32_t version) { struct wayvnc* self = data; if (self->disable_input) return false; if (strcmp(interface, wl_seat_interface.name) == 0) { struct wl_seat* wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, 7); if (!wl_seat) return true; struct seat* seat = seat_new(wl_seat, id); if (!seat) { wl_seat_release(wl_seat); return true; } wl_list_insert(&self->seats, &seat->link); return true; } if (strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0) { self->pointer_manager = wl_registry_bind(registry, id, &zwlr_virtual_pointer_manager_v1_interface, MIN(2, version)); return true; } if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) { self->keyboard_manager = wl_registry_bind(registry, id, &zwp_virtual_keyboard_manager_v1_interface, 1); return true; } if (strcmp(interface, zwlr_data_control_manager_v1_interface.name) == 0) { self->data_control_manager = wl_registry_bind(registry, id, &zwlr_data_control_manager_v1_interface, 2); return true; } if (strcmp(interface, ext_transient_seat_manager_v1_interface.name) == 0) { self->transient_seat_manager = wl_registry_bind(registry, id, &ext_transient_seat_manager_v1_interface, 1); return true; } return false; } static void registry_add(void* data, struct wl_registry* registry, uint32_t id, const char* interface, uint32_t version) { struct wayvnc* self = data; if (strcmp(interface, wl_output_interface.name) == 0) { nvnc_trace("Registering new output %u", id); struct wl_output* wl_output = wl_registry_bind(registry, id, &wl_output_interface, 3); if (!wl_output) return; struct output* output = output_new(wl_output, id); if (!output) return; wl_list_insert(&self->outputs, &output->link); if (!self->is_initializing) { wl_display_dispatch(self->display); wl_display_roundtrip(self->display); } return; } if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { nvnc_trace("Registering new xdg_output_manager"); xdg_output_manager = wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, 3); output_setup_wl_managers(&self->outputs); return; } if (strcmp(interface, zwlr_output_power_manager_v1_interface.name) == 0) { nvnc_trace("Registering new wlr_output_power_manager"); wlr_output_power_manager = wl_registry_bind(registry, id, &zwlr_output_power_manager_v1_interface, 1); output_setup_wl_managers(&self->outputs); return; } if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) { nvnc_trace("Registering new wlr_output_manager"); struct zwlr_output_manager_v1* wlr_output_manager = wl_registry_bind(registry, id, &zwlr_output_manager_v1_interface, 1); wlr_output_manager_setup(wlr_output_manager); return; } if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { self->screencopy.manager = wl_registry_bind(registry, id, &zwlr_screencopy_manager_v1_interface, MIN(3, version)); return; } if (strcmp(interface, wl_shm_interface.name) == 0) { wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); return; } if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) { zwp_linux_dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 3); return; } if (registry_add_input(data, registry, id, interface, version)) return; } static void disconnect_seat_clients(struct wayvnc* self, struct seat* seat) { struct nvnc_client* nvnc_client; for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client; nvnc_client = nvnc_client_next(nvnc_client)) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); assert(client); if (client->seat == seat) { nvnc_client_close(nvnc_client); } } } static void registry_remove(void* data, struct wl_registry* registry, uint32_t id) { struct wayvnc* self = data; struct output* out = output_find_by_id(&self->outputs, id); if (out) { if (out == self->selected_output) { nvnc_log(NVNC_LOG_WARNING, "Selected output %s went away", out->name); switch_to_prev_output(self); } else nvnc_log(NVNC_LOG_INFO, "Output %s went away", out->name); wl_list_remove(&out->link); output_destroy(out); if (out == self->selected_output) { if (self->start_detached) { nvnc_log(NVNC_LOG_WARNING, "No fallback outputs left. Detaching..."); wayland_detach(self); } else { nvnc_log(NVNC_LOG_ERROR, "No fallback outputs left. Exiting..."); wayvnc_exit(self); } } return; } struct seat* seat = seat_find_by_id(&self->seats, id); if (seat) { nvnc_log(NVNC_LOG_INFO, "Seat %s went away", seat->name); disconnect_seat_clients(self, seat); wl_list_remove(&seat->link); seat_destroy(seat); return; } } #ifdef ENABLE_SCREENCOPY_DMABUF static int find_render_node(char *node, size_t maxlen) { bool r = -1; drmDevice *devices[64]; int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); for (int i = 0; i < n; ++i) { drmDevice *dev = devices[i]; if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) continue; strlcpy(node, dev->nodes[DRM_NODE_RENDER], maxlen); r = 0; break; } drmFreeDevices(devices, n); return r; } static int init_render_node(int* fd) { char render_node[256]; if (find_render_node(render_node, sizeof(render_node)) < 0) return -1; *fd = open(render_node, O_RDWR); if (*fd < 0) return -1; gbm_device = gbm_create_device(*fd); if (!gbm_device) { close(*fd); return -1; } return 0; } #endif static void wayland_detach(struct wayvnc* self) { if (!self->display) return; aml_stop(aml_get_default(), self->wl_handler); aml_unref(self->wl_handler); self->wl_handler = NULL; // Screen blanking is required to release wl_shm of linux_dmabuf. if (self->nvnc) blank_screen(self); if (self->nvnc) { struct nvnc_client* nvnc_client; for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client; nvnc_client = nvnc_client_next(nvnc_client)) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); client_detach_wayland(client); } } self->selected_output = NULL; self->screencopy.wl_output = NULL; output_list_destroy(&self->outputs); seat_list_destroy(&self->seats); if (zwp_linux_dmabuf) zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf); zwp_linux_dmabuf = NULL; if (self->screencopy.manager) { screencopy_stop(&self->screencopy); screencopy_destroy(&self->screencopy); zwlr_screencopy_manager_v1_destroy(self->screencopy.manager); } self->screencopy.manager = NULL; if (xdg_output_manager) zxdg_output_manager_v1_destroy(xdg_output_manager); xdg_output_manager = NULL; if (wlr_output_power_manager) zwlr_output_power_manager_v1_destroy(wlr_output_power_manager); wlr_output_power_manager = NULL; wlr_output_manager_destroy(); wl_shm_destroy(wl_shm); wl_shm = NULL; if (self->keyboard_manager) zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager); self->keyboard_manager = NULL; if (self->pointer_manager) zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager); self->pointer_manager = NULL; if (self->data_control_manager) zwlr_data_control_manager_v1_destroy(self->data_control_manager); self->data_control_manager = NULL; if (self->performance_ticker) { aml_stop(aml_get_default(), self->performance_ticker); aml_unref(self->performance_ticker); } self->performance_ticker = NULL; if (self->capture_retry_timer) aml_unref(self->capture_retry_timer); self->capture_retry_timer = NULL; wl_registry_destroy(self->registry); self->registry = NULL; wl_display_disconnect(self->display); self->display = NULL; if (self->ctl) ctl_server_event_detached(self->ctl); } void wayvnc_destroy(struct wayvnc* self) { cfg_destroy(&self->cfg); wayland_detach(self); } void on_wayland_event(void* obj) { struct wayvnc* self = aml_get_userdata(obj); int rc MAYBE_UNUSED = wl_display_prepare_read(self->display); assert(rc == 0); if (wl_display_read_events(self->display) < 0) { if (errno == EPIPE || errno == ECONNRESET) { nvnc_log(NVNC_LOG_ERROR, "Compositor has gone away. Exiting..."); if (self->start_detached) wayland_detach(self); else wayvnc_exit(self); return; } else { nvnc_log(NVNC_LOG_ERROR, "Failed to read wayland events: %m"); } } if (wl_display_dispatch_pending(self->display) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to dispatch pending"); wayland_detach(self); // TODO: Re-attach } } static int init_wayland(struct wayvnc* self, const char* display) { self->is_initializing = true; static const struct wl_registry_listener registry_listener = { .global = registry_add, .global_remove = registry_remove, }; self->display = wl_display_connect(display); if (!self->display) { const char* display_name = display ? display: getenv("WAYLAND_DISPLAY"); if (!display_name) { nvnc_log(NVNC_LOG_ERROR, "WAYLAND_DISPLAY is not set in the environment"); } else { nvnc_log(NVNC_LOG_ERROR, "Failed to connect to WAYLAND_DISPLAY=\"%s\"", display_name); nvnc_log(NVNC_LOG_ERROR, "Ensure wayland is running with that display name"); } return -1; } wl_list_init(&self->outputs); wl_list_init(&self->seats); self->registry = wl_display_get_registry(self->display); if (!self->registry) { nvnc_log(NVNC_LOG_ERROR, "Could not locate the wayland compositor object registry"); goto failure; } wl_registry_add_listener(self->registry, ®istry_listener, self); wl_display_dispatch(self->display); wl_display_roundtrip(self->display); self->is_initializing = false; if (!self->pointer_manager && !self->disable_input) { nvnc_log(NVNC_LOG_ERROR, "Virtual Pointer protocol not supported by compositor."); nvnc_log(NVNC_LOG_ERROR, "wayvnc may still work if started with --disable-input."); goto failure; } if (!self->keyboard_manager && !self->disable_input) { nvnc_log(NVNC_LOG_ERROR, "Virtual Keyboard protocol not supported by compositor."); nvnc_log(NVNC_LOG_ERROR, "wayvnc may still work if started with --disable-input."); goto failure; } if (!self->screencopy.manager) { nvnc_log(NVNC_LOG_ERROR, "Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page."); goto failure; } if (!self->transient_seat_manager && self->use_transient_seat) { nvnc_log(NVNC_LOG_ERROR, "Transient seat protocol not supported by compositor"); goto failure; } self->screencopy.on_done = on_capture_done; self->screencopy.userdata = self; self->wl_handler = aml_handler_new(wl_display_get_fd(self->display), on_wayland_event, self, NULL); if (!self->wl_handler) goto failure; int rc = aml_start(aml_get_default(), self->wl_handler); if (rc < 0) goto handler_failure; return 0; failure: wl_display_disconnect(self->display); self->display = NULL; handler_failure: if (self->wl_handler) aml_unref(self->wl_handler); self->wl_handler = NULL; return -1; } void wayvnc_exit(struct wayvnc* self) { self->do_exit = true; } void on_signal(void* obj) { nvnc_log(NVNC_LOG_INFO, "Received termination signal."); struct wayvnc* self = aml_get_userdata(obj); wayvnc_exit(self); } struct cmd_response* on_output_cycle(struct ctl* ctl, enum output_cycle_direction direction) { struct wayvnc* self = ctl_server_userdata(ctl); nvnc_log(NVNC_LOG_INFO, "ctl command: Rotating to %s output", direction == OUTPUT_CYCLE_FORWARD ? "next" : "previous"); struct output* next = output_cycle(&self->outputs, self->selected_output, direction); switch_to_output(self, next); return cmd_ok(); } struct cmd_response* on_output_switch(struct ctl* ctl, const char* output_name) { nvnc_log(NVNC_LOG_INFO, "ctl command: Switch to output \"%s\"", output_name); struct wayvnc* self = ctl_server_userdata(ctl); if (!output_name || output_name[0] == '\0') return cmd_failed("Output name is required"); struct output* output = output_find_by_name(&self->outputs, output_name); if (!output) { return cmd_failed("No such output \"%s\"", output_name); } switch_to_output(self, output); return cmd_ok(); } static struct ctl_server_client *client_next(struct ctl* ctl, struct ctl_server_client *prev) { struct wayvnc* self = ctl_server_userdata(ctl); struct nvnc_client* vnc_prev = (struct nvnc_client*)prev; return prev ? (struct ctl_server_client*)nvnc_client_next(vnc_prev) : (struct ctl_server_client*)nvnc_client_first(self->nvnc); } static void compose_client_info(const struct wayvnc_client* client, struct ctl_server_client_info* info) { info->id = client->id; socklen_t addrlen = sizeof(info->address); nvnc_client_get_address(client->nvnc_client, (struct sockaddr*)&info->address, &addrlen); info->username = nvnc_client_get_auth_username(client->nvnc_client); info->seat = client->seat ? client->seat->name : NULL; } static void client_info(const struct ctl_server_client* client_handle, struct ctl_server_client_info* info) { const struct nvnc_client *vnc_client = (const struct nvnc_client*)client_handle; const struct wayvnc_client *client = nvnc_get_userdata(vnc_client); compose_client_info(client, info); } static int get_output_list(struct ctl* ctl, struct ctl_server_output** outputs) { struct wayvnc* self = ctl_server_userdata(ctl); int n = wl_list_length(&self->outputs); if (n == 0) { *outputs = NULL; return 0; } *outputs = calloc(n, sizeof(**outputs)); struct output* output; struct ctl_server_output* item = *outputs; wl_list_for_each(output, &self->outputs, link) { strlcpy(item->name, output->name, sizeof(item->name)); strlcpy(item->description, output->description, sizeof(item->description)); item->height = output->height; item->width = output->width; item->captured = (output->id == self->selected_output->id); strlcpy(item->power, output_power_state_name(output->power), sizeof(item->power)); item++; } return n; } static struct cmd_response* on_disconnect_client(struct ctl* ctl, const char* id_string) { char* endptr; unsigned int id = strtoul(id_string, &endptr, 0); if (!*id_string || *endptr) return cmd_failed("Invalid client ID \"%s\"", id_string); struct wayvnc* self = ctl_server_userdata(ctl); for (struct nvnc_client* nvnc_client = nvnc_client_first(self->nvnc); nvnc_client; nvnc_client = nvnc_client_next(nvnc_client)) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); if (client->id == id) { nvnc_log(NVNC_LOG_WARNING, "Disconnecting client %d via control socket command", client->id); nvnc_client_close(nvnc_client); return cmd_ok(); } } return cmd_failed("No such client with ID \"%s\"", id_string); } static struct cmd_response* on_wayvnc_exit(struct ctl* ctl) { struct wayvnc* self = ctl_server_userdata(ctl); nvnc_log(NVNC_LOG_WARNING, "Shutting down via control socket command"); wayvnc_exit(self); return cmd_ok(); } int init_main_loop(struct wayvnc* self) { struct aml* loop = aml_get_default(); struct aml_signal* sig; sig = aml_signal_new(SIGINT, on_signal, self, NULL); if (!sig) return -1; int rc = aml_start(loop, sig); aml_unref(sig); if (rc < 0) return -1; return 0; } static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y, enum nvnc_button_mask button_mask) { struct wayvnc_client* wv_client = nvnc_get_userdata(client); struct wayvnc* wayvnc = wv_client->server; if (!wv_client->pointer.pointer) { return; } uint32_t xfx = 0, xfy = 0; output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy); pointer_set(&wv_client->pointer, xfx, xfy, button_mask); } static void on_key_event(struct nvnc_client* client, uint32_t symbol, bool is_pressed) { struct wayvnc_client* wv_client = nvnc_get_userdata(client); if (!wv_client->keyboard.virtual_keyboard) { return; } keyboard_feed(&wv_client->keyboard, symbol, is_pressed); } static void on_key_code_event(struct nvnc_client* client, uint32_t code, bool is_pressed) { struct wayvnc_client* wv_client = nvnc_get_userdata(client); if (!wv_client->keyboard.virtual_keyboard) { return; } keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed); } static void on_client_cut_text(struct nvnc_client* nvnc_client, const char* text, uint32_t len) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); if (client->data_control.manager) { data_control_to_clipboard(&client->data_control, text, len); } } static bool on_client_resize(struct nvnc_client* nvnc_client, const struct nvnc_desktop_layout* layout) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); struct wayvnc* self = client->server; uint16_t width = nvnc_desktop_layout_get_width(layout); uint16_t height = nvnc_desktop_layout_get_height(layout); struct output* output = client->server->selected_output; if (output == NULL) return false; if (self->master_layout_client && self->master_layout_client != client) return false; self->master_layout_client = client; nvnc_log(NVNC_LOG_DEBUG, "Client resolution changed: %ux%u, capturing output %s which is headless: %s", width, height, output->name, output->is_headless ? "yes" : "no"); return wlr_output_manager_resize_output(output, width, height); } bool on_auth(const char* username, const char* password, void* ud) { struct wayvnc* self = ud; #ifdef ENABLE_PAM if (self->cfg.enable_pam) return pam_auth(username, password); #endif if (strcmp(username, self->cfg.username) != 0) return false; if (strcmp(password, self->cfg.password) != 0) return false; return true; } static struct nvnc_fb* create_placeholder_buffer(uint16_t width, uint16_t height) { uint16_t stride = width; struct nvnc_fb* fb = nvnc_fb_new(width, height, DRM_FORMAT_XRGB8888, stride); if (!fb) return NULL; size_t size = nvnc_fb_get_pixel_size(fb) * height * stride; memset(nvnc_fb_get_addr(fb), 0x60, size); return fb; } static int blank_screen(struct wayvnc* self) { int width = 1280; int height = 720; if (self->selected_output) { width = output_get_transformed_width(self->selected_output); height = output_get_transformed_height(self->selected_output); } struct nvnc_fb* placeholder_fb = create_placeholder_buffer(width, height); if (!placeholder_fb) { nvnc_log(NVNC_LOG_ERROR, "Failed to allocate a placeholder buffer"); return -1; } struct pixman_region16 damage; pixman_region_init_rect(&damage, 0, 0, nvnc_fb_get_width(placeholder_fb), nvnc_fb_get_height(placeholder_fb)); nvnc_display_feed_buffer(self->nvnc_display, placeholder_fb, &damage); pixman_region_fini(&damage); nvnc_fb_unref(placeholder_fb); return 0; } static char* get_cfg_path(const struct cfg* cfg, char* dst, const char* src) { if (!cfg->use_relative_paths || src[0] == '/') { strlcpy(dst, src, PATH_MAX); return dst; } snprintf(dst, PATH_MAX, "%s/%s", cfg->directory, src); return dst; } static int init_nvnc(struct wayvnc* self, const char* addr, uint16_t port, enum socket_type socket_type) { switch (socket_type) { case SOCKET_TYPE_TCP: self->nvnc = nvnc_open(addr, port); break; case SOCKET_TYPE_UNIX: self->nvnc = nvnc_open_unix(addr); break; case SOCKET_TYPE_WEBSOCKET: self->nvnc = nvnc_open_websocket(addr, port); break; default: abort(); } if (!self->nvnc) { nvnc_log(NVNC_LOG_ERROR, "Failed to bind to address. Add -Ldebug to the argument list for more info."); return -1; } if (socket_type == SOCKET_TYPE_UNIX) nvnc_log(NVNC_LOG_INFO, "Listening for connections on %s", addr); else nvnc_log(NVNC_LOG_INFO, "Listening for connections on %s:%d", addr, port); self->nvnc_display = nvnc_display_new(0, 0); if (!self->nvnc_display) goto failure; nvnc_add_display(self->nvnc, self->nvnc_display); nvnc_set_userdata(self->nvnc, self, NULL); nvnc_set_name(self->nvnc, "WayVNC"); nvnc_set_desktop_layout_fn(self->nvnc, on_client_resize); enum nvnc_auth_flags auth_flags = 0; if (self->cfg.enable_auth) { auth_flags |= NVNC_AUTH_REQUIRE_AUTH; } if (!self->cfg.relax_encryption) { auth_flags |= NVNC_AUTH_REQUIRE_ENCRYPTION; } if (self->cfg.enable_auth) { if (nvnc_enable_auth(self->nvnc, auth_flags, on_auth, self) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to enable authentication"); goto failure; } if (self->cfg.rsa_private_key_file) { char tmp[PATH_MAX]; const char* key_file = get_cfg_path(&self->cfg, tmp, self->cfg.rsa_private_key_file); if (nvnc_set_rsa_creds(self->nvnc, key_file) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to load RSA credentials"); goto failure; } } if (self->cfg.private_key_file) { char key_file[PATH_MAX]; char cert_file[PATH_MAX]; get_cfg_path(&self->cfg, key_file, self->cfg.private_key_file); get_cfg_path(&self->cfg, cert_file, self->cfg.certificate_file); int r = nvnc_set_tls_creds(self->nvnc, key_file, cert_file); if (r < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to enable TLS authentication"); goto failure; } } } nvnc_set_pointer_fn(self->nvnc, on_pointer_event); nvnc_set_key_fn(self->nvnc, on_key_event); nvnc_set_key_code_fn(self->nvnc, on_key_code_event); nvnc_set_new_client_fn(self->nvnc, on_nvnc_client_new); nvnc_set_cut_text_fn(self->nvnc, on_client_cut_text); if (blank_screen(self) != 0) goto failure; return 0; failure: nvnc_close(self->nvnc); return -1; } int wayvnc_start_capture(struct wayvnc* self) { int rc = screencopy_start(&self->screencopy); if (rc < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to start capture. Exiting..."); wayvnc_exit(self); } return rc; } int wayvnc_start_capture_immediate(struct wayvnc* self) { if (self->capture_retry_timer) return 0; if (self->selected_output->power == OUTPUT_POWER_OFF) { nvnc_log(NVNC_LOG_WARNING, "Selected output is in powersaving mode. Delaying capture until it turns on."); if (output_set_power_state(self->selected_output, OUTPUT_POWER_ON) == 0) nvnc_log(NVNC_LOG_WARNING, "Requested power ON."); return 0; } int rc = screencopy_start_immediate(&self->screencopy); if (rc < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to start capture. Exiting..."); wayvnc_exit(self); } return rc; } static void on_capture_restart_timer(void* obj) { struct wayvnc* self = aml_get_userdata(obj); aml_unref(self->capture_retry_timer); self->capture_retry_timer = NULL; wayvnc_start_capture_immediate(self); } static void wayvnc_restart_capture(struct wayvnc* self) { if (self->capture_retry_timer) return; int timeout = 100000; self->capture_retry_timer = aml_timer_new(timeout, on_capture_restart_timer, self, NULL); aml_start(aml_get_default(), self->capture_retry_timer); } // TODO: Handle transform change too void on_output_dimension_change(struct output* output) { struct wayvnc* self = output->userdata; assert(self->selected_output == output); if (self->nr_clients == 0) return; nvnc_log(NVNC_LOG_DEBUG, "Output dimensions changed. Restarting frame capturer..."); screencopy_stop(&self->screencopy); wayvnc_start_capture_immediate(self); } static void on_output_power_change(struct output* output) { nvnc_trace("Output %s power state changed to %s", output->name, output_power_state_name(output->power)); struct wayvnc* self = output->userdata; if (self->selected_output != output || self->nr_clients == 0) return; switch (output->power) { case OUTPUT_POWER_ON: nvnc_log(NVNC_LOG_WARNING, "Output is now on. Restarting frame capture"); wayvnc_start_capture_immediate(self); break; case OUTPUT_POWER_OFF: nvnc_log(NVNC_LOG_WARNING, "Output is now off. Pausing frame capture"); screencopy_stop(&self->screencopy); blank_screen(self); break; default: break; } } static uint32_t calculate_region_area(struct pixman_region16* region) { uint32_t area = 0; int n_rects = 0; struct pixman_box16* rects = pixman_region_rectangles(region, &n_rects); for (int i = 0; i < n_rects; ++i) { int width = rects[i].x2 - rects[i].x1; int height = rects[i].y2 - rects[i].y1; area += width * height; } return area; } void wayvnc_process_frame(struct wayvnc* self) { struct wv_buffer* buffer = self->screencopy.back; self->screencopy.back = NULL; self->n_frames_captured++; self->damage_area_sum += calculate_region_area(&buffer->damage); struct pixman_region16 damage; pixman_region_init(&damage); enum wl_output_transform output_transform, buffer_transform; output_transform = self->selected_output->transform; if (buffer->y_inverted) { buffer_transform = wv_output_transform_compose(output_transform, WL_OUTPUT_TRANSFORM_FLIPPED_180); wv_region_transform(&damage, &buffer->damage, WL_OUTPUT_TRANSFORM_FLIPPED_180, buffer->width, buffer->height); } else { buffer_transform = output_transform; pixman_region_copy(&damage, &buffer->damage); } nvnc_fb_set_transform(buffer->nvnc_fb, (enum nvnc_transform)buffer_transform); pixman_region_intersect_rect(&damage, &damage, 0, 0, buffer->width, buffer->height); nvnc_display_feed_buffer(self->nvnc_display, buffer->nvnc_fb, &damage); pixman_region_fini(&damage); wayvnc_start_capture(self); } void on_capture_done(struct screencopy* sc) { struct wayvnc* self = sc->userdata; switch (sc->status) { case SCREENCOPY_STOPPED: break; case SCREENCOPY_IN_PROGRESS: break; case SCREENCOPY_FATAL: nvnc_log(NVNC_LOG_ERROR, "Fatal error while capturing. Exiting..."); wayvnc_exit(self); break; case SCREENCOPY_FAILED: wayvnc_restart_capture(self); break; case SCREENCOPY_DONE: wayvnc_process_frame(self); break; } } int wayvnc_usage(struct option_parser* parser, FILE* stream, int rc) { fprintf(stream, "Usage: wayvnc"); option_parser_print_usage(parser, stream); fprintf(stream, "\n"); option_parser_print_cmd_summary("Starts a VNC server for $WAYLAND_DISPLAY", stream); if (option_parser_print_arguments(parser, stream)) fprintf(stream, "\n"); option_parser_print_options(parser, stream); fprintf(stream, "\n"); return rc; } int check_cfg_sanity(struct cfg* cfg) { if (cfg->enable_auth) { int rc = 0; if (!nvnc_has_auth()) { nvnc_log(NVNC_LOG_ERROR, "Authentication can't be enabled because it was not selected during build"); rc = -1; } if (!!cfg->certificate_file != !!cfg->private_key_file) { nvnc_log(NVNC_LOG_ERROR, "Need both certificate_file and private_key_file for TLS"); rc = -1; } if (!cfg->username && !cfg->enable_pam) { nvnc_log(NVNC_LOG_ERROR, "Authentication enabled, but missing username"); rc = -1; } if (!cfg->password && !cfg->enable_pam) { nvnc_log(NVNC_LOG_ERROR, "Authentication enabled, but missing password"); rc = -1; } if (cfg->relax_encryption) { nvnc_log(NVNC_LOG_WARNING, "Authentication enabled with relaxed encryption; not all sessions are guaranteed to be encrypted"); } return rc; } return 0; } static void on_perf_tick(void* obj) { struct wayvnc* self = aml_get_userdata(obj); double total_area = self->selected_output->width * self->selected_output->height; double area_avg = (double)self->damage_area_sum / (double)self->n_frames_captured; double relative_area_avg = 100.0 * area_avg / total_area; nvnc_log(NVNC_LOG_INFO, "Frames captured: %"PRIu32", average reported frame damage: %.1f %%", self->n_frames_captured, relative_area_avg); self->n_frames_captured = 0; self->damage_area_sum = 0; } static void start_performance_ticker(struct wayvnc* self) { if (!self->performance_ticker) return; aml_start(aml_get_default(), self->performance_ticker); } static void stop_performance_ticker(struct wayvnc* self) { if (!self->performance_ticker) return; aml_stop(aml_get_default(), self->performance_ticker); } static void client_init_wayland(struct wayvnc_client* self) { client_init_seat(self); client_init_keyboard(self); client_init_pointer(self); client_init_data_control(self); } static void client_detach_wayland(struct wayvnc_client* self) { self->seat = NULL; if (self->keyboard.virtual_keyboard) { zwp_virtual_keyboard_v1_destroy( self->keyboard.virtual_keyboard); keyboard_destroy(&self->keyboard); } self->keyboard.virtual_keyboard = NULL; if (self->pointer.pointer) pointer_destroy(&self->pointer); self->pointer.pointer = NULL; if (self->data_control.manager) data_control_destroy(&self->data_control); self->data_control.manager = NULL; } static unsigned next_client_id = 1; static struct wayvnc_client* client_create(struct wayvnc* wayvnc, struct nvnc_client* nvnc_client) { struct wayvnc_client* self = calloc(1, sizeof(*self)); if (!self) return NULL; self->server = wayvnc; self->nvnc_client = nvnc_client; self->id = next_client_id++; if (wayvnc->display) { client_init_wayland(self); } return self; } static void client_destroy(void* obj) { struct wayvnc_client* self = obj; struct nvnc* nvnc = nvnc_client_get_server(self->nvnc_client); struct wayvnc* wayvnc = nvnc_get_userdata(nvnc); if (self == wayvnc->master_layout_client) wayvnc->master_layout_client = NULL; if (self->transient_seat) ext_transient_seat_v1_destroy(self->transient_seat); if (self->seat) self->seat->occupancy--; wayvnc->nr_clients--; nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d", wayvnc->nr_clients); if (wayvnc->ctl) { struct ctl_server_client_info info = {}; compose_client_info(self, &info); ctl_server_event_disconnected(wayvnc->ctl, &info, wayvnc->nr_clients); } if (wayvnc->nr_clients == 0 && wayvnc->display) { nvnc_log(NVNC_LOG_INFO, "Stopping screen capture"); screencopy_stop(&wayvnc->screencopy); stop_performance_ticker(wayvnc); } if (self->keyboard.virtual_keyboard) { zwp_virtual_keyboard_v1_destroy( self->keyboard.virtual_keyboard); keyboard_destroy(&self->keyboard); } if (self->pointer.pointer) pointer_destroy(&self->pointer); if (self->data_control.manager) data_control_destroy(&self->data_control); free(self); } static void handle_first_client(struct wayvnc* self) { nvnc_log(NVNC_LOG_INFO, "Starting screen capture"); start_performance_ticker(self); wayvnc_start_capture_immediate(self); } static void on_nvnc_client_new(struct nvnc_client* client) { struct nvnc* nvnc = nvnc_client_get_server(client); struct wayvnc* self = nvnc_get_userdata(nvnc); struct wayvnc_client* wayvnc_client = client_create(self, client); assert(wayvnc_client); nvnc_set_userdata(client, wayvnc_client, client_destroy); if (self->nr_clients == 0 && self->display) { handle_first_client(self); } self->nr_clients++; nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d", self->nr_clients); struct ctl_server_client_info info = {}; compose_client_info(wayvnc_client, &info); ctl_server_event_connected(self->ctl, &info, self->nr_clients); } void parse_keyboard_option(struct wayvnc* self, const char* arg) { // Find optional variant, separated by - char* index = strchr(arg, '-'); if (index != NULL) { self->kb_variant = index + 1; // layout needs to be 0-terminated, replace the - by 0 *index = 0; } self->kb_layout = arg; } static void client_init_pointer(struct wayvnc_client* self) { struct wayvnc* wayvnc = self->server; if (!wayvnc->pointer_manager) return; self->pointer.vnc = self->server->nvnc; self->pointer.output = self->server->selected_output; if (self->pointer.pointer) pointer_destroy(&self->pointer); int pointer_manager_version = zwlr_virtual_pointer_manager_v1_get_version(wayvnc->pointer_manager); self->pointer.pointer = pointer_manager_version >= 2 ? zwlr_virtual_pointer_manager_v1_create_virtual_pointer_with_output( wayvnc->pointer_manager, self->seat->wl_seat, wayvnc->selected_output->wl_output) : zwlr_virtual_pointer_manager_v1_create_virtual_pointer( wayvnc->pointer_manager, self->seat->wl_seat); if (pointer_init(&self->pointer) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to initialise pointer"); } } static void handle_transient_seat_ready(void* data, struct ext_transient_seat_v1* transient_seat, uint32_t global_name) { (void)transient_seat; struct wayvnc_client* client = data; struct wayvnc* wayvnc = client->server; struct seat* seat = seat_find_by_id(&wayvnc->seats, global_name); assert(seat); client->seat = seat; } static void handle_transient_seat_denied(void* data, struct ext_transient_seat_v1* transient_seat) { (void)data; (void)transient_seat; // TODO: Something more graceful perhaps? nvnc_log(NVNC_LOG_PANIC, "Transient seat denied"); } static void client_init_transient_seat(struct wayvnc_client* self) { struct wayvnc* wayvnc = self->server; self->transient_seat = ext_transient_seat_manager_v1_create(wayvnc->transient_seat_manager); static const struct ext_transient_seat_v1_listener listener = { .ready = handle_transient_seat_ready, .denied = handle_transient_seat_denied, }; ext_transient_seat_v1_add_listener(self->transient_seat, &listener, self); // TODO: Make this asynchronous wl_display_roundtrip(wayvnc->display); assert(self->seat); } static void client_init_seat(struct wayvnc_client* self) { struct wayvnc* wayvnc = self->server; if (wayvnc->disable_input) return; if (wayvnc->selected_seat) { self->seat = wayvnc->selected_seat; } else if (wayvnc->use_transient_seat) { client_init_transient_seat(self); } else { self->seat = seat_find_unoccupied(&wayvnc->seats); if (!self->seat) { self->seat = seat_first(&wayvnc->seats); } } if (self->seat) self->seat->occupancy++; } static void client_init_keyboard(struct wayvnc_client* self) { struct wayvnc* wayvnc = self->server; if (!wayvnc->keyboard_manager) return; self->keyboard.virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( wayvnc->keyboard_manager, self->seat->wl_seat); struct xkb_rule_names rule_names = { .rules = wayvnc->cfg.xkb_rules, .layout = wayvnc->kb_layout ? wayvnc->kb_layout : wayvnc->cfg.xkb_layout, .model = wayvnc->cfg.xkb_model ? wayvnc->cfg.xkb_model : "pc105", .variant = wayvnc->kb_variant ? wayvnc->kb_variant : wayvnc->cfg.xkb_variant, .options = wayvnc->cfg.xkb_options, }; if (keyboard_init(&self->keyboard, &rule_names) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to initialise keyboard"); } } static void reinitialise_pointers(struct wayvnc* self) { struct nvnc_client* c; for (c = nvnc_client_first(self->nvnc); c; c = nvnc_client_next(c)) { struct wayvnc_client* client = nvnc_get_userdata(c); client_init_pointer(client); } } static void client_init_data_control(struct wayvnc_client* self) { struct wayvnc* wayvnc = self->server; if (!wayvnc->data_control_manager) return; self->data_control.manager = wayvnc->data_control_manager; data_control_init(&self->data_control, wayvnc->display, wayvnc->nvnc, self->seat->wl_seat); } void log_selected_output(struct wayvnc* self) { nvnc_log(NVNC_LOG_INFO, "Capturing output %s", self->selected_output->name); struct output* output; wl_list_for_each(output, &self->outputs, link) { bool this_output = (output->id == self->selected_output->id); nvnc_log(NVNC_LOG_INFO, "%s %s %dx%d+%dx%d Power:%s", this_output ? ">>" : "--", output->description, output->width, output->height, output->x, output->y, output_power_state_name(output->power)); } } void set_selected_output(struct wayvnc* self, struct output* output) { if (self->selected_output) { self->selected_output->on_dimension_change = NULL; } self->selected_output = output; self->screencopy.wl_output = output->wl_output; output->on_dimension_change = on_output_dimension_change; output->on_power_change = on_output_power_change; output->userdata = self; if (self->ctl) ctl_server_event_capture_changed(self->ctl, output->name); log_selected_output(self); } void switch_to_output(struct wayvnc* self, struct output* output) { if (self->selected_output == output) { nvnc_log(NVNC_LOG_INFO, "Already selected output %s", output->name); return; } screencopy_stop(&self->screencopy); set_selected_output(self, output); reinitialise_pointers(self); if (self->nr_clients > 0) wayvnc_start_capture_immediate(self); } void switch_to_next_output(struct wayvnc* self) { nvnc_log(NVNC_LOG_INFO, "Rotating to next output"); struct output* next = output_cycle(&self->outputs, self->selected_output, OUTPUT_CYCLE_FORWARD); switch_to_output(self, next); } void switch_to_prev_output(struct wayvnc* self) { nvnc_log(NVNC_LOG_INFO, "Rotating to previous output"); struct output* prev = output_cycle(&self->outputs, self->selected_output, OUTPUT_CYCLE_REVERSE); switch_to_output(self, prev); } static char intercepted_error[256]; static void intercept_cmd_error(const struct nvnc_log_data* meta, const char* message) { if (meta->level != NVNC_LOG_ERROR) { nvnc_default_logger(meta, message); return; } struct nvnc_log_data meta_override = *meta; meta_override.level = NVNC_LOG_DEBUG; nvnc_default_logger(&meta_override, message); size_t len = strlen(intercepted_error); if (len != 0 && len < sizeof(intercepted_error) - 2) intercepted_error[len++] = '\n'; strlcpy(intercepted_error + len, message, sizeof(intercepted_error) - len); } static struct cmd_response* on_attach(struct ctl* ctl, const char* display) { struct wayvnc* self = ctl_server_userdata(ctl); assert(self); memset(intercepted_error, 0, sizeof(intercepted_error)); nvnc_set_log_fn_thread_local(intercept_cmd_error); // TODO: Add optional output argument bool ok = wayland_attach(self, display, NULL); nvnc_set_log_fn_thread_local(NULL); return ok ? cmd_ok() : cmd_failed("%s", intercepted_error); } static bool wayland_attach(struct wayvnc* self, const char* display, const char* output) { if (self->display) { wayland_detach(self); } nvnc_log(NVNC_LOG_DEBUG, "Attaching to %s", display); if (init_wayland(self, display) < 0) { return false; } struct output* out; if (output) { out = output_find_by_name(&self->outputs, output); if (!out) { nvnc_log(NVNC_LOG_ERROR, "No such output: %s", output); wayland_detach(self); return false; } } else { out = output_first(&self->outputs); if (!out) { nvnc_log(NVNC_LOG_ERROR, "No output available"); wayland_detach(self); return false; } } screencopy_init(&self->screencopy); if (!self->screencopy.manager) { nvnc_log(NVNC_LOG_ERROR, "Attached display does not implement wlr-screencopy-v1"); wayland_detach(self); return false; } set_selected_output(self, out); struct nvnc_client* nvnc_client; for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client; nvnc_client = nvnc_client_next(nvnc_client)) { struct wayvnc_client* client = nvnc_get_userdata(nvnc_client); client_init_wayland(client); } nvnc_log(NVNC_LOG_INFO, "Attached to %s", display); if (self->nr_clients > 0) { handle_first_client(self); } return true; } static struct cmd_response* on_detach(struct ctl* ctl) { struct wayvnc* self = ctl_server_userdata(ctl); assert(self); if (!self->display) { return cmd_failed("Not attached!"); } wayland_detach(self); nvnc_log(NVNC_LOG_INFO, "Detached from wayland server"); return cmd_ok(); } static int log_level_from_string(const char* str) { if (0 == strcmp(str, "quiet")) return NVNC_LOG_PANIC; if (0 == strcmp(str, "error")) return NVNC_LOG_ERROR; if (0 == strcmp(str, "warning")) return NVNC_LOG_WARNING; if (0 == strcmp(str, "info")) return NVNC_LOG_INFO; if (0 == strcmp(str, "debug")) return NVNC_LOG_DEBUG; if (0 == strcmp(str, "trace")) return NVNC_LOG_TRACE; return -1; } int show_version(void) { printf("wayvnc: %s\n", wayvnc_version); printf("neatvnc: %s\n", nvnc_version); printf("aml: %s\n", aml_version); return 0; } int main(int argc, char* argv[]) { struct wayvnc self = { 0 }; const char* cfg_file = NULL; bool enable_gpu_features = false; const char* address = NULL; int port = 0; bool use_unix_socket = false; bool use_websocket = false; bool start_detached = false; const char* output_name = NULL; const char* seat_name = NULL; const char* socket_path = NULL; const char* keyboard_options = NULL; bool overlay_cursor = false; bool show_performance = false; int max_rate = 30; bool disable_input = false; bool use_transient_seat = false; int drm_fd MAYBE_UNUSED = -1; int log_level = NVNC_LOG_WARNING; static const struct wv_option opts[] = { { .positional = "address", .help = "The IP address or unix socket path to listen on.", .default_ = DEFAULT_ADDRESS}, { .positional = "port", .help = "The TCP port to listen on.", .default_ = XSTR(DEFAULT_PORT)}, { 'C', "config", "", "Select a config file." }, { 'g', "gpu", NULL, "Enable features that need GPU." }, { 'o', "output", "", "Select output to capture." }, { 'k', "keyboard", "[-]", "Select keyboard layout with an optional variant." }, { 's', "seat", "", "Select seat by name." }, { 'S', "socket", "", "Control socket path." }, { 't', "transient-seat", NULL, "Use transient seat." }, { 'r', "render-cursor", NULL, "Enable overlay cursor rendering." }, { 'f', "max-fps", "", "Set rate limit.", .default_ = "30" }, { 'p', "performance", NULL, "Show performance counters." }, { 'u', "unix-socket", NULL, "Create unix domain socket." }, { 'd', "disable-input", NULL, "Disable all remote input." }, { 'D', "detached", NULL, "Start detached from a compositor." }, { 'V', "version", NULL, "Show version info." }, { 'v', "verbose", NULL, "Be more verbose. Same as setting --log-level=info" }, { 'w', "websocket", NULL, "Create a websocket." }, { 'L', "log-level", "", "Set log level. The levels are: error, warning, info, debug trace and quiet.", .default_ = "warning" }, { 'h', "help", NULL, "Get help (this text)." }, {} }; struct option_parser option_parser; option_parser_init(&option_parser, opts); if (option_parser_parse(&option_parser, argc, (const char* const*)argv) < 0) return wayvnc_usage(&option_parser, stderr, 1); if (option_parser_get_value(&option_parser, "version")) { return show_version(); } if (option_parser_get_value(&option_parser, "help")) { return wayvnc_usage(&option_parser, stdout, 0); } cfg_file = option_parser_get_value(&option_parser, "config"); enable_gpu_features = !!option_parser_get_value(&option_parser, "gpu"); output_name = option_parser_get_value(&option_parser, "output"); seat_name = option_parser_get_value(&option_parser, "seat"); socket_path = option_parser_get_value(&option_parser, "socket"); overlay_cursor = !!option_parser_get_value(&option_parser, "render-cursor"); show_performance = !!option_parser_get_value(&option_parser, "performance"); use_unix_socket = !!option_parser_get_value(&option_parser, "unix-socket"); use_websocket = !!option_parser_get_value(&option_parser, "websocket"); disable_input = !!option_parser_get_value(&option_parser, "disable-input"); log_level = option_parser_get_value(&option_parser, "verbose") ? NVNC_LOG_INFO : NVNC_LOG_WARNING; log_level = log_level_from_string( option_parser_get_value(&option_parser, "log-level")); max_rate = atoi(option_parser_get_value(&option_parser, "max-fps")); use_transient_seat = !!option_parser_get_value(&option_parser, "transient-seat"); start_detached = !!option_parser_get_value(&option_parser, "detached"); self.start_detached = start_detached; keyboard_options = option_parser_get_value(&option_parser, "keyboard"); if (keyboard_options) parse_keyboard_option(&self, keyboard_options); nvnc_set_log_level(log_level); // Only check for explicitly-set values here (defaults applied below) address = option_parser_get_value_no_default(&option_parser, "address"); const char* port_str = option_parser_get_value_no_default(&option_parser, "port"); if (port_str) port = atoi(port_str); if (seat_name && disable_input) { nvnc_log(NVNC_LOG_ERROR, "seat and disable-input are conflicting options"); return 1; } if (use_unix_socket && use_websocket) { nvnc_log(NVNC_LOG_ERROR, "websocket and unix-socket are conflicting options"); return 1; } if (use_transient_seat && disable_input) { nvnc_log(NVNC_LOG_ERROR, "transient-seat and disable-input are conflicting options"); return 1; } if (seat_name && use_transient_seat) { nvnc_log(NVNC_LOG_ERROR, "transient seat and seat are conflicting options"); return 1; } errno = 0; int cfg_rc = cfg_load(&self.cfg, cfg_file); if (cfg_rc != 0 && (cfg_file || errno != ENOENT)) { if (cfg_rc > 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to load config. Error on line %d", cfg_rc); } else { nvnc_log(NVNC_LOG_ERROR, "Failed to load config. %m"); } return 1; } if (check_cfg_sanity(&self.cfg) < 0) return 1; if (cfg_rc == 0) { if (!address) address = self.cfg.address; if (!port) port = self.cfg.port; } if (!address) address = DEFAULT_ADDRESS; if (!port) port = DEFAULT_PORT; self.disable_input = disable_input; self.use_transient_seat = use_transient_seat; struct aml* aml = aml_new(); if (!aml) goto failure; aml_set_default(aml); if (init_main_loop(&self) < 0) goto failure; if (!start_detached) { if (init_wayland(&self, NULL) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to initialise wayland"); goto wayland_failure; } struct output* out; if (output_name) { out = output_find_by_name(&self.outputs, output_name); if (!out) { nvnc_log(NVNC_LOG_ERROR, "No such output"); goto wayland_failure; } } else { out = output_first(&self.outputs); if (!out) { nvnc_log(NVNC_LOG_ERROR, "No output found"); goto wayland_failure; } } set_selected_output(&self, out); struct seat* seat = NULL; if (seat_name) { seat = seat_find_by_name(&self.seats, seat_name); if (!seat) { nvnc_log(NVNC_LOG_ERROR, "No such seat"); goto wayland_failure; } } self.selected_seat = seat; } self.screencopy.rate_limit = max_rate; self.screencopy.enable_linux_dmabuf = enable_gpu_features; #ifdef ENABLE_SCREENCOPY_DMABUF if (enable_gpu_features && init_render_node(&drm_fd) < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to initialise DRM render node. No GPU acceleration will be available."); } #endif if (aml_unstable_abi_version != AML_UNSTABLE_API) nvnc_log(NVNC_LOG_PANIC, "libaml is incompatible with this build of wayvnc!"); enum socket_type socket_type = SOCKET_TYPE_TCP; if (use_unix_socket) socket_type = SOCKET_TYPE_UNIX; else if (use_websocket) socket_type = SOCKET_TYPE_WEBSOCKET; if (init_nvnc(&self, address, port, socket_type) < 0) goto nvnc_failure; if (!start_detached) { if (self.screencopy.manager) screencopy_init(&self.screencopy); if (!self.screencopy.manager) { nvnc_log(NVNC_LOG_ERROR, "screencopy is not supported by compositor"); goto capture_failure; } } self.screencopy.overlay_cursor = overlay_cursor; if (show_performance) self.performance_ticker = aml_ticker_new(1000000, on_perf_tick, &self, NULL); const struct ctl_server_actions ctl_actions = { .userdata = &self, .on_attach = on_attach, .on_detach = on_detach, .on_output_cycle = on_output_cycle, .on_output_switch = on_output_switch, .client_next = client_next, .client_info = client_info, .get_output_list = get_output_list, .on_disconnect_client = on_disconnect_client, .on_wayvnc_exit = on_wayvnc_exit, }; self.ctl = ctl_server_new(socket_path, &ctl_actions); if (!self.ctl) goto ctl_server_failure; if (self.display) wl_display_dispatch_pending(self.display); while (!self.do_exit) { if (self.display) wl_display_flush(self.display); aml_poll(aml, -1); aml_dispatch(aml); } nvnc_log(NVNC_LOG_INFO, "Exiting..."); if (self.display) screencopy_stop(&self.screencopy); ctl_server_destroy(self.ctl); self.ctl = NULL; nvnc_display_unref(self.nvnc_display); nvnc_close(self.nvnc); self.nvnc = NULL; wayvnc_destroy(&self); #ifdef ENABLE_SCREENCOPY_DMABUF if (gbm_device) { gbm_device_destroy(gbm_device); close(drm_fd); } #endif aml_unref(aml); return 0; ctl_server_failure: capture_failure: nvnc_display_unref(self.nvnc_display); nvnc_close(self.nvnc); nvnc_failure: wayland_failure: aml_unref(aml); failure: self.nvnc = NULL; wayvnc_destroy(&self); #ifdef ENABLE_SCREENCOPY_DMABUF if (gbm_device) gbm_device_destroy(gbm_device); if (drm_fd >= 0) close(drm_fd); #endif return 1; } wayvnc-0.8.0/src/option-parser.c000066400000000000000000000242741456662133500166070ustar00rootroot00000000000000/* * Copyright (c) 2022 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "option-parser.h" #include "strlcpy.h" #include "table-printer.h" #include #include #include #include #include #include #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) static int count_options(const struct wv_option* opts) { int n = 0; while (opts[n].short_opt || opts[n].long_opt || opts[n].positional) n++; return n; } void option_parser_init(struct option_parser* self, const struct wv_option* options) { memset(self, 0, sizeof(*self)); self->options = options; self->n_opts = count_options(options); self->name = "Options"; } static int get_left_col_width(const struct wv_option* opts, int n) { int max_width = 0; for (int i = 0; i < n; ++i) { int width = 0; if (opts[i].short_opt) width += 2; if (opts[i].long_opt) width += 2 + strlen(opts[i].long_opt); if (opts[i].short_opt && opts[i].long_opt) width += 1; // for ',' if (opts[i].schema) { width += strlen(opts[i].schema); if (opts[i].long_opt) width += 1; // for '=' } if (width > max_width) max_width = width; } return max_width; } static const char* format_help(const struct wv_option* opt) { if (!opt->default_) return opt->help; static char help_buf[256]; snprintf(help_buf, sizeof(help_buf), "%s\nDefault: %s", opt->help, opt->default_); return help_buf; } static void format_option(struct table_printer* printer, const struct wv_option* opt) { if (!opt->help || opt->positional) return; int n_chars = 0; char buf[64]; if (opt->short_opt) n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, "-%c", opt->short_opt); if (opt->long_opt) n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, "%s--%s", opt->short_opt ? "," : "", opt->long_opt); if (opt->schema) n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, "%s%s", opt->long_opt ? "=" : "", opt->schema); table_printer_print_line(printer, buf, format_help(opt)); } void option_parser_print_options(struct option_parser* self, FILE* stream) { fprintf(stream, "%s:\n", self->name); int left_col_width = get_left_col_width(self->options, self->n_opts); struct table_printer printer; table_printer_init(&printer, stream, left_col_width); for (int i = 0; i < self->n_opts; ++i) format_option(&printer, &self->options[i]); } static void print_string_tolower(FILE* stream, const char *src) { for (const char* c = src; *c != '\0'; c++) fprintf(stream, "%c", tolower(*c)); } void option_parser_print_usage(struct option_parser* self, FILE* stream) { fprintf(stream, " ["); print_string_tolower(stream, self->name); fprintf(stream, "]"); int optional_paren_count = 0; for (int i = 0; i < self->n_opts; ++i) { const struct wv_option* opt = &self->options[i]; if (!opt->positional) continue; const char* open = "<"; const char* close = ">"; if (opt->default_) { open = "["; close = ""; // Closed via optional_paren_count loop below optional_paren_count++; } else { // Enforce there must be NO non-optional args after // we've processed at least one optional arg assert(optional_paren_count == 0); } fprintf(stream, " %s%s%s", open, opt->positional, close); } for (int i = 0; i < optional_paren_count; ++i) fprintf(stream, "]"); } int option_parser_print_arguments(struct option_parser* self, FILE* stream) { size_t max_arg = 0; for (int i = 0; i < self->n_opts; ++i) { const struct wv_option* opt = &self->options[i]; if (!opt->positional || !opt->help || opt->is_subcommand) continue; max_arg = MAX(max_arg, strlen(opt->positional)); } if (!max_arg) return 0; fprintf(stream, "Arguments:\n"); struct table_printer printer; table_printer_init(&printer, stream, max_arg); int i; for (i = 0; i < self->n_opts; ++i) { const struct wv_option* opt = &self->options[i]; if (!opt->positional || !opt->help || opt->is_subcommand) continue; table_printer_print_line(&printer, opt->positional, format_help(opt)); } return i; } static const struct wv_option* find_long_option( const struct option_parser* self, const char* name) { for (int i = 0; i < self->n_opts; ++i) { if (!self->options[i].long_opt) continue; if (strcmp(self->options[i].long_opt, name) == 0) return &self->options[i]; } return NULL; } static const struct wv_option* find_short_option( const struct option_parser* self, char name) { for (int i = 0; i < self->n_opts; ++i) if (self->options[i].short_opt == name) return &self->options[i]; return NULL; } static const struct wv_option* find_positional_option( struct option_parser* self, int position) { int current_pos = 0; for (int i = 0; i < self->n_opts; ++i) { if (!self->options[i].positional) continue; if (current_pos == position) return &self->options[i]; current_pos += 1; } return NULL; } static const struct wv_option* find_positional_option_by_name( const struct option_parser* self, const char*name) { for (int i = 0; i < self->n_opts; ++i) { const struct wv_option* opt = &self->options[i]; if (!opt->positional) continue; if (strcmp(opt->positional, name) == 0) return opt; } return NULL; } static int append_value(struct option_parser* self, const struct wv_option* option, const char* value) { if ((size_t)self->n_values >= ARRAY_SIZE(self->values)) { fprintf(stderr, "ERROR: Too many arguments!\n"); return -1; } struct wv_option_value* dst = &self->values[self->n_values++]; dst->option = option; strlcpy(dst->value, value, sizeof(dst->value)); return 0; } static int parse_long_arg(struct option_parser* self, int argc, const char* const* argv, int index) { int count = 1; char name[256]; strlcpy(name, argv[index] + 2, sizeof(name)); char* eq = strchr(name, '='); if (eq) *eq = '\0'; const struct wv_option* opt = find_long_option(self, name); if (!opt) { fprintf(stderr, "ERROR: Unknown option: \"%s\"\n", name); return -1; } const char* value = "1"; if (opt->schema) { if (eq) { value = eq + 1; } else { if (index + 1 >= argc) { fprintf(stderr, "ERROR: An argument is required for the \"%s\" option\n", opt->long_opt); return -1; } value = argv[index + 1]; count += 1; } } if (append_value(self, opt, value) < 0) return -1; return count; } static int parse_short_args(struct option_parser* self, char argc, const char* const* argv, int index) { int count = 1; int len = strlen(argv[index]); for (int i = 1; i < len; ++i) { char name = argv[index][i]; const struct wv_option* opt = find_short_option(self, name); if (!opt) { fprintf(stderr, "ERROR: Unknown option: \"%c\"\n", name); return -1; } const char* value = "1"; if (opt->schema) { const char* tail = argv[index] + i + 1; if (tail[0] == '=') { value = tail + 1; } else if (tail[0]) { value = tail; } else { if (index + 1 >= argc) { fprintf(stderr, "ERROR: An argument is required for the \"%c\" option\n", opt->short_opt); return -1; } value = argv[index + 1]; count += 1; } } if (append_value(self, opt, value) < 0) return -1; if (opt->schema) break; } return count; } static int parse_positional_arg(struct option_parser* self, char argc, const char* const* argv, int i) { const struct wv_option* opt = find_positional_option(self, self->position); if (!opt) return 1; if (append_value(self, opt, argv[i]) < 0) return -1; self->position += 1; return opt->is_subcommand ? 0 : 1; } int option_parser_parse(struct option_parser* self, int argc, const char* const* argv) { int i = 1; while (i < argc) { if (argv[i][0] == '-') { if (argv[i][1] == '-') { if (argv[i][2] == '\0') { i++; break; } int rc = parse_long_arg(self, argc, argv, i); if (rc < 0) return -1; i += rc; } else { int rc = parse_short_args(self, argc, argv, i); if (rc < 0) return -1; i += rc; } } else { int rc = parse_positional_arg(self, argc, argv, i); if (rc < 0) return -1; if (rc == 0) break; i += rc; } } self->remaining_argc = argc - i; if (self->remaining_argc) self->remaining_argv = argv + i; return 0; } const char* option_parser_get_value_no_default(const struct option_parser* self, const char* name) { const struct wv_option* opt; bool is_short = name[0] && !name[1]; for (int i = 0; i < self->n_values; ++i) { const struct wv_option_value* value = &self->values[i]; opt = value->option; if (is_short) { if (opt->short_opt && opt->short_opt == *name) return value->value; } else { if (opt->long_opt && strcmp(opt->long_opt, name) == 0) return value->value; } if (opt->positional && strcmp(opt->positional, name) == 0) return value->value; } return NULL; } const char* option_parser_get_value(const struct option_parser* self, const char* name) { const char* value = option_parser_get_value_no_default(self, name); if (value) return value; bool is_short = name[0] && !name[1]; const struct wv_option* opt; if (is_short) { opt = find_short_option(self, name[0]); if (opt) return opt->default_; } else { opt = find_long_option(self, name); if (opt) return opt->default_; opt = find_positional_option_by_name(self, name); if (opt) return opt->default_; } return NULL; } void option_parser_print_cmd_summary(const char* summary, FILE* stream) { struct table_printer printer; table_printer_init(&printer, stream, 0); fprintf(stream, "\n"); table_printer_indent_and_reflow_text(stream, summary, printer.max_width, 0, 0); fprintf(stream, "\n"); } wayvnc-0.8.0/src/output-management.c000066400000000000000000000172221456662133500174520ustar00rootroot00000000000000/* * Copyright (c) 2023 The wayvnc authors * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "output.h" #include "output-management.h" #include "wlr-output-management-unstable-v1.h" struct output_manager_head { struct zwlr_output_head_v1* head; struct wl_list link; char* name; bool enabled; }; static struct wl_list heads; static uint32_t last_config_serial; static struct zwlr_output_manager_v1* wlr_output_manager; /* single head properties */ static void output_head_name(void* data, struct zwlr_output_head_v1* output_head, const char* name) { struct output_manager_head* head = data; nvnc_trace("Got head name: %s", name); free(head->name); head->name = strdup(name); } static void output_head_description(void* data, struct zwlr_output_head_v1* output_head, const char* description) { nvnc_trace("Got head description: %s", description); } static void output_head_physical_size(void* data, struct zwlr_output_head_v1* output_head, int32_t width, int32_t height) { nvnc_trace("Got head size: %dx%d", width, height); } static void output_head_mode(void* data, struct zwlr_output_head_v1* output_head, struct zwlr_output_mode_v1* mode) { nvnc_trace("Got head mode"); } static void output_head_enabled(void* data, struct zwlr_output_head_v1* output_head, int32_t enabled) { nvnc_trace("Got head enabled: %s", enabled ? "yes" : "no"); struct output_manager_head* head = data; head->enabled = !!enabled; } static void output_head_current_mode(void* data, struct zwlr_output_head_v1* output_head, struct zwlr_output_mode_v1* mode) { nvnc_trace("Got head current mode"); } static void output_head_position(void* data, struct zwlr_output_head_v1* output_head, int32_t x, int32_t y) { nvnc_trace("Got head position: %d,%d", x, y); } static void output_head_transform(void* data, struct zwlr_output_head_v1* output_head, int32_t transform) { nvnc_trace("Got head transform: %d", transform); } static void output_head_scale(void* data, struct zwlr_output_head_v1* output_head, wl_fixed_t scale_f) { double scale = wl_fixed_to_double(scale_f); nvnc_trace("Got head scale: %.2f", scale); } static void output_head_finished(void* data, struct zwlr_output_head_v1* output_head) { nvnc_trace("head gone, removing"); struct output_manager_head* head = data; zwlr_output_head_v1_destroy(output_head); wl_list_remove(&head->link); free(head->name); head->name = NULL; head->head = NULL; free(head); } struct zwlr_output_head_v1_listener wlr_output_head_listener = { .name = output_head_name, .description = output_head_description, .physical_size = output_head_physical_size, .mode = output_head_mode, .enabled = output_head_enabled, .current_mode = output_head_current_mode, .position = output_head_position, .transform = output_head_transform, .scale = output_head_scale, .finished = output_head_finished, }; /* config object */ static void output_manager_config_succeeded(void* data, struct zwlr_output_configuration_v1* config) { nvnc_trace("config request succeeded"); zwlr_output_configuration_v1_destroy(config); } static void output_manager_config_failed(void* data, struct zwlr_output_configuration_v1* config) { nvnc_trace("config request failed"); zwlr_output_configuration_v1_destroy(config); } static void output_manager_config_cancelled(void* data, struct zwlr_output_configuration_v1* config) { nvnc_trace("config request cancelled"); zwlr_output_configuration_v1_destroy(config); } struct zwlr_output_configuration_v1_listener wlr_output_config_listener = { .succeeded = output_manager_config_succeeded, .failed = output_manager_config_failed, .cancelled = output_manager_config_cancelled, }; /* manager itself */ static void output_manager_done(void* data, struct zwlr_output_manager_v1* zwlr_output_manager_v1, uint32_t serial) { last_config_serial = serial; nvnc_trace("Got new serial: %u", serial); } static void output_manager_finished(void* data, struct zwlr_output_manager_v1* zwlr_output_manager_v1) { nvnc_trace("output-manager destroyed"); wlr_output_manager = NULL; } static void output_manager_head(void* data, struct zwlr_output_manager_v1* zwlr_output_manager_v1, struct zwlr_output_head_v1* output_head) { struct output_manager_head* head = calloc(1, sizeof(*head)); if (!head) { nvnc_log(NVNC_LOG_ERROR, "OOM"); return; } head->head = output_head; wl_list_insert(heads.prev, &head->link); nvnc_trace("New head, now at %lu", wl_list_length(&heads)); zwlr_output_head_v1_add_listener(head->head, &wlr_output_head_listener, head); } static const struct zwlr_output_manager_v1_listener wlr_output_manager_listener = { .head = output_manager_head, .done = output_manager_done, .finished = output_manager_finished, }; /* Public API */ void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager) { if (wlr_output_manager) return; wl_list_init(&heads); wlr_output_manager = output_manager; zwlr_output_manager_v1_add_listener(wlr_output_manager, &wlr_output_manager_listener, NULL); } void wlr_output_manager_destroy(void) { if (!wlr_output_manager) return; struct output_manager_head* head; struct output_manager_head* tmp; wl_list_for_each_safe(head, tmp, &heads, link) { wl_list_remove(&head->link); free(head->name); free(head); } zwlr_output_manager_v1_destroy(wlr_output_manager); wlr_output_manager = NULL; last_config_serial = 0; } bool wlr_output_manager_resize_output(struct output* output, uint16_t width, uint16_t height) { if (!wlr_output_manager) { nvnc_log(NVNC_LOG_INFO, "output-management protocol not available, not resizing output"); return false; } if (!output->is_headless) { nvnc_log(NVNC_LOG_INFO, "not resizing output %s: not a headless one", output->name); return false; } // TODO: This could be synced to --max-fps int refresh_rate = 0; struct zwlr_output_configuration_v1* config; struct zwlr_output_configuration_head_v1* config_head; config = zwlr_output_manager_v1_create_configuration( wlr_output_manager, last_config_serial); zwlr_output_configuration_v1_add_listener(config, &wlr_output_config_listener, NULL); struct output_manager_head* head; wl_list_for_each(head, &heads, link) { if (!head->enabled) { nvnc_trace("disabling output %s", head->name); zwlr_output_configuration_v1_disable_head( config, head->head); continue; } config_head = zwlr_output_configuration_v1_enable_head( config, head->head); if (head->name && strcmp(head->name, output->name) == 0) { nvnc_trace("reconfiguring output %s", head->name); zwlr_output_configuration_head_v1_set_custom_mode( config_head, width, height, refresh_rate); /* It doesn't make any sense to have rotation on a * headless display, so we set the transform here to be * sure. */ zwlr_output_configuration_head_v1_set_transform( config_head, WL_OUTPUT_TRANSFORM_NORMAL); } } nvnc_trace("applying new output config"); zwlr_output_configuration_v1_apply(config); return true; } wayvnc-0.8.0/src/output.c000066400000000000000000000255361456662133500153470ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "output.h" #include "strlcpy.h" #include "xdg-output-unstable-v1.h" #include "wlr-output-power-management-unstable-v1.h" extern struct zxdg_output_manager_v1* xdg_output_manager; extern struct zwlr_output_power_manager_v1* wlr_output_power_manager; #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) void output_transform_coord(const struct output* self, uint32_t src_x, uint32_t src_y, uint32_t* dst_x, uint32_t* dst_y) { switch (self->transform) { case WL_OUTPUT_TRANSFORM_NORMAL: *dst_x = src_x; *dst_y = src_y; break; case WL_OUTPUT_TRANSFORM_90: *dst_x = src_y; *dst_y = self->height - src_x; break; case WL_OUTPUT_TRANSFORM_180: *dst_x = self->width - src_x; *dst_y = self->height - src_y; break; case WL_OUTPUT_TRANSFORM_270: *dst_x = self->width - src_y; *dst_y = src_x; break; case WL_OUTPUT_TRANSFORM_FLIPPED: *dst_x = self->width - src_x; *dst_y = src_y; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: *dst_x = src_y; *dst_y = src_x; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: *dst_x = src_x; *dst_y = self->height - src_y; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: *dst_x = self->width - src_y; *dst_y = self->height - src_x; break; } } void output_transform_box_coord(const struct output* self, uint32_t src_x0, uint32_t src_y0, uint32_t src_x1, uint32_t src_y1, uint32_t* dst_x0, uint32_t* dst_y0, uint32_t* dst_x1, uint32_t* dst_y1) { uint32_t x0 = 0, y0 = 0, x1 = 0, y1 = 0; output_transform_coord(self, src_x0, src_y0, &x0, &y0); output_transform_coord(self, src_x1, src_y1, &x1, &y1); *dst_x0 = MIN(x0, x1); *dst_x1 = MAX(x0, x1); *dst_y0 = MIN(y0, y1); *dst_y1 = MAX(y0, y1); } static bool is_transform_90_degrees(enum wl_output_transform transform) { switch (transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: return true; default: break; } return false; } uint32_t output_get_transformed_width(const struct output* self) { return is_transform_90_degrees(self->transform) ? self->height : self->width; } uint32_t output_get_transformed_height(const struct output* self) { return is_transform_90_degrees(self->transform) ? self->width : self->height; } static void output_handle_geometry(void* data, struct wl_output* wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, const char* make, const char* model, int32_t transform) { struct output* output = data; if (transform != (int32_t)output->transform) output->is_transform_changed = true; output->x = x; output->y = y; output->transform = transform; strlcpy(output->make, make, sizeof(output->make)); strlcpy(output->model, model, sizeof(output->model)); } static void output_handle_mode(void* data, struct wl_output* wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct output* output = data; if (!(flags & WL_OUTPUT_MODE_CURRENT)) return; if (width != (int32_t)output->width || height != (int32_t)output->height) output->is_dimension_changed = true; output->width = width; output->height = height; } static void output_handle_done(void* data, struct wl_output* wl_output) { struct output* output = data; if (output->is_dimension_changed && output->on_dimension_change) output->on_dimension_change(output); if (output->is_transform_changed && output->on_transform_change) output->on_transform_change(output); output->is_dimension_changed = false; output->is_transform_changed = false; } static void output_handle_scale(void* data, struct wl_output* wl_output, int32_t factor) { } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, .done = output_handle_done, .scale = output_handle_scale, }; void output_destroy(struct output* output) { if (output->xdg_output) zxdg_output_v1_destroy(output->xdg_output); if (output->wlr_output_power) zwlr_output_power_v1_destroy(output->wlr_output_power); wl_output_destroy(output->wl_output); free(output); } void output_list_destroy(struct wl_list* list) { struct output* output; struct output* tmp; wl_list_for_each_safe(output, tmp, list, link) { wl_list_remove(&output->link); output_destroy(output); } } void output_logical_position(void* data, struct zxdg_output_v1* xdg_output, int32_t x, int32_t y) { } void output_logical_size(void* data, struct zxdg_output_v1* xdg_output, int32_t width, int32_t height) { } void output_name(void* data, struct zxdg_output_v1* xdg_output, const char* name) { struct output* self = data; strlcpy(self->name, name, sizeof(self->name)); self->is_headless = (strncmp(name, "HEADLESS-", strlen("HEADLESS-")) == 0) || (strncmp(name, "NOOP-", strlen("NOOP-")) == 0); nvnc_trace("Output %u name: %s, headless: %s", self->id, self->name, self->is_headless ? "yes" : "no"); } void output_description(void* data, struct zxdg_output_v1* xdg_output, const char* description) { struct output* self = data; strlcpy(self->description, description, sizeof(self->description)); nvnc_trace("Output %u description: %s", self->id, self->description); } static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = output_logical_position, .logical_size = output_logical_size, .done = NULL, /* Deprecated */ .name = output_name, .description = output_description, }; static void output_setup_xdg_output_manager(struct output* self) { if (!xdg_output_manager || self->xdg_output) return; struct zxdg_output_v1* xdg_output = zxdg_output_manager_v1_get_xdg_output( xdg_output_manager, self->wl_output); self->xdg_output = xdg_output; zxdg_output_v1_add_listener(self->xdg_output, &xdg_output_listener, self); } const char* output_power_state_name(enum output_power_state state) { switch(state) { case OUTPUT_POWER_ON: return "ON"; case OUTPUT_POWER_OFF: return "OFF"; case OUTPUT_POWER_UNKNOWN: return "UNKNOWN"; } abort(); return NULL; } static void output_power_mode(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1, uint32_t mode) { struct output* self = data; nvnc_trace("Output %s power state changed to %s", self->name, (mode == ZWLR_OUTPUT_POWER_V1_MODE_ON) ? "ON" : "OFF"); enum output_power_state old = self->power; switch (mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: self->power = OUTPUT_POWER_OFF; break; case ZWLR_OUTPUT_POWER_V1_MODE_ON: self->power = OUTPUT_POWER_ON; break; } if (old != self->power && self->on_power_change) self->on_power_change(self); } static void output_power_failed(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1) { struct output* self = data; nvnc_log(NVNC_LOG_WARNING, "Output %s power state failure", self->name); self->power = OUTPUT_POWER_UNKNOWN; zwlr_output_power_v1_destroy(self->wlr_output_power); self->wlr_output_power = NULL; } static const struct zwlr_output_power_v1_listener wlr_output_power_listener = { .mode = output_power_mode, .failed = output_power_failed, }; static void output_setup_wlr_output_power_manager(struct output* self) { if (!wlr_output_power_manager || self->wlr_output_power) return; struct zwlr_output_power_v1* wlr_output_power = zwlr_output_power_manager_v1_get_output_power( wlr_output_power_manager, self->wl_output); self->wlr_output_power = wlr_output_power; zwlr_output_power_v1_add_listener(self->wlr_output_power, &wlr_output_power_listener, self); } int output_set_power_state(struct output* output, enum output_power_state state) { assert(state != OUTPUT_POWER_UNKNOWN); if (!output->wlr_output_power) { errno = ENOENT; return -1; } nvnc_trace("Output %s requesting power %s", output->name, output_power_state_name(state)); int mode = (state == OUTPUT_POWER_ON) ? ZWLR_OUTPUT_POWER_V1_MODE_ON : ZWLR_OUTPUT_POWER_V1_MODE_OFF; zwlr_output_power_v1_set_mode(output->wlr_output_power, mode); return 0; } struct output* output_find_by_id(struct wl_list* list, uint32_t id) { struct output* output; wl_list_for_each(output, list, link) if (output->id == id) return output; return NULL; } struct output* output_find_by_name(struct wl_list* list, const char* name) { struct output* output; wl_list_for_each(output, list, link) if (strcmp(output->name, name) == 0) return output; return NULL; } struct output* output_first(struct wl_list* list) { struct output* output; wl_list_for_each(output, list, link) return output; return output; } struct output* output_cycle(const struct wl_list* list, const struct output* current, enum output_cycle_direction direction) { const struct wl_list* iter = current ? ¤t->link : list; iter = (direction == OUTPUT_CYCLE_FORWARD) ? iter->next : iter->prev; if (iter == list) { if (wl_list_empty(list)) return NULL; iter = (direction == OUTPUT_CYCLE_FORWARD) ? iter->next : iter->prev; } struct output* output; return wl_container_of(iter, output, link); } void output_setup_wl_managers(struct wl_list* list) { struct output* output; wl_list_for_each(output, list, link) { output_setup_xdg_output_manager(output); output_setup_wlr_output_power_manager(output); } } struct output* output_new(struct wl_output* wl_output, uint32_t id) { struct output* output = calloc(1, sizeof(*output)); if (!output) { nvnc_log(NVNC_LOG_ERROR, "OOM"); return NULL; } output->wl_output = wl_output; output->id = id; output->power = OUTPUT_POWER_UNKNOWN; wl_output_add_listener(output->wl_output, &output_listener, output); output_setup_xdg_output_manager(output); output_setup_wlr_output_power_manager(output); return output; } wayvnc-0.8.0/src/pam_auth.c000066400000000000000000000046061456662133500156000ustar00rootroot00000000000000/* * Copyright (c) 2020 Nicholas Sica * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "pam_auth.h" #include #include #include #include struct credentials { const char* user; const char* password; }; static int pam_return_pwd(int num_msg, const struct pam_message** msgm, struct pam_response** response, void* appdata_ptr) { struct credentials* cred = appdata_ptr; struct pam_response* resp = calloc(num_msg, sizeof(*resp)); for (int i = 0; i < num_msg; i++) { resp[i].resp_retcode = PAM_SUCCESS; switch(msgm[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: resp[i].resp = strdup(cred->password); break; default: goto error; } } *response = resp; return PAM_SUCCESS; error: for (int i = 0; i < num_msg; i++) { free(resp[i].resp); } free(resp); return PAM_CONV_ERR; } bool pam_auth(const char* username, const char* password) { struct credentials cred = { username, password }; struct pam_conv conv = { &pam_return_pwd, &cred }; const char* service = "wayvnc"; pam_handle_t* pamh; int result = pam_start(service, username, &conv, &pamh); if (result != PAM_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "ERROR: PAM start failed: %s", pam_strerror(pamh, result)); return false; } result = pam_authenticate(pamh, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK); if (result != PAM_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "PAM authenticate failed: %s", pam_strerror(pamh, result)); goto error; } result = pam_acct_mgmt(pamh, 0); if (result != PAM_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "PAM account management failed: %s", pam_strerror(pamh, result)); goto error; } error: pam_end(pamh, result); return result == PAM_SUCCESS; } wayvnc-0.8.0/src/pixels.c000066400000000000000000000043001456662133500152750ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "pixels.h" #include #include #include #include #include enum wl_shm_format fourcc_to_wl_shm(uint32_t in) { assert(!(in & DRM_FORMAT_BIG_ENDIAN)); switch (in) { case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; } return in; } uint32_t fourcc_from_wl_shm(enum wl_shm_format in) { switch (in) { case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888; case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888; default:; } return in; } int pixel_size_from_fourcc(uint32_t fourcc) { switch (fourcc & ~DRM_FORMAT_BIG_ENDIAN) { case DRM_FORMAT_RGBA1010102: case DRM_FORMAT_RGBX1010102: case DRM_FORMAT_BGRA1010102: case DRM_FORMAT_BGRX1010102: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_ABGR2101010: case DRM_FORMAT_XBGR2101010: case DRM_FORMAT_RGBA8888: case DRM_FORMAT_RGBX8888: case DRM_FORMAT_BGRA8888: case DRM_FORMAT_BGRX8888: case DRM_FORMAT_ARGB8888: case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ABGR8888: case DRM_FORMAT_XBGR8888: return 4; case DRM_FORMAT_BGR888: case DRM_FORMAT_RGB888: return 3; case DRM_FORMAT_RGBA4444: case DRM_FORMAT_RGBX4444: case DRM_FORMAT_BGRA4444: case DRM_FORMAT_BGRX4444: case DRM_FORMAT_ARGB4444: case DRM_FORMAT_XRGB4444: case DRM_FORMAT_ABGR4444: case DRM_FORMAT_XBGR4444: return 2; } return 0; } wayvnc-0.8.0/src/pointer.c000066400000000000000000000063451456662133500154640ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "pointer.h" #include "wlr-virtual-pointer-unstable-v1.h" #include "time-util.h" int pointer_init(struct pointer* self) { zwlr_virtual_pointer_v1_axis_source(self->pointer, WL_POINTER_AXIS_SOURCE_WHEEL); return 0; } void pointer_destroy(struct pointer* self) { zwlr_virtual_pointer_v1_destroy(self->pointer); } static void pointer_set_button_mask(struct pointer* self, uint32_t t, enum nvnc_button_mask mask) { enum nvnc_button_mask diff = self->current_mask ^ mask; if (diff & NVNC_BUTTON_LEFT) zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_LEFT, !!(mask & NVNC_BUTTON_LEFT)); if (diff & NVNC_BUTTON_MIDDLE) zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_MIDDLE, !!(mask & NVNC_BUTTON_MIDDLE)); if (diff & NVNC_BUTTON_RIGHT) zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_RIGHT, !!(mask & NVNC_BUTTON_RIGHT)); int vaxis = WL_POINTER_AXIS_VERTICAL_SCROLL; int haxis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; /* I arrived at the magical value of 15 by connecting a mouse with a * scroll wheel and viewing the output of wev. */ if ((diff & NVNC_SCROLL_UP) && !(mask & NVNC_SCROLL_UP)) zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis, wl_fixed_from_int(-15), -1); if ((diff & NVNC_SCROLL_DOWN) && !(mask & NVNC_SCROLL_DOWN)) zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis, wl_fixed_from_int(15), 1); if ((diff & NVNC_SCROLL_LEFT) && !(mask & NVNC_SCROLL_LEFT)) zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis, wl_fixed_from_int(-15), -1); if ((diff & NVNC_SCROLL_RIGHT) && !(mask & NVNC_SCROLL_RIGHT)) zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis, wl_fixed_from_int(15), 1); self->current_mask = mask; } void pointer_set(struct pointer* self, uint32_t x, uint32_t y, enum nvnc_button_mask button_mask) { uint32_t t = gettime_ms(); if (x != self->current_x || y != self->current_y) zwlr_virtual_pointer_v1_motion_absolute(self->pointer, t, x, y, self->output->width, self->output->height); self->current_x = x; self->current_y = y; if (button_mask != self->current_mask) pointer_set_button_mask(self, t, button_mask); zwlr_virtual_pointer_v1_frame(self->pointer); } wayvnc-0.8.0/src/screencopy.c000066400000000000000000000170131456662133500161500ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "wlr-screencopy-unstable-v1.h" #include "buffer.h" #include "shm.h" #include "screencopy.h" #include "smooth.h" #include "time-util.h" #include "usdt.h" #include "pixels.h" #include "config.h" #define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s static void screencopy__stop(struct screencopy* self) { aml_stop(aml_get_default(), self->timer); self->status = SCREENCOPY_STOPPED; if (self->frame) { zwlr_screencopy_frame_v1_destroy(self->frame); self->frame = NULL; } } void screencopy_stop(struct screencopy* self) { if (self->front) wv_buffer_pool_release(self->pool, self->front); self->front = NULL; return screencopy__stop(self); } static void screencopy_linux_dmabuf(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { #ifdef ENABLE_SCREENCOPY_DMABUF struct screencopy* self = data; if (!(wv_buffer_get_available_types() & WV_BUFFER_DMABUF)) return; self->have_linux_dmabuf = true; self->dmabuf_width = width; self->dmabuf_height = height; self->fourcc = format; #endif } static void screencopy_buffer_done(void* data, struct zwlr_screencopy_frame_v1* frame) { struct screencopy* self = data; uint32_t width, height, stride, fourcc; enum wv_buffer_type type = WV_BUFFER_UNSPEC; #ifdef ENABLE_SCREENCOPY_DMABUF if (self->have_linux_dmabuf && self->enable_linux_dmabuf) { width = self->dmabuf_width; height = self->dmabuf_height; stride = 0; fourcc = self->fourcc; type = WV_BUFFER_DMABUF; } else #endif { width = self->wl_shm_width; height = self->wl_shm_height; stride = self->wl_shm_stride; fourcc = fourcc_from_wl_shm(self->wl_shm_format); type = WV_BUFFER_SHM; } wv_buffer_pool_resize(self->pool, type, width, height, stride, fourcc); struct wv_buffer* buffer = wv_buffer_pool_acquire(self->pool); if (!buffer) { screencopy__stop(self); self->status = SCREENCOPY_FATAL; self->on_done(self); return; } assert(!self->front); self->front = buffer; if (self->is_immediate_copy) zwlr_screencopy_frame_v1_copy(self->frame, buffer->wl_buffer); else zwlr_screencopy_frame_v1_copy_with_damage(self->frame, buffer->wl_buffer); } static void screencopy_buffer(void* data, struct zwlr_screencopy_frame_v1* frame, enum wl_shm_format format, uint32_t width, uint32_t height, uint32_t stride) { struct screencopy* self = data; self->wl_shm_format = format; self->wl_shm_width = width; self->wl_shm_height = height; self->wl_shm_stride = stride; int version = zwlr_screencopy_manager_v1_get_version(self->manager); if (version < 3) { self->have_linux_dmabuf = false; screencopy_buffer_done(data, frame); return; } } static void screencopy_flags(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t flags) { (void)frame; struct screencopy* self = data; self->front->y_inverted = !!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT); } static void screencopy_ready(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec) { struct screencopy* self = data; uint64_t sec = (uint64_t)sec_hi << 32 | (uint64_t)sec_lo; uint64_t pts = sec * UINT64_C(1000000) + (uint64_t)nsec / UINT64_C(1000); DTRACE_PROBE2(wayvnc, screencopy_ready, self, pts); screencopy__stop(self); self->last_time = gettime_us(); double delay = (self->last_time - self->start_time) * 1.0e-6; self->delay = smooth(&self->delay_smoother, delay); if (self->is_immediate_copy) wv_buffer_damage_whole(self->front); if (self->back) wv_buffer_pool_release(self->pool, self->back); self->back = self->front; self->front = NULL; nvnc_fb_set_pts(self->back->nvnc_fb, pts); self->status = SCREENCOPY_DONE; self->on_done(self); } static void screencopy_failed(void* data, struct zwlr_screencopy_frame_v1* frame) { struct screencopy* self = data; DTRACE_PROBE1(wayvnc, screencopy_failed, self); screencopy__stop(self); if (self->front) wv_buffer_pool_release(self->pool, self->front); self->front = NULL; self->status = SCREENCOPY_FAILED; self->on_done(self); } static void screencopy_damage(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { struct screencopy* self = data; DTRACE_PROBE1(wayvnc, screencopy_damage, self); wv_buffer_damage_rect(self->front, x, y, width, height); } static int screencopy__start_capture(struct screencopy* self) { DTRACE_PROBE1(wayvnc, screencopy_start, self); static const struct zwlr_screencopy_frame_v1_listener frame_listener = { .buffer = screencopy_buffer, .linux_dmabuf = screencopy_linux_dmabuf, .buffer_done = screencopy_buffer_done, .flags = screencopy_flags, .ready = screencopy_ready, .failed = screencopy_failed, .damage = screencopy_damage, }; self->start_time = gettime_us(); self->frame = zwlr_screencopy_manager_v1_capture_output(self->manager, self->overlay_cursor, self->wl_output); if (!self->frame) return -1; zwlr_screencopy_frame_v1_add_listener(self->frame, &frame_listener, self); return 0; } static void screencopy__poll(void* obj) { struct screencopy* self = aml_get_userdata(obj); screencopy__start_capture(self); } static int screencopy__start(struct screencopy* self, bool is_immediate_copy) { if (self->status == SCREENCOPY_IN_PROGRESS) return -1; self->is_immediate_copy = is_immediate_copy; uint64_t now = gettime_us(); double dt = (now - self->last_time) * 1.0e-6; int32_t time_left = (1.0 / self->rate_limit - dt - self->delay) * 1.0e6; self->status = SCREENCOPY_IN_PROGRESS; if (time_left > 0) { aml_set_duration(self->timer, time_left); return aml_start(aml_get_default(), self->timer); } return screencopy__start_capture(self); } int screencopy_start(struct screencopy* self) { return screencopy__start(self, false); } int screencopy_start_immediate(struct screencopy* self) { return screencopy__start(self, true); } void screencopy_init(struct screencopy* self) { self->pool = wv_buffer_pool_create(0, 0, 0, 0, 0); assert(self->pool); self->timer = aml_timer_new(0, screencopy__poll, self, NULL); assert(self->timer); self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT; } void screencopy_destroy(struct screencopy* self) { aml_stop(aml_get_default(), self->timer); aml_unref(self->timer); if (self->back) wv_buffer_pool_release(self->pool, self->back); if (self->front) wv_buffer_pool_release(self->pool, self->front); self->back = NULL; self->front = NULL; wv_buffer_pool_destroy(self->pool); } wayvnc-0.8.0/src/seat.c000066400000000000000000000051351456662133500147340ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "seat.h" #include "strlcpy.h" static void seat_capabilities(void* data, struct wl_seat* wl_seat, uint32_t capabilities) { struct seat* self = data; self->capabilities = capabilities; } static void seat_name(void* data, struct wl_seat* wl_seat, const char* name) { struct seat* self = data; strlcpy(self->name, name, sizeof(self->name)); } static const struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities, .name = seat_name, }; struct seat* seat_new(struct wl_seat* wl_seat, uint32_t id) { struct seat* self = calloc(1, sizeof(*self)); if (!self) return NULL; self->wl_seat = wl_seat; self->id = id; wl_seat_add_listener(wl_seat, &seat_listener, self); return self; } void seat_destroy(struct seat* self) { wl_seat_destroy(self->wl_seat); free(self); } void seat_list_destroy(struct wl_list* list) { struct seat* seat; struct seat* tmp; wl_list_for_each_safe(seat, tmp, list, link) { wl_list_remove(&seat->link); seat_destroy(seat); } } struct seat* seat_find_by_name(struct wl_list* list, const char* name) { struct seat* seat; wl_list_for_each(seat, list, link) if (strcmp(seat->name, name) == 0) return seat; return NULL; } struct seat* seat_find_by_id(struct wl_list* list, uint32_t id) { struct seat* seat; wl_list_for_each(seat, list, link) if (seat->id == id) return seat; return NULL; } struct seat* seat_find_unoccupied(struct wl_list* list) { struct seat* seat; wl_list_for_each(seat, list, link) if (seat->occupancy == 0) return seat; return NULL; } struct seat* seat_first(struct wl_list* list) { struct seat* seat; wl_list_for_each(seat, list, link) return seat; return NULL; } wayvnc-0.8.0/src/shm.c000066400000000000000000000042271456662133500145700ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "config.h" // Linux with glibc < 2.27 has no wrapper #if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE) #include static inline int memfd_create(const char *name, unsigned int flags) { return syscall(SYS_memfd_create, name, flags); } #endif #if !defined(HAVE_MEMFD) && !defined(__FreeBSD__) static void randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; for (int i = 0; i < 6; ++i) { buf[i] = 'A'+(r&15)+(r&16)*2; r >>= 5; } } #endif static int create_shm_file(void) { #ifdef HAVE_MEMFD return memfd_create("wayvnc-shm", 0); #elif defined(__FreeBSD__) // memfd_create added in FreeBSD 13, but SHM_ANON has been supported for ages return shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600); #else int retries = 100; do { char name[] = "/wl_shm-XXXXXX"; randname(name + sizeof(name) - 7); --retries; int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } } while (retries > 0 && errno == EEXIST); return -1; #endif } int shm_alloc_fd(size_t size) { int fd = create_shm_file(); if (fd < 0) return -1; int ret; do { ret = ftruncate(fd, size); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } wayvnc-0.8.0/src/smooth.c000066400000000000000000000022201456662133500153010ustar00rootroot00000000000000/* * Copyright (c) 2019 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "smooth.h" #include "time-util.h" #include double smooth(struct smooth* self, double input) { uint64_t now = gettime_us(); double dt = (now - self->last_time) * 1.0e-6; self->last_time = now; double factor = 1.0 - exp(-dt / self->time_constant); double result = factor * input + (1.0 - factor) * self->last_result; self->last_result = result; return result; } wayvnc-0.8.0/src/strlcpy.c000066400000000000000000000031001456662133500154660ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.13 2015/08/31 02:53:57 guenther Exp $ */ /* * Copyright (c) 1998, 2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if ((*dst++ = *src++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } return(src - osrc - 1); /* count does not include NUL */ } wayvnc-0.8.0/src/table-printer.c000066400000000000000000000065631456662133500165560ustar00rootroot00000000000000/* * Copyright (c) 2023 Andri Yngvason * Copyright (c) 2023 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "table-printer.h" #include #include #include #include #include static struct table_printer defaults = { .max_width = 80, .left_indent = 4, .column_offset = 8, .stream = NULL, .left_width = 0, }; void table_printer_set_defaults(int max_width, int left_indent, int column_offset) { defaults.max_width = max_width; defaults.left_indent = left_indent; defaults.column_offset = column_offset; } void table_printer_init(struct table_printer* self, FILE* stream, int left_width) { memcpy(self, &defaults, sizeof(*self)); self->stream = stream; self->left_width = left_width; } int table_printer_reflow_text(char* dst, int dst_size, const char* src, int width) { int line_len = 0; int last_space_pos = 0; int dst_len = 0; int i = 0; while (true) { char c = src[i]; if (line_len > width) { // first word > width assert(last_space_pos > 0); // subsequent word > width assert(dst[last_space_pos] != '\n'); dst_len -= i - last_space_pos; dst[dst_len++] = '\n'; i = last_space_pos + 1; line_len = 0; continue; } if (!c) break; if (c == ' ') last_space_pos = i; dst[dst_len++] = c; assert(dst_len < dst_size); ++line_len; ++i; if (c == '\n') line_len = 0; } dst[dst_len] = '\0'; return dst_len; } void table_printer_indent_and_reflow_text(FILE* stream, const char* src, int width, int first_line_indent, int subsequent_indent) { char buffer[256]; table_printer_reflow_text(buffer, sizeof(buffer), src, width); char* line = strtok(buffer, "\n"); fprintf(stream, "%*s%s\n", first_line_indent, "", line); while (true) { line = strtok(NULL, "\n"); if (!line) break; fprintf(stream, "%*s%s\n", subsequent_indent, "", line); } } void table_printer_print_line(struct table_printer* self, const char* left_text, const char* right_text) { fprintf(self->stream, "%*s", self->left_indent, ""); int field_len = fprintf(self->stream, "%s", left_text); fprintf(self->stream, "%*s", self->left_width - field_len + self->column_offset, ""); int column_indent = self->left_indent + self->left_width + self->column_offset; int column_width = self->max_width - column_indent; table_printer_indent_and_reflow_text(self->stream, right_text, column_width, 0, column_indent); } void table_printer_print_fmtline(struct table_printer* self, const char* right_text, const char* left_format, ...) { char buf[64]; va_list args; va_start(args, left_format); vsnprintf(buf, sizeof(buf), left_format, args); va_end(args); table_printer_print_line(self, buf, right_text); } wayvnc-0.8.0/src/transform-util.c000066400000000000000000000152361456662133500167710ustar00rootroot00000000000000/* * Copyright (c) 2020 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * For code borrowed from wlroots: * Copyright (c) 2017, 2018 Drew DeVault * Copyright (c) 2014 Jari Vetoniemi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include /* Note: This function yields the inverse pixman transform of the * wl_output_transform. */ void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst, enum wl_output_transform src, int width, int height) { #define F1 pixman_fixed_1 switch (src) { case WL_OUTPUT_TRANSFORM_NORMAL: { pixman_transform_t t = {{ { F1, 0, 0 }, { 0, F1, 0 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_90: { pixman_transform_t t = {{ { 0, F1, 0 }, { -F1, 0, height * F1 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_180: { pixman_transform_t t = {{ { -F1, 0, width * F1 }, { 0, -F1, height * F1 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_270: { pixman_transform_t t = {{ { 0, -F1, width * F1 }, { F1, 0, 0 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_FLIPPED: { pixman_transform_t t = {{ { -F1, 0, width * F1 }, { 0, F1, 0 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_FLIPPED_90: { pixman_transform_t t = {{ { 0, F1, 0 }, { F1, 0, 0 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_FLIPPED_180: { pixman_transform_t t = {{ { F1, 0, 0 }, { 0, -F1, height * F1 }, { 0, 0, F1 }, }}; *dst = t; } return; case WL_OUTPUT_TRANSFORM_FLIPPED_270: { pixman_transform_t t = {{ { 0, -F1, width * F1 }, { -F1, 0, height * F1 }, { 0, 0, F1 }, }}; *dst = t; } return; } #undef F1 abort(); } /* Borrowed these from wlroots */ void wv_region_transform(struct pixman_region16* dst, struct pixman_region16* src, enum wl_output_transform transform, int width, int height) { if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { pixman_region_copy(dst, src); return; } int nrects = 0; pixman_box16_t* src_rects = pixman_region_rectangles(src, &nrects); pixman_box16_t* dst_rects = malloc(nrects * sizeof(*dst_rects)); if (dst_rects == NULL) { return; } for (int i = 0; i < nrects; ++i) { switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: dst_rects[i].x1 = src_rects[i].x1; dst_rects[i].y1 = src_rects[i].y1; dst_rects[i].x2 = src_rects[i].x2; dst_rects[i].y2 = src_rects[i].y2; break; case WL_OUTPUT_TRANSFORM_90: dst_rects[i].x1 = height - src_rects[i].y2; dst_rects[i].y1 = src_rects[i].x1; dst_rects[i].x2 = height - src_rects[i].y1; dst_rects[i].y2 = src_rects[i].x2; break; case WL_OUTPUT_TRANSFORM_180: dst_rects[i].x1 = width - src_rects[i].x2; dst_rects[i].y1 = height - src_rects[i].y2; dst_rects[i].x2 = width - src_rects[i].x1; dst_rects[i].y2 = height - src_rects[i].y1; break; case WL_OUTPUT_TRANSFORM_270: dst_rects[i].x1 = src_rects[i].y1; dst_rects[i].y1 = width - src_rects[i].x2; dst_rects[i].x2 = src_rects[i].y2; dst_rects[i].y2 = width - src_rects[i].x1; break; case WL_OUTPUT_TRANSFORM_FLIPPED: dst_rects[i].x1 = width - src_rects[i].x2; dst_rects[i].y1 = src_rects[i].y1; dst_rects[i].x2 = width - src_rects[i].x1; dst_rects[i].y2 = src_rects[i].y2; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: dst_rects[i].x1 = src_rects[i].y1; dst_rects[i].y1 = src_rects[i].x1; dst_rects[i].x2 = src_rects[i].y2; dst_rects[i].y2 = src_rects[i].x2; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: dst_rects[i].x1 = src_rects[i].x1; dst_rects[i].y1 = height - src_rects[i].y2; dst_rects[i].x2 = src_rects[i].x2; dst_rects[i].y2 = height - src_rects[i].y1; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: dst_rects[i].x1 = height - src_rects[i].y2; dst_rects[i].y1 = width - src_rects[i].x2; dst_rects[i].x2 = height - src_rects[i].y1; dst_rects[i].y2 = width - src_rects[i].x1; break; } } pixman_region_fini(dst); pixman_region_init_rects(dst, dst_rects, nrects); free(dst_rects); } enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr) { if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) { tr ^= WL_OUTPUT_TRANSFORM_180; } return tr; } enum wl_output_transform wv_output_transform_compose( enum wl_output_transform tr_a, enum wl_output_transform tr_b) { uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED; uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180; uint32_t rotated; if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) { // When a rotation of k degrees is followed by a flip, the // equivalent transform is a flip followed by a rotation of // -k degrees. rotated = (tr_b - tr_a) & rotation_mask; } else { rotated = (tr_a + tr_b) & rotation_mask; } return flipped | rotated; } wayvnc-0.8.0/src/util.c000066400000000000000000000030721456662133500147530ustar00rootroot00000000000000/* * Copyright (c) 2019 - 2022 Andri Yngvason * Copyright (c) 2022 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "util.h" const char* wayvnc_version = #if defined(PROJECT_VERSION) PROJECT_VERSION; #else "UNKNOWN"; #endif const char* default_ctl_socket_path() { static char buffer[128]; char* xdg_runtime = getenv("XDG_RUNTIME_DIR"); if (xdg_runtime) snprintf(buffer, sizeof(buffer), "%s/wayvncctl", xdg_runtime); else snprintf(buffer, sizeof(buffer), "/tmp/wayvncctl-%d", getuid()); return buffer; } void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by) { ssize_t remainder = *current_len - advance_by; if (remainder < 0) remainder = 0; else if (remainder > 0) memmove(*buffer, *buffer + advance_by, remainder); *current_len = remainder; } wayvnc-0.8.0/src/wayvncctl.c000066400000000000000000000072411456662133500160120ustar00rootroot00000000000000/* * Copyright (c) 2022-2023 Jim Ramsay * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "ctl-client.h" #include "option-parser.h" #define MAYBE_UNUSED __attribute__((unused)) struct wayvncctl { bool do_exit; struct ctl_client* ctl; }; static int wayvncctl_usage(FILE* stream, struct option_parser* options, int rc) { fprintf(stream, "Usage: wayvncctl"); option_parser_print_usage(options, stream); fprintf(stream, " [parameters]\n"); option_parser_print_cmd_summary( "Connects to and interacts with a running wayvnc instance.", stream); option_parser_print_options(options, stream); fprintf(stream, "\n"); ctl_client_print_command_list(stream); return rc; } static int show_version(void) { printf("wayvnc: %s\n", wayvnc_version); return 0; } int main(int argc, char* argv[]) { struct wayvncctl self = { 0 }; bool verbose = false; const char* socket_path = NULL; unsigned flags = 0; static const struct wv_option opts[] = { { .positional = "command", .is_subcommand = true }, { 'S', "socket", "", "Control socket path." }, { 'w', "wait", NULL, "Wait for wayvnc to start up if it's not already running." }, { 'r', "reconnect", NULL, "If disconnected while waiting for events, wait for wayvnc to restart." }, { 'j', "json", NULL, "Output json on stdout." }, { 'V', "version", NULL, "Show version info." }, { 'v', "verbose", NULL, "Be more verbose." }, { 'h', "help", NULL, "Get help (this text)." }, { } }; struct option_parser option_parser; option_parser_init(&option_parser, opts); if (option_parser_parse(&option_parser, argc, (const char* const*)argv) < 0) return wayvncctl_usage(stderr, &option_parser, 1); if (option_parser_get_value(&option_parser, "help")) return wayvncctl_usage(stdout, &option_parser, 0); if (option_parser_get_value(&option_parser, "version")) return show_version(); socket_path = option_parser_get_value(&option_parser, "socket"); flags |= option_parser_get_value(&option_parser, "wait") ? CTL_CLIENT_SOCKET_WAIT : 0; flags |= option_parser_get_value(&option_parser, "reconnect") ? CTL_CLIENT_RECONNECT : 0; flags |= option_parser_get_value(&option_parser, "json") ? CTL_CLIENT_PRINT_JSON : 0; verbose = !!option_parser_get_value(&option_parser, "verbose"); // No command; nothing to do... if (!option_parser_get_value(&option_parser, "command")) return wayvncctl_usage(stdout, &option_parser, 1); ctl_client_debug_log(verbose); self.ctl = ctl_client_new(socket_path, &self); if (!self.ctl) goto ctl_client_failure; int result = ctl_client_run_command(self.ctl, &option_parser, flags); ctl_client_destroy(self.ctl); return result; ctl_client_failure: return 1; } wayvnc-0.8.0/test/000077500000000000000000000000001456662133500140205ustar00rootroot00000000000000wayvnc-0.8.0/test/integration/000077500000000000000000000000001456662133500163435ustar00rootroot00000000000000wayvnc-0.8.0/test/integration/README.md000066400000000000000000000015061456662133500176240ustar00rootroot00000000000000# Integration Testing ## Prerequisites The integration tests currently require that the following tools are installed: - sway (1.8 or later) - lsof - jq - bash - vncdotool Most of these are available in your normal distro package manager, except vncdotool which is a python tool and installable via pip: ``` pip install vncdotool ``` ## Running ``` ./test/integration/integration.sh ``` Two test suites are defined: ### Smoke test Tests basic functionality such as: - Can wayvnc start and connect to wayland? - Does the wayvncctl IPC mechanism work (both control and events)? - Can a VNC client connect and send a keystroke through to sway? ### Multi-output test Tests wayvnc with a multi-output sway, including: - Do we detect additions and removals of outputs? - Do the wayvncctl commands to cycle and switch outputs work? wayvnc-0.8.0/test/integration/integration.sh000077500000000000000000000231531456662133500212310ustar00rootroot00000000000000#!/usr/bin/env bash # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to # Integration test for wayvnc # # For now, this doesn't do much, but does check that some basic functionality isn't DOA # # Prerequisites: # - wayvnc and wayvncctl are built in ../build/, or in the $PATH # - Override by setting $WAYVNC and $WAYVNCCTL or $WAYVNC_BUILD_DIR # - sway and swaymsg are in the $PATH # - Override by setting $SWAY and $SWAYMSG # - jq for parsing json output is in the $PATH # - lsof for TCP port checking is in the $PATH # - vncdo for client testing is in the $PATH # (pip install vncdotool) set -e INTEGRATION_ROOT=$(realpath "$(dirname "$0")") REPO_ROOT=$(realpath "$INTEGRATION_ROOT/../..") WAYVNC_BUILD_DIR=${WAYVNC_BUILD_DIR:-$(realpath "$REPO_ROOT/build")} if [[ -d $WAYVNC_BUILD_DIR ]]; then export PATH=$WAYVNC_BUILD_DIR:$PATH fi echo "Looking for required binaries..." WAYVNC=${WAYVNC:-$(which wayvnc)} WAYVNCCTL=${WAYVNCCTL:-$(which wayvncctl)} SWAY=${SWAY:-$(which sway)} SWAYMSG=${SWAYMSG:-$(which swaymsg)} echo "Found: $WAYVNC $WAYVNCCTL $SWAY $SWAYMSG" $WAYVNC --version $SWAY --version IFS=" .-" read -r _ _ SWAYMAJOR SWAYMINOR _ < <($SWAY --version) VNCDO=${VNCDO:-$(which vncdo)} $VNCDO --version 2>/dev/null export XDG_CONFIG_HOME=$INTEGRATION_ROOT/xdg_config export XDG_RUNTIME_DIR=/tmp/wayvnc-integration-$$ test_setup() { [[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR" mkdir -p "$XDG_RUNTIME_DIR" echo "==============================================" echo "$*" echo "==============================================" } TIMEOUT_COUNTER=0 TIMEOUT_MAXCOUNT=1 TIMEOUT_DELAY=0.1 timeout_init() { TIMEOUT_COUNTER=0 TIMEOUT_MAXCOUNT=${1:-5} TIMEOUT_DELAY=${2:-0.1} } timeout_check() { if [[ $(( TIMEOUT_COUNTER++ )) -gt $TIMEOUT_MAXCOUNT ]]; then return 1 fi sleep "$TIMEOUT_DELAY" } wait_until() { timeout_init 10 local last until last=$(eval "$*" 2>&1); do if ! timeout_check; then echo "Timeout waiting for $*" >&2 printf "%s\n" "$last" >&2 return 1 fi done [[ -z $last ]] || printf "%s\n" "$last" } SWAY_ENV=$XDG_RUNTIME_DIR/sway.env SWAY_PID= start_sway() { echo "Starting sway..." SWAY_LOG=$XDG_RUNTIME_DIR/sway.log WLR_BACKENDS=headless \ WLR_LIBINPUT_NO_DEVICES=1 \ $SWAY &>"$SWAY_LOG" & SWAY_PID=$! wait_until [[ -f "$SWAY_ENV" ]] >/dev/null WAYLAND_DISPLAY=$(grep ^WAYLAND_DISPLAY= "$SWAY_ENV" | cut -d= -f2-) SWAYSOCK=$(grep ^SWAYSOCK= "$SWAY_ENV" | cut -d= -f2-) export WAYLAND_DISPLAY SWAYSOCK echo " sway is managing $WAYLAND_DISPLAY at $SWAYSOCK" } stop_sway() { [[ -z $SWAY_PID ]] && return 0 echo "Stopping sway ($SWAY_PID)" kill "$SWAY_PID" unset SWAY_PID WAYLAND_DISPLAY SWAYSOCK rm -f "$SWAY_ENV" || true } WAYVNC_PID= WAYVNC_ADDRESS=127.0.0.1 WAYVNC_PORT=5999 start_wayvnc() { echo "Starting wayvnc..." WAYVNC_LOG=$XDG_RUNTIME_DIR/wayvnc.log $WAYVNC "$@" -L debug "$WAYVNC_ADDRESS" "$WAYVNC_PORT" &>$WAYVNC_LOG & WAYVNC_PID=$! # Wait for the VNC listening port echo " Started $WAYVNC_PID" wait_until lsof -a -p$WAYVNC_PID -iTCP@$WAYVNC_ADDRESS:$WAYVNC_PORT \ -sTCP:LISTEN >/dev/null echo " Listening on $WAYVNC_ADDRESS:$WAYVNC_PORT" # Wait for the control socket wait_until [[ -S "$XDG_RUNTIME_DIR/wayvncctl" ]] >/dev/null echo " Control socket ready" } stop_wayvnc() { [[ -z $WAYVNC_PID ]] && return 0 echo "Stopping wayvnc ($WAYVNC_PID)" kill "$WAYVNC_PID" unset WAYVNC_PID } WAYVNCCTL_PID= WAYVNCCTL_LOG=$XDG_RUNTIME_DIR/wayvncctl.log WAYVNCCTL_EVENTS=$XDG_RUNTIME_DIR/wayvncctl.events start_wayvncctl_events() { $WAYVNCCTL --verbose --wait --reconnect --json event-receive >"$WAYVNCCTL_EVENTS" 2>"$WAYVNCCTL_LOG" & WAYVNCCTL_PID=$! } stop_wayvncctl_events() { [[ -z $WAYVNCCTL_PID ]] && return 0 echo "Stopping wayvncctl event recorder ($WAYVNCCTL_PID)" kill "$WAYVNCCTL_PID" rm -f "$WAYVNCCTL_EVENTS" || true unset WAYVNCCTL_PID } verify_events() { local expected=("$@") echo "Verifying recorded events" local name i=0 while IFS= read -r EVT; do name=$(jq -r '.method' <<<"$EVT") ex=${expected[$((i++))]} echo " Event: $name=~$ex" [[ $name == "$ex" ]] || return 1 done <"$WAYVNCCTL_EVENTS" if [[ $i -lt ${#expected[@]} ]]; then while [[ $i -lt ${#expected[@]} ]]; do echo " Missing: ${expected[$((i++))]}" done return 1 fi echo "Ok" } cleanup() { result=$? set +e stop_wayvnc stop_sway stop_wayvncctl_events if [[ $result != 0 ]]; then echo echo SWAY LOG echo -------- cat "$SWAY_LOG" echo echo WAYVNC_LOG echo ---------- cat "$WAYVNC_LOG" echo echo WAYVNCCTL_LOG echo ---------- cat "$WAYVNCCTL_LOG" echo echo VNCDO_LOG echo ---------- cat "$VNCDO_LOG" exit fi [[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR" } trap cleanup EXIT test_version_ipc() { echo "Checking version command" local version version=$($WAYVNCCTL --json version) [[ -n $version ]] echo " version IPC returned data" echo "ok" } sway_active_outputs() { $SWAYMSG -t get_outputs | jq 'map(select(.active == true))' } test_output_list_ipc() { local expected_capture=${1:-HEADLESS-1} echo "Checking output-list command" local sway_json wayvnc_json sway_json=$(sway_active_outputs) wayvnc_json=$($WAYVNCCTL --json output-list) local sway_list wayvnc_list sway_list=$(jq -r '.[].name' <<<"$sway_json" | sort -u) wayvnc_list=$(jq -r '.[].name' <<<"$wayvnc_json" | sort -u) [[ "$sway_list" == "$wayvnc_list" ]] echo " output-list IPC matches \`swaymsg -t get_outputs\`" wayvnc_capturing=$(jq -r '.[] | select(.captured == true).name' <<<"$wayvnc_json") echo " Capturing: $wayvnc_capturing=~$expected_capture" [[ $wayvnc_capturing == "$expected_capture" ]] echo "ok" } verify_wayvnc_exited() { wait_until ! kill -0 $WAYVNC_PID >/dev/null unset WAYVNC_PID } test_exit_ipc() { echo "Checking wayvnc-exit command" $WAYVNCCTL wayvnc-exit &>/dev/null verify_wayvnc_exited echo " wayvnc is shutdown" echo "ok" } client() { VNCDO_LOG=$XDG_RUNTIME_DIR/vncdo.log $VNCDO -v --server=$WAYVNC_ADDRESS::$WAYVNC_PORT "$@" &>>$VNCDO_LOG } test_client_connect() { echo "Connecting to send ctrl+t" client key ctrl-t echo " Looking for the result..." [[ -f $XDG_RUNTIME_DIR/test.txt ]] echo "Ok" } output_count() { sway_active_outputs | jq 'length' } sway_output_create() { local initial_count initial_count=$(output_count) echo "Creating new output" $SWAYMSG create_output &>/dev/null # shellcheck disable=SC2016 wait_until [[ '$(output_count)' -gt "$initial_count" ]] echo " $(sway_active_outputs | jq -r '.[-1].name')" echo "Ok" } sway_output_is_gone() { local output=$1 $SWAYMSG -t get_outputs | jq -e "all(.name != \"$output\")" } sway_output_destroy() { local output=$1 echo "Removing output $output" $SWAYMSG output "$output" unplug >/dev/null wait_until sway_output_is_gone "$output" >/dev/null echo "Ok" } smoke_test() { test_setup "smoke test" start_sway start_wayvncctl_events start_wayvnc test_version_ipc wait_until verify_events \ wayvnc-startup test_output_list_ipc test_client_connect wait_until verify_events \ wayvnc-startup \ client-connected \ client-disconnected test_exit_ipc wait_until verify_events \ wayvnc-startup \ client-connected \ client-disconnected \ wayvnc-shutdown stop_wayvncctl_events stop_sway } multioutput_test() { test_setup "multioutput test" start_sway sway_output_create start_wayvncctl_events # Ensure outout selection commandline works start_wayvnc -o HEADLESS-1 wait_until verify_events \ wayvnc-startup test_output_list_ipc HEADLESS-1 # Test outout-cycle $WAYVNCCTL output-cycle wait_until verify_events \ wayvnc-startup \ capture-changed test_output_list_ipc HEADLESS-2 # Test outout-cycle wraps $WAYVNCCTL output-cycle wait_until verify_events \ wayvnc-startup \ capture-changed \ capture-changed test_output_list_ipc HEADLESS-1 # Add a new output, then switch to it sway_output_create wait_until test_output_list_ipc HEADLESS-1 $WAYVNCCTL output-set HEADLESS-3 wait_until verify_events \ wayvnc-startup \ capture-changed \ capture-changed \ capture-changed test_output_list_ipc HEADLESS-3 if [[ $SWAYMAJOR -le 1 && $SWAYMINOR -lt 8 ]]; then echo "Warning: sway-1.8 or later is needed for complete testing" return 0 fi # Remove the output, and make sure we fallback properly sway_output_destroy HEADLESS-3 wait_until verify_events \ wayvnc-startup \ capture-changed \ capture-changed \ capture-changed \ capture-changed wait_until test_output_list_ipc HEADLESS-1 stop_sway verify_wayvnc_exited stop_wayvncctl_events } smoke_test multioutput_test wayvnc-0.8.0/test/integration/xdg_config/000077500000000000000000000000001456662133500204525ustar00rootroot00000000000000wayvnc-0.8.0/test/integration/xdg_config/sway/000077500000000000000000000000001456662133500214355ustar00rootroot00000000000000wayvnc-0.8.0/test/integration/xdg_config/sway/config000066400000000000000000000002021456662133500226170ustar00rootroot00000000000000xwayland disable bindsym Ctrl+t exec bash -c "echo OK > $XDG_RUNTIME_DIR/test.txt" exec bash -c "env > $XDG_RUNTIME_DIR/sway.env" wayvnc-0.8.0/test/meson.build000066400000000000000000000005641456662133500161670ustar00rootroot00000000000000test('table-printer', executable('table-printer', [ 'table-printer-test.c', '../src/table-printer.c', ], include_directories: inc, dependencies: [ ], )) test('option-parser', executable('option-parser', [ 'option-parser-test.c', '../src/option-parser.c', '../src/table-printer.c', '../src/strlcpy.c', ], include_directories: inc, dependencies: [ ], )) wayvnc-0.8.0/test/option-parser-test.c000066400000000000000000000227311456662133500177500ustar00rootroot00000000000000#include "tst.h" #include "option-parser.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) static const struct wv_option options[] = { { .positional = "first" }, { .positional = "second" }, { .positional = "third" }, { .positional = "command", .is_subcommand = true }, { 'a', "option-a", NULL, "Description of a" }, { 'b', "option-b", NULL, "Description of b" }, { 'v', "value-option", "value", "Description of v" }, { }, }; static const struct wv_option default_options[] = { { .positional = "first" }, { .positional = "second", .default_ = "second_default" }, { 'v', "value-option", "value", "Description of v", .default_ = "v_default" }, { }, }; static int test_simple(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-a", "-b", "pos 1", "pos 2", }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first")); ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second")); ASSERT_FALSE(option_parser_get_value(&parser, "third")); ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "option-b")); ASSERT_FALSE(option_parser_get_value(&parser, "value-option")); ASSERT_INT_EQ(0, parser.remaining_argc); ASSERT_FALSE(parser.remaining_argv); return 0; } static int test_extra_positional_args(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "pos 1", "pos 2", "-a", "pos 3", "-b", "pos 4", }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first")); ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second")); ASSERT_STR_EQ("pos 3", option_parser_get_value(&parser, "third")); ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "option-b")); ASSERT_FALSE(option_parser_get_value(&parser, "value-option")); ASSERT_INT_EQ(1, parser.remaining_argc); ASSERT_STR_EQ("pos 4", parser.remaining_argv[0]); return 0; } static int test_short_value_option_with_space(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-v", "value" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option")); return 0; } static int test_short_value_option_without_space(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-vvalue" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option")); return 0; } static int test_short_value_option_with_eq(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-v=value" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option")); return 0; } static int test_long_value_option_with_space(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "--value-option", "value" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option")); return 0; } static int test_long_value_option_without_space(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "--value-option=value" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option")); return 0; } static int test_multi_short_option(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-ab" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "b")); return 0; } static int test_multi_short_option_with_value(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-abvthe-value" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "b")); ASSERT_STR_EQ("the-value", option_parser_get_value(&parser, "v")); return 0; } static int test_stop(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "exec", "-a", "--", "-b"}; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_FALSE(option_parser_get_value(&parser, "b")); ASSERT_INT_EQ(1, parser.remaining_argc); ASSERT_STR_EQ("-b", parser.remaining_argv[0]); return 0; } static int test_unknown_short_option(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-x" }; ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); return 0; } static int test_unknown_long_option(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "--an-unknown-long-option" }; ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); return 0; } static int test_missing_short_value(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-v" }; ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); return 0; } static int test_missing_long_value(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "--value-option" }; ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); return 0; } static int test_subcommand_without_arguments(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-ab", "first", "second", "third", "do-stuff" }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command")); ASSERT_INT_EQ(1, parser.remaining_argc); ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]); return 0; } static int test_subcommand_with_arguments(void) { struct option_parser parser; option_parser_init(&parser, options); const char* argv[] = { "executable", "-ab", "first", "second", "third", "do-stuff", "--some-option", "another-argument"}; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command")); ASSERT_INT_EQ(3, parser.remaining_argc); ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]); ASSERT_STR_EQ("another-argument", parser.remaining_argv[2]); return 0; } static int test_defaults_not_set(void) { struct option_parser parser; option_parser_init(&parser, default_options); const char* argv[] = { "executable", "pos 1", }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first")); ASSERT_STR_EQ("second_default", option_parser_get_value(&parser, "second")); ASSERT_FALSE(option_parser_get_value_no_default(&parser, "second")); ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "value-option")); ASSERT_FALSE(option_parser_get_value_no_default(&parser, "value-option")); ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "v")); ASSERT_FALSE(option_parser_get_value_no_default(&parser, "v")); ASSERT_INT_EQ(0, parser.remaining_argc); ASSERT_FALSE(parser.remaining_argv); return 0; } static int test_defaults_overridden(void) { struct option_parser parser; option_parser_init(&parser, default_options); const char* argv[] = { "executable", "pos 1", "pos 2", "-v", "v_set", }; ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first")); ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second")); ASSERT_STR_EQ("pos 2", option_parser_get_value_no_default(&parser, "second")); ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "value-option")); ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "value-option")); ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "v")); ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "v")); ASSERT_INT_EQ(0, parser.remaining_argc); ASSERT_FALSE(parser.remaining_argv); return 0; } int main() { int r = 0; RUN_TEST(test_simple); RUN_TEST(test_extra_positional_args); RUN_TEST(test_short_value_option_with_space); RUN_TEST(test_short_value_option_without_space); RUN_TEST(test_short_value_option_with_eq); RUN_TEST(test_long_value_option_with_space); RUN_TEST(test_long_value_option_without_space); RUN_TEST(test_multi_short_option); RUN_TEST(test_multi_short_option_with_value); RUN_TEST(test_stop); RUN_TEST(test_unknown_short_option); RUN_TEST(test_unknown_long_option); RUN_TEST(test_missing_short_value); RUN_TEST(test_missing_long_value); RUN_TEST(test_subcommand_without_arguments); RUN_TEST(test_subcommand_with_arguments); RUN_TEST(test_defaults_not_set); RUN_TEST(test_defaults_overridden); return r; } wayvnc-0.8.0/test/table-printer-test.c000066400000000000000000000107761456662133500177240ustar00rootroot00000000000000#include "tst.h" #include "table-printer.h" #include static int test_reflow_text(void) { char buf[20]; const char* src = "one two three four"; int len; len = table_printer_reflow_text(buf, sizeof(buf), src, 20); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two three four", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 18); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two three four", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 17); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two three\nfour", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 10); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two\nthree four", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 8); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two\nthree\nfour", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 7); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one two\nthree\nfour", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 6); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf); len = table_printer_reflow_text(buf, sizeof(buf), src, 5); ASSERT_INT_EQ(18, len); ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf); // width <= 4 cause aborts (if any word length > width) return 0; } static int test_reflow_multiline(void) { char buf[20]; const char* src = "one two\nthree four"; table_printer_reflow_text(buf, sizeof(buf), src, 20); ASSERT_STR_EQ("one two\nthree four", buf); table_printer_reflow_text(buf, sizeof(buf), src, 18); ASSERT_STR_EQ("one two\nthree four", buf); table_printer_reflow_text(buf, sizeof(buf), src, 17); ASSERT_STR_EQ("one two\nthree four", buf); table_printer_reflow_text(buf, sizeof(buf), src, 10); ASSERT_STR_EQ("one two\nthree four", buf); table_printer_reflow_text(buf, sizeof(buf), src, 9); ASSERT_STR_EQ("one two\nthree\nfour", buf); table_printer_reflow_text(buf, sizeof(buf), src, 7); ASSERT_STR_EQ("one two\nthree\nfour", buf); table_printer_reflow_text(buf, sizeof(buf), src, 6); ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf); table_printer_reflow_text(buf, sizeof(buf), src, 5); ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf); return 0; } static int test_indent_and_reflow(void) { size_t len; char* buf; FILE* stream; stream = open_memstream(&buf, &len); table_printer_indent_and_reflow_text(stream, "one two three four", 7, 2, 4); fclose(stream); // strlen(src)=18 + first=2 + subsequent=(2x4) + newline=1 ASSERT_INT_EQ(29, len); ASSERT_STR_EQ(" one two\n three\n four\n", buf); free(buf); return 0; } static int test_defaults(void) { struct table_printer one; table_printer_init(&one, stdout, 1); table_printer_set_defaults(20, 2, 2); struct table_printer two; table_printer_init(&two, stderr, 2); ASSERT_INT_EQ(80, one.max_width); ASSERT_INT_EQ(4, one.left_indent); ASSERT_INT_EQ(8, one.column_offset); ASSERT_INT_EQ(1, one.left_width); ASSERT_PTR_EQ(stdout, one.stream); ASSERT_INT_EQ(20, two.max_width); ASSERT_INT_EQ(2, two.left_indent); ASSERT_INT_EQ(2, two.column_offset); ASSERT_INT_EQ(2, two.left_width); ASSERT_PTR_EQ(stderr, two.stream); return 0; } static int test_print_line(void) { size_t len; char* buf; struct table_printer printer = { .max_width = 20, .left_indent = 2, .left_width = 6, .column_offset = 2, }; printer.stream = open_memstream(&buf, &len); table_printer_print_line(&printer, "left", "right"); fclose(printer.stream); ASSERT_STR_EQ(" left right\n", buf); free(buf); printer.stream = open_memstream(&buf, &len); table_printer_print_line(&printer, "left", "right side will wrap"); fclose(printer.stream); ASSERT_STR_EQ(" left right side\n" " will wrap\n", buf); free(buf); return 0; } static int test_print_fmtline(void) { size_t len; char* buf; struct table_printer printer = { .max_width = 20, .left_indent = 2, .left_width = 6, .column_offset = 2, }; printer.stream = open_memstream(&buf, &len); table_printer_print_fmtline(&printer, "right", "left"); fclose(printer.stream); ASSERT_STR_EQ(" left right\n", buf); free(buf); printer.stream = open_memstream(&buf, &len); table_printer_print_fmtline(&printer, "right side will wrap", "left%d", 2); fclose(printer.stream); ASSERT_STR_EQ(" left2 right side\n" " will wrap\n", buf); free(buf); return 0; } int main() { int r = 0; RUN_TEST(test_reflow_text); RUN_TEST(test_reflow_multiline); RUN_TEST(test_indent_and_reflow); RUN_TEST(test_defaults); RUN_TEST(test_print_line); RUN_TEST(test_print_fmtline); return r; } wayvnc-0.8.0/util/000077500000000000000000000000001456662133500140165ustar00rootroot00000000000000wayvnc-0.8.0/util/latency_report.py000077500000000000000000000041721456662133500174310ustar00rootroot00000000000000#!/usr/bin/python import os import math stream = os.popen('perf script -F time,event') is_in_update_fb = False class StateTracker: def __init__(self, name, src, enter, leave): self.is_active = False self.name = name self.src = src self.enter = enter self.leave = leave self.n = 0 self.dt_sum = 0.0 self.dt_square_sum = 0.0 self.dt_max = 0.0 self.dt_min = math.inf def add_dt(self, dt): self.n += 1 self.dt_sum += dt self.dt_square_sum += dt ** 2 self.dt_max = max(self.dt_max, dt) self.dt_min = min(self.dt_min, dt) def apply(self, src, event, t): if self.is_active: if (src, event) == (self.src, self.leave): self.is_active = False self.add_dt(t - self.t0) else: if (src, event) == (self.src, self.enter): self.is_active = True self.t0 = t def avg(self): return self.dt_sum / self.n def var(self): return self.dt_square_sum / self.n - self.avg() ** 2 def stddev(self): return math.sqrt(self.var()) def report(self): if self.n == 0: return print('{}:'.format(self.name)) print('\tMin, max: {:.1f} ms, {:.1f} ms'.format(self.dt_min * 1e3, self.dt_max * 1e3)) print('\tAverage, std.dev.: {:.1f} ms, {:.1f} ms'.format(self.avg() * 1e3, self.stddev() * 1e3)) trackers = [ StateTracker('Framebuffer update', 'sdt_neatvnc', 'update_fb_start', 'update_fb_done'), StateTracker('Framebuffer update (only sending)', 'sdt_neatvnc', 'send_fb_start', 'send_fb_done'), StateTracker('Screencopy', 'sdt_wayvnc', 'screencopy_start', 'screencopy_ready'), StateTracker('Refine damage', 'sdt_wayvnc', 'refine_damage_start', 'refine_damage_end'), StateTracker('Render', 'sdt_wayvnc', 'render_start', 'render_end'), ] for line in stream: [t, src, event, _] = line.replace(' ', '').split(':') t = float(t) for tracker in trackers: tracker.apply(src, event, t) for tracker in trackers: tracker.report() print() wayvnc-0.8.0/util/trace.sh000077500000000000000000000006771456662133500154650ustar00rootroot00000000000000#!/bin/bash set -e EVENTS="sdt_wayvnc:* sdt_neatvnc:*" delete_all_events() { for e in $EVENTS; do sudo perf probe -d "$e" || true done } add_all_events() { for e in $EVENTS; do sudo perf probe "$e" done } sudo perf buildid-cache -a build/wayvnc sudo perf buildid-cache -a build/subprojects/neatvnc/libneatvnc.so delete_all_events add_all_events trap "sudo chown $USER:$USER perf.data*" EXIT sudo perf record -aR -e ${EVENTS// /,} wayvnc-0.8.0/util/valgrind.sh000077500000000000000000000002421456662133500161610ustar00rootroot00000000000000#!/bin/bash SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" valgrind --leak-check=full \ --show-leak-kinds=all \ --suppressions=$SCRIPT_DIR/valgrind.supp \ $@ wayvnc-0.8.0/util/valgrind.supp000066400000000000000000000000741456662133500165360ustar00rootroot00000000000000{ Ignore dlopen bug. Memcheck:Leak ... fun:_dl_* ... } wayvnc-0.8.0/wayvnc.pam000066400000000000000000000001701456662133500150450ustar00rootroot00000000000000auth required pam_unix.so nodelay deny=3 unlock_time=600 account required pam_unix.so nodelay deny=3 unlock_time=600 wayvnc-0.8.0/wayvnc.scd000066400000000000000000000274571456662133500150620ustar00rootroot00000000000000wayvnc(1) # NAME wayvnc - A VNC server for wlroots based Wayland compositors. # SYNOPSIS *wayvnc* [options] [address [port]] # OPTIONS *-C, --config=* Select a config file. *-g,--gpu* Enable features that require GPU. *-o, --output=* Select output to capture. *-k, --keyboard=[-variant]* Select keyboard layout. The variant can be appended if needed. *-s, --seat=* Select seat by name. *-S, --socket=* Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl or /tmp/wayvncctl-$UID *-r, --render-cursor* Enable overlay cursor rendering. *-f, --max-fps=* Set the rate limit (default 30). *-p, --show-performance* Show performance counters. *-u, --unix-socket* Create a UNIX domain socket instead of TCP, treating the address as a path. *-d, --disable-input* Disable all remote input. This allows using wayvnc without compositor support of virtual mouse / keyboard protocols. *-V, --version* Show version info. *-v,--verbose* Be more verbose. Same as setting `--log-level=info`. *-w,--websocket* Create a websocket. *-L,--log-level* Set log level. The levels are: error, warning, info, debug, trace and quiet. *-h, --help* Get help. # DESCRIPTION This is a VNC server for wlroots based Wayland compositors. It attaches to a running Wayland session, creates virtual input devices and exposes a single display via the RFB protocol. The Wayland session may be a headless one, so it is also possible to run wayvnc without a physical display attached. ## MULTIPLE OUTPUTS If the Wayland session consists of multiple outputs, only one will be captured. By default this will be the first one, but can be specified by the _-o_ command line argument. The argument accepts the short name such as _eDP-1_ or _DP-4_. Running wayvnc in verbose mode (_-v_) will display the names of all outputs on startup, or you can query them at runtime via the *wayvncctl output-list* command. You can also change which output is being captured on the fly via the *wayvncctl output-set* command. # CONFIGURATION wayvnc searches for a config file in the location ~/$XDG_CONFIG_HOME/wayvnc/config or if $XDG_CONFIG_HOME is not set ~/.config/wayvnc/config ## SYNTAX The configuration file is composed of key-value pairs separated with an *equal* sign. Whitespace around either the key or the value is insignificant and is not considered to be part of the key or the value. ## KEYWORDS *address* The address to which the server shall bind, e.g. 0.0.0.0 or localhost. *certificate_file* The path to the certificate file for encryption. Only applicable when *enable_auth*=true. *enable_auth* Enable authentication and encryption. Setting this value to *true* requires also setting *certificate_file*, *private_key_file*, *username* and *password*. *password* Choose a password for authentication. *port* The port to which the server shall bind. Default is 5900. *private_key_file_file* The path to the private key file for TLS encryption. Only applicable when *enable_auth*=true. *relax_encryption* Don't require encryption after the user has been authenticated. This enables some security types such as Apple Diffie-Hellman. *rsa_private_key_file* The path to the private key file for RSA-AES encryption. Only applicable when *enable_auth*=true. *username* Choose a username for authentication. *use_relative_paths* Make file paths relative to the location of the config file. *xkb_layout* The keyboard layout to use for key code lookup. Default: _XKB_DEFAULT_LAYOUT_ or system default. *xkb_model* The keyboard model by which to interpret keycodes and LEDs. Default: "pc105" *xkb_options* A comma separated list of options, through which the user specifies non-layout related preferences such as which key is the Compose key. Default: _XKB_DEFAULT_OPTIONS_ or system default. *xkb_rules* The rules file describes how to interpret the values of the model, layout, variant and options fields. Default: _XKB_DEFAULT_RULES_ or system default. *xkb_variant* The keyboard variant to use for keycode lookup. Default: _XKB_DEFAULT_VARIANT_ or system default. ## EXAMPLE ``` use_relative_paths=true address=0.0.0.0 enable_auth=true username=luser password=p455w0rd rsa_private_key_file=rsa_key.pem private_key_file=tls_key.pem certificate_file=tls_cert.pem ``` # WAYVNCCTL CONTROL SOCKET To facilitate runtime interaction and control, wayvnc opens a unix domain socket at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A client can connect and exchange json-formatted IPC messages to query and control the running wayvnc instance. ## IPC COMMANDS _HELP_ The *help* command, when issued without any parameters, lists the names of all available commands. If an optional *command* parameter refers to one of those commands by name, the response data will be a detailed description of that command and its parameters. _EVENT-RECEIVE_ The *event-receive* command registers for asynchronous server events. See the _EVENTS_ section below for details on the event message format, and the _IPC EVENTS_ section below for a description of all possible server events. Event registration registers for all available server events and is scoped to the current connection only. If a client disconnects and reconnects, it must re-register for events. _CLIENT-LIST_ The *client-list* command retrieves a list of all VNC clients currently connected to wayvnc. _CLIENT-DISCONNECT_ The *client-disconnect* command disconnects a single VNC client. Parameters: *id* Required: The ID of the client to disconnect. This ID can be found from the _GET-CLIENTS_ command or receipt of a _CLIENT-CONNECTED_ event. _OUTPUT-LIST_ The *output-list* command retrieves a list of all outputs known to wayvnc and whether or not each one is currently being captured. _OUTPUT-CYCLE_ For multi-output wayland displays, the *output-cycle* command switches which output is actively captured by wayvnc. Running this once will switch to the next available output. If no more outputs are available, it cycles back to the first again. _OUTPUT-SET_ For multi-output wayland displays, the *output-set* command switches which output is actively captured by wayvnc by name. *output-name=name* Required: The name of the output to capture next. _VERSION_ The *version* command queries the running wayvnc instance for its version information. Much like the _-V_ option, the response data will contain the version numbers of wayvnc, as well as the versions of the neatvnc and aml components. _WAYVNC-EXIT_ The *wayvnc-exit* command disconnects all clients and shuts down wayvnc. ## IPC EVENTS _CAPTURE_CHANGED_ The *capture-changed* event is sent when the currently captured output changes. Parameters: *output=...* The name of the output now being captured. _CLIENT-CONNECTED_ The *client-connected* event is sent when a new VNC client connects to wayvnc. Parameters: *id=...* A unique identifier for this client. *connection_count=...* The total number of connected VNC clients including this one. *address=...* The IP address of this client. May be null. *username=...* The username used to authenticate this client. May be null. _CLIENT-DISCONNECTED_ The *client-disconnected* event is sent when a VNC cliwnt disconnects from wayvnc. Parameters: *id=...* A unique identifier for this client. *connection_count=...* The total number of connected VNC clients not including this one. *address=...* The IP address of this client. May be null. *username=...* The username used to authenticate this client. May be null. ## IPC MESSAGE FORMAT The *wayvncctl(1)* command line utility will construct properly-formatted json ipc messages, but any client will work. The client initiates the connection and sends one or more request objects, each of which will receive a corresponding response object. *Note* This message format is unstable and may change substantially over the next few releases. _REQUEST_ The general form of a json-ipc request message is: ``` { "method": "command-name", "id": 123, "params": { "key1": "value1", "key2": "value2", } } ``` The *method* is the name of the command to be executed. Use the *help* method to query a list of all valid method names. The *id* field is optional and may be any valid json number or string. If provided, the response to this request will contain the identical id value, which the client may use to coordinate multiple requests and responses. The *params* object supplies optional parameters on a per-method basis, and may be omitted if empty. _RESPONSE_ ``` { "id": 123, "code": 0, "data": { ... } } ``` If the request had an id, the response will have an identical value for *id*. The numerical *code* provides an indication of how the request was handled. A value of *0* always signifies success. Any other value means failure, and varies depending on the method in question. The *data* object contains method-specific return data. This may be structured data in response to a query, a simple error string in the case of a failed request, or it may be omitted entirely if the error code alone is sufficient. _EVENTS_ Events are aaynchronous messages sent from a server to all registered clients. The message format is identical to a _REQUEST_, but without an "id" field, and a client must not send a response. Example event message: ``` { "method": "event-name", "params": { "key1": "value1", "key2": "value2", } } ``` In order to receive any events, a client must first register to receive them by sending a _event-receive_ request IPC. Once the success response has been sent by the server, the client must expect that asynchronous event messages may be sent by the server at any time, even between a request and the associated response. # ENVIRONMENT The following environment variables have an effect on wayvnc: _WAYLAND_DISPLAY_ Specifies the name of the Wayland display that the compositor to which wayvnc shall bind is running on. _XDG_CONFIG_HOME_ Specifies the location of configuration files. _XDG_RUNTIME_DIR_ Specifies the default location for the wayvncctl control socket. # FAQ *Wayvnc complains that a protocol is not supported* The error might look like this: ``` wl_registry@2: error 0: invalid version for global zxdg_output_manager_v1 (4): have 2, wanted 3 ERROR: ../src/main.c: 388: Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page. ERROR: ../src/main.c: 1024: Failed to initialise wayland ``` This means that your wayland compositor does not implement the screencopy protocol and wayvnc won't work with it. Screencopy is implemented by wlroots based compositors such as Sway and Wayfire. *How can I run wayvnc in headless mode/over an SSH session?* Set the environment variables _WLR_BACKENDS_=headless and _WLR_LIBINPUT_NO_DEVICES_=1 before starting the compositor, then run wayvnc as normal. *How can I pass my mod-key from Sway to the remote desktop session?* Create an almost empty mode in your sway config. Example: ``` mode passthrough { bindsym $mod+Pause mode default } bindsym $mod+Pause mode passthrough ``` This makes it so that when you press $mod+Pause, all keybindings, except the one to switch back, are disabled. *Not all symbols show up when I'm typing. What can I do to fix this?* Try setting the keyboard layout in wayvnc to the one that most closely matches the keyboard layout that you're using on the client side. An exact layout isn't needed, just one that has all the symbols that you use. *How do I enable the Compose key?* Set "xkb_options=compose:menu" in the config file. Any key that is not otherwise used will work. There just needs to be some key for wayvnc to match against. # AUTHORS Maintained by Andri Yngvason . Up-to-date sources can be found at https://github.com/any1/wayvnc and bugs reports or patches can be submitted to GitHub's issue tracker. # SEE ALSO *wayvncctl(1)* wayvnc-0.8.0/wayvncctl.scd000066400000000000000000000077771456662133500155700ustar00rootroot00000000000000wayvncctl(1) # NAME wayvncctl - A command line control client for wayvnc(1) # SYNOPSIS *wayvncctl* [options] [command [--parameter value ...]] # OPTIONS *-S, --socket=* Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl or /tmp/wayvncctl-$UID *-w, --wait* Wait for wayvnc to start up if it's not already running. Default: Exit immediately with an error if wayvnc is not running. *-r,--reconnect* If disconnected while waiting for events, wait for wayvnc to restart and re-register for events. Default: Exit when wayvnc exits. *-j, --json* Produce json output to stdout. *-V, --version* Show version info. *-v,--verbose* Be more verbose. *-h, --help* Get help about the wayvncctl command itself (lists these options). Does not connect to the wayvncctl control socket. # DESCRIPTION *wayvnc(1)* allows runtime interaction via a unix socket json-ipc mechanism. This command line utility provides easy interaction with those commands. This command is largely self-documenting: - Running *wayvncctl --help* lists all supported IPC commands. - Running *wayvncctl command-name --help* returns a description of the given command and its available parameters. - Running *wayvncctl event-receive --help* includes a list of all supported event names. - Running *wayvncctl event-receive --show=event-name* returns a description of the given event and expected data fields. # ASYNCHRONOUS EVENTS While *wayvncctl* normally terminates after sending one request and receiving the corresponding reply, the *event-receive* command acts differently. Instead of exiting immediately, *wayvncctl* waits for any events from the server, printing each to stdout as they arrive. This mode of operation will block until either it receives a signal to terminate, or until the wayvnc server terminates. In _--json_ mode, each event is printed on one line, with a newline character at the end, for ease in scripting: ``` $ wayvncctl --json event-receive {"method":"client-connected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":1}} {"method":"client-disconnected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":0}} ``` The default human-readible output is a multi-line yaml-like format, with two newline characters between each event: ``` $ wayvncctl event-receive client-connected: id: 0x10ef670 address: 192.168.1.18 connection_count: 1 client-disconnected: id: 0x10ef670 address: 192.168.1.18 connection_count: 0 ``` ## SPECIAL LOCAL EVENT TYPES Especially useful when using _--wait_ or _--reconnect_ mode, wayvncctl will generate 2 additional events not documented in *wayvnc(1)*: *wayvnc-startup* Sent when a successful wayvnc control connection is established and event registration has succeeded, both upon initial startup and on subsequent registrations with _--reconnect_. No paramerers. *wayvnc-shutdown* Sent when the wayvnc control connection is dropped, usually due to wayvnc exiting. No paramerers. # EXAMPLES Get help on the "output-set" IPC command: ``` $ wayvncctl output-set --help Usage: wayvncctl [options] output-set [params] ... ``` Cycle to the next active output: ``` $ wayvncctl output-cycle ``` Get json-formatted version information: ``` $ wayvncctl --json version {"wayvnc":"v0.5.0","neatvnc":"v0.5.1","aml":"v0.2.2"} ``` A script that takes an action for each client connect and disconnect event: ``` #!/bin/bash connection_count_now() { echo "Total clients: $1" } while IFS= read -r EVT; do case "$(jq -r '.method' <<<"$EVT")" in client-*onnected) count=$(jq -r '.params.connection_count' <<<"$EVT") connection_count_now "$count" ;; wayvnc-shutdown) connection_count_now 0 ;; esac done < <(wayvncctl --wait --reconnect --json event-receive) ``` # ENVIRONMENT The following environment variables have an effect on wayvncctl: _XDG_RUNTIME_DIR_ Specifies the default location for the wayvncctl control socket. # SEE ALSO *wayvnc(1)*