pax_global_header 0000666 0000000 0000000 00000000064 15223766131 0014520 g ustar 00root root 0000000 0000000 52 comment=ae53f076a83ad1ecd0d2adcaa063674a632bfe0f
any1-wayvnc-ae53f07/ 0000775 0000000 0000000 00000000000 15223766131 0014311 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/.builds/ 0000775 0000000 0000000 00000000000 15223766131 0015651 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/.builds/archlinux.yml 0000664 0000000 0000000 00000002135 15223766131 0020372 0 ustar 00root root 0000000 0000000 image: archlinux
packages:
- base-devel
- gdb
- libglvnd
- libxkbcommon
- pixman
- gnutls
- jansson
- wayland
- wayland-protocols
- meson
# runtime deps for integration testing:
- sway
- jq
- openbsd-netcat
- python-pycryptodomex # needed by vncdotool
- python-cryptography # needed by vncdotool
- vncdotool
sources:
- http://github.com/any1/wayvnc
- http://github.com/any1/neatvnc
- http://github.com/any1/aml
tasks:
- aml-build: |
cd aml
meson setup build --prefix=/usr --buildtype=debug
ninja -C build
sudo ninja -C build install
- neatvnc-build: |
cd neatvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true -Dexamples=true
ninja -C build
sudo ninja -C build install
- wayvnc-build: |
cd wayvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true
ninja -C build
- neatvnc-test: |
cd neatvnc
ninja -C build test
- wayvnc-test: |
cd wayvnc
ninja -C build test
- wayvnc-integration-test: |
cd wayvnc
./test/integration/integration.sh
any1-wayvnc-ae53f07/.builds/freebsd.yml 0000664 0000000 0000000 00000002346 15223766131 0020013 0 ustar 00root root 0000000 0000000 image: freebsd/latest
packages:
- devel/gdb
- 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
- shells/bash
- devel/py-pip
- security/py-cryptography
sources:
- http://github.com/any1/wayvnc
- http://github.com/any1/neatvnc
- http://github.com/any1/aml
tasks:
- pip-vncdotool: |
sudo pip install vncdotool
- aml-build: |
cd aml
meson setup build --prefix=/usr --buildtype=debug
ninja -C build
sudo ninja -C build install
- neatvnc-build: |
cd neatvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true -Dexamples=true
ninja -C build
sudo ninja -C build install
- wayvnc-build: |
cd wayvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true
ninja -C build
- neatvnc-test: |
cd neatvnc
ninja -C build test
- wayvnc-test: |
cd wayvnc
ninja -C build test
- wayvnc-integration-test: |
cd wayvnc
./test/integration/integration.sh
any1-wayvnc-ae53f07/.builds/ubuntu.yml 0000664 0000000 0000000 00000002050 15223766131 0017713 0 ustar 00root root 0000000 0000000 image: ubuntu/plucky
packages:
- gdb
- jq
- libavcodec-dev
- libavfilter-dev
- libavutil-dev
- libdrm-dev
- libgnutls28-dev
- libjansson-dev
- libpam0g-dev
- libturbojpeg0-dev
- libwlroots-dev
- libxkbcommon-dev
- lsof
- meson
- netcat-openbsd
- scdoc
- sway
- vncdotool
sources:
- http://github.com/any1/wayvnc
- http://github.com/any1/neatvnc
- http://github.com/any1/aml
tasks:
- aml-build: |
cd aml
meson setup build --prefix=/usr --buildtype=debug
ninja -C build
sudo ninja -C build install
- neatvnc-build: |
cd neatvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true -Dexamples=true
ninja -C build
sudo ninja -C build install
- wayvnc-build: |
cd wayvnc
meson setup build --prefix=/usr --buildtype=debug -Dtests=true
ninja -C build
- neatvnc-test: |
cd neatvnc
ninja -C build test
- wayvnc-test: |
cd wayvnc
ninja -C build test
- wayvnc-integration-test: |
cd wayvnc
./test/integration/integration.sh
any1-wayvnc-ae53f07/.github/ 0000775 0000000 0000000 00000000000 15223766131 0015651 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15223766131 0020034 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000002560 15223766131 0022531 0 ustar 00root root 0000000 0000000 ---
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`
- Provide context, including but not limited to
- Command line arguments
- Special environment variables, if any
- Which wayland compositor are you using? Which version?
- What VNC client are you using?
- Did you configure your VNC client in a specific manner?
- Linux distro/operating system
- Kernel version
- Graphics drivers
- Try to reproduce the problem while capturing a **trace log:**
- `wayvnc -Ltrace | tee wayvnc-bug.log`
- Get the **stack trace**, in case of a crash:
- If you 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
- Try to think about your problem beyond these instructions and include
whatever information that you believe will help to resolve the issue
any1-wayvnc-ae53f07/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000312 15223766131 0022020 0 ustar 00root root 0000000 0000000 blank_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"
any1-wayvnc-ae53f07/.github/ISSUE_TEMPLATE/enhancement.md 0000664 0000000 0000000 00000000113 15223766131 0022636 0 ustar 00root root 0000000 0000000 ---
name: Enhancements
about: New functionality
labels: 'enhancement'
---
any1-wayvnc-ae53f07/.github/pull_request_template.md 0000664 0000000 0000000 00000000072 15223766131 0022611 0 ustar 00root root 0000000 0000000 Please read CONTRIBUTING.md before making a pull request.
any1-wayvnc-ae53f07/.gitignore 0000664 0000000 0000000 00000000136 15223766131 0016301 0 ustar 00root root 0000000 0000000 build
subprojects
*.swp
.clang_complete
.ycm_extra_conf.py
perf.*
*.pem
.vimrc
.cache
sandbox
any1-wayvnc-ae53f07/CONTRIBUTING.md 0000664 0000000 0000000 00000012211 15223766131 0016537 0 ustar 00root root 0000000 0000000 # Contributing to wayvnc
## LLM/AI
It's OK to use an LLM to generate code, but you need to fully understand the
generated code in detail and as a whole. Beware that LLMs often present
falsehoods as truths. To counter this, you need to do your own research to
verify that what the LLM gives you is correct.
For example, if you are implementing a feature that is described on the
[RFB specification](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst),
you need to read and understand the relevant sections of the specification. You
can use an LLM to help you find the relevant sections, but do not rely on
summaries alone.
## 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.
any1-wayvnc-ae53f07/COPYING 0000664 0000000 0000000 00000001343 15223766131 0015345 0 ustar 00root root 0000000 0000000 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.
any1-wayvnc-ae53f07/FAQ.md 0000664 0000000 0000000 00000003240 15223766131 0015241 0 ustar 00root root 0000000 0000000 # 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.
Disable `floating_modifier` during the mode if it's set up in your config file
and you wish to be able to use the same functionality in the nested desktop:
```
mode passthrough {
bindsym $mod+Pause mode default; floating_modifier $mod normal
}
bindsym $mod+Pause mode passthrough; floating_modifier none
```
Replace `$mod normal` with different arguments if applicable.
**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.
**Q: How do i connect to a running Wayland session?**
A: Let's say you want to control the host at `$host` which is already up and running Wayland:
* `ssh -L 5900:localhost:5900 $user@$host WAYLAND_DISPLAY=wayland-1 wayvnc localhost`
* Now, connect from your local device (VNC tunneled through SSH session): `vncviewer localhost:5900`
any1-wayvnc-ae53f07/FUNDING.yml 0000664 0000000 0000000 00000000044 15223766131 0016124 0 ustar 00root root 0000000 0000000 github: any1
patreon: andriyngvason
any1-wayvnc-ae53f07/README.md 0000664 0000000 0000000 00000014473 15223766131 0015601 0 ustar 00root root 0000000 0000000 # wayvnc
[](https://builds.sr.ht/~andri/wayvnc?)
[](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 ec -pkeyopt ec_paramgen_curve:secp384r1 -sha384 \
-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.
#### DES Authentication (Legacy)
:warning: DES authentication provides **no encryption** of the VNC session. It
uses an outdated challenge-response scheme where only the first 8 characters of
the password are used. This is **not secure** by any modern standard.
This option exists solely for compatibility with VNC clients that support DES
but do not support the absence of authentication, such as macOS Screen Sharing.
If you need to support such clients, add the following to your config:
```
enable_auth=true
password=p455w0rd
relax_encryption=true
allow_broken_crypto=true
```
DES authentication does not work when `enable_pam` is enabled, as PAM overrides
password-based authentication.
On a network you do not fully trust, use SSH tunneling or a VPN to provide
encryption.
### 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.
any1-wayvnc-ae53f07/examples/ 0000775 0000000 0000000 00000000000 15223766131 0016127 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/examples/README.md 0000664 0000000 0000000 00000002110 15223766131 0017400 0 ustar 00root root 0000000 0000000 # 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.
any1-wayvnc-ae53f07/examples/auto-attach.py 0000775 0000000 0000000 00000004036 15223766131 0020721 0 ustar 00root root 0000000 0000000 #!/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())
any1-wayvnc-ae53f07/examples/event-watcher 0000775 0000000 0000000 00000003512 15223766131 0020632 0 ustar 00root root 0000000 0000000 #!/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)
any1-wayvnc-ae53f07/examples/single-output-sway 0000775 0000000 0000000 00000010356 15223766131 0021662 0 ustar 00root root 0000000 0000000 #!/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)
any1-wayvnc-ae53f07/include/ 0000775 0000000 0000000 00000000000 15223766131 0015734 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/include/buffer.h 0000664 0000000 0000000 00000006203 15223766131 0017357 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 - 2025 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 "observer.h"
#include
#include
#include
#include
#include
#include
struct wl_buffer;
struct gbm_bo;
struct gbm_device;
struct nvnc_frame;
struct nvnc_buffer;
enum wv_buffer_type {
WV_BUFFER_UNSPEC = 0,
WV_BUFFER_SHM,
#ifdef ENABLE_SCREENCOPY_DMABUF
WV_BUFFER_DMABUF,
#endif
};
#ifdef ENABLE_SCREENCOPY_DMABUF
struct wv_gbm_device {
atomic_int ref;
struct gbm_device* dev;
int fd;
};
#endif
struct wv_buffer {
enum wv_buffer_type type;
LIST_ENTRY(wv_buffer) link;
struct nvnc_frame* nvnc_frame;
struct nvnc_buffer* buffer;
struct wl_buffer* wl_buffer;
void* pixels;
size_t size;
int width, height, stride;
uint32_t format;
bool y_inverted;
struct observer wayland_destroy_observer;
struct pixman_region16 frame_damage;
struct pixman_region16 buffer_damage;
#ifdef ENABLE_SCREENCOPY_DMABUF
/* The following is only applicable to DMABUF */
struct gbm_bo* bo;
dev_t node;
int n_modifiers;
uint64_t* modifiers;
struct wv_gbm_device* gbm;
#endif
/* The following is only applicable to cursors */
uint16_t cursor_width;
uint16_t cursor_height;
uint16_t x_hotspot;
uint16_t y_hotspot;
};
LIST_HEAD(wv_buffer_list, wv_buffer);
struct wv_buffer_config {
enum wv_buffer_type type;
int width, height, stride;
uint32_t format;
/* The following is only applicable to DMABUF */
dev_t node;
int n_modifiers;
uint64_t* modifiers;
};
struct wv_buffer_pool {
struct nvnc_buffer_pool* nvnc_pool;
struct wv_buffer_list list;
struct wv_buffer_config config;
#ifdef ENABLE_SCREENCOPY_DMABUF
struct wv_gbm_device* gbm;
#endif
};
enum wv_buffer_type wv_buffer_get_available_types(void);
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);
void wv_buffer_release(struct wv_buffer* self);
struct wv_buffer_pool* wv_buffer_pool_create(
const struct wv_buffer_config* config);
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool);
bool wv_buffer_pool_reconfig(struct wv_buffer_pool* pool,
const struct wv_buffer_config* config);
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool);
void wv_buffer_pool_damage_all(struct wv_buffer_pool* pool,
struct pixman_region16* region);
any1-wayvnc-ae53f07/include/cfg.h 0000664 0000000 0000000 00000003020 15223766131 0016637 0 ustar 00root root 0000000 0000000 /*
* 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(bool, allow_broken_crypto) \
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);
any1-wayvnc-ae53f07/include/ctl-client.h 0000664 0000000 0000000 00000002601 15223766131 0020142 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/ctl-commands.h 0000664 0000000 0000000 00000003626 15223766131 0020475 0 ustar 00root root 0000000 0000000 /*
* 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_SET_DESKTOP_NAME,
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_OUTPUT_ADDED,
EVT_OUTPUT_REMOVED,
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[];
any1-wayvnc-ae53f07/include/ctl-server.h 0000664 0000000 0000000 00000006002 15223766131 0020171 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 - 2026 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 "image-source.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,
enum image_source_type, const char* image_source_name);
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_set_desktop_name)(struct ctl*,
const char* desktop_name);
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*);
void ctl_server_event_output_added(struct ctl*, const char* name);
void ctl_server_event_output_removed(struct ctl*, const char* name);
any1-wayvnc-ae53f07/include/data-control.h 0000664 0000000 0000000 00000004167 15223766131 0020504 0 ustar 00root root 0000000 0000000 /*
* 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"
#include "ext-data-control-v1.h"
#include "sys/queue.h"
struct receive_context;
struct send_context;
LIST_HEAD(receive_context_list, receive_context);
LIST_HEAD(send_context_list, send_context);
struct data_control {
struct wl_display* wl_display;
struct nvnc* server;
struct receive_context_list receive_contexts;
struct send_context_list send_contexts;
struct zwlr_data_control_manager_v1* wlr_manager;
struct ext_data_control_manager_v1* ext_manager;
union {
struct {
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;
} wlr;
struct {
struct ext_data_control_device_v1* device;
struct ext_data_control_source_v1* selection;
struct ext_data_control_source_v1* primary_selection;
struct ext_data_control_offer_v1* offer;
} ext;
};
bool is_own_offer;
const char* mime_type;
/* x-wayvnc-client-(8 hexadecimal digits) + \0 */
char custom_mime_type_name[32];
char* cb_data;
size_t cb_len;
};
void data_control_init(struct data_control* self, 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);
any1-wayvnc-ae53f07/include/desktop.h 0000664 0000000 0000000 00000003417 15223766131 0017563 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 - 2026 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 "image-source.h"
#include "observer.h"
#include "screencopy-interface.h"
#include
struct desktop_output;
struct desktop_capture;
struct desktop_output {
LIST_ENTRY(desktop_output) link;
struct output* output;
struct desktop* desktop;
struct observer power_change_observer;
struct observer geometry_change_observer;
struct screencopy* sc;
struct screencopy* cursor_sc;
};
LIST_HEAD(desktop_output_list, desktop_output);
struct desktop {
struct image_source image_source;
struct desktop_output_list outputs;
struct observer output_added_observer;
struct observer output_removed_observer;
struct desktop_capture* capture;
struct desktop_capture* cursor_capture;
};
struct desktop_capture {
struct screencopy base;
struct desktop* desktop;
struct wl_seat* seat;
bool render_cursor;
};
struct desktop* desktop_from_image_source(const struct image_source* source);
struct desktop* desktop_new(struct wl_list* output_list);
void desktop_destroy(struct desktop* self);
any1-wayvnc-ae53f07/include/image-source.h 0000664 0000000 0000000 00000006352 15223766131 0020473 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 - 2026 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 "observer.h"
#include
#include
#include
#include
struct output;
struct wl_list;
struct image_source_impl;
enum image_source_power_state {
IMAGE_SOURCE_POWER_UNKNOWN = 0,
IMAGE_SOURCE_POWER_OFF,
IMAGE_SOURCE_POWER_ON,
};
enum image_source_type {
IMAGE_SOURCE_TYPE_UNSPEC = 0,
IMAGE_SOURCE_TYPE_DESKTOP,
IMAGE_SOURCE_TYPE_OUTPUT,
IMAGE_SOURCE_TYPE_TOPLEVEL,
};
struct image_source {
struct image_source_impl* impl;
struct {
struct observable power_change;
struct observable geometry_change;
struct observable destroyed;
} observable;
};
struct image_source_impl {
void (*get_logical_size)(const struct image_source* self,
int* width, int* height);
void (*get_buffer_size)(const struct image_source* self,
int* width, int* height);
enum wl_output_transform (*get_transform)(const struct image_source* self);
enum image_source_power_state (*get_power_state)(
const struct image_source* self);
void (*describe)(const struct image_source*, char* description,
size_t maxlen);
int (*acquire_power_on)(struct image_source*);
void (*release_power_on)(struct image_source*);
double (*get_min_scale)(const struct image_source* self);
void (*deinit)(struct image_source*);
};
void image_source_init(struct image_source* self, struct image_source_impl* impl);
void image_source_deinit(struct image_source* self);
void image_source_destroy(struct image_source* self);
bool image_source_is_output(const struct image_source* self);
bool image_source_is_toplevel(const struct image_source* self);
bool image_source_is_desktop(const struct image_source* self);
bool image_source_get_logical_size(const struct image_source* self,
int* width, int* height);
bool image_source_get_buffer_size(const struct image_source* self,
int* width, int* height);
bool image_source_get_scale(const struct image_source* self,
double* h_scale, double* v_scale);
double image_source_get_min_scale(const struct image_source* self);
enum wl_output_transform image_source_get_transform(
const struct image_source* self);
enum image_source_power_state image_source_get_power(
const struct image_source* self);
const char* image_source_describe(const struct image_source* self,
char* description, size_t maxlen);
int image_source_acquire_power_on(struct image_source* self);
void image_source_release_power_on(struct image_source* self);
const char* image_source_power_state_name(enum image_source_power_state state);
any1-wayvnc-ae53f07/include/intset.h 0000664 0000000 0000000 00000002235 15223766131 0017415 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/json-ipc.h 0000664 0000000 0000000 00000004475 15223766131 0017641 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/keyboard.h 0000664 0000000 0000000 00000003160 15223766131 0017705 0 ustar 00root root 0000000 0000000 /*
* 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
#include "intset.h"
struct zwp_virtual_keyboard_v1;
struct table_entry;
struct nvnc;
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 last_sent_group;
};
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);
enum nvnc_keyboard_led_state keyboard_get_led_state(const struct keyboard*);
any1-wayvnc-ae53f07/include/observer.h 0000664 0000000 0000000 00000003576 15223766131 0017747 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 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 "sys/queue.h"
struct observer;
struct observable;
typedef void (*observer_notify_fn)(struct observer*, void* arg);
struct observer {
LIST_ENTRY(observer) link;
struct observable* subject;
observer_notify_fn notify;
};
LIST_HEAD(observable, observer);
static inline void observer_init(struct observer* self,
struct observable* subject, observer_notify_fn notify)
{
self->subject = subject;
self->notify = notify;
LIST_INSERT_HEAD(subject, self, link);
}
static inline void observer_deinit(struct observer* self)
{
if (self->subject)
LIST_REMOVE(self, link);
self->subject = NULL;
}
static inline void observable_init(struct observable* self)
{
LIST_INIT(self);
}
static inline void observable_deinit(struct observable* self)
{
while (!LIST_EMPTY(self)) {
struct observer* observer = LIST_FIRST(self);
LIST_REMOVE(observer, link);
observer->subject = NULL;
}
}
static inline void observable_notify(struct observable* self, void* arg)
{
struct observer* observer;
struct observer* tmp;
LIST_FOREACH_SAFE(observer, self, link, tmp)
observer->notify(observer, arg);
}
any1-wayvnc-ae53f07/include/option-parser.h 0000664 0000000 0000000 00000004106 15223766131 0020710 0 ustar 00root root 0000000 0000000 /*
* 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;
bool is_repeating;
};
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);
const char* option_parser_get_value_with_offset(const struct option_parser* self,
const char* name, int index);
void option_parser_print_cmd_summary(const char* summary, FILE* stream);
any1-wayvnc-ae53f07/include/output-management.h 0000664 0000000 0000000 00000003014 15223766131 0021555 0 ustar 00root root 0000000 0000000 /*
* 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;
struct zwlr_output_configuration_v1;
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager);
void wlr_output_manager_destroy(void);
struct zwlr_output_configuration_v1* wlr_output_manager_start_config(void);
bool wlr_output_manager_commit_config(struct zwlr_output_configuration_v1* config);
void wlr_output_manager_abort_config(struct zwlr_output_configuration_v1* config);
bool wlr_output_manager_configure_output(struct zwlr_output_configuration_v1* config,
struct output* output, uint16_t width, uint16_t height, uint16_t x,
uint16_t y);
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height);
any1-wayvnc-ae53f07/include/output.h 0000664 0000000 0000000 00000005675 15223766131 0017462 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2025 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 "image-source.h"
#include "observer.h"
#include
#include
#include
struct wayland;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
struct zwlr_output_power_manager_v1;
struct zwlr_output_power_v1;
struct output_state {
uint32_t logical_width;
uint32_t logical_height;
uint32_t buffer_width;
uint32_t buffer_height;
uint32_t x;
uint32_t y;
enum wl_output_transform transform;
char make[256];
char model[256];
char name[256];
char description[256];
};
struct output {
struct image_source image_source;
struct wayland* wayland;
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;
struct output_state state;
struct output_state pending;
enum image_source_power_state power;
bool power_management_failed;
void* userdata;
};
struct output* output_from_image_source(const struct image_source* source);
struct output* output_new(struct wayland* wayland, struct wl_output* wl_output,
uint32_t id);
void output_destroy(struct output* output);
void output_setup_xdg_output_managers(struct wayland* wayland,
struct wl_list* list);
int output_acquire_power_on(struct output* output);
void output_release_power_on(struct output* output);
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);
struct output* output_last(struct wl_list* list);
bool output_is_headless(const struct output* self);
void output_get_logical_size(const struct output* self,
int* width, int* height);
void output_get_buffer_size(const struct output* self,
int* width, int* height);
void output_get_pos(const struct output* self, int* x, int* y);
const char* output_get_name(const struct output* self);
const char* output_get_description(const struct output* self);
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);
any1-wayvnc-ae53f07/include/pam_auth.h 0000664 0000000 0000000 00000001547 15223766131 0017712 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/pixels.h 0000664 0000000 0000000 00000002124 15223766131 0017410 0 ustar 00root root 0000000 0000000 /*
* 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 pixman_region16;
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);
uint32_t calculate_region_area(struct pixman_region16* region);
any1-wayvnc-ae53f07/include/pointer.h 0000664 0000000 0000000 00000002335 15223766131 0017570 0 ustar 00root root 0000000 0000000 /*
* 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"
struct image_source;
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;
};
int pointer_init(struct pointer* self);
void pointer_destroy(struct pointer* self);
void pointer_set(struct pointer* self, double x, double y,
enum nvnc_button_mask button_mask);
any1-wayvnc-ae53f07/include/screencopy-interface.h 0000664 0000000 0000000 00000004617 15223766131 0022225 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 - 2025 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 "buffer.h"
#include
#include
struct image_source;
struct wl_seat;
enum screencopy_result {
SCREENCOPY_DONE,
SCREENCOPY_FATAL,
SCREENCOPY_FAILED,
};
enum screencopy_capabilitites {
SCREENCOPY_CAP_CURSOR = 1 << 0,
SCREENCOPY_CAP_TRANSFORM = 1 << 1,
};
typedef void (*screencopy_done_fn)(enum screencopy_result,
struct wv_buffer* buffer, struct image_source* source,
void* userdata);
struct screencopy_impl {
struct screencopy* (*create)(struct image_source*, bool render_cursor);
struct screencopy* (*create_cursor)(struct image_source*, struct wl_seat*);
void (*destroy)(struct screencopy*);
int (*start)(struct screencopy*, bool immediate);
void (*stop)(struct screencopy*);
enum screencopy_capabilitites
(*get_capabilities)(const struct screencopy*);
};
struct screencopy {
struct screencopy_impl* impl;
double rate_limit;
bool enable_linux_dmabuf;
screencopy_done_fn on_done;
void (*cursor_enter)(void* userdata);
void (*cursor_leave)(void* userdata);
void (*cursor_hotspot)(int x, int y, void* userdata);
double (*rate_format)(const void* userdata, enum wv_buffer_type type,
uint32_t format, uint64_t modifier);
void* userdata;
};
struct screencopy* screencopy_create(struct image_source* source,
bool render_cursor);
struct screencopy* screencopy_create_cursor(struct image_source* source,
struct wl_seat* seat);
void screencopy_destroy(struct screencopy* self);
enum screencopy_capabilitites screencopy_get_capabilities(
const struct screencopy* self);
int screencopy_start(struct screencopy* self, bool immediate);
void screencopy_stop(struct screencopy* self);
any1-wayvnc-ae53f07/include/seat.h 0000664 0000000 0000000 00000002533 15223766131 0017044 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/shm.h 0000664 0000000 0000000 00000001522 15223766131 0016674 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/strlcpy.h 0000664 0000000 0000000 00000001554 15223766131 0017612 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/sys/ 0000775 0000000 0000000 00000000000 15223766131 0016552 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/include/sys/queue.h 0000664 0000000 0000000 00000071132 15223766131 0020053 0 ustar 00root root 0000000 0000000 /*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD$
*/
#ifndef _SYS_QUEUE_H_
#define _SYS_QUEUE_H_
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may be traversed in either direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
* Below is a summary of implemented functions where:
* + means the macro is available
* - means the macro is not available
* s means the macro is available but is slow (runs in O(n) time)
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _CLASS_HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _CLASS_ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - + - +
* _LAST - - + +
* _LAST_FAST - - - +
* _FOREACH + + + +
* _FOREACH_FROM + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_FROM_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_FROM - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _FOREACH_REVERSE_FROM_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT s s + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE s + s +
* _SWAP + + + +
*
*/
#ifdef QUEUE_MACRO_DEBUG
#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
#define QUEUE_MACRO_DEBUG_TRACE
#define QUEUE_MACRO_DEBUG_TRASH
#endif
#ifdef QUEUE_MACRO_DEBUG_TRACE
/* Store the last 2 places the queue element or head was altered */
struct qm_trace {
unsigned long lastline;
unsigned long prevline;
const char *lastfile;
const char *prevfile;
};
#define TRACEBUF struct qm_trace trace;
#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } ,
#define QMD_TRACE_HEAD(head) do { \
(head)->trace.prevline = (head)->trace.lastline; \
(head)->trace.prevfile = (head)->trace.lastfile; \
(head)->trace.lastline = __LINE__; \
(head)->trace.lastfile = __FILE__; \
} while (0)
#define QMD_TRACE_ELEM(elem) do { \
(elem)->trace.prevline = (elem)->trace.lastline; \
(elem)->trace.prevfile = (elem)->trace.lastfile; \
(elem)->trace.lastline = __LINE__; \
(elem)->trace.lastfile = __FILE__; \
} while (0)
#else /* !QUEUE_MACRO_DEBUG_TRACE */
#define QMD_TRACE_ELEM(elem)
#define QMD_TRACE_HEAD(head)
#define TRACEBUF
#define TRACEBUF_INITIALIZER
#endif /* QUEUE_MACRO_DEBUG_TRACE */
#ifdef QUEUE_MACRO_DEBUG_TRASH
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
#define QMD_IS_TRASHED(x) ((x) == (void *)(intptr_t)-1)
#else /* !QUEUE_MACRO_DEBUG_TRASH */
#define QMD_SAVELINK(name, link)
#define TRASHIT(x)
#define QMD_IS_TRASHED(x) 0
#endif /* QUEUE_MACRO_DEBUG_TRASH */
#ifdef __cplusplus
/*
* In C++ there can be structure lists and class lists:
*/
#define QUEUE_TYPEOF(type) type
#else
#define QUEUE_TYPEOF(type) struct type
#endif
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_CLASS_HEAD(name, type) \
struct name { \
class type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
#define SLIST_CLASS_ENTRY(type) \
struct { \
class type *sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \
if (*(prevp) != (elm)) \
panic("Bad prevptr *(%p) == %p != %p", \
(prevp), *(prevp), (elm)); \
} while (0)
#else
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm)
#endif
#define SLIST_CONCAT(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \
if (curelm == NULL) { \
if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \
SLIST_INIT(head2); \
} else if (SLIST_FIRST(head2) != NULL) { \
while (SLIST_NEXT(curelm, field) != NULL) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \
SLIST_INIT(head2); \
} \
} while (0)
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != NULL; \
(varp) = &SLIST_NEXT((var), field))
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
#define SLIST_REMOVE_PREVPTR(prevp, elm, field) do { \
QMD_SLIST_CHECK_PREVPTR(prevp, elm); \
*(prevp) = SLIST_NEXT(elm, field); \
TRASHIT((elm)->field.sle_next); \
} while (0)
#define SLIST_SWAP(head1, head2, type) do { \
QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
SLIST_FIRST(head2) = swap_first; \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_CLASS_HEAD(name, type) \
struct name { \
class type *stqh_first; /* first element */ \
class type **stqh_last; /* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
#define STAILQ_CLASS_ENTRY(type) \
struct { \
class type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? NULL : \
__containerof((head)->stqh_last, \
QUEUE_TYPEOF(type), field.stqe_next))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \
QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_CLASS_HEAD(name, type) \
struct name { \
class type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
#define LIST_CLASS_ENTRY(type) \
struct { \
class type *le_next; /* next element */ \
class type **le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
/*
* QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME)
*
* If the list is non-empty, validates that the first element of the list
* points back at 'head.'
*/
#define QMD_LIST_CHECK_HEAD(head, field) do { \
if (LIST_FIRST((head)) != NULL && \
LIST_FIRST((head))->field.le_prev != \
&LIST_FIRST((head))) \
panic("Bad list head %p first->prev != head", (head)); \
} while (0)
/*
* QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME)
*
* If an element follows 'elm' in the list, validates that the next element
* points back at 'elm.'
*/
#define QMD_LIST_CHECK_NEXT(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL && \
LIST_NEXT((elm), field)->field.le_prev != \
&((elm)->field.le_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
/*
* QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME)
*
* Validates that the previous element (or head of the list) points to 'elm.'
*/
#define QMD_LIST_CHECK_PREV(elm, field) do { \
if (*(elm)->field.le_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_LIST_CHECK_HEAD(head, field)
#define QMD_LIST_CHECK_NEXT(elm, field)
#define QMD_LIST_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define LIST_CONCAT(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \
if (curelm == NULL) { \
if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \
LIST_FIRST(head2)->field.le_prev = \
&LIST_FIRST((head1)); \
LIST_INIT(head2); \
} \
} else if (LIST_FIRST(head2) != NULL) { \
while (LIST_NEXT(curelm, field) != NULL) \
curelm = LIST_NEXT(curelm, field); \
LIST_NEXT(curelm, field) = LIST_FIRST(head2); \
LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \
LIST_INIT(head2); \
} \
} while (0)
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
QMD_LIST_CHECK_NEXT(listelm, field); \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
QMD_LIST_CHECK_PREV(listelm, field); \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
QMD_LIST_CHECK_HEAD((head), field); \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_PREV(elm, head, type, field) \
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
__containerof((elm)->field.le_prev, \
QUEUE_TYPEOF(type), field.le_next))
#define LIST_REMOVE(elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.le_next); \
QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
QMD_LIST_CHECK_NEXT(elm, field); \
QMD_LIST_CHECK_PREV(elm, field); \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_CLASS_HEAD(name, type) \
struct name { \
class type *tqh_first; /* first element */ \
class type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first, TRACEBUF_INITIALIZER }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
#define TAILQ_CLASS_ENTRY(type) \
struct { \
class type *tqe_next; /* next element */ \
class type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
/*
* Tail queue functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
/*
* QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
*
* If the tailq is non-empty, validates that the first element of the tailq
* points back at 'head.'
*/
#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
if (!TAILQ_EMPTY(head) && \
TAILQ_FIRST((head))->field.tqe_prev != \
&TAILQ_FIRST((head))) \
panic("Bad tailq head %p first->prev != head", (head)); \
} while (0)
/*
* QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
*
* Validates that the tail of the tailq is a pointer to pointer to NULL.
*/
#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
if (*(head)->tqh_last != NULL) \
panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
} while (0)
/*
* QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME)
*
* If an element follows 'elm' in the tailq, validates that the next element
* points back at 'elm.'
*/
#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
if (TAILQ_NEXT((elm), field) != NULL && \
TAILQ_NEXT((elm), field)->field.tqe_prev != \
&((elm)->field.tqe_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
/*
* QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME)
*
* Validates that the previous element (or head of the tailq) points to 'elm.'
*/
#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
if (*(elm)->field.tqe_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_TAILQ_CHECK_HEAD(head, field)
#define QMD_TAILQ_CHECK_TAIL(head, headname)
#define QMD_TAILQ_CHECK_NEXT(elm, field)
#define QMD_TAILQ_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
QMD_TRACE_HEAD(head1); \
QMD_TRACE_HEAD(head2); \
} \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
QMD_TAILQ_CHECK_NEXT(listelm, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
} \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&(listelm)->field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
QMD_TAILQ_CHECK_PREV(listelm, field); \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&(listelm)->field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
QMD_TAILQ_CHECK_HEAD(head, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
QMD_TAILQ_CHECK_TAIL(head, field); \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
/*
* The FAST function is fast in that it causes no data access other
* then the access to the head. The standard LAST function above
* will cause a data access of both the element you want and
* the previous element. FAST is very useful for instances when
* you may want to prefetch the last data element.
*/
#define TAILQ_LAST_FAST(head, type, field) \
(TAILQ_EMPTY(head) ? NULL : __containerof((head)->tqh_last, QUEUE_TYPEOF(type), field.tqe_next))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_PREV_FAST(elm, head, type, field) \
((elm)->field.tqe_prev == &(head)->tqh_first ? NULL : \
__containerof((elm)->field.tqe_prev, QUEUE_TYPEOF(type), field.tqe_next))
#define TAILQ_REMOVE(head, elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
QMD_TAILQ_CHECK_NEXT(elm, field); \
QMD_TAILQ_CHECK_PREV(elm, field); \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
QMD_TRACE_HEAD(head); \
} \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \
QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif /* !_SYS_QUEUE_H_ */
any1-wayvnc-ae53f07/include/table-printer.h 0000664 0000000 0000000 00000003105 15223766131 0020654 0 ustar 00root root 0000000 0000000 /*
* 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);
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);
any1-wayvnc-ae53f07/include/time-util.h 0000664 0000000 0000000 00000002660 15223766131 0020022 0 ustar 00root root 0000000 0000000 /*
* 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);
}
any1-wayvnc-ae53f07/include/toplevel.h 0000664 0000000 0000000 00000002661 15223766131 0017744 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 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 "image-source.h"
struct ext_foreign_toplevel_handle_v1;
struct toplevel {
struct image_source image_source;
struct ext_foreign_toplevel_handle_v1 *handle;
struct wl_list link;
void (*on_closed)(struct toplevel* self);
void* userdata;
char title[256];
char app_id[256];
char identifier[33];
};
struct toplevel* toplevel_from_image_source(const struct image_source* source);
struct toplevel* toplevel_new(struct ext_foreign_toplevel_handle_v1* handle);
void toplevel_destroy(struct toplevel* self);
void toplevel_list_destroy(struct wl_list* list);
struct toplevel* toplevel_find_by_identifier(struct wl_list* list,
const char *identifier);
any1-wayvnc-ae53f07/include/transform-util.h 0000664 0000000 0000000 00000002640 15223766131 0021075 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 - 2025 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);
void wv_output_transform_canvas_point(enum wl_output_transform transform,
double* point_x, double* point_y);
any1-wayvnc-ae53f07/include/tst.h 0000664 0000000 0000000 00000012725 15223766131 0016726 0 ustar 00root root 0000000 0000000 /*
* 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);
any1-wayvnc-ae53f07/include/usdt.h 0000664 0000000 0000000 00000002033 15223766131 0017062 0 ustar 00root root 0000000 0000000 /*
* 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
any1-wayvnc-ae53f07/include/util.h 0000664 0000000 0000000 00000002057 15223766131 0017066 0 ustar 00root root 0000000 0000000 /*
* 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))
#define ALIGN_UP(a, b) ((b) * UDIV_UP((a), (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);
any1-wayvnc-ae53f07/include/vec.h 0000664 0000000 0000000 00000004757 15223766131 0016677 0 ustar 00root root 0000000 0000000 /*
* 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
struct wv_vec {
void* data;
size_t len;
size_t cap;
};
static inline void wv_vec_clear(struct wv_vec* self)
{
self->len = 0;
}
int wv_vec_init(struct wv_vec*, size_t cap);
void wv_vec_destroy(struct wv_vec*);
int wv_vec_reserve(struct wv_vec*, size_t cap);
void wv_vec_bzero(struct wv_vec*);
int wv_vec_assign(struct wv_vec*, const void* data, size_t size);
int wv_vec_append(struct wv_vec*, const void* data, size_t size);
void* wv_vec_append_zero(struct wv_vec*, size_t size);
static inline void wv_vec_fast_append_8(struct wv_vec* self, uint8_t value)
{
assert(self->len < self->cap);
((uint8_t*)self->data)[self->len++] = value;
}
static inline void wv_vec_fast_append_32(struct wv_vec* self, uint32_t value)
{
assert(self->len + sizeof(value) <= self->cap);
assert(self->len % sizeof(value) == 0);
uint32_t* p = (uint32_t*)((uint8_t*)self->data + self->len);
*p = value;
self->len += sizeof(value);
}
#define wv_vec_for(elem, self) \
for (elem = (self)->data; \
((ptrdiff_t)elem - (ptrdiff_t)(self)->data) < (ptrdiff_t)(self)->len;\
++elem)
#define wv_vec_for_tail(elem, self) \
for (elem = (self)->data, ++elem; \
((ptrdiff_t)elem - (ptrdiff_t)(self)->data) < (ptrdiff_t)(self)->len;\
++elem)
#define wv_vec_for_ptr(elem, self) \
__typeof__(elem)* ptr_; \
wv_vec_for(ptr_, self) \
if ((elem = *ptr_))
any1-wayvnc-ae53f07/include/wayland.h 0000664 0000000 0000000 00000005314 15223766131 0017547 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 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 "observer.h"
#include
// TODO: Remove this once wl_list is replaced with queue.h
#include
#define X_WAYLAND_PROTOCOLS \
X(ext_data_control_manager_v1) \
X(ext_foreign_toplevel_image_capture_source_manager_v1) \
X(ext_foreign_toplevel_list_v1) \
X(ext_image_copy_capture_manager_v1) \
X(ext_output_image_capture_source_manager_v1) \
X(ext_transient_seat_manager_v1) \
X(wl_shm) \
X(zwlr_data_control_manager_v1) \
X(zwlr_output_power_manager_v1) \
X(zwlr_screencopy_manager_v1) \
X(zwlr_virtual_pointer_manager_v1) \
X(zwp_linux_dmabuf_v1) \
X(zwp_virtual_keyboard_manager_v1) \
X(zxdg_output_manager_v1)
#define X_WAYLAND_OBSERVABLES \
X(destroyed) \
X(output_added) \
X(output_removed) \
X(seat_added) \
X(seat_removed)
struct aml_handler;
struct ext_foreign_toplevel_image_capture_source_manager_v1;
struct ext_foreign_toplevel_list_v1;
struct ext_image_copy_capture_manager_v1;
struct ext_output_image_capture_source_manager_v1;
struct ext_transient_seat_manager_v1;
struct wl_shm;
struct zwlr_data_control_manager_v1;
struct zwlr_output_power_manager_v1;
struct zwlr_screencopy_manager_v1;
struct zwlr_virtual_pointer_manager_v1;
struct zwp_linux_dmabuf_v1;
struct zwp_virtual_keyboard_manager_v1;
struct zxdg_output_manager_v1;
enum wayland_flags {
WAYLAND_FLAG_ENABLE_INPUT = 1 << 0,
WAYLAND_FLAG_ENABLE_TOPLEVEL_CAPTURE = 1 << 1,
WAYLAND_FLAG_ENABLE_TRANSIENT_SEAT = 1 << 2,
};
struct wayland {
bool is_initialising;
enum wayland_flags flags;
struct aml_handler* wl_handler;
struct wl_display* display;
struct wl_registry* registry;
struct wl_list outputs;
struct wl_list seats;
struct wl_list toplevels;
#define X(x) struct x* x;
X_WAYLAND_PROTOCOLS
#undef X
struct {
#define X(x) struct observable x;
X_WAYLAND_OBSERVABLES
#undef X
} observable;
};
struct wayland* wayland_connect(const char* display, enum wayland_flags flags);
void wayland_destroy(struct wayland* self);
any1-wayvnc-ae53f07/meson.build 0000664 0000000 0000000 00000011147 15223766131 0016457 0 ustar 00root root 0000000 0000000 project(
'wayvnc',
'c',
version: '0.10.1',
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'], check: false)
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
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 = ['>=1.0.0', '<2.0.0']
neatvnc_version = ['>=1.0.0', '<2.0.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('aml1', 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/ext-image-copy-capture.c',
'src/screencopy-interface.c',
'src/data-control.c',
'src/output.c',
'src/output-management.c',
'src/pointer.c',
'src/keyboard.c',
'src/seat.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',
'src/image-source.c',
'src/toplevel.c',
'src/desktop.c',
'src/wayland.c',
'src/vec.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_header('linux/dma-heap.h') and cc.has_header('linux/dma-buf.h')
config.set('HAVE_LINUX_DMA_HEAP', 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_variable(pkgconfig: '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.full_path(), output)
],
install: true,
install_dir: '@0@/man1'.format(mandir)
)
endforeach
endif
if get_option('tests')
subdir('test')
endif
any1-wayvnc-ae53f07/meson_options.txt 0000664 0000000 0000000 00000000722 15223766131 0017747 0 ustar 00root root 0000000 0000000 option('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')
any1-wayvnc-ae53f07/protocols/ 0000775 0000000 0000000 00000000000 15223766131 0016335 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/protocols/ext-data-control-v1.xml 0000664 0000000 0000000 00000027375 15223766131 0022606 0 ustar 00root root 0000000 0000000
Copyright © 2018 Simon Ser
Copyright © 2019 Ivan Molodetskikh
Copyright © 2024 Neal Gompa
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 currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
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 triggers the used_source protocol error.
To unset the selection, set the source to NULL.
Destroys the data device object.
The data_offer event introduces a new ext_data_control_offer object,
which will subsequently be used in either the
ext_data_control_device.selection event (for the regular clipboard
selections) or the ext_data_control_device.primary_selection event (for
the primary clipboard selections). Immediately following the
ext_data_control_device.data_offer event, the new data_offer object
will send out ext_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
ext_data_control_offer for the selection for this device. The
ext_data_control_device.data_offer and the ext_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 ext_data_control_offer is valid until a new
ext_data_control_offer or NULL is received. The client must destroy the
previous selection ext_data_control_offer, if any, upon receiving this
event. Regardless, the previous selection will be ignored once a new
selection ext_data_control_offer is received.
The first selection event is sent upon binding the
ext_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
ext_data_control_offer for the primary selection for this device. The
ext_data_control_device.data_offer and the ext_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 ext_data_control_offer is valid until
a new ext_data_control_offer or NULL is received. The client must
destroy the previous primary selection ext_data_control_offer, if any,
upon receiving this event. Regardless, the previous primary selection
will be ignored once a new primary selection ext_data_control_offer is
received.
If the compositor supports primary selection, the first
primary_selection event is sent upon binding the
ext_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 triggers the used_source 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 ext_data_control_source object is the source side of a
ext_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 ext_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 ext_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 ext_data_control_offer object.
One event per offered MIME type.
any1-wayvnc-ae53f07/protocols/ext-foreign-toplevel-list-v1.xml 0000664 0000000 0000000 00000023446 15223766131 0024444 0 ustar 00root root 0000000 0000000
Copyright © 2018 Ilia Bozhinov
Copyright © 2020 Isaac Freund
Copyright © 2022 wb9688
Copyright © 2023 i509VCB
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.
The purpose of this protocol is to provide protocol object handles for
toplevels, possibly originating from another client.
This protocol is intentionally minimalistic and expects additional
functionality (e.g. creating a screencopy source from a toplevel handle,
getting information about the state of the toplevel) to be implemented
in extension protocols.
The compositor may choose to restrict this protocol to a special client
launched by the compositor itself or expose it to all clients,
this is compositor policy.
The key words "must", "must not", "required", "shall", "shall not",
"should", "should not", "recommended", "may", and "optional" in this
document are to be interpreted as described in IETF RFC 2119.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
A toplevel is defined as a surface with a role similar to xdg_toplevel.
XWayland surfaces may be treated like toplevels in this protocol.
After a client binds the ext_foreign_toplevel_list_v1, each mapped
toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
event.
Clients which only care about the current state can perform a roundtrip after
binding this global.
For each instance of ext_foreign_toplevel_list_v1, the compositor must
create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel.
If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
event after the global is bound, the compositor must not send any
ext_foreign_toplevel_list_v1.toplevel events.
This event is emitted whenever a new toplevel window is created. It is
emitted for all toplevels, regardless of the app that has created them.
All initial properties of the toplevel (identifier, title, app_id) will be sent
immediately after this event using the corresponding events for
ext_foreign_toplevel_handle_v1. The compositor will use the
ext_foreign_toplevel_handle_v1.done event to indicate when all data has
been sent.
This event indicates that the compositor is done sending events
to this object. The client should destroy the object.
See ext_foreign_toplevel_list_v1.destroy for more information.
The compositor must not send any more toplevel events after this event.
This request indicates that the client no longer wishes to receive
events for new toplevels.
The Wayland protocol is asynchronous, meaning the compositor may send
further toplevel events until the stop request is processed.
The client should wait for a ext_foreign_toplevel_list_v1.finished
event before destroying this object.
This request should be called either when the client will no longer
use the ext_foreign_toplevel_list_v1 or after the finished event
has been received to allow destruction of the object.
If a client wishes to destroy this object it should send a
ext_foreign_toplevel_list_v1.stop request and wait for a ext_foreign_toplevel_list_v1.finished
event, then destroy the handles and then this object.
A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
window. A single app may have multiple mapped toplevels.
This request should be used when the client will no longer use the handle
or after the closed event has been received to allow destruction of the
object.
When a handle is destroyed, a new handle may not be created by the server
until the toplevel is unmapped and then remapped. Destroying a toplevel handle
is not recommended unless the client is cleaning up child objects
before destroying the ext_foreign_toplevel_list_v1 object, the toplevel
was closed or the toplevel handle will not be used in the future.
Other protocols which extend the ext_foreign_toplevel_handle_v1
interface should require destructors for extension interfaces be
called before allowing the toplevel handle to be destroyed.
The server will emit no further events on the ext_foreign_toplevel_handle_v1
after this event. Any requests received aside from the destroy request must
be ignored. Upon receiving this event, the client should destroy the handle.
Other protocols which extend the ext_foreign_toplevel_handle_v1
interface must also ignore requests other than destructors.
This event is sent after all changes in the toplevel state have
been sent.
This allows changes to the ext_foreign_toplevel_handle_v1 properties
to be atomically applied. Other protocols which extend the
ext_foreign_toplevel_handle_v1 interface may use this event to also
atomically apply any pending state.
This event must not be sent after the ext_foreign_toplevel_handle_v1.closed
event.
The title of the toplevel has changed.
The configured state must not be applied immediately. See
ext_foreign_toplevel_handle_v1.done for details.
The app id of the toplevel has changed.
The configured state must not be applied immediately. See
ext_foreign_toplevel_handle_v1.done for details.
This identifier is used to check if two or more toplevel handles belong
to the same toplevel.
The identifier is useful for command line tools or privileged clients
which may need to reference an exact toplevel across processes or
instances of the ext_foreign_toplevel_list_v1 global.
The compositor must only send this event when the handle is created.
The identifier must be unique per toplevel and it's handles. Two different
toplevels must not have the same identifier. The identifier is only valid
as long as the toplevel is mapped. If the toplevel is unmapped the identifier
must not be reused. An identifier must not be reused by the compositor to
ensure there are no races when sharing identifiers between processes.
An identifier is a string that contains up to 32 printable ASCII bytes.
An identifier must not be an empty string. It is recommended that a
compositor includes an opaque generation value in identifiers. How the
generation value is used when generating the identifier is implementation
dependent.
any1-wayvnc-ae53f07/protocols/ext-image-capture-source-v1.xml 0000664 0000000 0000000 00000011635 15223766131 0024230 0 ustar 00root root 0000000 0000000
Copyright © 2022 Andri Yngvason
Copyright © 2024 Simon Ser
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 serves as an intermediary between capturing protocols and
potential image capture sources such as outputs and toplevels.
This protocol may be extended to support more image capture sources in the
future, thereby adding those image capture sources to other protocols that
use the image capture source object without having to modify those
protocols.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
The image capture source object is an opaque descriptor for a capturable
resource. This resource may be any sort of entity from which an image
may be derived.
Note, because ext_image_capture_source_v1 objects are created from multiple
independent factory interfaces, the ext_image_capture_source_v1 interface is
frozen at version 1.
Destroys the image capture source. This request may be sent at any time
by the client.
A manager for creating image capture source objects for wl_output objects.
Creates a source object for an output. Images captured from this source
will show the same content as the output. Some elements may be omitted,
such as cursors and overlays that have been marked as transparent to
capturing.
Destroys the manager. This request may be sent at any time by the client
and objects created by the manager will remain valid after its
destruction.
A manager for creating image capture source objects for
ext_foreign_toplevel_handle_v1 objects.
Creates a source object for a foreign toplevel handle. Images captured
from this source will show the same content as the toplevel.
Destroys the manager. This request may be sent at any time by the client
and objects created by the manager will remain valid after its
destruction.
any1-wayvnc-ae53f07/protocols/ext-image-copy-capture-v1.xml 0000664 0000000 0000000 00000045104 15223766131 0023700 0 ustar 00root root 0000000 0000000
Copyright © 2021-2023 Andri Yngvason
Copyright © 2024 Simon Ser
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 capture image sources
such as outputs and toplevels into user submitted buffers.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
This object is a manager which offers requests to start capturing from a
source.
Create a capturing session for an image capture source.
If the paint_cursors option is set, cursors shall be composited onto
the captured frame. The cursor must not be composited onto the frame
if this flag is not set.
If the options bitfield is invalid, the invalid_option protocol error
is sent.
Create a cursor capturing session for the pointer of an image capture
source.
Destroy the manager object.
Other objects created via this interface are unaffected.
This object represents an active image copy capture session.
After a capture session is created, buffer constraint events will be
emitted from the compositor to tell the client which buffer types and
formats are supported for reading from the session. The compositor may
re-send buffer constraint events whenever they change.
The advertise buffer constraints, the compositor must send in no
particular order: zero or more shm_format and dmabuf_format events, zero
or one dmabuf_device event, and exactly one buffer_size event. Then the
compositor must send a done event.
When the client has received all the buffer constraints, it can create a
buffer accordingly, attach it to the capture session using the
attach_buffer request, set the buffer damage using the damage_buffer
request and then send the capture request.
Provides the dimensions of the source image in buffer pixel coordinates.
The client must attach buffers that match this size.
Provides the format that must be used for shared-memory buffers.
This event may be emitted multiple times, in which case the client may
choose any given format.
This event advertises the device buffers must be allocated on for
dma-buf buffers.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
Provides the format that must be used for dma-buf buffers.
The client may choose any of the modifiers advertised in the array of
64-bit unsigned integers.
This event may be emitted multiple times, in which case the client may
choose any given format.
This event is sent once when all buffer constraint events have been
sent.
The compositor must always end a batch of buffer constraint events with
this event, regardless of whether it sends the initial constraints or
an update.
This event indicates that the capture session has stopped and is no
longer available. This can happen in a number of cases, e.g. when the
underlying source is destroyed, if the user decides to end the image
capture, or if an unrecoverable runtime error has occurred.
The client should destroy the session after receiving this event.
Create a capture frame for this session.
At most one frame object can exist for a given session at any time. If
a client sends a create_frame request before a previous frame object
has been destroyed, the duplicate_frame protocol error is raised.
Destroys the session. This request can be sent at any time by the
client.
This request doesn't affect ext_image_copy_capture_frame_v1 objects created by
this object.
This object represents an image capture frame.
The client should attach a buffer, damage the buffer, and then send a
capture request.
If the capture is successful, the compositor must send the frame metadata
(transform, damage, presentation_time in any order) followed by the ready
event.
If the capture fails, the compositor must send the failed event.
Destroys the session. This request can be sent at any time by the
client.
Attach a buffer to the session.
The wl_buffer.release request is unused.
The new buffer replaces any previously attached buffer.
This request must not be sent after capture, or else the
already_captured protocol error is raised.
Apply damage to the buffer which is to be captured next. This request
may be sent multiple times to describe a region.
The client indicates the accumulated damage since this wl_buffer was
last captured. During capture, the compositor will update the buffer
with at least the union of the region passed by the client and the
region advertised by ext_image_copy_capture_frame_v1.damage.
When a wl_buffer is captured for the first time, or when the client
doesn't track damage, the client must damage the whole buffer.
This is for optimisation purposes. The compositor may use this
information to reduce copying.
These coordinates originate from the upper left corner of the buffer.
If x or y are strictly negative, or if width or height are negative or
zero, the invalid_buffer_damage protocol error is raised.
This request must not be sent after capture, or else the
already_captured protocol error is raised.
Capture a frame.
Unless this is the first successful captured frame performed in this
session, the compositor may wait an indefinite amount of time for the
source content to change before performing the copy.
This request may only be sent once, or else the already_captured
protocol error is raised. A buffer must be attached before this request
is sent, or else the no_buffer protocol error is raised.
This event is sent before the ready event and holds the transform that
the compositor has applied to the buffer contents.
This event is sent before the ready event. It may be generated multiple
times to describe a region.
The first captured frame in a session will always carry full damage.
Subsequent frames' damaged regions describe which parts of the buffer
have changed since the last ready event.
These coordinates originate in the upper left corner of the buffer.
This event indicates the time at which the frame is presented to the
output in system monotonic time. This event is sent before the ready
event.
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].
Called as soon as the frame is copied, indicating it is available
for reading.
The buffer may be re-used by the client after this event.
After receiving this event, the client must destroy the object.
An unspecified runtime error has occurred. The client may retry.
The buffer submitted by the client doesn't match the latest session
constraints. The client should re-allocate its buffers and retry.
The session has stopped. See ext_image_copy_capture_session_v1.stopped.
This event indicates that the attempted frame copy has failed.
After receiving this event, the client must destroy the object.
This object represents a cursor capture session. It extends the base
capture session with cursor-specific metadata.
Destroys the session. This request can be sent at any time by the
client.
This request doesn't affect ext_image_copy_capture_frame_v1 objects created by
this object.
Gets the image copy capture session for this cursor session.
The session will produce frames of the cursor image. The compositor may
pause the session when the cursor leaves the captured area.
This request must not be sent more than once, or else the
duplicate_session protocol error is raised.
Sent when a cursor enters the captured area. It shall be generated
before the "position" and "hotspot" events when and only when a cursor
enters the area.
The cursor enters the captured area when the cursor image intersects
with the captured area. Note, this is different from e.g.
wl_pointer.enter.
Sent when a cursor leaves the captured area. No "position" or "hotspot"
event is generated for the cursor until the cursor enters the captured
area again.
Cursors outside the image capture source do not get captured and no
event will be generated for them.
The given position is the position of the cursor's hotspot and it is
relative to the main buffer's top left corner in transformed buffer
pixel coordinates. The coordinates may be negative or greater than the
main buffer size.
The hotspot describes the offset between the cursor image and the
position of the input device.
The given coordinates are the hotspot's offset from the origin in
buffer coordinates.
Clients should not apply the hotspot immediately: the hotspot becomes
effective when the next ext_image_copy_capture_frame_v1.ready event is received.
Compositors may delay this event until the client captures a new frame.
any1-wayvnc-ae53f07/protocols/ext-transient-seat-v1.xml 0000664 0000000 0000000 00000011100 15223766131 0023133 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/linux-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000042726 15223766131 0023624 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/meson.build 0000664 0000000 0000000 00000002503 15223766131 0020477 0 ustar 00root root 0000000 0000000 wayland_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',
'ext-image-copy-capture-v1.xml',
'ext-image-capture-source-v1.xml',
'ext-foreign-toplevel-list-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',
'ext-data-control-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,
)
any1-wayvnc-ae53f07/protocols/virtual-keyboard-unstable-v1.xml 0000664 0000000 0000000 00000011426 15223766131 0024506 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-data-control-unstable-v1.xml 0000664 0000000 0000000 00000027416 15223766131 0024421 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-export-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000021735 15223766131 0024605 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-output-management-unstable-v1.xml 0000664 0000000 0000000 00000062266 15223766131 0025506 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-output-power-management-unstable-v1.xml 0000664 0000000 0000000 00000012733 15223766131 0026632 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-screencopy-unstable-v1.xml 0000664 0000000 0000000 00000023666 15223766131 0024207 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/wlr-virtual-pointer-unstable-v1.xml 0000664 0000000 0000000 00000015357 15223766131 0025177 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/protocols/xdg-output-unstable-v1.xml 0000664 0000000 0000000 00000022512 15223766131 0023340 0 ustar 00root root 0000000 0000000
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.
any1-wayvnc-ae53f07/src/ 0000775 0000000 0000000 00000000000 15223766131 0015100 5 ustar 00root root 0000000 0000000 any1-wayvnc-ae53f07/src/buffer.c 0000664 0000000 0000000 00000040465 15223766131 0016526 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 - 2026 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 "linux-dmabuf-unstable-v1.h"
#include "shm.h"
#include "sys/queue.h"
#include "buffer.h"
#include "pixels.h"
#include "config.h"
#include "util.h"
#include "strlcpy.h"
#include "wayland.h"
#ifdef ENABLE_SCREENCOPY_DMABUF
#include
#include
#include
#include
#ifdef HAVE_LINUX_DMA_HEAP
#include
#include
#endif // HAVE_LINUX_DMA_HEAP
#endif // ENABLE_SCREENCOPY_DMABUF
extern struct wayland* wayland;
static void wv_buffer__handle_cleanup(void* userdata);
static bool modifiers_match(const uint64_t* a, int a_len, const uint64_t* b,
int b_len)
{
if (a_len != b_len)
return false;
return a_len == 0 || memcmp(a, b, a_len) == 0;
}
static bool buffer_configs_match(const struct wv_buffer_config* a,
const struct wv_buffer_config* b)
{
#define X(n) if (a->n != b->n) return false
X(type);
X(width);
X(height);
X(stride);
X(format);
X(node);
X(n_modifiers);
#undef X
return modifiers_match(a->modifiers, a->n_modifiers, b->modifiers,
b->n_modifiers);
}
static void copy_buffer_config(struct wv_buffer_config* dst,
const struct wv_buffer_config* src)
{
free(dst->modifiers);
memcpy(dst, src, sizeof(*dst));
dst->n_modifiers = 0;
dst->modifiers = NULL;
if (src->n_modifiers > 0) {
assert(src->modifiers);
dst->modifiers = malloc(src->n_modifiers * 8);
assert(dst->modifiers);
memcpy(dst->modifiers, src->modifiers, src->n_modifiers * 8);
dst->n_modifiers = src->n_modifiers;
}
}
enum wv_buffer_type wv_buffer_get_available_types(void)
{
enum wv_buffer_type type = 0;
if (wayland->wl_shm)
type |= WV_BUFFER_SHM;
#ifdef ENABLE_SCREENCOPY_DMABUF
if (wayland->zwp_linux_dmabuf_v1)
type |= WV_BUFFER_DMABUF;
#endif
return type;
}
static void wv_buffer_handle_wayland_destroyed(struct observer* observer,
void *data)
{
struct wv_buffer* self = wl_container_of(observer, self,
wayland_destroy_observer);
if (self->wl_buffer)
wl_buffer_destroy(self->wl_buffer);
self->wl_buffer = NULL;
}
static struct wv_buffer* wv_buffer_create_shm(
const struct wv_buffer_config* config)
{
assert(wayland->wl_shm);
enum wl_shm_format wl_fmt = fourcc_to_wl_shm(config->format);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_SHM;
self->width = config->width;
self->height = config->height;
self->stride = config->stride;
self->format = config->format;
self->size = config->height * config->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(wayland->wl_shm, fd,
self->size);
if (!pool)
goto pool_failure;
self->wl_buffer = wl_shm_pool_create_buffer(pool, 0, config->width,
config->height, config->stride, wl_fmt);
wl_shm_pool_destroy(pool);
if (!self->wl_buffer)
goto shm_failure;
int bpp = pixel_size_from_fourcc(config->format);
assert(bpp > 0);
self->buffer = nvnc_buffer_from_addr(self->pixels);
if (!self->buffer) {
goto nvnc_buffer_failure;
}
nvnc_buffer_set_userdata(self->buffer, self, wv_buffer__handle_cleanup);
pixman_region_init(&self->frame_damage);
pixman_region_init_rect(&self->buffer_damage, 0, 0, config->width,
config->height);
observer_init(&self->wayland_destroy_observer,
&wayland->observable.destroyed,
wv_buffer_handle_wayland_destroyed);
close(fd);
return self;
nvnc_buffer_failure:
wl_buffer_destroy(self->wl_buffer);
shm_failure:
pool_failure:
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
#ifdef ENABLE_SCREENCOPY_DMABUF
#ifdef HAVE_LINUX_DMA_HEAP
static const char *get_cma_path(void)
{
static const char *path;
if (path)
return path;
path = getenv("WAYVNC_CMA");
return path;
}
static int linux_cma_alloc(size_t size)
{
int fd = open(get_cma_path(), O_RDWR | O_CLOEXEC, 0);
if (fd < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to open CMA device: %m");
return -1;
}
struct dma_heap_allocation_data data = {
.len = size,
.fd_flags = O_CLOEXEC | O_RDWR,
};
int r = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
if (r < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate CMA buffer: %m");
return -1;
}
close(fd);
return data.fd;
}
// Some devices (mostly ARM SBCs) need CMA for hardware encoders.
static struct gbm_bo* create_cma_gbm_bo(int width, int height, uint32_t fourcc,
struct wv_gbm_device* gbm)
{
int bpp = pixel_size_from_fourcc(fourcc);
if (!bpp) {
nvnc_log(NVNC_LOG_PANIC, "Unsupported pixel format: %" PRIu32,
fourcc);
}
/* TODO: Get alignment through feedback mechanism.
* Buffer sizes are aligned on both axes by 16 and we'll do the same
* in the encoder, but this requirement should come from the encoder.
*/
int stride = bpp * ALIGN_UP(width, 16);
int fd = linux_cma_alloc(stride * ALIGN_UP(height, 16));
if (fd < 0) {
return NULL;
}
struct gbm_import_fd_modifier_data d = {
.format = fourcc,
.width = width,
.height = height,
// v4l2m2m doesn't support modifiers, so we use linear
.modifier = DRM_FORMAT_MOD_LINEAR,
.num_fds = 1,
.fds[0] = fd,
.offsets[0] = 0,
.strides[0] = stride,
};
struct gbm_bo* bo = gbm_bo_import(gbm->dev, GBM_BO_IMPORT_FD_MODIFIER,
&d, 0);
if (!bo) {
nvnc_log(NVNC_LOG_DEBUG, "Failed to import dmabuf: %m");
close(fd);
return NULL;
}
return bo;
}
#endif // HAVE_LINUX_DMA_HEAP
#ifdef ENABLE_SCREENCOPY_DMABUF
static void wv_gbm_device_ref(struct wv_gbm_device* dev)
{
++dev->ref;
}
static void wv_gbm_device_unref(struct wv_gbm_device* dev)
{
if (!dev || --dev->ref != 0)
return;
if (dev->dev)
gbm_device_destroy(dev->dev);
if (dev->fd > 0)
close(dev->fd);
free(dev);
}
#endif
static struct wv_buffer* wv_buffer_create_dmabuf(
const struct wv_buffer_config* config,
struct wv_gbm_device* gbm)
{
assert(wayland->zwp_linux_dmabuf_v1);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_DMABUF;
self->width = config->width;
self->height = config->height;
self->format = config->format;
self->node = config->node;
self->n_modifiers = config->n_modifiers;
if (self->n_modifiers > 0) {
self->modifiers = malloc(config->n_modifiers * 8);
assert(self->modifiers);
memcpy(self->modifiers, config->modifiers, self->n_modifiers * 8);
}
#ifdef HAVE_LINUX_DMA_HEAP
if (get_cma_path()) {
self->bo = create_cma_gbm_bo(config->width, config->height,
config->format, gbm);
} else
#endif
{
self->bo = gbm_bo_create_with_modifiers2(gbm->dev,
config->width, config->height, config->format,
config->modifiers, config->n_modifiers,
GBM_BO_USE_RENDERING);
}
if (!self->bo)
goto bo_failure;
struct zwp_linux_buffer_params_v1* params;
params = zwp_linux_dmabuf_v1_create_params(wayland->zwp_linux_dmabuf_v1);
if (!params)
goto params_failure;
int n_planes = gbm_bo_get_plane_count(self->bo);
assert(n_planes <= 4);
uint64_t mod = gbm_bo_get_modifier(self->bo);
int fds[4] = { -1, -1, -1, -1 };
for (int i = 0; i < n_planes; ++i) {
uint32_t offset = gbm_bo_get_offset(self->bo, i);
uint32_t stride = gbm_bo_get_stride_for_plane(self->bo, i);
fds[i] = gbm_bo_get_fd_for_plane(self->bo, i);
if (fds[i] < 0)
goto fd_failure;
zwp_linux_buffer_params_v1_add(params, fds[i], i, offset, stride,
mod >> 32, mod & 0xffffffff);
}
self->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params,
config->width, config->height, config->format,
/* flags */ 0);
zwp_linux_buffer_params_v1_destroy(params);
for (int i = 0; i < 4; ++i)
if (fds[i] >= 0)
close(fds[i]);
if (!self->wl_buffer)
goto buffer_failure;
self->buffer = nvnc_buffer_from_gbm_bo(self->bo);
if (!self->buffer) {
goto nvnc_buffer_failure;
}
nvnc_buffer_set_userdata(self->buffer, self, wv_buffer__handle_cleanup);
pixman_region_init(&self->frame_damage);
pixman_region_init_rect(&self->buffer_damage, 0, 0, config->width,
config->height);
self->gbm = gbm;
wv_gbm_device_ref(gbm);
observer_init(&self->wayland_destroy_observer,
&wayland->observable.destroyed,
wv_buffer_handle_wayland_destroyed);
return self;
nvnc_buffer_failure:
wl_buffer_destroy(self->wl_buffer);
buffer_failure:
fd_failure:
for (int i = 0; i < 4; ++i)
if (fds[i] >= 0)
close(fds[i]);
zwp_linux_buffer_params_v1_destroy(params);
params_failure:
gbm_bo_destroy(self->bo);
bo_failure:
free(self);
return NULL;
}
#endif
#ifdef ENABLE_SCREENCOPY_DMABUF
static struct wv_buffer* wv_buffer_create(const struct wv_buffer_config* config,
struct wv_gbm_device* gbm)
#else
static struct wv_buffer* wv_buffer_create(const struct wv_buffer_config* config)
#endif
{
nvnc_trace("wv_buffer_create: %dx%d, stride: %d, format: %"PRIu32,
config->width, config->height, config->stride,
config->format);
switch (config->type) {
case WV_BUFFER_SHM:
return wv_buffer_create_shm(config);
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
return wv_buffer_create_dmabuf(config, gbm);
#endif
case WV_BUFFER_UNSPEC:;
}
abort();
return NULL;
}
static void wv_buffer_destroy_shm(struct wv_buffer* self)
{
munmap(self->pixels, self->size);
free(self);
}
#ifdef ENABLE_SCREENCOPY_DMABUF
static void wv_buffer_destroy_dmabuf(struct wv_buffer* self)
{
free(self->modifiers);
gbm_bo_destroy(self->bo);
wv_gbm_device_unref(self->gbm);
free(self);
}
#endif
static void wv_buffer_destroy(struct wv_buffer* self)
{
if (self->link.le_next || self->link.le_prev)
LIST_REMOVE(self, link);
pixman_region_fini(&self->buffer_damage);
pixman_region_fini(&self->frame_damage);
observer_deinit(&self->wayland_destroy_observer);
if (self->wl_buffer)
wl_buffer_destroy(self->wl_buffer);
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();
}
static void wv_buffer__handle_cleanup(void* userdata)
{
struct wv_buffer* buffer = userdata;
wv_buffer_destroy(buffer);
}
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height)
{
pixman_region_union_rect(&self->frame_damage, &self->frame_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->frame_damage);
}
struct wv_buffer_pool* wv_buffer_pool_create(
const struct wv_buffer_config* config)
{
struct wv_buffer_pool* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
LIST_INIT(&self->list);
if (config)
wv_buffer_pool_reconfig(self, config);
return self;
}
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool)
{
while (!LIST_EMPTY(&pool->list)) {
struct wv_buffer* buffer = LIST_FIRST(&pool->list);
LIST_REMOVE(buffer, link);
memset(&buffer->link, 0, sizeof(buffer->link));
}
nvnc_buffer_pool_unref(pool->nvnc_pool);
free(pool->config.modifiers);
#ifdef ENABLE_SCREENCOPY_DMABUF
wv_gbm_device_unref(pool->gbm);
#endif
free(pool);
}
#ifdef ENABLE_SCREENCOPY_DMABUF
static int render_node_from_dev_t(char* node, size_t maxlen, dev_t device)
{
drmDevice *dev_ptr;
if (drmGetDeviceFromDevId(device, 0, &dev_ptr) < 0)
return -1;
if (dev_ptr->available_nodes & (1 << DRM_NODE_RENDER))
strlcpy(node, dev_ptr->nodes[DRM_NODE_RENDER], maxlen);
drmFreeDevice(&dev_ptr);
return 0;
}
static int find_render_node(char *node, size_t maxlen) {
int 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 void open_render_node(struct wv_buffer_pool* pool)
{
char path[256];
if (pool->config.node) {
if (render_node_from_dev_t(path, sizeof(path),
pool->config.node) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Could not find render node from dev_t");
return;
}
} else if (find_render_node(path, sizeof(path)) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Could not find a render node");
return;
}
nvnc_log(NVNC_LOG_DEBUG, "Using render node: %s", path);
pool->gbm = calloc(1, sizeof(*pool->gbm));
assert(pool->gbm);
pool->gbm->ref = 1;
pool->gbm->fd = open(path, O_RDWR);
if (pool->gbm->fd < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to open render node %s: %m",
path);
free(pool->gbm);
pool->gbm = NULL;
return;
}
pool->gbm->dev = gbm_create_device(pool->gbm->fd);
if (!pool->gbm->dev) {
nvnc_log(NVNC_LOG_ERROR, "Failed to create a GBM device");
close(pool->gbm->fd);
free(pool->gbm);
pool->gbm = NULL;
}
}
bool reconfig_render_node(struct wv_buffer_pool* pool,
const struct wv_buffer_config* config, dev_t old_node)
{
if (config->type != WV_BUFFER_DMABUF) {
wv_gbm_device_unref(pool->gbm);
pool->gbm = NULL;
return true;
}
if (old_node != config->node) {
wv_gbm_device_unref(pool->gbm);
pool->gbm = NULL;
open_render_node(pool);
}
if (!pool->config.node && !pool->gbm)
open_render_node(pool);
return !!pool->gbm;
}
#endif // ENABLE_SCREENCOPY_DMABUF
static struct nvnc_buffer* wv_buffer_pool__alloc(
struct nvnc_buffer_pool* nvnc_pool)
{
struct wv_buffer_pool* pool = nvnc_buffer_pool_get_userdata(nvnc_pool);
#ifdef ENABLE_SCREENCOPY_DMABUF
struct wv_buffer* buffer = wv_buffer_create(&pool->config, pool->gbm);
#else
struct wv_buffer* buffer = wv_buffer_create(&pool->config);
#endif
if (!buffer)
return NULL;
LIST_INSERT_HEAD(&pool->list, buffer, link);
return buffer->buffer;
}
bool wv_buffer_pool_reconfig(struct wv_buffer_pool* pool,
const struct wv_buffer_config* config)
{
if (buffer_configs_match(&pool->config, config))
return true;
nvnc_log(NVNC_LOG_DEBUG, "Reconfiguring buffer pool");
nvnc_buffer_pool_unref(pool->nvnc_pool);
pool->nvnc_pool = nvnc_buffer_pool_new(wv_buffer_pool__alloc);
nvnc_buffer_pool_set_userdata(pool->nvnc_pool, pool, NULL);
#ifdef ENABLE_SCREENCOPY_DMABUF
dev_t old_node = pool->config.node;
#endif
copy_buffer_config(&pool->config, config);
#ifdef ENABLE_SCREENCOPY_DMABUF
return reconfig_render_node(pool, config, old_node);
#else
return true;
#endif
}
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool)
{
struct nvnc_buffer* nvnc_buffer = nvnc_buffer_pool_acquire(pool->nvnc_pool);
if (!nvnc_buffer)
return NULL;
struct wv_buffer* buffer = nvnc_buffer_get_userdata(nvnc_buffer);
assert(buffer);
struct wv_buffer_config* config = &pool->config;
int bpp = pixel_size_from_fourcc(config->format);
assert(bpp > 0);
assert(!buffer->nvnc_frame);
buffer->nvnc_frame = nvnc_frame_from_buffer(nvnc_buffer, config->width,
config->height, config->format, config->stride / bpp);
assert(buffer->nvnc_frame);
// Ownership is passed over to nvnc_frame
nvnc_buffer_unref(nvnc_buffer);
return buffer;
}
void wv_buffer_release(struct wv_buffer* self)
{
if (!self)
return;
pixman_region_clear(&self->frame_damage);
struct nvnc_frame* fb = self->nvnc_frame;
self->nvnc_frame = NULL;
nvnc_frame_unref(fb);
}
void wv_buffer_pool_damage_all(struct wv_buffer_pool* self,
struct pixman_region16* region)
{
struct wv_buffer* buffer;
LIST_FOREACH(buffer, &self->list, link)
pixman_region_union(&buffer->buffer_damage,
&buffer->buffer_damage, region);
}
any1-wayvnc-ae53f07/src/cfg.c 0000664 0000000 0000000 00000006736 15223766131 0016017 0 ustar 00root root 0000000 0000000 /*
* 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);
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);
}
any1-wayvnc-ae53f07/src/ctl-client.c 0000664 0000000 0000000 00000052050 15223766131 0017304 0 ustar 00root root 0000000 0000000 /*
* 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_SET_DESKTOP_NAME:
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");
struct table_printer printer;
table_printer_init(&printer, stdout);
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 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");
struct table_printer printer;
table_printer_init(&printer, stdout);
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");
struct table_printer printer;
table_printer_init(&printer, stdout);
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;
}
any1-wayvnc-ae53f07/src/ctl-commands.c 0000664 0000000 0000000 00000012057 15223766131 0017632 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 - 2026 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 },
{ "desktop", "Capture all outputs" },
{ "output", "Capture a specific output", "" },
{ "toplevel", "Capture a toplevel", "" },
{},
}
},
[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_SET_DESKTOP_NAME] = { "set-desktop-name",
"Update the desktop name",
{
{ "desktop-name",
"The desktop name",
"", 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",
{}
},
[EVT_OUTPUT_ADDED] = {"output-added",
"Sent when an output is added by the compositor",
{
{ "name", "Output name", "" },
{}
}
},
[EVT_OUTPUT_REMOVED] = {"output-removed",
"Sent when an output is removed by the compositor",
{
{ "name", "Output name", "" },
{}
}
},
};
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));
}
any1-wayvnc-ae53f07/src/ctl-server.c 0000664 0000000 0000000 00000070015 15223766131 0017335 0 ustar 00root root 0000000 0000000 /*
* 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
#include "output.h"
#include "ctl-commands.h"
#include "ctl-server.h"
#include "json-ipc.h"
#include "util.h"
#include "strlcpy.h"
#include "image-source.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];
enum image_source_type image_source_type;
char image_source_name[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_set_desktop_name {
struct cmd cmd;
char desktop_name[256];
};
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;
const char* is_desktop = false; // TODO: This should NOT be a string
const char* output = NULL;
const char* toplevel = NULL;
if (json_unpack(args, "{s:s, s?s, s?s, s?s}",
"display", &display,
"desktop", &is_desktop,
"output", &output,
"toplevel", &toplevel) == -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));
if (is_desktop) {
cmd->image_source_type = IMAGE_SOURCE_TYPE_DESKTOP;
} else if (output) {
cmd->image_source_type = IMAGE_SOURCE_TYPE_OUTPUT;
strlcpy(cmd->image_source_name, output,
sizeof(cmd->image_source_name));
} else if (toplevel) {
cmd->image_source_type = IMAGE_SOURCE_TYPE_TOPLEVEL;
strlcpy(cmd->image_source_name, toplevel,
sizeof(cmd->image_source_name));
}
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 struct cmd_set_desktop_name* cmd_set_desktop_name_new(json_t* args,
struct jsonipc_error* err)
{
const char* desktop_name = NULL;
if (json_unpack(args, "{s:s}", "desktop-name", &desktop_name) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing desktop name");
return NULL;
}
struct cmd_set_desktop_name* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->desktop_name, desktop_name, sizeof(cmd->desktop_name));
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_SET_DESKTOP_NAME:
cmd = (struct cmd*)cmd_set_desktop_name_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,
c->image_source_type, c->image_source_name);
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_SET_DESKTOP_NAME: {
struct cmd_set_desktop_name* c =
(struct cmd_set_desktop_name*)cmd;
response = self->actions.on_set_desktop_name(self, c->desktop_name);
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(struct aml_handler* handler)
{
struct ctl_client* client = aml_get_userdata(handler);
uint32_t events = aml_get_revents(handler);
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(struct aml_handler* handler)
{
nvnc_log(NVNC_LOG_DEBUG, "New connection");
struct ctl* server = aml_get_userdata(handler);
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());
}
void ctl_server_event_output_added(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_ADDED,
json_pack("{s:s}", "name", name));
}
void ctl_server_event_output_removed(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_REMOVED,
json_pack("{s:s}", "name", name));
}
any1-wayvnc-ae53f07/src/data-control.c 0000664 0000000 0000000 00000043003 15223766131 0017633 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 Scott Moreau
* Copyright (c) 2020 - 2026 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 "data-control.h"
#include "vec.h"
static const char custom_mime_type_data[] = "wayvnc";
struct receive_context {
struct nvnc* server;
struct aml_handler* handler;
LIST_ENTRY(receive_context) link;
int fd;
struct wv_vec buffer;
};
struct send_context {
struct aml_handler* handler;
LIST_ENTRY(send_context) link;
int fd;
char* data;
size_t length;
size_t index;
};
static void destroy_receive_context(struct receive_context* ctx)
{
aml_stop(aml_get_default(), ctx->handler);
aml_unref(ctx->handler);
close(ctx->fd);
wv_vec_destroy(&ctx->buffer);
LIST_REMOVE(ctx, link);
free(ctx);
}
static void destroy_send_context(struct send_context* ctx)
{
aml_stop(aml_get_default(), ctx->handler);
aml_unref(ctx->handler);
close(ctx->fd);
free(ctx->data);
LIST_REMOVE(ctx, link);
free(ctx);
}
static void on_receive(struct aml_handler* 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 == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
nvnc_log(NVNC_LOG_ERROR, "Clipboard read failed: %m");
destroy_receive_context(ctx);
} else if (ret > 0) {
wv_vec_append(&ctx->buffer, buf, ret);
return;
}
if (ctx->buffer.len != 0)
nvnc_send_cut_text(ctx->server, ctx->buffer.data,
ctx->buffer.len);
wv_vec_clear(&ctx->buffer);
destroy_receive_context(ctx);
}
static void on_send(struct aml_handler* handler)
{
struct send_context* ctx = aml_get_userdata(handler);
int fd = aml_get_fd(handler);
assert(ctx->fd == fd);
int ret;
ret = write(fd, ctx->data + ctx->index, ctx->length - ctx->index);
if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
nvnc_log(NVNC_LOG_ERROR, "Clipboard write failed/incomplete: %m");
destroy_send_context(ctx);
} else if (ret == (int)(ctx->length - ctx->index)) {
destroy_send_context(ctx);
} else {
ctx->index += ret;
}
}
static int dont_block(int fd)
{
int ret = fcntl(fd, F_GETFL);
if (ret == -1)
return -1;
return fcntl(fd, F_SETFL, ret | O_NONBLOCK);
}
static struct receive_context* receive_data_init(int pipe_fd[])
{
if (pipe(pipe_fd) == -1) {
nvnc_log(NVNC_LOG_ERROR, "pipe() failed: %m");
return NULL;
}
if (dont_block(pipe_fd[0]) == -1) {
nvnc_log(NVNC_LOG_ERROR, "Failed to set O_NONBLOCK on clipbooard receive fd");
goto failure;
}
struct receive_context* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
goto failure;
}
return ctx;
failure:
close(pipe_fd[0]);
close(pipe_fd[1]);
return NULL;
}
static void receive_data_finish(struct data_control* self,
struct receive_context* ctx, int pipe_fd[])
{
close(pipe_fd[1]);
ctx->fd = pipe_fd[0];
ctx->server = self->server;
if (wv_vec_init(&ctx->buffer, 4096) < 0) {
nvnc_log(NVNC_LOG_ERROR, "open_memstream() failed: %m");
goto open_memstream_failure;
}
ctx->handler = aml_handler_new(ctx->fd, on_receive, ctx, NULL);
if (!ctx->handler) {
goto handler_failure;
}
if (aml_start(aml_get_default(), ctx->handler) < 0) {
goto poll_start_failure;
}
LIST_INSERT_HEAD(&self->receive_contexts, ctx, link);
return;
poll_start_failure:
aml_unref(ctx->handler);
handler_failure:
wv_vec_destroy(&ctx->buffer);
open_memstream_failure:
free(ctx);
close(pipe_fd[0]);
}
static void receive_data_wlr(void* data,
struct zwlr_data_control_offer_v1* offer)
{
struct data_control* self = data;
int pipe_fd[2];
struct receive_context *ctx = receive_data_init(pipe_fd);
if (!ctx)
return;
zwlr_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]);
receive_data_finish(self, ctx, pipe_fd);
}
static void data_control_offer_wlr(void* data,
struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1,
const char* mime_type)
{
struct data_control* self = data;
if (strcmp(mime_type, self->custom_mime_type_name) == 0) {
self->is_own_offer = true;
return;
}
if (self->wlr.offer)
return;
if (strcmp(mime_type, self->mime_type) == 0)
self->wlr.offer = zwlr_data_control_offer_v1;
}
static struct
zwlr_data_control_offer_v1_listener data_control_offer_listener_wlr = {
data_control_offer_wlr
};
static void data_control_device_offer_wlr(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_wlr, data);
}
static void data_control_device_selection_wlr(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) {
if (self->wlr.offer) {
zwlr_data_control_offer_v1_destroy(self->wlr.offer);
self->wlr.offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->wlr.offer && !self->is_own_offer)
receive_data_wlr(data, id);
zwlr_data_control_offer_v1_destroy(id);
self->wlr.offer = NULL;
self->is_own_offer = false;
}
static void data_control_device_finished_wlr(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_wlr(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) {
if (self->wlr.offer) {
zwlr_data_control_offer_v1_destroy(self->wlr.offer);
self->wlr.offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->wlr.offer && !self->is_own_offer)
receive_data_wlr(data, id);
zwlr_data_control_offer_v1_destroy(id);
self->wlr.offer = NULL;
self->is_own_offer = false;
}
static struct
zwlr_data_control_device_v1_listener data_control_device_listener_wlr = {
.data_offer = data_control_device_offer_wlr,
.selection = data_control_device_selection_wlr,
.finished = data_control_device_finished_wlr,
.primary_selection = data_control_device_primary_selection_wlr
};
static void data_control_source_send_common(void* data,
const char* mime_type, int32_t fd)
{
struct data_control* self = data;
const char* d = self->cb_data;
size_t len = self->cb_len;
int ret;
assert(d);
assert(len);
if (strcmp(mime_type, self->custom_mime_type_name) == 0) {
d = custom_mime_type_data;
len = strlen(custom_mime_type_data);
}
if (dont_block(fd) == -1) {
nvnc_log(NVNC_LOG_ERROR, "Failed to set O_NONBLOCK on clipbooard send fd");
close(fd);
return;
}
ret = write(fd, d, len);
if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
ret = 0;
} else {
nvnc_log(NVNC_LOG_ERROR, "Clipboard write failed: %m");
close(fd);
return;
}
} else if (ret == (int)len) {
close(fd);
return;
}
/* we did a partial write, so continue sending data asynchronously */
struct send_context* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
goto ctx_alloc_failure;
return;
}
ctx->fd = fd;
ctx->length = len - ret;
ctx->index = 0;
ctx->data = malloc(ctx->length);
if (!ctx->data) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
goto ctx_data_alloc_failure;
}
memcpy(ctx->data, d + ret, ctx->length);
ctx->handler = aml_handler_new(ctx->fd, on_send, ctx, NULL);
if (!ctx->handler)
goto handler_failure;
aml_set_event_mask(ctx->handler, AML_EVENT_WRITE);
if (aml_start(aml_get_default(), ctx->handler) < 0)
goto poll_start_failure;
LIST_INSERT_HEAD(&self->send_contexts, ctx, link);
return;
poll_start_failure:
aml_unref(ctx->handler);
handler_failure:
free(ctx->data);
ctx_data_alloc_failure:
free(ctx);
ctx_alloc_failure:
close(fd);
nvnc_log(NVNC_LOG_ERROR, "Clipboard write incomplete");
}
static void data_control_source_send_wlr(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1,
const char* mime_type, int32_t fd)
{
data_control_source_send_common(data, mime_type, fd);
}
static void data_control_source_cancelled_wlr(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1)
{
struct data_control* self = data;
if (self->wlr.selection == zwlr_data_control_source_v1) {
self->wlr.selection = NULL;
}
if (self->wlr.primary_selection == zwlr_data_control_source_v1) {
self->wlr.primary_selection = NULL;
}
zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1);
}
static struct zwlr_data_control_source_v1_listener
data_control_source_listener_wlr = {
.send = data_control_source_send_wlr,
.cancelled = data_control_source_cancelled_wlr
};
static struct zwlr_data_control_source_v1* set_selection_wlr(
struct data_control* self, bool primary) {
struct zwlr_data_control_source_v1* selection;
selection = zwlr_data_control_manager_v1_create_data_source(
self->wlr_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_wlr, self);
zwlr_data_control_source_v1_offer(selection, self->mime_type);
zwlr_data_control_source_v1_offer(selection, self->custom_mime_type_name);
if (primary)
zwlr_data_control_device_v1_set_primary_selection(
self->wlr.device, selection);
else
zwlr_data_control_device_v1_set_selection(
self->wlr.device, selection);
return selection;
}
static void receive_data_ext(void* data,
struct ext_data_control_offer_v1* offer)
{
struct data_control* self = data;
int pipe_fd[2];
struct receive_context *ctx = receive_data_init(pipe_fd);
if (!ctx)
return;
ext_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]);
receive_data_finish(self, ctx, pipe_fd);
}
static void data_control_offer_ext(void* data,
struct ext_data_control_offer_v1* ext_data_control_offer_v1,
const char* mime_type)
{
struct data_control* self = data;
if (strcmp(mime_type, self->custom_mime_type_name) == 0) {
self->is_own_offer = true;
return;
}
if (self->ext.offer)
return;
if (strcmp(mime_type, self->mime_type) == 0)
self->ext.offer = ext_data_control_offer_v1;
}
static struct
ext_data_control_offer_v1_listener data_control_offer_listener_ext = {
data_control_offer_ext
};
static void data_control_device_offer_ext(void* data,
struct ext_data_control_device_v1* ext_data_control_device_v1,
struct ext_data_control_offer_v1* id)
{
if (!id)
return;
ext_data_control_offer_v1_add_listener(id,
&data_control_offer_listener_ext, data);
}
static void data_control_device_selection_ext(void* data,
struct ext_data_control_device_v1* ext_data_control_device_v1,
struct ext_data_control_offer_v1* id)
{
struct data_control* self = data;
if (!id) {
if (self->ext.offer) {
ext_data_control_offer_v1_destroy(self->ext.offer);
self->ext.offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->ext.offer && !self->is_own_offer)
receive_data_ext(data, id);
ext_data_control_offer_v1_destroy(id);
self->ext.offer = NULL;
self->is_own_offer = false;
}
static void data_control_device_finished_ext(void* data,
struct ext_data_control_device_v1* ext_data_control_device_v1)
{
ext_data_control_device_v1_destroy(ext_data_control_device_v1);
}
static void data_control_device_primary_selection_ext(void* data,
struct ext_data_control_device_v1* ext_data_control_device_v1,
struct ext_data_control_offer_v1* id)
{
struct data_control* self = data;
if (!id) {
if (self->ext.offer) {
ext_data_control_offer_v1_destroy(self->ext.offer);
self->ext.offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->ext.offer && !self->is_own_offer)
receive_data_ext(data, id);
ext_data_control_offer_v1_destroy(id);
self->ext.offer = NULL;
self->is_own_offer = false;
}
static struct
ext_data_control_device_v1_listener data_control_device_listener_ext = {
.data_offer = data_control_device_offer_ext,
.selection = data_control_device_selection_ext,
.finished = data_control_device_finished_ext,
.primary_selection = data_control_device_primary_selection_ext
};
static void
data_control_source_send_ext(void* data,
struct ext_data_control_source_v1* ext_data_control_source_v1,
const char* mime_type,
int32_t fd)
{
data_control_source_send_common(data, mime_type, fd);
}
static void data_control_source_cancelled_ext(void* data,
struct ext_data_control_source_v1* ext_data_control_source_v1)
{
struct data_control* self = data;
if (self->ext.selection == ext_data_control_source_v1) {
self->ext.selection = NULL;
}
if (self->ext.primary_selection == ext_data_control_source_v1) {
self->ext.primary_selection = NULL;
}
ext_data_control_source_v1_destroy(ext_data_control_source_v1);
}
static struct ext_data_control_source_v1_listener
data_control_source_listener_ext = {
.send = data_control_source_send_ext,
.cancelled = data_control_source_cancelled_ext
};
static struct ext_data_control_source_v1* set_selection_ext(
struct data_control* self, bool primary) {
struct ext_data_control_source_v1* selection;
selection = ext_data_control_manager_v1_create_data_source(
self->ext_manager);
if (selection == NULL) {
nvnc_log(NVNC_LOG_ERROR, "ext_data_control_manager_v1_create_data_source() failed");
free(self->cb_data);
self->cb_data = NULL;
return NULL;
}
ext_data_control_source_v1_add_listener(selection,
&data_control_source_listener_ext, self);
ext_data_control_source_v1_offer(selection, self->mime_type);
ext_data_control_source_v1_offer(selection, self->custom_mime_type_name);
if (primary)
ext_data_control_device_v1_set_primary_selection(
self->ext.device, selection);
else
ext_data_control_device_v1_set_selection(
self->ext.device, selection);
return selection;
}
void data_control_init(struct data_control* self, struct nvnc* server,
struct wl_seat* seat)
{
self->server = server;
LIST_INIT(&self->receive_contexts);
LIST_INIT(&self->send_contexts);
if (self->wlr_manager) {
self->wlr.device = zwlr_data_control_manager_v1_get_data_device(
self->wlr_manager, seat);
zwlr_data_control_device_v1_add_listener(self->wlr.device,
&data_control_device_listener_wlr, self);
} else if (self->ext_manager) {
self->ext.device = ext_data_control_manager_v1_get_data_device(
self->ext_manager, seat);
ext_data_control_device_v1_add_listener(self->ext.device,
&data_control_device_listener_ext, self);
} else {
nvnc_log(NVNC_LOG_PANIC, "No data control manager available");
}
self->ext.selection = NULL;
self->ext.primary_selection = NULL;
self->ext.offer = NULL;
self->is_own_offer = false;
self->cb_data = NULL;
self->cb_len = 0;
self->mime_type = "text/plain;charset=utf-8";
snprintf(self->custom_mime_type_name,
sizeof(self->custom_mime_type_name),
"x-wayvnc-client-%08x", (unsigned int)rand());
}
void data_control_destroy(struct data_control* self)
{
while (!LIST_EMPTY(&self->receive_contexts))
destroy_receive_context(LIST_FIRST(&self->receive_contexts));
while (!LIST_EMPTY(&self->send_contexts)) {
nvnc_log(NVNC_LOG_ERROR, "Clipboard write incomplete due to client disconnection");
destroy_send_context(LIST_FIRST(&self->send_contexts));
}
if (self->wlr_manager) {
if (self->wlr.selection) {
zwlr_data_control_source_v1_destroy(self->wlr.selection);
self->wlr.selection = NULL;
}
if (self->wlr.primary_selection) {
zwlr_data_control_source_v1_destroy(
self->wlr.primary_selection);
self->wlr.primary_selection = NULL;
}
zwlr_data_control_device_v1_destroy(self->wlr.device);
} else if (self->ext_manager) {
if (self->ext.selection) {
ext_data_control_source_v1_destroy(self->ext.selection);
self->ext.selection = NULL;
}
if (self->ext.primary_selection) {
ext_data_control_source_v1_destroy(
self->ext.primary_selection);
self->ext.primary_selection = NULL;
}
ext_data_control_device_v1_destroy(self->ext.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_DEBUG, "Ignoring empty clipboard from VNC client");
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;
if (self->wlr_manager) {
// Set copy/paste buffer
self->wlr.selection = set_selection_wlr(self, false);
// Set highlight/middle_click buffer
self->wlr.primary_selection = set_selection_wlr(self, true);
} else if (self->ext_manager) {
// Set copy/paste buffer
self->ext.selection = set_selection_ext(self, false);
// Set highlight/middle_click buffer
self->ext.primary_selection = set_selection_ext(self, true);
}
}
any1-wayvnc-ae53f07/src/desktop.c 0000664 0000000 0000000 00000036172 15223766131 0016726 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2025 - 2026 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 "desktop.h"
#include "output.h"
#include "wayland.h"
#include
#include
#include
#include
#include
#include
static void desktop_capture_handle_done(enum screencopy_result result,
struct wv_buffer* buffer, struct image_source* source,
void* userdata);
static double desktop_capture_rate_format(const void* userdata,
enum wv_buffer_type type, uint32_t format, uint64_t modifier);
extern struct wayland* wayland;
struct desktop* desktop_from_image_source(const struct image_source* source)
{
assert(image_source_is_desktop(source));
struct desktop* self = wl_container_of(source, self, image_source);
return self;
}
void desktop_destroy(struct desktop *self)
{
image_source_destroy(&self->image_source);
}
static double desktop_image_source_get_min_scale(const struct image_source* base)
{
struct desktop* self = desktop_from_image_source(base);
double min_scale = HUGE_VAL;
struct desktop_output* output;
LIST_FOREACH(output, &self->outputs, link) {
double h_scale, v_scale;
if (!image_source_get_scale(&output->output->image_source,
&h_scale, &v_scale))
continue;
double scale = fmin(h_scale, v_scale);
if (scale < min_scale)
min_scale = scale;
}
return isinf(min_scale) ? 1.0 : min_scale;
}
static void desktop_image_source_get_logical_size(const struct image_source* base,
int* width_out, int* height_out)
{
struct desktop* self = desktop_from_image_source(base);
double min_scale = desktop_image_source_get_min_scale(base);
int width = 0;
int height = 0;
struct desktop_output* output;
LIST_FOREACH(output, &self->outputs, link) {
int logical_width, logical_height;
output_get_logical_size(output->output, &logical_width,
&logical_height);
int x, y;
output_get_pos(output->output, &x, &y);
int w = round((x + logical_width) / min_scale);
int h = round((y + logical_height) / min_scale);
if (w > width)
width = w;
if (h > height)
height = h;
}
if (width_out)
*width_out = width;
if (height_out)
*height_out = height;
}
static enum image_source_power_state desktop_image_source_get_power_state(
const struct image_source* base)
{
struct desktop* self = desktop_from_image_source(base);
enum image_source_power_state state = IMAGE_SOURCE_POWER_UNKNOWN;
struct desktop_output* output = LIST_FIRST(&self->outputs);
if (!output)
return IMAGE_SOURCE_POWER_UNKNOWN;
state = image_source_get_power(&output->output->image_source);
for (output = LIST_NEXT(output, link); output;
output = LIST_NEXT(output, link)) {
enum image_source_power_state this_state =
image_source_get_power(&output->output->image_source);
if (this_state != state) {
nvnc_log(NVNC_LOG_WARNING, "Power state mismatch between outputs");
return IMAGE_SOURCE_POWER_UNKNOWN;
}
}
nvnc_log(NVNC_LOG_DEBUG, "Returing power state: %s\n",
image_source_power_state_name(state));
return state;
}
static void desktop_image_source_describe(const struct image_source* base,
char* dst, size_t maxlen)
{
snprintf(dst, maxlen, "Desktop");
}
static int desktop_image_source_acquire_power_on(struct image_source* base)
{
struct desktop* self = desktop_from_image_source(base);
int status = 0;
struct desktop_output* output;
LIST_FOREACH(output, &self->outputs, link) {
int rc = output_acquire_power_on(output->output);
if (rc < 0)
goto fail;
if (rc > 0)
status = 1;
}
return status;
fail:
LIST_FOREACH(output, &self->outputs, link)
output_release_power_on(output->output);
return -1;
}
static void desktop_image_source_release_power_on(struct image_source* base)
{
struct desktop* self = desktop_from_image_source(base);
struct desktop_output* output;
LIST_FOREACH(output, &self->outputs, link)
output_release_power_on(output->output);
}
static void desktop_image_source_output_power_change(struct observer* observer,
void *arg)
{
struct desktop_output* event_source = wl_container_of(observer,
event_source, power_change_observer);
struct desktop* desktop = event_source->desktop;
int n = 0, n_off = 0, n_on = 0;
struct desktop_output* output;
LIST_FOREACH(output, &desktop->outputs, link) {
n++;
enum image_source_power_state state;
state = image_source_get_power(&output->output->image_source);
switch (state) {
case IMAGE_SOURCE_POWER_ON:
n_on++;
break;
case IMAGE_SOURCE_POWER_OFF:
n_off++;
break;
default:
break;
}
}
if (n_on == n || n_off == n) {
nvnc_log(NVNC_LOG_DEBUG, "Desktop power state changed\n");
observable_notify(&desktop->image_source.observable.power_change,
NULL);
}
}
static void desktop_image_source_output_geometry_change(struct observer* observer,
void *arg)
{
struct desktop_output* event_source = wl_container_of(observer,
event_source, geometry_change_observer);
struct desktop* desktop = event_source->desktop;
observable_notify(&desktop->image_source.observable.geometry_change,
NULL);
}
static struct desktop_output *desktop_output_create(struct output* output,
struct desktop* desktop)
{
struct desktop_output* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->output = output;
self->desktop = desktop;
observer_init(&self->power_change_observer,
&output->image_source.observable.power_change,
desktop_image_source_output_power_change);
observer_init(&self->geometry_change_observer,
&output->image_source.observable.geometry_change,
desktop_image_source_output_geometry_change);
struct desktop_capture* capture = desktop->capture;
if (capture) {
struct screencopy *sc = screencopy_create(&output->image_source,
capture->render_cursor);
sc->userdata = capture;
sc->on_done = desktop_capture_handle_done;
sc->rate_format = desktop_capture_rate_format;
self->sc = sc;
}
struct desktop_capture* cursor_capture = desktop->cursor_capture;
if (cursor_capture) {
struct screencopy *sc = screencopy_create_cursor(
&output->image_source, cursor_capture->seat);
if (sc) {
sc->userdata = cursor_capture;
sc->on_done = desktop_capture_handle_done;
sc->rate_format = desktop_capture_rate_format;
sc->enable_linux_dmabuf = false;
self->cursor_sc = sc;
}
}
return self;
}
static void desktop_output_destroy(struct desktop_output* self)
{
screencopy_destroy(self->sc);
screencopy_destroy(self->cursor_sc);
observer_deinit(&self->power_change_observer);
observer_deinit(&self->geometry_change_observer);
free(self);
}
static void desktop_image_source_output_added(struct observer* observer,
void* arg)
{
struct output* output = arg;
struct desktop* self = wl_container_of(observer, self,
output_added_observer);
struct desktop_output *desktop_output =
desktop_output_create(output, self);
assert(desktop_output);
LIST_INSERT_HEAD(&self->outputs, desktop_output, link);
observable_notify(&self->image_source.observable.geometry_change, NULL);
}
static void desktop_image_source_output_removed(struct observer* observer,
void* arg)
{
struct output* output = arg;
struct desktop* self = wl_container_of(observer, self,
output_removed_observer);
struct desktop_output* desktop_output = NULL;
LIST_FOREACH(desktop_output, &self->outputs, link)
if (desktop_output->output == output)
break;
assert(desktop_output && desktop_output->output == output);
LIST_REMOVE(desktop_output, link);
desktop_output_destroy(desktop_output);
observable_notify(&self->image_source.observable.geometry_change, NULL);
}
static void desktop_image_source_deinit(struct image_source* base)
{
struct desktop* self = desktop_from_image_source(base);
if (self->capture)
self->capture->desktop = NULL;
while (!LIST_EMPTY(&self->outputs)) {
struct desktop_output* output = LIST_FIRST(&self->outputs);
LIST_REMOVE(output, link);
desktop_output_destroy(output);
}
observer_deinit(&self->output_added_observer);
observer_deinit(&self->output_removed_observer);
}
static struct image_source_impl image_source_impl = {
.get_logical_size = desktop_image_source_get_logical_size,
.get_min_scale = desktop_image_source_get_min_scale,
.get_power_state = desktop_image_source_get_power_state,
.describe = desktop_image_source_describe,
.acquire_power_on = desktop_image_source_acquire_power_on,
.release_power_on = desktop_image_source_release_power_on,
.deinit = desktop_image_source_deinit,
};
bool image_source_is_desktop(const struct image_source* source)
{
return source->impl == &image_source_impl;
}
struct desktop* desktop_new(struct wl_list* output_list)
{
struct desktop* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
observer_init(&self->output_added_observer,
&wayland->observable.output_added,
desktop_image_source_output_added);
observer_init(&self->output_removed_observer,
&wayland->observable.output_removed,
desktop_image_source_output_removed);
LIST_INIT(&self->outputs);
struct output* output;
wl_list_for_each(output, output_list, link) {
struct desktop_output *desktop_output =
desktop_output_create(output, self);
assert(desktop_output);
LIST_INSERT_HEAD(&self->outputs, desktop_output, link);
}
image_source_init(&self->image_source, &image_source_impl);
return self;
}
struct screencopy_impl desktop_capture_impl;
static void desktop_capture_handle_done(enum screencopy_result result,
struct wv_buffer* buffer, struct image_source* source,
void* userdata)
{
struct desktop_capture* self = userdata;
// TODO: Maybe extra handling is needed for failures here?
self->base.on_done(result, buffer, source, self->base.userdata);
}
static double desktop_capture_rate_format(const void* userdata,
enum wv_buffer_type type, uint32_t format, uint64_t modifier)
{
const struct desktop_capture* self = userdata;
if (self->base.rate_format)
return self->base.rate_format(self->base.userdata, type, format,
modifier);
return 1;
}
static struct screencopy* desktop_capture_create(struct image_source* source,
bool render_cursor)
{
assert(image_source_is_desktop(source));
struct desktop_capture* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->base.impl = &desktop_capture_impl;
self->base.rate_limit = 30;
self->render_cursor = render_cursor;
struct desktop* desktop = desktop_from_image_source(source);
self->desktop = desktop;
assert(!desktop->capture);
desktop->capture = self;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
struct output* output = desktop_output->output;
struct screencopy *sc = screencopy_create(&output->image_source,
render_cursor);
sc->userdata = self;
sc->on_done = desktop_capture_handle_done;
sc->rate_format = desktop_capture_rate_format;
desktop_output->sc = sc;
}
return (struct screencopy*)self;
}
static struct screencopy* desktop_capture_create_cursor(
struct image_source* source, struct wl_seat* seat)
{
assert(image_source_is_desktop(source));
struct desktop_capture* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->base.impl = &desktop_capture_impl;
self->base.rate_limit = 30;
struct desktop* desktop = desktop_from_image_source(source);
self->desktop = desktop;
assert(!desktop->cursor_capture);
desktop->cursor_capture = self;
// TODO: Do something when seat goes away
self->seat = seat;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
struct output* output = desktop_output->output;
struct screencopy *sc = screencopy_create_cursor(
&output->image_source, seat);
if (!sc)
goto failure;
sc->userdata = self;
sc->on_done = desktop_capture_handle_done;
sc->rate_format = desktop_capture_rate_format;
sc->enable_linux_dmabuf = false;
desktop_output->cursor_sc = sc;
}
return (struct screencopy*)self;
failure:
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
screencopy_destroy(desktop_output->cursor_sc);
desktop_output->cursor_sc = NULL;
}
desktop->cursor_capture = NULL;
free(self);
return NULL;
}
static void desktop_capture_destroy(struct screencopy* base)
{
struct desktop_capture* self = (struct desktop_capture*)base;
struct desktop* desktop = self->desktop;
if (desktop && desktop->capture == self) {
desktop->capture = NULL;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
screencopy_destroy(desktop_output->sc);
desktop_output->sc = NULL;
}
}
if (desktop && desktop->cursor_capture == self) {
desktop->cursor_capture = NULL;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
screencopy_destroy(desktop_output->cursor_sc);
desktop_output->cursor_sc = NULL;
}
}
free(self);
}
static int desktop_capture_start(struct screencopy* base, bool immediate)
{
struct desktop_capture* self = (struct desktop_capture*)base;
struct desktop* desktop = self->desktop;
if (!desktop)
return -1;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
struct screencopy* sc;
if (self == desktop->capture) {
sc = desktop_output->sc;
} else {
assert(self == desktop->cursor_capture);
sc = desktop_output->cursor_sc;
}
sc->rate_limit = base->rate_limit;
sc->enable_linux_dmabuf = base->enable_linux_dmabuf;
int rc = screencopy_start(sc, immediate);
if (rc != 0) {
return -1;
}
}
return 0;
}
static void desktop_capture_stop(struct screencopy* base)
{
struct desktop_capture* self = (struct desktop_capture*)base;
struct desktop* desktop = self->desktop;
if (!desktop)
return;
struct desktop_output* desktop_output;
LIST_FOREACH(desktop_output, &desktop->outputs, link) {
struct screencopy* sc;
if (self == desktop->capture) {
sc = desktop_output->sc;
} else {
assert(self == desktop->cursor_capture);
sc = desktop_output->cursor_sc;
}
screencopy_stop(sc);
}
}
static enum screencopy_capabilitites desktop_capture_get_caps(
const struct screencopy* base)
{
struct desktop_capture* self = (struct desktop_capture*)base;
struct desktop* desktop = self->desktop;
if (!desktop)
return 0;
if (LIST_EMPTY(&desktop->outputs)) {
nvnc_log(NVNC_LOG_ERROR, "Whoops. No outputs. Can't get capabilities");
return 0;
}
struct desktop_output* output = LIST_FIRST(&desktop->outputs);
return screencopy_get_capabilities(output->sc);
}
struct screencopy_impl desktop_capture_impl = {
.create = desktop_capture_create,
.create_cursor = desktop_capture_create_cursor,
.destroy = desktop_capture_destroy,
.start = desktop_capture_start,
.stop = desktop_capture_stop,
.get_capabilities = desktop_capture_get_caps,
};
any1-wayvnc-ae53f07/src/ext-image-copy-capture.c 0000664 0000000 0000000 00000052551 15223766131 0021545 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 - 2025 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 "screencopy-interface.h"
#include "ext-image-copy-capture-v1.h"
#include "ext-image-capture-source-v1.h"
#include "ext-foreign-toplevel-list-v1.h"
#include "buffer.h"
#include "shm.h"
#include "time-util.h"
#include "usdt.h"
#include "pixels.h"
#include "config.h"
#include "image-source.h"
#include "output.h"
#include "toplevel.h"
struct format_entry {
double score;
uint32_t format;
uint64_t modifier;
};
struct format_array {
int len;
int cap;
struct format_entry *entries;
};
struct ext_image_copy_capture {
struct screencopy parent;
struct image_source *image_source;
struct wl_seat* wl_seat;
struct ext_image_copy_capture_session_v1* session;
struct ext_image_copy_capture_frame_v1* frame;
struct ext_image_copy_capture_cursor_session_v1* cursor;
bool render_cursors;
struct wv_buffer_pool* pool;
struct wv_buffer* buffer;
bool have_constraints;
bool should_start;
bool is_cursor_session;
uint32_t frame_count;
uint32_t width, height;
struct format_array wl_shm_formats;
struct format_array dmabuf_formats;
bool have_dmabuf_dev;
dev_t dmabuf_dev;
struct { int x, y; } hotspot;
uint64_t last_time;
struct aml_timer* timer;
};
extern struct wayland* wayland;
struct screencopy_impl ext_image_copy_capture_impl;
static struct ext_image_copy_capture_session_v1_listener session_listener;
static struct ext_image_copy_capture_frame_v1_listener frame_listener;
static struct ext_image_copy_capture_cursor_session_v1_listener cursor_listener;
static bool config_buffers(struct ext_image_copy_capture* self);
static void clear_constraints(struct ext_image_copy_capture* self)
{
if (!self->have_constraints)
return;
self->dmabuf_formats.len = 0;
self->wl_shm_formats.len = 0;
self->have_constraints = false;
}
static void ext_image_copy_capture_deinit_session(struct ext_image_copy_capture* self)
{
clear_constraints(self);
if (self->frame)
ext_image_copy_capture_frame_v1_destroy(self->frame);
self->frame = NULL;
if (self->session)
ext_image_copy_capture_session_v1_destroy(self->session);
self->session = NULL;
if (self->cursor)
ext_image_copy_capture_cursor_session_v1_destroy(self->cursor);
self->cursor = NULL;
wv_buffer_release(self->buffer);
self->buffer = NULL;
}
struct ext_image_capture_source_v1* image_capture_source_from_image_source(
struct image_source* image_source)
{
if (image_source_is_output(image_source)) {
struct wl_output *wl_output =
output_from_image_source(image_source)->wl_output;
return ext_output_image_capture_source_manager_v1_create_source(
wayland->ext_output_image_capture_source_manager_v1,
wl_output);
}
if (image_source_is_toplevel(image_source)) {
struct ext_foreign_toplevel_handle_v1 *handle =
toplevel_from_image_source(image_source)->handle;
return ext_foreign_toplevel_image_capture_source_manager_v1_create_source(
wayland->ext_foreign_toplevel_image_capture_source_manager_v1,
handle);
}
return NULL;
}
static int ext_image_copy_capture_init_session(struct ext_image_copy_capture* self)
{
struct ext_image_capture_source_v1 *source =
image_capture_source_from_image_source(self->image_source);
if (!source)
return -1;
enum ext_image_copy_capture_manager_v1_options options = 0;
if (self->render_cursors)
options |= EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS;
self->session = ext_image_copy_capture_manager_v1_create_session(
wayland->ext_image_copy_capture_manager_v1, source,
options);
ext_image_capture_source_v1_destroy(source);
if (!self->session)
return -1;
ext_image_copy_capture_session_v1_add_listener(self->session,
&session_listener, self);
return 0;
}
static int ext_image_copy_capture_init_cursor_session(struct ext_image_copy_capture* self)
{
struct ext_image_capture_source_v1 *source =
image_capture_source_from_image_source(self->image_source);
if (!source)
return -1;
struct wl_pointer* pointer = wl_seat_get_pointer(self->wl_seat);
self->cursor = ext_image_copy_capture_manager_v1_create_pointer_cursor_session(
wayland->ext_image_copy_capture_manager_v1, source,
pointer);
ext_image_capture_source_v1_destroy(source);
wl_pointer_release(pointer);
if (!self->cursor)
return -1;
ext_image_copy_capture_cursor_session_v1_add_listener(self->cursor,
&cursor_listener, self);
self->session = ext_image_copy_capture_cursor_session_v1_get_capture_session(
self->cursor);
assert(self->session);
ext_image_copy_capture_session_v1_add_listener(self->session,
&session_listener, self);
return 0;
}
static void ext_image_copy_capture_schedule_capture(
struct ext_image_copy_capture* self, uint64_t now)
{
assert(!self->frame);
/* This is done to check if pixel format ratings have changed since
* last time.
*/
config_buffers(self);
self->buffer = wv_buffer_pool_acquire(self->pool);
if (!self->buffer) {
self->parent.on_done(SCREENCOPY_FATAL, NULL, self->image_source,
self->parent.userdata);
return;
}
self->frame = ext_image_copy_capture_session_v1_create_frame(self->session);
assert(self->frame);
ext_image_copy_capture_frame_v1_attach_buffer(self->frame,
self->buffer->wl_buffer);
ext_image_copy_capture_frame_v1_add_listener(self->frame, &frame_listener,
self);
int n_rects = 0;
struct pixman_box16* rects =
pixman_region_rectangles(&self->buffer->buffer_damage, &n_rects);
for (int i = 0; i < n_rects; ++i) {
uint32_t x = rects[i].x1;
uint32_t y = rects[i].y1;
uint32_t width = rects[i].x2 - x;
uint32_t height = rects[i].y2 - y;
ext_image_copy_capture_frame_v1_damage_buffer(self->frame, x, y,
width, height);
}
ext_image_copy_capture_frame_v1_capture(self->frame);
#ifndef NDEBUG
float damage_area = calculate_region_area(&self->buffer->buffer_damage);
float pixel_area = self->buffer->width * self->buffer->height;
nvnc_trace("Committed %sbuffer: %p with %.02f %% damage",
self->is_cursor_session ? "cursor " : "", self->buffer,
100.0 * damage_area / pixel_area);
#endif
self->last_time = now;
}
static void ext_image_copy_capture_schedule_from_timer(struct aml_timer* timer)
{
struct ext_image_copy_capture* self = aml_get_userdata(timer);
assert(self);
uint64_t now = gettime_us();
ext_image_copy_capture_schedule_capture(self, now);
}
static void format_array_append(struct format_array* self,
uint32_t format, uint64_t modifier)
{
if (self->cap <= self->len) {
int next_cap = MAX(256, self->cap * 2);
struct format_entry* formats = realloc(self->entries,
sizeof(*formats) * next_cap);
assert(formats);
self->entries = formats;
self->cap = next_cap;
}
struct format_entry* entry = &self->entries[self->len++];
entry->format = format;
entry->modifier = modifier;
}
static int cmp_format_entries(const void* a, const void* b)
{
const struct format_entry* entry_a = a;
const struct format_entry* entry_b = b;
return entry_a->score > entry_b->score ?
-1 : entry_a->score < entry_b->score;
}
static void format_array_sort_by_score(struct format_array* self)
{
qsort(self->entries, self->len, sizeof(*self->entries),
cmp_format_entries);
}
static void session_handle_format_shm(void *data,
struct ext_image_copy_capture_session_v1* session,
uint32_t format)
{
struct ext_image_copy_capture* self = data;
clear_constraints(self);
format_array_append(&self->wl_shm_formats, fourcc_from_wl_shm(format), 0);
}
static void session_handle_format_drm(void *data,
struct ext_image_copy_capture_session_v1 *session,
uint32_t format, struct wl_array* modifiers)
{
#ifdef ENABLE_SCREENCOPY_DMABUF
struct ext_image_copy_capture* self = data;
clear_constraints(self);
if (modifiers->size % 8 != 0) {
nvnc_log(NVNC_LOG_WARNING, "DMA-BUF modifier array size is not a multiple of 8");
}
int n_modifiers = modifiers->size / 8;
for (int i = 0; i < n_modifiers; ++i) {
uint64_t modifier = 0;
// Not sure if modifier data is aligned. Let's just memcpy it.
const uint64_t* data = modifiers->data;
memcpy(&modifier, &data[i], sizeof(modifier));
format_array_append(&self->dmabuf_formats, format, modifier);
}
#endif
}
static void session_handle_dmabuf_device(void* data,
struct ext_image_copy_capture_session_v1* session,
struct wl_array *device)
{
struct ext_image_copy_capture* self = data;
clear_constraints(self);
if (device->size != sizeof(self->dmabuf_dev)) {
nvnc_log(NVNC_LOG_ERROR, "array size != sizeof(dev_t)");
return;
}
self->have_dmabuf_dev = true;
memcpy(&self->dmabuf_dev, device->data, sizeof(self->dmabuf_dev));
}
static void session_handle_dimensions(void *data,
struct ext_image_copy_capture_session_v1 *session, uint32_t width,
uint32_t height)
{
struct ext_image_copy_capture* self = data;
clear_constraints(self);
nvnc_log(NVNC_LOG_DEBUG, "Buffer dimensions: %"PRIu32"x%"PRIu32,
width, height);
self->width = width;
self->height = height;
}
static double rate_format(const struct ext_image_copy_capture* self,
enum wv_buffer_type type, uint32_t format, uint64_t modifier)
{
#ifdef ENABLE_SCREENCOPY_DMABUF
if (type == WV_BUFFER_DMABUF && !self->parent.enable_linux_dmabuf) {
return 0;
}
#endif
return self->parent.rate_format(self->parent.userdata, type, format,
modifier);
}
static void rate_formats_in_array(const struct ext_image_copy_capture* self,
struct format_array* array, enum wv_buffer_type type)
{
for (int i = 0; i < array->len; ++i) {
struct format_entry* entry = &array->entries[i];
entry->score = rate_format(self, type, entry->format,
entry->modifier);
nvnc_trace("Format:modifier %.4s:%"PRIx64" score: %f",
(const char*)&entry->format, entry->modifier,
entry->score);
}
}
#ifdef ENABLE_SCREENCOPY_DMABUF
static void select_modifiers_for_top_format(struct wv_buffer_config* config,
const struct format_array* formats)
{
// Let's just make it big enough; no point in counting.
config->modifiers = malloc(8 * formats->len);
assert(config->modifiers);
struct format_entry* top_entry = formats->entries;
for (int i = 0; i < formats->len; ++i) {
struct format_entry* entry = &formats->entries[i];
if (entry->format != top_entry->format ||
entry->score != top_entry->score)
break;
nvnc_trace("Adding modifier: %"PRIx64, entry->modifier);
config->modifiers[config->n_modifiers++] = entry->modifier;
}
}
#endif
static bool config_dma_buffers(struct ext_image_copy_capture* self)
{
#ifdef ENABLE_SCREENCOPY_DMABUF
struct wv_buffer_config config = {
.width = self->width,
.height = self->height,
.stride = 0,
.type = WV_BUFFER_DMABUF,
};
rate_formats_in_array(self, &self->dmabuf_formats, WV_BUFFER_DMABUF);
format_array_sort_by_score(&self->dmabuf_formats);
if (self->dmabuf_formats.len == 0 ||
self->dmabuf_formats.entries[0].score == 0)
return false;
config.format = self->dmabuf_formats.entries[0].format;
select_modifiers_for_top_format(&config, &self->dmabuf_formats);
if (self->have_dmabuf_dev)
config.node = self->dmabuf_dev;
nvnc_trace("Choosing DMA-BUF format \"%.4s\" with %d modifiers",
(const char*)&config.format, config.n_modifiers);
bool ok = wv_buffer_pool_reconfig(self->pool, &config);
free(config.modifiers);
return ok;
#else
return false;
#endif
}
static bool config_shm_buffers(struct ext_image_copy_capture* self)
{
rate_formats_in_array(self, &self->wl_shm_formats,
WV_BUFFER_SHM);
format_array_sort_by_score(&self->wl_shm_formats);
if (self->wl_shm_formats.len == 0 ||
self->wl_shm_formats.entries[0].score == 0)
return false;
struct wv_buffer_config config = {
.width = self->width,
.height = self->height,
.type = WV_BUFFER_SHM,
};
config.format = self->wl_shm_formats.entries[0].format;
int bpp = pixel_size_from_fourcc(config.format);
assert(bpp > 0);
config.stride = bpp * config.width;
nvnc_trace("Choosing SHM format \"%.4s\"", (const char*)&config.format);
return wv_buffer_pool_reconfig(self->pool, &config);
}
static bool config_buffers(struct ext_image_copy_capture* self)
{
if (!config_dma_buffers(self) && !config_shm_buffers(self)) {
nvnc_log(NVNC_LOG_ERROR, "No supported buffer formats were found");
return false;
}
return true;
}
static void session_handle_constraints_done(void *data,
struct ext_image_copy_capture_session_v1 *session)
{
struct ext_image_copy_capture* self = data;
if (!config_buffers(self))
return;
if (self->should_start) {
uint64_t now = gettime_us();
ext_image_copy_capture_schedule_capture(self, now);
self->should_start = false;
}
self->have_constraints = true;
nvnc_log(NVNC_LOG_DEBUG, "Init done");
}
static void restart_session(struct ext_image_copy_capture* self)
{
ext_image_copy_capture_deinit_session(self);
if (self->is_cursor_session)
ext_image_copy_capture_init_cursor_session(self);
else
ext_image_copy_capture_init_session(self);
}
static void session_handle_stopped(void* data,
struct ext_image_copy_capture_session_v1* session)
{
nvnc_log(NVNC_LOG_DEBUG, "Session %p stopped", session);
// TODO: Restart session if it is stopped?
}
static void frame_handle_transform(void *data,
struct ext_image_copy_capture_frame_v1 *frame, uint32_t transform)
{
struct ext_image_copy_capture* self = data;
assert(self->buffer);
// TODO: Tell main.c not to override this transform
nvnc_frame_set_transform(self->buffer->nvnc_frame, transform);
}
static void frame_handle_ready(void *data,
struct ext_image_copy_capture_frame_v1 *frame)
{
struct ext_image_copy_capture* self = data;
assert(frame == self->frame);
ext_image_copy_capture_frame_v1_destroy(self->frame);
self->frame = NULL;
#ifndef NDEBUG
float damage_area = calculate_region_area(&self->buffer->frame_damage);
float pixel_area = self->buffer->width * self->buffer->height;
nvnc_trace("Frame ready with damage: %.02f %", 100.0 * damage_area /
pixel_area);
#endif
assert(self->buffer);
wv_buffer_pool_damage_all(self->pool, &self->buffer->frame_damage);
pixman_region_clear(&self->buffer->buffer_damage);
struct wv_buffer* buffer = self->buffer;
self->buffer = NULL;
buffer->x_hotspot = self->hotspot.x;
buffer->y_hotspot = self->hotspot.y;
self->frame_count++;
self->parent.on_done(SCREENCOPY_DONE, buffer, self->image_source,
self->parent.userdata);
}
static void frame_handle_failed(void *data,
struct ext_image_copy_capture_frame_v1 *frame,
enum ext_image_copy_capture_frame_v1_failure_reason reason)
{
struct ext_image_copy_capture* self = data;
assert(frame == self->frame);
ext_image_copy_capture_frame_v1_destroy(self->frame);
self->frame = NULL;
nvnc_log(NVNC_LOG_DEBUG, "Failed!\n");
assert(self->buffer);
wv_buffer_release(self->buffer);
self->buffer = NULL;
if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS) {
screencopy_start(&self->parent, false);
return;
}
self->parent.on_done(SCREENCOPY_FATAL, NULL, self->image_source,
self->parent.userdata);
}
static void frame_handle_damage(void *data,
struct ext_image_copy_capture_frame_v1 *frame,
int32_t x, int32_t y, int32_t width, int32_t height)
{
struct ext_image_copy_capture* self = data;
nvnc_trace("Got frame damage: %dx%d", width, height);
wv_buffer_damage_rect(self->buffer, x, y, width, height);
}
static void frame_handle_presentation_time(void *data,
struct ext_image_copy_capture_frame_v1 *frame,
uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec)
{
struct ext_image_copy_capture* 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);
nvnc_trace("Setting buffer pts: %" PRIu64, pts);
nvnc_frame_set_pts(self->buffer->nvnc_frame, pts);
}
static struct ext_image_copy_capture_session_v1_listener session_listener = {
.shm_format = session_handle_format_shm,
.dmabuf_format = session_handle_format_drm,
.dmabuf_device = session_handle_dmabuf_device,
.buffer_size = session_handle_dimensions,
.done = session_handle_constraints_done,
.stopped = session_handle_stopped,
};
static struct ext_image_copy_capture_frame_v1_listener frame_listener = {
.damage = frame_handle_damage,
.presentation_time = frame_handle_presentation_time,
.transform = frame_handle_transform,
.ready = frame_handle_ready,
.failed = frame_handle_failed,
};
static void cursor_handle_enter(void* data,
struct ext_image_copy_capture_cursor_session_v1* cursor)
{
struct ext_image_copy_capture* self = data;
if (self->parent.cursor_enter)
self->parent.cursor_enter(self->parent.userdata);
}
static void cursor_handle_leave(void* data,
struct ext_image_copy_capture_cursor_session_v1* cursor)
{
struct ext_image_copy_capture* self = data;
if (self->parent.cursor_leave)
self->parent.cursor_leave(self->parent.userdata);
}
static void cursor_handle_position(void* data,
struct ext_image_copy_capture_cursor_session_v1* cursor, int x, int y)
{
// Don't care
}
static void cursor_handle_hotspot(void* data,
struct ext_image_copy_capture_cursor_session_v1* cursor, int x, int y)
{
struct ext_image_copy_capture* self = data;
self->hotspot.x = x;
self->hotspot.y = y;
if (self->parent.cursor_hotspot)
self->parent.cursor_hotspot(x, y, self->parent.userdata);
nvnc_trace("Got hotspot at %d, %d", x, y);
}
static struct ext_image_copy_capture_cursor_session_v1_listener cursor_listener = {
.enter = cursor_handle_enter,
.leave = cursor_handle_leave,
.position = cursor_handle_position,
.hotspot = cursor_handle_hotspot,
};
static int ext_image_copy_capture_start(struct screencopy* ptr, bool immediate)
{
struct ext_image_copy_capture* self = (struct ext_image_copy_capture*)ptr;
if (self->frame) {
return 0;
}
if (immediate || self->frame_count == 0) {
// Flush state:
restart_session(self);
self->should_start = true;
return 0;
}
if (!self->have_constraints) {
self->should_start = true;
return 0;
}
uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6;
int32_t time_left = (1.0 / ptr->rate_limit - dt) * 1.0e6;
if (time_left > 0) {
nvnc_trace("Scheduling %scapture after %"PRIu64" µs",
self->is_cursor_session ? "cursor " : "",
time_left);
aml_set_duration(self->timer, time_left);
return aml_start(aml_get_default(), self->timer);
}
aml_stop(aml_get_default(), self->timer);
ext_image_copy_capture_schedule_capture(self, now);
return 0;
}
static void ext_image_copy_capture_stop(struct screencopy* base)
{
struct ext_image_copy_capture* self = (struct ext_image_copy_capture*)base;
aml_stop(aml_get_default(), self->timer);
ext_image_copy_capture_deinit_session(self);
self->frame_count = 0;
}
static struct screencopy* ext_image_copy_capture_create(
struct image_source* source, bool render_cursor)
{
struct ext_image_copy_capture* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->parent.impl = &ext_image_copy_capture_impl;
self->parent.rate_limit = 30;
self->image_source = source;
self->render_cursors = render_cursor;
self->timer = aml_timer_new(0,
ext_image_copy_capture_schedule_from_timer, self, NULL);
assert(self->timer);
self->pool = wv_buffer_pool_create(NULL);
if (!self->pool)
goto failure;
return (struct screencopy*)self;
failure:
free(self);
return NULL;
}
static struct screencopy* ext_image_copy_capture_create_cursor(
struct image_source* source, struct wl_seat* seat)
{
struct ext_image_copy_capture* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->parent.impl = &ext_image_copy_capture_impl;
self->parent.rate_limit = 30;
self->image_source = source;
self->wl_seat = seat;
self->timer = aml_timer_new(0,
ext_image_copy_capture_schedule_from_timer, self, NULL);
self->is_cursor_session = true;
assert(self->timer);
self->pool = wv_buffer_pool_create(NULL);
if (!self->pool)
goto failure;
return (struct screencopy*)self;
failure:
free(self);
return NULL;
}
void ext_image_copy_capture_destroy(struct screencopy* ptr)
{
struct ext_image_copy_capture* self = (struct ext_image_copy_capture*)ptr;
aml_stop(aml_get_default(), self->timer);
aml_unref(self->timer);
ext_image_copy_capture_deinit_session(self);
wv_buffer_pool_destroy(self->pool);
free(self->dmabuf_formats.entries);
free(self->wl_shm_formats.entries);
free(self);
}
static enum screencopy_capabilitites ext_image_copy_capture_get_caps(
const struct screencopy* self)
{
return SCREENCOPY_CAP_TRANSFORM | SCREENCOPY_CAP_CURSOR;
}
struct screencopy_impl ext_image_copy_capture_impl = {
.create = ext_image_copy_capture_create,
.create_cursor = ext_image_copy_capture_create_cursor,
.destroy = ext_image_copy_capture_destroy,
.start = ext_image_copy_capture_start,
.stop = ext_image_copy_capture_stop,
.get_capabilities = ext_image_copy_capture_get_caps,
};
any1-wayvnc-ae53f07/src/image-source.c 0000664 0000000 0000000 00000010203 15223766131 0017620 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2026 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 "image-source.h"
#include
#include
#include
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
const char* image_source_power_state_name(enum image_source_power_state state)
{
switch(state) {
case IMAGE_SOURCE_POWER_ON:
return "ON";
case IMAGE_SOURCE_POWER_OFF:
return "OFF";
case IMAGE_SOURCE_POWER_UNKNOWN:
return "UNKNOWN";
}
abort();
return NULL;
}
void image_source_init(struct image_source* self, struct image_source_impl* impl)
{
self->impl = impl;
observable_init(&self->observable.power_change);
observable_init(&self->observable.geometry_change);
observable_init(&self->observable.destroyed);
}
void image_source_deinit(struct image_source* self)
{
assert(self->impl);
observable_notify(&self->observable.destroyed, NULL);
observable_deinit(&self->observable.destroyed);
observable_deinit(&self->observable.power_change);
observable_deinit(&self->observable.geometry_change);
if (self->impl->deinit)
self->impl->deinit(self);
}
void image_source_destroy(struct image_source* self)
{
if (!self)
return;
image_source_deinit(self);
free(self);
}
bool image_source_get_logical_size(const struct image_source* self,
int* width, int *height)
{
assert(self->impl);
if (self->impl->get_logical_size) {
self->impl->get_logical_size(self, width, height);
return true;
}
return false;
}
enum wl_output_transform image_source_get_transform(
const struct image_source* self)
{
assert(self->impl);
if (self->impl->get_transform)
return self->impl->get_transform(self);
return WL_OUTPUT_TRANSFORM_NORMAL;
}
enum image_source_power_state image_source_get_power(
const struct image_source* self)
{
assert(self->impl);
if (self->impl->get_power_state)
return self->impl->get_power_state(self);
return IMAGE_SOURCE_POWER_ON;
}
const char* image_source_describe(const struct image_source* self,
char* dst, size_t maxlen)
{
assert(self->impl && self->impl->describe);
self->impl->describe(self, dst, maxlen);
return dst;
}
int image_source_acquire_power_on(struct image_source* self)
{
assert(self->impl);
if (self->impl->acquire_power_on)
return self->impl->acquire_power_on(self);
return 1;
}
void image_source_release_power_on(struct image_source* self)
{
assert(self->impl);
if (self->impl->release_power_on)
self->impl->release_power_on(self);
}
bool image_source_get_buffer_size(const struct image_source* self,
int* width, int* height)
{
assert(self->impl);
if (self->impl->get_buffer_size) {
self->impl->get_buffer_size(self, width, height);
return true;
}
return false;
}
bool image_source_get_scale(const struct image_source* self,
double* h_scale, double* v_scale)
{
int logical_w, logical_h;
int buffer_w, buffer_h;
if (!image_source_get_logical_size(self,
&logical_w, &logical_h))
return false;
if (!image_source_get_buffer_size(self,
&buffer_w, &buffer_h))
return false;
if (buffer_w == 0 || buffer_h == 0)
return false;
if (h_scale)
*h_scale = (double)logical_w / buffer_w;
if (v_scale)
*v_scale = (double)logical_h / buffer_h;
return true;
}
double image_source_get_min_scale(const struct image_source* self)
{
assert(self->impl);
if (self->impl->get_min_scale)
return self->impl->get_min_scale(self);
double h_scale, v_scale;
if (!image_source_get_scale(self, &h_scale, &v_scale))
return 1.0;
return fmin(h_scale, v_scale);
}
any1-wayvnc-ae53f07/src/intset.c 0000664 0000000 0000000 00000004202 15223766131 0016550 0 ustar 00root root 0000000 0000000 /*
* 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;
}
any1-wayvnc-ae53f07/src/json-ipc.c 0000664 0000000 0000000 00000012660 15223766131 0016773 0 ustar 00root root 0000000 0000000 /*
* 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 "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[256];
int len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
return json_stringn(buffer, MIN(sizeof(buffer) - 1, len));
}
any1-wayvnc-ae53f07/src/keyboard.c 0000664 0000000 0000000 00000033444 15223766131 0017054 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2025 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;
int group;
};
struct key_iter_context {
struct keyboard* kb;
int group;
};
struct kb_mods {
xkb_mod_mask_t depressed, latched, locked;
};
static void save_mods(struct keyboard* self, struct kb_mods* mods);
static void append_entry(struct keyboard* self, xkb_keysym_t symbol,
xkb_keycode_t code, int level, int group)
{
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;
entry->group = group;
}
static void key_iter(struct xkb_keymap* map, xkb_keycode_t code, void* userdata)
{
struct key_iter_context* ctx = userdata;
struct keyboard* self = ctx->kb;
int group = ctx->group;
size_t n_levels = xkb_keymap_num_levels_for_key(map, code, group);
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,
group, level, &symbols);
for (size_t sym_idx = 0; sym_idx < n_syms; sym_idx++)
append_entry(self, symbols[sym_idx], code, level, group);
}
}
static int compare_symbols(const void* a, const void* b)
{
const struct table_entry* x = a;
const struct table_entry* y = b;
if (x->group != y->group)
return x->group < y->group ? -1 : 1;
if (x->symbol != y->symbol)
return x->symbol < y->symbol ? -1 : 1;
return x->code < y->code ? -1 : x->code > y->code;
}
static int compare_symbols2(const void* a, const void* b)
{
const struct table_entry* x = a;
const struct table_entry* y = b;
if (x->group != y->group)
return x->group < y->group ? -1 : 1;
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;
int n_groups = xkb_keymap_num_layouts(self->keymap);
for (int group = 0; group < n_groups; ++group) {
struct key_iter_context ctx = {
.kb = self,
.group = group,
};
xkb_keymap_key_for_each(self->keymap, key_iter, &ctx);
}
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 *group_name MAYBE_UNUSED =
xkb_keymap_layout_get_name(self->keymap, entry->group);
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, "group=%s symbol=%s level=%d code=%s %s",
group_name, 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;
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)
{
zwp_virtual_keyboard_v1_destroy(self->virtual_keyboard);
free(self->lookup_table);
xkb_state_unref(self->state);
xkb_keymap_unref(self->keymap);
intset_destroy(&self->key_state);
xkb_context_unref(self->context);
}
static xkb_layout_index_t get_current_layout_group(const struct keyboard* self)
{
int n_groups = xkb_keymap_num_layouts(self->keymap);
assert(n_groups > 0);
for (int i = 0; i < n_groups; ++i) {
if (xkb_state_layout_index_is_active(self->state, i,
XKB_STATE_LAYOUT_EFFECTIVE))
return i;
}
return -1;
}
static struct table_entry* keyboard_find_symbol_in_group(const struct keyboard* self,
xkb_keysym_t symbol, int group)
{
struct table_entry cmp = {
.group = group,
.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 struct table_entry* keyboard_find_symbol(struct keyboard* self,
xkb_keysym_t symbol)
{
int n_groups = xkb_keymap_num_layouts(self->keymap);
struct table_entry* entry = NULL;
int current_group = get_current_layout_group(self);
int group = -1;
for (int g = 0; g < n_groups; ++g) {
group = (current_group + g) % n_groups;
entry = keyboard_find_symbol_in_group(self, symbol, group);
if (entry)
break;
}
assert(group >= 0);
if (entry && group != current_group) {
nvnc_log(NVNC_LOG_DEBUG, "Keyboard symbol was not found in active layout group; changing to a different one where it was found...");
struct kb_mods mods;
save_mods(self, &mods);
xkb_state_update_mask(self->state, mods.depressed, mods.latched,
mods.locked, 0, 0, group);
}
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 = get_current_layout_group(self);
self->last_sent_group = group;
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;
int current_group = get_current_layout_group(self);
if (!(comp & compmask) && self->last_sent_group == current_group)
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,
entry->group);
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)
{
int current_group = get_current_layout_group(self);
xkb_state_update_mask(self->state, mods->depressed, mods->latched,
mods->locked, 0, 0, current_group);
}
static void send_key_with_level(struct keyboard* self, xkb_keycode_t code,
bool is_pressed, int level)
{
int current_group = get_current_layout_group(self);
struct kb_mods save;
save_mods(self, &save);
xkb_mod_mask_t mods = 0;
xkb_keymap_key_get_mods_for_level(self->keymap, code,
current_group, level, &mods, 1);
xkb_state_update_mask(self->state, mods, 0, 0, 0, 0,
current_group);
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);
}
}
enum nvnc_keyboard_led_state keyboard_get_led_state(
const struct keyboard* self)
{
enum nvnc_keyboard_led_state led_state = 0;
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_SCROLL))
led_state |= NVNC_KEYBOARD_LED_SCROLL_LOCK;
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_NUM))
led_state |= NVNC_KEYBOARD_LED_NUM_LOCK;
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_CAPS))
led_state |= NVNC_KEYBOARD_LED_CAPS_LOCK;
return led_state;
}
any1-wayvnc-ae53f07/src/main.c 0000664 0000000 0000000 00000227774 15223766131 0016213 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2026 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
#include "ext-transient-seat-v1.h"
#include "virtual-keyboard-unstable-v1.h"
#include "screencopy-interface.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"
#include "pixels.h"
#include "buffer.h"
#include "time-util.h"
#include "image-source.h"
#include "toplevel.h"
#include "observer.h"
#include "desktop.h"
#include "wayland.h"
#ifdef ENABLE_PAM
#include "pam_auth.h"
#endif
#define DEFAULT_ADDRESS "127.0.0.1"
#define DEFAULT_PORT 5900
#define XSTR(x) STR(x)
#define STR(x) #x
struct wayvnc_client;
enum socket_type {
SOCKET_TYPE_TCP = 0,
SOCKET_TYPE_UNIX,
SOCKET_TYPE_FROM_FD,
};
struct wayvnc_display {
LIST_ENTRY(wayvnc_display) link;
struct wayvnc* wayvnc;
struct nvnc_display* nvnc_display;
struct image_source* image_source;
struct wv_buffer* next_frame;
struct observer geometry_change_observer;
struct observer destruction_observer;
struct {
bool is_set;
int width, height;
enum wl_output_transform transform;
} last_frame_info;
};
LIST_HEAD(wayvnc_display_list, wayvnc_display);
struct wayvnc {
bool do_exit;
bool exit_on_disconnect;
struct cfg cfg;
struct image_source* image_source;
const char* selected_seat_name;
enum image_source_type image_source_type;
char image_source_name[128];
struct screencopy* screencopy;
struct aml_signal* signal_handler;
struct nvnc* nvnc;
struct wayvnc_display_list wayvnc_displays;
const char* desktop_name;
const char* kb_layout;
const char* kb_variant;
uint32_t damage_area_sum;
uint32_t n_frames_captured;
uint32_t n_frames_sent;
bool disable_input;
bool use_transient_seat;
bool use_toplevel;
int nr_clients;
struct aml_ticker* performance_ticker;
struct aml_timer* capture_retry_timer;
struct ctl* ctl;
bool start_detached;
bool overlay_cursor;
int max_rate;
bool enable_gpu_features;
bool enable_resizing;
struct wayvnc_client* master_layout_client;
struct wayvnc_client* cursor_master;
struct screencopy* cursor_sc;
uint64_t last_send_time;
struct aml_timer* rate_limiter;
// wayland observers
struct observer output_added_observer;
struct observer output_removed_observer;
struct observer seat_removed_observer;
struct observer wayland_destroy_observer;
// image source observers
struct observer power_change_observer;
struct observer destruction_observer;
struct aml_idle* deferred_detach;
};
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(enum screencopy_result result, struct wv_buffer* buffer,
struct image_source* source, void* userdata);
static void on_cursor_capture_done(enum screencopy_result result,
struct wv_buffer* buffer, struct image_source* source,
void* userdata);
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,
enum image_source_type, const char* image_source_name);
static void wayland_detach(struct wayvnc* self);
static bool configure_cursor_sc(struct wayvnc* self,
struct wayvnc_client* client);
static bool wayvnc_desktop_display_add(struct wayvnc* self,
struct image_source* image_source);
struct wayland* wayland = NULL;
extern struct screencopy_impl wlr_screencopy_impl, ext_image_copy_capture_impl;
static void cancel_deferred_detach(struct wayvnc* self)
{
if (!self->deferred_detach)
return;
aml_stop(aml_get_default(), self->deferred_detach);
aml_unref(self->deferred_detach);
self->deferred_detach = NULL;
}
static void handle_wayland_detach(struct aml_idle* idle)
{
struct wayvnc* self = aml_get_userdata(idle);
assert(self->deferred_detach == idle);
cancel_deferred_detach(self);
wayland_detach(self);
}
static void schedule_wayland_detach(struct wayvnc* self)
{
if (self->deferred_detach)
return;
struct aml_idle* idle = aml_idle_new(handle_wayland_detach, self, NULL);
aml_start(aml_get_default(), idle);
self->deferred_detach = idle;
}
static void on_output_added(struct observer* observer, void* data)
{
struct wayvnc* self = wl_container_of(observer, self,
output_added_observer);
struct output* output = data;
ctl_server_event_output_added(self->ctl, output_get_name(output));
cancel_deferred_detach(self);
if (self->image_source_type == IMAGE_SOURCE_TYPE_OUTPUT ||
self->image_source_type == IMAGE_SOURCE_TYPE_UNSPEC) {
if (!self->image_source)
switch_to_output(self, output);
} else if (self->image_source_type == IMAGE_SOURCE_TYPE_DESKTOP) {
wayvnc_desktop_display_add(self, &output->image_source);
}
}
static void on_output_removed(struct observer* observer, void* data)
{
struct wayvnc* self = wl_container_of(observer, self,
output_removed_observer);
struct output* out = data;
if (self->image_source && image_source_is_output(self->image_source) &&
out == output_from_image_source(self->image_source)) {
nvnc_log(NVNC_LOG_WARNING, "Selected output %s went away",
output_get_name(out));
switch_to_prev_output(self);
} else
nvnc_log(NVNC_LOG_INFO, "Output %s went away",
output_get_name(out));
ctl_server_event_output_removed(self->ctl, output_get_name(out));
if (self->image_source && image_source_is_output(self->image_source) &&
out == output_from_image_source(self->image_source)) {
if (self->start_detached) {
nvnc_log(NVNC_LOG_WARNING, "No fallback outputs left. Detaching...");
self->image_source = NULL;
schedule_wayland_detach(self);
} else {
nvnc_log(NVNC_LOG_ERROR, "No fallback outputs left. Exiting...");
wayvnc_exit(self);
}
}
}
static void on_seat_removed(struct observer* observer, void* data)
{
struct wayvnc* self = wl_container_of(observer, self,
seat_removed_observer);
struct seat* seat = data;
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_client_get_userdata(nvnc_client);
assert(client);
if (client->seat == seat) {
nvnc_client_close(nvnc_client);
}
}
}
static void wayvnc_display_detach(struct wayvnc_display* display)
{
nvnc_trace("removing destruction observer");
observer_deinit(&display->destruction_observer);
nvnc_trace("removing geometry observer");
observer_deinit(&display->geometry_change_observer);
if (display->next_frame) {
wv_buffer_release(display->next_frame);
display->next_frame = NULL;
}
display->image_source = NULL;
}
static void wayvnc_display_list_detach(struct wayvnc_display_list* list)
{
struct wayvnc_display* display;
LIST_FOREACH(display, list, link)
wayvnc_display_detach(display);
}
static void wayland_detach(struct wayvnc* self)
{
wayland_destroy(wayland);
wayland = NULL;
}
static void on_wayland_destroyed(struct observer* observer, void* data)
{
struct wayvnc* self = wl_container_of(observer, self,
wayland_destroy_observer);
observer_deinit(&self->output_added_observer);
observer_deinit(&self->output_removed_observer);
observer_deinit(&self->seat_removed_observer);
observer_deinit(&self->wayland_destroy_observer);
observer_deinit(&self->power_change_observer);
observer_deinit(&self->destruction_observer);
wayvnc_display_list_detach(&self->wayvnc_displays);
// 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_client_get_userdata(nvnc_client);
client_detach_wayland(client);
}
}
screencopy_stop(self->screencopy);
screencopy_destroy(self->screencopy);
self->screencopy = NULL;
screencopy_stop(self->cursor_sc);
screencopy_destroy(self->cursor_sc);
self->cursor_sc = NULL;
if (self->image_source && image_source_is_desktop(self->image_source))
image_source_destroy(self->image_source);
self->image_source = 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_stop(aml_get_default(), self->capture_retry_timer);
aml_unref(self->capture_retry_timer);
}
self->capture_retry_timer = NULL;
if (self->ctl)
ctl_server_event_detached(self->ctl);
if (!self->start_detached)
wayvnc_exit(self);
wayland = NULL;
}
static int init_wayland(struct wayvnc* self, const char* display)
{
assert(!wayland);
enum wayland_flags flags = 0;
if (!self->disable_input)
flags |= WAYLAND_FLAG_ENABLE_INPUT;
if (self->use_transient_seat)
flags |= WAYLAND_FLAG_ENABLE_TRANSIENT_SEAT;
if (self->use_toplevel)
flags |= WAYLAND_FLAG_ENABLE_TOPLEVEL_CAPTURE;
wayland = wayland_connect(display, flags);
if (!wayland)
return -1;
observer_init(&self->output_added_observer,
&wayland->observable.output_added, on_output_added);
observer_init(&self->output_removed_observer,
&wayland->observable.output_removed, on_output_removed);
observer_init(&self->seat_removed_observer,
&wayland->observable.seat_removed, on_seat_removed);
observer_init(&self->wayland_destroy_observer,
&wayland->observable.destroyed, on_wayland_destroyed);
if (!wayland->zwlr_virtual_pointer_manager_v1 && !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 (!wayland->zwp_virtual_keyboard_manager_v1 && !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 (!wayland->zwlr_screencopy_manager_v1 &&
!wayland->ext_image_copy_capture_manager_v1) {
nvnc_log(NVNC_LOG_ERROR, "Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page.");
goto failure;
}
if (!wayland->ext_transient_seat_manager_v1 && self->use_transient_seat) {
nvnc_log(NVNC_LOG_ERROR, "Transient seat protocol not supported by compositor");
goto failure;
}
bool have_toplevel_capture =
wayland->ext_foreign_toplevel_list_v1 &&
wayland->ext_foreign_toplevel_image_capture_source_manager_v1;
if (self->use_toplevel && !have_toplevel_capture) {
nvnc_log(NVNC_LOG_ERROR, "Toplevel capture is not supported by the compositor");
goto failure;
}
return 0;
failure:
wayland_destroy(wayland);
wayland = NULL;
return -1;
}
void wayvnc_exit(struct wayvnc* self)
{
self->do_exit = true;
}
void on_signal(struct aml_signal* 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");
if (!wayland)
return cmd_failed("Not attached!");
if (!image_source_is_output(self->image_source))
return cmd_failed("Not capturing an output!");
struct output* next = output_cycle(&wayland->outputs,
output_from_image_source(self->image_source), 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 (!wayland)
return cmd_failed("Not attached!");
if (!image_source_is_output(self->image_source))
return cmd_failed("Not capturing an output!");
if (!output_name || output_name[0] == '\0')
return cmd_failed("Output name is required");
struct output* output = output_find_by_name(&wayland->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_storage);
nvnc_client_get_address(client->nvnc_client,
(struct sockaddr*)&info->address_storage, &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_client_get_userdata(vnc_client);
compose_client_info(client, info);
}
static struct cmd_response* on_set_desktop_name(struct ctl* ctl,
const char* name)
{
nvnc_log(NVNC_LOG_INFO, "ctl command: Setting desktop name to \"%s\"", name);
struct wayvnc* self = ctl_server_userdata(ctl);
nvnc_set_name(self->nvnc, name);
return cmd_ok();
}
static int get_output_list(struct ctl* ctl,
struct ctl_server_output** outputs)
{
struct wayvnc* self = ctl_server_userdata(ctl);
if (!wayland) {
*outputs = NULL;
return 0;
}
int n = wl_list_length(&wayland->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, &wayland->outputs, link) {
strlcpy(item->name, output_get_name(output), sizeof(item->name));
strlcpy(item->description, output_get_description(output),
sizeof(item->description));
int width = 0, height = 0;
image_source_get_logical_size(&output->image_source, &width,
&height);
item->width = width;
item->height = height;
if (image_source_is_output(self->image_source)) {
struct output* source_output =
output_from_image_source(self->image_source);
item->captured = (output->id == source_output->id);
}
strlcpy(item->power, image_source_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_client_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 struct wayvnc_display *wayvnc_display_find_by_source(struct wayvnc* self,
struct image_source *source)
{
struct wayvnc_display* display;
LIST_FOREACH(display, &self->wayvnc_displays, link)
if (display->image_source == source)
return display;
return NULL;
}
static void on_pointer_event(struct nvnc_client* client, double x, double y,
enum nvnc_button_mask button_mask)
{
struct wayvnc_client* wv_client = nvnc_client_get_userdata(client);
struct wayvnc* wayvnc = wv_client->server;
if (!wv_client->pointer.pointer) {
return;
}
if (x < 0.0 || x > 1.0 || y < 0.0 || y > 1.0)
nvnc_log(NVNC_LOG_WARNING, "Got out-of-bounds cursor coordinates: %f:%f", x, y);
x = fmin(fmax(x, 0.0), 1.0);
y = fmin(fmax(y, 0.0), 1.0);
enum wl_output_transform transform;
if (image_source_get_logical_size(wayvnc->image_source, NULL, NULL)) {
transform = image_source_get_transform(wayvnc->image_source);
} else {
struct wayvnc_display* display = wayvnc_display_find_by_source(
wayvnc, wayvnc->image_source);
assert(display);
if (display->last_frame_info.is_set) {
transform = display->last_frame_info.transform;
} else {
transform = WL_OUTPUT_TRANSFORM_NORMAL;
}
}
struct { double x, y; } xf = { x, y };
wv_output_transform_canvas_point(transform, &xf.x, &xf.y);
pointer_set(&wv_client->pointer, xf.x, xf.y, button_mask);
}
static void on_key_event(struct nvnc_client* client, uint32_t symbol,
bool is_pressed)
{
struct wayvnc_client* wv_client = nvnc_client_get_userdata(client);
if (!wv_client->keyboard.virtual_keyboard) {
return;
}
keyboard_feed(&wv_client->keyboard, symbol, is_pressed);
nvnc_client_set_led_state(wv_client->nvnc_client,
keyboard_get_led_state(&wv_client->keyboard));
}
static void on_key_code_event(struct nvnc_client* client, uint32_t code,
bool is_pressed)
{
struct wayvnc_client* wv_client = nvnc_client_get_userdata(client);
if (!wv_client->keyboard.virtual_keyboard) {
return;
}
keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed);
nvnc_client_set_led_state(wv_client->nvnc_client,
keyboard_get_led_state(&wv_client->keyboard));
}
static void on_client_cut_text(struct nvnc_client* nvnc_client,
const char* text, uint32_t len)
{
struct wayvnc_client* client = nvnc_client_get_userdata(nvnc_client);
if (client->data_control.wlr_manager ||
client->data_control.ext_manager) {
data_control_to_clipboard(&client->data_control, text, len);
}
}
static int count_displays(const struct wayvnc* self)
{
int count = 0;
struct wayvnc_display* display;
LIST_FOREACH(display, &self->wayvnc_displays, link)
count++;
return count;
}
// TODO: Add some more sanity checks to this function
static bool handle_client_resize_desktop(struct wayvnc* self,
struct wayvnc_client* client,
const struct nvnc_desktop_layout* layout)
{
int n_displays = count_displays(self);
int n_layout_displays = nvnc_desktop_layout_get_display_count(layout);
if (n_layout_displays != n_displays) {
nvnc_log(NVNC_LOG_DEBUG, "Client referenced %d displays in layout, but we have %d displays in the layout",
n_layout_displays, n_displays);
return false;
}
struct zwlr_output_configuration_v1* config =
wlr_output_manager_start_config();
for (int i = 0; i < n_displays; ++i) {
uint16_t width, height, x, y;
width = nvnc_desktop_layout_get_display_width(layout, i);
height = nvnc_desktop_layout_get_display_height(layout, i);
x = nvnc_desktop_layout_get_display_x_pos(layout, i);
y = nvnc_desktop_layout_get_display_y_pos(layout, i);
struct nvnc_display* nvnc_display;
nvnc_display = nvnc_desktop_layout_get_display(layout, i);
if (!nvnc_display)
goto abort;
struct wayvnc_display* display = nvnc_display_get_userdata(nvnc_display);
nvnc_assert(display && display->nvnc_display == nvnc_display,
"Bad userdata for nvnc_display");
struct output* output;
output = output_from_image_source(display->image_source);
if (!wlr_output_manager_configure_output(config, output, width,
height, x, y)) {
nvnc_log(NVNC_LOG_ERROR, "Failed to configure output: %s",
output_get_name(output));
goto abort;
}
}
if (!wlr_output_manager_commit_config(config)) {
nvnc_log(NVNC_LOG_ERROR, "Failed to configure desktop layout");
return false;
}
self->master_layout_client = client;
return true;
abort:
wlr_output_manager_abort_config(config);
return false;
}
static bool handle_client_resize_output(struct wayvnc* self,
struct wayvnc_client* client,
const struct nvnc_desktop_layout* layout)
{
int n_displays = nvnc_desktop_layout_get_display_count(layout);
if (n_displays != 1) {
nvnc_log(NVNC_LOG_ERROR, "Client wanted to change layout for %d displays, but we only have 1",
n_displays);
return false;
}
struct nvnc_display* nvnc_display = nvnc_desktop_layout_get_display(
layout, 0);
if (!nvnc_display) {
nvnc_log(NVNC_LOG_ERROR, "Client requested to change the layout of an unknown display. The request is rejected.");
return false;
}
struct wayvnc_display* display = nvnc_display_get_userdata(nvnc_display);
nvnc_assert(display && display->nvnc_display == nvnc_display,
"Bad userdata for nvnc_display");
uint16_t width = nvnc_desktop_layout_get_display_width(layout, 0);
uint16_t height = nvnc_desktop_layout_get_display_height(layout, 0);
self->master_layout_client = client;
struct output* output;
output = output_from_image_source(client->server->image_source);
nvnc_log(NVNC_LOG_DEBUG,
"Client resolution changed: %ux%u, capturing output %s which is headless: %s",
width, height, output_get_name(output),
output_is_headless(output) ? "yes" : "no");
return wlr_output_manager_resize_output(output, width, height);
}
// TODO: Turn this into an image source method?
static bool on_client_resize(struct nvnc_client* nvnc_client,
const struct nvnc_desktop_layout* layout)
{
struct wayvnc_client* client = nvnc_client_get_userdata(nvnc_client);
struct wayvnc* self = client->server;
if (!self->image_source)
return false;
if (self->master_layout_client && self->master_layout_client != client)
return false;
if (image_source_is_desktop(self->image_source))
return handle_client_resize_desktop(self, client, layout);
if (image_source_is_output(self->image_source))
return handle_client_resize_output(self, client, layout);
return false;
}
static bool authenticate_user(struct wayvnc* self,
const struct nvnc_auth_creds* credentials)
{
const char* username = nvnc_auth_creds_get_username(credentials);
const char* password = nvnc_auth_creds_get_password(credentials);
const char* cfg_username = self->cfg.username ? self->cfg.username : "";
const char* cfg_password = self->cfg.password ? self->cfg.password : "";
#ifdef ENABLE_PAM
if (self->cfg.enable_pam) {
if (!username || !password)
return false;
return pam_auth(username, password);
}
#endif
if (!password)
return nvnc_auth_creds_verify(credentials, cfg_password);
if (!username)
return false;
if (strcmp(username, cfg_username) != 0)
return false;
if (strcmp(password, cfg_password) != 0)
return false;
return true;
}
void on_auth(struct nvnc_auth_creds *credentials, void* ud)
{
struct wayvnc* self = ud;
if (authenticate_user(self, credentials))
nvnc_auth_creds_accept(credentials);
else
nvnc_auth_creds_reject(credentials,
"Invalid username or password");
}
static struct nvnc_frame* create_placeholder_buffer(uint16_t width, uint16_t height)
{
uint16_t stride = width;
struct nvnc_frame* fb = nvnc_frame_new(width, height, DRM_FORMAT_XRGB8888,
stride);
if (!fb)
return NULL;
size_t size = nvnc_frame_get_pixel_size(fb) * height * stride;
memset(nvnc_frame_get_addr(fb), 0x60, size);
return fb;
}
static int wayvnc_display_blank(struct wayvnc* self,
struct wayvnc_display* display)
{
int width = 1280;
int height = 720;
if (display->image_source) {
if (image_source_get_logical_size(display->image_source,
&width, &height)) {
} else if (display->last_frame_info.is_set) {
width = display->last_frame_info.width;
height = display->last_frame_info.height;
}
}
struct nvnc_frame* placeholder_fb = create_placeholder_buffer(width, height);
if (!placeholder_fb) {
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate a placeholder buffer");
return -1;
}
nvnc_display_feed_frame(display->nvnc_display, placeholder_fb);
nvnc_frame_unref(placeholder_fb);
nvnc_set_cursor(self->nvnc, NULL, 0, 0, false);
return 0;
}
static int blank_screen(struct wayvnc* self)
{
struct wayvnc_display* display;
LIST_FOREACH(display, &self->wayvnc_displays, link)
wayvnc_display_blank(self, display);
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 bool is_prefix(const char* prefix, const char* str)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}
static int count_colons_in_string(const char* str)
{
int n = 0;
for (int i = 0; str[i]; ++i)
if (str[i] == ':')
++n;
return n;
}
static char* parse_address_prefix(char* addr, enum socket_type* socket_type,
enum nvnc_stream_type* stream_type)
{
if (is_prefix("tcp:", addr)) {
*socket_type = SOCKET_TYPE_TCP;
*stream_type = NVNC_STREAM_NORMAL;
addr += 4;
} else if (is_prefix("unix:", addr)) {
*socket_type = SOCKET_TYPE_UNIX;
*stream_type = NVNC_STREAM_NORMAL;
addr += 5;
} else if (is_prefix("fd:", addr)) {
*socket_type = SOCKET_TYPE_FROM_FD;
*stream_type = NVNC_STREAM_NORMAL;
addr += 3;
} else if (is_prefix("ws:", addr)) {
*socket_type = SOCKET_TYPE_TCP;
*stream_type = NVNC_STREAM_WEBSOCKET;
addr += 3;
} else if (is_prefix("ws-tcp:", addr)) {
*socket_type = SOCKET_TYPE_TCP;
*stream_type = NVNC_STREAM_WEBSOCKET;
addr += 7;
} else if (is_prefix("ws-unix:", addr)) {
*socket_type = SOCKET_TYPE_UNIX;
*stream_type = NVNC_STREAM_WEBSOCKET;
addr += 8;
} else if (is_prefix("ws-fd:", addr)) {
*socket_type = SOCKET_TYPE_FROM_FD;
*stream_type = NVNC_STREAM_WEBSOCKET;
addr += 6;
}
return addr;
}
static char* parse_address_port(char* addr, uint16_t* port)
{
// IPv6 addresses with port need to look like: [::]:5900
if (addr[0] == '[') {
char* end = strchr(addr, ']');
if (!end)
return addr;
*end++ = '\0';
if (*end == ':')
*port = atoi(end + 1);
return addr + 1;
}
if (count_colons_in_string(addr) > 1) {
// This is most likely IPv6, so let's leave it alone
return addr;
}
char* p = NULL;
p = strrchr(addr, ':');
if (!p)
return addr;
*p++ = '\0';
*port = atoi(p);
return addr;
}
static int add_listening_address(struct wayvnc* self, const char* address,
uint16_t port, enum socket_type socket_type,
enum nvnc_stream_type stream_type)
{
char buffer[256];
char* addr = buffer;
strlcpy(buffer, address, sizeof(buffer));
addr = parse_address_prefix(addr, &socket_type, &stream_type);
if (socket_type == SOCKET_TYPE_TCP) {
uint16_t parsed_port = 0;
addr = parse_address_port(addr, &parsed_port);
if (parsed_port)
port = parsed_port;
}
int rc = -1;
switch (socket_type) {
case SOCKET_TYPE_TCP:
rc = nvnc_listen_tcp(self->nvnc, addr, port,
stream_type);
break;
case SOCKET_TYPE_UNIX:
rc = nvnc_listen_unix(self->nvnc, addr, stream_type);
break;
case SOCKET_TYPE_FROM_FD:;
int fd = atoi(addr);
rc = nvnc_listen(self->nvnc, fd, stream_type);
break;
default:
abort();
}
if (rc < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to listen on socket or bind to its 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 if (socket_type == SOCKET_TYPE_FROM_FD)
nvnc_log(NVNC_LOG_INFO, "Listening for connections on fd %s",
addr);
else
nvnc_log(NVNC_LOG_INFO, "Listening for connections on %s:%d",
addr, port);
return 0;
}
static int apply_addresses_from_config(struct wayvnc* self,
enum socket_type default_socket_type,
enum nvnc_stream_type default_stream_type)
{
uint16_t port = self->cfg.port ? self->cfg.port : DEFAULT_PORT;
if (!self->cfg.address)
return add_listening_address(self, DEFAULT_ADDRESS, port,
default_socket_type,
default_stream_type);
char *addresses = strdup(self->cfg.address);
assert(addresses);
int rc = -1;
const char* delim = " ";
char* tok = strtok(addresses, delim);
while (tok) {
if (add_listening_address(self, tok, port, default_socket_type,
default_stream_type) < 0)
goto out;
tok = strtok(NULL, delim);
}
rc = 0;
out:
free(addresses);
return rc;
}
static struct wayvnc_display* wayvnc_display_add(struct wayvnc* self,
struct image_source* image_source, uint16_t x, uint16_t y)
{
struct wayvnc_display* display = calloc(1, sizeof(*display));
if (!display)
return NULL;
LIST_INSERT_HEAD(&self->wayvnc_displays, display, link);
display->wayvnc = self;
display->image_source = image_source;
nvnc_log(NVNC_LOG_DEBUG, "Adding display at %d, %d", (int)x, (int)y);
display->nvnc_display = nvnc_display_new(x, y);
if (!display->nvnc_display) {
free(display);
return NULL;
}
nvnc_display_set_userdata(display->nvnc_display, display, NULL);
nvnc_add_display(self->nvnc, display->nvnc_display);
return display;
}
static void wayvnc_display_destroy(struct wayvnc_display* display)
{
LIST_REMOVE(display, link);
wayvnc_display_detach(display);
if (display->wayvnc && display->wayvnc->nvnc)
nvnc_remove_display(display->wayvnc->nvnc, display->nvnc_display);
nvnc_display_unref(display->nvnc_display);
free(display);
}
static void wayvnc_display_set_desktop_geometry(struct wayvnc_display* display,
double min_scale)
{
struct output* output = output_from_image_source(display->image_source);
int x, y;
output_get_pos(output, &x, &y);
nvnc_display_set_position(display->nvnc_display,
round(x / min_scale), round(y / min_scale));
int logical_width, logical_height;
output_get_logical_size(output, &logical_width, &logical_height);
nvnc_display_set_logical_size(display->nvnc_display,
round(logical_width / min_scale),
round(logical_height / min_scale));
}
static void on_output_geometry_change(struct observer* observer, void* data)
{
struct wayvnc_display* self = wl_container_of(observer, self,
geometry_change_observer);
double min_scale = image_source_get_min_scale(
self->wayvnc->image_source);
struct wayvnc_display* display;
LIST_FOREACH(display, &self->wayvnc->wayvnc_displays, link)
wayvnc_display_set_desktop_geometry(display, min_scale);
nvnc_log(NVNC_LOG_DEBUG, "Output geometry changed");
}
static void on_desktop_output_destroyed(struct observer* observer, void* data)
{
struct wayvnc_display* self = wl_container_of(observer, self,
destruction_observer);
struct wayvnc* wayvnc = self->wayvnc;
wayvnc_display_destroy(self);
if (!wayland || !wl_list_empty(&wayland->outputs))
return;
if (wayvnc->start_detached) {
nvnc_log(NVNC_LOG_WARNING, "No desktop outputs left. Detaching...");
schedule_wayland_detach(wayvnc);
} else {
nvnc_log(NVNC_LOG_ERROR, "No desktop outputs left. Exiting...");
wayvnc_exit(wayvnc);
}
}
static bool wayvnc_desktop_display_add(struct wayvnc* self,
struct image_source* image_source)
{
struct wayvnc_display* display = wayvnc_display_add(self, image_source,
0, 0);
if (!display)
return false;
double min_scale = image_source_get_min_scale(self->image_source);
wayvnc_display_set_desktop_geometry(display, min_scale);
nvnc_trace("setting geometry observer");
observer_init(&display->geometry_change_observer,
&display->image_source->observable.geometry_change,
on_output_geometry_change);
nvnc_trace("setting destruction observer");
observer_init(&display->destruction_observer,
&display->image_source->observable.destroyed,
on_desktop_output_destroyed);
return true;
}
static void wayvnc_display_list_init(struct wayvnc* self)
{
LIST_INIT(&self->wayvnc_displays);
if (self->image_source && image_source_is_desktop(self->image_source)) {
struct output* output;
wl_list_for_each(output, &wayland->outputs, link)
wayvnc_desktop_display_add(self, &output->image_source);
} else {
wayvnc_display_add(self, self->image_source, 0, 0);
}
}
static void wayvnc_display_list_deinit(struct wayvnc_display_list* list)
{
while (!LIST_EMPTY(list)) {
struct wayvnc_display* display = LIST_FIRST(list);
wayvnc_display_destroy(display);
}
}
static int init_nvnc(struct wayvnc* self)
{
self->nvnc = nvnc_new();
if (!self->nvnc)
return -1;
nvnc_set_userdata(self->nvnc, self, NULL);
nvnc_set_name(self->nvnc, self->desktop_name);
if (self->enable_resizing)
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.enable_pam) {
auth_flags |= NVNC_AUTH_REQUIRE_USERNAME;
}
if (self->cfg.allow_broken_crypto) {
auth_flags |= NVNC_AUTH_ALLOW_BROKEN_CRYPTO;
}
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 auth_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 auth_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 auth_failure;
}
}
}
nvnc_set_normalised_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);
return 0;
auth_failure:
wayvnc_display_list_deinit(&self->wayvnc_displays);
nvnc_del(self->nvnc);
return -1;
}
static void wayvnc_start_cursor_capture(struct wayvnc* self, bool immediate)
{
if (self->cursor_sc) {
screencopy_start(self->cursor_sc, immediate);
}
}
int wayvnc_start_capture(struct wayvnc* self)
{
int rc = screencopy_start(self->screencopy, false);
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;
int rc = image_source_acquire_power_on(self->image_source);
if (rc == 0) {
nvnc_log(NVNC_LOG_DEBUG, "Acquired power state management. Waiting for power event to start capturing");
return 0;
} else if (rc > 0 && image_source_get_power(self->image_source)
!= IMAGE_SOURCE_POWER_ON) {
nvnc_log(NVNC_LOG_DEBUG, "Output power state management already acquired, but not yet powered on");
return 0;
} else if (rc < 0) {
nvnc_log(NVNC_LOG_WARNING, "Failed to acquire power state control. Capturing may fail.");
}
rc = screencopy_start(self->screencopy, true);
if (rc < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to start capture. Exiting...");
wayvnc_exit(self);
}
return rc;
}
static void on_capture_restart_timer(struct aml_timer* 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);
}
static void on_image_source_power_change(struct observer* observer,
void* arg)
{
struct wayvnc* self = wl_container_of(observer, self,
power_change_observer);
struct image_source* image_source = self->image_source;
char description[256];
image_source_describe(image_source, description, sizeof(description));
enum image_source_power_state state = image_source_get_power(image_source);
nvnc_trace("%s power state changed to %s", description,
image_source_power_state_name(state));
if (self->image_source != image_source || self->nr_clients == 0)
return;
switch (state) {
case IMAGE_SOURCE_POWER_ON:
wayvnc_start_capture_immediate(self);
wayvnc_start_cursor_capture(self, true);
break;
case IMAGE_SOURCE_POWER_OFF:
nvnc_log(NVNC_LOG_WARNING, "Output is now off. Pausing frame capture");
screencopy_stop(self->cursor_sc);
screencopy_stop(self->screencopy);
blank_screen(self);
break;
default:
break;
}
}
static void apply_output_transform(const struct wayvnc* self,
struct wv_buffer* buffer, struct pixman_region16* damage)
{
enum wl_output_transform output_transform, buffer_transform;
output_transform = image_source_get_transform(self->image_source);
if (buffer->y_inverted) {
buffer_transform = wv_output_transform_compose(output_transform,
WL_OUTPUT_TRANSFORM_FLIPPED_180);
wv_region_transform(damage, &buffer->frame_damage,
WL_OUTPUT_TRANSFORM_FLIPPED_180,
buffer->width, buffer->height);
} else {
buffer_transform = output_transform;
pixman_region_copy(damage, &buffer->frame_damage);
}
nvnc_frame_set_transform(buffer->nvnc_frame,
(enum nvnc_transform)buffer_transform);
}
static void wayvnc_display_send_next_frame(struct wayvnc* self,
struct wayvnc_display* display, uint64_t now)
{
struct wv_buffer* buffer = display->next_frame;
display->next_frame = NULL;
if (!buffer)
return;
struct pixman_region16 damage;
pixman_region_init(&damage);
if (screencopy_get_capabilities(self->screencopy)
& SCREENCOPY_CAP_TRANSFORM) {
pixman_region_copy(&damage, &buffer->frame_damage);
} else {
// TODO: During desktop capture, use correct transform
apply_output_transform(self, buffer, &damage);
}
pixman_region_intersect_rect(&damage, &damage, 0, 0, buffer->width,
buffer->height);
nvnc_frame_set_damage(buffer->nvnc_frame, &damage);
pixman_region_fini(&damage);
nvnc_display_feed_frame(display->nvnc_display, buffer->nvnc_frame);
self->n_frames_sent++;
wayvnc_start_capture(self);
wv_buffer_release(buffer);
self->last_send_time = now;
}
static void wayvnc_send_next_frame(struct wayvnc* self, uint64_t now)
{
struct wayvnc_display* display;
LIST_FOREACH(display, &self->wayvnc_displays, link)
wayvnc_display_send_next_frame(self, display, now);
}
static void wayvnc_handle_rate_limit_timeout(struct aml_timer* timer)
{
struct wayvnc* self = aml_get_userdata(timer);
uint64_t now = gettime_us();
wayvnc_send_next_frame(self, now);
}
static void wayvnc_process_frame(struct wayvnc* self, struct wv_buffer* buffer,
struct image_source* source)
{
nvnc_trace("Processing buffer: %p", buffer);
self->n_frames_captured++;
self->damage_area_sum += calculate_region_area(&buffer->frame_damage);
struct wayvnc_display* display =
wayvnc_display_find_by_source(self, source);
assert(display);
if (!display)
return;
display->last_frame_info.is_set = true;
display->last_frame_info.width = buffer->width;
display->last_frame_info.height = buffer->height;
if (screencopy_get_capabilities(self->screencopy)
& SCREENCOPY_CAP_TRANSFORM)
display->last_frame_info.transform =
(enum wl_output_transform)nvnc_frame_get_transform(buffer->nvnc_frame);
bool have_pending_frame = false;
if (display->next_frame) {
pixman_region_union(&buffer->frame_damage,
&buffer->frame_damage,
&display->next_frame->frame_damage);
wv_buffer_release(display->next_frame);
have_pending_frame = true;
}
display->next_frame = buffer;
if (have_pending_frame)
return;
uint64_t now = gettime_us();
double dt = (now - self->last_send_time) * 1.0e-6;
int32_t time_left = (1.0 / self->max_rate - dt) * 1.0e6;
if (time_left > 0) {
aml_set_duration(self->rate_limiter, time_left);
aml_start(aml_get_default(), self->rate_limiter);
} else {
wayvnc_send_next_frame(self, now);
}
}
void on_capture_done(enum screencopy_result result, struct wv_buffer* buffer,
struct image_source* source, void* userdata)
{
struct wayvnc* self = userdata;
switch (result) {
case SCREENCOPY_FATAL:
nvnc_log(NVNC_LOG_ERROR, "Failed to capture image. The source probably went away");
break;
case SCREENCOPY_FAILED:
wayvnc_restart_capture(self);
break;
case SCREENCOPY_DONE:
wayvnc_process_frame(self, buffer, source);
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->allow_broken_crypto) {
nvnc_log(NVNC_LOG_WARNING, "Authentication enabled with allow_broken_crypto; insecure authentication methods are available");
}
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->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(struct aml_ticker* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
double total_area = 0;
int width, height;
if (image_source_get_buffer_size(self->image_source, &width, &height)) {
total_area = width * height;
} else {
struct wayvnc_display *display;
LIST_FOREACH(display, &self->wayvnc_displays, link) {
if (!display->last_frame_info.is_set)
break;
width = display->last_frame_info.width;
height = display->last_frame_info.height;
total_area += width * height;
}
}
if (total_area == 0)
return;
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", frames sent: %"PRIu32" average reported frame damage: %.1f %%",
self->n_frames_captured, self->n_frames_sent, relative_area_avg);
self->n_frames_captured = 0;
self->n_frames_sent = 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)
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.wlr_manager || self->data_control.ext_manager)
data_control_destroy(&self->data_control);
self->data_control.wlr_manager = NULL;
self->data_control.ext_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->cursor_master)
wayvnc->cursor_master = self;
if (wayland)
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 == wayvnc->cursor_master) {
nvnc_set_cursor(wayvnc->nvnc, NULL, 0, 0, false);
screencopy_stop(wayvnc->cursor_sc);
screencopy_destroy(wayvnc->cursor_sc);
wayvnc->cursor_sc = NULL;
wayvnc->cursor_master = 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 (self->server->exit_on_disconnect && self->server->nr_clients == 0) {
wayvnc_exit(wayvnc);
}
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 && wayland) {
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
screencopy_stop(wayvnc->screencopy);
image_source_release_power_on(wayvnc->image_source);
stop_performance_ticker(wayvnc);
}
if (self->keyboard.virtual_keyboard)
keyboard_destroy(&self->keyboard);
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
if (self->data_control.wlr_manager || self->data_control.ext_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_client_set_userdata(client, wayvnc_client, client_destroy);
if (self->nr_clients++ == 0 && wayland) {
handle_first_client(self);
}
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);
}
static void parse_keyboard_option(struct wayvnc* self, 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;
struct zwlr_virtual_pointer_manager_v1* pointer_manager =
wayland->zwlr_virtual_pointer_manager_v1;
if (!pointer_manager)
return;
// TODO
if (!image_source_is_output(wayvnc->image_source) &&
!image_source_is_desktop(wayvnc->image_source))
return;
int pointer_manager_version =
zwlr_virtual_pointer_manager_v1_get_version(pointer_manager);
struct output* output = NULL;
if (pointer_manager_version >= 2 &&
image_source_is_output(wayvnc->image_source))
output = output_from_image_source(wayvnc->image_source);
self->pointer.vnc = self->server->nvnc;
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
self->pointer.pointer = output
? zwlr_virtual_pointer_manager_v1_create_virtual_pointer_with_output(
pointer_manager, self->seat->wl_seat, output->wl_output)
: zwlr_virtual_pointer_manager_v1_create_virtual_pointer(
pointer_manager, self->seat->wl_seat);
if (pointer_init(&self->pointer) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise pointer");
}
if (self == wayvnc->cursor_master) {
// Get seat capability update
// TODO: Make this asynchronous
wl_display_roundtrip(wayland->display);
wl_display_dispatch_pending(wayland->display);
configure_cursor_sc(wayvnc, self);
if (wayvnc->cursor_sc)
screencopy_start(wayvnc->cursor_sc, true);
}
}
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 seat* seat = seat_find_by_id(&wayland->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)
{
self->transient_seat = ext_transient_seat_manager_v1_create(
wayland->ext_transient_seat_manager_v1);
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(wayland->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_name) {
self->seat = seat_find_by_name(&wayland->seats,
wayvnc->selected_seat_name);
assert(self->seat);
} else if (wayvnc->use_transient_seat) {
client_init_transient_seat(self);
} else {
self->seat = seat_find_unoccupied(&wayland->seats);
if (!self->seat) {
self->seat = seat_first(&wayland->seats);
}
}
if (self->seat)
self->seat->occupancy++;
}
static void client_init_keyboard(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
struct zwp_virtual_keyboard_manager_v1* keyboard_manager =
wayland->zwp_virtual_keyboard_manager_v1;
if (!keyboard_manager)
return;
self->keyboard.virtual_keyboard =
zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
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_client_get_userdata(c);
client_init_pointer(client);
}
}
static void client_init_data_control(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
if (wayland->ext_data_control_manager_v1) {
self->data_control.ext_manager =
wayland->ext_data_control_manager_v1;
} else if (wayland->zwlr_data_control_manager_v1) {
self->data_control.wlr_manager =
wayland->zwlr_data_control_manager_v1;
} else {
return;
}
data_control_init(&self->data_control, wayvnc->nvnc,
self->seat->wl_seat);
}
void log_image_source(struct wayvnc* self)
{
char description[256];
image_source_describe(self->image_source, description, sizeof(description));
nvnc_log(NVNC_LOG_INFO, "Capturing %s", description);
if (!image_source_is_output(self->image_source))
return;
// TODO: Maybe remove this?
struct output* source_output = output_from_image_source(self->image_source);
struct output* output;
wl_list_for_each(output, &wayland->outputs, link) {
bool this_output = (output->id == source_output->id);
int width = 0, height = 0;
output_get_logical_size(output, &width, &height);
int x = 0, y = 0;
output_get_pos(output, &x, &y);
nvnc_log(NVNC_LOG_INFO, "%s %s %dx%d+%dx%d Power:%s",
this_output ? ">>" : "--",
output_get_description(output), width, height,
x, y,
image_source_power_state_name(output->power));
}
}
static void wayvnc_process_cursor(struct wayvnc* self, struct wv_buffer* buffer,
struct image_source* source)
{
nvnc_log(NVNC_LOG_DEBUG, "Got new cursor");
bool is_damaged = pixman_region_not_empty(&buffer->frame_damage);
double h_scale = 1.0;
double v_scale = 1.0;
image_source_get_scale(source, &h_scale, &v_scale);
double min_scale = image_source_get_min_scale(self->image_source);
h_scale /= min_scale;
v_scale /= min_scale;
nvnc_frame_set_logical_width(buffer->nvnc_frame,
round(h_scale * nvnc_frame_get_width(buffer->nvnc_frame)));
nvnc_frame_set_logical_height(buffer->nvnc_frame,
round(v_scale * nvnc_frame_get_height(buffer->nvnc_frame)));
int x_hotspot = round(h_scale * buffer->x_hotspot);
int y_hotspot = round(v_scale * buffer->y_hotspot);
nvnc_set_cursor(self->nvnc, buffer->nvnc_frame, x_hotspot, y_hotspot,
is_damaged);
wv_buffer_release(buffer);
wayvnc_start_cursor_capture(self, false);
}
static void on_cursor_capture_done(enum screencopy_result result,
struct wv_buffer* buffer, struct image_source* source,
void* userdata)
{
struct wayvnc* self = userdata;
switch (result) {
case SCREENCOPY_FATAL:
nvnc_log(NVNC_LOG_ERROR, "Failed to capture cursor. The source probably went away");
break;
case SCREENCOPY_FAILED:
wayvnc_start_cursor_capture(self, true);
break;
case SCREENCOPY_DONE:
wayvnc_process_cursor(self, buffer, source);
break;
}
}
static enum nvnc_buffer_type buffer_type_to_nvnc_buffer_type(enum wv_buffer_type type)
{
switch (type) {
case WV_BUFFER_SHM:
return NVNC_BUFFER_SIMPLE;
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
return NVNC_BUFFER_GBM_BO;
#endif
case WV_BUFFER_UNSPEC:;
}
return NVNC_BUFFER_UNSPEC;
}
static double rate_output_format(const void* userdata,
enum wv_buffer_type type, uint32_t format, uint64_t modifier)
{
const struct wayvnc* self = userdata;
enum nvnc_buffer_type fb_type = buffer_type_to_nvnc_buffer_type(type);
assert(fb_type != NVNC_BUFFER_UNSPEC);
return nvnc_rate_pixel_format(self->nvnc, fb_type, format,
modifier);
}
static double rate_cursor_format(const void* userdata,
enum wv_buffer_type type, uint32_t format, uint64_t modifier)
{
const struct wayvnc* self = userdata;
enum nvnc_buffer_type fb_type = buffer_type_to_nvnc_buffer_type(type);
assert(fb_type != NVNC_BUFFER_UNSPEC);
return nvnc_rate_cursor_pixel_format(self->nvnc, fb_type, format,
modifier);
}
static bool configure_cursor_sc(struct wayvnc* self,
struct wayvnc_client* client)
{
nvnc_log(NVNC_LOG_DEBUG, "Configuring cursor capturing");
screencopy_stop(self->cursor_sc);
screencopy_destroy(self->cursor_sc);
self->cursor_sc = NULL;
struct seat* seat = client->seat;
assert(seat);
if (!(seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) {
nvnc_log(NVNC_LOG_DEBUG, "Client's seat has no pointer capability");
return false;
}
self->cursor_sc = screencopy_create_cursor(self->image_source,
seat->wl_seat);
if (!self->cursor_sc) {
nvnc_log(NVNC_LOG_DEBUG, "Failed to capture cursor");
return false;
}
self->cursor_sc->on_done = on_cursor_capture_done;
self->cursor_sc->rate_format = rate_cursor_format;
self->cursor_sc->userdata = self;
self->cursor_sc->rate_limit = self->max_rate * 2;
self->cursor_sc->enable_linux_dmabuf = false;
nvnc_log(NVNC_LOG_DEBUG, "Configured cursor capturing");
return true;
}
bool configure_screencopy(struct wayvnc* self)
{
screencopy_stop(self->screencopy);
screencopy_destroy(self->screencopy);
self->screencopy = screencopy_create(self->image_source,
self->overlay_cursor);
if (!self->screencopy) {
nvnc_log(NVNC_LOG_ERROR, "screencopy is not supported by compositor");
return false;
}
self->screencopy->on_done = on_capture_done;
self->screencopy->rate_format = rate_output_format;
self->screencopy->userdata = self;
/* Because screencopy (at least the way it's implemented in wlroots),
* does not capture immediately, but rather schedules a frame to be
* captured on next output commit event, if we use the exact rate limit,
* we'll sometimes hit the frame before commit and sometimes after.
*
* This is why we multiply the capture rate limit by 2 here and have a
* secondary rate limiter for frames sent to VNC.
*/
self->screencopy->rate_limit = self->max_rate * 2;
self->screencopy->enable_linux_dmabuf = self->enable_gpu_features;
return true;
}
static void on_toplevel_closed(struct toplevel* toplevel)
{
struct wayvnc* self = toplevel->userdata;
nvnc_log(NVNC_LOG_ERROR, "Toplevel was closed. Exiting...");
wayvnc_exit(self);
}
static void on_image_source_destroyed(struct observer* observer, void* arg)
{
struct wayvnc* self = wl_container_of(observer, self,
destruction_observer);
self->image_source = NULL;
}
void set_image_source(struct wayvnc* self, struct image_source* image_source)
{
if (self->image_source) {
observer_deinit(&self->power_change_observer);
observer_deinit(&self->destruction_observer);
}
self->image_source = image_source;
observer_init(&self->power_change_observer,
&image_source->observable.power_change,
on_image_source_power_change);
observer_init(&self->destruction_observer,
&image_source->observable.destroyed,
on_image_source_destroyed);
if (image_source_is_toplevel(image_source)) {
struct toplevel* toplevel =
toplevel_from_image_source(image_source);
toplevel->on_closed = on_toplevel_closed;
toplevel->userdata = self;
}
if (self->ctl && image_source_is_output(image_source)) {
struct output* output = output_from_image_source(image_source);
ctl_server_event_capture_changed(self->ctl,
output_get_name(output));
}
log_image_source(self);
}
void switch_to_output(struct wayvnc* self, struct output* output)
{
if (self->image_source) {
assert(image_source_is_output(self->image_source));
struct output* current_output =
output_from_image_source(self->image_source);
if (current_output == output) {
nvnc_log(NVNC_LOG_DEBUG, "Already selected output %s",
output_get_name(output));
return;
}
screencopy_stop(self->screencopy);
output_release_power_on(current_output);
}
set_image_source(self, &output->image_source);
configure_screencopy(self);
reinitialise_pointers(self);
if (self->nr_clients > 0)
wayvnc_start_capture_immediate(self);
screencopy_stop(self->cursor_sc);
if (self->cursor_sc)
screencopy_start(self->cursor_sc, true);
}
void switch_to_next_output(struct wayvnc* self)
{
nvnc_log(NVNC_LOG_INFO, "Rotating to next output");
struct output* next = output_cycle(&wayland->outputs,
output_from_image_source(self->image_source),
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(&wayland->outputs,
output_from_image_source(self->image_source),
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,
enum image_source_type image_source_type,
const char* image_source_name)
{
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);
bool ok = wayland_attach(self, display, image_source_type,
image_source_name);
wayvnc_display_list_deinit(&self->wayvnc_displays);
wayvnc_display_list_init(self);
blank_screen(self);
if (!ok)
goto 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_client_get_userdata(nvnc_client);
client_init_wayland(client);
}
nvnc_log(NVNC_LOG_INFO, "Attached to %s",
display ? display : "default display");
if (self->nr_clients > 0) {
handle_first_client(self);
}
nvnc_set_log_fn_thread_local(NULL);
out:
return ok ? cmd_ok() : cmd_failed("%s", intercepted_error);
}
// TODO: Also add arguments for seat
static bool wayland_attach(struct wayvnc* self, const char* display,
enum image_source_type image_source_type,
const char* image_source_name)
{
if (wayland) {
wayland_detach(self);
}
if (image_source_type) {
self->image_source_type = image_source_type;
self->image_source_name[0] = '\0';
if (image_source_name) {
strlcpy(self->image_source_name, image_source_name,
sizeof(self->image_source_name));
}
}
nvnc_log(NVNC_LOG_DEBUG, "Attaching to %s",
display ? display : "default display");
if (init_wayland(self, display) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise wayland");
goto failure;
}
if (!wayland->zwlr_screencopy_manager_v1 &&
!wayland->ext_image_copy_capture_manager_v1) {
nvnc_log(NVNC_LOG_ERROR, "Attached display does not implement screencapturing");
goto failure;
}
switch (self->image_source_type) {
case IMAGE_SOURCE_TYPE_OUTPUT:;
struct output* out;
out = output_find_by_name(&wayland->outputs,
self->image_source_name);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output");
goto failure;
}
set_image_source(self, &out->image_source);
break;
case IMAGE_SOURCE_TYPE_DESKTOP:;
struct desktop* desktop = desktop_new(&wayland->outputs);
if (!desktop) {
nvnc_log(NVNC_LOG_ERROR, "Failed to set up desktop capture");
goto failure;
}
set_image_source(self, &desktop->image_source);
break;
case IMAGE_SOURCE_TYPE_TOPLEVEL:;
struct toplevel* toplevel;
toplevel = toplevel_find_by_identifier(&wayland->toplevels,
self->image_source_name);
if (!toplevel) {
nvnc_log(NVNC_LOG_ERROR, "No such toplevel");
goto failure;
}
set_image_source(self, &toplevel->image_source);
break;
case IMAGE_SOURCE_TYPE_UNSPEC:;
out = output_first(&wayland->outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output found");
goto failure;
}
set_image_source(self, &out->image_source);
}
if (self->selected_seat_name) {
struct seat* seat = seat_find_by_name(&wayland->seats,
self->selected_seat_name);
if (!seat) {
nvnc_log(NVNC_LOG_ERROR, "No such seat: %s",
self->selected_seat_name);
goto failure;
}
}
if (!configure_screencopy(self)) {
goto failure;
}
return true;
failure:
wayland_detach(self);
return false;
}
static struct cmd_response* on_detach(struct ctl* ctl)
{
struct wayvnc* self = ctl_server_userdata(ctl);
assert(self);
if (!wayland) {
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_desktop_capture = false;
bool use_external_fd = false;
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* toplevel_id;
const char* keyboard_options = NULL;
char keyboard_options_buffer[256];
bool overlay_cursor = false;
bool show_performance = false;
int max_rate = 30;
bool disable_input = false;
bool use_transient_seat = false;
bool exit_on_disconnect = false;
const char* log_level_name = NULL;
const char* log_filter = NULL;
bool is_verbose = false;
int log_level = NVNC_LOG_WARNING;
static const struct wv_option opts[] = {
{ .positional = "address",
.help = "An address to listen on.",
.default_ = "localhost",
.is_repeating = true },
{ 'a', "desktop", NULL,
"Capture all outputs." },
{ 'C', "config", "",
"Select a config file." },
{ 'd', "disable-input", NULL,
"Disable all remote input." },
{ 'D', "detached", NULL,
"Start detached from a compositor." },
{ 'e', "exit-on-disconnect", NULL,
"Exit when last client disconnects." },
{ 'f', "max-fps", "",
"Set rate limit.",
.default_ = "30" },
{ 'F', "log-filter", "",
"Set log filter." },
{ 'g', "gpu", NULL,
"Enable features that need GPU." },
{ 'h', "help", NULL,
"Get help (this text)." },
{ 'k', "keyboard", "[-]",
"Select keyboard layout with an optional variant." },
{ 'L', "log-level", "",
"Set log level. The levels are: error, warning, info, debug trace and quiet.",
.default_ = "warning" },
{ 'n', "name", "",
"Set the desktop name.",
.default_ = "WayVNC" },
{ 'o', "output", "",
"Select output to capture." },
{ 'p', "show-performance", NULL,
"Show performance counters." },
{ 'r', "render-cursor", NULL,
"Enable overlay cursor rendering." },
{ 'R', "disable-resizing", NULL,
"Disable automatic resizing." },
{ 's', "seat", "",
"Select seat by name." },
{ 'S', "socket", "",
"Control socket path." },
{ 't', "transient-seat", NULL,
"Use transient seat." },
{ 'T', "toplevel", "",
"Capture a toplevel" },
{ 'u', "unix-socket", NULL,
"Create unix domain socket." },
{ 'v', "verbose", NULL,
"Be more verbose. Same as setting --log-level=info" },
{ 'V', "version", NULL,
"Show version info." },
{ 'w', "websocket", NULL,
"Create a websocket." },
{ 'x', "external-listener-fd", NULL,
"The address is a pre-bound file descriptor.", },
{}
};
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);
}
use_desktop_capture = option_parser_get_value(&option_parser, "desktop");
cfg_file = option_parser_get_value(&option_parser, "config");
enable_gpu_features = !!option_parser_get_value(&option_parser, "gpu");
self.desktop_name = option_parser_get_value(&option_parser, "name");
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,
"show-performance");
exit_on_disconnect = !!option_parser_get_value(&option_parser, "exit-on-disconnect");
use_unix_socket = !!option_parser_get_value(&option_parser, "unix-socket");
use_websocket = !!option_parser_get_value(&option_parser, "websocket");
use_external_fd = !!option_parser_get_value(&option_parser,
"external-listener-fd");
disable_input = !!option_parser_get_value(&option_parser, "disable-input");
is_verbose = option_parser_get_value(&option_parser, "verbose");
log_level_name = option_parser_get_value(&option_parser, "log-level");
log_filter = option_parser_get_value(&option_parser, "log-filter");
max_rate = atoi(option_parser_get_value(&option_parser, "max-fps"));
use_transient_seat = !!option_parser_get_value(&option_parser,
"transient-seat");
toplevel_id = option_parser_get_value(&option_parser, "toplevel");
start_detached = !!option_parser_get_value(&option_parser, "detached");
self.enable_resizing = !option_parser_get_value(&option_parser,
"disable-resizing");
self.start_detached = start_detached;
self.exit_on_disconnect = exit_on_disconnect;
self.overlay_cursor = overlay_cursor;
self.max_rate = max_rate;
self.enable_gpu_features = enable_gpu_features;
self.use_toplevel = !!toplevel_id;
self.selected_seat_name = seat_name;
keyboard_options = option_parser_get_value(&option_parser, "keyboard");
if (keyboard_options) {
strlcpy(keyboard_options_buffer, keyboard_options,
sizeof(keyboard_options_buffer));
parse_keyboard_option(&self, keyboard_options_buffer);
}
log_level = log_level_from_string(log_level_name);
if (log_level < 0) {
nvnc_log(NVNC_LOG_ERROR, "Invalid log level: %s", log_level_name);
return 1;
}
if (is_verbose && log_level < NVNC_LOG_INFO)
log_level = NVNC_LOG_INFO;
nvnc_set_log_level(log_level);
if (log_filter)
nvnc_set_log_filter(log_filter);
if (seat_name && disable_input) {
nvnc_log(NVNC_LOG_ERROR, "seat and disable-input are conflicting options");
return 1;
}
int n_address_modifiers = use_unix_socket + use_external_fd;
if (n_address_modifiers > 1) {
nvnc_log(NVNC_LOG_ERROR, "Only one of unix-socket or the external-listener-fd options may be set");
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;
}
if (toplevel_id && output_name) {
nvnc_log(NVNC_LOG_ERROR, "toplevel and output are conflicting options");
return 1;
}
if (toplevel_id && start_detached) {
nvnc_log(NVNC_LOG_ERROR, "toplevel and start-detached are conflicting options");
return 1;
}
if (use_desktop_capture && output_name) {
nvnc_log(NVNC_LOG_ERROR, "desktop and output are conflicting options");
return 1;
}
if (use_desktop_capture && toplevel_id) {
nvnc_log(NVNC_LOG_ERROR, "desktop and toplevel 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;
self.disable_input = disable_input;
self.use_transient_seat = use_transient_seat;
srand(time(NULL));
signal(SIGPIPE, SIG_IGN);
struct aml* aml = aml_new();
if (!aml)
goto failure;
aml_set_default(aml);
if (init_main_loop(&self) < 0)
goto failure;
self.rate_limiter = aml_timer_new(0, wayvnc_handle_rate_limit_timeout,
&self, NULL);
if (!self.rate_limiter)
goto rate_limiter_failure;
if (output_name) {
self.image_source_type = IMAGE_SOURCE_TYPE_OUTPUT;
strlcpy(self.image_source_name, output_name,
sizeof(self.image_source_name));
} else if (use_desktop_capture) {
self.image_source_type = IMAGE_SOURCE_TYPE_DESKTOP;
} else if (toplevel_id) {
self.image_source_type = IMAGE_SOURCE_TYPE_OUTPUT;
strlcpy(self.image_source_name, toplevel_id,
sizeof(self.image_source_name));
} else {
self.image_source_type = IMAGE_SOURCE_TYPE_UNSPEC;
}
if (!start_detached)
if (!wayland_attach(&self, NULL, IMAGE_SOURCE_TYPE_UNSPEC,
NULL))
goto wayland_failure;
enum socket_type default_socket_type = SOCKET_TYPE_TCP;
if (use_unix_socket)
default_socket_type = SOCKET_TYPE_UNIX;
else if (use_external_fd)
default_socket_type = SOCKET_TYPE_FROM_FD;
enum nvnc_stream_type default_stream_type = NVNC_STREAM_NORMAL;
if (use_websocket)
default_stream_type = NVNC_STREAM_WEBSOCKET;
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,
.on_set_desktop_name = on_set_desktop_name,
.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 (init_nvnc(&self) < 0)
goto nvnc_failure;
wayvnc_display_list_init(&self);
blank_screen(&self);
address = option_parser_get_value_with_offset(&option_parser,
"address", 0);
// If the second address argument is a number, we will assume it to be a
// port for historical reasons.
const char* port_str = option_parser_get_value_with_offset(
&option_parser, "address", 1);
if (port_str) {
char* endptr;
port = strtoul(port_str, &endptr, 0);
if (!port_str[0] || endptr[0])
port = 0;
}
if (port) {
if (add_listening_address(&self, address, port,
default_socket_type, default_stream_type) < 0)
goto nvnc_failure;
} else if (!option_parser_get_value_with_offset(&option_parser,
"address", 0)) {
if (apply_addresses_from_config(&self, default_socket_type,
default_stream_type) < 0)
goto nvnc_failure;
} else {
port = self.cfg.port ? self.cfg.port : DEFAULT_PORT;
for (int i = 0;; ++i) {
const char* address;
address = option_parser_get_value_with_offset(
&option_parser, "address", i);
if (!address)
break;
if (add_listening_address(&self, address, port,
default_socket_type,
default_stream_type) < 0)
goto nvnc_failure;
}
}
if (wayland)
wl_display_dispatch_pending(wayland->display);
while (!self.do_exit) {
if (wayland)
wl_display_flush(wayland->display);
aml_poll(aml, -1);
aml_dispatch(aml);
}
nvnc_log(NVNC_LOG_INFO, "Exiting...");
cancel_deferred_detach(&self);
ctl_server_destroy(self.ctl);
self.ctl = NULL;
wayvnc_display_list_deinit(&self.wayvnc_displays);
nvnc_del(self.nvnc);
self.nvnc = NULL;
wayland_destroy(wayland);
wayland = NULL;
aml_stop(aml, self.rate_limiter);
aml_unref(self.rate_limiter);
aml_unref(aml);
cfg_destroy(&self.cfg);
return 0;
nvnc_failure:
ctl_server_destroy(self.ctl);
self.ctl = NULL;
wayvnc_display_list_deinit(&self.wayvnc_displays);
nvnc_del(self.nvnc);
self.nvnc = NULL;
ctl_server_failure:
wayland_detach(&self);
wayland_failure:
rate_limiter_failure:
aml_stop(aml, self.rate_limiter);
aml_unref(self.rate_limiter);
aml_unref(aml);
failure:
cfg_destroy(&self.cfg);
return 1;
}
any1-wayvnc-ae53f07/src/option-parser.c 0000664 0000000 0000000 00000024524 15223766131 0020055 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 - 2024 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 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);
struct table_printer printer;
table_printer_init(&printer, stream);
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;
int end = self->n_opts;
for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional)
continue;
if (opt->is_repeating) {
end = i;
break;
}
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, "]");
for (int i = end; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional)
continue;
assert(opt->is_repeating);
fprintf(stream, " [%s...]", opt->positional);
}
}
int option_parser_print_arguments(struct option_parser* self, FILE* stream)
{
bool have_args = 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;
have_args = true;
}
if (!have_args)
return 0;
fprintf(stream, "Arguments:\n");
struct table_printer printer;
table_printer_init(&printer, stream);
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;
if (!opt->is_repeating)
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;
}
const char* option_parser_get_value_with_offset(const struct option_parser* self,
const char* name, int index)
{
const struct wv_option* opt;
for (int i = 0; i < self->n_values; ++i) {
const struct wv_option_value* value = &self->values[i];
opt = value->option;
if (!opt->positional || !opt->is_repeating)
continue;
if (strcmp(opt->positional, name) != 0)
continue;
if (index-- > 0)
continue;
return value->value;
}
return NULL;
}
void option_parser_print_cmd_summary(const char* summary, FILE* stream)
{
struct table_printer printer;
table_printer_init(&printer, stream);
fprintf(stream, "\n");
table_printer_indent_and_reflow_text(stream, summary, printer.max_width, 0, 0);
fprintf(stream, "\n");
}
any1-wayvnc-ae53f07/src/output-management.c 0000664 0000000 0000000 00000023725 15223766131 0020727 0 ustar 00root root 0000000 0000000 /*
* 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_mode {
struct zwlr_output_mode_v1* mode;
struct wl_list link;
};
struct output_manager_head {
struct zwlr_output_head_v1* head;
struct wl_list link;
struct wl_list modes;
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");
struct output_manager_head* head = data;
struct output_manager_mode* entry = calloc(1, sizeof(*entry));
if (!entry) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
zwlr_output_mode_v1_release(mode);
return;
}
entry->mode = mode;
wl_list_insert(head->modes.prev, &entry->link);
}
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)
{
#ifndef NDEBUG
double scale = wl_fixed_to_double(scale_f);
nvnc_trace("Got head scale: %.2f", scale);
#endif
}
static void output_head_make(void* data,
struct zwlr_output_head_v1* output_head, const char* make)
{
nvnc_trace("Got head make: %s", make);
}
static void output_head_model(void* data,
struct zwlr_output_head_v1* output_head, const char* model)
{
nvnc_trace("Got head model: %s", model);
}
static void output_head_serial_number(void* data,
struct zwlr_output_head_v1* output_head,
const char* serial_number)
{
nvnc_trace("Got head serial number: %s", serial_number);
}
static void output_head_destroy(struct output_manager_head* head)
{
struct output_manager_mode* mode;
struct output_manager_mode* tmp;
wl_list_for_each_safe(mode, tmp, &head->modes, link) {
wl_list_remove(&mode->link);
zwlr_output_mode_v1_release(mode->mode);
free(mode);
}
zwlr_output_head_v1_release(head->head);
wl_list_remove(&head->link);
free(head->name);
head->name = NULL;
head->head = NULL;
free(head);
}
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;
output_head_destroy(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,
.make = output_head_make,
.model = output_head_model,
.serial_number = output_head_serial_number,
};
/* 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_init(&head->modes);
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)
output_head_destroy(head);
zwlr_output_manager_v1_destroy(wlr_output_manager);
wlr_output_manager = NULL;
last_config_serial = 0;
}
struct zwlr_output_configuration_v1* wlr_output_manager_start_config(void)
{
if (!wlr_output_manager) {
nvnc_log(NVNC_LOG_INFO,
"output-management protocol not available, not resizing output");
return NULL;
}
struct zwlr_output_configuration_v1* 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);
return config;
}
bool wlr_output_manager_commit_config(
struct zwlr_output_configuration_v1* config)
{
struct output_manager_head* head;
wl_list_for_each(head, &heads, link) {
if (head->enabled)
continue;
nvnc_trace("disabling output %s", head->name);
zwlr_output_configuration_v1_disable_head(
config, head->head);
}
nvnc_trace("applying new output config");
zwlr_output_configuration_v1_apply(config);
return true;
}
void wlr_output_manager_abort_config(
struct zwlr_output_configuration_v1* config)
{
zwlr_output_configuration_v1_destroy(config);
}
bool wlr_output_manager_configure_output(
struct zwlr_output_configuration_v1* config,
struct output* output, uint16_t width, uint16_t height,
uint16_t x, uint16_t y)
{
// TODO: This could be synced to --max-fps
int refresh_rate = 0;
if (!output_is_headless(output)) {
nvnc_log(NVNC_LOG_INFO,
"not resizing output %s: not a headless one",
output_get_name(output));
return false;
}
bool found = false;
struct output_manager_head* head;
wl_list_for_each(head, &heads, link) {
const char* output_name = output_get_name(output);
if (!head->name || strcmp(head->name, output_name) != 0)
continue;
struct zwlr_output_configuration_head_v1* config_head =
zwlr_output_configuration_v1_enable_head(
config, head->head);
nvnc_trace("reconfiguring output %s", head->name);
zwlr_output_configuration_head_v1_set_custom_mode(
config_head, width, height, refresh_rate);
zwlr_output_configuration_head_v1_set_position(config_head,
x, y);
/* 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);
found = true;
zwlr_output_configuration_head_v1_destroy(config_head);
}
return found;
}
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height)
{
struct zwlr_output_configuration_v1* config =
wlr_output_manager_start_config();
if (!config)
return false;
if (!wlr_output_manager_configure_output(config, output, width, height,
0, 0))
return false;
return wlr_output_manager_commit_config(config);
}
any1-wayvnc-ae53f07/src/output.c 0000664 0000000 0000000 00000032411 15223766131 0016605 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2026 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 "output.h"
#include "strlcpy.h"
#include "wayland.h"
#include "xdg-output-unstable-v1.h"
#include "wlr-output-power-management-unstable-v1.h"
struct output* output_from_image_source(const struct image_source* source)
{
assert(image_source_is_output(source));
return (struct output*)source;
}
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;
output->pending.transform = transform;
strlcpy(output->pending.make, make, sizeof(output->pending.make));
strlcpy(output->pending.model, model, sizeof(output->pending.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;
output->pending.buffer_width = width;
output->pending.buffer_height = height;
}
static void output_handle_done(void* data, struct wl_output* wl_output)
{
struct output* output = data;
bool geometry_changed = false;
geometry_changed |= output->state.transform != output->pending.transform;
geometry_changed |= output->state.logical_width != output->pending.logical_width;
geometry_changed |= output->state.logical_height != output->pending.logical_height;
geometry_changed |= output->state.buffer_width != output->pending.buffer_width;
geometry_changed |= output->state.buffer_height != output->pending.buffer_height;
geometry_changed |= output->state.x != output->pending.x;
geometry_changed |= output->state.y != output->pending.y;
output->state.logical_width = output->pending.logical_width;
output->state.logical_height = output->pending.logical_height;
output->state.buffer_width = output->pending.buffer_width;
output->state.buffer_height = output->pending.buffer_height;
output->state.x = output->pending.x;
output->state.y = output->pending.y;
output->state.transform = output->pending.transform;
strcpy(output->state.make, output->pending.make);
strcpy(output->state.model, output->pending.model);
strcpy(output->state.name, output->pending.name);
strcpy(output->state.description, output->pending.description);
if (geometry_changed) {
nvnc_trace("%s: geometry changed: logical: %"PRIu32"x%"PRIu32"+(%"PRIu32",%"PRIu32"), buffer: %"PRIu32"x%"PRIu32,
output->state.name,
output->state.logical_width,
output->state.logical_height, output->state.x,
output->state.y, output->state.buffer_width,
output->state.buffer_height);
observable_notify(&output->image_source.observable.geometry_change,
NULL);
}
}
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)
{
image_source_destroy(&output->image_source);
}
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)
{
struct output* output = data;
output->pending.x = x;
output->pending.y = y;
}
void output_logical_size(void* data, struct zxdg_output_v1* xdg_output,
int32_t width, int32_t height)
{
struct output* output = data;
output->pending.logical_width = width;
output->pending.logical_height = height;
}
void output_name(void* data, struct zxdg_output_v1* xdg_output,
const char* name)
{
struct output* self = data;
strlcpy(self->pending.name, name, sizeof(self->pending.name));
}
void output_description(void* data, struct zxdg_output_v1* xdg_output,
const char* description)
{
struct output* self = data;
strlcpy(self->pending.description, description,
sizeof(self->pending.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 wayland* wayland,
struct output* self)
{
if (!wayland->zxdg_output_manager_v1 || self->xdg_output)
return;
struct zxdg_output_v1* xdg_output = zxdg_output_manager_v1_get_xdg_output(
wayland->zxdg_output_manager_v1, self->wl_output);
self->xdg_output = xdg_output;
zxdg_output_v1_add_listener(self->xdg_output, &xdg_output_listener,
self);
}
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->state.name,
(mode == ZWLR_OUTPUT_POWER_V1_MODE_ON) ? "ON" : "OFF");
enum image_source_power_state old = self->power;
switch (mode) {
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
self->power = IMAGE_SOURCE_POWER_OFF;
break;
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
self->power = IMAGE_SOURCE_POWER_ON;
break;
}
if (old != self->power)
observable_notify(&self->image_source.observable.power_change,
NULL);
}
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->state.name);
self->power_management_failed = true;
self->power = IMAGE_SOURCE_POWER_ON;
zwlr_output_power_v1_destroy(self->wlr_output_power);
self->wlr_output_power = NULL;
observable_notify(&self->image_source.observable.power_change, NULL);
}
static const struct zwlr_output_power_v1_listener wlr_output_power_listener = {
.mode = output_power_mode,
.failed = output_power_failed,
};
int output_acquire_power_on(struct output* output)
{
if (output->power_management_failed)
return -1;
if (output->wlr_output_power)
return 1;
struct zwlr_output_power_manager_v1 *power_manager =
output->wayland->zwlr_output_power_manager_v1;
if (!power_manager)
return -1;
struct zwlr_output_power_v1* wlr_output_power =
zwlr_output_power_manager_v1_get_output_power(
power_manager,
output->wl_output);
output->wlr_output_power = wlr_output_power;
zwlr_output_power_v1_add_listener(output->wlr_output_power,
&wlr_output_power_listener, output);
zwlr_output_power_v1_set_mode(output->wlr_output_power,
ZWLR_OUTPUT_POWER_V1_MODE_ON);
return 0;
}
void output_release_power_on(struct output* output)
{
if (!output->wlr_output_power)
return;
zwlr_output_power_v1_destroy(output->wlr_output_power);
output->wlr_output_power = NULL;
output->power = IMAGE_SOURCE_POWER_UNKNOWN;
}
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->state.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 NULL;
}
struct output* output_last(struct wl_list* list)
{
struct output* output;
wl_list_for_each_reverse(output, list, link)
return output;
return NULL;
}
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_xdg_output_managers(struct wayland* wayland,
struct wl_list* list)
{
struct output* output;
wl_list_for_each(output, list, link) {
output_setup_xdg_output_manager(wayland, output);
}
}
static void output_image_source_get_logical_size(const struct image_source* self,
int* width, int* height)
{
struct output* output = output_from_image_source(self);
output_get_logical_size(output, width, height);
}
static enum wl_output_transform output_image_source_get_transform(
const struct image_source* self)
{
return output_from_image_source(self)->state.transform;
}
static enum image_source_power_state output_image_source_get_power_state(
const struct image_source* self)
{
return output_from_image_source(self)->power;
}
static void output_image_source_describe(const struct image_source* self,
char* dst, size_t maxlen)
{
struct output* output = output_from_image_source(self);
snprintf(dst, maxlen, "output %s", output->state.name);
}
static int output_image_source_acquire_power_on(struct image_source* self)
{
return output_acquire_power_on(output_from_image_source(self));
}
static void output_image_source_release_power_on(struct image_source* self)
{
output_release_power_on(output_from_image_source(self));
}
static void output_image_source_deinit(struct image_source* base)
{
struct output* output = output_from_image_source(base);
output_release_power_on(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);
}
static void output_image_source_get_buffer_size(const struct image_source* self,
int* width, int* height)
{
struct output* output = output_from_image_source(self);
output_get_buffer_size(output, width, height);
}
static struct image_source_impl image_source_impl = {
.get_logical_size = output_image_source_get_logical_size,
.get_buffer_size = output_image_source_get_buffer_size,
.get_transform = output_image_source_get_transform,
.get_power_state = output_image_source_get_power_state,
.describe = output_image_source_describe,
.acquire_power_on = output_image_source_acquire_power_on,
.release_power_on = output_image_source_release_power_on,
.deinit = output_image_source_deinit,
};
bool image_source_is_output(const struct image_source* self)
{
return self->impl == &image_source_impl;
}
struct output* output_new(struct wayland* wayland, 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;
}
image_source_init(&output->image_source, &image_source_impl);
output->wayland = wayland;
output->wl_output = wl_output;
output->id = id;
output->power = IMAGE_SOURCE_POWER_UNKNOWN;
wl_output_add_listener(output->wl_output, &output_listener,
output);
output_setup_xdg_output_manager(wayland, output);
return output;
}
bool output_is_headless(const struct output* self)
{
return (strncmp(self->state.name, "HEADLESS-", strlen("HEADLESS-")) == 0) ||
(strncmp(self->state.name, "NOOP-", strlen("NOOP-")) == 0);
}
void output_get_logical_size(const struct output* self,
int* width, int* height)
{
if (width)
*width = self->state.logical_width;
if (height)
*height = self->state.logical_height;
}
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;
}
void output_get_buffer_size(const struct output* self,
int* width, int* height)
{
if (is_transform_90_degrees(self->state.transform)) {
if (width)
*width = self->state.buffer_height;
if (height)
*height = self->state.buffer_width;
} else {
if (width)
*width = self->state.buffer_width;
if (height)
*height = self->state.buffer_height;
}
}
void output_get_pos(const struct output* self, int* x, int* y)
{
if (x)
*x = self->state.x;
if (y)
*y = self->state.y;
}
const char* output_get_name(const struct output* self)
{
return self->state.name;
}
const char* output_get_description(const struct output* self)
{
return self->state.description;
}
any1-wayvnc-ae53f07/src/pam_auth.c 0000664 0000000 0000000 00000004606 15223766131 0017050 0 ustar 00root root 0000000 0000000 /*
* 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;
}
any1-wayvnc-ae53f07/src/pixels.c 0000664 0000000 0000000 00000005034 15223766131 0016552 0 ustar 00root root 0000000 0000000 /*
* 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;
}
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;
}
any1-wayvnc-ae53f07/src/pointer.c 0000664 0000000 0000000 00000006644 15223766131 0016736 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2024 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"
#include "image-source.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));
if (diff & NVNC_BUTTON_BACK)
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_BACK,
!!(mask & NVNC_BUTTON_BACK));
if (diff & NVNC_BUTTON_FORWARD)
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_FORWARD,
!!(mask & NVNC_BUTTON_FORWARD));
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, double x, double 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 * INT32_MAX, y * INT32_MAX,
INT32_MAX, INT32_MAX);
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);
}
any1-wayvnc-ae53f07/src/screencopy-interface.c 0000664 0000000 0000000 00000004601 15223766131 0021355 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 - 2025 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 "screencopy-interface.h"
#include "image-source.h"
#include "wayland.h"
#include
extern struct wayland* wayland;
extern struct screencopy_impl wlr_screencopy_impl;
extern struct screencopy_impl ext_image_copy_capture_impl;
extern struct screencopy_impl desktop_capture_impl;
struct screencopy* screencopy_create(struct image_source* source,
bool render_cursor)
{
if (image_source_is_desktop(source))
return desktop_capture_impl.create(source, render_cursor);
if (wayland->ext_image_copy_capture_manager_v1 &&
wayland->ext_output_image_capture_source_manager_v1)
return ext_image_copy_capture_impl.create(source, render_cursor);
if (wayland->zwlr_screencopy_manager_v1)
return wlr_screencopy_impl.create(source, render_cursor);
return NULL;
}
struct screencopy* screencopy_create_cursor(struct image_source* source,
struct wl_seat* seat)
{
if (image_source_is_desktop(source))
return desktop_capture_impl.create_cursor(source, seat);
if (wayland->ext_image_copy_capture_manager_v1 &&
wayland->ext_output_image_capture_source_manager_v1)
return ext_image_copy_capture_impl.create_cursor(source, seat);
return NULL;
}
void screencopy_destroy(struct screencopy* self)
{
if (self)
self->impl->destroy(self);
}
int screencopy_start(struct screencopy* self, bool immediate)
{
return self->impl->start(self, immediate);
}
void screencopy_stop(struct screencopy* self)
{
if (self)
self->impl->stop(self);
}
enum screencopy_capabilitites screencopy_get_capabilities(
const struct screencopy* self)
{
if (!self->impl->get_capabilities)
return 0;
return self->impl->get_capabilities(self);
}
any1-wayvnc-ae53f07/src/screencopy.c 0000664 0000000 0000000 00000022055 15223766131 0017422 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2025 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